From 31046990fead6ee11843a38a5272368a224dfe9a Mon Sep 17 00:00:00 2001 From: Ben Berry Date: Thu, 15 Nov 2018 21:31:47 -0800 Subject: [PATCH 0001/1101] Fix typo --- tcod/bsp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/bsp.py b/tcod/bsp.py index c73a414f..d7acea1a 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -1,5 +1,5 @@ ''' -The following example shows how to travrse the BSP tree using Python. This +The following example shows how to traverse the BSP tree using Python. This assumes `create_room` and `connect_rooms` will be replaced by custom code. Example:: From ee0a69bc2e2d41f9ac5d9c3a4044671e87478a78 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 16 Nov 2018 08:52:56 -0800 Subject: [PATCH 0002/1101] Handle cases where frame printing is given an empty string. --- CHANGELOG.rst | 2 ++ tcod/console.py | 16 +++++++++++----- tcod/libtcodpy.py | 13 +++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7e43ca90..c0c4aef1 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Printing a frame with an empty string no longer displays a title bar. 8.1.0 - 2018-11-15 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 250ab818..570e2fe7 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -335,21 +335,23 @@ def vline(self, x, y, height, bg_blend=tcod.libtcod.BKGND_DEFAULT): """ lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) - def print_frame(self, x, y, width, height, string='', - clear=True, bg_blend=tcod.libtcod.BKGND_DEFAULT): + def print_frame(self, x: int, y: int, width: int, height: int, + string: str='', clear: bool=True, + bg_blend: int=tcod.libtcod.BKGND_DEFAULT): """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the rectangle and the default foreground to draw the outline. - string will be printed on the inside of the rectangle, word-wrapped. + `string` will be printed on the inside of the rectangle, word-wrapped. + If `string` is empty then no title will be drawn. Args: x (int): The x coordinate from the left. y (int): The y coordinate from the top. width (int): The width if the frame. height (int): The height of the frame. - string (Text): A Unicode string to print. + string (str): A Unicode string to print. clear (bool): If True all text in the affected area will be removed. bg_blend (int): The background blending flag. @@ -357,8 +359,12 @@ def print_frame(self, x, y, width, height, string='', Note: This method does not support Unicode outside of the 0-255 range. """ + if string: + string = string.encode('latin-1') + else: + string = ffi.NULL lib.TCOD_console_print_frame(self.console_c, x, y, width, height, - clear, bg_blend, string.encode('latin-1')) + clear, bg_blend, string) def blit(self, dest, dest_x=0, dest_y=0, src_x=0, src_y=0, width=0, height=0, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 2c16368a..358ecb5a 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1023,16 +1023,21 @@ def console_vline(con, x, y, l, flag=BKGND_DEFAULT): """ lib.TCOD_console_vline(_console(con), x, y, l, flag) -def console_print_frame(con, x, y, w, h, clear=True, flag=BKGND_DEFAULT, fmt=b''): +def console_print_frame(con, x: int, y: int, w: int, h: int, clear: bool=True, + flag: int=BKGND_DEFAULT, fmt: bytes=b''): """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the rectangle and the default foreground to draw the outline. - fmt will be printed on the inside of the rectangle, word-wrapped. + `fmt` will be printed on the inside of the rectangle, word-wrapped. + If `fmt` is empty then no title will be drawn. """ - lib.TCOD_console_print_frame( - _console(con), x, y, w, h, clear, flag, _fmt_bytes(fmt)) + if fmt: + fmt = _fmt_bytes(fmt) + else: + fmt = ffi.NULL + lib.TCOD_console_print_frame(_console(con), x, y, w, h, clear, flag, fmt) def console_set_color_control(con, fore, back): """Configure :any:`color controls`. From a3e8114b5202f29b3193a0ca98d7bbd60c035e4b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 16 Nov 2018 09:13:37 -0800 Subject: [PATCH 0003/1101] Prepare 8.1.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c0c4aef1..ff79aef2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +8.1.1 - 2018-11-16 +------------------ Fixed - Printing a frame with an empty string no longer displays a title bar. From f885ff54e2872b3265f57f430dfb09b0cfb5e479 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 22 Nov 2018 12:50:20 -0800 Subject: [PATCH 0004/1101] Convert tabs to spaces in Termbox code. --- examples/termbox/termbox.py | 265 ++++++++++++++++---------------- examples/termbox/termboxtest.py | 200 ++++++++++++------------ 2 files changed, 235 insertions(+), 230 deletions(-) diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index 787360b3..1b338a70 100644 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -17,10 +17,10 @@ """ class TermboxException(Exception): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return self.msg + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg _instance = None @@ -43,12 +43,12 @@ def __str__(self): KEY_PGUP = (0xFFFF-16) KEY_PGDN = (0xFFFF-17) -KEY_MOUSE_LEFT =(0xFFFF-22) -KEY_MOUSE_RIGHT =(0xFFFF-23) -KEY_MOUSE_MIDDLE =(0xFFFF-24) -KEY_MOUSE_RELEASE =(0xFFFF-25) -KEY_MOUSE_WHEEL_UP =(0xFFFF-26) -KEY_MOUSE_WHEEL_DOWN =(0xFFFF-27) +KEY_MOUSE_LEFT = (0xFFFF-22) +KEY_MOUSE_RIGHT = (0xFFFF-23) +KEY_MOUSE_MIDDLE = (0xFFFF-24) +KEY_MOUSE_RELEASE = (0xFFFF-25) +KEY_MOUSE_WHEEL_UP = (0xFFFF-26) +KEY_MOUSE_WHEEL_DOWN = (0xFFFF-27) KEY_CTRL_TILDE = 0x00 KEY_CTRL_2 = 0x00 @@ -143,10 +143,10 @@ def __str__(self): # -- mapped to tdl -EVENT_KEY = 'KEYDOWN' +EVENT_KEY = 'KEYDOWN' # /-- -EVENT_RESIZE = 2 -EVENT_MOUSE = 3 +EVENT_RESIZE = 2 +EVENT_MOUSE = 3 class Event: """ Aggregate for Termbox Event structure """ @@ -160,131 +160,136 @@ class Event: mousey = None def gettuple(self): - return (self.type, self.ch, self.key, self.mod, self.width, self.height, self.mousex, self.mousey) + return (self.type, self.ch, self.key, self.mod, self.width, + self.height, self.mousex, self.mousey) class Termbox: - def __init__(self, width=132, height=60): - global _instance - if _instance: - raise TermboxException("It is possible to create only one instance of Termbox") + def __init__(self, width=132, height=60): + global _instance + if _instance: + raise TermboxException( + "It is possible to create only one instance of Termbox") - try: - self.console = tdl.init(width, height) - except tdl.TDLException as e: - raise TermboxException(e) + try: + self.console = tdl.init(width, height) + except tdl.TDLException as e: + raise TermboxException(e) - self.e = Event() # cache for event data + self.e = Event() # cache for event data - _instance = self + _instance = self - def __del__(self): - self.close() + def __del__(self): + self.close() - def __exit__(self, *args):#t, value, traceback): - self.close() + def __exit__(self, *args):#t, value, traceback): + self.close() - def __enter__(self): - return self + def __enter__(self): + return self - def close(self): - global _instance - # tb_shutdown() - _instance = None + def close(self): + global _instance + # tb_shutdown() + _instance = None # TBD, does nothing - def present(self): - """Sync state of the internal cell buffer with the terminal. - """ - tdl.flush() - - def change_cell(self, x, y, ch, fg, bg): - """Change cell in position (x;y). - """ - self.console.draw_char(x, y, ch, fg, bg) - - def width(self): - """Returns width of the terminal screen. - """ - return self.console.width - - def height(self): - """Return height of the terminal screen. - """ - return self.console.height - - def clear(self): - """Clear the internal cell buffer. - """ - self.console.clear() - - def set_cursor(self, x, y): - """Set cursor position to (x;y). - - Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to hide it. - """ - tb_set_cursor(x, y) - - def hide_cursor(self): - """Hide cursor. - """ - tb_set_cursor(-1, -1) - - def select_input_mode(self, mode): - """Select preferred input mode: INPUT_ESC or INPUT_ALT. - - INPUT_CURRENT returns the selected mode without changing anything. - """ - return int(tb_select_input_mode(mode)) - - def select_output_mode(self, mode): - """Select preferred output mode: one of OUTPUT_* constants. - - OUTPUT_CURRENT returns the selected mode without changing anything. - """ - return int(tb_select_output_mode(mode)) - - def peek_event(self, timeout=0): - """Wait for an event up to 'timeout' milliseconds and return it. - - Returns None if there was no event and timeout is expired. - Returns a tuple otherwise: (type, unicode character, key, mod, width, height, mousex, mousey). - """ - """ - cdef tb_event e - with self._poll_lock: - with nogil: - result = tb_peek_event(&e, timeout) - assert(result >= 0) - if result == 0: - return None - if e.ch: - uch = unichr(e.ch) - else: - uch = None - """ - pass #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) - - def poll_event(self): - """Wait for an event and return it. - - Returns a tuple: (type, unicode character, key, mod, width, height, mousex, mousey). - """ - """ - cdef tb_event e - with self._poll_lock: - with nogil: - result = tb_poll_event(&e) - assert(result >= 0) - if e.ch: - uch = unichr(e.ch) - else: - uch = None - """ - for e in tdl.event.get(): - # [ ] not all events are passed thru - self.e.type = e.type - if e.type == 'KEYDOWN': - self.e.key = e.key - return self.e.gettuple() - - #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) + def present(self): + """Sync state of the internal cell buffer with the terminal. + """ + tdl.flush() + + def change_cell(self, x, y, ch, fg, bg): + """Change cell in position (x;y). + """ + self.console.draw_char(x, y, ch, fg, bg) + + def width(self): + """Returns width of the terminal screen. + """ + return self.console.width + + def height(self): + """Return height of the terminal screen. + """ + return self.console.height + + def clear(self): + """Clear the internal cell buffer. + """ + self.console.clear() + + def set_cursor(self, x, y): + """Set cursor position to (x;y). + + Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to + hide it. + """ + tb_set_cursor(x, y) + + def hide_cursor(self): + """Hide cursor. + """ + tb_set_cursor(-1, -1) + + def select_input_mode(self, mode): + """Select preferred input mode: INPUT_ESC or INPUT_ALT. + + INPUT_CURRENT returns the selected mode without changing anything. + """ + return int(tb_select_input_mode(mode)) + + def select_output_mode(self, mode): + """Select preferred output mode: one of OUTPUT_* constants. + + OUTPUT_CURRENT returns the selected mode without changing anything. + """ + return int(tb_select_output_mode(mode)) + + def peek_event(self, timeout=0): + """Wait for an event up to 'timeout' milliseconds and return it. + + Returns None if there was no event and timeout is expired. + Returns a tuple otherwise: (type, unicode character, key, mod, + width, height, mousex, mousey). + """ + """ + cdef tb_event e + with self._poll_lock: + with nogil: + result = tb_peek_event(&e, timeout) + assert(result >= 0) + if result == 0: + return None + if e.ch: + uch = unichr(e.ch) + else: + uch = None + """ + pass #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) + + def poll_event(self): + """Wait for an event and return it. + + Returns a tuple: (type, unicode character, key, mod, width, height, + mousex, mousey). + """ + """ + cdef tb_event e + with self._poll_lock: + with nogil: + result = tb_poll_event(&e) + assert(result >= 0) + if e.ch: + uch = unichr(e.ch) + else: + uch = None + """ + for e in tdl.event.get(): + # [ ] not all events are passed thru + self.e.type = e.type + if e.type == 'KEYDOWN': + self.e.key = e.key + return self.e.gettuple() + + #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py index f9585feb..f51e5079 100644 --- a/examples/termbox/termboxtest.py +++ b/examples/termbox/termboxtest.py @@ -12,118 +12,118 @@ spaceord = ord(u" ") def print_line(t, msg, y, fg, bg): - w = t.width() - l = len(msg) - x = 0 - for i in range(w): - c = spaceord - if i < l: - c = ord(msg[i]) - t.change_cell(x+i, y, c, fg, bg) + w = t.width() + l = len(msg) + x = 0 + for i in range(w): + c = spaceord + if i < l: + c = ord(msg[i]) + t.change_cell(x+i, y, c, fg, bg) class SelectBox(object): - def __init__(self, tb, choices, active=-1): - self.tb = tb - self.active = active - self.choices = choices - self.color_active = (termbox.BLACK, termbox.CYAN) - self.color_normal = (termbox.WHITE, termbox.BLACK) + def __init__(self, tb, choices, active=-1): + self.tb = tb + self.active = active + self.choices = choices + self.color_active = (termbox.BLACK, termbox.CYAN) + self.color_normal = (termbox.WHITE, termbox.BLACK) - def draw(self): - for i, c in enumerate(self.choices): - color = self.color_normal - if i == self.active: - color = self.color_active - print_line(self.tb, c, i, *color) + def draw(self): + for i, c in enumerate(self.choices): + color = self.color_normal + if i == self.active: + color = self.color_active + print_line(self.tb, c, i, *color) - def validate_active(self): - if self.active < 0: - self.active = 0 - if self.active >= len(self.choices): - self.active = len(self.choices)-1 + def validate_active(self): + if self.active < 0: + self.active = 0 + if self.active >= len(self.choices): + self.active = len(self.choices)-1 - def set_active(self, i): - self.active = i - self.validate_active() + def set_active(self, i): + self.active = i + self.validate_active() - def move_up(self): - self.active -= 1 - self.validate_active() + def move_up(self): + self.active -= 1 + self.validate_active() - def move_down(self): - self.active += 1 - self.validate_active() + def move_down(self): + self.active += 1 + self.validate_active() choices = [ - u"This instructs Psyco", - u"to compile and run as", - u"much of your application", - u"code as possible. This is the", - u"simplest interface to Psyco.", - u"In good cases you can just add", - u"these two lines and enjoy the speed-up.", - u"If your application does a lot", - u"of initialization stuff before", - u"the real work begins, you can put", - u"the above two lines after this", - u"initialization - e.g. after importing", - u"modules, creating constant global objects, etc.", - u"This instructs Psyco", - u"to compile and run as", - u"much of your application", - u"code as possible. This is the", - u"simplest interface to Psyco.", - u"In good cases you can just add", - u"these two lines and enjoy the speed-up.", - u"If your application does a lot", - u"of initialization stuff before", - u"the real work begins, you can put", - u"the above two lines after this", - u"initialization - e.g. after importing", - u"modules, creating constant global objects, etc." + u"This instructs Psyco", + u"to compile and run as", + u"much of your application", + u"code as possible. This is the", + u"simplest interface to Psyco.", + u"In good cases you can just add", + u"these two lines and enjoy the speed-up.", + u"If your application does a lot", + u"of initialization stuff before", + u"the real work begins, you can put", + u"the above two lines after this", + u"initialization - e.g. after importing", + u"modules, creating constant global objects, etc.", + u"This instructs Psyco", + u"to compile and run as", + u"much of your application", + u"code as possible. This is the", + u"simplest interface to Psyco.", + u"In good cases you can just add", + u"these two lines and enjoy the speed-up.", + u"If your application does a lot", + u"of initialization stuff before", + u"the real work begins, you can put", + u"the above two lines after this", + u"initialization - e.g. after importing", + u"modules, creating constant global objects, etc." ] def draw_bottom_line(t, i): - i = i % 8 - w = t.width() - h = t.height() - c = i - palette = [termbox.DEFAULT, termbox.BLACK, termbox.RED, termbox.GREEN, - termbox.YELLOW, termbox.BLUE, termbox.MAGENTA, termbox.CYAN, - termbox.WHITE] - for x in range(w): - t.change_cell(x, h-1, ord(u' '), termbox.BLACK, palette[c]) - t.change_cell(x, h-2, ord(u' '), termbox.BLACK, palette[c]) - c += 1 - if c > 7: - c = 0 + i = i % 8 + w = t.width() + h = t.height() + c = i + palette = [termbox.DEFAULT, termbox.BLACK, termbox.RED, termbox.GREEN, + termbox.YELLOW, termbox.BLUE, termbox.MAGENTA, termbox.CYAN, + termbox.WHITE] + for x in range(w): + t.change_cell(x, h-1, ord(u' '), termbox.BLACK, palette[c]) + t.change_cell(x, h-2, ord(u' '), termbox.BLACK, palette[c]) + c += 1 + if c > 7: + c = 0 with termbox.Termbox() as t: - sb = SelectBox(t, choices, 0) - t.clear() - sb.draw() - t.present() - i = 0 - run_app = True - while run_app: - event_here = t.poll_event() - while event_here: - (type, ch, key, mod, w, h, x, y) = event_here - if type == termbox.EVENT_KEY and key == termbox.KEY_ESC: - run_app = False - if type == termbox.EVENT_KEY: - if key == termbox.KEY_ARROW_DOWN: - sb.move_down() - elif key == termbox.KEY_ARROW_UP: - sb.move_up() - elif key == termbox.KEY_HOME: - sb.set_active(-1) - elif key == termbox.KEY_END: - sb.set_active(999) - event_here = t.peek_event() + sb = SelectBox(t, choices, 0) + t.clear() + sb.draw() + t.present() + i = 0 + run_app = True + while run_app: + event_here = t.poll_event() + while event_here: + (type, ch, key, mod, w, h, x, y) = event_here + if type == termbox.EVENT_KEY and key == termbox.KEY_ESC: + run_app = False + if type == termbox.EVENT_KEY: + if key == termbox.KEY_ARROW_DOWN: + sb.move_down() + elif key == termbox.KEY_ARROW_UP: + sb.move_up() + elif key == termbox.KEY_HOME: + sb.set_active(-1) + elif key == termbox.KEY_END: + sb.set_active(999) + event_here = t.peek_event() - t.clear() - sb.draw() - draw_bottom_line(t, i) - t.present() - i += 1 + t.clear() + sb.draw() + draw_bottom_line(t, i) + t.present() + i += 1 From 9ddce82039f4d19155ceee94571e288058e54296 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 23 Nov 2018 11:34:24 -0800 Subject: [PATCH 0005/1101] Resolve more style issues with Termbox. --- examples/termbox/termbox.py | 16 ++++++++-------- examples/termbox/termboxtest.py | 6 ------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index 1b338a70..db4c9813 100644 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -160,8 +160,8 @@ class Event: mousey = None def gettuple(self): - return (self.type, self.ch, self.key, self.mod, self.width, - self.height, self.mousex, self.mousey) + return (self.type, self.ch, self.key, self.mod, self.width, + self.height, self.mousex, self.mousey) class Termbox: def __init__(self, width=132, height=60): @@ -192,7 +192,7 @@ def close(self): global _instance # tb_shutdown() _instance = None - # TBD, does nothing + # TBD, does nothing def present(self): """Sync state of the internal cell buffer with the terminal. @@ -286,10 +286,10 @@ def poll_event(self): uch = None """ for e in tdl.event.get(): - # [ ] not all events are passed thru - self.e.type = e.type - if e.type == 'KEYDOWN': - self.e.key = e.key - return self.e.gettuple() + # [ ] not all events are passed thru + self.e.type = e.type + if e.type == 'KEYDOWN': + self.e.key = e.key + return self.e.gettuple() #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py index f51e5079..4020e49a 100644 --- a/examples/termbox/termboxtest.py +++ b/examples/termbox/termboxtest.py @@ -2,12 +2,6 @@ # -*- encoding: utf-8 -*- import termbox -import time -import sys -import random -#import psyco - -#psyco.full() spaceord = ord(u" ") From 6be708505a32ec43fec790e60c3eae6c97d07ef9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 23 Nov 2018 11:36:53 -0800 Subject: [PATCH 0006/1101] Add code-style guidelines to CONTRIBUTING file. --- CONTRIBUTING.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b8aaa03..7f9dfa50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,15 @@ +# Code style +New and refactored Python code should follow the +[PEP 8](https://www.python.org/dev/peps/pep-0008/) guidelines. -# Building tdl +It's recommended to use an editor supporting +[EditorConfig](https://editorconfig.org/). -To work with the tdl source, your environment must be set up to build +# Building python-tcod + +To work with the tcod source, your environment must be set up to build Python C extensions. You'll also need `cpp` installed for use with pycparser. @@ -11,14 +17,14 @@ use with pycparser. - Install [Microsoft Visual Studio](https://www.visualstudio.com/vs/community/) -- When asked, choose to install the Python development tools. -- Install [MinGW](http://www.mingw.org/). --- Installer is [here](https://sourceforge.net/projects/mingw/files/latest/download). --- Add the binary folder (default folder is `C:\MinGW\bin`) to your user +- Install [MinGW](http://www.mingw.org/) or [MSYS2](https://www.msys2.org/). +-- The MinGW installer is [here](https://sourceforge.net/projects/mingw/files/latest/download). +-- Add the binary folder (default MinGW folder is `C:\MinGW\bin`) to your user environment PATH variable. - Open a command prompt in the cloned git directory. - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` -- Install an editable version of tdl with this command: +- Install an editable version of tcod with this command: `py -m pip install --editable . --verbose` ## MacOS @@ -28,14 +34,14 @@ use with pycparser. `xcode-select --install` - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` -- Install an editable version of tdl with this command: +- Install an editable version of tcod with this command: `pip install --editable . --verbose` ## Linux - Open a command prompt in the cloned git directory. - Assuming a Debian based distribution of Linux. - Install tdl's dependancies with this command: + Install tcod's dependancies with this command: `sudo apt install gcc python-dev libsdl2-dev libffi-dev libomp-dev` - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` From 113ed6840c762dd91b7fef8a0eab924e4031f7fc Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 23 Nov 2018 13:31:36 -0800 Subject: [PATCH 0007/1101] Fix markdown formatting. --- CONTRIBUTING.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f9dfa50..fd2a4fec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,4 @@ - -# Code style +## Code style New and refactored Python code should follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) guidelines. @@ -7,27 +6,27 @@ New and refactored Python code should follow the It's recommended to use an editor supporting [EditorConfig](https://editorconfig.org/). -# Building python-tcod +## Building python-tcod To work with the tcod source, your environment must be set up to build Python C extensions. You'll also need `cpp` installed for use with pycparser. -## Windows +### Windows - Install [Microsoft Visual Studio](https://www.visualstudio.com/vs/community/) --- When asked, choose to install the Python development tools. + - When asked, choose to install the Python development tools. - Install [MinGW](http://www.mingw.org/) or [MSYS2](https://www.msys2.org/). --- The MinGW installer is [here](https://sourceforge.net/projects/mingw/files/latest/download). --- Add the binary folder (default MinGW folder is `C:\MinGW\bin`) to your user - environment PATH variable. + - The MinGW installer is [here](https://sourceforge.net/projects/mingw/files/latest/download). + - Add the binary folder (default MinGW folder is `C:\MinGW\bin`) to your user + environment PATH variable. - Open a command prompt in the cloned git directory. - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` - Install an editable version of tcod with this command: `py -m pip install --editable . --verbose` -## MacOS +### MacOS - Open a command prompt in the cloned git directory. - Install the Xcode command line tools with this command: @@ -37,7 +36,7 @@ use with pycparser. - Install an editable version of tcod with this command: `pip install --editable . --verbose` -## Linux +### Linux - Open a command prompt in the cloned git directory. - Assuming a Debian based distribution of Linux. From 3653f2a64bcbf82910e3d7d682b95a6f5298c378 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 26 Nov 2018 18:07:24 -0800 Subject: [PATCH 0008/1101] Update libtcod to 1.10.2 --- CHANGELOG.rst | 4 ++++ build_libtcod.py | 1 - libtcod | 2 +- tcod/constants.py | 1 + tcod/libtcodpy.py | 14 ++++++++++---- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ff79aef2..3464ed95 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,10 @@ v2.0.0 Unreleased ------------------ +Added + - New layout `tcod.FONT_LAYOUT_CP437`. +Changed + - Updated libtcod to 1.10.2 8.1.1 - 2018-11-16 ------------------ diff --git a/build_libtcod.py b/build_libtcod.py index 5d0e3b76..94b05386 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -345,5 +345,4 @@ def write_library_constants(): if __name__ == "__main__": - ffi.compile() write_library_constants() diff --git a/libtcod b/libtcod index f1089129..64388d72 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit f1089129890bbf2d84abed08b73874c129d6ec6f +Subproject commit 64388d72fea2855a27619155898cd444c9733358 diff --git a/tcod/constants.py b/tcod/constants.py index b559c440..ce3592d1 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -242,6 +242,7 @@ EVENT_NONE = 0 FONT_LAYOUT_ASCII_INCOL = 1 FONT_LAYOUT_ASCII_INROW = 2 +FONT_LAYOUT_CP437 = 16 FONT_LAYOUT_TCOD = 8 FONT_TYPE_GRAYSCALE = 4 FONT_TYPE_GREYSCALE = 4 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 358ecb5a..25c8ed36 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -666,11 +666,17 @@ def console_set_custom_font(fontFile: AnyStr, flags: Flags can be a mix of the following: - * tcod.FONT_LAYOUT_ASCII_INCOL - * tcod.FONT_LAYOUT_ASCII_INROW - * tcod.FONT_TYPE_GREYSCALE + * tcod.FONT_LAYOUT_ASCII_INCOL: + Decode tileset raw in column-major order. + * tcod.FONT_LAYOUT_ASCII_INROW: + Decode tileset raw in row-major order. + * tcod.FONT_TYPE_GREYSCALE: + Force tileset to be read as greyscale. * tcod.FONT_TYPE_GRAYSCALE - * tcod.FONT_LAYOUT_TCOD + * tcod.FONT_LAYOUT_TCOD: + Unique layout used by libtcod. + * tcod.FONT_LAYOUT_CP437: + Decode a row-major Code Page 437 tileset into Unicode. `nb_char_horiz` and `nb_char_vertic` are the columns and rows of the font file respectfully. From 2f19f7ef1576f773205f85da81dd62983e454964 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 27 Nov 2018 15:27:34 -0800 Subject: [PATCH 0009/1101] Convert print functions from wchar to utf8. This also adds Unicode support to print frame functions. --- CHANGELOG.rst | 4 ++++ tcod/console.py | 59 ++++++++++++++++++++++------------------------- tcod/libtcodpy.py | 56 +++++++++++++++++++++++++------------------- tcod/tcod.py | 12 ++++++++++ 4 files changed, 75 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3464ed95..0ebfd1f2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,10 @@ Added - New layout `tcod.FONT_LAYOUT_CP437`. Changed - Updated libtcod to 1.10.2 + - `tcod.console_print_frame` and `Console.print_frame` now support Unicode + strings. +Deprecated + - Deprecated using bytes strings for all printing functions. 8.1.1 - 2018-11-16 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 570e2fe7..57685a2c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -27,6 +27,7 @@ import sys +from typing import Optional import warnings import numpy as np @@ -35,15 +36,9 @@ from tcod.libtcod import ffi, lib import tcod._internal -if sys.version_info[0] == 2: # Python 2 - def _fmt(string): - if not isinstance(string, unicode): - string = string.decode('latin-1') - return string.replace(u'%', u'%%') -else: - def _fmt(string): - """Return a string that escapes 'C printf' side effects.""" - return string.replace('%', '%%') +def _fmt(string: str) -> bytes: + """Return a string that escapes 'C printf' side effects.""" + return string.encode('utf-8').replace(b'%', b'%%') _root_console = None @@ -235,24 +230,26 @@ def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT): """ lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend) - def print_(self, x, y, string, bg_blend=tcod.libtcod.BKGND_DEFAULT, - alignment=None): + def print_(self, x: int, y: int, string: str, + bg_blend: int=tcod.libtcod.BKGND_DEFAULT, + alignment: Optional[int]=None) -> None: """Print a color formatted string on a console. Args: x (int): The x coordinate from the left. y (int): The y coordinate from the top. - string (Text): A Unicode string optionaly using color codes. + string (str): A Unicode string optionaly using color codes. bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT. - alignment (Optinal[int]): Text alignment. + alignment (Optional[int]): Text alignment. """ alignment = self.default_alignment if alignment is None else alignment - lib.TCOD_console_print_ex_utf(self.console_c, x, y, - bg_blend, alignment, _fmt(string)) + lib.TCOD_console_printf_ex(self.console_c, x, y, + bg_blend, alignment, _fmt(string)) - def print_rect(self, x, y, width, height, string, - bg_blend=tcod.libtcod.BKGND_DEFAULT, alignment=None): + def print_rect(self, x: int, y: int, width: int, height:int, string: str, + bg_blend: int=tcod.libtcod.BKGND_DEFAULT, + alignment: Optional[int]=None) -> int: """Print a string constrained to a rectangle. If h > 0 and the bottom of the rectangle is reached, @@ -264,7 +261,7 @@ def print_rect(self, x, y, width, height, string, y (int): The y coordinate from the top. width (int): Maximum width to render the text. height (int): Maximum lines to render the text. - string (Text): A Unicode string. + string (str): A Unicode string. bg_blend (int): Background blending flag. alignment (Optional[int]): Alignment flag. @@ -272,10 +269,11 @@ def print_rect(self, x, y, width, height, string, int: The number of lines of text once word-wrapped. """ alignment = self.default_alignment if alignment is None else alignment - return lib.TCOD_console_print_rect_ex_utf(self.console_c, + return lib.TCOD_console_printf_rect_ex(self.console_c, x, y, width, height, bg_blend, alignment, _fmt(string)) - def get_height_rect(self, x, y, width, height, string): + def get_height_rect(self, x: int, y: int, width: int, height: int, + string: str) -> int: """Return the height of this text word-wrapped into this rectangle. Args: @@ -283,16 +281,16 @@ def get_height_rect(self, x, y, width, height, string): y (int): The y coordinate from the top. width (int): Maximum width to render the text. height (int): Maximum lines to render the text. - string (Text): A Unicode string. + string (str): A Unicode string. Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_get_height_rect_utf( + return lib.TCOD_console_get_height_rect_fmt( self.console_c, x, y, width, height, _fmt(string)) - def rect(self, x, y, width, height, clear, - bg_blend=tcod.libtcod.BKGND_DEFAULT): + def rect(self, x: int, y: int, width: int, height: int, clear: bool, + bg_blend: int=tcod.libtcod.BKGND_DEFAULT) -> None: """Draw a the background color on a rect optionally clearing the text. If clr is True the affected tiles are changed to space character. @@ -356,15 +354,12 @@ def print_frame(self, x: int, y: int, width: int, height: int, removed. bg_blend (int): The background blending flag. - Note: - This method does not support Unicode outside of the 0-255 range. + .. versionchanged:: 8.2 + Now supports Unicode strings. """ - if string: - string = string.encode('latin-1') - else: - string = ffi.NULL - lib.TCOD_console_print_frame(self.console_c, x, y, width, height, - clear, bg_blend, string) + string = _fmt(string) if string else ffi.NULL + lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, + clear, bg_blend, string) def blit(self, dest, dest_x=0, dest_y=0, src_x=0, src_y=0, width=0, height=0, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 25c8ed36..84d04537 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -18,7 +18,7 @@ from tcod._internal import deprecate from tcod.tcod import _int, _unpack_char_p -from tcod.tcod import _bytes, _unicode, _fmt_bytes, _fmt_unicode +from tcod.tcod import _bytes, _unicode, _fmt_bytes, _fmt_unicode, _fmt from tcod.tcod import _CDataWrapper from tcod.tcod import _PropagateException from tcod.tcod import _console @@ -953,7 +953,7 @@ def console_get_alignment(con): """ return lib.TCOD_console_get_alignment(_console(con)) -def console_print(con, x, y, fmt): +def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: """Print a color formatted string on a console. Args: @@ -962,9 +962,10 @@ def console_print(con, x, y, fmt): y (int): Character y position from the top. fmt (AnyStr): A unicode or bytes string optionaly using color codes. """ - lib.TCOD_console_print(_console(con), x, y, _fmt_unicode(fmt)) + lib.TCOD_console_printf(_console(con), x, y, _fmt(fmt)) -def console_print_ex(con, x, y, flag, alignment, fmt): +def console_print_ex(con: tcod.console.Console, x: int, y: int, flag: int, + alignment: int, fmt: str) -> None: """Print a string on a console using a blend mode and alignment mode. Args: @@ -972,10 +973,11 @@ def console_print_ex(con, x, y, flag, alignment, fmt): x (int): Character x position from the left. y (int): Character y position from the top. """ - lib.TCOD_console_print_ex(_console(con), - x, y, flag, alignment, _fmt_unicode(fmt)) + lib.TCOD_console_printf_ex(_console(con), + x, y, flag, alignment, _fmt(fmt)) -def console_print_rect(con, x, y, w, h, fmt): +def console_print_rect(con: tcod.console.Console, + x: int, y: int, w: int, h: int, fmt: str) -> int: """Print a string constrained to a rectangle. If h > 0 and the bottom of the rectangle is reached, @@ -987,42 +989,48 @@ def console_print_rect(con, x, y, w, h, fmt): Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_print_rect( - _console(con), x, y, w, h, _fmt_unicode(fmt)) + return lib.TCOD_console_printf_rect( + _console(con), x, y, w, h, _fmt(fmt)) -def console_print_rect_ex(con, x, y, w, h, flag, alignment, fmt): +def console_print_rect_ex(con: tcod.console.Console, + x: int, y: int, w: int, h: int, + flag: int, alignment: int, fmt: str) -> int: """Print a string constrained to a rectangle with blend and alignment. Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_print_rect_ex( - _console(con), x, y, w, h, flag, alignment, _fmt_unicode(fmt)) + return lib.TCOD_console_printf_rect_ex( + _console(con), x, y, w, h, flag, alignment, _fmt(fmt)) -def console_get_height_rect(con, x, y, w, h, fmt): +def console_get_height_rect(con: tcod.console.Console, + x: int, y: int, w: int, h: int, fmt: str) -> int: """Return the height of this text once word-wrapped into this rectangle. Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_get_height_rect( - _console(con), x, y, w, h, _fmt_unicode(fmt)) + return lib.TCOD_console_get_height_rect_fmt( + _console(con), x, y, w, h, _fmt(fmt)) -def console_rect(con, x, y, w, h, clr, flag=BKGND_DEFAULT): +def console_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, + clr: bool, flag: int=BKGND_DEFAULT) -> None: """Draw a the background color on a rect optionally clearing the text. If clr is True the affected tiles are changed to space character. """ lib.TCOD_console_rect(_console(con), x, y, w, h, clr, flag) -def console_hline(con, x, y, l, flag=BKGND_DEFAULT): +def console_hline(con: tcod.console.Console, x: int, y: int, l: int, + flag: int=BKGND_DEFAULT) -> None: """Draw a horizontal line on the console. This always uses the character 196, the horizontal line character. """ lib.TCOD_console_hline(_console(con), x, y, l, flag) -def console_vline(con, x, y, l, flag=BKGND_DEFAULT): +def console_vline(con: tcod.console.Console, x: int, y: int, l: int, + flag: int=BKGND_DEFAULT): """Draw a vertical line on the console. This always uses the character 179, the vertical line character. @@ -1030,7 +1038,7 @@ def console_vline(con, x, y, l, flag=BKGND_DEFAULT): lib.TCOD_console_vline(_console(con), x, y, l, flag) def console_print_frame(con, x: int, y: int, w: int, h: int, clear: bool=True, - flag: int=BKGND_DEFAULT, fmt: bytes=b''): + flag: int=BKGND_DEFAULT, fmt: str='') -> None: """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the @@ -1038,12 +1046,12 @@ def console_print_frame(con, x: int, y: int, w: int, h: int, clear: bool=True, `fmt` will be printed on the inside of the rectangle, word-wrapped. If `fmt` is empty then no title will be drawn. + + .. versionchanged:: 8.2 + Now supports Unicode strings. """ - if fmt: - fmt = _fmt_bytes(fmt) - else: - fmt = ffi.NULL - lib.TCOD_console_print_frame(_console(con), x, y, w, h, clear, flag, fmt) + fmt = _fmt(fmt) if fmt else ffi.NULL + lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) def console_set_color_control(con, fore, back): """Configure :any:`color controls`. diff --git a/tcod/tcod.py b/tcod/tcod.py index b8fd80c8..35b42ce7 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -47,6 +47,18 @@ def _fmt_bytes(string: AnyStr) -> bytes: def _fmt_unicode(string: AnyStr) -> str: return _unicode(string, stacklevel=3).encode('utf-8').replace(b'%', b'%%') +def _fmt(string: str, stacklevel: int=2) -> bytes: + if isinstance(string, bytes): + warnings.warn( + ("Passing byte strings as parameters to Unicode functions is " + "deprecated."), + DeprecationWarning, + stacklevel=stacklevel + 1, + ) + string = string.decode('latin-1') + return string.encode('utf-8').replace(b'%', b'%%') + + class _PropagateException(): """ context manager designed to propagate exceptions outside of a cffi From 6c5b2f5e35fdbe8202b6fb82a7ad11fbaf48b91b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 27 Nov 2018 15:32:49 -0800 Subject: [PATCH 0010/1101] Fix bad initialization of console objects. --- CHANGELOG.rst | 3 +++ tcod/console.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0ebfd1f2..31a33a1f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,9 @@ Changed strings. Deprecated - Deprecated using bytes strings for all printing functions. +Fixed + - Console objects are now initialized with spaces. This fixes some blit + operations. 8.1.1 - 2018-11-16 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 57685a2c..9e7d8713 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -60,7 +60,7 @@ class Console(object): def __init__(self, width, height, order='C'): self._key_color = None - self._ch = np.zeros((height, width), dtype=np.intc) + self._ch = np.full((height, width), 0x20, dtype=np.intc) self._fg = np.zeros((height, width), dtype='(3,)u1') self._bg = np.zeros((height, width), dtype='(3,)u1') self._order = tcod._internal.verify_order(order) From 6dcd15acf4e698bf37274053998227ffa0f84c56 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 27 Nov 2018 15:39:15 -0800 Subject: [PATCH 0011/1101] Use random instead of time to generate a default seed. --- tcod/random.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/random.py b/tcod/random.py index ed9beb0d..d82905d9 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -4,7 +4,7 @@ from __future__ import absolute_import as _ -import time +import random from tcod.libtcod import ffi, lib from tcod.libtcod import RNG_MT as MERSENNE_TWISTER @@ -30,7 +30,7 @@ class Random(object): def __init__(self, algorithm, seed=None): """Create a new instance using this algorithm and seed.""" if seed is None: - seed = time.time() + time.clock() + seed = random.getrandbits(32) self.random_c = ffi.gc( ffi.cast('mersenne_data_t*', lib.TCOD_random_new_from_seed(algorithm, From 81325306390d7db946e4178c5ae8365d91b5b79f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 27 Nov 2018 15:56:02 -0800 Subject: [PATCH 0012/1101] Prepare 8.2.0 release. --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31a33a1f..da48e7f5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +8.2.0 - 2018-11-27 +------------------ Added - New layout `tcod.FONT_LAYOUT_CP437`. Changed @@ -20,6 +23,7 @@ Deprecated Fixed - Console objects are now initialized with spaces. This fixes some blit operations. + - Unicode code-points above U+FFFF will now work on all platforms. 8.1.1 - 2018-11-16 ------------------ From b13cb1c1170d40f6738919d112d118def2431967 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 6 Dec 2018 02:52:44 -0800 Subject: [PATCH 0013/1101] Add traversal methods to BSP, deprecate old functions. --- CHANGELOG.rst | 4 ++ tcod/bsp.py | 156 ++++++++++++++++++++++------------------ tcod/libtcodpy.py | 35 ++++++--- tests/test_libtcodpy.py | 7 ++ 4 files changed, 123 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index da48e7f5..7ca18a7c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,10 @@ v2.0.0 Unreleased ------------------ +Added + - Added BSP traversal methods in tcod.bsp to gain parity with libtcodpy. +Deprecated + - Already deprecated bsp functions are now even more deprecated. 8.2.0 - 2018-11-27 ------------------ diff --git a/tcod/bsp.py b/tcod/bsp.py index d7acea1a..01264d0e 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -1,4 +1,4 @@ -''' +""" The following example shows how to traverse the BSP tree using Python. This assumes `create_room` and `connect_rooms` will be replaced by custom code. @@ -6,26 +6,6 @@ import tcod.bsp - def create_room(node): - """Initialize the room at this current node.""" - print('Create a room for %s.' % node) - - def connect_rooms(node): - """Connect two fully initialized rooms.""" - node1, node2 = node.children - print('Connect the rooms:\\n%s\\n%s' % (node1, node2)) - - def traverse(node): - """Traverse a BSP tree dispatching nodes to the correct calls.""" - # For nodes without children, node.children is an empty tuple. - for child in node.children: - traverse(child) - - if node.children: - connect_rooms(node) - else: - create_room(node) - bsp = tcod.bsp.BSP(x=0, y=0, width=80, height=60) bsp.split_recursive( depth=5, @@ -33,12 +13,20 @@ def traverse(node): min_height=3, max_horizontal_ratio=1.5, max_vertical_ratio=1.5, - ) - traverse(bsp) -''' -from __future__ import absolute_import as _ + ) + + # In pre order, leaf nodes are visited before the nodes that connect them. + for node in bsp.pre_order(): + if node.children: + node1, node2 = node.children + print('Connect the rooms:\\n%s\\n%s' % (node1, node2)) + else: + print('Dig a room for %s.' % node) +""" +from typing import Iterator, Optional, Union, Tuple, List from tcod.libtcod import lib, ffi +from tcod._internal import deprecate class BSP(object): @@ -65,31 +53,31 @@ class BSP(object): height (int): Rectangle height. """ - def __init__(self, x, y, width, height): - self.x = x - self.y = y - self.width = width - self.height = height + def __init__(self, x: int, y: int, width: int, height: int): + self.x = x # type: int + self.y = y # type: int + self.width = width # type: int + self.height = height # type: int - self.level = 0 - self.position = 0 - self.horizontal = False + self.level = 0 # type: int + self.position = 0 # type: int + self.horizontal = False # type: bool - self.parent = None - self.children = () + self.parent = None # type: Optional['BSP'] + self.children = () # type: Union[Tuple[], Tuple['BSP', 'BSP']] @property - def w(self): + def w(self) -> int: return self.width @w.setter - def w(self, value): + def w(self, value: int): self.width = value @property - def h(self): + def h(self) -> int: return self.height @h.setter - def h(self, value): + def h(self, value: int): self.height = value def _as_cdata(self): @@ -126,7 +114,7 @@ def _unpack_bsp_tree(self, cdata): self.children[1].parent = self self.children[1]._unpack_bsp_tree(lib.TCOD_bsp_right(cdata)) - def split_once(self, horizontal, position): + def split_once(self, horizontal: bool, position: int): """Split this partition into 2 sub-partitions. Args: @@ -162,50 +150,75 @@ def split_recursive(self, depth, min_width, min_height, ) self._unpack_bsp_tree(cdata) - def walk(self): - """Iterate over this BSP's hieracrhy. - - The iterator will include the instance which called it. - It will traverse its own children and grandchildren, in no particular - order. - - Returns: - Iterator[BSP]: An iterator of BSP nodes. + @deprecate('Use pre_order method instead of walk.') + def walk(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in pre order. .. deprecated:: 2.3 - See the module example for how to iterate over a BSP tree. + Use :any:`pre_order` instead. """ - return self._iter_post_order() + return self.post_order() + + def pre_order(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in pre order. - def _iter_pre_order(self): + .. versionadded:: 8.3 + """ yield self for child in self.children: - for grandchild in child._iter_pre_order(): - yield grandchild + yield from child.pre_order() - def _iter_in_order(self): + def in_order(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in order. + + .. versionadded:: 8.3 + """ if self.children: - for grandchild in self.children[0]._iter_in_order(): - yield grandchild + yield from self.children[0].in_order() yield self - for grandchild in self.children[1]._iter_in_order(): - yield grandchild + yield from self.children[1].in_order() else: yield self - def _iter_post_order(self): + def post_order(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in post order. + + .. versionadded:: 8.3 + """ for child in self.children: - for grandchild in child._iter_post_order(): - yield grandchild + yield from child.post_order() yield self - def _iter_level_order(self): - return sorted(self._iter_pre_order(), key=lambda n:n.level) - - def _iter_inverted_level_order(self): - return reversed(self._iter_level_order()) + def level_order(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in level order. - def contains(self, x, y): + .. versionadded:: 8.3 + """ + next = [self] # type: List['BSP'] + while next: + level = next # type: List['BSP'] + next = [] + yield from level + for node in level: + next.extend(node.children) + + def inverted_level_order(self) -> Iterator['BSP']: + """Iterate over this BSP's hierarchy in inverse level order. + + .. versionadded:: 8.3 + """ + levels = [[self]] # type: List[List['BSP']] + next = [self] # type: List['BSP'] + while next: + level = next # type: List['BSP'] + next = [] + for node in level: + next.extend(node.children) + levels.append(next) + for level in levels[::-1]: + yield from level + + def contains(self, x: int, y: int) -> bool: """Returns True if this node contains these coordinates. Args: @@ -219,7 +232,7 @@ def contains(self, x, y): return (self.x <= x < self.x + self.width and self.y <= y < self.y + self.height) - def find_node(self, x, y): + def find_node(self, x: int, y: int) -> Optional['BSP']: """Return the deepest node which contains these coordinates. Returns: @@ -232,3 +245,8 @@ def find_node(self, x, y): if found: return found return self + + +__all__ = [ + "BSP", +] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 84d04537..a162d8b8 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -401,6 +401,7 @@ def bsp_split_recursive(node, randomizer, nb, minHSize, minVSize, maxHRatio, node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer) +@deprecate("Assign values via attribute instead.") def bsp_resize(node, x, y, w, h): """ .. deprecated:: 2.0 @@ -411,6 +412,7 @@ def bsp_resize(node, x, y, w, h): node.width = w node.height = h +@deprecate("Access children with 'node.children' instead.") def bsp_left(node): """ .. deprecated:: 2.0 @@ -418,6 +420,7 @@ def bsp_left(node): """ return None if not node.children else node.children[0] +@deprecate("Access children with 'node.children' instead.") def bsp_right(node): """ .. deprecated:: 2.0 @@ -425,6 +428,7 @@ def bsp_right(node): """ return None if not node.children else node.children[1] +@deprecate("Get the parent with 'node.parent' instead.") def bsp_father(node): """ .. deprecated:: 2.0 @@ -432,6 +436,7 @@ def bsp_father(node): """ return node.parent +@deprecate("Check for children with 'bool(node.children)' instead.") def bsp_is_leaf(node): """ .. deprecated:: 2.0 @@ -439,6 +444,7 @@ def bsp_is_leaf(node): """ return not node.children +@deprecate("Use 'node.contains' instead.") def bsp_contains(node, cx, cy): """ .. deprecated:: 2.0 @@ -446,6 +452,7 @@ def bsp_contains(node, cx, cy): """ return node.contains(cx, cy) +@deprecate("Use 'node.find_node' instead.") def bsp_find_node(node, cx, cy): """ .. deprecated:: 2.0 @@ -460,46 +467,53 @@ def _bsp_traverse(node_iter, callback, userData): for node in node_iter: callback(node, userData) +@deprecate("Iterate over nodes using 'for n in node.pre_order():' instead.") def bsp_traverse_pre_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 - Use :any:`BSP.walk` instead. + Use :any:`BSP.pre_order` instead. """ - _bsp_traverse(node._iter_pre_order(), callback, userData) + _bsp_traverse(node.pre_order(), callback, userData) +@deprecate("Iterate over nodes using 'for n in node.in_order():' instead.") def bsp_traverse_in_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 - Use :any:`BSP.walk` instead. + Use :any:`BSP.in_order` instead. """ - _bsp_traverse(node._iter_in_order(), callback, userData) + _bsp_traverse(node.in_order(), callback, userData) +@deprecate("Iterate over nodes using 'for n in node.post_order():' instead.") def bsp_traverse_post_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 - Use :any:`BSP.walk` instead. + Use :any:`BSP.post_order` instead. """ - _bsp_traverse(node._iter_post_order(), callback, userData) + _bsp_traverse(node.post_order(), callback, userData) +@deprecate("Iterate over nodes using 'for n in node.level_order():' instead.") def bsp_traverse_level_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 - Use :any:`BSP.walk` instead. + Use :any:`BSP.level_order` instead. """ - _bsp_traverse(node._iter_level_order(), callback, userData) + _bsp_traverse(node.level_order(), callback, userData) +@deprecate("Iterate over nodes using " + "'for n in node.inverted_level_order():' instead.") def bsp_traverse_inverted_level_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 - Use :any:`BSP.walk` instead. + Use :any:`BSP.inverted_level_order` instead. """ - _bsp_traverse(node._iter_inverted_level_order(), callback, userData) + _bsp_traverse(node.inverted_level_order(), callback, userData) +@deprecate("Delete bsp children using 'node.children = ()' instead.") def bsp_remove_sons(node): """Delete all children of a given node. Not recommended. @@ -512,6 +526,7 @@ def bsp_remove_sons(node): """ node.children = () +@deprecate("libtcod objects are deleted automatically.") def bsp_delete(node): # type: (Any) -> None """Exists for backward compatibility. Does nothing. diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 20776381..a9385a2f 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -364,6 +364,13 @@ def test_line_iter(): """ assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS +@pytest.mark.filterwarnings("ignore:Assign values via attribute instead.") +@pytest.mark.filterwarnings("ignore:Use 'node.contains' instead.") +@pytest.mark.filterwarnings("ignore:Check for children with") +@pytest.mark.filterwarnings("ignore:Iterate over nodes using") +@pytest.mark.filterwarnings("ignore:Access children with") +@pytest.mark.filterwarnings("ignore:Delete bsp children using") +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_bsp(): """ commented out statements work in libtcod-cffi From bc83a9b9f608d851cf66356200b7fa3be2a5af89 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 6 Dec 2018 03:56:14 -0800 Subject: [PATCH 0014/1101] Filter all warnings and deprecate delete functions loudly. --- tcod/libtcodpy.py | 7 +++++++ tests/test_libtcodpy.py | 26 +++++++++++++++++++++++++- tests/test_tcod.py | 2 ++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a162d8b8..43b1397a 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1452,6 +1452,7 @@ def path_walk(p, recompute): return x[0], y[0] return None,None +@deprecate("libtcod objects are deleted automatically.") def path_delete(p): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -1499,6 +1500,7 @@ def dijkstra_path_walk(p): return x[0], y[0] return None,None +@deprecate("libtcod objects are deleted automatically.") def dijkstra_delete(p): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2094,6 +2096,7 @@ def image_blit_2x(image, console, dx, dy, sx=0, sy=0, w=-1, h=-1): def image_save(image, filename): image.save_as(filename) +@deprecate("libtcod objects are deleted automatically.") def image_delete(image): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2326,6 +2329,7 @@ def map_is_walkable(m, x, y): """ return lib.TCOD_map_is_walkable(m.map_c, x, y) +@deprecate("libtcod objects are deleted automatically.") def map_delete(m): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2463,6 +2467,7 @@ def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): return lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new('float[4]', f), oc, typ) +@deprecate("libtcod objects are deleted automatically.") def noise_delete(n): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2560,6 +2565,7 @@ def parser_run(parser, filename, listener=None): with propagate_manager: lib.TCOD_parser_run(parser, _bytes(filename), clistener) +@deprecate("libtcod objects are deleted automatically.") def parser_delete(parser): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2760,6 +2766,7 @@ def random_restore(rnd, backup): lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL, backup.random_c) +@deprecate("libtcod objects are deleted automatically.") def random_delete(rnd): # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index a9385a2f..dd020d68 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -7,7 +7,7 @@ except ImportError: numpy = None -import libtcodpy +import tcod as libtcodpy def test_console_behaviour(console): assert not console @@ -145,6 +145,7 @@ def test_console_rexpaint_load_test_file(console): assert (libtcodpy.console_get_char_background(xp_console, 2, 1) == libtcodpy.Color(0, 0, 255)) +@pytest.mark.filterwarnings("ignore:Falsy console parameters are deprecated") def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): libtcodpy.console_print(console, 0, 0, 'test') libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) @@ -282,6 +283,7 @@ def sdl_callback(sdl_surface): libtcodpy.console_flush() assert escape, 'proof that sdl_callback was called' +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_image(console, tmpdir): img = libtcodpy.image_new(16, 16) libtcodpy.image_clear(img, libtcodpy.Color(0, 0, 0)) @@ -432,6 +434,7 @@ def traverse(node, user_data): libtcodpy.bsp_delete(bsp) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_map(): map = libtcodpy.map_new(16, 16) assert libtcodpy.map_get_width(map) == 16 @@ -490,6 +493,7 @@ def test_namegen_parse(): assert libtcodpy.namegen_get_sets() libtcodpy.namegen_destroy() +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_noise(): noise = libtcodpy.noise_new(1) libtcodpy.noise_set_type(noise, libtcodpy.NOISE_SIMPLEX) @@ -498,6 +502,7 @@ def test_noise(): libtcodpy.noise_get_turbulence(noise, [0], 4) libtcodpy.noise_delete(noise) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_random(): rand = libtcodpy.random_get_instance() rand = libtcodpy.random_new() @@ -517,6 +522,20 @@ def test_random(): libtcodpy.random_delete(rand) libtcodpy.random_delete(backup) + +@pytest.mark.filterwarnings("ignore:Assign to this heightmap with") +@pytest.mark.filterwarnings("ignore:Add a scalar to an array using") +@pytest.mark.filterwarnings("ignore:Multiply an array with a scaler using `hm") +@pytest.mark.filterwarnings("ignore:Clear an array with") +@pytest.mark.filterwarnings("ignore:Clamp array values using") +@pytest.mark.filterwarnings("ignore:Copy an array using") +@pytest.mark.filterwarnings("ignore:Add 2 arrays using") +@pytest.mark.filterwarnings("ignore:Multiply 2 arrays using") +@pytest.mark.filterwarnings("ignore:Arrays of noise should be sampled using t") +@pytest.mark.filterwarnings("ignore:Get a value from this heightmap with") +@pytest.mark.filterwarnings("ignore:This function is deprecated, see document") +@pytest.mark.filterwarnings("ignore:Use `hm.min\\(\\)` and `hm.max\\(\\)`") +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_heightmap(): hmap = libtcodpy.heightmap_new(16, 16) repr(hmap) @@ -596,9 +615,11 @@ def callback(ox, oy, dx, dy, user_data): return 0 return callback +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_map_fov(map_): libtcodpy.map_compute_fov(map_, *POINT_A) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_astar(map_): astar = libtcodpy.path_new_using_map(map_) @@ -623,6 +644,7 @@ def test_astar(map_): libtcodpy.path_delete(astar) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_astar_callback(map_, path_callback): astar = libtcodpy.path_new_using_function( libtcodpy.map_get_width(map_), @@ -632,6 +654,7 @@ def test_astar_callback(map_, path_callback): libtcodpy.path_compute(astar, *POINTS_AB) libtcodpy.path_delete(astar) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_dijkstra(map_): path = libtcodpy.dijkstra_new(map_) @@ -654,6 +677,7 @@ def test_dijkstra(map_): libtcodpy.dijkstra_delete(path) +@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_dijkstra_callback(map_, path_callback): path = libtcodpy.dijkstra_new_using_function( libtcodpy.map_get_width(map_), diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 9245d68f..b1be18d6 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -19,6 +19,8 @@ def test_line_error(): tcod.line(*LINE_ARGS, py_callback=raise_Exception) +@pytest.mark.filterwarnings("ignore:Iterate over nodes using") +@pytest.mark.filterwarnings("ignore:Use pre_order method instead of walk.") def test_tcod_bsp(): """ test tcod additions to BSP From ba21c25061e1f1262312a2cd44c336bf76972f76 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 6 Dec 2018 06:45:06 -0800 Subject: [PATCH 0015/1101] Slightly better inverted_level_order implementation. --- tcod/bsp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/bsp.py b/tcod/bsp.py index 01264d0e..7b32ffee 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -207,16 +207,16 @@ def inverted_level_order(self) -> Iterator['BSP']: .. versionadded:: 8.3 """ - levels = [[self]] # type: List[List['BSP']] + levels = [] # type: List[List['BSP']] next = [self] # type: List['BSP'] while next: + levels.append(next) level = next # type: List['BSP'] next = [] for node in level: next.extend(node.children) - levels.append(next) - for level in levels[::-1]: - yield from level + while levels: + yield from levels.pop() def contains(self, x: int, y: int) -> bool: """Returns True if this node contains these coordinates. From 2fd26515c74641ed66a6cf8040b268ff6fec4498 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 8 Dec 2018 05:43:21 -0800 Subject: [PATCH 0016/1101] Prepare 8.3.0 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7ca18a7c..a794009d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,8 +9,11 @@ v2.0.0 Unreleased ------------------ + +8.3.0 - 2018-12-08 +------------------ Added - - Added BSP traversal methods in tcod.bsp to gain parity with libtcodpy. + - Added BSP traversal methods in tcod.bsp for parity with libtcodpy. Deprecated - Already deprecated bsp functions are now even more deprecated. From 813521b5e043ba711da16d4a26c53d13dea8620b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 23 Dec 2018 04:34:05 -0800 Subject: [PATCH 0017/1101] Make libtcodpy key/mouse parameters more strict. --- CHANGELOG.rst | 2 ++ tcod/libtcodpy.py | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a794009d..14adb5ed 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - libtcodpy key and mouse functions will no longer accept the wrong types. 8.3.0 - 2018-12-08 ------------------ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 43b1397a..7d3218de 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -313,6 +313,10 @@ def __repr__(self): params.append('%s=%r' % (attr, getattr(self, attr))) return 'tcod.Key(%s)' % ', '.join(params) + @property + def key_p(self): + return self.cdata + class Mouse(_CDataWrapper): """Mouse event instance @@ -367,6 +371,10 @@ def __repr__(self): params.append('%s=%r' % (attr, getattr(self, attr))) return 'tcod.Mouse(%s)' % ', '.join(params) + @property + def mouse_p(self): + return self.cdata + def bsp_new_with_size(x, y, w, h): """Create a new BSP instance with the given rectangle. @@ -1127,14 +1135,14 @@ def console_wait_for_keypress(flush): Returns: Key: A new Key instance. """ - k=Key() - lib.TCOD_console_wait_for_keypress_wrapper(k.cdata, flush) - return k + key = Key() + lib.TCOD_console_wait_for_keypress_wrapper(key.key_p, flush) + return key def console_check_for_keypress(flags=KEY_RELEASED): - k=Key() - lib.TCOD_console_check_for_keypress_wrapper(k.cdata, flags) - return k + key = Key() + lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags) + return key def console_is_key_pressed(key): return lib.TCOD_console_is_key_pressed(key) @@ -2994,7 +3002,7 @@ def sys_check_for_event(mask, k, m): with an event. Can be None. """ return lib.TCOD_sys_check_for_event( - mask, k.cdata if k else ffi.NULL, m.cdata if m else ffi.NULL) + mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL) def sys_wait_for_event(mask, k, m, flush): """Wait for an event then return. @@ -3011,7 +3019,7 @@ def sys_wait_for_event(mask, k, m, flush): flush (bool): Clear the event buffer before waiting. """ return lib.TCOD_sys_wait_for_event( - mask, k.cdata if k else ffi.NULL, m.cdata if m else ffi.NULL, flush) + mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL, flush) @deprecate("This function does not provide reliable access to the clipboard.") def sys_clipboard_set(text): From b526f368bd94291f591e98d2ba7bb31d4584a6cf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 25 Dec 2018 19:28:52 -0800 Subject: [PATCH 0018/1101] Noted requirement for the MSVC runtime. --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index c7ef2366..5ca28cc7 100755 --- a/README.rst +++ b/README.rst @@ -67,6 +67,7 @@ install python-tcod and its dependencies to your user environment:: ============== * Python 3.5+ * Windows, Linux, or MacOS X 10.9+. +* On Windows, requires the Visual C++ runtime 2015 or later. * On Linux, requires libsdl2 (2.0.5+) and libomp5 to run. ========= From 934c909d211d6950a75c5fd1495bdfa9ab35e269 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Dec 2018 10:54:48 -0800 Subject: [PATCH 0019/1101] Fix new_struct callback for libtcodpy's custom parser. Fixes #60 --- CHANGELOG.rst | 1 + tcod/libtcodpy.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 14adb5ed..007becf6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased ------------------ Fixed - libtcodpy key and mouse functions will no longer accept the wrong types. + - The `new_struct` method was not being called for libtcodpy's custom parsers. 8.3.0 - 2018-12-08 ------------------ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 7d3218de..6b3858ff 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2529,7 +2529,7 @@ def parser_new_struct(parser, name): @ffi.def_extern() def _pycall_parser_new_struct(struct, name): - return _parser_listener.end_struct(struct, _unpack_char_p(name)) + return _parser_listener.new_struct(struct, _unpack_char_p(name)) @ffi.def_extern() def _pycall_parser_new_flag(name): From 508ce059cde2ab79783ec996c9c04f7d4c0e3378 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Dec 2018 11:09:06 -0800 Subject: [PATCH 0020/1101] Prepare 8.3.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 007becf6..5707cc7f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +8.3.1 - 2018-12-28 +------------------ Fixed - libtcodpy key and mouse functions will no longer accept the wrong types. - The `new_struct` method was not being called for libtcodpy's custom parsers. From ae132cae384a68b91feb803a8e257be65bf5cb60 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Dec 2018 12:12:33 -0800 Subject: [PATCH 0021/1101] Enable pytest-faulthander for tests. --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4ff44fe..c2cb6a2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ install: - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' before_script: -- pip install pytest pytest-cov +- pip install pytest pytest-cov pytest-faulthandler script: - pytest -v after_success: diff --git a/appveyor.yml b/appveyor.yml index 83b37843..34b3b214 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -43,7 +43,7 @@ install: - cmd: "python setup.py build sdist develop bdist_wheel" build: off before_test: -- cmd: "pip install pytest pytest-cov" +- cmd: "pip install pytest pytest-cov pytest-faulthandler" test_script: - ps: "pytest -v" From 1d86f4dc1b23eb5346ff5b79e70168e989621947 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Dec 2018 14:11:14 -0800 Subject: [PATCH 0022/1101] Hold local copies of all strings sent to C. This seems to fix random access violations on AppVeyor tests. --- CHANGELOG.rst | 3 +++ tcod/console.py | 10 +++++---- tcod/libtcodpy.py | 54 ++++++++++++++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5707cc7f..9b501fd1 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed rare access violations for some functions which took strings as + parameters, such as `tcod.console_init_root`. 8.3.1 - 2018-12-28 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 9e7d8713..2d465cfd 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -243,9 +243,9 @@ def print_(self, x: int, y: int, string: str, alignment (Optional[int]): Text alignment. """ alignment = self.default_alignment if alignment is None else alignment - + string = _fmt(string) lib.TCOD_console_printf_ex(self.console_c, x, y, - bg_blend, alignment, _fmt(string)) + bg_blend, alignment, string) def print_rect(self, x: int, y: int, width: int, height:int, string: str, bg_blend: int=tcod.libtcod.BKGND_DEFAULT, @@ -269,8 +269,9 @@ def print_rect(self, x: int, y: int, width: int, height:int, string: str, int: The number of lines of text once word-wrapped. """ alignment = self.default_alignment if alignment is None else alignment + string = _fmt(string) return lib.TCOD_console_printf_rect_ex(self.console_c, - x, y, width, height, bg_blend, alignment, _fmt(string)) + x, y, width, height, bg_blend, alignment, string) def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: @@ -286,8 +287,9 @@ def get_height_rect(self, x: int, y: int, width: int, height: int, Returns: int: The number of lines of text once word-wrapped. """ + string = _fmt(string) return lib.TCOD_console_get_height_rect_fmt( - self.console_c, x, y, width, height, _fmt(string)) + self.console_c, x, y, width, height, string) def rect(self, x: int, y: int, width: int, height: int, clear: bool, bg_blend: int=tcod.libtcod.BKGND_DEFAULT) -> None: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 6b3858ff..4a634928 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -676,7 +676,8 @@ def console_init_root( title = os.path.basename(sys.argv[0]) if renderer is None: renderer = RENDERER_GLSL # Stable for now. - lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer) + title = _bytes(title) + lib.TCOD_console_init_root(w, h, title, fullscreen, renderer) return tcod.console.Console._get_root(order) @@ -707,7 +708,8 @@ def console_set_custom_font(fontFile: AnyStr, flags: if not os.path.exists(fontFile): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(fontFile),)) - lib.TCOD_console_set_custom_font(_bytes(fontFile), flags, + fontFile = _bytes(fontFile) + lib.TCOD_console_set_custom_font(fontFile, flags, nb_char_horiz, nb_char_vertic) @@ -825,7 +827,8 @@ def console_set_window_title(title): Args: title (AnyStr): A string to change the title bar to. """ - lib.TCOD_console_set_window_title(_bytes(title)) + title = _bytes(title) + lib.TCOD_console_set_window_title(title) def console_credits(): lib.TCOD_console_credits() @@ -2057,8 +2060,9 @@ def image_load(filename): Args: filename (AnyStr): Path to a .bmp or .png image file. """ + filename = _bytes(filename) return tcod.image.Image._from_cdata( - ffi.gc(lib.TCOD_image_load(_bytes(filename)), + ffi.gc(lib.TCOD_image_load(filename), lib.TCOD_image_delete) ) @@ -2381,15 +2385,18 @@ def mouse_get_status(): # type: () -> Mouse return Mouse(lib.TCOD_mouse_get_status()) -def namegen_parse(filename,random=None): - lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) +def namegen_parse(filename, random=None): + filename = _bytes(filename) + lib.TCOD_namegen_parse(filename, random or ffi.NULL) def namegen_generate(name): - return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) + name = _bytes(name) + return _unpack_char_p(lib.TCOD_namegen_generate(name, False)) def namegen_generate_custom(name, rule): - return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), - _bytes(rule), False)) + name = _bytes(name) + rule = _bytes(rule) + return _unpack_char_p(lib.TCOD_namegen_generate(name, rule, False)) def namegen_get_sets(): sets = lib.TCOD_namegen_get_sets() @@ -2550,8 +2557,9 @@ def _pycall_parser_error(msg): def parser_run(parser, filename, listener=None): global _parser_listener + filename = _bytes(filename) if not listener: - lib.TCOD_parser_run(parser, _bytes(filename), ffi.NULL) + lib.TCOD_parser_run(parser, filename, ffi.NULL) return propagate_manager = _PropagateException() @@ -2571,7 +2579,7 @@ def parser_run(parser, filename, listener=None): with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, _bytes(filename), clistener) + lib.TCOD_parser_run(parser, filename, clistener) @deprecate("libtcod objects are deleted automatically.") def parser_delete(parser): @@ -2582,32 +2590,40 @@ def parser_delete(parser): """ def parser_get_bool_property(parser, name): - return bool(lib.TCOD_parser_get_bool_property(parser, _bytes(name))) + name = _bytes(name) + return bool(lib.TCOD_parser_get_bool_property(parser, name)) def parser_get_int_property(parser, name): - return lib.TCOD_parser_get_int_property(parser, _bytes(name)) + name = _bytes(name) + return lib.TCOD_parser_get_int_property(parser, name) def parser_get_char_property(parser, name): - return _chr(lib.TCOD_parser_get_char_property(parser, _bytes(name))) + name = _bytes(name) + return _chr(lib.TCOD_parser_get_char_property(parser, name)) def parser_get_float_property(parser, name): - return lib.TCOD_parser_get_float_property(parser, _bytes(name)) + name = _bytes(name) + return lib.TCOD_parser_get_float_property(parser, name) def parser_get_string_property(parser, name): + name = _bytes(name) return _unpack_char_p( - lib.TCOD_parser_get_string_property(parser, _bytes(name))) + lib.TCOD_parser_get_string_property(parser, name)) def parser_get_color_property(parser, name): + name = _bytes(name) return Color._new_from_cdata( - lib.TCOD_parser_get_color_property(parser, _bytes(name))) + lib.TCOD_parser_get_color_property(parser, name)) def parser_get_dice_property(parser, name): d = ffi.new('TCOD_dice_t *') - lib.TCOD_parser_get_dice_property_py(parser, _bytes(name), d) + name = _bytes(name) + lib.TCOD_parser_get_dice_property_py(parser, name, d) return Dice(d) def parser_get_list_property(parser, name, type): - clist = lib.TCOD_parser_get_list_property(parser, _bytes(name), type) + name = _bytes(name) + clist = lib.TCOD_parser_get_list_property(parser, name, type) return _convert_TCODList(clist, type) RNG_MT = 0 From c9a7c6035fcbb567c6db2585bd41316469814dbd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Dec 2018 15:04:01 -0800 Subject: [PATCH 0023/1101] Prepare 8.3.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9b501fd1..5630b424 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +8.3.2 - 2018-12-28 +------------------ Fixed - Fixed rare access violations for some functions which took strings as parameters, such as `tcod.console_init_root`. From d363aef39dfe898bfeefc3950563be09f857bdf8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 5 Jan 2019 08:16:34 -0800 Subject: [PATCH 0024/1101] Added missing spaces in Color's repr method. --- tcod/color.py | 4 ++-- tcod/libtcodpy.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/color.py b/tcod/color.py index b95055b1..93f5cfad 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -89,5 +89,5 @@ def __mul__(self, other): def __repr__(self): """Return a printable representation of the current color.""" - return "%s(%i,%i,%i)" % (self.__class__.__name__, - self.r, self.g, self.b) + return "%s(%i, %i, %i)" \ + % (self.__class__.__name__, self.r, self.g, self.b) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 4a634928..f695049b 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -629,8 +629,8 @@ def color_gen_map(colors, indexes): Example: >>> tcod.color_gen_map([(0, 0, 0), (255, 128, 0)], [0, 5]) - [Color(0,0,0), Color(51,25,0), Color(102,51,0), Color(153,76,0), \ -Color(204,102,0), Color(255,128,0)] + [Color(0, 0, 0), Color(51, 25, 0), Color(102, 51, 0), \ +Color(153, 76, 0), Color(204, 102, 0), Color(255, 128, 0)] """ ccolors = ffi.new('TCOD_color_t[]', colors) cindexes = ffi.new('int[]', indexes) From a2634b739880679d20e597e45fddf1542363cfc0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 5 Jan 2019 10:10:30 -0800 Subject: [PATCH 0025/1101] Clean up formatting in constants module. --- tcod/constants.py | 394 +++++++++++++++++++++++----------------------- 1 file changed, 197 insertions(+), 197 deletions(-) diff --git a/tcod/constants.py b/tcod/constants.py index ce3592d1..f457c1d9 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -305,200 +305,200 @@ TYPE_VALUELIST15 = 23 # --- colors --- -amber = Color(255,191,0) -azure = Color(0,127,255) -black = Color(0,0,0) -blue = Color(0,0,255) -brass = Color(191,151,96) -celadon = Color(172,255,175) -chartreuse = Color(127,255,0) -copper = Color(197,136,124) -crimson = Color(255,0,63) -cyan = Color(0,255,255) -dark_amber = Color(191,143,0) -dark_azure = Color(0,95,191) -dark_blue = Color(0,0,191) -dark_chartreuse = Color(95,191,0) -dark_crimson = Color(191,0,47) -dark_cyan = Color(0,191,191) -dark_flame = Color(191,47,0) -dark_fuchsia = Color(191,0,191) -dark_gray = Color(95,95,95) -dark_green = Color(0,191,0) -dark_grey = Color(95,95,95) -dark_han = Color(47,0,191) -dark_lime = Color(143,191,0) -dark_magenta = Color(191,0,143) -dark_orange = Color(191,95,0) -dark_pink = Color(191,0,95) -dark_purple = Color(143,0,191) -dark_red = Color(191,0,0) -dark_sea = Color(0,191,95) -dark_sepia = Color(94,75,47) -dark_sky = Color(0,143,191) -dark_turquoise = Color(0,191,143) -dark_violet = Color(95,0,191) -dark_yellow = Color(191,191,0) -darker_amber = Color(127,95,0) -darker_azure = Color(0,63,127) -darker_blue = Color(0,0,127) -darker_chartreuse = Color(63,127,0) -darker_crimson = Color(127,0,31) -darker_cyan = Color(0,127,127) -darker_flame = Color(127,31,0) -darker_fuchsia = Color(127,0,127) -darker_gray = Color(63,63,63) -darker_green = Color(0,127,0) -darker_grey = Color(63,63,63) -darker_han = Color(31,0,127) -darker_lime = Color(95,127,0) -darker_magenta = Color(127,0,95) -darker_orange = Color(127,63,0) -darker_pink = Color(127,0,63) -darker_purple = Color(95,0,127) -darker_red = Color(127,0,0) -darker_sea = Color(0,127,63) -darker_sepia = Color(63,50,31) -darker_sky = Color(0,95,127) -darker_turquoise = Color(0,127,95) -darker_violet = Color(63,0,127) -darker_yellow = Color(127,127,0) -darkest_amber = Color(63,47,0) -darkest_azure = Color(0,31,63) -darkest_blue = Color(0,0,63) -darkest_chartreuse = Color(31,63,0) -darkest_crimson = Color(63,0,15) -darkest_cyan = Color(0,63,63) -darkest_flame = Color(63,15,0) -darkest_fuchsia = Color(63,0,63) -darkest_gray = Color(31,31,31) -darkest_green = Color(0,63,0) -darkest_grey = Color(31,31,31) -darkest_han = Color(15,0,63) -darkest_lime = Color(47,63,0) -darkest_magenta = Color(63,0,47) -darkest_orange = Color(63,31,0) -darkest_pink = Color(63,0,31) -darkest_purple = Color(47,0,63) -darkest_red = Color(63,0,0) -darkest_sea = Color(0,63,31) -darkest_sepia = Color(31,24,15) -darkest_sky = Color(0,47,63) -darkest_turquoise = Color(0,63,47) -darkest_violet = Color(31,0,63) -darkest_yellow = Color(63,63,0) -desaturated_amber = Color(127,111,63) -desaturated_azure = Color(63,95,127) -desaturated_blue = Color(63,63,127) -desaturated_chartreuse = Color(95,127,63) -desaturated_crimson = Color(127,63,79) -desaturated_cyan = Color(63,127,127) -desaturated_flame = Color(127,79,63) -desaturated_fuchsia = Color(127,63,127) -desaturated_green = Color(63,127,63) -desaturated_han = Color(79,63,127) -desaturated_lime = Color(111,127,63) -desaturated_magenta = Color(127,63,111) -desaturated_orange = Color(127,95,63) -desaturated_pink = Color(127,63,95) -desaturated_purple = Color(111,63,127) -desaturated_red = Color(127,63,63) -desaturated_sea = Color(63,127,95) -desaturated_sky = Color(63,111,127) -desaturated_turquoise = Color(63,127,111) -desaturated_violet = Color(95,63,127) -desaturated_yellow = Color(127,127,63) -flame = Color(255,63,0) -fuchsia = Color(255,0,255) -gold = Color(229,191,0) -gray = Color(127,127,127) -green = Color(0,255,0) -grey = Color(127,127,127) -han = Color(63,0,255) -light_amber = Color(255,207,63) -light_azure = Color(63,159,255) -light_blue = Color(63,63,255) -light_chartreuse = Color(159,255,63) -light_crimson = Color(255,63,111) -light_cyan = Color(63,255,255) -light_flame = Color(255,111,63) -light_fuchsia = Color(255,63,255) -light_gray = Color(159,159,159) -light_green = Color(63,255,63) -light_grey = Color(159,159,159) -light_han = Color(111,63,255) -light_lime = Color(207,255,63) -light_magenta = Color(255,63,207) -light_orange = Color(255,159,63) -light_pink = Color(255,63,159) -light_purple = Color(207,63,255) -light_red = Color(255,63,63) -light_sea = Color(63,255,159) -light_sepia = Color(158,134,100) -light_sky = Color(63,207,255) -light_turquoise = Color(63,255,207) -light_violet = Color(159,63,255) -light_yellow = Color(255,255,63) -lighter_amber = Color(255,223,127) -lighter_azure = Color(127,191,255) -lighter_blue = Color(127,127,255) -lighter_chartreuse = Color(191,255,127) -lighter_crimson = Color(255,127,159) -lighter_cyan = Color(127,255,255) -lighter_flame = Color(255,159,127) -lighter_fuchsia = Color(255,127,255) -lighter_gray = Color(191,191,191) -lighter_green = Color(127,255,127) -lighter_grey = Color(191,191,191) -lighter_han = Color(159,127,255) -lighter_lime = Color(223,255,127) -lighter_magenta = Color(255,127,223) -lighter_orange = Color(255,191,127) -lighter_pink = Color(255,127,191) -lighter_purple = Color(223,127,255) -lighter_red = Color(255,127,127) -lighter_sea = Color(127,255,191) -lighter_sepia = Color(191,171,143) -lighter_sky = Color(127,223,255) -lighter_turquoise = Color(127,255,223) -lighter_violet = Color(191,127,255) -lighter_yellow = Color(255,255,127) -lightest_amber = Color(255,239,191) -lightest_azure = Color(191,223,255) -lightest_blue = Color(191,191,255) -lightest_chartreuse = Color(223,255,191) -lightest_crimson = Color(255,191,207) -lightest_cyan = Color(191,255,255) -lightest_flame = Color(255,207,191) -lightest_fuchsia = Color(255,191,255) -lightest_gray = Color(223,223,223) -lightest_green = Color(191,255,191) -lightest_grey = Color(223,223,223) -lightest_han = Color(207,191,255) -lightest_lime = Color(239,255,191) -lightest_magenta = Color(255,191,239) -lightest_orange = Color(255,223,191) -lightest_pink = Color(255,191,223) -lightest_purple = Color(239,191,255) -lightest_red = Color(255,191,191) -lightest_sea = Color(191,255,223) -lightest_sepia = Color(222,211,195) -lightest_sky = Color(191,239,255) -lightest_turquoise = Color(191,255,239) -lightest_violet = Color(223,191,255) -lightest_yellow = Color(255,255,191) -lime = Color(191,255,0) -magenta = Color(255,0,191) -orange = Color(255,127,0) -peach = Color(255,159,127) -pink = Color(255,0,127) -purple = Color(191,0,255) -red = Color(255,0,0) -sea = Color(0,255,127) -sepia = Color(127,101,63) -silver = Color(203,203,203) -sky = Color(0,191,255) -turquoise = Color(0,255,191) -violet = Color(127,0,255) -white = Color(255,255,255) -yellow = Color(255,255,0) +amber = Color(255, 191, 0) +azure = Color(0, 127, 255) +black = Color(0, 0, 0) +blue = Color(0, 0, 255) +brass = Color(191, 151, 96) +celadon = Color(172, 255, 175) +chartreuse = Color(127, 255, 0) +copper = Color(197, 136, 124) +crimson = Color(255, 0, 63) +cyan = Color(0, 255, 255) +dark_amber = Color(191, 143, 0) +dark_azure = Color(0, 95, 191) +dark_blue = Color(0, 0, 191) +dark_chartreuse = Color(95, 191, 0) +dark_crimson = Color(191, 0, 47) +dark_cyan = Color(0, 191, 191) +dark_flame = Color(191, 47, 0) +dark_fuchsia = Color(191, 0, 191) +dark_gray = Color(95, 95, 95) +dark_green = Color(0, 191, 0) +dark_grey = Color(95, 95, 95) +dark_han = Color(47, 0, 191) +dark_lime = Color(143, 191, 0) +dark_magenta = Color(191, 0, 143) +dark_orange = Color(191, 95, 0) +dark_pink = Color(191, 0, 95) +dark_purple = Color(143, 0, 191) +dark_red = Color(191, 0, 0) +dark_sea = Color(0, 191, 95) +dark_sepia = Color(94, 75, 47) +dark_sky = Color(0, 143, 191) +dark_turquoise = Color(0, 191, 143) +dark_violet = Color(95, 0, 191) +dark_yellow = Color(191, 191, 0) +darker_amber = Color(127, 95, 0) +darker_azure = Color(0, 63, 127) +darker_blue = Color(0, 0, 127) +darker_chartreuse = Color(63, 127, 0) +darker_crimson = Color(127, 0, 31) +darker_cyan = Color(0, 127, 127) +darker_flame = Color(127, 31, 0) +darker_fuchsia = Color(127, 0, 127) +darker_gray = Color(63, 63, 63) +darker_green = Color(0, 127, 0) +darker_grey = Color(63, 63, 63) +darker_han = Color(31, 0, 127) +darker_lime = Color(95, 127, 0) +darker_magenta = Color(127, 0, 95) +darker_orange = Color(127, 63, 0) +darker_pink = Color(127, 0, 63) +darker_purple = Color(95, 0, 127) +darker_red = Color(127, 0, 0) +darker_sea = Color(0, 127, 63) +darker_sepia = Color(63, 50, 31) +darker_sky = Color(0, 95, 127) +darker_turquoise = Color(0, 127, 95) +darker_violet = Color(63, 0, 127) +darker_yellow = Color(127, 127, 0) +darkest_amber = Color(63, 47, 0) +darkest_azure = Color(0, 31, 63) +darkest_blue = Color(0, 0, 63) +darkest_chartreuse = Color(31, 63, 0) +darkest_crimson = Color(63, 0, 15) +darkest_cyan = Color(0, 63, 63) +darkest_flame = Color(63, 15, 0) +darkest_fuchsia = Color(63, 0, 63) +darkest_gray = Color(31, 31, 31) +darkest_green = Color(0, 63, 0) +darkest_grey = Color(31, 31, 31) +darkest_han = Color(15, 0, 63) +darkest_lime = Color(47, 63, 0) +darkest_magenta = Color(63, 0, 47) +darkest_orange = Color(63, 31, 0) +darkest_pink = Color(63, 0, 31) +darkest_purple = Color(47, 0, 63) +darkest_red = Color(63, 0, 0) +darkest_sea = Color(0, 63, 31) +darkest_sepia = Color(31, 24, 15) +darkest_sky = Color(0, 47, 63) +darkest_turquoise = Color(0, 63, 47) +darkest_violet = Color(31, 0, 63) +darkest_yellow = Color(63, 63, 0) +desaturated_amber = Color(127, 111, 63) +desaturated_azure = Color(63, 95, 127) +desaturated_blue = Color(63, 63, 127) +desaturated_chartreuse = Color(95, 127, 63) +desaturated_crimson = Color(127, 63, 79) +desaturated_cyan = Color(63, 127, 127) +desaturated_flame = Color(127, 79, 63) +desaturated_fuchsia = Color(127, 63, 127) +desaturated_green = Color(63, 127, 63) +desaturated_han = Color(79, 63, 127) +desaturated_lime = Color(111, 127, 63) +desaturated_magenta = Color(127, 63, 111) +desaturated_orange = Color(127, 95, 63) +desaturated_pink = Color(127, 63, 95) +desaturated_purple = Color(111, 63, 127) +desaturated_red = Color(127, 63, 63) +desaturated_sea = Color(63, 127, 95) +desaturated_sky = Color(63, 111, 127) +desaturated_turquoise = Color(63, 127, 111) +desaturated_violet = Color(95, 63, 127) +desaturated_yellow = Color(127, 127, 63) +flame = Color(255, 63, 0) +fuchsia = Color(255, 0, 255) +gold = Color(229, 191, 0) +gray = Color(127, 127, 127) +green = Color(0, 255, 0) +grey = Color(127, 127, 127) +han = Color(63, 0, 255) +light_amber = Color(255, 207, 63) +light_azure = Color(63, 159, 255) +light_blue = Color(63, 63, 255) +light_chartreuse = Color(159, 255, 63) +light_crimson = Color(255, 63, 111) +light_cyan = Color(63, 255, 255) +light_flame = Color(255, 111, 63) +light_fuchsia = Color(255, 63, 255) +light_gray = Color(159, 159, 159) +light_green = Color(63, 255, 63) +light_grey = Color(159, 159, 159) +light_han = Color(111, 63, 255) +light_lime = Color(207, 255, 63) +light_magenta = Color(255, 63, 207) +light_orange = Color(255, 159, 63) +light_pink = Color(255, 63, 159) +light_purple = Color(207, 63, 255) +light_red = Color(255, 63, 63) +light_sea = Color(63, 255, 159) +light_sepia = Color(158, 134, 100) +light_sky = Color(63, 207, 255) +light_turquoise = Color(63, 255, 207) +light_violet = Color(159, 63, 255) +light_yellow = Color(255, 255, 63) +lighter_amber = Color(255, 223, 127) +lighter_azure = Color(127, 191, 255) +lighter_blue = Color(127, 127, 255) +lighter_chartreuse = Color(191, 255, 127) +lighter_crimson = Color(255, 127, 159) +lighter_cyan = Color(127, 255, 255) +lighter_flame = Color(255, 159, 127) +lighter_fuchsia = Color(255, 127, 255) +lighter_gray = Color(191, 191, 191) +lighter_green = Color(127, 255, 127) +lighter_grey = Color(191, 191, 191) +lighter_han = Color(159, 127, 255) +lighter_lime = Color(223, 255, 127) +lighter_magenta = Color(255, 127, 223) +lighter_orange = Color(255, 191, 127) +lighter_pink = Color(255, 127, 191) +lighter_purple = Color(223, 127, 255) +lighter_red = Color(255, 127, 127) +lighter_sea = Color(127, 255, 191) +lighter_sepia = Color(191, 171, 143) +lighter_sky = Color(127, 223, 255) +lighter_turquoise = Color(127, 255, 223) +lighter_violet = Color(191, 127, 255) +lighter_yellow = Color(255, 255, 127) +lightest_amber = Color(255, 239, 191) +lightest_azure = Color(191, 223, 255) +lightest_blue = Color(191, 191, 255) +lightest_chartreuse = Color(223, 255, 191) +lightest_crimson = Color(255, 191, 207) +lightest_cyan = Color(191, 255, 255) +lightest_flame = Color(255, 207, 191) +lightest_fuchsia = Color(255, 191, 255) +lightest_gray = Color(223, 223, 223) +lightest_green = Color(191, 255, 191) +lightest_grey = Color(223, 223, 223) +lightest_han = Color(207, 191, 255) +lightest_lime = Color(239, 255, 191) +lightest_magenta = Color(255, 191, 239) +lightest_orange = Color(255, 223, 191) +lightest_pink = Color(255, 191, 223) +lightest_purple = Color(239, 191, 255) +lightest_red = Color(255, 191, 191) +lightest_sea = Color(191, 255, 223) +lightest_sepia = Color(222, 211, 195) +lightest_sky = Color(191, 239, 255) +lightest_turquoise = Color(191, 255, 239) +lightest_violet = Color(223, 191, 255) +lightest_yellow = Color(255, 255, 191) +lime = Color(191, 255, 0) +magenta = Color(255, 0, 191) +orange = Color(255, 127, 0) +peach = Color(255, 159, 127) +pink = Color(255, 0, 127) +purple = Color(191, 0, 255) +red = Color(255, 0, 0) +sea = Color(0, 255, 127) +sepia = Color(127, 101, 63) +silver = Color(203, 203, 203) +sky = Color(0, 191, 255) +turquoise = Color(0, 255, 191) +violet = Color(127, 0, 255) +white = Color(255, 255, 255) +yellow = Color(255, 255, 0) From 8753437d5610a80eeb684a72fae690d8e6c98ffd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 5 Jan 2019 11:37:54 -0800 Subject: [PATCH 0026/1101] Reformat tcod modules with Black. --- CHANGELOG.rst | 2 + pyproject.toml | 4 + setup.cfg | 3 + tcod/__init__.py | 10 +- tcod/_internal.py | 10 +- tcod/bsp.py | 111 +++-- tcod/color.py | 27 +- tcod/console.py | 227 ++++++--- tcod/image.py | 66 ++- tcod/libtcod.py | 30 +- tcod/libtcodpy.py | 1176 ++++++++++++++++++++++++++++++++------------- tcod/map.py | 60 +-- tcod/noise.py | 147 +++--- tcod/path.py | 93 ++-- tcod/random.py | 52 +- tcod/tcod.py | 70 ++- 16 files changed, 1395 insertions(+), 693 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5630b424..0b5c6e39 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed libtcodpy `struct_add_value_list` function. 8.3.2 - 2018-12-28 ------------------ diff --git a/pyproject.toml b/pyproject.toml index c7a16d74..0aeaca41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,7 @@ requires = [ "cffi>=1.8.1,<2", "pycparser>=2.14,<3", ] + +[tool.black] +line-length = 79 +py36 = false diff --git a/setup.cfg b/setup.cfg index 86f10e1b..04171cf8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,6 @@ test=pytest [tool:pytest] addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl + +[flake8] +ignore = E203 W503 diff --git a/tcod/__init__.py b/tcod/__init__.py index 5ae07ef0..9e200a97 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -21,14 +21,14 @@ import warnings -from tcod.libtcodpy import * +from tcod.libtcodpy import * # noqa: F4 + try: from tcod.version import __version__ -except ImportError: # Gets imported without version.py by ReadTheDocs - __version__ = '' +except ImportError: # Gets imported without version.py by ReadTheDocs + __version__ = "" if sys.version_info[0] == 2: warnings.warn( - "python-tcod has dropped support for Python 2.7.", - DeprecationWarning + "python-tcod has dropped support for Python 2.7.", DeprecationWarning ) diff --git a/tcod/_internal.py b/tcod/_internal.py index e5a0aad2..f1f749aa 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,25 +1,29 @@ - import functools import warnings + def deprecate(message, category=DeprecationWarning, stacklevel=0): def decorator(func): @functools.wraps(func) def wrapper(*args, **kargs): warnings.warn(message, category, stacklevel=stacklevel + 2) return func(*args, **kargs) + return wrapper + return decorator + def verify_order(order): order = order.upper() - if order != 'C' and order != 'F': + if order != "C" and order != "F": raise TypeError("order must be 'C' or 'F', not %r" % (order,)) return order + def handle_order(shape, order): order = verify_order(order) - if order == 'C': + if order == "C": return shape else: return tuple(reversed(shape)) diff --git a/tcod/bsp.py b/tcod/bsp.py index 7b32ffee..b6e58546 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -23,10 +23,11 @@ else: print('Dig a room for %s.' % node) """ -from typing import Iterator, Optional, Union, Tuple, List +from typing import Iterator, Optional from tcod.libtcod import lib, ffi from tcod._internal import deprecate +import tcod.random class BSP(object): @@ -54,21 +55,22 @@ class BSP(object): """ def __init__(self, x: int, y: int, width: int, height: int): - self.x = x # type: int - self.y = y # type: int - self.width = width # type: int - self.height = height # type: int + self.x = x # type: int + self.y = y # type: int + self.width = width # type: int + self.height = height # type: int - self.level = 0 # type: int - self.position = 0 # type: int - self.horizontal = False # type: bool + self.level = 0 # type: int + self.position = 0 # type: int + self.horizontal = False # type: bool - self.parent = None # type: Optional['BSP'] - self.children = () # type: Union[Tuple[], Tuple['BSP', 'BSP']] + self.parent = None # type: Optional['BSP'] + self.children = () # type: Union[Tuple[], Tuple['BSP', 'BSP']] @property def w(self) -> int: return self.width + @w.setter def w(self, value: int): self.width = value @@ -76,29 +78,41 @@ def w(self, value: int): @property def h(self) -> int: return self.height + @h.setter def h(self, value: int): self.height = value def _as_cdata(self): - cdata = ffi.gc(lib.TCOD_bsp_new_with_size(self.x, self.y, - self.width, self.height), - lib.TCOD_bsp_delete) + cdata = ffi.gc( + lib.TCOD_bsp_new_with_size( + self.x, self.y, self.width, self.height + ), + lib.TCOD_bsp_delete, + ) cdata.level = self.level return cdata def __str__(self): """Provide a useful readout when printed.""" - status = 'leaf' + status = "leaf" if self.children: - status = ('split at position=%i,horizontal=%r' % - (self.position, self.horizontal)) - - return ('<%s(x=%i,y=%i,width=%i,height=%i)level=%i,%s>' % - (self.__class__.__name__, - self.x, self.y, self.width, self.height, self.level, status)) + status = "split at position=%i,horizontal=%r" % ( + self.position, + self.horizontal, + ) + + return "<%s(x=%i,y=%i,width=%i,height=%i)level=%i,%s>" % ( + self.__class__.__name__, + self.x, + self.y, + self.width, + self.height, + self.level, + status, + ) - def _unpack_bsp_tree(self, cdata): + def _unpack_bsp_tree(self, cdata) -> None: self.x = cdata.x self.y = cdata.y self.width = cdata.w @@ -114,7 +128,7 @@ def _unpack_bsp_tree(self, cdata): self.children[1].parent = self self.children[1]._unpack_bsp_tree(lib.TCOD_bsp_right(cdata)) - def split_once(self, horizontal: bool, position: int): + def split_once(self, horizontal: bool, position: int) -> None: """Split this partition into 2 sub-partitions. Args: @@ -125,8 +139,15 @@ def split_once(self, horizontal: bool, position: int): lib.TCOD_bsp_split_once(cdata, horizontal, position) self._unpack_bsp_tree(cdata) - def split_recursive(self, depth, min_width, min_height, - max_horizontal_ratio, max_vertical_ratio, seed=None): + def split_recursive( + self, + depth: int, + min_width: int, + min_height: int, + max_horizontal_ratio: float, + max_vertical_ratio: float, + seed: Optional[tcod.random.Random] = None, + ) -> None: """Divide this partition recursively. Args: @@ -145,13 +166,15 @@ def split_recursive(self, depth, min_width, min_height, cdata, seed or ffi.NULL, depth, - min_width, min_height, - max_horizontal_ratio, max_vertical_ratio, + min_width, + min_height, + max_horizontal_ratio, + max_vertical_ratio, ) self._unpack_bsp_tree(cdata) - @deprecate('Use pre_order method instead of walk.') - def walk(self) -> Iterator['BSP']: + @deprecate("Use pre_order method instead of walk.") + def walk(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in pre order. .. deprecated:: 2.3 @@ -159,7 +182,7 @@ def walk(self) -> Iterator['BSP']: """ return self.post_order() - def pre_order(self) -> Iterator['BSP']: + def pre_order(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in pre order. .. versionadded:: 8.3 @@ -168,7 +191,7 @@ def pre_order(self) -> Iterator['BSP']: for child in self.children: yield from child.pre_order() - def in_order(self) -> Iterator['BSP']: + def in_order(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in order. .. versionadded:: 8.3 @@ -180,7 +203,7 @@ def in_order(self) -> Iterator['BSP']: else: yield self - def post_order(self) -> Iterator['BSP']: + def post_order(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in post order. .. versionadded:: 8.3 @@ -189,29 +212,29 @@ def post_order(self) -> Iterator['BSP']: yield from child.post_order() yield self - def level_order(self) -> Iterator['BSP']: + def level_order(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in level order. .. versionadded:: 8.3 """ - next = [self] # type: List['BSP'] + next = [self] # type: List['BSP'] while next: - level = next # type: List['BSP'] + level = next # type: List['BSP'] next = [] yield from level for node in level: next.extend(node.children) - def inverted_level_order(self) -> Iterator['BSP']: + def inverted_level_order(self) -> Iterator["BSP"]: """Iterate over this BSP's hierarchy in inverse level order. .. versionadded:: 8.3 """ - levels = [] # type: List[List['BSP']] - next = [self] # type: List['BSP'] + levels = [] # type: List[List['BSP']] + next = [self] # type: List['BSP'] while next: levels.append(next) - level = next # type: List['BSP'] + level = next # type: List['BSP'] next = [] for node in level: next.extend(node.children) @@ -229,10 +252,12 @@ def contains(self, x: int, y: int) -> bool: bool: True if this node contains these coordinates. Otherwise False. """ - return (self.x <= x < self.x + self.width and - self.y <= y < self.y + self.height) + return ( + self.x <= x < self.x + self.width + and self.y <= y < self.y + self.height + ) - def find_node(self, x: int, y: int) -> Optional['BSP']: + def find_node(self, x: int, y: int) -> Optional["BSP"]: """Return the deepest node which contains these coordinates. Returns: @@ -247,6 +272,4 @@ def find_node(self, x: int, y: int) -> Optional['BSP']: return self -__all__ = [ - "BSP", -] +__all__ = ["BSP"] diff --git a/tcod/color.py b/tcod/color.py index 93f5cfad..6878f393 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -4,7 +4,7 @@ from __future__ import absolute_import -from tcod.libtcod import ffi, lib +from tcod.libtcod import lib class Color(list): @@ -17,7 +17,7 @@ class Color(list): """ def __init__(self, r=0, g=0, b=0): - self[:] = (r & 0xff, g & 0xff, b & 0xff) + self[:] = (r & 0xFF, g & 0xFF, b & 0xFF) @property def r(self): @@ -26,23 +26,25 @@ def r(self): @r.setter def r(self, value): - self[0] = value & 0xff + self[0] = value & 0xFF @property def g(self): """int: Green value, always normalised to 0-255.""" return self[1] + @g.setter def g(self, value): - self[1] = value & 0xff + self[1] = value & 0xFF @property def b(self): """int: Blue value, always normalised to 0-255.""" return self[2] + @b.setter def b(self, value): - self[2] = value & 0xff + self[2] = value & 0xFF @classmethod def _new_from_cdata(cls, cdata): @@ -53,13 +55,13 @@ def __getitem__(self, index): try: return list.__getitem__(self, index) except TypeError: - return list.__getitem__(self, 'rgb'.index(index)) + return list.__getitem__(self, "rgb".index(index)) def __setitem__(self, index, value): try: list.__setitem__(self, index, value) except TypeError: - list.__setitem__(self, 'rgb'.index(index), value) + list.__setitem__(self, "rgb".index(index), value) def __eq__(self, other): """Compare equality between colors. @@ -85,9 +87,14 @@ def __mul__(self, other): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) else: return Color._new_from_cdata( - lib.TCOD_color_multiply_scalar(self, other)) + lib.TCOD_color_multiply_scalar(self, other) + ) def __repr__(self): """Return a printable representation of the current color.""" - return "%s(%i, %i, %i)" \ - % (self.__class__.__name__, self.r, self.g, self.b) + return "%s(%i, %i, %i)" % ( + self.__class__.__name__, + self.r, + self.g, + self.b, + ) diff --git a/tcod/console.py b/tcod/console.py index 2d465cfd..d8e6365c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -23,10 +23,6 @@ # The window is closed here, after the above context exits. """ -from __future__ import absolute_import - -import sys - from typing import Optional import warnings @@ -36,12 +32,15 @@ from tcod.libtcod import ffi, lib import tcod._internal + def _fmt(string: str) -> bytes: """Return a string that escapes 'C printf' side effects.""" - return string.encode('utf-8').replace(b'%', b'%%') + return string.encode("utf-8").replace(b"%", b"%%") + _root_console = None + class Console(object): """A console object containing a grid of characters with foreground/background colors. @@ -58,11 +57,11 @@ class Console(object): console_c (CData): A cffi pointer to a TCOD_console_t object. """ - def __init__(self, width, height, order='C'): + def __init__(self, width, height, order="C"): self._key_color = None self._ch = np.full((height, width), 0x20, dtype=np.intc) - self._fg = np.zeros((height, width), dtype='(3,)u1') - self._bg = np.zeros((height, width), dtype='(3,)u1') + self._fg = np.zeros((height, width), dtype="(3,)u1") + self._bg = np.zeros((height, width), dtype="(3,)u1") self._order = tcod._internal.verify_order(order) # libtcod uses the root console for defaults. @@ -72,21 +71,22 @@ def __init__(self, width, height, order='C'): alignment = lib.TCOD_ctx.root.alignment self._console_data = self.console_c = ffi.new( - 'struct TCOD_Console*', + "struct TCOD_Console*", { - 'w': width, 'h': height, - 'ch_array': ffi.cast('int*', self._ch.ctypes.data), - 'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data), - 'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data), - 'bkgnd_flag': bkgnd_flag, - 'alignment': alignment, - 'fore': (255, 255, 255), - 'back': (0, 0, 0), + "w": width, + "h": height, + "ch_array": ffi.cast("int*", self._ch.ctypes.data), + "fg_array": ffi.cast("TCOD_color_t*", self._fg.ctypes.data), + "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), + "bkgnd_flag": bkgnd_flag, + "alignment": alignment, + "fore": (255, 255, 255), + "back": (0, 0, 0), }, ) @classmethod - def _from_cdata(cls, cdata, order='C'): + def _from_cdata(cls, cdata, order="C"): if isinstance(cdata, cls): return cdata self = object.__new__(cls) @@ -110,7 +110,7 @@ def _get_root(cls, order=None): self._init_setup_console_data(self._order) return self - def _init_setup_console_data(self, order='C'): + def _init_setup_console_data(self, order="C"): """Setup numpy arrays over libtcod data buffers.""" global _root_console self._key_color = None @@ -118,11 +118,13 @@ def _init_setup_console_data(self, order='C'): _root_console = self self._console_data = lib.TCOD_ctx.root else: - self._console_data = ffi.cast('struct TCOD_Console*', self.console_c) + self._console_data = ffi.cast( + "struct TCOD_Console*", self.console_c + ) def unpack_color(color_data): """return a (height, width, 3) shaped array from an image struct""" - color_buffer = ffi.buffer(color_data[0:self.width * self.height]) + color_buffer = ffi.buffer(color_data[0 : self.width * self.height]) array = np.frombuffer(color_buffer, np.uint8) return array.reshape((self.height, self.width, 3)) @@ -130,9 +132,10 @@ def unpack_color(color_data): self._bg = unpack_color(self._console_data.bg_array) buf = self._console_data.ch_array - buf = ffi.buffer(buf[0:self.width * self.height]) - self._ch = np.frombuffer(buf, np.intc).reshape((self.height, - self.width)) + buf = ffi.buffer(buf[0 : self.width * self.height]) + self._ch = np.frombuffer(buf, np.intc).reshape( + (self.height, self.width) + ) self._order = tcod._internal.verify_order(order) @@ -156,7 +159,7 @@ def bg(self): ``console.bg[x, y, channel] # order='F'``. """ - return self._bg.transpose(1, 0, 2) if self._order == 'F' else self._bg + return self._bg.transpose(1, 0, 2) if self._order == "F" else self._bg @property def fg(self): @@ -167,7 +170,7 @@ def fg(self): Index this array with ``console.fg[i, j, channel] # order='C'`` or ``console.fg[x, y, channel] # order='F'``. """ - return self._fg.transpose(1, 0, 2) if self._order == 'F' else self._fg + return self._fg.transpose(1, 0, 2) if self._order == "F" else self._fg @property def ch(self): @@ -178,13 +181,14 @@ def ch(self): Index this array with ``console.ch[i, j] # order='C'`` or ``console.ch[x, y] # order='F'``. """ - return self._ch.T if self._order == 'F' else self._ch + return self._ch.T if self._order == "F" else self._ch @property def default_bg(self): """Tuple[int, int, int]: The default background color.""" color = self._console_data.back return color.r, color.g, color.b + @default_bg.setter def default_bg(self, color): self._console_data.back = color @@ -194,6 +198,7 @@ def default_fg(self): """Tuple[int, int, int]: The default foreground color.""" color = self._console_data.fore return color.r, color.g, color.b + @default_fg.setter def default_fg(self, color): self._console_data.fore = color @@ -202,6 +207,7 @@ def default_fg(self, color): def default_bg_blend(self): """int: The default blending mode.""" return self._console_data.bkgnd_flag + @default_bg_blend.setter def default_bg_blend(self, value): self._console_data.bkgnd_flag = value @@ -210,6 +216,7 @@ def default_bg_blend(self, value): def default_alignment(self): """int: The default text alignment.""" return self._console_data.alignment + @default_alignment.setter def default_alignment(self, value): self._console_data.alignment = value @@ -230,9 +237,14 @@ def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT): """ lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend) - def print_(self, x: int, y: int, string: str, - bg_blend: int=tcod.libtcod.BKGND_DEFAULT, - alignment: Optional[int]=None) -> None: + def print_( + self, + x: int, + y: int, + string: str, + bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + alignment: Optional[int] = None, + ) -> None: """Print a color formatted string on a console. Args: @@ -244,12 +256,20 @@ def print_(self, x: int, y: int, string: str, """ alignment = self.default_alignment if alignment is None else alignment string = _fmt(string) - lib.TCOD_console_printf_ex(self.console_c, x, y, - bg_blend, alignment, string) + lib.TCOD_console_printf_ex( + self.console_c, x, y, bg_blend, alignment, string + ) - def print_rect(self, x: int, y: int, width: int, height:int, string: str, - bg_blend: int=tcod.libtcod.BKGND_DEFAULT, - alignment: Optional[int]=None) -> int: + def print_rect( + self, + x: int, + y: int, + width: int, + height: int, + string: str, + bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + alignment: Optional[int] = None, + ) -> int: """Print a string constrained to a rectangle. If h > 0 and the bottom of the rectangle is reached, @@ -270,11 +290,13 @@ def print_rect(self, x: int, y: int, width: int, height:int, string: str, """ alignment = self.default_alignment if alignment is None else alignment string = _fmt(string) - return lib.TCOD_console_printf_rect_ex(self.console_c, - x, y, width, height, bg_blend, alignment, string) + return lib.TCOD_console_printf_rect_ex( + self.console_c, x, y, width, height, bg_blend, alignment, string + ) - def get_height_rect(self, x: int, y: int, width: int, height: int, - string: str) -> int: + def get_height_rect( + self, x: int, y: int, width: int, height: int, string: str + ) -> int: """Return the height of this text word-wrapped into this rectangle. Args: @@ -289,10 +311,18 @@ def get_height_rect(self, x: int, y: int, width: int, height: int, """ string = _fmt(string) return lib.TCOD_console_get_height_rect_fmt( - self.console_c, x, y, width, height, string) + self.console_c, x, y, width, height, string + ) - def rect(self, x: int, y: int, width: int, height: int, clear: bool, - bg_blend: int=tcod.libtcod.BKGND_DEFAULT) -> None: + def rect( + self, + x: int, + y: int, + width: int, + height: int, + clear: bool, + bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + ) -> None: """Draw a the background color on a rect optionally clearing the text. If clr is True the affected tiles are changed to space character. @@ -306,8 +336,9 @@ def rect(self, x: int, y: int, width: int, height: int, clear: bool, removed. bg_blend (int): Background blending flag. """ - lib.TCOD_console_rect(self.console_c, x, y, width, height, clear, - bg_blend) + lib.TCOD_console_rect( + self.console_c, x, y, width, height, clear, bg_blend + ) def hline(self, x, y, width, bg_blend=tcod.libtcod.BKGND_DEFAULT): """Draw a horizontal line on the console. @@ -335,9 +366,16 @@ def vline(self, x, y, height, bg_blend=tcod.libtcod.BKGND_DEFAULT): """ lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) - def print_frame(self, x: int, y: int, width: int, height: int, - string: str='', clear: bool=True, - bg_blend: int=tcod.libtcod.BKGND_DEFAULT): + def print_frame( + self, + x: int, + y: int, + width: int, + height: int, + string: str = "", + clear: bool = True, + bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + ): """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the @@ -360,12 +398,23 @@ def print_frame(self, x: int, y: int, width: int, height: int, Now supports Unicode strings. """ string = _fmt(string) if string else ffi.NULL - lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, - clear, bg_blend, string) + lib.TCOD_console_printf_frame( + self.console_c, x, y, width, height, clear, bg_blend, string + ) - def blit(self, dest, dest_x=0, dest_y=0, - src_x=0, src_y=0, width=0, height=0, - fg_alpha=1.0, bg_alpha=1.0, key_color=None): + def blit( + self, + dest, + dest_x=0, + dest_y=0, + src_x=0, + src_y=0, + width=0, + height=0, + fg_alpha=1.0, + bg_alpha=1.0, + key_color=None, + ): """Blit from this console onto the ``dest`` console. Args: @@ -392,25 +441,49 @@ def blit(self, dest, dest_x=0, dest_y=0, `(x, y, width, height, dest, dest_x, dest_y, *)` """ # The old syntax is easy to detect and correct. - if hasattr(src_y, 'console_c'): - src_x, src_y, width, height, dest, dest_x, dest_y = \ - dest, dest_x, dest_y, src_x, src_y, width, height + if hasattr(src_y, "console_c"): + src_x, src_y, width, height, dest, dest_x, dest_y = ( + dest, + dest_x, + dest_y, + src_x, + src_y, + width, + height, + ) warnings.warn( "Parameter names have been moved around, see documentation.", DeprecationWarning, stacklevel=2, - ) + ) if key_color or self._key_color: - key_color = ffi.new('TCOD_color_t*', key_color) + key_color = ffi.new("TCOD_color_t*", key_color) lib.TCOD_console_blit_key_color( - self.console_c, src_x, src_y, width, height, - dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha, key_color + self.console_c, + src_x, + src_y, + width, + height, + dest.console_c, + dest_x, + dest_y, + fg_alpha, + bg_alpha, + key_color, ) else: lib.TCOD_console_blit( - self.console_c, src_x, src_y, width, height, - dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha + self.console_c, + src_x, + src_y, + width, + height, + dest.console_c, + dest_x, + dest_y, + fg_alpha, + bg_alpha, ) def set_key_color(self, color): @@ -432,7 +505,7 @@ def __enter__(self): not be closed on its own otherwise. """ if self.console_c != ffi.NULL: - raise NotImplementedError('Only the root console has a context.') + raise NotImplementedError("Only the root console has a context.") return self def __exit__(self, *args): @@ -453,18 +526,19 @@ def __bool__(self): def __getstate__(self): state = self.__dict__.copy() - del state['console_c'] - state['_console_data'] = { - 'w': self.width, 'h': self.height, - 'bkgnd_flag': self.default_bg_blend, - 'alignment': self.default_alignment, - 'fore': self.default_fg, - 'back': self.default_bg, + del state["console_c"] + state["_console_data"] = { + "w": self.width, + "h": self.height, + "bkgnd_flag": self.default_bg_blend, + "alignment": self.default_alignment, + "fore": self.default_fg, + "back": self.default_bg, } if self.console_c == ffi.NULL: - state['_ch'] = np.copy(self._ch) - state['_fg'] = np.copy(self._fg) - state['_bg'] = np.copy(self._bg) + state["_ch"] = np.copy(self._ch) + state["_fg"] = np.copy(self._fg) + state["_bg"] = np.copy(self._bg) return state def __setstate__(self, state): @@ -472,10 +546,11 @@ def __setstate__(self, state): self.__dict__.update(state) self._console_data.update( { - 'ch_array': ffi.cast('int*', self._ch.ctypes.data), - 'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data), - 'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data), + "ch_array": ffi.cast("int*", self._ch.ctypes.data), + "fg_array": ffi.cast("TCOD_color_t*", self._fg.ctypes.data), + "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), } ) self._console_data = self.console_c = ffi.new( - 'struct TCOD_Console*', self._console_data) + "struct TCOD_Console*", self._console_data + ) diff --git a/tcod/image.py b/tcod/image.py index fa0b6da3..efde52a5 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,4 +1,3 @@ - from __future__ import absolute_import import numpy as np @@ -6,12 +5,13 @@ from tcod.libtcod import ffi, lib from tcod.tcod import _console -class _ImageBufferArray(np.ndarray): +class _ImageBufferArray(np.ndarray): def __new__(cls, image): size = image.height * image.width - self = np.frombuffer(ffi.buffer(lib.TCOD_image_get_colors()[size]), - np.uint8) + self = np.frombuffer( + ffi.buffer(lib.TCOD_image_get_colors()[size]), np.uint8 + ) self = self.reshape((image.height, image.width, 3)).view(cls) self._image_c = image.cdata return self @@ -19,7 +19,7 @@ def __new__(cls, image): def __array_finalize__(self, obj): if obj is None: return - self._image_c = getattr(obj, '_image_c', None) + self._image_c = getattr(obj, "_image_c", None) def __repr__(self): return repr(self.view(np.ndarray)) @@ -41,10 +41,12 @@ class Image(object): width (int): Read only width of this Image. height (int): Read only height of this Image. """ + def __init__(self, width, height): self.width, self.height = width, height - self.image_c = ffi.gc(lib.TCOD_image_new(width, height), - lib.TCOD_image_delete) + self.image_c = ffi.gc( + lib.TCOD_image_new(width, height), lib.TCOD_image_delete + ) @classmethod def _from_cdata(cls, cdata): @@ -134,8 +136,8 @@ def _get_size(self): Returns: Tuple[int, int]: The (width, height) of this Image """ - w = ffi.new('int *') - h = ffi.new('int *') + w = ffi.new("int *") + h = ffi.new("int *") lib.TCOD_image_get_size(self.image_c, w, h) return w[0], h[0] @@ -172,8 +174,9 @@ def get_mipmap_pixel(self, left, top, right, bottom): An (r, g, b) tuple containing the averaged color value. Values are in a 0 to 255 range. """ - color = lib.TCOD_image_get_mipmap_pixel(self.image_c, - left, top, right, bottom) + color = lib.TCOD_image_get_mipmap_pixel( + self.image_c, left, top, right, bottom + ) return (color.r, color.g, color.b) def put_pixel(self, x, y, color): @@ -203,8 +206,15 @@ def blit(self, console, x, y, bg_blend, scale_x, scale_y, angle): angle (float): Rotation angle in radians. (Clockwise?) """ lib.TCOD_image_blit( - self.image_c, _console(console), - x, y, bg_blend, scale_x, scale_y, angle) + self.image_c, + _console(console), + x, + y, + bg_blend, + scale_x, + scale_y, + angle, + ) def blit_rect(self, console, x, y, width, height, bg_blend): """Blit onto a Console without scaling or rotation. @@ -218,10 +228,19 @@ def blit_rect(self, console, x, y, width, height, bg_blend): bg_blend (int): Background blending mode to use. """ lib.TCOD_image_blit_rect( - self.image_c, _console(console), x, y, width, height, bg_blend) - - def blit_2x(self, console, dest_x, dest_y, - img_x=0, img_y=0, img_width=-1, img_height=-1): + self.image_c, _console(console), x, y, width, height, bg_blend + ) + + def blit_2x( + self, + console, + dest_x, + dest_y, + img_x=0, + img_y=0, + img_width=-1, + img_height=-1, + ): """Blit onto a Console with double resolution. Args: @@ -236,8 +255,15 @@ def blit_2x(self, console, dest_x, dest_y, Use -1 for the full Image height. """ lib.TCOD_image_blit_2x( - self.image_c, _console(console), - dest_x, dest_y, img_x, img_y, img_width, img_height) + self.image_c, + _console(console), + dest_x, + dest_y, + img_x, + img_y, + img_width, + img_height, + ) def save_as(self, filename): """Save the Image to a 32-bit .bmp or .png file. @@ -245,4 +271,4 @@ def save_as(self, filename): Args: filename (Text): File path to same this Image. """ - lib.TCOD_image_save(self.image_c, filename.encode('utf-8')) + lib.TCOD_image_save(self.image_c, filename.encode("utf-8")) diff --git a/tcod/libtcod.py b/tcod/libtcod.py index afd2cdc3..c0dc7d4c 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -10,30 +10,34 @@ from tcod import __path__ -if _sys.platform == 'win32': +if _sys.platform == "win32": # add Windows dll's to PATH _bits, _linkage = _platform.architecture() - _os.environ['PATH'] = '%s;%s' % ( - _os.path.join(__path__[0], 'x86' if _bits == '32bit' else 'x64'), - _os.environ['PATH'], - ) + _os.environ["PATH"] = "%s;%s" % ( + _os.path.join(__path__[0], "x86" if _bits == "32bit" else "x64"), + _os.environ["PATH"], + ) NOISE_DEFAULT_HURST = 0.5 NOISE_DEFAULT_LACUNARITY = 2.0 -def FOV_PERMISSIVE(p) : - return FOV_PERMISSIVE_0+p + +def FOV_PERMISSIVE(p): + return FOV_PERMISSIVE_0 + p + def BKGND_ALPHA(a): return BKGND_ALPH | (int(a * 255) << 8) + def BKGND_ADDALPHA(a): return BKGND_ADDA | (int(a * 255) << 8) + class _Mock(object): """Mock object needed for ReadTheDocs.""" - CData = () # This gets passed to an isinstance call. + CData = () # This gets passed to an isinstance call. @staticmethod def def_extern(): @@ -50,14 +54,16 @@ def __call__(self, *args, **kargs): def __str__(self): """Just have ? in case anything leaks as a parameter default.""" - return '?' + return "?" -if _os.environ.get('READTHEDOCS'): +if _os.environ.get("READTHEDOCS"): # Mock the lib and ffi objects needed to compile docs for readthedocs.io # Allows an import without building the cffi module first. lib = ffi = _Mock() else: - from tcod._libtcod import lib, ffi + from tcod._libtcod import lib, ffi # noqa: F401 -from tcod.constants import * +from tcod.constants import * # noqa: F4 +from tcod.constants import FOV_PERMISSIVE_0 # noqa: F402 +from tcod.constants import BKGND_ALPH, BKGND_ADDA # noqa: F402 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index f695049b..11459773 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -13,12 +13,23 @@ import numpy as _np import numpy as np -from tcod.libtcod import * +from tcod.libtcod import * # noqa: F401 F403 +from tcod.libtcod import ffi, lib +from tcod.libtcod import ( + NOISE_DEFAULT_LACUNARITY, + NOISE_DEFAULT_HURST, + NOISE_DEFAULT, + KEY_RELEASED, + BKGND_DEFAULT, + BKGND_SET, + FONT_LAYOUT_ASCII_INCOL, + RENDERER_GLSL, +) from tcod._internal import deprecate from tcod.tcod import _int, _unpack_char_p -from tcod.tcod import _bytes, _unicode, _fmt_bytes, _fmt_unicode, _fmt +from tcod.tcod import _bytes, _unicode, _fmt from tcod.tcod import _CDataWrapper from tcod.tcod import _PropagateException from tcod.tcod import _console @@ -53,7 +64,19 @@ class ConsoleBuffer(object): fore_b (int): Blue foreground color, from 0 to 255. char (AnyStr): A single character str or bytes object. """ - def __init__(self, width, height, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '): + + def __init__( + self, + width, + height, + back_r=0, + back_g=0, + back_b=0, + fore_r=0, + fore_g=0, + fore_b=0, + char=" ", + ): """initialize with given width and height. values to fill the buffer are optional, defaults to black with no characters. """ @@ -66,7 +89,16 @@ def __init__(self, width, height, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g self.height = height self.clear(back_r, back_g, back_b, fore_r, fore_g, fore_b, char) - def clear(self, back_r=0, back_g=0, back_b=0, fore_r=0, fore_g=0, fore_b=0, char=' '): + def clear( + self, + back_r=0, + back_g=0, + back_b=0, + fore_r=0, + fore_g=0, + fore_b=0, + char=" ", + ): """Clears the console. Values to fill it with are optional, defaults to black with no characters. @@ -174,9 +206,11 @@ def blit(self, dest, fill_fore=True, fill_back=True): """ if not dest: dest = tcod.console.Console._from_cdata(ffi.NULL) - if (dest.width != self.width or - dest.height != self.height): - raise ValueError('ConsoleBuffer.blit: Destination console has an incorrect size.') + if dest.width != self.width or dest.height != self.height: + raise ValueError( + "ConsoleBuffer.blit: " + "Destination console has an incorrect size." + ) if fill_back: bg = dest.bg.ravel() @@ -191,6 +225,7 @@ def blit(self, dest, fill_fore=True, fill_back=True): fg[2::3] = self.fore_b dest.ch.ravel()[:] = self.char + class Dice(_CDataWrapper): """ @@ -206,14 +241,17 @@ class Dice(_CDataWrapper): """ def __init__(self, *args, **kargs): - warnings.warn("Using this class is not recommended.", - DeprecationWarning, stacklevel=2) + warnings.warn( + "Using this class is not recommended.", + DeprecationWarning, + stacklevel=2, + ) super(Dice, self).__init__(*args, **kargs) if self.cdata == ffi.NULL: self._init(*args, **kargs) def _init(self, nb_dices=0, nb_faces=0, multiplier=0, addsub=0): - self.cdata = ffi.new('TCOD_dice_t*') + self.cdata = ffi.new("TCOD_dice_t*") self.nb_dices = nb_dices self.nb_faces = nb_faces self.multiplier = multiplier @@ -221,26 +259,38 @@ def _init(self, nb_dices=0, nb_faces=0, multiplier=0, addsub=0): def _get_nb_dices(self): return self.nb_rolls + def _set_nb_dices(self, value): self.nb_rolls = value + nb_dices = property(_get_nb_dices, _set_nb_dices) def __str__(self): - add = '+(%s)' % self.addsub if self.addsub != 0 else '' - return '%id%ix%s%s' % (self.nb_dices, self.nb_faces, - self.multiplier, add) + add = "+(%s)" % self.addsub if self.addsub != 0 else "" + return "%id%ix%s%s" % ( + self.nb_dices, + self.nb_faces, + self.multiplier, + add, + ) def __repr__(self): - return ('%s(nb_dices=%r,nb_faces=%r,multiplier=%r,addsub=%r)' % - (self.__class__.__name__, self.nb_dices, self.nb_faces, - self.multiplier, self.addsub)) + return "%s(nb_dices=%r,nb_faces=%r,multiplier=%r,addsub=%r)" % ( + self.__class__.__name__, + self.nb_dices, + self.nb_faces, + self.multiplier, + self.addsub, + ) # reverse lookup table for KEY_X attributes, used by Key.__repr__ _LOOKUP_VK = { - value:'KEY_%s' % key[6:] for key,value in lib.__dict__.items() - if key.startswith('TCODK') - } + value: "KEY_%s" % key[6:] + for key, value in lib.__dict__.items() + if key.startswith("TCODK") +} + class Key(_CDataWrapper): """Key Event instance @@ -248,8 +298,10 @@ class Key(_CDataWrapper): Attributes: vk (int): TCOD_keycode_t key code c (int): character if vk == TCODK_CHAR else 0 - text (Text): text[TCOD_KEY_TEXT_SIZE]; text if vk == TCODK_TEXT else text[0] == '\0' - pressed (bool): does this correspond to a key press or key release event ? + text (Text): text[TCOD_KEY_TEXT_SIZE]; + text if vk == TCODK_TEXT else text[0] == '\0' + pressed (bool): does this correspond to a key press or key release + event? lalt (bool): True when left alt is held. lctrl (bool): True when left control is held. lmeta (bool): True when left meta key is held. @@ -259,16 +311,35 @@ class Key(_CDataWrapper): shift (bool): True when any shift is held. """ - _BOOL_ATTRIBUTES = ('lalt', 'lctrl', 'lmeta', - 'ralt', 'rctrl', 'rmeta', 'pressed', 'shift') + _BOOL_ATTRIBUTES = ( + "lalt", + "lctrl", + "lmeta", + "ralt", + "rctrl", + "rmeta", + "pressed", + "shift", + ) - def __init__(self, vk=0, c=0, text='', pressed=False, - lalt=False, lctrl=False, lmeta=False, - ralt=False, rctrl=False, rmeta=False, shift=False): + def __init__( + self, + vk=0, + c=0, + text="", + pressed=False, + lalt=False, + lctrl=False, + lmeta=False, + ralt=False, + rctrl=False, + rmeta=False, + shift=False, + ): if isinstance(vk, ffi.CData): self.cdata = vk return - self.cdata = ffi.new('TCOD_key_t*') + self.cdata = ffi.new("TCOD_key_t*") self.vk = vk self.c = c self.text = text @@ -284,16 +355,16 @@ def __init__(self, vk=0, c=0, text='', pressed=False, def __getattr__(self, attr): if attr in self._BOOL_ATTRIBUTES: return bool(getattr(self.cdata, attr)) - if attr == 'c': + if attr == "c": return ord(self.cdata.c) - if attr == 'text': + if attr == "text": return ffi.string(self.cdata.text).decode() return super(Key, self).__getattr__(attr) def __setattr__(self, attr, value): - if attr == 'c': - self.cdata.c = chr(value).encode('latin-1') - elif attr == 'text': + if attr == "c": + self.cdata.c = chr(value).encode("latin-1") + elif attr == "text": self.cdata.text = value.encode() else: super(Key, self).__setattr__(attr, value) @@ -301,17 +372,25 @@ def __setattr__(self, attr, value): def __repr__(self): """Return a representation of this Key object.""" params = [] - params.append('pressed=%r, vk=tcod.%s' % - (self.pressed, _LOOKUP_VK[self.vk])) + params.append( + "pressed=%r, vk=tcod.%s" % (self.pressed, _LOOKUP_VK[self.vk]) + ) if self.c: - params.append('c=ord(%r)' % chr(self.c)) + params.append("c=ord(%r)" % chr(self.c)) if self.text: - params.append('text=%r' % self.text) - for attr in ['shift', 'lalt', 'lctrl', 'lmeta', - 'ralt', 'rctrl', 'rmeta']: + params.append("text=%r" % self.text) + for attr in [ + "shift", + "lalt", + "lctrl", + "lmeta", + "ralt", + "rctrl", + "rmeta", + ]: if getattr(self, attr): - params.append('%s=%r' % (attr, getattr(self, attr))) - return 'tcod.Key(%s)' % ', '.join(params) + params.append("%s=%r" % (attr, getattr(self, attr))) + return "tcod.Key(%s)" % ", ".join(params) @property def key_p(self): @@ -340,12 +419,13 @@ class Mouse(_CDataWrapper): wheel_down (bool): Wheel down event. """ - def __init__(self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0, - **kargs): + def __init__( + self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0, **kargs + ): if isinstance(x, ffi.CData): self.cdata = x return - self.cdata = ffi.new('TCOD_mouse_t*') + self.cdata = ffi.new("TCOD_mouse_t*") self.x = x self.y = y self.dx = dx @@ -360,16 +440,23 @@ def __init__(self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0, def __repr__(self): """Return a representation of this Mouse object.""" params = [] - for attr in ['x', 'y', 'dx', 'dy', 'cx', 'cy', 'dcx', 'dcy']: + for attr in ["x", "y", "dx", "dy", "cx", "cy", "dcx", "dcy"]: if getattr(self, attr) == 0: continue - params.append('%s=%r' % (attr, getattr(self, attr))) - for attr in ['lbutton', 'rbutton', 'mbutton', - 'lbutton_pressed', 'rbutton_pressed', 'mbutton_pressed', - 'wheel_up', 'wheel_down']: + params.append("%s=%r" % (attr, getattr(self, attr))) + for attr in [ + "lbutton", + "rbutton", + "mbutton", + "lbutton_pressed", + "rbutton_pressed", + "mbutton_pressed", + "wheel_up", + "wheel_down", + ]: if getattr(self, attr): - params.append('%s=%r' % (attr, getattr(self, attr))) - return 'tcod.Mouse(%s)' % ', '.join(params) + params.append("%s=%r" % (attr, getattr(self, attr))) + return "tcod.Mouse(%s)" % ", ".join(params) @property def mouse_p(self): @@ -393,6 +480,7 @@ def bsp_new_with_size(x, y, w, h): """ return Bsp(x, y, w, h) + def bsp_split_once(node, horizontal, position): """ .. deprecated:: 2.0 @@ -400,14 +488,18 @@ def bsp_split_once(node, horizontal, position): """ node.split_once(horizontal, position) -def bsp_split_recursive(node, randomizer, nb, minHSize, minVSize, maxHRatio, - maxVRatio): + +def bsp_split_recursive( + node, randomizer, nb, minHSize, minVSize, maxHRatio, maxVRatio +): """ .. deprecated:: 2.0 Use :any:`BSP.split_recursive` instead. """ - node.split_recursive(nb, minHSize, minVSize, - maxHRatio, maxVRatio, randomizer) + node.split_recursive( + nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer + ) + @deprecate("Assign values via attribute instead.") def bsp_resize(node, x, y, w, h): @@ -420,6 +512,7 @@ def bsp_resize(node, x, y, w, h): node.width = w node.height = h + @deprecate("Access children with 'node.children' instead.") def bsp_left(node): """ @@ -428,6 +521,7 @@ def bsp_left(node): """ return None if not node.children else node.children[0] + @deprecate("Access children with 'node.children' instead.") def bsp_right(node): """ @@ -436,6 +530,7 @@ def bsp_right(node): """ return None if not node.children else node.children[1] + @deprecate("Get the parent with 'node.parent' instead.") def bsp_father(node): """ @@ -444,6 +539,7 @@ def bsp_father(node): """ return node.parent + @deprecate("Check for children with 'bool(node.children)' instead.") def bsp_is_leaf(node): """ @@ -452,6 +548,7 @@ def bsp_is_leaf(node): """ return not node.children + @deprecate("Use 'node.contains' instead.") def bsp_contains(node, cx, cy): """ @@ -460,6 +557,7 @@ def bsp_contains(node, cx, cy): """ return node.contains(cx, cy) + @deprecate("Use 'node.find_node' instead.") def bsp_find_node(node, cx, cy): """ @@ -468,6 +566,7 @@ def bsp_find_node(node, cx, cy): """ return node.find_node(cx, cy) + def _bsp_traverse(node_iter, callback, userData): """pack callback into a handle for use with the callback _pycall_bsp_callback @@ -475,6 +574,7 @@ def _bsp_traverse(node_iter, callback, userData): for node in node_iter: callback(node, userData) + @deprecate("Iterate over nodes using 'for n in node.pre_order():' instead.") def bsp_traverse_pre_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. @@ -484,6 +584,7 @@ def bsp_traverse_pre_order(node, callback, userData=0): """ _bsp_traverse(node.pre_order(), callback, userData) + @deprecate("Iterate over nodes using 'for n in node.in_order():' instead.") def bsp_traverse_in_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. @@ -493,6 +594,7 @@ def bsp_traverse_in_order(node, callback, userData=0): """ _bsp_traverse(node.in_order(), callback, userData) + @deprecate("Iterate over nodes using 'for n in node.post_order():' instead.") def bsp_traverse_post_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. @@ -502,6 +604,7 @@ def bsp_traverse_post_order(node, callback, userData=0): """ _bsp_traverse(node.post_order(), callback, userData) + @deprecate("Iterate over nodes using 'for n in node.level_order():' instead.") def bsp_traverse_level_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. @@ -511,8 +614,11 @@ def bsp_traverse_level_order(node, callback, userData=0): """ _bsp_traverse(node.level_order(), callback, userData) -@deprecate("Iterate over nodes using " - "'for n in node.inverted_level_order():' instead.") + +@deprecate( + "Iterate over nodes using " + "'for n in node.inverted_level_order():' instead." +) def bsp_traverse_inverted_level_order(node, callback, userData=0): """Traverse this nodes hierarchy with a callback. @@ -521,6 +627,7 @@ def bsp_traverse_inverted_level_order(node, callback, userData=0): """ _bsp_traverse(node.inverted_level_order(), callback, userData) + @deprecate("Delete bsp children using 'node.children = ()' instead.") def bsp_remove_sons(node): """Delete all children of a given node. Not recommended. @@ -534,6 +641,7 @@ def bsp_remove_sons(node): """ node.children = () + @deprecate("libtcod objects are deleted automatically.") def bsp_delete(node): # type: (Any) -> None @@ -547,6 +655,7 @@ def bsp_delete(node): BSP deletion is automatic. """ + def color_lerp(c1, c2, a): """Return the linear interpolation between two colors. @@ -565,6 +674,7 @@ def color_lerp(c1, c2, a): """ return Color._new_from_cdata(lib.TCOD_color_lerp(c1, c2, a)) + def color_set_hsv(c, h, s, v): """Set a color using: hue, saturation, and value parameters. @@ -576,10 +686,11 @@ def color_set_hsv(c, h, s, v): s (float): Saturation, from 0 to 1. v (float): Value, from 0 to 1. """ - new_color = ffi.new('TCOD_color_t*') + new_color = ffi.new("TCOD_color_t*") lib.TCOD_color_set_HSV(new_color, h, s, v) c[:] = new_color.r, new_color.g, new_color.b + def color_get_hsv(c): """Return the (hue, saturation, value) of a color. @@ -591,10 +702,11 @@ def color_get_hsv(c): Tuple[float, float, float]: A tuple with (hue, saturation, value) values, from 0 to 1. """ - hsv = ffi.new('float [3]') + hsv = ffi.new("float [3]") lib.TCOD_color_get_HSV(c, hsv, hsv + 1, hsv + 2) return hsv[0], hsv[1], hsv[2] + def color_scale_HSV(c, scoef, vcoef): """Scale a color's saturation and value. @@ -607,11 +719,12 @@ def color_scale_HSV(c, scoef, vcoef): vcoef (float): Value multiplier, from 0 to 1. Use 1 to keep current value. """ - color_p = ffi.new('TCOD_color_t*') + color_p = ffi.new("TCOD_color_t*") color_p.r, color_p.g, color_p.b = c.r, c.g, c.b lib.TCOD_color_scale_HSV(color_p, scoef, vcoef) c[:] = color_p.r, color_p.g, color_p.b + def color_gen_map(colors, indexes): """Return a smoothly defined scale of colors. @@ -632,16 +745,21 @@ def color_gen_map(colors, indexes): [Color(0, 0, 0), Color(51, 25, 0), Color(102, 51, 0), \ Color(153, 76, 0), Color(204, 102, 0), Color(255, 128, 0)] """ - ccolors = ffi.new('TCOD_color_t[]', colors) - cindexes = ffi.new('int[]', indexes) - cres = ffi.new('TCOD_color_t[]', max(indexes) + 1) + ccolors = ffi.new("TCOD_color_t[]", colors) + cindexes = ffi.new("int[]", indexes) + cres = ffi.new("TCOD_color_t[]", max(indexes) + 1) lib.TCOD_color_gen_map(cres, len(colors), ccolors, cindexes) return [Color._new_from_cdata(cdata) for cdata in cres] def console_init_root( - w: int, h: int, title: Optional[AnyStr]=None, fullscreen: bool=False, - renderer: Optional[int]=None, order: str='C') -> tcod.console.Console: + w: int, + h: int, + title: Optional[AnyStr] = None, + fullscreen: bool = False, + renderer: Optional[int] = None, + order: str = "C", +) -> tcod.console.Console: """Set up the primary display and return the root console. `w` and `h` are the columns and rows of the new window (in tiles.) @@ -675,15 +793,18 @@ def console_init_root( # Use the scripts filename as the title. title = os.path.basename(sys.argv[0]) if renderer is None: - renderer = RENDERER_GLSL # Stable for now. + renderer = RENDERER_GLSL # Stable for now. title = _bytes(title) lib.TCOD_console_init_root(w, h, title, fullscreen, renderer) return tcod.console.Console._get_root(order) -def console_set_custom_font(fontFile: AnyStr, flags: - int=FONT_LAYOUT_ASCII_INCOL, - nb_char_horiz: int=0, nb_char_vertic: int=0): +def console_set_custom_font( + fontFile: AnyStr, + flags: int = FONT_LAYOUT_ASCII_INCOL, + nb_char_horiz: int = 0, + nb_char_vertic: int = 0, +): """Load the custom font file at `fontFile`. Call this before function before calling :any:`tcod.console_init_root`. @@ -706,11 +827,13 @@ def console_set_custom_font(fontFile: AnyStr, flags: file respectfully. """ if not os.path.exists(fontFile): - raise RuntimeError("File not found:\n\t%s" - % (os.path.realpath(fontFile),)) + raise RuntimeError( + "File not found:\n\t%s" % (os.path.realpath(fontFile),) + ) fontFile = _bytes(fontFile) - lib.TCOD_console_set_custom_font(fontFile, flags, - nb_char_horiz, nb_char_vertic) + lib.TCOD_console_set_custom_font( + fontFile, flags, nb_char_horiz, nb_char_vertic + ) def console_get_width(con): @@ -727,6 +850,7 @@ def console_get_width(con): """ return lib.TCOD_console_get_width(_console(con)) + def console_get_height(con): """Return the height of a console. @@ -741,6 +865,7 @@ def console_get_height(con): """ return lib.TCOD_console_get_height(_console(con)) + def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY): """Set a character code to new coordinates on the tile-set. @@ -755,11 +880,14 @@ def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY): fontCharY (int): The Y tile coordinate on the loaded tileset. 0 is the topmost tile. """ - lib.TCOD_console_map_ascii_code_to_font(_int(asciiCode), fontCharX, - fontCharY) + lib.TCOD_console_map_ascii_code_to_font( + _int(asciiCode), fontCharX, fontCharY + ) -def console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, fontCharX, - fontCharY): + +def console_map_ascii_codes_to_font( + firstAsciiCode, nbCodes, fontCharX, fontCharY +): """Remap a contiguous set of codes to a contiguous set of tiles. Both the tile-set and character codes must be contiguous to use this @@ -775,8 +903,10 @@ def console_map_ascii_codes_to_font(firstAsciiCode, nbCodes, fontCharX, 0 is the topmost tile. """ - lib.TCOD_console_map_ascii_codes_to_font(_int(firstAsciiCode), nbCodes, - fontCharX, fontCharY) + lib.TCOD_console_map_ascii_codes_to_font( + _int(firstAsciiCode), nbCodes, fontCharX, fontCharY + ) + def console_map_string_to_font(s, fontCharX, fontCharY): """Remap a string of codes to a contiguous set of tiles. @@ -792,6 +922,7 @@ def console_map_string_to_font(s, fontCharX, fontCharY): """ lib.TCOD_console_map_string_to_font_utf(_unicode(s), fontCharX, fontCharY) + def console_is_fullscreen(): """Returns True if the display is fullscreen. @@ -800,6 +931,7 @@ def console_is_fullscreen(): """ return bool(lib.TCOD_console_is_fullscreen()) + def console_set_fullscreen(fullscreen): """Change the display to be fullscreen or windowed. @@ -809,18 +941,22 @@ def console_set_fullscreen(fullscreen): """ lib.TCOD_console_set_fullscreen(fullscreen) + def console_is_window_closed(): """Returns True if the window has received and exit event.""" return lib.TCOD_console_is_window_closed() + def console_has_mouse_focus(): """Return True if the window has mouse focus.""" return lib.TCOD_console_has_mouse_focus() + def console_is_active(): """Return True if the window has keyboard focus.""" return lib.TCOD_console_is_active() + def console_set_window_title(title): """Change the current title bar string. @@ -830,19 +966,24 @@ def console_set_window_title(title): title = _bytes(title) lib.TCOD_console_set_window_title(title) + def console_credits(): lib.TCOD_console_credits() + def console_credits_reset(): lib.TCOD_console_credits_reset() + def console_credits_render(x, y, alpha): return lib.TCOD_console_credits_render(x, y, alpha) + def console_flush(): """Update the display to represent the root consoles current state.""" lib.TCOD_console_flush() + # drawing on a console def console_set_default_background(con, col): """Change the default background color for a console. @@ -854,6 +995,7 @@ def console_set_default_background(con, col): """ lib.TCOD_console_set_default_background(_console(con), col) + def console_set_default_foreground(con, col): """Change the default foreground color for a console. @@ -864,6 +1006,7 @@ def console_set_default_foreground(con, col): """ lib.TCOD_console_set_default_foreground(_console(con), col) + def console_clear(con): """Reset a console to its default colors and the space character. @@ -876,6 +1019,7 @@ def console_clear(con): """ return lib.TCOD_console_clear(_console(con)) + def console_put_char(con, x, y, c, flag=BKGND_DEFAULT): """Draw the character c at x,y using the default colors and a blend mode. @@ -888,6 +1032,7 @@ def console_put_char(con, x, y, c, flag=BKGND_DEFAULT): """ lib.TCOD_console_put_char(_console(con), x, y, _int(c), flag) + def console_put_char_ex(con, x, y, c, fore, back): """Draw the character c at x,y using the colors fore and back. @@ -903,6 +1048,7 @@ def console_put_char_ex(con, x, y, c, fore, back): """ lib.TCOD_console_put_char_ex(_console(con), x, y, _int(c), fore, back) + def console_set_char_background(con, x, y, col, flag=BKGND_SET): """Change the background color of x,y to col using a blend mode. @@ -916,6 +1062,7 @@ def console_set_char_background(con, x, y, col, flag=BKGND_SET): """ lib.TCOD_console_set_char_background(_console(con), x, y, col, flag) + @deprecate("Directly access a consoles foreground color with `console.fg`") def console_set_char_foreground(con, x, y, col): """Change the foreground color of x,y to col. @@ -929,6 +1076,7 @@ def console_set_char_foreground(con, x, y, col): """ lib.TCOD_console_set_char_foreground(_console(con), x, y, col) + @deprecate("Directly access a consoles characters with `console.ch`") def console_set_char(con, x, y, c): """Change the character at x,y to c, keeping the current colors. @@ -941,6 +1089,7 @@ def console_set_char(con, x, y, c): """ lib.TCOD_console_set_char(_console(con), x, y, _int(c)) + def console_set_background_flag(con, flag): """Change the default blend mode for this console. @@ -950,6 +1099,7 @@ def console_set_background_flag(con, flag): """ lib.TCOD_console_set_background_flag(_console(con), flag) + def console_get_background_flag(con): """Return this consoles current blend mode. @@ -958,6 +1108,7 @@ def console_get_background_flag(con): """ return lib.TCOD_console_get_background_flag(_console(con)) + def console_set_alignment(con, alignment): """Change this consoles current alignment mode. @@ -971,6 +1122,7 @@ def console_set_alignment(con, alignment): """ lib.TCOD_console_set_alignment(_console(con), alignment) + def console_get_alignment(con): """Return this consoles current alignment mode. @@ -979,6 +1131,7 @@ def console_get_alignment(con): """ return lib.TCOD_console_get_alignment(_console(con)) + def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: """Print a color formatted string on a console. @@ -990,8 +1143,15 @@ def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: """ lib.TCOD_console_printf(_console(con), x, y, _fmt(fmt)) -def console_print_ex(con: tcod.console.Console, x: int, y: int, flag: int, - alignment: int, fmt: str) -> None: + +def console_print_ex( + con: tcod.console.Console, + x: int, + y: int, + flag: int, + alignment: int, + fmt: str, +) -> None: """Print a string on a console using a blend mode and alignment mode. Args: @@ -999,11 +1159,12 @@ def console_print_ex(con: tcod.console.Console, x: int, y: int, flag: int, x (int): Character x position from the left. y (int): Character y position from the top. """ - lib.TCOD_console_printf_ex(_console(con), - x, y, flag, alignment, _fmt(fmt)) + lib.TCOD_console_printf_ex(_console(con), x, y, flag, alignment, _fmt(fmt)) + -def console_print_rect(con: tcod.console.Console, - x: int, y: int, w: int, h: int, fmt: str) -> int: +def console_print_rect( + con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str +) -> int: """Print a string constrained to a rectangle. If h > 0 and the bottom of the rectangle is reached, @@ -1015,56 +1176,96 @@ def console_print_rect(con: tcod.console.Console, Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_printf_rect( - _console(con), x, y, w, h, _fmt(fmt)) + return lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)) + -def console_print_rect_ex(con: tcod.console.Console, - x: int, y: int, w: int, h: int, - flag: int, alignment: int, fmt: str) -> int: +def console_print_rect_ex( + con: tcod.console.Console, + x: int, + y: int, + w: int, + h: int, + flag: int, + alignment: int, + fmt: str, +) -> int: """Print a string constrained to a rectangle with blend and alignment. Returns: int: The number of lines of text once word-wrapped. """ return lib.TCOD_console_printf_rect_ex( - _console(con), x, y, w, h, flag, alignment, _fmt(fmt)) + _console(con), x, y, w, h, flag, alignment, _fmt(fmt) + ) + -def console_get_height_rect(con: tcod.console.Console, - x: int, y: int, w: int, h: int, fmt: str) -> int: +def console_get_height_rect( + con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str +) -> int: """Return the height of this text once word-wrapped into this rectangle. Returns: int: The number of lines of text once word-wrapped. """ return lib.TCOD_console_get_height_rect_fmt( - _console(con), x, y, w, h, _fmt(fmt)) + _console(con), x, y, w, h, _fmt(fmt) + ) + -def console_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, - clr: bool, flag: int=BKGND_DEFAULT) -> None: +def console_rect( + con: tcod.console.Console, + x: int, + y: int, + w: int, + h: int, + clr: bool, + flag: int = BKGND_DEFAULT, +) -> None: """Draw a the background color on a rect optionally clearing the text. If clr is True the affected tiles are changed to space character. """ lib.TCOD_console_rect(_console(con), x, y, w, h, clr, flag) -def console_hline(con: tcod.console.Console, x: int, y: int, l: int, - flag: int=BKGND_DEFAULT) -> None: + +def console_hline( + con: tcod.console.Console, + x: int, + y: int, + l: int, + flag: int = BKGND_DEFAULT, +) -> None: """Draw a horizontal line on the console. This always uses the character 196, the horizontal line character. """ lib.TCOD_console_hline(_console(con), x, y, l, flag) -def console_vline(con: tcod.console.Console, x: int, y: int, l: int, - flag: int=BKGND_DEFAULT): + +def console_vline( + con: tcod.console.Console, + x: int, + y: int, + l: int, + flag: int = BKGND_DEFAULT, +): """Draw a vertical line on the console. This always uses the character 179, the vertical line character. """ lib.TCOD_console_vline(_console(con), x, y, l, flag) -def console_print_frame(con, x: int, y: int, w: int, h: int, clear: bool=True, - flag: int=BKGND_DEFAULT, fmt: str='') -> None: + +def console_print_frame( + con, + x: int, + y: int, + w: int, + h: int, + clear: bool = True, + flag: int = BKGND_DEFAULT, + fmt: str = "", +) -> None: """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the @@ -1079,6 +1280,7 @@ def console_print_frame(con, x: int, y: int, w: int, h: int, clear: bool=True, fmt = _fmt(fmt) if fmt else ffi.NULL lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) + def console_set_color_control(con, fore, back): """Configure :any:`color controls`. @@ -1091,42 +1293,55 @@ def console_set_color_control(con, fore, back): """ lib.TCOD_console_set_color_control(con, fore, back) + def console_get_default_background(con): """Return this consoles default background color.""" return Color._new_from_cdata( - lib.TCOD_console_get_default_background(_console(con))) + lib.TCOD_console_get_default_background(_console(con)) + ) + def console_get_default_foreground(con): """Return this consoles default foreground color.""" return Color._new_from_cdata( - lib.TCOD_console_get_default_foreground(_console(con))) + lib.TCOD_console_get_default_foreground(_console(con)) + ) + @deprecate("Directly access a consoles background color with `console.bg`") def console_get_char_background(con, x, y): """Return the background color at the x,y of this console.""" return Color._new_from_cdata( - lib.TCOD_console_get_char_background(_console(con), x, y)) + lib.TCOD_console_get_char_background(_console(con), x, y) + ) + @deprecate("Directly access a consoles foreground color with `console.fg`") def console_get_char_foreground(con, x, y): """Return the foreground color at the x,y of this console.""" return Color._new_from_cdata( - lib.TCOD_console_get_char_foreground(_console(con), x, y)) + lib.TCOD_console_get_char_foreground(_console(con), x, y) + ) + @deprecate("Directly access a consoles characters with `console.ch`") def console_get_char(con, x, y): """Return the character at the x,y of this console.""" return lib.TCOD_console_get_char(_console(con), x, y) + def console_set_fade(fade, fadingColor): lib.TCOD_console_set_fade(fade, fadingColor) + def console_get_fade(): return lib.TCOD_console_get_fade() + def console_get_fading_color(): return Color._new_from_cdata(lib.TCOD_console_get_fading_color()) + # handling keyboard input def console_wait_for_keypress(flush): """Block until the user presses a key, then returns a new Key. @@ -1142,19 +1357,23 @@ def console_wait_for_keypress(flush): lib.TCOD_console_wait_for_keypress_wrapper(key.key_p, flush) return key + def console_check_for_keypress(flags=KEY_RELEASED): key = Key() lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags) return key + def console_is_key_pressed(key): return lib.TCOD_console_is_key_pressed(key) + # using offscreen consoles def console_new(w, h): """Return an offscreen console of size: w,h.""" return tcod.console.Console(w, h) + def console_from_file(filename): """Return a new console object from a filename. @@ -1167,20 +1386,24 @@ def console_from_file(filename): Returns: A new :any`Console` instance. """ return tcod.console.Console._from_cdata( - lib.TCOD_console_from_file(filename.encode('utf-8'))) + lib.TCOD_console_from_file(filename.encode("utf-8")) + ) -def console_blit(src, x, y, w, h, dst, xdst, ydst, ffade=1.0,bfade=1.0): + +def console_blit(src, x, y, w, h, dst, xdst, ydst, ffade=1.0, bfade=1.0): """Blit the console src from x,y,w,h to console dst at xdst,ydst.""" lib.TCOD_console_blit( - _console(src), x, y, w, h, - _console(dst), xdst, ydst, ffade, bfade) + _console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade + ) + def console_set_key_color(con, col): """Set a consoles blit transparent color.""" lib.TCOD_console_set_key_color(_console(con), col) - if hasattr(con, 'set_key_color'): + if hasattr(con, "set_key_color"): con.set_key_color(col) + def console_delete(con): # type: (Console) -> None """Closes the window if `con` is the root console. @@ -1195,6 +1418,7 @@ def console_delete(con): if con == ffi.NULL: lib.TCOD_console_delete(con) + # fast color filling def console_fill_foreground(con, r, g, b): """Fill the foregound of a console with r,g,b. @@ -1206,24 +1430,28 @@ def console_fill_foreground(con, r, g, b): b (Sequence[int]): An array of integers with a length of width*height. """ if len(r) != len(g) or len(r) != len(b): - raise TypeError('R, G and B must all have the same size.') - if (isinstance(r, _np.ndarray) and isinstance(g, _np.ndarray) and - isinstance(b, _np.ndarray)): - #numpy arrays, use numpy's ctypes functions + raise TypeError("R, G and B must all have the same size.") + if ( + isinstance(r, _np.ndarray) + and isinstance(g, _np.ndarray) + and isinstance(b, _np.ndarray) + ): + # numpy arrays, use numpy's ctypes functions r = _np.ascontiguousarray(r, dtype=_np.intc) g = _np.ascontiguousarray(g, dtype=_np.intc) b = _np.ascontiguousarray(b, dtype=_np.intc) - cr = ffi.cast('int *', r.ctypes.data) - cg = ffi.cast('int *', g.ctypes.data) - cb = ffi.cast('int *', b.ctypes.data) + cr = ffi.cast("int *", r.ctypes.data) + cg = ffi.cast("int *", g.ctypes.data) + cb = ffi.cast("int *", b.ctypes.data) else: # otherwise convert using ffi arrays - cr = ffi.new('int[]', r) - cg = ffi.new('int[]', g) - cb = ffi.new('int[]', b) + cr = ffi.new("int[]", r) + cg = ffi.new("int[]", g) + cb = ffi.new("int[]", b) lib.TCOD_console_fill_foreground(_console(con), cr, cg, cb) + def console_fill_background(con, r, g, b): """Fill the backgound of a console with r,g,b. @@ -1234,77 +1462,87 @@ def console_fill_background(con, r, g, b): b (Sequence[int]): An array of integers with a length of width*height. """ if len(r) != len(g) or len(r) != len(b): - raise TypeError('R, G and B must all have the same size.') - if (isinstance(r, _np.ndarray) and isinstance(g, _np.ndarray) and - isinstance(b, _np.ndarray)): - #numpy arrays, use numpy's ctypes functions + raise TypeError("R, G and B must all have the same size.") + if ( + isinstance(r, _np.ndarray) + and isinstance(g, _np.ndarray) + and isinstance(b, _np.ndarray) + ): + # numpy arrays, use numpy's ctypes functions r = _np.ascontiguousarray(r, dtype=_np.intc) g = _np.ascontiguousarray(g, dtype=_np.intc) b = _np.ascontiguousarray(b, dtype=_np.intc) - cr = ffi.cast('int *', r.ctypes.data) - cg = ffi.cast('int *', g.ctypes.data) - cb = ffi.cast('int *', b.ctypes.data) + cr = ffi.cast("int *", r.ctypes.data) + cg = ffi.cast("int *", g.ctypes.data) + cb = ffi.cast("int *", b.ctypes.data) else: # otherwise convert using ffi arrays - cr = ffi.new('int[]', r) - cg = ffi.new('int[]', g) - cb = ffi.new('int[]', b) + cr = ffi.new("int[]", r) + cg = ffi.new("int[]", g) + cb = ffi.new("int[]", b) lib.TCOD_console_fill_background(_console(con), cr, cg, cb) -def console_fill_char(con,arr): + +def console_fill_char(con: tcod.console.Console, arr: Sequence[int]): """Fill the character tiles of a console with an array. - Args: - con (Console): Any Console instance. - arr (Sequence[int]): An array of integers with a length of width*height. + `arr` is an array of integers with a length of the consoles width and + height. """ if isinstance(arr, _np.ndarray): - #numpy arrays, use numpy's ctypes functions + # numpy arrays, use numpy's ctypes functions arr = _np.ascontiguousarray(arr, dtype=_np.intc) - carr = ffi.cast('int *', arr.ctypes.data) + carr = ffi.cast("int *", arr.ctypes.data) else: - #otherwise convert using the ffi module - carr = ffi.new('int[]', arr) + # otherwise convert using the ffi module + carr = ffi.new("int[]", arr) lib.TCOD_console_fill_char(_console(con), carr) + def console_load_asc(con, filename): """Update a console from a non-delimited ASCII `.asc` file.""" - return lib.TCOD_console_load_asc(_console(con), filename.encode('utf-8')) + return lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8")) + def console_save_asc(con, filename): """Save a console to a non-delimited ASCII `.asc` file.""" - return lib.TCOD_console_save_asc(_console(con), filename.encode('utf-8')) + return lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8")) + def console_load_apf(con, filename): """Update a console from an ASCII Paint `.apf` file.""" - return lib.TCOD_console_load_apf(_console(con), filename.encode('utf-8')) + return lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8")) + def console_save_apf(con, filename): """Save a console to an ASCII Paint `.apf` file.""" - return lib.TCOD_console_save_apf(_console(con), filename.encode('utf-8')) + return lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8")) + def console_load_xp(con, filename): """Update a console from a REXPaint `.xp` file.""" - return lib.TCOD_console_load_xp(_console(con), filename.encode('utf-8')) + return lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8")) + def console_save_xp(con, filename, compress_level=9): """Save a console to a REXPaint `.xp` file.""" return lib.TCOD_console_save_xp( - _console(con), - filename.encode('utf-8'), - compress_level, - ) + _console(con), filename.encode("utf-8"), compress_level + ) + def console_from_xp(filename): """Return a single console from a REXPaint `.xp` file.""" return tcod.console.Console._from_cdata( - lib.TCOD_console_from_xp(filename.encode('utf-8'))) + lib.TCOD_console_from_xp(filename.encode("utf-8")) + ) + def console_list_load_xp(filename): """Return a list of consoles from a REXPaint `.xp` file.""" - tcod_list = lib.TCOD_console_list_from_xp(filename.encode('utf-8')) + tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) if tcod_list == ffi.NULL: return None try: @@ -1312,12 +1550,13 @@ def console_list_load_xp(filename): lib.TCOD_list_reverse(tcod_list) while not lib.TCOD_list_is_empty(tcod_list): python_list.append( - tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list)), - ) + tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list)) + ) return python_list finally: lib.TCOD_list_delete(tcod_list) + def console_list_save_xp(console_list, filename, compress_level=9): """Save a list of consoles to a REXPaint `.xp` file.""" tcod_list = lib.TCOD_list_new() @@ -1325,11 +1564,12 @@ def console_list_save_xp(console_list, filename, compress_level=9): for console in console_list: lib.TCOD_list_push(tcod_list, _console(console)) return lib.TCOD_console_list_save_xp( - tcod_list, filename.encode('utf-8'), compress_level - ) + tcod_list, filename.encode("utf-8"), compress_level + ) finally: lib.TCOD_list_delete(tcod_list) + def path_new_using_map(m, dcost=1.41): """Return a new AStar using the given Map. @@ -1342,6 +1582,7 @@ def path_new_using_map(m, dcost=1.41): """ return tcod.path.AStar(m, dcost) + def path_new_using_function(w, h, func, userData=0, dcost=1.41): """Return a new AStar using the given callable function. @@ -1356,9 +1597,9 @@ def path_new_using_function(w, h, func, userData=0, dcost=1.41): AStar: A new AStar instance. """ return tcod.path.AStar( - tcod.path._EdgeCostFunc((func, userData), (w, h)), - dcost, - ) + tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost + ) + def path_compute(p, ox, oy, dx, dy): """Find a path from (ox, oy) to (dx, dy). Return True if path is found. @@ -1374,6 +1615,7 @@ def path_compute(p, ox, oy, dx, dy): """ return lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy) + def path_get_origin(p): """Get the current origin position. @@ -1384,11 +1626,12 @@ def path_get_origin(p): Returns: Tuple[int, int]: An (x, y) point. """ - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") lib.TCOD_path_get_origin(p._path_c, x, y) return x[0], y[0] + def path_get_destination(p): """Get the current destination position. @@ -1397,11 +1640,12 @@ def path_get_destination(p): Returns: Tuple[int, int]: An (x, y) point. """ - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") lib.TCOD_path_get_destination(p._path_c, x, y) return x[0], y[0] + def path_size(p): """Return the current length of the computed path. @@ -1412,6 +1656,7 @@ def path_size(p): """ return lib.TCOD_path_size(p._path_c) + def path_reverse(p): """Reverse the direction of a path. @@ -1422,6 +1667,7 @@ def path_reverse(p): """ lib.TCOD_path_reverse(p._path_c) + def path_get(p, idx): """Get a point on a path. @@ -1429,11 +1675,12 @@ def path_get(p, idx): p (AStar): An AStar instance. idx (int): Should be in range: 0 <= inx < :any:`path_size` """ - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") lib.TCOD_path_get(p._path_c, idx, x, y) return x[0], y[0] + def path_is_empty(p): """Return True if a path is empty. @@ -1444,6 +1691,7 @@ def path_is_empty(p): """ return lib.TCOD_path_is_empty(p._path_c) + def path_walk(p, recompute): """Return the next (x, y) point in a path, or (None, None) if it's empty. @@ -1457,11 +1705,12 @@ def path_walk(p, recompute): Union[Tuple[int, int], Tuple[None, None]]: A single (x, y) point, or (None, None) """ - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") if lib.TCOD_path_walk(p._path_c, x, y, recompute): return x[0], y[0] - return None,None + return None, None + @deprecate("libtcod objects are deleted automatically.") def path_delete(p): @@ -1471,45 +1720,55 @@ def path_delete(p): This function exists for backwards compatibility with libtcodpy. """ + def dijkstra_new(m, dcost=1.41): return tcod.path.Dijkstra(m, dcost) + def dijkstra_new_using_function(w, h, func, userData=0, dcost=1.41): return tcod.path.Dijkstra( - tcod.path._EdgeCostFunc((func, userData), (w, h)), - dcost, - ) + tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost + ) + def dijkstra_compute(p, ox, oy): lib.TCOD_dijkstra_compute(p._path_c, ox, oy) + def dijkstra_path_set(p, x, y): return lib.TCOD_dijkstra_path_set(p._path_c, x, y) + def dijkstra_get_distance(p, x, y): return lib.TCOD_dijkstra_get_distance(p._path_c, x, y) + def dijkstra_size(p): return lib.TCOD_dijkstra_size(p._path_c) + def dijkstra_reverse(p): lib.TCOD_dijkstra_reverse(p._path_c) + def dijkstra_get(p, idx): - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") lib.TCOD_dijkstra_get(p._path_c, idx, x, y) return x[0], y[0] + def dijkstra_is_empty(p): return lib.TCOD_dijkstra_is_empty(p._path_c) + def dijkstra_path_walk(p): - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") if lib.TCOD_dijkstra_path_walk(p._path_c, x, y): return x[0], y[0] - return None,None + return None, None + @deprecate("libtcod objects are deleted automatically.") def dijkstra_delete(p): @@ -1519,22 +1778,24 @@ def dijkstra_delete(p): This function exists for backwards compatibility with libtcodpy. """ + def _heightmap_cdata(array: np.ndarray) -> ffi.CData: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. """ - if array.flags['F_CONTIGUOUS']: + if array.flags["F_CONTIGUOUS"]: array = array.transpose() - if not array.flags['C_CONTIGUOUS']: - raise ValueError('array must be a contiguous segment.') + if not array.flags["C_CONTIGUOUS"]: + raise ValueError("array must be a contiguous segment.") if array.dtype != _np.float32: - raise ValueError('array dtype must be float32, not %r' % array.dtype) + raise ValueError("array dtype must be float32, not %r" % array.dtype) width, height = array.shape - pointer = ffi.cast('float *', array.ctypes.data) - return ffi.new('TCOD_heightmap_t *', (width, height, pointer)) + pointer = ffi.cast("float *", array.ctypes.data) + return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) -def heightmap_new(w: int, h: int, order: str='C') -> np.ndarray: + +def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: """Return a new numpy.ndarray formatted for use with heightmap functions. `w` and `h` are the width and height of the array. @@ -1552,31 +1813,39 @@ def heightmap_new(w: int, h: int, order: str='C') -> np.ndarray: .. versionchanged:: 8.1 Added the `order` parameter. """ - if order == 'C': - return np.zeros((h, w), _np.float32, order='C') - elif order == 'F': - return np.zeros((w, h), _np.float32, order='F') + if order == "C": + return np.zeros((h, w), _np.float32, order="C") + elif order == "F": + return np.zeros((w, h), _np.float32, order="F") else: raise ValueError("Invalid order parameter, should be 'C' or 'F'.") + def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None: """Set the value of a point on a heightmap. .. deprecated:: 2.0 `hm` is a NumPy array, so values should be assigned to it directly. """ - if hm.flags['C_CONTIGUOUS']: - warnings.warn("Assign to this heightmap with hm[i,j] = value\n" - "consider using order='F'", - DeprecationWarning, stacklevel=2) + if hm.flags["C_CONTIGUOUS"]: + warnings.warn( + "Assign to this heightmap with hm[i,j] = value\n" + "consider using order='F'", + DeprecationWarning, + stacklevel=2, + ) hm[y, x] = value - elif hm.flags['F_CONTIGUOUS']: - warnings.warn("Assign to this heightmap with hm[x,y] = value", - DeprecationWarning, stacklevel=2) + elif hm.flags["F_CONTIGUOUS"]: + warnings.warn( + "Assign to this heightmap with hm[x,y] = value", + DeprecationWarning, + stacklevel=2, + ) hm[x, y] = value else: raise ValueError("This array is not contiguous.") + @deprecate("Add a scalar to an array using `hm[:] += value`") def heightmap_add(hm: np.ndarray, value: float) -> None: """Add value to all values on this heightmap. @@ -1590,6 +1859,7 @@ def heightmap_add(hm: np.ndarray, value: float) -> None: """ hm[:] += value + @deprecate("Multiply an array with a scaler using `hm[:] *= value`") def heightmap_scale(hm: np.ndarray, value: float) -> None: """Multiply all items on this heightmap by value. @@ -1603,6 +1873,7 @@ def heightmap_scale(hm: np.ndarray, value: float) -> None: """ hm[:] *= value + @deprecate("Clear an array with`hm[:] = 0`") def heightmap_clear(hm: np.ndarray) -> None: """Add value to all values on this heightmap. @@ -1615,6 +1886,7 @@ def heightmap_clear(hm: np.ndarray) -> None: """ hm[:] = 0 + @deprecate("Clamp array values using `hm.clip(mi, ma)`") def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None: """Clamp all values on this heightmap between ``mi`` and ``ma`` @@ -1629,6 +1901,7 @@ def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None: """ hm.clip(mi, ma) + @deprecate("Copy an array using `hm2[:] = hm1[:]`, or `hm1.copy()`") def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: """Copy the heightmap ``hm1`` to ``hm2``. @@ -1642,7 +1915,10 @@ def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: """ hm2[:] = hm1[:] -def heightmap_normalize(hm: np.ndarray, mi: float=0.0, ma: float=1.0) -> None: + +def heightmap_normalize( + hm: np.ndarray, mi: float = 0.0, ma: float = 1.0 +) -> None: """Normalize heightmap values between ``mi`` and ``ma``. Args: @@ -1651,8 +1927,10 @@ def heightmap_normalize(hm: np.ndarray, mi: float=0.0, ma: float=1.0) -> None: """ lib.TCOD_heightmap_normalize(_heightmap_cdata(hm), mi, ma) -def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, - coef: float) -> None: + +def heightmap_lerp_hm( + hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: float +) -> None: """Perform linear interpolation between two heightmaps storing the result in ``hm3``. @@ -1664,12 +1942,18 @@ def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, hm3 (numpy.ndarray): A destination heightmap to store the result. coef (float): The linear interpolation coefficient. """ - lib.TCOD_heightmap_lerp_hm(_heightmap_cdata(hm1), _heightmap_cdata(hm2), - _heightmap_cdata(hm3), coef) + lib.TCOD_heightmap_lerp_hm( + _heightmap_cdata(hm1), + _heightmap_cdata(hm2), + _heightmap_cdata(hm3), + coef, + ) + @deprecate("Add 2 arrays using `hm3 = hm1 + hm2`") -def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray, - hm3: np.ndarray) -> None: +def heightmap_add_hm( + hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray +) -> None: """Add two heightmaps together and stores the result in ``hm3``. Args: @@ -1682,9 +1966,11 @@ def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray, """ hm3[:] = hm1[:] + hm2[:] + @deprecate("Multiply 2 arrays using `hm3 = hm1 * hm2`") -def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray, - hm3: np.ndarray) -> None: +def heightmap_multiply_hm( + hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray +) -> None: """Multiplies two heightmap's together and stores the result in ``hm3``. Args: @@ -1698,8 +1984,10 @@ def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray, """ hm3[:] = hm1[:] * hm2[:] -def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float, - height: float) -> None: + +def heightmap_add_hill( + hm: np.ndarray, x: float, y: float, radius: float, height: float +) -> None: """Add a hill (a half spheroid) at given position. If height == radius or -radius, the hill is a half-sphere. @@ -1713,14 +2001,17 @@ def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float, """ lib.TCOD_heightmap_add_hill(_heightmap_cdata(hm), x, y, radius, height) -def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float, - height: float) -> None: + +def heightmap_dig_hill( + hm: np.ndarray, x: float, y: float, radius: float, height: float +) -> None: """ This function takes the highest value (if height > 0) or the lowest (if height < 0) between the map and the hill. - It's main goal is to carve things in maps (like rivers) by digging hills along a curve. + It's main goal is to carve things in maps (like rivers) by digging hills + along a curve. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. @@ -1731,9 +2022,14 @@ def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float, """ lib.TCOD_heightmap_dig_hill(_heightmap_cdata(hm), x, y, radius, height) -def heightmap_rain_erosion(hm: np.ndarray, nbDrops: int, erosionCoef: float, - sedimentationCoef: float, - rnd: Optional[tcod.random.Random]=None) -> None: + +def heightmap_rain_erosion( + hm: np.ndarray, + nbDrops: int, + erosionCoef: float, + sedimentationCoef: float, + rnd: Optional[tcod.random.Random] = None, +) -> None: """Simulate the effect of rain drops on the terrain, resulting in erosion. ``nbDrops`` should be at least hm.size. @@ -1747,14 +2043,23 @@ def heightmap_rain_erosion(hm: np.ndarray, nbDrops: int, erosionCoef: float, rnd (Optional[Random]): A tcod.Random instance, or None. """ lib.TCOD_heightmap_rain_erosion( - _heightmap_cdata(hm), nbDrops, erosionCoef, - sedimentationCoef, rnd.random_c if rnd else ffi.NULL + _heightmap_cdata(hm), + nbDrops, + erosionCoef, + sedimentationCoef, + rnd.random_c if rnd else ffi.NULL, ) -def heightmap_kernel_transform(hm: np.ndarray, kernelsize: int, - dx: Sequence[int], dy: Sequence[int], - weight: Sequence[float], - minLevel: float, maxLevel: float) -> None: + +def heightmap_kernel_transform( + hm: np.ndarray, + kernelsize: int, + dx: Sequence[int], + dy: Sequence[int], + weight: Sequence[float], + minLevel: float, + maxLevel: float, +) -> None: """Apply a generic transformation on the map, so that each resulting cell value is the weighted sum of several neighbour cells. @@ -1796,15 +2101,21 @@ def heightmap_kernel_transform(hm: np.ndarray, kernelsize: int, >>> tcod.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, ... 0.0, 1.0) """ - cdx = ffi.new('int[]', dx) - cdy = ffi.new('int[]', dy) - cweight = ffi.new('float[]', weight) - lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, - cdx, cdy, cweight, minLevel, maxLevel) + cdx = ffi.new("int[]", dx) + cdy = ffi.new("int[]", dy) + cweight = ffi.new("float[]", weight) + lib.TCOD_heightmap_kernel_transform( + _heightmap_cdata(hm), kernelsize, cdx, cdy, cweight, minLevel, maxLevel + ) + -def heightmap_add_voronoi(hm: np.ndarray, nbPoints: Any, nbCoef: int, - coef: Sequence[float], - rnd: Optional[tcod.random.Random]=None) -> None: +def heightmap_add_voronoi( + hm: np.ndarray, + nbPoints: Any, + nbCoef: int, + coef: Sequence[float], + rnd: Optional[tcod.random.Random] = None, +) -> None: """Add values from a Voronoi diagram to the heightmap. Args: @@ -1819,15 +2130,28 @@ def heightmap_add_voronoi(hm: np.ndarray, nbPoints: Any, nbCoef: int, rnd (Optional[Random]): A Random instance, or None. """ nbPoints = len(coef) - ccoef = ffi.new('float[]', coef) + ccoef = ffi.new("float[]", coef) lib.TCOD_heightmap_add_voronoi( - _heightmap_cdata(hm), nbPoints, - nbCoef, ccoef, rnd.random_c if rnd else ffi.NULL) + _heightmap_cdata(hm), + nbPoints, + nbCoef, + ccoef, + rnd.random_c if rnd else ffi.NULL, + ) + @deprecate("Arrays of noise should be sampled using the tcod.noise module.") -def heightmap_add_fbm(hm: np.ndarray, noise: tcod.noise.Noise, - mulx: float, muly: float, addx: float, addy: float, - octaves: float, delta: float, scale: float) -> None: +def heightmap_add_fbm( + hm: np.ndarray, + noise: tcod.noise.Noise, + mulx: float, + muly: float, + addx: float, + addy: float, + octaves: float, + delta: float, + scale: float, +) -> None: """Add FBM noise to the heightmap. The noise coordinate for each map cell is @@ -1851,13 +2175,31 @@ def heightmap_add_fbm(hm: np.ndarray, noise: tcod.noise.Noise, as :any:`Noise.sample_ogrid`. """ noise = noise.noise_c if noise is not None else ffi.NULL - lib.TCOD_heightmap_add_fbm(_heightmap_cdata(hm), noise, - mulx, muly, addx, addy, octaves, delta, scale) + lib.TCOD_heightmap_add_fbm( + _heightmap_cdata(hm), + noise, + mulx, + muly, + addx, + addy, + octaves, + delta, + scale, + ) + @deprecate("Arrays of noise should be sampled using the tcod.noise module.") -def heightmap_scale_fbm(hm: np.ndarray, noise: tcod.noise.Noise, - mulx: float, muly: float, addx: float, addy: float, - octaves: float, delta: float, scale: float) -> None: +def heightmap_scale_fbm( + hm: np.ndarray, + noise: tcod.noise.Noise, + mulx: float, + muly: float, + addx: float, + addy: float, + octaves: float, + delta: float, + scale: float, +) -> None: """Multiply the heighmap values with FBM noise. Args: @@ -1876,14 +2218,28 @@ def heightmap_scale_fbm(hm: np.ndarray, noise: tcod.noise.Noise, as :any:`Noise.sample_ogrid`. """ noise = noise.noise_c if noise is not None else ffi.NULL - lib.TCOD_heightmap_scale_fbm(_heightmap_cdata(hm), noise, - mulx, muly, addx, addy, octaves, delta, scale) - -def heightmap_dig_bezier(hm: np.ndarray, - px: Tuple[int, int, int, int], - py: Tuple[int, int, int, int], - startRadius: float, startDepth: float, - endRadius: float, endDepth: float) -> None: + lib.TCOD_heightmap_scale_fbm( + _heightmap_cdata(hm), + noise, + mulx, + muly, + addx, + addy, + octaves, + delta, + scale, + ) + + +def heightmap_dig_bezier( + hm: np.ndarray, + px: Tuple[int, int, int, int], + py: Tuple[int, int, int, int], + startRadius: float, + startDepth: float, + endRadius: float, + endDepth: float, +) -> None: """Carve a path along a cubic Bezier curve. Both radius and depth can vary linearly along the path. @@ -1897,9 +2253,16 @@ def heightmap_dig_bezier(hm: np.ndarray, endRadius (float): The ending radius size. endDepth (float): The ending depth. """ - lib.TCOD_heightmap_dig_bezier(_heightmap_cdata(hm), px, py, startRadius, - startDepth, endRadius, - endDepth) + lib.TCOD_heightmap_dig_bezier( + _heightmap_cdata(hm), + px, + py, + startRadius, + startDepth, + endRadius, + endDepth, + ) + def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: """Return the value at ``x``, ``y`` in a heightmap. @@ -1907,21 +2270,28 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: .. deprecated:: 2.0 Access `hm` as a NumPy array instead. """ - if hm.flags['C_CONTIGUOUS']: - warnings.warn("Get a value from this heightmap with hm[i,j]\n" - "consider using order='F'", - DeprecationWarning, stacklevel=2) + if hm.flags["C_CONTIGUOUS"]: + warnings.warn( + "Get a value from this heightmap with hm[i,j]\n" + "consider using order='F'", + DeprecationWarning, + stacklevel=2, + ) return hm[y, x] - elif hm.flags['F_CONTIGUOUS']: - warnings.warn("Get a value from this heightmap with hm[x,y]", - DeprecationWarning, stacklevel=2) + elif hm.flags["F_CONTIGUOUS"]: + warnings.warn( + "Get a value from this heightmap with hm[x,y]", + DeprecationWarning, + stacklevel=2, + ) return hm[x, y] else: raise ValueError("This array is not contiguous.") -def heightmap_get_interpolated_value(hm: np.ndarray, - x: float, y: float) -> float: +def heightmap_get_interpolated_value( + hm: np.ndarray, x: float, y: float +) -> float: """Return the interpolated height at non integer coordinates. Args: @@ -1932,8 +2302,10 @@ def heightmap_get_interpolated_value(hm: np.ndarray, Returns: float: The value at ``x``, ``y``. """ - return lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), - x, y) + return lib.TCOD_heightmap_get_interpolated_value( + _heightmap_cdata(hm), x, y + ) + def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: """Return the slope between 0 and (pi / 2) at given coordinates. @@ -1948,8 +2320,10 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: """ return lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y) -def heightmap_get_normal(hm: np.ndarray, x: float, y: float, - waterLevel: float) -> Tuple[float, float, float]: + +def heightmap_get_normal( + hm: np.ndarray, x: float, y: float, waterLevel: float +) -> Tuple[float, float, float]: """Return the map normal at given coordinates. Args: @@ -1961,10 +2335,11 @@ def heightmap_get_normal(hm: np.ndarray, x: float, y: float, Returns: Tuple[float, float, float]: An (x, y, z) vector normal. """ - cn = ffi.new('float[3]') + cn = ffi.new("float[3]") lib.TCOD_heightmap_get_normal(_heightmap_cdata(hm), x, y, cn, waterLevel) return tuple(cn) + @deprecate("This function is deprecated, see documentation.") def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: """Return the number of map cells which value is between ``mi`` and ``ma``. @@ -1983,6 +2358,7 @@ def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: """ return lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma) + def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: """Returns True if the map edges are below ``waterlevel``, otherwise False. @@ -1993,8 +2369,10 @@ def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: Returns: bool: True if the map edges are below ``waterlevel``, otherwise False. """ - return lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm), - waterlevel) + return lib.TCOD_heightmap_has_land_on_border( + _heightmap_cdata(hm), waterlevel + ) + @deprecate("Use `hm.min()` and `hm.max()` instead.") def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]: @@ -2009,11 +2387,12 @@ def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]: .. deprecated:: 2.0 Use ``hm.min()`` or ``hm.max()`` instead. """ - mi = ffi.new('float *') - ma = ffi.new('float *') + mi = ffi.new("float *") + ma = ffi.new("float *") lib.TCOD_heightmap_get_minmax(_heightmap_cdata(hm), mi, ma) return mi[0], ma[0] + @deprecate("libtcod objects are deleted automatically.") def heightmap_delete(hm: Any) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2024,36 +2403,47 @@ def heightmap_delete(hm: Any) -> None: libtcod-cffi deletes heightmaps automatically. """ + def image_new(width, height): return tcod.image.Image(width, height) + def image_clear(image, col): image.clear(col) + def image_invert(image): image.invert() + def image_hflip(image): image.hflip() + def image_rotate90(image, num=1): image.rotate90(num) + def image_vflip(image): image.vflip() + def image_scale(image, neww, newh): image.scale(neww, newh) + def image_set_key_color(image, col): image.set_key_color(col) + def image_get_alpha(image, x, y): image.get_alpha(x, y) + def image_is_pixel_transparent(image, x, y): lib.TCOD_image_is_pixel_transparent(image.image_c, x, y) + def image_load(filename): """Load an image file into an Image instance and return it. @@ -2062,9 +2452,9 @@ def image_load(filename): """ filename = _bytes(filename) return tcod.image.Image._from_cdata( - ffi.gc(lib.TCOD_image_load(filename), - lib.TCOD_image_delete) - ) + ffi.gc(lib.TCOD_image_load(filename), lib.TCOD_image_delete) + ) + def image_from_console(console): """Return an Image with a Consoles pixel data. @@ -2078,36 +2468,46 @@ def image_from_console(console): ffi.gc( lib.TCOD_image_from_console(_console(console)), lib.TCOD_image_delete, - ) ) + ) + def image_refresh_console(image, console): image.refresh_console(console) + def image_get_size(image): return image.width, image.height + def image_get_pixel(image, x, y): return image.get_pixel(x, y) + def image_get_mipmap_pixel(image, x0, y0, x1, y1): return image.get_mipmap_pixel(x0, y0, x1, y1) + def image_put_pixel(image, x, y, col): image.put_pixel(x, y, col) + def image_blit(image, console, x, y, bkgnd_flag, scalex, scaley, angle): image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle) + def image_blit_rect(image, console, x, y, w, h, bkgnd_flag): image.blit_rect(console, x, y, w, h, bkgnd_flag) + def image_blit_2x(image, console, dx, dy, sx=0, sy=0, w=-1, h=-1): image.blit_2x(console, dx, dy, sx, sy, w, h) + def image_save(image, filename): image.save_as(filename) + @deprecate("libtcod objects are deleted automatically.") def image_delete(image): # type (Any) -> None @@ -2116,6 +2516,7 @@ def image_delete(image): This function exists for backwards compatibility with libtcodpy. """ + def line_init(xo, yo, xd, yd): """Initilize a line whose points will be returned by `line_step`. @@ -2134,6 +2535,7 @@ def line_init(xo, yo, xd, yd): """ lib.TCOD_line_init(xo, yo, xd, yd) + def line_step(): """After calling line_init returns (x, y) points of the line. @@ -2147,12 +2549,12 @@ def line_step(): .. deprecated:: 2.0 Use `line_iter` instead. """ - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") ret = lib.TCOD_line_step(x, y) if not ret: return x[0], y[0] - return None,None + return None, None def line(xo, yo, xd, yd, py_callback): @@ -2200,10 +2602,10 @@ def line_iter(xo, yo, xd, yd): Returns: Iterator[Tuple[int,int]]: An iterator of (x,y) points. """ - data = ffi.new('TCOD_bresenham_data_t *') + data = ffi.new("TCOD_bresenham_data_t *") lib.TCOD_line_init_mt(xo, yo, xd, yd, data) - x = ffi.new('int *') - y = ffi.new('int *') + x = ffi.new("int *") + y = ffi.new("int *") yield xo, yo while not lib.TCOD_line_step_mt(x, y, data): yield (x[0], y[0]) @@ -2232,8 +2634,8 @@ def line_where(x1, y1, x2, y2, inclusive=True): """ length = max(abs(x1 - x2), abs(y1 - y2)) + 1 array = np.ndarray((2, length), dtype=np.intc) - x = ffi.cast('int*', array[0].ctypes.data) - y = ffi.cast('int*', array[1].ctypes.data) + x = ffi.cast("int*", array[0].ctypes.data) + y = ffi.cast("int*", array[1].ctypes.data) lib.LineWhere(x1, y1, x2, y2, x, y) if not inclusive: array = array[:, 1:] @@ -2255,6 +2657,7 @@ def line_where(x1, y1, x2, y2, inclusive=True): FOV_RESTRICTIVE = 12 NB_FOV_ALGORITHMS = 13 + def map_new(w, h): # type: (int, int) -> tcod.map.Map """Return a :any:`tcod.map.Map` with a width and height. @@ -2265,6 +2668,7 @@ def map_new(w, h): """ return tcod.map.Map(w, h) + def map_copy(source, dest): # type: (tcod.map.Map, tcod.map.Map) -> None """Copy map data from `source` to `dest`. @@ -2277,6 +2681,7 @@ def map_copy(source, dest): dest.__init__(source.width, source.height, source._order) dest._Map__buffer[:] = source._Map__buffer[:] + def map_set_properties(m, x, y, isTrans, isWalk): # type: (tcod.map.Map, int, int, bool, bool) -> None """Set the properties of a single cell. @@ -2289,6 +2694,7 @@ def map_set_properties(m, x, y, isTrans, isWalk): """ lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk) + def map_clear(m, transparent=False, walkable=False): # type: (tcod.map.Map, bool, bool) -> None """Change all map cells to a specific value. @@ -2300,7 +2706,8 @@ def map_clear(m, transparent=False, walkable=False): m.transparent[:] = transparent m.walkable[:] = walkable -def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE ): + +def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE): # type: (tcod.map.Map, int, int, int, bool, int) -> None """Compute the field-of-view for a map instance. @@ -2309,6 +2716,7 @@ def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE ): """ m.compute_fov(x, y, radius, light_walls, algo) + def map_is_in_fov(m, x, y): # type: (tcod.map.Map, int, int) -> bool """Return True if the cell at x,y is lit by the last field-of-view @@ -2321,6 +2729,7 @@ def map_is_in_fov(m, x, y): """ return lib.TCOD_map_is_in_fov(m.map_c, x, y) + def map_is_transparent(m, x, y): # type: (tcod.map.Map, int, int) -> bool """ @@ -2331,6 +2740,7 @@ def map_is_transparent(m, x, y): """ return lib.TCOD_map_is_transparent(m.map_c, x, y) + def map_is_walkable(m, x, y): # type: (tcod.map.Map, int, int) -> bool """ @@ -2341,6 +2751,7 @@ def map_is_walkable(m, x, y): """ return lib.TCOD_map_is_walkable(m.map_c, x, y) + @deprecate("libtcod objects are deleted automatically.") def map_delete(m): # type (Any) -> None @@ -2349,6 +2760,7 @@ def map_delete(m): This function exists for backwards compatibility with libtcodpy. """ + def map_get_width(map): # type: (tcod.map.Map) -> int """Return the width of a map. @@ -2358,6 +2770,7 @@ def map_get_width(map): """ return map.width + def map_get_height(map): # type: (tcod.map.Map) -> int """Return the height of a map. @@ -2367,52 +2780,65 @@ def map_get_height(map): """ return map.height + def mouse_show_cursor(visible): # type: (bool) -> None """Change the visibility of the mouse cursor.""" lib.TCOD_mouse_show_cursor(visible) + def mouse_is_cursor_visible(): # type: () -> bool """Return True if the mouse cursor is visible.""" return lib.TCOD_mouse_is_cursor_visible() + def mouse_move(x, y): # type (int, int) -> None lib.TCOD_mouse_move(x, y) + def mouse_get_status(): # type: () -> Mouse return Mouse(lib.TCOD_mouse_get_status()) + def namegen_parse(filename, random=None): filename = _bytes(filename) lib.TCOD_namegen_parse(filename, random or ffi.NULL) + def namegen_generate(name): name = _bytes(name) return _unpack_char_p(lib.TCOD_namegen_generate(name, False)) + def namegen_generate_custom(name, rule): name = _bytes(name) rule = _bytes(rule) return _unpack_char_p(lib.TCOD_namegen_generate(name, rule, False)) + def namegen_get_sets(): sets = lib.TCOD_namegen_get_sets() try: lst = [] while not lib.TCOD_list_is_empty(sets): - lst.append(_unpack_char_p(ffi.cast('char *', lib.TCOD_list_pop(sets)))) + lst.append( + _unpack_char_p(ffi.cast("char *", lib.TCOD_list_pop(sets))) + ) finally: lib.TCOD_list_delete(sets) return lst + def namegen_destroy(): lib.TCOD_namegen_destroy() -def noise_new(dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY, - random=None): + +def noise_new( + dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY, random=None +): """Return a new Noise instance. Args: @@ -2426,6 +2852,7 @@ def noise_new(dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY, """ return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random) + def noise_set_type(n, typ): """Set a Noise objects default noise algorithm. @@ -2434,6 +2861,7 @@ def noise_set_type(n, typ): """ n.algorithm = typ + def noise_get(n, f, typ=NOISE_DEFAULT): """Return the noise value sampled from the ``f`` coordinate. @@ -2450,7 +2878,8 @@ def noise_get(n, f, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_ex(n.noise_c, ffi.new('float[4]', f), typ) + return lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ) + def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT): """Return the fractal Brownian motion sampled from the ``f`` coordinate. @@ -2464,8 +2893,10 @@ def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new('float[4]', f), - oc, typ) + return lib.TCOD_noise_get_fbm_ex( + n.noise_c, ffi.new("float[4]", f), oc, typ + ) + def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): """Return the turbulence noise sampled from the ``f`` coordinate. @@ -2479,8 +2910,10 @@ def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new('float[4]', f), - oc, typ) + return lib.TCOD_noise_get_turbulence_ex( + n.noise_c, ffi.new("float[4]", f), oc, typ + ) + @deprecate("libtcod objects are deleted automatically.") def noise_delete(n): @@ -2490,27 +2923,24 @@ def noise_delete(n): This function exists for backwards compatibility with libtcodpy. """ -_chr = chr -try: - _chr = unichr # Python 2 -except NameError: - pass def _unpack_union(type_, union): - ''' + """ unpack items from parser new_property (value_converter) - ''' + """ if type_ == lib.TCOD_TYPE_BOOL: return bool(union.b) elif type_ == lib.TCOD_TYPE_CHAR: - return union.c.decode('latin-1') + return union.c.decode("latin-1") elif type_ == lib.TCOD_TYPE_INT: return union.i elif type_ == lib.TCOD_TYPE_FLOAT: return union.f - elif (type_ == lib.TCOD_TYPE_STRING or - lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00): - return _unpack_char_p(union.s) + elif ( + type_ == lib.TCOD_TYPE_STRING + or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00 + ): + return _unpack_char_p(union.s) elif type_ == lib.TCOD_TYPE_COLOR: return Color._new_from_cdata(union.col) elif type_ == lib.TCOD_TYPE_DICE: @@ -2518,43 +2948,56 @@ def _unpack_union(type_, union): elif type_ & lib.TCOD_TYPE_LIST: return _convert_TCODList(union.list, type_ & 0xFF) else: - raise RuntimeError('Unknown libtcod type: %i' % type_) + raise RuntimeError("Unknown libtcod type: %i" % type_) + def _convert_TCODList(clist, type_): - return [_unpack_union(type_, lib.TDL_list_get_union(clist, i)) - for i in range(lib.TCOD_list_size(clist))] + return [ + _unpack_union(type_, lib.TDL_list_get_union(clist, i)) + for i in range(lib.TCOD_list_size(clist)) + ] + def parser_new(): return ffi.gc(lib.TCOD_parser_new(), lib.TCOD_parser_delete) + def parser_new_struct(parser, name): return lib.TCOD_parser_new_struct(parser, name) + # prevent multiple threads from messing with def_extern callbacks _parser_callback_lock = _threading.Lock() -_parser_listener = None # temporary global pointer to a listener instance +_parser_listener = None # temporary global pointer to a listener instance + @ffi.def_extern() def _pycall_parser_new_struct(struct, name): return _parser_listener.new_struct(struct, _unpack_char_p(name)) + @ffi.def_extern() def _pycall_parser_new_flag(name): return _parser_listener.new_flag(_unpack_char_p(name)) + @ffi.def_extern() def _pycall_parser_new_property(propname, type, value): - return _parser_listener.new_property(_unpack_char_p(propname), type, - _unpack_union(type, value)) + return _parser_listener.new_property( + _unpack_char_p(propname), type, _unpack_union(type, value) + ) + @ffi.def_extern() def _pycall_parser_end_struct(struct, name): return _parser_listener.end_struct(struct, _unpack_char_p(name)) + @ffi.def_extern() def _pycall_parser_error(msg): _parser_listener.error(_unpack_char_p(msg)) + def parser_run(parser, filename, listener=None): global _parser_listener filename = _bytes(filename) @@ -2563,16 +3006,15 @@ def parser_run(parser, filename, listener=None): return propagate_manager = _PropagateException() - propagate = propagate_manager.propagate clistener = ffi.new( - 'TCOD_parser_listener_t *', + "TCOD_parser_listener_t *", { - 'new_struct': lib._pycall_parser_new_struct, - 'new_flag': lib._pycall_parser_new_flag, - 'new_property': lib._pycall_parser_new_property, - 'end_struct': lib._pycall_parser_end_struct, - 'error': lib._pycall_parser_error, + "new_struct": lib._pycall_parser_new_struct, + "new_flag": lib._pycall_parser_new_flag, + "new_property": lib._pycall_parser_new_property, + "end_struct": lib._pycall_parser_end_struct, + "error": lib._pycall_parser_error, }, ) @@ -2581,6 +3023,7 @@ def parser_run(parser, filename, listener=None): with propagate_manager: lib.TCOD_parser_run(parser, filename, clistener) + @deprecate("libtcod objects are deleted automatically.") def parser_delete(parser): # type (Any) -> None @@ -2589,43 +3032,52 @@ def parser_delete(parser): This function exists for backwards compatibility with libtcodpy. """ + def parser_get_bool_property(parser, name): name = _bytes(name) return bool(lib.TCOD_parser_get_bool_property(parser, name)) + def parser_get_int_property(parser, name): name = _bytes(name) return lib.TCOD_parser_get_int_property(parser, name) + def parser_get_char_property(parser, name): name = _bytes(name) - return _chr(lib.TCOD_parser_get_char_property(parser, name)) + return chr(lib.TCOD_parser_get_char_property(parser, name)) + def parser_get_float_property(parser, name): name = _bytes(name) return lib.TCOD_parser_get_float_property(parser, name) + def parser_get_string_property(parser, name): name = _bytes(name) - return _unpack_char_p( - lib.TCOD_parser_get_string_property(parser, name)) + return _unpack_char_p(lib.TCOD_parser_get_string_property(parser, name)) + def parser_get_color_property(parser, name): name = _bytes(name) return Color._new_from_cdata( - lib.TCOD_parser_get_color_property(parser, name)) + lib.TCOD_parser_get_color_property(parser, name) + ) + def parser_get_dice_property(parser, name): - d = ffi.new('TCOD_dice_t *') + d = ffi.new("TCOD_dice_t *") name = _bytes(name) lib.TCOD_parser_get_dice_property_py(parser, name, d) return Dice(d) + def parser_get_list_property(parser, name, type): name = _bytes(name) clist = lib.TCOD_parser_get_list_property(parser, name, type) return _convert_TCODList(clist, type) + RNG_MT = 0 RNG_CMWC = 1 @@ -2635,6 +3087,7 @@ def parser_get_list_property(parser, name, type): DISTRIBUTION_GAUSSIAN_INVERSE = 3 DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4 + def random_get_instance(): """Return the default Random instance. @@ -2642,7 +3095,9 @@ def random_get_instance(): Random: A Random instance using the default random number generator. """ return tcod.random.Random._new_from_cdata( - ffi.cast('mersenne_data_t*', lib.TCOD_random_get_instance())) + ffi.cast("mersenne_data_t*", lib.TCOD_random_get_instance()) + ) + def random_new(algo=RNG_CMWC): """Return a new Random instance. Using ``algo``. @@ -2655,6 +3110,7 @@ def random_new(algo=RNG_CMWC): """ return tcod.random.Random(algo) + def random_new_from_seed(seed, algo=RNG_CMWC): """Return a new Random instance. Using the given ``seed`` and ``algo``. @@ -2668,6 +3124,7 @@ def random_new_from_seed(seed, algo=RNG_CMWC): """ return tcod.random.Random(algo, seed) + def random_set_distribution(rnd, dist): """Change the distribution mode of a random number generator. @@ -2677,6 +3134,7 @@ def random_set_distribution(rnd, dist): """ lib.TCOD_random_set_distribution(rnd.random_c if rnd else ffi.NULL, dist) + def random_get_int(rnd, mi, ma): """Return a random integer in the range: ``mi`` <= n <= ``ma``. @@ -2692,6 +3150,7 @@ def random_get_int(rnd, mi, ma): """ return lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma) + def random_get_float(rnd, mi, ma): """Return a random float in the range: ``mi`` <= n <= ``ma``. @@ -2707,7 +3166,9 @@ def random_get_float(rnd, mi, ma): in the range ``mi`` <= n <= ``ma``. """ return lib.TCOD_random_get_double( - rnd.random_c if rnd else ffi.NULL, mi, ma) + rnd.random_c if rnd else ffi.NULL, mi, ma + ) + def random_get_double(rnd, mi, ma): """Return a random float in the range: ``mi`` <= n <= ``ma``. @@ -2717,7 +3178,9 @@ def random_get_double(rnd, mi, ma): Both funtions return a double precision float. """ return lib.TCOD_random_get_double( - rnd.random_c if rnd else ffi.NULL, mi, ma) + rnd.random_c if rnd else ffi.NULL, mi, ma + ) + def random_get_int_mean(rnd, mi, ma, mean): """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. @@ -2734,7 +3197,9 @@ def random_get_int_mean(rnd, mi, ma, mean): int: A random weighted integer in the range ``mi`` <= n <= ``ma``. """ return lib.TCOD_random_get_int_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean) + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) + def random_get_float_mean(rnd, mi, ma, mean): """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. @@ -2752,7 +3217,9 @@ def random_get_float_mean(rnd, mi, ma, mean): in the range ``mi`` <= n <= ``ma``. """ return lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean) + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) + def random_get_double_mean(rnd, mi, ma, mean): """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. @@ -2762,7 +3229,9 @@ def random_get_double_mean(rnd, mi, ma, mean): Both funtions return a double precision float. """ return lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean) + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) + def random_save(rnd): """Return a copy of a random number generator. @@ -2775,10 +3244,14 @@ def random_save(rnd): """ return tcod.random.Random._new_from_cdata( ffi.gc( - ffi.cast('mersenne_data_t*', - lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL)), - lib.TCOD_random_delete), + ffi.cast( + "mersenne_data_t*", + lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL), + ), + lib.TCOD_random_delete, ) + ) + def random_restore(rnd, backup): """Restore a random number generator from a backed up copy. @@ -2787,8 +3260,8 @@ def random_restore(rnd, backup): rnd (Optional[Random]): A Random instance, or None to use the default. backup (Random): The Random instance which was used as a backup. """ - lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL, - backup.random_c) + lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL, backup.random_c) + @deprecate("libtcod objects are deleted automatically.") def random_delete(rnd): @@ -2798,35 +3271,43 @@ def random_delete(rnd): This function exists for backwards compatibility with libtcodpy. """ + def struct_add_flag(struct, name): lib.TCOD_struct_add_flag(struct, name) + def struct_add_property(struct, name, typ, mandatory): lib.TCOD_struct_add_property(struct, name, typ, mandatory) + def struct_add_value_list(struct, name, value_list, mandatory): - CARRAY = c_char_p * (len(value_list) + 1) - cvalue_list = CARRAY() - for i, value in enumerate(value_list): - cvalue_list[i] = cast(value, c_char_p) - cvalue_list[len(value_list)] = 0 - lib.TCOD_struct_add_value_list(struct, name, cvalue_list, mandatory) + c_strings = [ + ffi.new("char[]", value.encode("utf-8")) for value in value_list + ] + c_value_list = ffi.new("char*[]", c_strings) + lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory) + def struct_add_list_property(struct, name, typ, mandatory): lib.TCOD_struct_add_list_property(struct, name, typ, mandatory) + def struct_add_structure(struct, sub_struct): lib.TCOD_struct_add_structure(struct, sub_struct) + def struct_get_name(struct): return _unpack_char_p(lib.TCOD_struct_get_name(struct)) + def struct_is_mandatory(struct, name): return lib.TCOD_struct_is_mandatory(struct, name) + def struct_get_type(struct, name): return lib.TCOD_struct_get_type(struct, name) + # high precision time functions def sys_set_fps(fps): """Set the maximum frame rate. @@ -2838,6 +3319,7 @@ def sys_set_fps(fps): """ lib.TCOD_sys_set_fps(fps) + def sys_get_fps(): """Return the current frames per second. @@ -2851,6 +3333,7 @@ def sys_get_fps(): """ return lib.TCOD_sys_get_fps() + def sys_get_last_frame_length(): """Return the delta time of the last rendered frame in seconds. @@ -2859,6 +3342,7 @@ def sys_get_last_frame_length(): """ return lib.TCOD_sys_get_last_frame_length() + @deprecate("Use Python's standard 'time' module instead of this function.") def sys_sleep_milli(val): """Sleep for 'val' milliseconds. @@ -2871,6 +3355,7 @@ def sys_sleep_milli(val): """ lib.TCOD_sys_sleep_milli(val) + @deprecate("Use Python's standard 'time' module instead of this function.") def sys_elapsed_milli(): """Get number of milliseconds since the start of the program. @@ -2883,6 +3368,7 @@ def sys_elapsed_milli(): """ return lib.TCOD_sys_elapsed_milli() + @deprecate("Use Python's standard 'time' module instead of this function.") def sys_elapsed_seconds(): """Get number of seconds since the start of the program. @@ -2895,6 +3381,7 @@ def sys_elapsed_seconds(): """ return lib.TCOD_sys_elapsed_seconds() + def sys_set_renderer(renderer): """Change the current rendering mode to renderer. @@ -2905,12 +3392,14 @@ def sys_set_renderer(renderer): if tcod.console._root_console is not None: tcod.console.Console._get_root() + def sys_get_renderer(): """Return the current rendering mode. """ return lib.TCOD_sys_get_renderer() + # easy screenshots def sys_save_screenshot(name=None): """Save a screenshot to a file. @@ -2928,6 +3417,7 @@ def sys_save_screenshot(name=None): name = _bytes(name) lib.TCOD_sys_save_screenshot(name or ffi.NULL) + # custom fullscreen resolution def sys_force_fullscreen_resolution(width, height): """Force a specific resolution in fullscreen. @@ -2945,28 +3435,31 @@ def sys_force_fullscreen_resolution(width, height): """ lib.TCOD_sys_force_fullscreen_resolution(width, height) + def sys_get_current_resolution(): """Return the current resolution as (width, height) Returns: Tuple[int,int]: The current resolution. """ - w = ffi.new('int *') - h = ffi.new('int *') + w = ffi.new("int *") + h = ffi.new("int *") lib.TCOD_sys_get_current_resolution(w, h) return w[0], h[0] + def sys_get_char_size(): """Return the current fonts character size as (width, height) Returns: Tuple[int,int]: The current font glyph size in (width, height) """ - w = ffi.new('int *') - h = ffi.new('int *') + w = ffi.new("int *") + h = ffi.new("int *") lib.TCOD_sys_get_char_size(w, h) return w[0], h[0] + # update font bitmap def sys_update_char(asciiCode, fontx, fonty, img, x, y): """Dynamically update the current frot with img. @@ -2986,6 +3479,7 @@ def sys_update_char(asciiCode, fontx, fonty, img, x, y): """ lib.TCOD_sys_update_char(_int(asciiCode), fontx, fonty, img, x, y) + def sys_register_SDL_renderer(callback): """Register a custom randering function with libtcod. @@ -3002,11 +3496,14 @@ def sys_register_SDL_renderer(callback): A function which takes a single argument. """ with _PropagateException() as propagate: + @ffi.def_extern(onerror=propagate) def _pycall_sdl_hook(sdl_surface): callback(sdl_surface) + lib.TCOD_sys_register_SDL_renderer(lib._pycall_sdl_hook) + def sys_check_for_event(mask, k, m): """Check for and return an event. @@ -3018,7 +3515,9 @@ def sys_check_for_event(mask, k, m): with an event. Can be None. """ return lib.TCOD_sys_check_for_event( - mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL) + mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL + ) + def sys_wait_for_event(mask, k, m, flush): """Wait for an event then return. @@ -3035,7 +3534,9 @@ def sys_wait_for_event(mask, k, m, flush): flush (bool): Clear the event buffer before waiting. """ return lib.TCOD_sys_wait_for_event( - mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL, flush) + mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL, flush + ) + @deprecate("This function does not provide reliable access to the clipboard.") def sys_clipboard_set(text): @@ -3044,7 +3545,8 @@ def sys_clipboard_set(text): .. deprecated:: 6.0 This function does not provide reliable access to the clipboard. """ - return lib.TCOD_sys_clipboard_set(text.encode('utf-8')) + return lib.TCOD_sys_clipboard_set(text.encode("utf-8")) + @deprecate("This function does not provide reliable access to the clipboard.") def sys_clipboard_get(): @@ -3053,4 +3555,4 @@ def sys_clipboard_get(): .. deprecated:: 6.0 This function does not provide reliable access to the clipboard. """ - return ffi.string(lib.TCOD_sys_clipboard_get()).decode('utf-8') + return ffi.string(lib.TCOD_sys_clipboard_get()).decode("utf-8") diff --git a/tcod/map.py b/tcod/map.py index d3150be2..c6a59b5d 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -63,7 +63,7 @@ class Map(object): """ - def __init__(self, width, height, order='C'): + def __init__(self, width, height, order="C"): self.width = width self.height = height self._order = tcod._internal.verify_order(order) @@ -71,35 +71,40 @@ def __init__(self, width, height, order='C'): self.__buffer = np.zeros((height, width, 3), dtype=np.bool_) self.map_c = self.__as_cdata() - def __as_cdata(self): return ffi.new( - 'struct TCOD_Map*', + "struct TCOD_Map*", ( self.width, self.height, self.width * self.height, - ffi.cast('struct TCOD_MapCell*', self.__buffer.ctypes.data), - ) + ffi.cast("struct TCOD_MapCell*", self.__buffer.ctypes.data), + ), ) @property def transparent(self): - buffer = self.__buffer[:,:,0] - return buffer.T if self._order == 'F' else buffer + buffer = self.__buffer[:, :, 0] + return buffer.T if self._order == "F" else buffer @property def walkable(self): - buffer = self.__buffer[:,:,1] - return buffer.T if self._order == 'F' else buffer + buffer = self.__buffer[:, :, 1] + return buffer.T if self._order == "F" else buffer @property def fov(self): - buffer = self.__buffer[:,:,2] - return buffer.T if self._order == 'F' else buffer - - def compute_fov(self, x, y, radius=0, light_walls=True, - algorithm=tcod.constants.FOV_RESTRICTIVE): + buffer = self.__buffer[:, :, 2] + return buffer.T if self._order == "F" else buffer + + def compute_fov( + self, + x, + y, + radius=0, + light_walls=True, + algorithm=tcod.constants.FOV_RESTRICTIVE, + ): # type (int, int, int, bool, int) -> None """Compute a field-of-view on the current instance. @@ -113,25 +118,26 @@ def compute_fov(self, x, y, radius=0, light_walls=True, algorithm (int): Defaults to tcod.FOV_RESTRICTIVE """ lib.TCOD_map_compute_fov( - self.map_c, x, y, radius, light_walls, algorithm) + self.map_c, x, y, radius, light_walls, algorithm + ) def __setstate__(self, state): - if '_Map__buffer' not in state: # deprecated + if "_Map__buffer" not in state: # deprecated # remove this check on major version update - self.__buffer = np.zeros((state['height'], state['width'], 3), - dtype=np.bool_) - self.__buffer[:,:,0] = state['buffer'] & 0x01 - self.__buffer[:,:,1] = state['buffer'] & 0x02 - self.__buffer[:,:,2] = state['buffer'] & 0x04 - del state['buffer'] - state['_order'] = 'F' - if '_order' not in state: # remove this check on major version update - raise RuntimeError( - 'This Map was saved with a bad version of tdl.') + self.__buffer = np.zeros( + (state["height"], state["width"], 3), dtype=np.bool_ + ) + self.__buffer[:, :, 0] = state["buffer"] & 0x01 + self.__buffer[:, :, 1] = state["buffer"] & 0x02 + self.__buffer[:, :, 2] = state["buffer"] & 0x04 + del state["buffer"] + state["_order"] = "F" + if "_order" not in state: # remove this check on major version update + raise RuntimeError("This Map was saved with a bad version of tdl.") self.__dict__.update(state) self.map_c = self.__as_cdata() def __getstate__(self): state = self.__dict__.copy() - del state['map_c'] + del state["map_c"] return state diff --git a/tcod/noise.py b/tcod/noise.py index 36acf010..03bc8c8d 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -45,6 +45,7 @@ FBM = 1 TURBULENCE = 2 + class Noise(object): """ @@ -70,38 +71,48 @@ class Noise(object): noise_c (CData): A cffi pointer to a TCOD_noise_t object. """ - def __init__(self, dimensions, algorithm=2, implementation=SIMPLE, - hurst=0.5, lacunarity=2.0, octaves=4, seed=None): + def __init__( + self, + dimensions, + algorithm=2, + implementation=SIMPLE, + hurst=0.5, + lacunarity=2.0, + octaves=4, + seed=None, + ): if not 0 < dimensions <= 4: - raise ValueError('dimensions must be in range 0 < n <= 4, got %r' % - (dimensions,)) + raise ValueError( + "dimensions must be in range 0 < n <= 4, got %r" + % (dimensions,) + ) self._random = seed _random_c = seed.random_c if seed else ffi.NULL self._algorithm = algorithm self.noise_c = ffi.gc( ffi.cast( - 'struct TCOD_Noise*', - lib.TCOD_noise_new(dimensions, hurst, lacunarity, - _random_c), - ), - lib.TCOD_noise_delete) - self._tdl_noise_c = ffi.new('TDLNoise*', (self.noise_c, - dimensions, - 0, - octaves)) - self.implementation = implementation # sanity check + "struct TCOD_Noise*", + lib.TCOD_noise_new(dimensions, hurst, lacunarity, _random_c), + ), + lib.TCOD_noise_delete, + ) + self._tdl_noise_c = ffi.new( + "TDLNoise*", (self.noise_c, dimensions, 0, octaves) + ) + self.implementation = implementation # sanity check @property def dimensions(self): return self._tdl_noise_c.dimensions @property - def dimentions(self): # deprecated + def dimentions(self): # deprecated return self.dimensions @property def algorithm(self): return self.noise_c.noise_type + @algorithm.setter def algorithm(self, value): lib.TCOD_noise_set_type(self.noise_c, value) @@ -109,10 +120,11 @@ def algorithm(self, value): @property def implementation(self): return self._tdl_noise_c.implementation + @implementation.setter def implementation(self, value): if not 0 <= value < 3: - raise ValueError('%r is not a valid implementation. ' % (value,)) + raise ValueError("%r is not a valid implementation. " % (value,)) self._tdl_noise_c.implementation = value @property @@ -126,6 +138,7 @@ def lacunarity(self): @property def octaves(self): return self._tdl_noise_c.octaves + @octaves.setter def octaves(self, value): self._tdl_noise_c.octaves = value @@ -159,15 +172,22 @@ def sample_mgrid(self, mgrid): """ mgrid = np.ascontiguousarray(mgrid, np.float32) if mgrid.shape[0] != self.dimensions: - raise ValueError('mgrid.shape[0] must equal self.dimensions, ' - '%r[0] != %r' % (mgrid.shape, self.dimensions)) + raise ValueError( + "mgrid.shape[0] must equal self.dimensions, " + "%r[0] != %r" % (mgrid.shape, self.dimensions) + ) out = np.ndarray(mgrid.shape[1:], np.float32) if mgrid.shape[1:] != out.shape: - raise ValueError('mgrid.shape[1:] must equal out.shape, ' - '%r[1:] != %r' % (mgrid.shape, out.shape)) - lib.NoiseSampleMeshGrid(self._tdl_noise_c, out.size, - ffi.cast('float*', mgrid.ctypes.data), - ffi.cast('float*', out.ctypes.data)) + raise ValueError( + "mgrid.shape[1:] must equal out.shape, " + "%r[1:] != %r" % (mgrid.shape, out.shape) + ) + lib.NoiseSampleMeshGrid( + self._tdl_noise_c, + out.size, + ffi.cast("float*", mgrid.ctypes.data), + ffi.cast("float*", out.ctypes.data), + ) return out def sample_ogrid(self, ogrid): @@ -184,17 +204,19 @@ def sample_ogrid(self, ogrid): The ``dtype`` is `numpy.float32`. """ if len(ogrid) != self.dimensions: - raise ValueError('len(ogrid) must equal self.dimensions, ' - '%r != %r' % (len(ogrid), self.dimensions)) + raise ValueError( + "len(ogrid) must equal self.dimensions, " + "%r != %r" % (len(ogrid), self.dimensions) + ) ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid] out = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( self._tdl_noise_c, len(ogrids), out.shape, - [ffi.cast('float*', array.ctypes.data) for array in ogrids], - ffi.cast('float*', out.ctypes.data), - ) + [ffi.cast("float*", array.ctypes.data) for array in ogrids], + ffi.cast("float*", out.ctypes.data), + ) return out def __getstate__(self): @@ -208,49 +230,52 @@ def __getstate__(self): waveletTileData = None if self.noise_c.waveletTileData != ffi.NULL: - waveletTileData = list(self.noise_c.waveletTileData[0:32*32*32]) - state['_waveletTileData'] = waveletTileData - - state['noise_c'] = { - 'ndim': self.noise_c.ndim, - 'map': list(self.noise_c.map), - 'buffer': [list(sub_buffer) for sub_buffer in self.noise_c.buffer], - 'H': self.noise_c.H, - 'lacunarity': self.noise_c.lacunarity, - 'exponent': list(self.noise_c.exponent), - 'waveletTileData': waveletTileData, - 'noise_type': self.noise_c.noise_type, - } - state['_tdl_noise_c'] = { - 'dimensions': self._tdl_noise_c.dimensions, - 'implementation': self._tdl_noise_c.implementation, - 'octaves': self._tdl_noise_c.octaves, - } + waveletTileData = list( + self.noise_c.waveletTileData[0 : 32 * 32 * 32] + ) + state["_waveletTileData"] = waveletTileData + + state["noise_c"] = { + "ndim": self.noise_c.ndim, + "map": list(self.noise_c.map), + "buffer": [list(sub_buffer) for sub_buffer in self.noise_c.buffer], + "H": self.noise_c.H, + "lacunarity": self.noise_c.lacunarity, + "exponent": list(self.noise_c.exponent), + "waveletTileData": waveletTileData, + "noise_type": self.noise_c.noise_type, + } + state["_tdl_noise_c"] = { + "dimensions": self._tdl_noise_c.dimensions, + "implementation": self._tdl_noise_c.implementation, + "octaves": self._tdl_noise_c.octaves, + } return state def __setstate__(self, state): - if isinstance(state, tuple): # deprecated format + if isinstance(state, tuple): # deprecated format return self._setstate_old(state) # unpack wavelet tile data if it exists - if '_waveletTileData' in state: - state['_waveletTileData'] = ffi.new('float[]', - state['_waveletTileData']) - state['noise_c']['waveletTileData'] = state['_waveletTileData'] + if "_waveletTileData" in state: + state["_waveletTileData"] = ffi.new( + "float[]", state["_waveletTileData"] + ) + state["noise_c"]["waveletTileData"] = state["_waveletTileData"] else: - state['noise_c']['waveletTileData'] = ffi.NULL + state["noise_c"]["waveletTileData"] = ffi.NULL # unpack TCOD_Noise and link to Random instance - state['noise_c']['rand'] = state['_random'].random_c - state['noise_c'] = ffi.new('struct TCOD_Noise*', state['noise_c']) + state["noise_c"]["rand"] = state["_random"].random_c + state["noise_c"] = ffi.new("struct TCOD_Noise*", state["noise_c"]) # unpack TDLNoise and link to libtcod noise - state['_tdl_noise_c']['noise'] = state['noise_c'] - state['_tdl_noise_c'] = ffi.new('TDLNoise*', state['_tdl_noise_c']) + state["_tdl_noise_c"]["noise"] = state["noise_c"] + state["_tdl_noise_c"] = ffi.new("TDLNoise*", state["_tdl_noise_c"]) self.__dict__.update(state) def _setstate_old(self, state): self._random = state[0] - self.noise_c = ffi.new('struct TCOD_Noise*') + self.noise_c = ffi.new("struct TCOD_Noise*") self.noise_c.ndim = state[3] ffi.buffer(self.noise_c.map)[:] = state[4] ffi.buffer(self.noise_c.buffer)[:] = state[5] @@ -259,9 +284,9 @@ def _setstate_old(self, state): ffi.buffer(self.noise_c.exponent)[:] = state[8] if state[9]: # high change of this being prematurely garbage collected! - self.__waveletTileData = ffi.new('float[]', 32*32*32) + self.__waveletTileData = ffi.new("float[]", 32 * 32 * 32) ffi.buffer(self.__waveletTileData)[:] = state[9] self.noise_c.noise_type = state[10] - self._tdl_noise_c = ffi.new('TDLNoise*', - (self.noise_c, self.noise_c.ndim, - state[1], state[2])) + self._tdl_noise_c = ffi.new( + "TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2]) + ) diff --git a/tcod/path.py b/tcod/path.py index 75c091ff..b73c1479 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -80,7 +80,7 @@ def _pycall_path_dest_only(x1, y1, x2, y2, handle): def _get_pathcost_func(name): # type: (str) -> Callable[[int, int, int, int, cffi.CData], float] """Return a properly cast PathCostArray callback.""" - return ffi.cast('TCOD_path_func_t', ffi.addressof(lib, name)) + return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) class _EdgeCostFunc(object): @@ -90,6 +90,7 @@ class _EdgeCostFunc(object): `shape` is the maximum boundary for the algorithm. """ + _CALLBACK_P = lib._pycall_path_old def __init__(self, userdata, shape): @@ -103,10 +104,11 @@ def get_tcod_path_ffi(self): return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape def __repr__(self): - return '%s(%r, shape=%r)' % ( + return "%s(%r, shape=%r)" % ( self.__class__.__name__, - self._userdata, self.shape, - ) + self._userdata, + self.shape, + ) class EdgeCostCallback(_EdgeCostFunc): @@ -121,6 +123,7 @@ class EdgeCostCallback(_EdgeCostFunc): .. versionchanged:: 5.0 Now only accepts a `shape` argument instead of `width` and `height`. """ + _CALLBACK_P = lib._pycall_path_simple def __init__(self, callback, shape): @@ -128,6 +131,7 @@ def __init__(self, callback, shape): self.callback = callback super(EdgeCostCallback, self).__init__(callback, shape) + class NodeCostArray(np.ndarray): """Calculate cost from a numpy array of nodes. @@ -136,15 +140,15 @@ class NodeCostArray(np.ndarray): """ _C_ARRAY_CALLBACKS = { - np.float32: ('float*', _get_pathcost_func('PathCostArrayFloat32')), - np.bool_: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')), - np.int8: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')), - np.uint8: ('uint8_t*', _get_pathcost_func('PathCostArrayUInt8')), - np.int16: ('int16_t*', _get_pathcost_func('PathCostArrayInt16')), - np.uint16: ('uint16_t*', _get_pathcost_func('PathCostArrayUInt16')), - np.int32: ('int32_t*', _get_pathcost_func('PathCostArrayInt32')), - np.uint32: ('uint32_t*', _get_pathcost_func('PathCostArrayUInt32')), - } + np.float32: ("float*", _get_pathcost_func("PathCostArrayFloat32")), + np.bool_: ("int8_t*", _get_pathcost_func("PathCostArrayInt8")), + np.int8: ("int8_t*", _get_pathcost_func("PathCostArrayInt8")), + np.uint8: ("uint8_t*", _get_pathcost_func("PathCostArrayUInt8")), + np.int16: ("int16_t*", _get_pathcost_func("PathCostArrayInt16")), + np.uint16: ("uint16_t*", _get_pathcost_func("PathCostArrayUInt16")), + np.int32: ("int32_t*", _get_pathcost_func("PathCostArrayInt32")), + np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), + } def __new__(cls, array): """Validate a numpy array and setup a C callback.""" @@ -152,23 +156,27 @@ def __new__(cls, array): return self def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, - repr(self.view(np.ndarray))) + return "%s(%r)" % ( + self.__class__.__name__, + repr(self.view(np.ndarray)), + ) def get_tcod_path_ffi(self): # type: () -> Tuple[cffi.CData, cffi.CData, Tuple[int, int]] if len(self.shape) != 2: - raise ValueError('Array must have a 2d shape, shape is %r' % - (self.shape,)) + raise ValueError( + "Array must have a 2d shape, shape is %r" % (self.shape,) + ) if self.dtype.type not in self._C_ARRAY_CALLBACKS: - raise ValueError('dtype must be one of %r, dtype is %r' % - (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type)) + raise ValueError( + "dtype must be one of %r, dtype is %r" + % (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type) + ) - array_type, callback = \ - self._C_ARRAY_CALLBACKS[self.dtype.type] + array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type] userdata = ffi.new( - 'struct PathCostArray*', - (ffi.cast('char*', self.ctypes.data), self.strides), + "struct PathCostArray*", + (ffi.cast("char*", self.ctypes.data), self.strides), ) return callback, userdata, self.shape @@ -183,43 +191,48 @@ def __init__(self, cost, diagonal=1.41): self._path_c = None self._callback = self._userdata = None - if hasattr(self.cost, 'map_c'): + if hasattr(self.cost, "map_c"): self.shape = self.cost.width, self.cost.height self._path_c = ffi.gc( self._path_new_using_map(self.cost.map_c, diagonal), self._path_delete, - ) + ) return - if not hasattr(self.cost, 'get_tcod_path_ffi'): - assert not callable(self.cost), \ - "Any callback alone is missing shape information. " \ + if not hasattr(self.cost, "get_tcod_path_ffi"): + assert not callable(self.cost), ( + "Any callback alone is missing shape information. " "Wrap your callback in tcod.path.EdgeCostCallback" + ) self.cost = NodeCostArray(self.cost) - self._callback, self._userdata, self.shape = \ + self._callback, self._userdata, self.shape = ( self.cost.get_tcod_path_ffi() + ) self._path_c = ffi.gc( self._path_new_using_function( self.cost.shape[0], self.cost.shape[1], self._callback, self._userdata, - diagonal - ), + diagonal, + ), self._path_delete, - ) + ) def __repr__(self): - return '%s(cost=%r, diagonal=%r)' % (self.__class__.__name__, - self.cost, self.diagonal) + return "%s(cost=%r, diagonal=%r)" % ( + self.__class__.__name__, + self.cost, + self.diagonal, + ) def __getstate__(self): state = self.__dict__.copy() - del state['_path_c'] - del state['shape'] - del state['_callback'] - del state['_userdata'] + del state["_path_c"] + del state["shape"] + del state["_callback"] + del state["_userdata"] return state def __setstate__(self, state): @@ -254,7 +267,7 @@ def get_path(self, start_x, start_y, goal_x, goal_y): """ lib.TCOD_path_compute(self._path_c, start_x, start_y, goal_x, goal_y) path = [] - x = ffi.new('int[2]') + x = ffi.new("int[2]") y = x + 1 while lib.TCOD_path_walk(self._path_c, x, y, False): path.append((x[0], y[0])) @@ -285,7 +298,7 @@ def get_path(self, x, y): """ lib.TCOD_dijkstra_path_set(self._path_c, x, y) path = [] - pointer_x = ffi.new('int[2]') + pointer_x = ffi.new("int[2]") pointer_y = pointer_x + 1 while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y): path.append((pointer_x[0], pointer_y[0])) diff --git a/tcod/random.py b/tcod/random.py index d82905d9..720cb9bf 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -7,8 +7,11 @@ import random from tcod.libtcod import ffi, lib -from tcod.libtcod import RNG_MT as MERSENNE_TWISTER -from tcod.libtcod import RNG_CMWC as COMPLEMENTARY_MULTIPLY_WITH_CARRY +from tcod.libtcod import RNG_MT +from tcod.libtcod import RNG_CMWC + +MERSENNE_TWISTER = RNG_MT +COMPLEMENTARY_MULTIPLY_WITH_CARRY = RNG_CMWC class Random(object): @@ -27,15 +30,20 @@ class Random(object): Attributes: random_c (CData): A cffi pointer to a TCOD_random_t object. """ + def __init__(self, algorithm, seed=None): """Create a new instance using this algorithm and seed.""" if seed is None: seed = random.getrandbits(32) self.random_c = ffi.gc( - ffi.cast('mersenne_data_t*', - lib.TCOD_random_new_from_seed(algorithm, - hash(seed) % (1 << 32))), - lib.TCOD_random_delete) + ffi.cast( + "mersenne_data_t*", + lib.TCOD_random_new_from_seed( + algorithm, hash(seed) % (1 << 32) + ), + ), + lib.TCOD_random_delete, + ) @classmethod def _new_from_cdata(cls, cdata): @@ -90,28 +98,30 @@ def inverse_guass(self, mu, sigma): Returns: float: A random float. """ - return lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma) + return lib.TCOD_random_get_gaussian_double_inv( + self.random_c, mu, sigma + ) def __getstate__(self): """Pack the self.random_c attribute into a portable state.""" state = self.__dict__.copy() - state['random_c'] = { - 'algo': self.random_c.algo, - 'distribution': self.random_c.distribution, - 'mt': list(self.random_c.mt), - 'cur_mt': self.random_c.cur_mt, - 'Q': list(self.random_c.Q), - 'c': self.random_c.c, - 'cur': self.random_c.cur, - } + state["random_c"] = { + "algo": self.random_c.algo, + "distribution": self.random_c.distribution, + "mt": list(self.random_c.mt), + "cur_mt": self.random_c.cur_mt, + "Q": list(self.random_c.Q), + "c": self.random_c.c, + "cur": self.random_c.cur, + } return state def __setstate__(self, state): """Create a new cdata object with the stored paramaters.""" try: - cdata = state['random_c'] - except KeyError: # old/deprecated format - cdata = state['cdata'] - del state['cdata'] - state['random_c'] = ffi.new('mersenne_data_t*', cdata) + cdata = state["random_c"] + except KeyError: # old/deprecated format + cdata = state["cdata"] + del state["cdata"] + state["random_c"] = ffi.new("mersenne_data_t*", cdata) self.__dict__.update(state) diff --git a/tcod/tcod.py b/tcod/tcod.py index 35b42ce7..6abff349 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -1,66 +1,62 @@ """This module focuses on improvements to the Python libtcod API. """ -from __future__ import absolute_import as _ - -import sys as _sys from typing import Any, AnyStr import warnings -from tcod.libtcod import lib, ffi, BKGND_DEFAULT, BKGND_SET +from tcod.libtcod import ffi def _unpack_char_p(char_p): if char_p == ffi.NULL: - return '' + return "" return ffi.string(char_p).decode() def _int(int_or_str: Any) -> int: - 'return an integer where a single character string may be expected' + "return an integer where a single character string may be expected" if isinstance(int_or_str, str): return ord(int_or_str) if isinstance(int_or_str, bytes): return int_or_str[0] - return int(int_or_str) # check for __count__ + return int(int_or_str) # check for __count__ + def _bytes(string: AnyStr) -> bytes: if isinstance(string, str): - return string.encode('utf-8') + return string.encode("utf-8") return string -def _unicode(string: AnyStr, stacklevel: int=2) -> str: + +def _unicode(string: AnyStr, stacklevel: int = 2) -> str: if isinstance(string, bytes): warnings.warn( - ("Passing byte strings as parameters to Unicode functions is " - "deprecated."), + ( + "Passing byte strings as parameters to Unicode functions is " + "deprecated." + ), DeprecationWarning, stacklevel=stacklevel + 1, - ) - return string.decode('latin-1') + ) + return string.decode("latin-1") return string -def _fmt_bytes(string: AnyStr) -> bytes: - return _bytes(string).replace(b'%', b'%%') - -def _fmt_unicode(string: AnyStr) -> str: - return _unicode(string, stacklevel=3).encode('utf-8').replace(b'%', b'%%') - -def _fmt(string: str, stacklevel: int=2) -> bytes: +def _fmt(string: str, stacklevel: int = 2) -> bytes: if isinstance(string, bytes): warnings.warn( - ("Passing byte strings as parameters to Unicode functions is " - "deprecated."), + ( + "Passing byte strings as parameters to Unicode functions is " + "deprecated." + ), DeprecationWarning, stacklevel=stacklevel + 1, ) - string = string.decode('latin-1') - return string.encode('utf-8').replace(b'%', b'%%') - + string = string.decode("latin-1") + return string.encode("utf-8").replace(b"%", b"%%") -class _PropagateException(): +class _PropagateException: """ context manager designed to propagate exceptions outside of a cffi callback context. normally cffi suppresses the exception @@ -72,7 +68,7 @@ class _PropagateException(): """ def __init__(self): - self.exc_info = None # (exception, exc_value, traceback) + self.exc_info = None # (exception, exc_value, traceback) def propagate(self, *exc_info): """ set an exception to be raised once this context exits @@ -106,10 +102,9 @@ def __exit__(self, type, value, traceback): class _CDataWrapper(object): - def __init__(self, *args, **kargs): self.cdata = self._get_cdata_from_args(*args, **kargs) - if self.cdata == None: + if self.cdata is None: self.cdata = ffi.NULL super(_CDataWrapper, self).__init__() @@ -120,7 +115,6 @@ def _get_cdata_from_args(*args, **kargs): else: return None - def __hash__(self): return hash(self.cdata) @@ -131,12 +125,12 @@ def __eq__(self, other): return NotImplemented def __getattr__(self, attr): - if 'cdata' in self.__dict__: - return getattr(self.__dict__['cdata'], attr) + if "cdata" in self.__dict__: + return getattr(self.__dict__["cdata"], attr) raise AttributeError(attr) def __setattr__(self, attr, value): - if hasattr(self, 'cdata') and hasattr(self.cdata, attr): + if hasattr(self, "cdata") and hasattr(self.cdata, attr): setattr(self.cdata, attr, value) else: super(_CDataWrapper, self).__setattr__(attr, value) @@ -148,10 +142,12 @@ def _console(console): return console.console_c except AttributeError: warnings.warn( - ("Falsy console parameters are deprecated, " - "always use the root console instance returned by " - "console_init_root."), + ( + "Falsy console parameters are deprecated, " + "always use the root console instance returned by " + "console_init_root." + ), DeprecationWarning, stacklevel=3, - ) + ) return ffi.NULL From dcc9f48c7f4f4c2a272ea2bca79d24727e7f5766 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 5 Jan 2019 15:20:39 -0800 Subject: [PATCH 0027/1101] Clean old tcod module code. --- tcod/__init__.py | 2 -- tcod/_internal.py | 2 +- tcod/color.py | 3 -- tcod/console.py | 16 ++++----- tcod/constants.py | 2 -- tcod/image.py | 2 -- tcod/libtcod.py | 40 +++++---------------- tcod/libtcodpy.py | 90 +++++++++++++++++++++++------------------------ tcod/map.py | 3 -- tcod/noise.py | 6 ++-- tcod/path.py | 3 -- tcod/random.py | 10 ++---- tcod/tcod.py | 1 - 13 files changed, 68 insertions(+), 112 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 9e200a97..ca73e7f0 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,8 +15,6 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ -from __future__ import absolute_import - import sys import warnings diff --git a/tcod/_internal.py b/tcod/_internal.py index f1f749aa..d37a501a 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -16,7 +16,7 @@ def wrapper(*args, **kargs): def verify_order(order): order = order.upper() - if order != "C" and order != "F": + if order not in ("C", "F"): raise TypeError("order must be 'C' or 'F', not %r" % (order,)) return order diff --git a/tcod/color.py b/tcod/color.py index 6878f393..0f81aa43 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,9 +1,6 @@ """ """ - -from __future__ import absolute_import - from tcod.libtcod import lib diff --git a/tcod/console.py b/tcod/console.py index d8e6365c..5c835313 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -28,7 +28,7 @@ import numpy as np -import tcod.libtcod +import tcod.constants from tcod.libtcod import ffi, lib import tcod._internal @@ -226,7 +226,7 @@ def clear(self): """ lib.TCOD_console_clear(self.console_c) - def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT): + def put_char(self, x, y, ch, bg_blend=tcod.constants.BKGND_DEFAULT): """Draw the character c at x,y using the default colors and a blend mode. Args: @@ -242,7 +242,7 @@ def print_( x: int, y: int, string: str, - bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + bg_blend: int = tcod.constants.BKGND_DEFAULT, alignment: Optional[int] = None, ) -> None: """Print a color formatted string on a console. @@ -267,7 +267,7 @@ def print_rect( width: int, height: int, string: str, - bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + bg_blend: int = tcod.constants.BKGND_DEFAULT, alignment: Optional[int] = None, ) -> int: """Print a string constrained to a rectangle. @@ -321,7 +321,7 @@ def rect( width: int, height: int, clear: bool, - bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + bg_blend: int = tcod.constants.BKGND_DEFAULT, ) -> None: """Draw a the background color on a rect optionally clearing the text. @@ -340,7 +340,7 @@ def rect( self.console_c, x, y, width, height, clear, bg_blend ) - def hline(self, x, y, width, bg_blend=tcod.libtcod.BKGND_DEFAULT): + def hline(self, x, y, width, bg_blend=tcod.constants.BKGND_DEFAULT): """Draw a horizontal line on the console. This always uses the character 196, the horizontal line character. @@ -353,7 +353,7 @@ def hline(self, x, y, width, bg_blend=tcod.libtcod.BKGND_DEFAULT): """ lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend) - def vline(self, x, y, height, bg_blend=tcod.libtcod.BKGND_DEFAULT): + def vline(self, x, y, height, bg_blend=tcod.constants.BKGND_DEFAULT): """Draw a vertical line on the console. This always uses the character 179, the vertical line character. @@ -374,7 +374,7 @@ def print_frame( height: int, string: str = "", clear: bool = True, - bg_blend: int = tcod.libtcod.BKGND_DEFAULT, + bg_blend: int = tcod.constants.BKGND_DEFAULT, ): """Draw a framed rectangle with optinal text. diff --git a/tcod/constants.py b/tcod/constants.py index f457c1d9..a263ede0 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -3,8 +3,6 @@ This module is auto-generated by `build_libtcod.py`. """ -from __future__ import absolute_import - from tcod.color import Color FOV_BASIC = 0 diff --git a/tcod/image.py b/tcod/image.py index efde52a5..bad81f94 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import numpy as np from tcod.libtcod import ffi, lib diff --git a/tcod/libtcod.py b/tcod/libtcod.py index c0dc7d4c..6890d74c 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -1,38 +1,20 @@ """This module handles loading of the libtcod cffi API. """ -from __future__ import absolute_import as _ - -import sys as _sys -import os as _os - -import platform as _platform +import sys +import os +import platform from tcod import __path__ -if _sys.platform == "win32": +if sys.platform == "win32": # add Windows dll's to PATH - _bits, _linkage = _platform.architecture() - _os.environ["PATH"] = "%s;%s" % ( - _os.path.join(__path__[0], "x86" if _bits == "32bit" else "x64"), - _os.environ["PATH"], + _bits, _linkage = platform.architecture() + os.environ["PATH"] = "%s;%s" % ( + os.path.join(__path__[0], "x86" if _bits == "32bit" else "x64"), + os.environ["PATH"], ) -NOISE_DEFAULT_HURST = 0.5 -NOISE_DEFAULT_LACUNARITY = 2.0 - - -def FOV_PERMISSIVE(p): - return FOV_PERMISSIVE_0 + p - - -def BKGND_ALPHA(a): - return BKGND_ALPH | (int(a * 255) << 8) - - -def BKGND_ADDALPHA(a): - return BKGND_ADDA | (int(a * 255) << 8) - class _Mock(object): """Mock object needed for ReadTheDocs.""" @@ -57,13 +39,9 @@ def __str__(self): return "?" -if _os.environ.get("READTHEDOCS"): +if os.environ.get("READTHEDOCS"): # Mock the lib and ffi objects needed to compile docs for readthedocs.io # Allows an import without building the cffi module first. lib = ffi = _Mock() else: from tcod._libtcod import lib, ffi # noqa: F401 - -from tcod.constants import * # noqa: F4 -from tcod.constants import FOV_PERMISSIVE_0 # noqa: F402 -from tcod.constants import BKGND_ALPH, BKGND_ADDA # noqa: F402 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 11459773..b4e25bfe 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1,28 +1,27 @@ """This module handles backward compatibility with the ctypes libtcodpy module. """ - -from __future__ import absolute_import as _ - import os import sys -import threading as _threading +import threading from typing import Any, AnyStr, Optional, Sequence, Tuple import warnings -import numpy as _np import numpy as np -from tcod.libtcod import * # noqa: F401 F403 from tcod.libtcod import ffi, lib -from tcod.libtcod import ( - NOISE_DEFAULT_LACUNARITY, - NOISE_DEFAULT_HURST, - NOISE_DEFAULT, - KEY_RELEASED, + +from tcod.constants import * # noqa: F4 +from tcod.constants import ( BKGND_DEFAULT, BKGND_SET, + BKGND_ALPH, + BKGND_ADDA, FONT_LAYOUT_ASCII_INCOL, + FOV_RESTRICTIVE, + FOV_PERMISSIVE_0, + NOISE_DEFAULT, + KEY_RELEASED, RENDERER_GLSL, ) @@ -45,6 +44,23 @@ Bsp = tcod.bsp.BSP +NB_FOV_ALGORITHMS = 13 + +NOISE_DEFAULT_HURST = 0.5 +NOISE_DEFAULT_LACUNARITY = 2.0 + + +def FOV_PERMISSIVE(p): + return FOV_PERMISSIVE_0 + p + + +def BKGND_ALPHA(a): + return BKGND_ALPH | (int(a * 255) << 8) + + +def BKGND_ADDALPHA(a): + return BKGND_ADDA | (int(a * 255) << 8) + class ConsoleBuffer(object): """Simple console that allows direct (fast) access to cells. simplifies @@ -1432,14 +1448,14 @@ def console_fill_foreground(con, r, g, b): if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") if ( - isinstance(r, _np.ndarray) - and isinstance(g, _np.ndarray) - and isinstance(b, _np.ndarray) + isinstance(r, np.ndarray) + and isinstance(g, np.ndarray) + and isinstance(b, np.ndarray) ): # numpy arrays, use numpy's ctypes functions - r = _np.ascontiguousarray(r, dtype=_np.intc) - g = _np.ascontiguousarray(g, dtype=_np.intc) - b = _np.ascontiguousarray(b, dtype=_np.intc) + r = np.ascontiguousarray(r, dtype=np.intc) + g = np.ascontiguousarray(g, dtype=np.intc) + b = np.ascontiguousarray(b, dtype=np.intc) cr = ffi.cast("int *", r.ctypes.data) cg = ffi.cast("int *", g.ctypes.data) cb = ffi.cast("int *", b.ctypes.data) @@ -1464,14 +1480,14 @@ def console_fill_background(con, r, g, b): if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") if ( - isinstance(r, _np.ndarray) - and isinstance(g, _np.ndarray) - and isinstance(b, _np.ndarray) + isinstance(r, np.ndarray) + and isinstance(g, np.ndarray) + and isinstance(b, np.ndarray) ): # numpy arrays, use numpy's ctypes functions - r = _np.ascontiguousarray(r, dtype=_np.intc) - g = _np.ascontiguousarray(g, dtype=_np.intc) - b = _np.ascontiguousarray(b, dtype=_np.intc) + r = np.ascontiguousarray(r, dtype=np.intc) + g = np.ascontiguousarray(g, dtype=np.intc) + b = np.ascontiguousarray(b, dtype=np.intc) cr = ffi.cast("int *", r.ctypes.data) cg = ffi.cast("int *", g.ctypes.data) cb = ffi.cast("int *", b.ctypes.data) @@ -1490,9 +1506,9 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]): `arr` is an array of integers with a length of the consoles width and height. """ - if isinstance(arr, _np.ndarray): + if isinstance(arr, np.ndarray): # numpy arrays, use numpy's ctypes functions - arr = _np.ascontiguousarray(arr, dtype=_np.intc) + arr = np.ascontiguousarray(arr, dtype=np.intc) carr = ffi.cast("int *", arr.ctypes.data) else: # otherwise convert using the ffi module @@ -1788,7 +1804,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: raise ValueError("array must be a contiguous segment.") - if array.dtype != _np.float32: + if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) width, height = array.shape pointer = ffi.cast("float *", array.ctypes.data) @@ -1814,9 +1830,9 @@ def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: Added the `order` parameter. """ if order == "C": - return np.zeros((h, w), _np.float32, order="C") + return np.zeros((h, w), np.float32, order="C") elif order == "F": - return np.zeros((w, h), _np.float32, order="F") + return np.zeros((w, h), np.float32, order="F") else: raise ValueError("Invalid order parameter, should be 'C' or 'F'.") @@ -2642,22 +2658,6 @@ def line_where(x1, y1, x2, y2, inclusive=True): return tuple(array) -FOV_BASIC = 0 -FOV_DIAMOND = 1 -FOV_SHADOW = 2 -FOV_PERMISSIVE_0 = 3 -FOV_PERMISSIVE_1 = 4 -FOV_PERMISSIVE_2 = 5 -FOV_PERMISSIVE_3 = 6 -FOV_PERMISSIVE_4 = 7 -FOV_PERMISSIVE_5 = 8 -FOV_PERMISSIVE_6 = 9 -FOV_PERMISSIVE_7 = 10 -FOV_PERMISSIVE_8 = 11 -FOV_RESTRICTIVE = 12 -NB_FOV_ALGORITHMS = 13 - - def map_new(w, h): # type: (int, int) -> tcod.map.Map """Return a :any:`tcod.map.Map` with a width and height. @@ -2967,7 +2967,7 @@ def parser_new_struct(parser, name): # prevent multiple threads from messing with def_extern callbacks -_parser_callback_lock = _threading.Lock() +_parser_callback_lock = threading.Lock() _parser_listener = None # temporary global pointer to a listener instance diff --git a/tcod/map.py b/tcod/map.py index c6a59b5d..4311ce7c 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -30,9 +30,6 @@ False """ - -from __future__ import absolute_import - import numpy as np from tcod.libtcod import lib, ffi diff --git a/tcod/noise.py b/tcod/noise.py index 03bc8c8d..67b3aa3d 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -33,12 +33,10 @@ samples = noise.sample_ogrid(ogrid) print(samples) """ -from __future__ import absolute_import - import numpy as np from tcod.libtcod import ffi, lib -import tcod.libtcod +import tcod.constants """Noise implementation constants""" SIMPLE = 0 @@ -224,7 +222,7 @@ def __getstate__(self): if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # Trigger a side effect of wavelet, so that copies will be synced. saved_algo = self.algorithm - self.algorithm = tcod.libtcod.NOISE_WAVELET + self.algorithm = tcod.constants.NOISE_WAVELET self.get_point() self.algorithm = saved_algo diff --git a/tcod/path.py b/tcod/path.py index b73c1479..2de918b4 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -40,9 +40,6 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ - -from __future__ import absolute_import - import numpy as np from tcod.libtcod import lib, ffi diff --git a/tcod/random.py b/tcod/random.py index 720cb9bf..73185b8d 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -1,17 +1,13 @@ """ Random module docs. """ - -from __future__ import absolute_import as _ - import random from tcod.libtcod import ffi, lib -from tcod.libtcod import RNG_MT -from tcod.libtcod import RNG_CMWC +import tcod.constants -MERSENNE_TWISTER = RNG_MT -COMPLEMENTARY_MULTIPLY_WITH_CARRY = RNG_CMWC +MERSENNE_TWISTER = tcod.constants.RNG_MT +COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC class Random(object): diff --git a/tcod/tcod.py b/tcod/tcod.py index 6abff349..0b77a15b 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -1,6 +1,5 @@ """This module focuses on improvements to the Python libtcod API. """ - from typing import Any, AnyStr import warnings From f599d11e139a08f409778a0e124e54dfe5216daf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 5 Jan 2019 17:09:05 -0800 Subject: [PATCH 0028/1101] Setup __all__ for constants module. --- build_libtcod.py | 10 +- tcod/constants.py | 496 ++++++++++++++++++++++++++++++++++++++++++++++ tcod/libtcod.py | 2 + 3 files changed, 506 insertions(+), 2 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 94b05386..b85edb7a 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -309,8 +309,6 @@ def get_ast(): This module is auto-generated by `build_libtcod.py`. """ -from __future__ import absolute_import - from tcod.color import Color ''' @@ -319,6 +317,7 @@ def write_library_constants(): """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi import tcod.color + all_names = [] with open('tcod/constants.py', 'w') as f: f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): @@ -326,10 +325,13 @@ def write_library_constants(): if name[:5] == 'TCOD_': if name.isupper(): # const names f.write('%s = %r\n' % (name[5:], value)) + all_names.append(name[5:]) elif name.startswith('FOV'): # fov const names f.write('%s = %r\n' % (name, value)) + all_names.append(name) elif name[:6] == 'TCODK_': # key name f.write('KEY_%s = %r\n' % (name[6:], value)) + all_names.append('KEY_%s' % name[6:]) f.write('\n# --- colors ---\n') for name in dir(lib): @@ -342,6 +344,10 @@ def write_library_constants(): continue color = tcod.color.Color._new_from_cdata(value) f.write('%s = %r\n' % (name[5:], color)) + all_names.append(name[5:]) + + all_names = ",\n ".join('"%s"' % name for name in all_names) + f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) if __name__ == "__main__": diff --git a/tcod/constants.py b/tcod/constants.py index a263ede0..0d809943 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -500,3 +500,499 @@ violet = Color(127, 0, 255) white = Color(255, 255, 255) yellow = Color(255, 255, 0) + +__all__ = [ + "FOV_BASIC", + "FOV_DIAMOND", + "FOV_PERMISSIVE_0", + "FOV_PERMISSIVE_1", + "FOV_PERMISSIVE_2", + "FOV_PERMISSIVE_3", + "FOV_PERMISSIVE_4", + "FOV_PERMISSIVE_5", + "FOV_PERMISSIVE_6", + "FOV_PERMISSIVE_7", + "FOV_PERMISSIVE_8", + "FOV_RESTRICTIVE", + "FOV_SHADOW", + "KEY_0", + "KEY_1", + "KEY_2", + "KEY_3", + "KEY_4", + "KEY_5", + "KEY_6", + "KEY_7", + "KEY_8", + "KEY_9", + "KEY_ALT", + "KEY_APPS", + "KEY_BACKSPACE", + "KEY_CAPSLOCK", + "KEY_CHAR", + "KEY_CONTROL", + "KEY_DELETE", + "KEY_DOWN", + "KEY_END", + "KEY_ENTER", + "KEY_ESCAPE", + "KEY_F1", + "KEY_F10", + "KEY_F11", + "KEY_F12", + "KEY_F2", + "KEY_F3", + "KEY_F4", + "KEY_F5", + "KEY_F6", + "KEY_F7", + "KEY_F8", + "KEY_F9", + "KEY_HOME", + "KEY_INSERT", + "KEY_KP0", + "KEY_KP1", + "KEY_KP2", + "KEY_KP3", + "KEY_KP4", + "KEY_KP5", + "KEY_KP6", + "KEY_KP7", + "KEY_KP8", + "KEY_KP9", + "KEY_KPADD", + "KEY_KPDEC", + "KEY_KPDIV", + "KEY_KPENTER", + "KEY_KPMUL", + "KEY_KPSUB", + "KEY_LEFT", + "KEY_LWIN", + "KEY_NONE", + "KEY_NUMLOCK", + "KEY_PAGEDOWN", + "KEY_PAGEUP", + "KEY_PAUSE", + "KEY_PRINTSCREEN", + "KEY_RIGHT", + "KEY_RWIN", + "KEY_SCROLLLOCK", + "KEY_SHIFT", + "KEY_SPACE", + "KEY_TAB", + "KEY_TEXT", + "KEY_UP", + "BKGND_ADD", + "BKGND_ADDA", + "BKGND_ALPH", + "BKGND_BURN", + "BKGND_COLOR_BURN", + "BKGND_COLOR_DODGE", + "BKGND_DARKEN", + "BKGND_DEFAULT", + "BKGND_LIGHTEN", + "BKGND_MULTIPLY", + "BKGND_NONE", + "BKGND_OVERLAY", + "BKGND_SCREEN", + "BKGND_SET", + "CENTER", + "CHAR_ARROW2_E", + "CHAR_ARROW2_N", + "CHAR_ARROW2_S", + "CHAR_ARROW2_W", + "CHAR_ARROW_E", + "CHAR_ARROW_N", + "CHAR_ARROW_S", + "CHAR_ARROW_W", + "CHAR_BLOCK1", + "CHAR_BLOCK2", + "CHAR_BLOCK3", + "CHAR_BULLET", + "CHAR_BULLET_INV", + "CHAR_BULLET_SQUARE", + "CHAR_CENT", + "CHAR_CHECKBOX_SET", + "CHAR_CHECKBOX_UNSET", + "CHAR_CLUB", + "CHAR_COPYRIGHT", + "CHAR_CROSS", + "CHAR_CURRENCY", + "CHAR_DARROW_H", + "CHAR_DARROW_V", + "CHAR_DCROSS", + "CHAR_DHLINE", + "CHAR_DIAMOND", + "CHAR_DIVISION", + "CHAR_DNE", + "CHAR_DNW", + "CHAR_DSE", + "CHAR_DSW", + "CHAR_DTEEE", + "CHAR_DTEEN", + "CHAR_DTEES", + "CHAR_DTEEW", + "CHAR_DVLINE", + "CHAR_EXCLAM_DOUBLE", + "CHAR_FEMALE", + "CHAR_FUNCTION", + "CHAR_GRADE", + "CHAR_HALF", + "CHAR_HEART", + "CHAR_HLINE", + "CHAR_LIGHT", + "CHAR_MALE", + "CHAR_MULTIPLICATION", + "CHAR_NE", + "CHAR_NOTE", + "CHAR_NOTE_DOUBLE", + "CHAR_NW", + "CHAR_ONE_QUARTER", + "CHAR_PILCROW", + "CHAR_POUND", + "CHAR_POW1", + "CHAR_POW2", + "CHAR_POW3", + "CHAR_RADIO_SET", + "CHAR_RADIO_UNSET", + "CHAR_RESERVED", + "CHAR_SE", + "CHAR_SECTION", + "CHAR_SMILIE", + "CHAR_SMILIE_INV", + "CHAR_SPADE", + "CHAR_SUBP_DIAG", + "CHAR_SUBP_E", + "CHAR_SUBP_N", + "CHAR_SUBP_NE", + "CHAR_SUBP_NW", + "CHAR_SUBP_SE", + "CHAR_SUBP_SW", + "CHAR_SW", + "CHAR_TEEE", + "CHAR_TEEN", + "CHAR_TEES", + "CHAR_TEEW", + "CHAR_THREE_QUARTERS", + "CHAR_UMLAUT", + "CHAR_VLINE", + "CHAR_YEN", + "COLCTRL_1", + "COLCTRL_2", + "COLCTRL_3", + "COLCTRL_4", + "COLCTRL_5", + "COLCTRL_BACK_RGB", + "COLCTRL_FORE_RGB", + "COLCTRL_NUMBER", + "COLCTRL_STOP", + "COLOR_AMBER", + "COLOR_AZURE", + "COLOR_BLUE", + "COLOR_CHARTREUSE", + "COLOR_CRIMSON", + "COLOR_CYAN", + "COLOR_DARK", + "COLOR_DARKER", + "COLOR_DARKEST", + "COLOR_DESATURATED", + "COLOR_FLAME", + "COLOR_FUCHSIA", + "COLOR_GREEN", + "COLOR_HAN", + "COLOR_LEVELS", + "COLOR_LIGHT", + "COLOR_LIGHTER", + "COLOR_LIGHTEST", + "COLOR_LIME", + "COLOR_MAGENTA", + "COLOR_NB", + "COLOR_NORMAL", + "COLOR_ORANGE", + "COLOR_PINK", + "COLOR_PURPLE", + "COLOR_RED", + "COLOR_SEA", + "COLOR_SKY", + "COLOR_TURQUOISE", + "COLOR_VIOLET", + "COLOR_YELLOW", + "DISTRIBUTION_GAUSSIAN", + "DISTRIBUTION_GAUSSIAN_INVERSE", + "DISTRIBUTION_GAUSSIAN_RANGE", + "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE", + "DISTRIBUTION_LINEAR", + "EVENT_ANY", + "EVENT_FINGER", + "EVENT_FINGER_MOVE", + "EVENT_FINGER_PRESS", + "EVENT_FINGER_RELEASE", + "EVENT_KEY", + "EVENT_KEY_PRESS", + "EVENT_KEY_RELEASE", + "EVENT_MOUSE", + "EVENT_MOUSE_MOVE", + "EVENT_MOUSE_PRESS", + "EVENT_MOUSE_RELEASE", + "EVENT_NONE", + "FONT_LAYOUT_ASCII_INCOL", + "FONT_LAYOUT_ASCII_INROW", + "FONT_LAYOUT_CP437", + "FONT_LAYOUT_TCOD", + "FONT_TYPE_GRAYSCALE", + "FONT_TYPE_GREYSCALE", + "KEY_PRESSED", + "KEY_RELEASED", + "LEFT", + "NB_RENDERERS", + "NOISE_DEFAULT", + "NOISE_PERLIN", + "NOISE_SIMPLEX", + "NOISE_WAVELET", + "RENDERER_GLSL", + "RENDERER_OPENGL", + "RENDERER_OPENGL2", + "RENDERER_SDL", + "RENDERER_SDL2", + "RIGHT", + "RNG_CMWC", + "RNG_MT", + "TYPE_BOOL", + "TYPE_CHAR", + "TYPE_COLOR", + "TYPE_CUSTOM00", + "TYPE_CUSTOM01", + "TYPE_CUSTOM02", + "TYPE_CUSTOM03", + "TYPE_CUSTOM04", + "TYPE_CUSTOM05", + "TYPE_CUSTOM06", + "TYPE_CUSTOM07", + "TYPE_CUSTOM08", + "TYPE_CUSTOM09", + "TYPE_CUSTOM10", + "TYPE_CUSTOM11", + "TYPE_CUSTOM12", + "TYPE_CUSTOM13", + "TYPE_CUSTOM14", + "TYPE_CUSTOM15", + "TYPE_DICE", + "TYPE_FLOAT", + "TYPE_INT", + "TYPE_LIST", + "TYPE_NONE", + "TYPE_STRING", + "TYPE_VALUELIST00", + "TYPE_VALUELIST01", + "TYPE_VALUELIST02", + "TYPE_VALUELIST03", + "TYPE_VALUELIST04", + "TYPE_VALUELIST05", + "TYPE_VALUELIST06", + "TYPE_VALUELIST07", + "TYPE_VALUELIST08", + "TYPE_VALUELIST09", + "TYPE_VALUELIST10", + "TYPE_VALUELIST11", + "TYPE_VALUELIST12", + "TYPE_VALUELIST13", + "TYPE_VALUELIST14", + "TYPE_VALUELIST15", + "amber", + "azure", + "black", + "blue", + "brass", + "celadon", + "chartreuse", + "copper", + "crimson", + "cyan", + "dark_amber", + "dark_azure", + "dark_blue", + "dark_chartreuse", + "dark_crimson", + "dark_cyan", + "dark_flame", + "dark_fuchsia", + "dark_gray", + "dark_green", + "dark_grey", + "dark_han", + "dark_lime", + "dark_magenta", + "dark_orange", + "dark_pink", + "dark_purple", + "dark_red", + "dark_sea", + "dark_sepia", + "dark_sky", + "dark_turquoise", + "dark_violet", + "dark_yellow", + "darker_amber", + "darker_azure", + "darker_blue", + "darker_chartreuse", + "darker_crimson", + "darker_cyan", + "darker_flame", + "darker_fuchsia", + "darker_gray", + "darker_green", + "darker_grey", + "darker_han", + "darker_lime", + "darker_magenta", + "darker_orange", + "darker_pink", + "darker_purple", + "darker_red", + "darker_sea", + "darker_sepia", + "darker_sky", + "darker_turquoise", + "darker_violet", + "darker_yellow", + "darkest_amber", + "darkest_azure", + "darkest_blue", + "darkest_chartreuse", + "darkest_crimson", + "darkest_cyan", + "darkest_flame", + "darkest_fuchsia", + "darkest_gray", + "darkest_green", + "darkest_grey", + "darkest_han", + "darkest_lime", + "darkest_magenta", + "darkest_orange", + "darkest_pink", + "darkest_purple", + "darkest_red", + "darkest_sea", + "darkest_sepia", + "darkest_sky", + "darkest_turquoise", + "darkest_violet", + "darkest_yellow", + "desaturated_amber", + "desaturated_azure", + "desaturated_blue", + "desaturated_chartreuse", + "desaturated_crimson", + "desaturated_cyan", + "desaturated_flame", + "desaturated_fuchsia", + "desaturated_green", + "desaturated_han", + "desaturated_lime", + "desaturated_magenta", + "desaturated_orange", + "desaturated_pink", + "desaturated_purple", + "desaturated_red", + "desaturated_sea", + "desaturated_sky", + "desaturated_turquoise", + "desaturated_violet", + "desaturated_yellow", + "flame", + "fuchsia", + "gold", + "gray", + "green", + "grey", + "han", + "light_amber", + "light_azure", + "light_blue", + "light_chartreuse", + "light_crimson", + "light_cyan", + "light_flame", + "light_fuchsia", + "light_gray", + "light_green", + "light_grey", + "light_han", + "light_lime", + "light_magenta", + "light_orange", + "light_pink", + "light_purple", + "light_red", + "light_sea", + "light_sepia", + "light_sky", + "light_turquoise", + "light_violet", + "light_yellow", + "lighter_amber", + "lighter_azure", + "lighter_blue", + "lighter_chartreuse", + "lighter_crimson", + "lighter_cyan", + "lighter_flame", + "lighter_fuchsia", + "lighter_gray", + "lighter_green", + "lighter_grey", + "lighter_han", + "lighter_lime", + "lighter_magenta", + "lighter_orange", + "lighter_pink", + "lighter_purple", + "lighter_red", + "lighter_sea", + "lighter_sepia", + "lighter_sky", + "lighter_turquoise", + "lighter_violet", + "lighter_yellow", + "lightest_amber", + "lightest_azure", + "lightest_blue", + "lightest_chartreuse", + "lightest_crimson", + "lightest_cyan", + "lightest_flame", + "lightest_fuchsia", + "lightest_gray", + "lightest_green", + "lightest_grey", + "lightest_han", + "lightest_lime", + "lightest_magenta", + "lightest_orange", + "lightest_pink", + "lightest_purple", + "lightest_red", + "lightest_sea", + "lightest_sepia", + "lightest_sky", + "lightest_turquoise", + "lightest_violet", + "lightest_yellow", + "lime", + "magenta", + "orange", + "peach", + "pink", + "purple", + "red", + "sea", + "sepia", + "silver", + "sky", + "turquoise", + "violet", + "white", + "yellow", +] diff --git a/tcod/libtcod.py b/tcod/libtcod.py index 6890d74c..8793c87b 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -45,3 +45,5 @@ def __str__(self): lib = ffi = _Mock() else: from tcod._libtcod import lib, ffi # noqa: F401 + +__all__ = ["ffi", "lib"] From 8db3dac842e6f39bc59c447a6d8927290189c0fb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 11 Jan 2019 19:13:42 -0800 Subject: [PATCH 0029/1101] Have pip upgrade pytest modules. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2cb6a2f..ce714c04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ install: - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' before_script: -- pip install pytest pytest-cov pytest-faulthandler +- pip install --upgrade pytest pytest-cov pytest-faulthandler script: - pytest -v after_success: From ccdd4e4a3bd6f78c05af9a17f9f58621bce02f7e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 13 Jan 2019 18:22:22 -0800 Subject: [PATCH 0030/1101] Merge sdlevent.py into the library as tcod.event --- build_libtcod.py | 68 +- examples/sdlevent.py | 333 +-------- tcod/constants.py | 4 +- tcod/event.py | 283 ++++++++ tcod/event_constants.py | 1520 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1884 insertions(+), 324 deletions(-) create mode 100644 tcod/event.py create mode 100644 tcod/event_constants.py diff --git a/build_libtcod.py b/build_libtcod.py index b85edb7a..31ed4865 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -4,6 +4,7 @@ import sys import glob +from typing import List, Tuple, Any, Dict, Iterator from cffi import FFI from pycparser import c_parser, c_ast, parse_file, c_generator @@ -305,20 +306,60 @@ def get_ast(): ) CONSTANT_MODULE_HEADER = '''""" - Constants from the libtcod C API. +Constants from the libtcod C API. - This module is auto-generated by `build_libtcod.py`. +This module is auto-generated by `build_libtcod.py`. """ from tcod.color import Color ''' +EVENT_CONSTANT_MODULE_HEADER = '''""" +Event constants from SDL's C API. + +This module is auto-generated by `build_libtcod.py`. +""" +''' + + +def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Any]]: + """Return names and values from `tcod.lib`. + + `prefix` is used to filter out which names to copy. + """ + from tcod._libtcod import lib + if prefix.startswith("SDL_"): + name_starts_at = 4 + elif prefix.startswith("SDL"): + name_starts_at = 3 + else: + name_starts_at = 0 + for attr in dir(lib): + if attr.startswith(prefix): + yield attr[name_starts_at:], getattr(lib, attr) + +def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: + """Return the name/value pairs, and the final dictionary string for the + library attributes with `prefix`. + + Append matching names to the `all_names` list. + """ + names = [] + lookup = [] + for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): + all_names.append(name) + names.append('%s = %s' % (name, value)) + lookup.append('%s: "tcod.event.%s"' % (value, name)) + names = '\n'.join(names) + lookup = '{\n %s,\n}' % (',\n '.join(lookup),) + return names, lookup + def write_library_constants(): """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi import tcod.color - all_names = [] with open('tcod/constants.py', 'w') as f: + all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): value = getattr(lib, name) @@ -349,6 +390,27 @@ def write_library_constants(): all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) + with open('tcod/event_constants.py', 'w') as f: + all_names = [] + f.write(EVENT_CONSTANT_MODULE_HEADER) + f.write('# --- SDL scancodes ---\n') + f.write("%s\n_REVERSE_SCANCODE_TABLE = %s\n" + % parse_sdl_attrs("SDL_SCANCODE", all_names)) + + f.write('\n# --- SDL keyboard symbols ---\n') + f.write("%s\n_REVERSE_SYM_TABLE = %s\n" + % parse_sdl_attrs("SDLK", all_names)) + + f.write('\n# --- SDL keyboard modifiers ---\n') + f.write("%s\n_REVERSE_MOD_TABLE = %s\n" + % parse_sdl_attrs("KMOD", all_names)) + + f.write('\n# --- SDL wheel ---\n') + f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" + % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) + all_names = ",\n ".join('"%s"' % name for name in all_names) + f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) + if __name__ == "__main__": write_library_constants() diff --git a/examples/sdlevent.py b/examples/sdlevent.py index 8aeb1228..acfc01c5 100644 --- a/examples/sdlevent.py +++ b/examples/sdlevent.py @@ -1,335 +1,30 @@ -#!/usr/bin/env python -""" - An alternative, more direct implementation of event handling using cffi - calls to SDL functions. The current code is incomplete, but can be - extended easily by following the official SDL documentation. - - This module be run directly like any other event get example, but is meant - to be copied into your code base. Then you can use the sdlevent.get and - sdlevent.wait functions in your code. - - Printing any event will tell you its attributes in a human readable format. - An events type attr is just the classes name with all letters upper-case. - - Like in tdl, events use a type attribute to tell events apart. Unlike tdl - and tcod the names and values used are directly derived from SDL. - - As a general guideline for turn-based rouge-likes, you should use - KeyDown.sym for commands, and TextInput.text for name entry. - - This module may be included as a tcod.event module once it is more mature. - - An absolute minimal example: - - import tcod - import sdlevent - - with tcod.console_init_root(80, 60, 'title') as console: - while True: - for event in sdlevent.wait(): - print(event) - if event.type == 'QUIT': - raise SystemExit() - tcod.console_flush() -""" -# CC0 License: https://creativecommons.org/publicdomain/zero/1.0/ -# To the extent possible under law, Kyle Stewart has waived all copyright and -# related or neighboring rights to this sdlevent.py module. +#!/usr/bin/env python3 import tcod - -def _copy_attrs(prefix): - """Copy names and values from tcod.lib into this modules top-level scope. - - This is a private function, used internally. - - Args: - prefix (str): Used to filter out which names to copy. - - Returns: - Dict[Any,str]: A reverse lookup table used in Event repr functions. - """ - g = globals() # dynamically add names to the global state - # removes the SDL prefix, this module already has SDL in the name - if prefix.startswith('SDL_'): - name_starts_at = 4 - elif prefix.startswith('SDL'): - name_starts_at = 3 - else: - name_starts_at = 0 - revere_table = {} - for attr in dir(tcod.lib): - if attr.startswith(prefix): - name = attr[name_starts_at:] - value = getattr(tcod.lib, attr) - revere_table[value] = 'sdlevent.' + name - g[name] = value - - return revere_table - -def _describe_bitmask(bits, table, default='0'): - """Returns a bitmask in human readable form. - - This is a private function, used internally. - - Args: - bits (int): The bitmask to be represented. - table (Dict[Any,str]): A reverse lookup table. - default (Any): A default return value when bits is 0. - - Returns: str: A printable version of the bits variable. - """ - result = [] - for bit, name in table.items(): - if bit & bits: - result.append(name) - if not result: - return default - return '|'.join(result) - - -_REVSRSE_SCANCODE_TABLE = _copy_attrs('SDL_SCANCODE') -_REVSRSE_SYM_TABLE = _copy_attrs('SDLK') -_REVSRSE_MOD_TABLE = _copy_attrs('KMOD') -_REVSRSE_WHEEL_TABLE = _copy_attrs('SDL_MOUSEWHEEL') - -# manually define names for SDL macros -BUTTON_LEFT = 1 -BUTTON_MIDDLE = 2 -BUTTON_RIGHT = 3 -BUTTON_X1 = 4 -BUTTON_X2 = 5 -BUTTON_LMASK = 0x01 -BUTTON_MMASK = 0x02 -BUTTON_RMASK = 0x04 -BUTTON_X1MASK = 0x08 -BUTTON_X2MASK = 0x10 - -_REVSRSE_BUTTON_TABLE = { - BUTTON_LEFT: 'sdlevent.BUTTON_LEFT', - BUTTON_MIDDLE: 'sdlevent.BUTTON_MIDDLE', - BUTTON_RIGHT: 'sdlevent.BUTTON_RIGHT', - BUTTON_X1: 'sdlevent.BUTTON_X1', - BUTTON_X2: 'sdlevent.BUTTON_X2', -} - -_REVSRSE_BUTTON_MASK_TABLE = { - BUTTON_LMASK: 'sdlevent.BUTTON_LMASK', - BUTTON_MMASK: 'sdlevent.BUTTON_MMASK', - BUTTON_RMASK: 'sdlevent.BUTTON_RMASK', - BUTTON_X1MASK: 'sdlevent.BUTTON_X1MASK', - BUTTON_X2MASK: 'sdlevent.BUTTON_X2MASK', -} - -class Event(object): - """The base event class.""" - @classmethod - def from_sdl_event(cls, sdl_event): - """Return a class instance from a cffi SDL_Event pointer.""" - raise NotImplementedError() - - @property - def type(self): - """All event types are just the class name, but all upper-case.""" - return self.__class__.__name__.upper() - - -class Quit(Event): - """An application quit request event. - - For more info on when this event is triggered see: - https://wiki.libsdl.org/SDL_EventType#SDL_QUIT - """ - @classmethod - def from_sdl_event(cls, sdl_event): - return cls() - - def __repr__(self): - return 'sdlevent.%s()' % self.__class__.__name__ - - -class KeyboardEvent(Event): - - def __init__(self, scancode, sym, mod, repeat=False): - self.scancode = scancode - self.sym = sym - self.mod = mod - self.repeat = repeat - - @classmethod - def from_sdl_event(cls, sdl_event): - keysym = sdl_event.key.keysym - return cls(keysym.scancode, keysym.sym, keysym.mod, - bool(sdl_event.key.repeat)) - - def __repr__(self): - return ('sdlevent.%s(scancode=%s, sym=%s, mod=%s%s)' % - (self.__class__.__name__, - _REVSRSE_SCANCODE_TABLE[self.scancode], - _REVSRSE_SYM_TABLE[self.sym], - _describe_bitmask(self.mod, _REVSRSE_MOD_TABLE), - ', repeat=True' if self.repeat else '', - ) - ) - - -class KeyDown(KeyboardEvent): - pass +import tcod.event -class KeyUp(KeyboardEvent): - pass - - -class MouseMotion(Event): - - def __init__(self, x, y, xrel, yrel, state): - self.x = x - self.y = y - self.xrel = xrel - self.yrel = yrel - self.state = state - - @classmethod - def from_sdl_event(cls, sdl_event): - motion = sdl_event.motion - return cls(motion.x, motion.y, motion.xrel, motion.yrel, motion.state) - - def __repr__(self): - return ('sdlevent.%s(x=%i, y=%i, xrel=%i, yrel=%i, state=%s)' % - (self.__class__.__name__, - self.x, self.y, - self.xrel, self.yrel, - _describe_bitmask(self.state, _REVSRSE_BUTTON_MASK_TABLE), - ) - ) - - -class MouseButtonEvent(Event): - - def __init__(self, x, y, button): - self.x = x - self.y = y - self.button = button - - @classmethod - def from_sdl_event(cls, sdl_event): - button = sdl_event.button - return cls(button.x, button.y, button.button) - - def __repr__(self): - return ('sdlevent.%s(x=%i, y=%i, button=%s)' % - (self.__class__.__name__, - self.x, self.y, _REVSRSE_BUTTON_TABLE[self.button], - ) - ) - - -class MouseButtonDown(MouseButtonEvent): - pass - - -class MouseButtonUp(MouseButtonEvent): - pass - - -class MouseWheel(Event): - - def __init__(self, x, y, direction): - self.x = x - self.y = y - self.direction = direction - - @classmethod - def from_sdl_event(cls, sdl_event): - wheel = sdl_event.wheel - return cls(wheel.x, wheel.y, wheel.direction) - - def __repr__(self): - return ('sdlevent.%s(x=%i, y=%i, direction=%s)' % - (self.__class__.__name__, - self.x, self.y, - _REVSRSE_WHEEL_TABLE[self.direction], - ) - ) - - -class TextInput(Event): - - def __init__(self, text): - self.text = text - - @classmethod - def from_sdl_event(cls, sdl_event): - return cls(tcod.ffi.string(sdl_event.text.text, 32).decode('utf8')) - - def __repr__(self): - return ('sdlevent.%s(text=%r)' % (self.__class__.__name__, self.text)) - - -_SDL_TO_CLASS_TABLE = { - tcod.lib.SDL_QUIT: Quit, - tcod.lib.SDL_KEYDOWN: KeyDown, - tcod.lib.SDL_KEYUP: KeyUp, - tcod.lib.SDL_MOUSEMOTION: MouseMotion, - tcod.lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, - tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp, - tcod.lib.SDL_MOUSEWHEEL: MouseWheel, - tcod.lib.SDL_TEXTINPUT: TextInput, -} - -def get(): - """Iterate over all pending events. - - Returns: - Iterator[sdlevent.Event]: - An iterator of Event subclasses. - """ - sdl_event = tcod.ffi.new('SDL_Event*') - while tcod.lib.SDL_PollEvent(sdl_event): - if sdl_event.type in _SDL_TO_CLASS_TABLE: - yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) - -def wait(timeout=None): - """Block until an event exists, then iterate over all events. - - Keep in mind that this function will wake even for events not handled by - this module. - - Args: - timeout (Optional[int]): - Maximum number of milliseconds to wait, or None to wait forever. - - Returns: - Iterator[sdlevent.Event]: Same iterator as a call to sdlevent.get - """ - if timeout is not None: - tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, timeout) - else: - tcod.lib.SDL_WaitEvent(tcod.ffi.NULL) - return get() - -def _main(): - """An example program for when this module is run directly.""" +def main(): + """Example program for tcod.event""" WIDTH, HEIGHT = 120, 60 - TITLE = 'sdlevent.py engine' + TITLE = None - with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order='F') as console: + with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order="F") as console: tcod.sys_set_fps(24) while True: - for event in wait(): + tcod.console_flush() + for event in tcod.event.wait(): print(event) - if event.type == 'QUIT': + if event.type == "QUIT": raise SystemExit() - elif event.type == 'MOUSEMOTION': - console.ch[:,-1] = 0 + elif event.type == "MOUSEMOTION": + console.ch[:, -1] = 0 console.print_(0, HEIGHT - 1, repr(event)) else: console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) - console.ch[:,-3] = 0 + console.ch[:, -3] = 0 console.print_(0, HEIGHT - 3, repr(event)) - tcod.console_flush() -if __name__ == '__main__': - _main() +if __name__ == "__main__": + main() diff --git a/tcod/constants.py b/tcod/constants.py index 0d809943..50b53a88 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -1,7 +1,7 @@ """ - Constants from the libtcod C API. +Constants from the libtcod C API. - This module is auto-generated by `build_libtcod.py`. +This module is auto-generated by `build_libtcod.py`. """ from tcod.color import Color diff --git a/tcod/event.py b/tcod/event.py new file mode 100644 index 00000000..6f1e432f --- /dev/null +++ b/tcod/event.py @@ -0,0 +1,283 @@ +""" +An alternative, more direct implementation of event handling using cffi +calls to SDL functions. The current code is incomplete, but can be +extended easily by following the official SDL documentation. + +This module be run directly like any other event get example, but is meant +to be copied into your code base. Then you can use the tcod.event.get and +tcod.event.wait functions in your code. + +Printing any event will tell you its attributes in a human readable format. +An events type attr is just the classes name with all letters upper-case. + +Like in tdl, events use a type attribute to tell events apart. Unlike tdl +and tcod the names and values used are directly derived from SDL. + +As a general guideline for turn-based rouge-likes, you should use +KeyDown.sym for commands, and TextInput.text for name entry fields. +""" +from typing import Any, Dict, Optional, Iterator + +import tcod +import tcod.event_constants +from tcod.event_constants import * + +def _describe_bitmask( + bits: int, table: Dict[Any, str], default: Any = "0" +) -> str: + """Returns a bitmask in human readable form. + + This is a private function, used internally. + + Args: + bits (int): The bitmask to be represented. + table (Dict[Any,str]): A reverse lookup table. + default (Any): A default return value when bits is 0. + + Returns: str: A printable version of the bits variable. + """ + result = [] + for bit, name in table.items(): + if bit & bits: + result.append(name) + if not result: + return default + return "|".join(result) + + +# manually define names for SDL macros +BUTTON_LEFT = 1 +BUTTON_MIDDLE = 2 +BUTTON_RIGHT = 3 +BUTTON_X1 = 4 +BUTTON_X2 = 5 +BUTTON_LMASK = 0x01 +BUTTON_MMASK = 0x02 +BUTTON_RMASK = 0x04 +BUTTON_X1MASK = 0x08 +BUTTON_X2MASK = 0x10 + +# reverse tables are used to get the tcod.event name from the value. +_REVERSE_BUTTON_TABLE = { + BUTTON_LEFT: "tcod.event.BUTTON_LEFT", + BUTTON_MIDDLE: "tcod.event.BUTTON_MIDDLE", + BUTTON_RIGHT: "tcod.event.BUTTON_RIGHT", + BUTTON_X1: "tcod.event.BUTTON_X1", + BUTTON_X2: "tcod.event.BUTTON_X2", +} + +_REVERSE_BUTTON_MASK_TABLE = { + BUTTON_LMASK: "tcod.event.BUTTON_LMASK", + BUTTON_MMASK: "tcod.event.BUTTON_MMASK", + BUTTON_RMASK: "tcod.event.BUTTON_RMASK", + BUTTON_X1MASK: "tcod.event.BUTTON_X1MASK", + BUTTON_X2MASK: "tcod.event.BUTTON_X2MASK", +} + + +class Event: + """The base event class.""" + + @classmethod + def from_sdl_event(cls, sdl_event): + """Return a class instance from a cffi SDL_Event pointer.""" + raise NotImplementedError() + + @property + def type(self): + """All event types are just the class name, but all upper-case.""" + return self.__class__.__name__.upper() + + +class Quit(Event): + """An application quit request event. + + For more info on when this event is triggered see: + https://wiki.libsdl.org/SDL_EventType#SDL_QUIT + """ + + @classmethod + def from_sdl_event(cls, sdl_event): + return cls() + + def __repr__(self): + return "tcod.event.%s()" % self.__class__.__name__ + + +class KeyboardEvent(Event): + def __init__(self, scancode, sym, mod, repeat=False): + self.scancode = scancode + self.sym = sym + self.mod = mod + self.repeat = repeat + + @classmethod + def from_sdl_event(cls, sdl_event): + keysym = sdl_event.key.keysym + return cls( + keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat) + ) + + def __repr__(self): + return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( + self.__class__.__name__, + tcod.event_constants._REVERSE_SCANCODE_TABLE[self.scancode], + tcod.event_constants._REVERSE_SYM_TABLE[self.sym], + _describe_bitmask(self.mod, tcod.event_constants._REVERSE_MOD_TABLE), + ", repeat=True" if self.repeat else "", + ) + + +class KeyDown(KeyboardEvent): + pass + + +class KeyUp(KeyboardEvent): + pass + + +class MouseMotion(Event): + def __init__(self, x, y, xrel, yrel, state): + self.x = x + self.y = y + self.xrel = xrel + self.yrel = yrel + self.state = state + + @classmethod + def from_sdl_event(cls, sdl_event): + motion = sdl_event.motion + return cls(motion.x, motion.y, motion.xrel, motion.yrel, motion.state) + + def __repr__(self): + return "tcod.event.%s(x=%i, y=%i, xrel=%i, yrel=%i, state=%s)" % ( + self.__class__.__name__, + self.x, + self.y, + self.xrel, + self.yrel, + _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), + ) + + +class MouseButtonEvent(Event): + def __init__(self, x, y, button): + self.x = x + self.y = y + self.button = button + + @classmethod + def from_sdl_event(cls, sdl_event): + button = sdl_event.button + return cls(button.x, button.y, button.button) + + def __repr__(self): + return "tcod.event.%s(x=%i, y=%i, button=%s)" % ( + self.__class__.__name__, + self.x, + self.y, + _REVERSE_BUTTON_TABLE[self.button], + ) + + +class MouseButtonDown(MouseButtonEvent): + pass + + +class MouseButtonUp(MouseButtonEvent): + pass + + +class MouseWheel(Event): + def __init__(self, x, y, direction): + self.x = x + self.y = y + self.direction = direction + + @classmethod + def from_sdl_event(cls, sdl_event): + wheel = sdl_event.wheel + return cls(wheel.x, wheel.y, wheel.direction) + + def __repr__(self): + return "tcod.event.%s(x=%i, y=%i, direction=%s)" % ( + self.__class__.__name__, + self.x, + self.y, + tcod.event_constants._REVERSE_WHEEL_TABLE[self.direction], + ) + + +class TextInput(Event): + def __init__(self, text): + self.text = text + + @classmethod + def from_sdl_event(cls, sdl_event): + return cls(tcod.ffi.string(sdl_event.text.text, 32).decode("utf8")) + + def __repr__(self): + return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) + + +_SDL_TO_CLASS_TABLE = { + tcod.lib.SDL_QUIT: Quit, + tcod.lib.SDL_KEYDOWN: KeyDown, + tcod.lib.SDL_KEYUP: KeyUp, + tcod.lib.SDL_MOUSEMOTION: MouseMotion, + tcod.lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, + tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp, + tcod.lib.SDL_MOUSEWHEEL: MouseWheel, + tcod.lib.SDL_TEXTINPUT: TextInput, +} + + +def get() -> Iterator[Any]: + """Iterate over all pending events. + + Returns: + Iterator[tcod.event.Event]: + An iterator of Event subclasses. + """ + sdl_event = tcod.ffi.new("SDL_Event*") + while tcod.lib.SDL_PollEvent(sdl_event): + if sdl_event.type in _SDL_TO_CLASS_TABLE: + yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) + + +def wait(timeout: Optional[float] = None) -> Iterator[Any]: + """Block until events exist, then iterate over all events. + + Keep in mind that this function will wake even for events not handled by + this module. + + Args: + timeout (Optional[float]): + Maximum number of seconds to wait, or None to wait forever. + Has millisecond percision. + + Returns: + Iterator[tcod.event.Event]: Same iterator as a call to tcod.event.get + """ + if timeout is not None: + tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, int(timeout * 1000)) + else: + tcod.lib.SDL_WaitEvent(tcod.ffi.NULL) + return get() + + +__all__ = [ + "Event", + "Quit", + "KeynoardEvent", + "KeyDown", + "KeyUp", + "MouseMotion", + "MouseButtonEvent", + "MouseButtonDown", + "MouseButtonUp", + "MouseWheel", + "TextInput", + "get", + "wait", +] + tcod.event_constants.__all__ diff --git a/tcod/event_constants.py b/tcod/event_constants.py new file mode 100644 index 00000000..8b01cd8f --- /dev/null +++ b/tcod/event_constants.py @@ -0,0 +1,1520 @@ +""" +Event constants from SDL's C API. + +This module is auto-generated by `build_libtcod.py`. +""" +# --- SDL scancodes --- +SCANCODE_UNKNOWN = 0 +SCANCODE_A = 4 +SCANCODE_B = 5 +SCANCODE_C = 6 +SCANCODE_D = 7 +SCANCODE_E = 8 +SCANCODE_F = 9 +SCANCODE_G = 10 +SCANCODE_H = 11 +SCANCODE_I = 12 +SCANCODE_J = 13 +SCANCODE_K = 14 +SCANCODE_L = 15 +SCANCODE_M = 16 +SCANCODE_N = 17 +SCANCODE_O = 18 +SCANCODE_P = 19 +SCANCODE_Q = 20 +SCANCODE_R = 21 +SCANCODE_S = 22 +SCANCODE_T = 23 +SCANCODE_U = 24 +SCANCODE_V = 25 +SCANCODE_W = 26 +SCANCODE_X = 27 +SCANCODE_Y = 28 +SCANCODE_Z = 29 +SCANCODE_1 = 30 +SCANCODE_2 = 31 +SCANCODE_3 = 32 +SCANCODE_4 = 33 +SCANCODE_5 = 34 +SCANCODE_6 = 35 +SCANCODE_7 = 36 +SCANCODE_8 = 37 +SCANCODE_9 = 38 +SCANCODE_0 = 39 +SCANCODE_RETURN = 40 +SCANCODE_ESCAPE = 41 +SCANCODE_BACKSPACE = 42 +SCANCODE_TAB = 43 +SCANCODE_SPACE = 44 +SCANCODE_MINUS = 45 +SCANCODE_EQUALS = 46 +SCANCODE_LEFTBRACKET = 47 +SCANCODE_RIGHTBRACKET = 48 +SCANCODE_BACKSLASH = 49 +SCANCODE_NONUSHASH = 50 +SCANCODE_SEMICOLON = 51 +SCANCODE_APOSTROPHE = 52 +SCANCODE_GRAVE = 53 +SCANCODE_COMMA = 54 +SCANCODE_PERIOD = 55 +SCANCODE_SLASH = 56 +SCANCODE_CAPSLOCK = 57 +SCANCODE_F1 = 58 +SCANCODE_F2 = 59 +SCANCODE_F3 = 60 +SCANCODE_F4 = 61 +SCANCODE_F5 = 62 +SCANCODE_F6 = 63 +SCANCODE_F7 = 64 +SCANCODE_F8 = 65 +SCANCODE_F9 = 66 +SCANCODE_F10 = 67 +SCANCODE_F11 = 68 +SCANCODE_F12 = 69 +SCANCODE_PRINTSCREEN = 70 +SCANCODE_SCROLLLOCK = 71 +SCANCODE_PAUSE = 72 +SCANCODE_INSERT = 73 +SCANCODE_HOME = 74 +SCANCODE_PAGEUP = 75 +SCANCODE_DELETE = 76 +SCANCODE_END = 77 +SCANCODE_PAGEDOWN = 78 +SCANCODE_RIGHT = 79 +SCANCODE_LEFT = 80 +SCANCODE_DOWN = 81 +SCANCODE_UP = 82 +SCANCODE_NUMLOCKCLEAR = 83 +SCANCODE_KP_DIVIDE = 84 +SCANCODE_KP_MULTIPLY = 85 +SCANCODE_KP_MINUS = 86 +SCANCODE_KP_PLUS = 87 +SCANCODE_KP_ENTER = 88 +SCANCODE_KP_1 = 89 +SCANCODE_KP_2 = 90 +SCANCODE_KP_3 = 91 +SCANCODE_KP_4 = 92 +SCANCODE_KP_5 = 93 +SCANCODE_KP_6 = 94 +SCANCODE_KP_7 = 95 +SCANCODE_KP_8 = 96 +SCANCODE_KP_9 = 97 +SCANCODE_KP_0 = 98 +SCANCODE_KP_PERIOD = 99 +SCANCODE_NONUSBACKSLASH = 100 +SCANCODE_APPLICATION = 101 +SCANCODE_POWER = 102 +SCANCODE_KP_EQUALS = 103 +SCANCODE_F13 = 104 +SCANCODE_F14 = 105 +SCANCODE_F15 = 106 +SCANCODE_F16 = 107 +SCANCODE_F17 = 108 +SCANCODE_F18 = 109 +SCANCODE_F19 = 110 +SCANCODE_F20 = 111 +SCANCODE_F21 = 112 +SCANCODE_F22 = 113 +SCANCODE_F23 = 114 +SCANCODE_F24 = 115 +SCANCODE_EXECUTE = 116 +SCANCODE_HELP = 117 +SCANCODE_MENU = 118 +SCANCODE_SELECT = 119 +SCANCODE_STOP = 120 +SCANCODE_AGAIN = 121 +SCANCODE_UNDO = 122 +SCANCODE_CUT = 123 +SCANCODE_COPY = 124 +SCANCODE_PASTE = 125 +SCANCODE_FIND = 126 +SCANCODE_MUTE = 127 +SCANCODE_VOLUMEUP = 128 +SCANCODE_VOLUMEDOWN = 129 +SCANCODE_KP_COMMA = 133 +SCANCODE_KP_EQUALSAS400 = 134 +SCANCODE_INTERNATIONAL1 = 135 +SCANCODE_INTERNATIONAL2 = 136 +SCANCODE_INTERNATIONAL3 = 137 +SCANCODE_INTERNATIONAL4 = 138 +SCANCODE_INTERNATIONAL5 = 139 +SCANCODE_INTERNATIONAL6 = 140 +SCANCODE_INTERNATIONAL7 = 141 +SCANCODE_INTERNATIONAL8 = 142 +SCANCODE_INTERNATIONAL9 = 143 +SCANCODE_LANG1 = 144 +SCANCODE_LANG2 = 145 +SCANCODE_LANG3 = 146 +SCANCODE_LANG4 = 147 +SCANCODE_LANG5 = 148 +SCANCODE_LANG6 = 149 +SCANCODE_LANG7 = 150 +SCANCODE_LANG8 = 151 +SCANCODE_LANG9 = 152 +SCANCODE_ALTERASE = 153 +SCANCODE_SYSREQ = 154 +SCANCODE_CANCEL = 155 +SCANCODE_CLEAR = 156 +SCANCODE_PRIOR = 157 +SCANCODE_RETURN2 = 158 +SCANCODE_SEPARATOR = 159 +SCANCODE_OUT = 160 +SCANCODE_OPER = 161 +SCANCODE_CLEARAGAIN = 162 +SCANCODE_CRSEL = 163 +SCANCODE_EXSEL = 164 +SCANCODE_KP_00 = 176 +SCANCODE_KP_000 = 177 +SCANCODE_THOUSANDSSEPARATOR = 178 +SCANCODE_DECIMALSEPARATOR = 179 +SCANCODE_CURRENCYUNIT = 180 +SCANCODE_CURRENCYSUBUNIT = 181 +SCANCODE_KP_LEFTPAREN = 182 +SCANCODE_KP_RIGHTPAREN = 183 +SCANCODE_KP_LEFTBRACE = 184 +SCANCODE_KP_RIGHTBRACE = 185 +SCANCODE_KP_TAB = 186 +SCANCODE_KP_BACKSPACE = 187 +SCANCODE_KP_A = 188 +SCANCODE_KP_B = 189 +SCANCODE_KP_C = 190 +SCANCODE_KP_D = 191 +SCANCODE_KP_E = 192 +SCANCODE_KP_F = 193 +SCANCODE_KP_XOR = 194 +SCANCODE_KP_POWER = 195 +SCANCODE_KP_PERCENT = 196 +SCANCODE_KP_LESS = 197 +SCANCODE_KP_GREATER = 198 +SCANCODE_KP_AMPERSAND = 199 +SCANCODE_KP_DBLAMPERSAND = 200 +SCANCODE_KP_VERTICALBAR = 201 +SCANCODE_KP_DBLVERTICALBAR = 202 +SCANCODE_KP_COLON = 203 +SCANCODE_KP_HASH = 204 +SCANCODE_KP_SPACE = 205 +SCANCODE_KP_AT = 206 +SCANCODE_KP_EXCLAM = 207 +SCANCODE_KP_MEMSTORE = 208 +SCANCODE_KP_MEMRECALL = 209 +SCANCODE_KP_MEMCLEAR = 210 +SCANCODE_KP_MEMADD = 211 +SCANCODE_KP_MEMSUBTRACT = 212 +SCANCODE_KP_MEMMULTIPLY = 213 +SCANCODE_KP_MEMDIVIDE = 214 +SCANCODE_KP_PLUSMINUS = 215 +SCANCODE_KP_CLEAR = 216 +SCANCODE_KP_CLEARENTRY = 217 +SCANCODE_KP_BINARY = 218 +SCANCODE_KP_OCTAL = 219 +SCANCODE_KP_DECIMAL = 220 +SCANCODE_KP_HEXADECIMAL = 221 +SCANCODE_LCTRL = 224 +SCANCODE_LSHIFT = 225 +SCANCODE_LALT = 226 +SCANCODE_LGUI = 227 +SCANCODE_RCTRL = 228 +SCANCODE_RSHIFT = 229 +SCANCODE_RALT = 230 +SCANCODE_RGUI = 231 +SCANCODE_MODE = 257 +SCANCODE_AUDIONEXT = 258 +SCANCODE_AUDIOPREV = 259 +SCANCODE_AUDIOSTOP = 260 +SCANCODE_AUDIOPLAY = 261 +SCANCODE_AUDIOMUTE = 262 +SCANCODE_MEDIASELECT = 263 +SCANCODE_WWW = 264 +SCANCODE_MAIL = 265 +SCANCODE_CALCULATOR = 266 +SCANCODE_COMPUTER = 267 +SCANCODE_AC_SEARCH = 268 +SCANCODE_AC_HOME = 269 +SCANCODE_AC_BACK = 270 +SCANCODE_AC_FORWARD = 271 +SCANCODE_AC_STOP = 272 +SCANCODE_AC_REFRESH = 273 +SCANCODE_AC_BOOKMARKS = 274 +SCANCODE_BRIGHTNESSDOWN = 275 +SCANCODE_BRIGHTNESSUP = 276 +SCANCODE_DISPLAYSWITCH = 277 +SCANCODE_KBDILLUMTOGGLE = 278 +SCANCODE_KBDILLUMDOWN = 279 +SCANCODE_KBDILLUMUP = 280 +SCANCODE_EJECT = 281 +SCANCODE_SLEEP = 282 +SCANCODE_APP1 = 283 +SCANCODE_APP2 = 284 +SCANCODE_AUDIOREWIND = 285 +SCANCODE_AUDIOFASTFORWARD = 286 +_REVERSE_SCANCODE_TABLE = { + 0: "tcod.event.SCANCODE_UNKNOWN", + 4: "tcod.event.SCANCODE_A", + 5: "tcod.event.SCANCODE_B", + 6: "tcod.event.SCANCODE_C", + 7: "tcod.event.SCANCODE_D", + 8: "tcod.event.SCANCODE_E", + 9: "tcod.event.SCANCODE_F", + 10: "tcod.event.SCANCODE_G", + 11: "tcod.event.SCANCODE_H", + 12: "tcod.event.SCANCODE_I", + 13: "tcod.event.SCANCODE_J", + 14: "tcod.event.SCANCODE_K", + 15: "tcod.event.SCANCODE_L", + 16: "tcod.event.SCANCODE_M", + 17: "tcod.event.SCANCODE_N", + 18: "tcod.event.SCANCODE_O", + 19: "tcod.event.SCANCODE_P", + 20: "tcod.event.SCANCODE_Q", + 21: "tcod.event.SCANCODE_R", + 22: "tcod.event.SCANCODE_S", + 23: "tcod.event.SCANCODE_T", + 24: "tcod.event.SCANCODE_U", + 25: "tcod.event.SCANCODE_V", + 26: "tcod.event.SCANCODE_W", + 27: "tcod.event.SCANCODE_X", + 28: "tcod.event.SCANCODE_Y", + 29: "tcod.event.SCANCODE_Z", + 30: "tcod.event.SCANCODE_1", + 31: "tcod.event.SCANCODE_2", + 32: "tcod.event.SCANCODE_3", + 33: "tcod.event.SCANCODE_4", + 34: "tcod.event.SCANCODE_5", + 35: "tcod.event.SCANCODE_6", + 36: "tcod.event.SCANCODE_7", + 37: "tcod.event.SCANCODE_8", + 38: "tcod.event.SCANCODE_9", + 39: "tcod.event.SCANCODE_0", + 40: "tcod.event.SCANCODE_RETURN", + 41: "tcod.event.SCANCODE_ESCAPE", + 42: "tcod.event.SCANCODE_BACKSPACE", + 43: "tcod.event.SCANCODE_TAB", + 44: "tcod.event.SCANCODE_SPACE", + 45: "tcod.event.SCANCODE_MINUS", + 46: "tcod.event.SCANCODE_EQUALS", + 47: "tcod.event.SCANCODE_LEFTBRACKET", + 48: "tcod.event.SCANCODE_RIGHTBRACKET", + 49: "tcod.event.SCANCODE_BACKSLASH", + 50: "tcod.event.SCANCODE_NONUSHASH", + 51: "tcod.event.SCANCODE_SEMICOLON", + 52: "tcod.event.SCANCODE_APOSTROPHE", + 53: "tcod.event.SCANCODE_GRAVE", + 54: "tcod.event.SCANCODE_COMMA", + 55: "tcod.event.SCANCODE_PERIOD", + 56: "tcod.event.SCANCODE_SLASH", + 57: "tcod.event.SCANCODE_CAPSLOCK", + 58: "tcod.event.SCANCODE_F1", + 59: "tcod.event.SCANCODE_F2", + 60: "tcod.event.SCANCODE_F3", + 61: "tcod.event.SCANCODE_F4", + 62: "tcod.event.SCANCODE_F5", + 63: "tcod.event.SCANCODE_F6", + 64: "tcod.event.SCANCODE_F7", + 65: "tcod.event.SCANCODE_F8", + 66: "tcod.event.SCANCODE_F9", + 67: "tcod.event.SCANCODE_F10", + 68: "tcod.event.SCANCODE_F11", + 69: "tcod.event.SCANCODE_F12", + 70: "tcod.event.SCANCODE_PRINTSCREEN", + 71: "tcod.event.SCANCODE_SCROLLLOCK", + 72: "tcod.event.SCANCODE_PAUSE", + 73: "tcod.event.SCANCODE_INSERT", + 74: "tcod.event.SCANCODE_HOME", + 75: "tcod.event.SCANCODE_PAGEUP", + 76: "tcod.event.SCANCODE_DELETE", + 77: "tcod.event.SCANCODE_END", + 78: "tcod.event.SCANCODE_PAGEDOWN", + 79: "tcod.event.SCANCODE_RIGHT", + 80: "tcod.event.SCANCODE_LEFT", + 81: "tcod.event.SCANCODE_DOWN", + 82: "tcod.event.SCANCODE_UP", + 83: "tcod.event.SCANCODE_NUMLOCKCLEAR", + 84: "tcod.event.SCANCODE_KP_DIVIDE", + 85: "tcod.event.SCANCODE_KP_MULTIPLY", + 86: "tcod.event.SCANCODE_KP_MINUS", + 87: "tcod.event.SCANCODE_KP_PLUS", + 88: "tcod.event.SCANCODE_KP_ENTER", + 89: "tcod.event.SCANCODE_KP_1", + 90: "tcod.event.SCANCODE_KP_2", + 91: "tcod.event.SCANCODE_KP_3", + 92: "tcod.event.SCANCODE_KP_4", + 93: "tcod.event.SCANCODE_KP_5", + 94: "tcod.event.SCANCODE_KP_6", + 95: "tcod.event.SCANCODE_KP_7", + 96: "tcod.event.SCANCODE_KP_8", + 97: "tcod.event.SCANCODE_KP_9", + 98: "tcod.event.SCANCODE_KP_0", + 99: "tcod.event.SCANCODE_KP_PERIOD", + 100: "tcod.event.SCANCODE_NONUSBACKSLASH", + 101: "tcod.event.SCANCODE_APPLICATION", + 102: "tcod.event.SCANCODE_POWER", + 103: "tcod.event.SCANCODE_KP_EQUALS", + 104: "tcod.event.SCANCODE_F13", + 105: "tcod.event.SCANCODE_F14", + 106: "tcod.event.SCANCODE_F15", + 107: "tcod.event.SCANCODE_F16", + 108: "tcod.event.SCANCODE_F17", + 109: "tcod.event.SCANCODE_F18", + 110: "tcod.event.SCANCODE_F19", + 111: "tcod.event.SCANCODE_F20", + 112: "tcod.event.SCANCODE_F21", + 113: "tcod.event.SCANCODE_F22", + 114: "tcod.event.SCANCODE_F23", + 115: "tcod.event.SCANCODE_F24", + 116: "tcod.event.SCANCODE_EXECUTE", + 117: "tcod.event.SCANCODE_HELP", + 118: "tcod.event.SCANCODE_MENU", + 119: "tcod.event.SCANCODE_SELECT", + 120: "tcod.event.SCANCODE_STOP", + 121: "tcod.event.SCANCODE_AGAIN", + 122: "tcod.event.SCANCODE_UNDO", + 123: "tcod.event.SCANCODE_CUT", + 124: "tcod.event.SCANCODE_COPY", + 125: "tcod.event.SCANCODE_PASTE", + 126: "tcod.event.SCANCODE_FIND", + 127: "tcod.event.SCANCODE_MUTE", + 128: "tcod.event.SCANCODE_VOLUMEUP", + 129: "tcod.event.SCANCODE_VOLUMEDOWN", + 133: "tcod.event.SCANCODE_KP_COMMA", + 134: "tcod.event.SCANCODE_KP_EQUALSAS400", + 135: "tcod.event.SCANCODE_INTERNATIONAL1", + 136: "tcod.event.SCANCODE_INTERNATIONAL2", + 137: "tcod.event.SCANCODE_INTERNATIONAL3", + 138: "tcod.event.SCANCODE_INTERNATIONAL4", + 139: "tcod.event.SCANCODE_INTERNATIONAL5", + 140: "tcod.event.SCANCODE_INTERNATIONAL6", + 141: "tcod.event.SCANCODE_INTERNATIONAL7", + 142: "tcod.event.SCANCODE_INTERNATIONAL8", + 143: "tcod.event.SCANCODE_INTERNATIONAL9", + 144: "tcod.event.SCANCODE_LANG1", + 145: "tcod.event.SCANCODE_LANG2", + 146: "tcod.event.SCANCODE_LANG3", + 147: "tcod.event.SCANCODE_LANG4", + 148: "tcod.event.SCANCODE_LANG5", + 149: "tcod.event.SCANCODE_LANG6", + 150: "tcod.event.SCANCODE_LANG7", + 151: "tcod.event.SCANCODE_LANG8", + 152: "tcod.event.SCANCODE_LANG9", + 153: "tcod.event.SCANCODE_ALTERASE", + 154: "tcod.event.SCANCODE_SYSREQ", + 155: "tcod.event.SCANCODE_CANCEL", + 156: "tcod.event.SCANCODE_CLEAR", + 157: "tcod.event.SCANCODE_PRIOR", + 158: "tcod.event.SCANCODE_RETURN2", + 159: "tcod.event.SCANCODE_SEPARATOR", + 160: "tcod.event.SCANCODE_OUT", + 161: "tcod.event.SCANCODE_OPER", + 162: "tcod.event.SCANCODE_CLEARAGAIN", + 163: "tcod.event.SCANCODE_CRSEL", + 164: "tcod.event.SCANCODE_EXSEL", + 176: "tcod.event.SCANCODE_KP_00", + 177: "tcod.event.SCANCODE_KP_000", + 178: "tcod.event.SCANCODE_THOUSANDSSEPARATOR", + 179: "tcod.event.SCANCODE_DECIMALSEPARATOR", + 180: "tcod.event.SCANCODE_CURRENCYUNIT", + 181: "tcod.event.SCANCODE_CURRENCYSUBUNIT", + 182: "tcod.event.SCANCODE_KP_LEFTPAREN", + 183: "tcod.event.SCANCODE_KP_RIGHTPAREN", + 184: "tcod.event.SCANCODE_KP_LEFTBRACE", + 185: "tcod.event.SCANCODE_KP_RIGHTBRACE", + 186: "tcod.event.SCANCODE_KP_TAB", + 187: "tcod.event.SCANCODE_KP_BACKSPACE", + 188: "tcod.event.SCANCODE_KP_A", + 189: "tcod.event.SCANCODE_KP_B", + 190: "tcod.event.SCANCODE_KP_C", + 191: "tcod.event.SCANCODE_KP_D", + 192: "tcod.event.SCANCODE_KP_E", + 193: "tcod.event.SCANCODE_KP_F", + 194: "tcod.event.SCANCODE_KP_XOR", + 195: "tcod.event.SCANCODE_KP_POWER", + 196: "tcod.event.SCANCODE_KP_PERCENT", + 197: "tcod.event.SCANCODE_KP_LESS", + 198: "tcod.event.SCANCODE_KP_GREATER", + 199: "tcod.event.SCANCODE_KP_AMPERSAND", + 200: "tcod.event.SCANCODE_KP_DBLAMPERSAND", + 201: "tcod.event.SCANCODE_KP_VERTICALBAR", + 202: "tcod.event.SCANCODE_KP_DBLVERTICALBAR", + 203: "tcod.event.SCANCODE_KP_COLON", + 204: "tcod.event.SCANCODE_KP_HASH", + 205: "tcod.event.SCANCODE_KP_SPACE", + 206: "tcod.event.SCANCODE_KP_AT", + 207: "tcod.event.SCANCODE_KP_EXCLAM", + 208: "tcod.event.SCANCODE_KP_MEMSTORE", + 209: "tcod.event.SCANCODE_KP_MEMRECALL", + 210: "tcod.event.SCANCODE_KP_MEMCLEAR", + 211: "tcod.event.SCANCODE_KP_MEMADD", + 212: "tcod.event.SCANCODE_KP_MEMSUBTRACT", + 213: "tcod.event.SCANCODE_KP_MEMMULTIPLY", + 214: "tcod.event.SCANCODE_KP_MEMDIVIDE", + 215: "tcod.event.SCANCODE_KP_PLUSMINUS", + 216: "tcod.event.SCANCODE_KP_CLEAR", + 217: "tcod.event.SCANCODE_KP_CLEARENTRY", + 218: "tcod.event.SCANCODE_KP_BINARY", + 219: "tcod.event.SCANCODE_KP_OCTAL", + 220: "tcod.event.SCANCODE_KP_DECIMAL", + 221: "tcod.event.SCANCODE_KP_HEXADECIMAL", + 224: "tcod.event.SCANCODE_LCTRL", + 225: "tcod.event.SCANCODE_LSHIFT", + 226: "tcod.event.SCANCODE_LALT", + 227: "tcod.event.SCANCODE_LGUI", + 228: "tcod.event.SCANCODE_RCTRL", + 229: "tcod.event.SCANCODE_RSHIFT", + 230: "tcod.event.SCANCODE_RALT", + 231: "tcod.event.SCANCODE_RGUI", + 257: "tcod.event.SCANCODE_MODE", + 258: "tcod.event.SCANCODE_AUDIONEXT", + 259: "tcod.event.SCANCODE_AUDIOPREV", + 260: "tcod.event.SCANCODE_AUDIOSTOP", + 261: "tcod.event.SCANCODE_AUDIOPLAY", + 262: "tcod.event.SCANCODE_AUDIOMUTE", + 263: "tcod.event.SCANCODE_MEDIASELECT", + 264: "tcod.event.SCANCODE_WWW", + 265: "tcod.event.SCANCODE_MAIL", + 266: "tcod.event.SCANCODE_CALCULATOR", + 267: "tcod.event.SCANCODE_COMPUTER", + 268: "tcod.event.SCANCODE_AC_SEARCH", + 269: "tcod.event.SCANCODE_AC_HOME", + 270: "tcod.event.SCANCODE_AC_BACK", + 271: "tcod.event.SCANCODE_AC_FORWARD", + 272: "tcod.event.SCANCODE_AC_STOP", + 273: "tcod.event.SCANCODE_AC_REFRESH", + 274: "tcod.event.SCANCODE_AC_BOOKMARKS", + 275: "tcod.event.SCANCODE_BRIGHTNESSDOWN", + 276: "tcod.event.SCANCODE_BRIGHTNESSUP", + 277: "tcod.event.SCANCODE_DISPLAYSWITCH", + 278: "tcod.event.SCANCODE_KBDILLUMTOGGLE", + 279: "tcod.event.SCANCODE_KBDILLUMDOWN", + 280: "tcod.event.SCANCODE_KBDILLUMUP", + 281: "tcod.event.SCANCODE_EJECT", + 282: "tcod.event.SCANCODE_SLEEP", + 283: "tcod.event.SCANCODE_APP1", + 284: "tcod.event.SCANCODE_APP2", + 285: "tcod.event.SCANCODE_AUDIOREWIND", + 286: "tcod.event.SCANCODE_AUDIOFASTFORWARD", +} + +# --- SDL keyboard symbols --- +K_UNKNOWN = 0 +K_BACKSPACE = 8 +K_TAB = 9 +K_RETURN = 13 +K_ESCAPE = 27 +K_SPACE = 32 +K_EXCLAIM = 33 +K_QUOTEDBL = 34 +K_HASH = 35 +K_DOLLAR = 36 +K_PERCENT = 37 +K_AMPERSAND = 38 +K_QUOTE = 39 +K_LEFTPAREN = 40 +K_RIGHTPAREN = 41 +K_ASTERISK = 42 +K_PLUS = 43 +K_COMMA = 44 +K_MINUS = 45 +K_PERIOD = 46 +K_SLASH = 47 +K_0 = 48 +K_1 = 49 +K_2 = 50 +K_3 = 51 +K_4 = 52 +K_5 = 53 +K_6 = 54 +K_7 = 55 +K_8 = 56 +K_9 = 57 +K_COLON = 58 +K_SEMICOLON = 59 +K_LESS = 60 +K_EQUALS = 61 +K_GREATER = 62 +K_QUESTION = 63 +K_AT = 64 +K_LEFTBRACKET = 91 +K_BACKSLASH = 92 +K_RIGHTBRACKET = 93 +K_CARET = 94 +K_UNDERSCORE = 95 +K_BACKQUOTE = 96 +K_a = 97 +K_b = 98 +K_c = 99 +K_d = 100 +K_e = 101 +K_f = 102 +K_g = 103 +K_h = 104 +K_i = 105 +K_j = 106 +K_k = 107 +K_l = 108 +K_m = 109 +K_n = 110 +K_o = 111 +K_p = 112 +K_q = 113 +K_r = 114 +K_s = 115 +K_t = 116 +K_u = 117 +K_v = 118 +K_w = 119 +K_x = 120 +K_y = 121 +K_z = 122 +K_DELETE = 127 +K_CAPSLOCK = 1073741881 +K_F1 = 1073741882 +K_F2 = 1073741883 +K_F3 = 1073741884 +K_F4 = 1073741885 +K_F5 = 1073741886 +K_F6 = 1073741887 +K_F7 = 1073741888 +K_F8 = 1073741889 +K_F9 = 1073741890 +K_F10 = 1073741891 +K_F11 = 1073741892 +K_F12 = 1073741893 +K_PRINTSCREEN = 1073741894 +K_SCROLLLOCK = 1073741895 +K_PAUSE = 1073741896 +K_INSERT = 1073741897 +K_HOME = 1073741898 +K_PAGEUP = 1073741899 +K_END = 1073741901 +K_PAGEDOWN = 1073741902 +K_RIGHT = 1073741903 +K_LEFT = 1073741904 +K_DOWN = 1073741905 +K_UP = 1073741906 +K_NUMLOCKCLEAR = 1073741907 +K_KP_DIVIDE = 1073741908 +K_KP_MULTIPLY = 1073741909 +K_KP_MINUS = 1073741910 +K_KP_PLUS = 1073741911 +K_KP_ENTER = 1073741912 +K_KP_1 = 1073741913 +K_KP_2 = 1073741914 +K_KP_3 = 1073741915 +K_KP_4 = 1073741916 +K_KP_5 = 1073741917 +K_KP_6 = 1073741918 +K_KP_7 = 1073741919 +K_KP_8 = 1073741920 +K_KP_9 = 1073741921 +K_KP_0 = 1073741922 +K_KP_PERIOD = 1073741923 +K_APPLICATION = 1073741925 +K_POWER = 1073741926 +K_KP_EQUALS = 1073741927 +K_F13 = 1073741928 +K_F14 = 1073741929 +K_F15 = 1073741930 +K_F16 = 1073741931 +K_F17 = 1073741932 +K_F18 = 1073741933 +K_F19 = 1073741934 +K_F20 = 1073741935 +K_F21 = 1073741936 +K_F22 = 1073741937 +K_F23 = 1073741938 +K_F24 = 1073741939 +K_EXECUTE = 1073741940 +K_HELP = 1073741941 +K_MENU = 1073741942 +K_SELECT = 1073741943 +K_STOP = 1073741944 +K_AGAIN = 1073741945 +K_UNDO = 1073741946 +K_CUT = 1073741947 +K_COPY = 1073741948 +K_PASTE = 1073741949 +K_FIND = 1073741950 +K_MUTE = 1073741951 +K_VOLUMEUP = 1073741952 +K_VOLUMEDOWN = 1073741953 +K_KP_COMMA = 1073741957 +K_KP_EQUALSAS400 = 1073741958 +K_ALTERASE = 1073741977 +K_SYSREQ = 1073741978 +K_CANCEL = 1073741979 +K_CLEAR = 1073741980 +K_PRIOR = 1073741981 +K_RETURN2 = 1073741982 +K_SEPARATOR = 1073741983 +K_OUT = 1073741984 +K_OPER = 1073741985 +K_CLEARAGAIN = 1073741986 +K_CRSEL = 1073741987 +K_EXSEL = 1073741988 +K_KP_00 = 1073742000 +K_KP_000 = 1073742001 +K_THOUSANDSSEPARATOR = 1073742002 +K_DECIMALSEPARATOR = 1073742003 +K_CURRENCYUNIT = 1073742004 +K_CURRENCYSUBUNIT = 1073742005 +K_KP_LEFTPAREN = 1073742006 +K_KP_RIGHTPAREN = 1073742007 +K_KP_LEFTBRACE = 1073742008 +K_KP_RIGHTBRACE = 1073742009 +K_KP_TAB = 1073742010 +K_KP_BACKSPACE = 1073742011 +K_KP_A = 1073742012 +K_KP_B = 1073742013 +K_KP_C = 1073742014 +K_KP_D = 1073742015 +K_KP_E = 1073742016 +K_KP_F = 1073742017 +K_KP_XOR = 1073742018 +K_KP_POWER = 1073742019 +K_KP_PERCENT = 1073742020 +K_KP_LESS = 1073742021 +K_KP_GREATER = 1073742022 +K_KP_AMPERSAND = 1073742023 +K_KP_DBLAMPERSAND = 1073742024 +K_KP_VERTICALBAR = 1073742025 +K_KP_DBLVERTICALBAR = 1073742026 +K_KP_COLON = 1073742027 +K_KP_HASH = 1073742028 +K_KP_SPACE = 1073742029 +K_KP_AT = 1073742030 +K_KP_EXCLAM = 1073742031 +K_KP_MEMSTORE = 1073742032 +K_KP_MEMRECALL = 1073742033 +K_KP_MEMCLEAR = 1073742034 +K_KP_MEMADD = 1073742035 +K_KP_MEMSUBTRACT = 1073742036 +K_KP_MEMMULTIPLY = 1073742037 +K_KP_MEMDIVIDE = 1073742038 +K_KP_PLUSMINUS = 1073742039 +K_KP_CLEAR = 1073742040 +K_KP_CLEARENTRY = 1073742041 +K_KP_BINARY = 1073742042 +K_KP_OCTAL = 1073742043 +K_KP_DECIMAL = 1073742044 +K_KP_HEXADECIMAL = 1073742045 +K_LCTRL = 1073742048 +K_LSHIFT = 1073742049 +K_LALT = 1073742050 +K_LGUI = 1073742051 +K_RCTRL = 1073742052 +K_RSHIFT = 1073742053 +K_RALT = 1073742054 +K_RGUI = 1073742055 +K_MODE = 1073742081 +K_AUDIONEXT = 1073742082 +K_AUDIOPREV = 1073742083 +K_AUDIOSTOP = 1073742084 +K_AUDIOPLAY = 1073742085 +K_AUDIOMUTE = 1073742086 +K_MEDIASELECT = 1073742087 +K_WWW = 1073742088 +K_MAIL = 1073742089 +K_CALCULATOR = 1073742090 +K_COMPUTER = 1073742091 +K_AC_SEARCH = 1073742092 +K_AC_HOME = 1073742093 +K_AC_BACK = 1073742094 +K_AC_FORWARD = 1073742095 +K_AC_STOP = 1073742096 +K_AC_REFRESH = 1073742097 +K_AC_BOOKMARKS = 1073742098 +K_BRIGHTNESSDOWN = 1073742099 +K_BRIGHTNESSUP = 1073742100 +K_DISPLAYSWITCH = 1073742101 +K_KBDILLUMTOGGLE = 1073742102 +K_KBDILLUMDOWN = 1073742103 +K_KBDILLUMUP = 1073742104 +K_EJECT = 1073742105 +K_SLEEP = 1073742106 +K_APP1 = 1073742107 +K_APP2 = 1073742108 +K_AUDIOREWIND = 1073742109 +K_AUDIOFASTFORWARD = 1073742110 +_REVERSE_SYM_TABLE = { + 0: "tcod.event.K_UNKNOWN", + 8: "tcod.event.K_BACKSPACE", + 9: "tcod.event.K_TAB", + 13: "tcod.event.K_RETURN", + 27: "tcod.event.K_ESCAPE", + 32: "tcod.event.K_SPACE", + 33: "tcod.event.K_EXCLAIM", + 34: "tcod.event.K_QUOTEDBL", + 35: "tcod.event.K_HASH", + 36: "tcod.event.K_DOLLAR", + 37: "tcod.event.K_PERCENT", + 38: "tcod.event.K_AMPERSAND", + 39: "tcod.event.K_QUOTE", + 40: "tcod.event.K_LEFTPAREN", + 41: "tcod.event.K_RIGHTPAREN", + 42: "tcod.event.K_ASTERISK", + 43: "tcod.event.K_PLUS", + 44: "tcod.event.K_COMMA", + 45: "tcod.event.K_MINUS", + 46: "tcod.event.K_PERIOD", + 47: "tcod.event.K_SLASH", + 48: "tcod.event.K_0", + 49: "tcod.event.K_1", + 50: "tcod.event.K_2", + 51: "tcod.event.K_3", + 52: "tcod.event.K_4", + 53: "tcod.event.K_5", + 54: "tcod.event.K_6", + 55: "tcod.event.K_7", + 56: "tcod.event.K_8", + 57: "tcod.event.K_9", + 58: "tcod.event.K_COLON", + 59: "tcod.event.K_SEMICOLON", + 60: "tcod.event.K_LESS", + 61: "tcod.event.K_EQUALS", + 62: "tcod.event.K_GREATER", + 63: "tcod.event.K_QUESTION", + 64: "tcod.event.K_AT", + 91: "tcod.event.K_LEFTBRACKET", + 92: "tcod.event.K_BACKSLASH", + 93: "tcod.event.K_RIGHTBRACKET", + 94: "tcod.event.K_CARET", + 95: "tcod.event.K_UNDERSCORE", + 96: "tcod.event.K_BACKQUOTE", + 97: "tcod.event.K_a", + 98: "tcod.event.K_b", + 99: "tcod.event.K_c", + 100: "tcod.event.K_d", + 101: "tcod.event.K_e", + 102: "tcod.event.K_f", + 103: "tcod.event.K_g", + 104: "tcod.event.K_h", + 105: "tcod.event.K_i", + 106: "tcod.event.K_j", + 107: "tcod.event.K_k", + 108: "tcod.event.K_l", + 109: "tcod.event.K_m", + 110: "tcod.event.K_n", + 111: "tcod.event.K_o", + 112: "tcod.event.K_p", + 113: "tcod.event.K_q", + 114: "tcod.event.K_r", + 115: "tcod.event.K_s", + 116: "tcod.event.K_t", + 117: "tcod.event.K_u", + 118: "tcod.event.K_v", + 119: "tcod.event.K_w", + 120: "tcod.event.K_x", + 121: "tcod.event.K_y", + 122: "tcod.event.K_z", + 127: "tcod.event.K_DELETE", + 1073741881: "tcod.event.K_CAPSLOCK", + 1073741882: "tcod.event.K_F1", + 1073741883: "tcod.event.K_F2", + 1073741884: "tcod.event.K_F3", + 1073741885: "tcod.event.K_F4", + 1073741886: "tcod.event.K_F5", + 1073741887: "tcod.event.K_F6", + 1073741888: "tcod.event.K_F7", + 1073741889: "tcod.event.K_F8", + 1073741890: "tcod.event.K_F9", + 1073741891: "tcod.event.K_F10", + 1073741892: "tcod.event.K_F11", + 1073741893: "tcod.event.K_F12", + 1073741894: "tcod.event.K_PRINTSCREEN", + 1073741895: "tcod.event.K_SCROLLLOCK", + 1073741896: "tcod.event.K_PAUSE", + 1073741897: "tcod.event.K_INSERT", + 1073741898: "tcod.event.K_HOME", + 1073741899: "tcod.event.K_PAGEUP", + 1073741901: "tcod.event.K_END", + 1073741902: "tcod.event.K_PAGEDOWN", + 1073741903: "tcod.event.K_RIGHT", + 1073741904: "tcod.event.K_LEFT", + 1073741905: "tcod.event.K_DOWN", + 1073741906: "tcod.event.K_UP", + 1073741907: "tcod.event.K_NUMLOCKCLEAR", + 1073741908: "tcod.event.K_KP_DIVIDE", + 1073741909: "tcod.event.K_KP_MULTIPLY", + 1073741910: "tcod.event.K_KP_MINUS", + 1073741911: "tcod.event.K_KP_PLUS", + 1073741912: "tcod.event.K_KP_ENTER", + 1073741913: "tcod.event.K_KP_1", + 1073741914: "tcod.event.K_KP_2", + 1073741915: "tcod.event.K_KP_3", + 1073741916: "tcod.event.K_KP_4", + 1073741917: "tcod.event.K_KP_5", + 1073741918: "tcod.event.K_KP_6", + 1073741919: "tcod.event.K_KP_7", + 1073741920: "tcod.event.K_KP_8", + 1073741921: "tcod.event.K_KP_9", + 1073741922: "tcod.event.K_KP_0", + 1073741923: "tcod.event.K_KP_PERIOD", + 1073741925: "tcod.event.K_APPLICATION", + 1073741926: "tcod.event.K_POWER", + 1073741927: "tcod.event.K_KP_EQUALS", + 1073741928: "tcod.event.K_F13", + 1073741929: "tcod.event.K_F14", + 1073741930: "tcod.event.K_F15", + 1073741931: "tcod.event.K_F16", + 1073741932: "tcod.event.K_F17", + 1073741933: "tcod.event.K_F18", + 1073741934: "tcod.event.K_F19", + 1073741935: "tcod.event.K_F20", + 1073741936: "tcod.event.K_F21", + 1073741937: "tcod.event.K_F22", + 1073741938: "tcod.event.K_F23", + 1073741939: "tcod.event.K_F24", + 1073741940: "tcod.event.K_EXECUTE", + 1073741941: "tcod.event.K_HELP", + 1073741942: "tcod.event.K_MENU", + 1073741943: "tcod.event.K_SELECT", + 1073741944: "tcod.event.K_STOP", + 1073741945: "tcod.event.K_AGAIN", + 1073741946: "tcod.event.K_UNDO", + 1073741947: "tcod.event.K_CUT", + 1073741948: "tcod.event.K_COPY", + 1073741949: "tcod.event.K_PASTE", + 1073741950: "tcod.event.K_FIND", + 1073741951: "tcod.event.K_MUTE", + 1073741952: "tcod.event.K_VOLUMEUP", + 1073741953: "tcod.event.K_VOLUMEDOWN", + 1073741957: "tcod.event.K_KP_COMMA", + 1073741958: "tcod.event.K_KP_EQUALSAS400", + 1073741977: "tcod.event.K_ALTERASE", + 1073741978: "tcod.event.K_SYSREQ", + 1073741979: "tcod.event.K_CANCEL", + 1073741980: "tcod.event.K_CLEAR", + 1073741981: "tcod.event.K_PRIOR", + 1073741982: "tcod.event.K_RETURN2", + 1073741983: "tcod.event.K_SEPARATOR", + 1073741984: "tcod.event.K_OUT", + 1073741985: "tcod.event.K_OPER", + 1073741986: "tcod.event.K_CLEARAGAIN", + 1073741987: "tcod.event.K_CRSEL", + 1073741988: "tcod.event.K_EXSEL", + 1073742000: "tcod.event.K_KP_00", + 1073742001: "tcod.event.K_KP_000", + 1073742002: "tcod.event.K_THOUSANDSSEPARATOR", + 1073742003: "tcod.event.K_DECIMALSEPARATOR", + 1073742004: "tcod.event.K_CURRENCYUNIT", + 1073742005: "tcod.event.K_CURRENCYSUBUNIT", + 1073742006: "tcod.event.K_KP_LEFTPAREN", + 1073742007: "tcod.event.K_KP_RIGHTPAREN", + 1073742008: "tcod.event.K_KP_LEFTBRACE", + 1073742009: "tcod.event.K_KP_RIGHTBRACE", + 1073742010: "tcod.event.K_KP_TAB", + 1073742011: "tcod.event.K_KP_BACKSPACE", + 1073742012: "tcod.event.K_KP_A", + 1073742013: "tcod.event.K_KP_B", + 1073742014: "tcod.event.K_KP_C", + 1073742015: "tcod.event.K_KP_D", + 1073742016: "tcod.event.K_KP_E", + 1073742017: "tcod.event.K_KP_F", + 1073742018: "tcod.event.K_KP_XOR", + 1073742019: "tcod.event.K_KP_POWER", + 1073742020: "tcod.event.K_KP_PERCENT", + 1073742021: "tcod.event.K_KP_LESS", + 1073742022: "tcod.event.K_KP_GREATER", + 1073742023: "tcod.event.K_KP_AMPERSAND", + 1073742024: "tcod.event.K_KP_DBLAMPERSAND", + 1073742025: "tcod.event.K_KP_VERTICALBAR", + 1073742026: "tcod.event.K_KP_DBLVERTICALBAR", + 1073742027: "tcod.event.K_KP_COLON", + 1073742028: "tcod.event.K_KP_HASH", + 1073742029: "tcod.event.K_KP_SPACE", + 1073742030: "tcod.event.K_KP_AT", + 1073742031: "tcod.event.K_KP_EXCLAM", + 1073742032: "tcod.event.K_KP_MEMSTORE", + 1073742033: "tcod.event.K_KP_MEMRECALL", + 1073742034: "tcod.event.K_KP_MEMCLEAR", + 1073742035: "tcod.event.K_KP_MEMADD", + 1073742036: "tcod.event.K_KP_MEMSUBTRACT", + 1073742037: "tcod.event.K_KP_MEMMULTIPLY", + 1073742038: "tcod.event.K_KP_MEMDIVIDE", + 1073742039: "tcod.event.K_KP_PLUSMINUS", + 1073742040: "tcod.event.K_KP_CLEAR", + 1073742041: "tcod.event.K_KP_CLEARENTRY", + 1073742042: "tcod.event.K_KP_BINARY", + 1073742043: "tcod.event.K_KP_OCTAL", + 1073742044: "tcod.event.K_KP_DECIMAL", + 1073742045: "tcod.event.K_KP_HEXADECIMAL", + 1073742048: "tcod.event.K_LCTRL", + 1073742049: "tcod.event.K_LSHIFT", + 1073742050: "tcod.event.K_LALT", + 1073742051: "tcod.event.K_LGUI", + 1073742052: "tcod.event.K_RCTRL", + 1073742053: "tcod.event.K_RSHIFT", + 1073742054: "tcod.event.K_RALT", + 1073742055: "tcod.event.K_RGUI", + 1073742081: "tcod.event.K_MODE", + 1073742082: "tcod.event.K_AUDIONEXT", + 1073742083: "tcod.event.K_AUDIOPREV", + 1073742084: "tcod.event.K_AUDIOSTOP", + 1073742085: "tcod.event.K_AUDIOPLAY", + 1073742086: "tcod.event.K_AUDIOMUTE", + 1073742087: "tcod.event.K_MEDIASELECT", + 1073742088: "tcod.event.K_WWW", + 1073742089: "tcod.event.K_MAIL", + 1073742090: "tcod.event.K_CALCULATOR", + 1073742091: "tcod.event.K_COMPUTER", + 1073742092: "tcod.event.K_AC_SEARCH", + 1073742093: "tcod.event.K_AC_HOME", + 1073742094: "tcod.event.K_AC_BACK", + 1073742095: "tcod.event.K_AC_FORWARD", + 1073742096: "tcod.event.K_AC_STOP", + 1073742097: "tcod.event.K_AC_REFRESH", + 1073742098: "tcod.event.K_AC_BOOKMARKS", + 1073742099: "tcod.event.K_BRIGHTNESSDOWN", + 1073742100: "tcod.event.K_BRIGHTNESSUP", + 1073742101: "tcod.event.K_DISPLAYSWITCH", + 1073742102: "tcod.event.K_KBDILLUMTOGGLE", + 1073742103: "tcod.event.K_KBDILLUMDOWN", + 1073742104: "tcod.event.K_KBDILLUMUP", + 1073742105: "tcod.event.K_EJECT", + 1073742106: "tcod.event.K_SLEEP", + 1073742107: "tcod.event.K_APP1", + 1073742108: "tcod.event.K_APP2", + 1073742109: "tcod.event.K_AUDIOREWIND", + 1073742110: "tcod.event.K_AUDIOFASTFORWARD", +} + +# --- SDL keyboard modifiers --- +KMOD_NONE = 0 +KMOD_LSHIFT = 1 +KMOD_RSHIFT = 2 +KMOD_LCTRL = 64 +KMOD_RCTRL = 128 +KMOD_LALT = 256 +KMOD_RALT = 512 +KMOD_LGUI = 1024 +KMOD_RGUI = 2048 +KMOD_NUM = 4096 +KMOD_CAPS = 8192 +KMOD_MODE = 16384 +KMOD_RESERVED = 32768 +_REVERSE_MOD_TABLE = { + 0: "tcod.event.KMOD_NONE", + 1: "tcod.event.KMOD_LSHIFT", + 2: "tcod.event.KMOD_RSHIFT", + 64: "tcod.event.KMOD_LCTRL", + 128: "tcod.event.KMOD_RCTRL", + 256: "tcod.event.KMOD_LALT", + 512: "tcod.event.KMOD_RALT", + 1024: "tcod.event.KMOD_LGUI", + 2048: "tcod.event.KMOD_RGUI", + 4096: "tcod.event.KMOD_NUM", + 8192: "tcod.event.KMOD_CAPS", + 16384: "tcod.event.KMOD_MODE", + 32768: "tcod.event.KMOD_RESERVED", +} + +# --- SDL wheel --- +MOUSEWHEEL_NORMAL = 0 +MOUSEWHEEL_FLIPPED = 1 +MOUSEWHEEL = 1027 +_REVERSE_WHEEL_TABLE = { + 0: "tcod.event.MOUSEWHEEL_NORMAL", + 1: "tcod.event.MOUSEWHEEL_FLIPPED", + 1027: "tcod.event.MOUSEWHEEL", +} + +__all__ = [ + "SCANCODE_UNKNOWN", + "SCANCODE_A", + "SCANCODE_B", + "SCANCODE_C", + "SCANCODE_D", + "SCANCODE_E", + "SCANCODE_F", + "SCANCODE_G", + "SCANCODE_H", + "SCANCODE_I", + "SCANCODE_J", + "SCANCODE_K", + "SCANCODE_L", + "SCANCODE_M", + "SCANCODE_N", + "SCANCODE_O", + "SCANCODE_P", + "SCANCODE_Q", + "SCANCODE_R", + "SCANCODE_S", + "SCANCODE_T", + "SCANCODE_U", + "SCANCODE_V", + "SCANCODE_W", + "SCANCODE_X", + "SCANCODE_Y", + "SCANCODE_Z", + "SCANCODE_1", + "SCANCODE_2", + "SCANCODE_3", + "SCANCODE_4", + "SCANCODE_5", + "SCANCODE_6", + "SCANCODE_7", + "SCANCODE_8", + "SCANCODE_9", + "SCANCODE_0", + "SCANCODE_RETURN", + "SCANCODE_ESCAPE", + "SCANCODE_BACKSPACE", + "SCANCODE_TAB", + "SCANCODE_SPACE", + "SCANCODE_MINUS", + "SCANCODE_EQUALS", + "SCANCODE_LEFTBRACKET", + "SCANCODE_RIGHTBRACKET", + "SCANCODE_BACKSLASH", + "SCANCODE_NONUSHASH", + "SCANCODE_SEMICOLON", + "SCANCODE_APOSTROPHE", + "SCANCODE_GRAVE", + "SCANCODE_COMMA", + "SCANCODE_PERIOD", + "SCANCODE_SLASH", + "SCANCODE_CAPSLOCK", + "SCANCODE_F1", + "SCANCODE_F2", + "SCANCODE_F3", + "SCANCODE_F4", + "SCANCODE_F5", + "SCANCODE_F6", + "SCANCODE_F7", + "SCANCODE_F8", + "SCANCODE_F9", + "SCANCODE_F10", + "SCANCODE_F11", + "SCANCODE_F12", + "SCANCODE_PRINTSCREEN", + "SCANCODE_SCROLLLOCK", + "SCANCODE_PAUSE", + "SCANCODE_INSERT", + "SCANCODE_HOME", + "SCANCODE_PAGEUP", + "SCANCODE_DELETE", + "SCANCODE_END", + "SCANCODE_PAGEDOWN", + "SCANCODE_RIGHT", + "SCANCODE_LEFT", + "SCANCODE_DOWN", + "SCANCODE_UP", + "SCANCODE_NUMLOCKCLEAR", + "SCANCODE_KP_DIVIDE", + "SCANCODE_KP_MULTIPLY", + "SCANCODE_KP_MINUS", + "SCANCODE_KP_PLUS", + "SCANCODE_KP_ENTER", + "SCANCODE_KP_1", + "SCANCODE_KP_2", + "SCANCODE_KP_3", + "SCANCODE_KP_4", + "SCANCODE_KP_5", + "SCANCODE_KP_6", + "SCANCODE_KP_7", + "SCANCODE_KP_8", + "SCANCODE_KP_9", + "SCANCODE_KP_0", + "SCANCODE_KP_PERIOD", + "SCANCODE_NONUSBACKSLASH", + "SCANCODE_APPLICATION", + "SCANCODE_POWER", + "SCANCODE_KP_EQUALS", + "SCANCODE_F13", + "SCANCODE_F14", + "SCANCODE_F15", + "SCANCODE_F16", + "SCANCODE_F17", + "SCANCODE_F18", + "SCANCODE_F19", + "SCANCODE_F20", + "SCANCODE_F21", + "SCANCODE_F22", + "SCANCODE_F23", + "SCANCODE_F24", + "SCANCODE_EXECUTE", + "SCANCODE_HELP", + "SCANCODE_MENU", + "SCANCODE_SELECT", + "SCANCODE_STOP", + "SCANCODE_AGAIN", + "SCANCODE_UNDO", + "SCANCODE_CUT", + "SCANCODE_COPY", + "SCANCODE_PASTE", + "SCANCODE_FIND", + "SCANCODE_MUTE", + "SCANCODE_VOLUMEUP", + "SCANCODE_VOLUMEDOWN", + "SCANCODE_KP_COMMA", + "SCANCODE_KP_EQUALSAS400", + "SCANCODE_INTERNATIONAL1", + "SCANCODE_INTERNATIONAL2", + "SCANCODE_INTERNATIONAL3", + "SCANCODE_INTERNATIONAL4", + "SCANCODE_INTERNATIONAL5", + "SCANCODE_INTERNATIONAL6", + "SCANCODE_INTERNATIONAL7", + "SCANCODE_INTERNATIONAL8", + "SCANCODE_INTERNATIONAL9", + "SCANCODE_LANG1", + "SCANCODE_LANG2", + "SCANCODE_LANG3", + "SCANCODE_LANG4", + "SCANCODE_LANG5", + "SCANCODE_LANG6", + "SCANCODE_LANG7", + "SCANCODE_LANG8", + "SCANCODE_LANG9", + "SCANCODE_ALTERASE", + "SCANCODE_SYSREQ", + "SCANCODE_CANCEL", + "SCANCODE_CLEAR", + "SCANCODE_PRIOR", + "SCANCODE_RETURN2", + "SCANCODE_SEPARATOR", + "SCANCODE_OUT", + "SCANCODE_OPER", + "SCANCODE_CLEARAGAIN", + "SCANCODE_CRSEL", + "SCANCODE_EXSEL", + "SCANCODE_KP_00", + "SCANCODE_KP_000", + "SCANCODE_THOUSANDSSEPARATOR", + "SCANCODE_DECIMALSEPARATOR", + "SCANCODE_CURRENCYUNIT", + "SCANCODE_CURRENCYSUBUNIT", + "SCANCODE_KP_LEFTPAREN", + "SCANCODE_KP_RIGHTPAREN", + "SCANCODE_KP_LEFTBRACE", + "SCANCODE_KP_RIGHTBRACE", + "SCANCODE_KP_TAB", + "SCANCODE_KP_BACKSPACE", + "SCANCODE_KP_A", + "SCANCODE_KP_B", + "SCANCODE_KP_C", + "SCANCODE_KP_D", + "SCANCODE_KP_E", + "SCANCODE_KP_F", + "SCANCODE_KP_XOR", + "SCANCODE_KP_POWER", + "SCANCODE_KP_PERCENT", + "SCANCODE_KP_LESS", + "SCANCODE_KP_GREATER", + "SCANCODE_KP_AMPERSAND", + "SCANCODE_KP_DBLAMPERSAND", + "SCANCODE_KP_VERTICALBAR", + "SCANCODE_KP_DBLVERTICALBAR", + "SCANCODE_KP_COLON", + "SCANCODE_KP_HASH", + "SCANCODE_KP_SPACE", + "SCANCODE_KP_AT", + "SCANCODE_KP_EXCLAM", + "SCANCODE_KP_MEMSTORE", + "SCANCODE_KP_MEMRECALL", + "SCANCODE_KP_MEMCLEAR", + "SCANCODE_KP_MEMADD", + "SCANCODE_KP_MEMSUBTRACT", + "SCANCODE_KP_MEMMULTIPLY", + "SCANCODE_KP_MEMDIVIDE", + "SCANCODE_KP_PLUSMINUS", + "SCANCODE_KP_CLEAR", + "SCANCODE_KP_CLEARENTRY", + "SCANCODE_KP_BINARY", + "SCANCODE_KP_OCTAL", + "SCANCODE_KP_DECIMAL", + "SCANCODE_KP_HEXADECIMAL", + "SCANCODE_LCTRL", + "SCANCODE_LSHIFT", + "SCANCODE_LALT", + "SCANCODE_LGUI", + "SCANCODE_RCTRL", + "SCANCODE_RSHIFT", + "SCANCODE_RALT", + "SCANCODE_RGUI", + "SCANCODE_MODE", + "SCANCODE_AUDIONEXT", + "SCANCODE_AUDIOPREV", + "SCANCODE_AUDIOSTOP", + "SCANCODE_AUDIOPLAY", + "SCANCODE_AUDIOMUTE", + "SCANCODE_MEDIASELECT", + "SCANCODE_WWW", + "SCANCODE_MAIL", + "SCANCODE_CALCULATOR", + "SCANCODE_COMPUTER", + "SCANCODE_AC_SEARCH", + "SCANCODE_AC_HOME", + "SCANCODE_AC_BACK", + "SCANCODE_AC_FORWARD", + "SCANCODE_AC_STOP", + "SCANCODE_AC_REFRESH", + "SCANCODE_AC_BOOKMARKS", + "SCANCODE_BRIGHTNESSDOWN", + "SCANCODE_BRIGHTNESSUP", + "SCANCODE_DISPLAYSWITCH", + "SCANCODE_KBDILLUMTOGGLE", + "SCANCODE_KBDILLUMDOWN", + "SCANCODE_KBDILLUMUP", + "SCANCODE_EJECT", + "SCANCODE_SLEEP", + "SCANCODE_APP1", + "SCANCODE_APP2", + "SCANCODE_AUDIOREWIND", + "SCANCODE_AUDIOFASTFORWARD", + "K_UNKNOWN", + "K_BACKSPACE", + "K_TAB", + "K_RETURN", + "K_ESCAPE", + "K_SPACE", + "K_EXCLAIM", + "K_QUOTEDBL", + "K_HASH", + "K_DOLLAR", + "K_PERCENT", + "K_AMPERSAND", + "K_QUOTE", + "K_LEFTPAREN", + "K_RIGHTPAREN", + "K_ASTERISK", + "K_PLUS", + "K_COMMA", + "K_MINUS", + "K_PERIOD", + "K_SLASH", + "K_0", + "K_1", + "K_2", + "K_3", + "K_4", + "K_5", + "K_6", + "K_7", + "K_8", + "K_9", + "K_COLON", + "K_SEMICOLON", + "K_LESS", + "K_EQUALS", + "K_GREATER", + "K_QUESTION", + "K_AT", + "K_LEFTBRACKET", + "K_BACKSLASH", + "K_RIGHTBRACKET", + "K_CARET", + "K_UNDERSCORE", + "K_BACKQUOTE", + "K_a", + "K_b", + "K_c", + "K_d", + "K_e", + "K_f", + "K_g", + "K_h", + "K_i", + "K_j", + "K_k", + "K_l", + "K_m", + "K_n", + "K_o", + "K_p", + "K_q", + "K_r", + "K_s", + "K_t", + "K_u", + "K_v", + "K_w", + "K_x", + "K_y", + "K_z", + "K_DELETE", + "K_CAPSLOCK", + "K_F1", + "K_F2", + "K_F3", + "K_F4", + "K_F5", + "K_F6", + "K_F7", + "K_F8", + "K_F9", + "K_F10", + "K_F11", + "K_F12", + "K_PRINTSCREEN", + "K_SCROLLLOCK", + "K_PAUSE", + "K_INSERT", + "K_HOME", + "K_PAGEUP", + "K_END", + "K_PAGEDOWN", + "K_RIGHT", + "K_LEFT", + "K_DOWN", + "K_UP", + "K_NUMLOCKCLEAR", + "K_KP_DIVIDE", + "K_KP_MULTIPLY", + "K_KP_MINUS", + "K_KP_PLUS", + "K_KP_ENTER", + "K_KP_1", + "K_KP_2", + "K_KP_3", + "K_KP_4", + "K_KP_5", + "K_KP_6", + "K_KP_7", + "K_KP_8", + "K_KP_9", + "K_KP_0", + "K_KP_PERIOD", + "K_APPLICATION", + "K_POWER", + "K_KP_EQUALS", + "K_F13", + "K_F14", + "K_F15", + "K_F16", + "K_F17", + "K_F18", + "K_F19", + "K_F20", + "K_F21", + "K_F22", + "K_F23", + "K_F24", + "K_EXECUTE", + "K_HELP", + "K_MENU", + "K_SELECT", + "K_STOP", + "K_AGAIN", + "K_UNDO", + "K_CUT", + "K_COPY", + "K_PASTE", + "K_FIND", + "K_MUTE", + "K_VOLUMEUP", + "K_VOLUMEDOWN", + "K_KP_COMMA", + "K_KP_EQUALSAS400", + "K_ALTERASE", + "K_SYSREQ", + "K_CANCEL", + "K_CLEAR", + "K_PRIOR", + "K_RETURN2", + "K_SEPARATOR", + "K_OUT", + "K_OPER", + "K_CLEARAGAIN", + "K_CRSEL", + "K_EXSEL", + "K_KP_00", + "K_KP_000", + "K_THOUSANDSSEPARATOR", + "K_DECIMALSEPARATOR", + "K_CURRENCYUNIT", + "K_CURRENCYSUBUNIT", + "K_KP_LEFTPAREN", + "K_KP_RIGHTPAREN", + "K_KP_LEFTBRACE", + "K_KP_RIGHTBRACE", + "K_KP_TAB", + "K_KP_BACKSPACE", + "K_KP_A", + "K_KP_B", + "K_KP_C", + "K_KP_D", + "K_KP_E", + "K_KP_F", + "K_KP_XOR", + "K_KP_POWER", + "K_KP_PERCENT", + "K_KP_LESS", + "K_KP_GREATER", + "K_KP_AMPERSAND", + "K_KP_DBLAMPERSAND", + "K_KP_VERTICALBAR", + "K_KP_DBLVERTICALBAR", + "K_KP_COLON", + "K_KP_HASH", + "K_KP_SPACE", + "K_KP_AT", + "K_KP_EXCLAM", + "K_KP_MEMSTORE", + "K_KP_MEMRECALL", + "K_KP_MEMCLEAR", + "K_KP_MEMADD", + "K_KP_MEMSUBTRACT", + "K_KP_MEMMULTIPLY", + "K_KP_MEMDIVIDE", + "K_KP_PLUSMINUS", + "K_KP_CLEAR", + "K_KP_CLEARENTRY", + "K_KP_BINARY", + "K_KP_OCTAL", + "K_KP_DECIMAL", + "K_KP_HEXADECIMAL", + "K_LCTRL", + "K_LSHIFT", + "K_LALT", + "K_LGUI", + "K_RCTRL", + "K_RSHIFT", + "K_RALT", + "K_RGUI", + "K_MODE", + "K_AUDIONEXT", + "K_AUDIOPREV", + "K_AUDIOSTOP", + "K_AUDIOPLAY", + "K_AUDIOMUTE", + "K_MEDIASELECT", + "K_WWW", + "K_MAIL", + "K_CALCULATOR", + "K_COMPUTER", + "K_AC_SEARCH", + "K_AC_HOME", + "K_AC_BACK", + "K_AC_FORWARD", + "K_AC_STOP", + "K_AC_REFRESH", + "K_AC_BOOKMARKS", + "K_BRIGHTNESSDOWN", + "K_BRIGHTNESSUP", + "K_DISPLAYSWITCH", + "K_KBDILLUMTOGGLE", + "K_KBDILLUMDOWN", + "K_KBDILLUMUP", + "K_EJECT", + "K_SLEEP", + "K_APP1", + "K_APP2", + "K_AUDIOREWIND", + "K_AUDIOFASTFORWARD", + "KMOD_NONE", + "KMOD_LSHIFT", + "KMOD_RSHIFT", + "KMOD_LCTRL", + "KMOD_RCTRL", + "KMOD_LALT", + "KMOD_RALT", + "KMOD_LGUI", + "KMOD_RGUI", + "KMOD_NUM", + "KMOD_CAPS", + "KMOD_MODE", + "KMOD_RESERVED", + "MOUSEWHEEL_NORMAL", + "MOUSEWHEEL_FLIPPED", + "MOUSEWHEEL", +] From aebee29debde4f76678364c1aee2384f60c30b09 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 13 Jan 2019 18:27:04 -0800 Subject: [PATCH 0031/1101] Reformat build_libtcod.py with Black. --- build_libtcod.py | 358 +++++++++++++++++++++++++---------------------- 1 file changed, 194 insertions(+), 164 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 31ed4865..a5310f66 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -12,71 +12,76 @@ import shutil import subprocess import platform +import zipfile + try: from urllib import urlretrieve except ImportError: from urllib.request import urlretrieve -import zipfile -SDL2_VERSION = os.environ.get('SDL_VERSION', '2.0.9') -TDL_NO_SDL2_EXPORTS = os.environ.get('TDL_NO_SDL2_EXPORTS', '0') == '1' +SDL2_VERSION = os.environ.get("SDL_VERSION", "2.0.9") +TDL_NO_SDL2_EXPORTS = os.environ.get("TDL_NO_SDL2_EXPORTS", "0") == "1" -CFFI_HEADER = 'tcod/cffi.h' -CFFI_EXTRA_CDEFS = 'tcod/cdef.h' +CFFI_HEADER = "tcod/cffi.h" +CFFI_EXTRA_CDEFS = "tcod/cdef.h" BITSIZE, LINKAGE = platform.architecture() + def walk_sources(directory): for path, dirs, files in os.walk(directory): for source in files: - if source.endswith('.c') or source.endswith('.cpp'): + if source.endswith(".c") or source.endswith(".cpp"): yield os.path.join(path, source) + def find_sources(directory): - return [os.path.join(directory, source) - for source in os.listdir(directory) - if source.endswith('.c')] + return [ + os.path.join(directory, source) + for source in os.listdir(directory) + if source.endswith(".c") + ] + def get_sdl2_file(version): - if sys.platform == 'win32': - sdl2_file = 'SDL2-devel-%s-VC.zip' % (version,) + if sys.platform == "win32": + sdl2_file = "SDL2-devel-%s-VC.zip" % (version,) else: - assert sys.platform == 'darwin' - sdl2_file = 'SDL2-%s.dmg' % (version,) - sdl2_local_file = os.path.join('dependencies', sdl2_file) - sdl2_remote_file = 'https://www.libsdl.org/release/%s' % sdl2_file + assert sys.platform == "darwin" + sdl2_file = "SDL2-%s.dmg" % (version,) + sdl2_local_file = os.path.join("dependencies", sdl2_file) + sdl2_remote_file = "https://www.libsdl.org/release/%s" % sdl2_file if not os.path.exists(sdl2_local_file): - print('Downloading %s' % sdl2_remote_file) + print("Downloading %s" % sdl2_remote_file) urlretrieve(sdl2_remote_file, sdl2_local_file) return sdl2_local_file + def unpack_sdl2(version): - sdl2_path = 'dependencies/SDL2-%s' % (version,) - if sys.platform == 'darwin': + sdl2_path = "dependencies/SDL2-%s" % (version,) + if sys.platform == "darwin": sdl2_dir = sdl2_path - sdl2_path += '/SDL2.framework' + sdl2_path += "/SDL2.framework" if os.path.exists(sdl2_path): return sdl2_path sdl2_arc = get_sdl2_file(version) - print('Extracting %s' % sdl2_arc) - if sdl2_arc.endswith('.zip'): + print("Extracting %s" % sdl2_arc) + if sdl2_arc.endswith(".zip"): with zipfile.ZipFile(sdl2_arc) as zf: - zf.extractall('dependencies/') + zf.extractall("dependencies/") else: - assert sdl2_arc.endswith('.dmg') - subprocess.check_call(['hdiutil', 'mount', sdl2_arc]) - subprocess.check_call(['mkdir', '-p', sdl2_dir]) - subprocess.check_call(['cp', '-r', '/Volumes/SDL2/SDL2.framework', - sdl2_dir]) - subprocess.check_call(['hdiutil', 'unmount', '/Volumes/SDL2']) + assert sdl2_arc.endswith(".dmg") + subprocess.check_call(["hdiutil", "mount", sdl2_arc]) + subprocess.check_call(["mkdir", "-p", sdl2_dir]) + subprocess.check_call( + ["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir] + ) + subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL2"]) return sdl2_path -module_name = 'tcod._libtcod' -include_dirs = [ - '.', - 'libtcod/src/vendor/', - 'libtcod/src/vendor/zlib/', -] + +module_name = "tcod._libtcod" +include_dirs = [".", "libtcod/src/vendor/", "libtcod/src/vendor/zlib/"] extra_parse_args = [] extra_compile_args = [] @@ -87,82 +92,92 @@ def unpack_sdl2(version): library_dirs = [] define_macros = [] -sources += walk_sources('tcod/') -sources += walk_sources('tdl/') -sources += walk_sources('libtcod/src/libtcod') -sources += ['libtcod/src/vendor/glad.c'] -sources += ['libtcod/src/vendor/lodepng.cpp'] -sources += ['libtcod/src/vendor/stb.c'] -sources += ['libtcod/src/vendor/utf8proc/utf8proc.c'] -sources += glob.glob('libtcod/src/vendor/zlib/*.c') +sources += walk_sources("tcod/") +sources += walk_sources("tdl/") +sources += walk_sources("libtcod/src/libtcod") +sources += ["libtcod/src/vendor/glad.c"] +sources += ["libtcod/src/vendor/lodepng.cpp"] +sources += ["libtcod/src/vendor/stb.c"] +sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] +sources += glob.glob("libtcod/src/vendor/zlib/*.c") if TDL_NO_SDL2_EXPORTS: - extra_parse_args.append('-DTDL_NO_SDL2_EXPORTS') + extra_parse_args.append("-DTDL_NO_SDL2_EXPORTS") -if sys.platform == 'win32': - libraries += ['User32', 'OpenGL32'] - define_macros.append(('TCODLIB_API', '')) - define_macros.append(('_CRT_SECURE_NO_WARNINGS', None)) +if sys.platform == "win32": + libraries += ["User32", "OpenGL32"] + define_macros.append(("TCODLIB_API", "")) + define_macros.append(("_CRT_SECURE_NO_WARNINGS", None)) -if 'linux' in sys.platform: - libraries += ['GL'] +if "linux" in sys.platform: + libraries += ["GL"] -if sys.platform == 'darwin': - extra_link_args += ['-framework', 'OpenGL'] - extra_link_args += ['-framework', 'SDL2'] +if sys.platform == "darwin": + extra_link_args += ["-framework", "OpenGL"] + extra_link_args += ["-framework", "SDL2"] else: - libraries += ['SDL2'] + libraries += ["SDL2"] # included SDL headers are for whatever OS's don't easily come with them -if sys.platform in ['win32', 'darwin']: +if sys.platform in ["win32", "darwin"]: SDL2_PATH = unpack_sdl2(SDL2_VERSION) - include_dirs.append('libtcod/src/zlib/') + include_dirs.append("libtcod/src/zlib/") -if sys.platform == 'win32': - include_dirs.append(os.path.join(SDL2_PATH, 'include')) - ARCH_MAPPING = {'32bit': 'x86', '64bit': 'x64'} - SDL2_LIB_DIR = os.path.join(SDL2_PATH, 'lib/', ARCH_MAPPING[BITSIZE]) +if sys.platform == "win32": + include_dirs.append(os.path.join(SDL2_PATH, "include")) + ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} + SDL2_LIB_DIR = os.path.join(SDL2_PATH, "lib/", ARCH_MAPPING[BITSIZE]) library_dirs.append(SDL2_LIB_DIR) - SDL2_LIB_DEST = os.path.join('tcod', ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DEST = os.path.join("tcod", ARCH_MAPPING[BITSIZE]) if not os.path.exists(SDL2_LIB_DEST): os.mkdir(SDL2_LIB_DEST) - shutil.copy(os.path.join(SDL2_LIB_DIR, 'SDL2.dll'), SDL2_LIB_DEST) + shutil.copy(os.path.join(SDL2_LIB_DIR, "SDL2.dll"), SDL2_LIB_DEST) + def fix_header(filepath): """Removes leading whitespace from a MacOS header file. This whitespace is causing issues with directives on some platforms. """ - with open(filepath, 'r+') as f: + with open(filepath, "r+") as f: current = f.read() - fixed = '\n'.join(line.strip() for line in current.split('\n')) + fixed = "\n".join(line.strip() for line in current.split("\n")) if current == fixed: return f.seek(0) f.truncate() f.write(fixed) -if sys.platform == 'darwin': - HEADER_DIR = os.path.join(SDL2_PATH, 'Headers') - fix_header(os.path.join(HEADER_DIR, 'SDL_assert.h')) - fix_header(os.path.join(HEADER_DIR, 'SDL_config_macosx.h')) + +if sys.platform == "darwin": + HEADER_DIR = os.path.join(SDL2_PATH, "Headers") + fix_header(os.path.join(HEADER_DIR, "SDL_assert.h")) + fix_header(os.path.join(HEADER_DIR, "SDL_config_macosx.h")) include_dirs.append(HEADER_DIR) - extra_link_args += ['-F%s/..' % SDL2_PATH] - extra_link_args += ['-rpath', '%s/..' % SDL2_PATH] - extra_link_args += ['-rpath', '/usr/local/opt/llvm/lib/'] - -if sys.platform not in ['win32', 'darwin']: - extra_parse_args += subprocess.check_output(['sdl2-config', '--cflags'], - universal_newlines=True - ).strip().split() + extra_link_args += ["-F%s/.." % SDL2_PATH] + extra_link_args += ["-rpath", "%s/.." % SDL2_PATH] + extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] + +if sys.platform not in ["win32", "darwin"]: + extra_parse_args += ( + subprocess.check_output( + ["sdl2-config", "--cflags"], universal_newlines=True + ) + .strip() + .split() + ) extra_compile_args += extra_parse_args - extra_link_args += subprocess.check_output(['sdl2-config', '--libs'], - universal_newlines=True - ).strip().split() + extra_link_args += ( + subprocess.check_output( + ["sdl2-config", "--libs"], universal_newlines=True + ) + .strip() + .split() + ) -class CustomPostParser(c_ast.NodeVisitor): +class CustomPostParser(c_ast.NodeVisitor): def __init__(self): self.ast = None self.typedefs = [] @@ -179,13 +194,13 @@ def parse(self, ast): return ast def visit_Typedef(self, node): - if node.name in ['wchar_t', 'size_t']: + if node.name in ["wchar_t", "size_t"]: # remove fake typedef placeholders self.removeable_typedefs.append(node) else: self.generic_visit(node) if node.name in self.typedefs: - print('warning: %s redefined' % node.name) + print("warning: %s redefined" % node.name) self.removeable_typedefs.append(node) self.typedefs.append(node.name) @@ -195,26 +210,27 @@ def visit_EnumeratorList(self, node): if enum.value is None: pass elif isinstance(enum.value, (c_ast.BinaryOp, c_ast.UnaryOp)): - enum.value = c_ast.Constant('int', '...') - elif hasattr(enum.value, 'type'): - enum.value = c_ast.Constant(enum.value.type, '...') + enum.value = c_ast.Constant("int", "...") + elif hasattr(enum.value, "type"): + enum.value = c_ast.Constant(enum.value.type, "...") def visit_ArrayDecl(self, node): if not node.dim: return if isinstance(node.dim, (c_ast.BinaryOp, c_ast.UnaryOp)): - node.dim = c_ast.Constant('int', '...') + node.dim = c_ast.Constant("int", "...") def visit_Decl(self, node): if node.name is None: self.generic_visit(node) - elif (node.name and 'vsprint' in node.name or - node.name in ['SDL_vsscanf', - 'SDL_vsnprintf', - 'SDL_LogMessageV']): + elif ( + node.name + and "vsprint" in node.name + or node.name in ["SDL_vsscanf", "SDL_vsnprintf", "SDL_LogMessageV"] + ): # exclude va_list related functions self.ast.ext.remove(node) - elif node.name in ['screen']: + elif node.name in ["screen"]: # exclude outdated 'extern SDL_Surface* screen;' line self.ast.ext.remove(node) else: @@ -224,55 +240,54 @@ def visit_FuncDef(self, node): """Exclude function definitions. Should be declarations only.""" self.funcdefs.append(node) + def get_cdef(): generator = c_generator.CGenerator() return generator.visit(get_ast()) + def get_ast(): global extra_parse_args - if 'win32' in sys.platform: - extra_parse_args += [r'-I%s/include' % SDL2_PATH] - if 'darwin' in sys.platform: - extra_parse_args += [r'-I%s/Headers' % SDL2_PATH] - - ast = parse_file(filename=CFFI_HEADER, use_cpp=True, - cpp_args=[r'-Idependencies/fake_libc_include', - r'-DDECLSPEC=', - r'-DSDLCALL=', - r'-DTCODLIB_API=', - r'-DSDL_FORCE_INLINE=', - r'-U__GNUC__', - r'-D_SDL_thread_h', - r'-DDOXYGEN_SHOULD_IGNORE_THIS', - r'-DMAC_OS_X_VERSION_MIN_REQUIRED=1060', - r'-D__attribute__(x)=', - r'-D_PSTDINT_H_INCLUDED', - ] + extra_parse_args) + if "win32" in sys.platform: + extra_parse_args += [r"-I%s/include" % SDL2_PATH] + if "darwin" in sys.platform: + extra_parse_args += [r"-I%s/Headers" % SDL2_PATH] + + ast = parse_file( + filename=CFFI_HEADER, + use_cpp=True, + cpp_args=[ + r"-Idependencies/fake_libc_include", + r"-DDECLSPEC=", + r"-DSDLCALL=", + r"-DTCODLIB_API=", + r"-DSDL_FORCE_INLINE=", + r"-U__GNUC__", + r"-D_SDL_thread_h", + r"-DDOXYGEN_SHOULD_IGNORE_THIS", + r"-DMAC_OS_X_VERSION_MIN_REQUIRED=1060", + r"-D__attribute__(x)=", + r"-D_PSTDINT_H_INCLUDED", + ] + + extra_parse_args, + ) ast = CustomPostParser().parse(ast) return ast + # Can force the use of OpenMP with this variable. try: - USE_OPENMP = eval(os.environ.get('USE_OPENMP', 'None').title()) + USE_OPENMP = eval(os.environ.get("USE_OPENMP", "None").title()) except Exception: USE_OPENMP = None -tdl_build = os.environ.get('TDL_BUILD', 'RELEASE').upper() - -MSVC_CFLAGS = { - 'DEBUG': ['/Od'], - 'RELEASE': ['/GL', '/O2', '/GS-'], -} -MSVC_LDFLAGS = { - 'DEBUG': [], - 'RELEASE': ['/LTCG'], -} -GCC_CFLAGS = { - 'DEBUG': ['-O0'], - 'RELEASE': ['-flto', '-O3', '-fPIC'], -} - -if sys.platform == 'win32' and '--compiler=mingw32' not in sys.argv: +tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() + +MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-"]} +MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} +GCC_CFLAGS = {"DEBUG": ["-O0"], "RELEASE": ["-flto", "-O3", "-fPIC"]} + +if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv: extra_compile_args.extend(MSVC_CFLAGS[tdl_build]) extra_link_args.extend(MSVC_LDFLAGS[tdl_build]) @@ -280,22 +295,23 @@ def get_ast(): USE_OPENMP = sys.version_info[:2] >= (3, 5) if USE_OPENMP: - extra_compile_args.append('/openmp') + extra_compile_args.append("/openmp") else: extra_compile_args.extend(GCC_CFLAGS[tdl_build]) extra_link_args.extend(GCC_CFLAGS[tdl_build]) if USE_OPENMP is None: - USE_OPENMP = sys.platform != 'darwin' + USE_OPENMP = sys.platform != "darwin" if USE_OPENMP: - extra_compile_args.append('-fopenmp') - extra_link_args.append('-fopenmp') + extra_compile_args.append("-fopenmp") + extra_link_args.append("-fopenmp") ffi = FFI() ffi.cdef(get_cdef()) -ffi.cdef(open(CFFI_EXTRA_CDEFS, 'r').read()) +ffi.cdef(open(CFFI_EXTRA_CDEFS, "r").read()) ffi.set_source( - module_name, '#include ', + module_name, + "#include ", include_dirs=include_dirs, library_dirs=library_dirs, sources=sources, @@ -328,6 +344,7 @@ def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Any]]: `prefix` is used to filter out which names to copy. """ from tcod._libtcod import lib + if prefix.startswith("SDL_"): name_starts_at = 4 elif prefix.startswith("SDL"): @@ -338,6 +355,7 @@ def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Any]]: if attr.startswith(prefix): yield attr[name_starts_at:], getattr(lib, attr) + def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: """Return the name/value pairs, and the final dictionary string for the library attributes with `prefix`. @@ -346,68 +364,80 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: """ names = [] lookup = [] - for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): + for name, value in sorted( + find_sdl_attrs(prefix), key=lambda item: item[1] + ): all_names.append(name) - names.append('%s = %s' % (name, value)) + names.append("%s = %s" % (name, value)) lookup.append('%s: "tcod.event.%s"' % (value, name)) - names = '\n'.join(names) - lookup = '{\n %s,\n}' % (',\n '.join(lookup),) + names = "\n".join(names) + lookup = "{\n %s,\n}" % (",\n ".join(lookup),) return names, lookup + def write_library_constants(): """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi import tcod.color - with open('tcod/constants.py', 'w') as f: + + with open("tcod/constants.py", "w") as f: all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): value = getattr(lib, name) - if name[:5] == 'TCOD_': - if name.isupper(): # const names - f.write('%s = %r\n' % (name[5:], value)) + if name[:5] == "TCOD_": + if name.isupper(): # const names + f.write("%s = %r\n" % (name[5:], value)) all_names.append(name[5:]) - elif name.startswith('FOV'): # fov const names - f.write('%s = %r\n' % (name, value)) + elif name.startswith("FOV"): # fov const names + f.write("%s = %r\n" % (name, value)) all_names.append(name) - elif name[:6] == 'TCODK_': # key name - f.write('KEY_%s = %r\n' % (name[6:], value)) - all_names.append('KEY_%s' % name[6:]) + elif name[:6] == "TCODK_": # key name + f.write("KEY_%s = %r\n" % (name[6:], value)) + all_names.append("KEY_%s" % name[6:]) - f.write('\n# --- colors ---\n') + f.write("\n# --- colors ---\n") for name in dir(lib): - if name[:5] != 'TCOD_': + if name[:5] != "TCOD_": continue value = getattr(lib, name) if not isinstance(value, ffi.CData): continue - if ffi.typeof(value) != ffi.typeof('TCOD_color_t'): + if ffi.typeof(value) != ffi.typeof("TCOD_color_t"): continue color = tcod.color.Color._new_from_cdata(value) - f.write('%s = %r\n' % (name[5:], color)) + f.write("%s = %r\n" % (name[5:], color)) all_names.append(name[5:]) all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) - with open('tcod/event_constants.py', 'w') as f: + with open("tcod/event_constants.py", "w") as f: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) - f.write('# --- SDL scancodes ---\n') - f.write("%s\n_REVERSE_SCANCODE_TABLE = %s\n" - % parse_sdl_attrs("SDL_SCANCODE", all_names)) - - f.write('\n# --- SDL keyboard symbols ---\n') - f.write("%s\n_REVERSE_SYM_TABLE = %s\n" - % parse_sdl_attrs("SDLK", all_names)) - - f.write('\n# --- SDL keyboard modifiers ---\n') - f.write("%s\n_REVERSE_MOD_TABLE = %s\n" - % parse_sdl_attrs("KMOD", all_names)) - - f.write('\n# --- SDL wheel ---\n') - f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" - % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) + f.write("# --- SDL scancodes ---\n") + f.write( + "%s\n_REVERSE_SCANCODE_TABLE = %s\n" + % parse_sdl_attrs("SDL_SCANCODE", all_names) + ) + + f.write("\n# --- SDL keyboard symbols ---\n") + f.write( + "%s\n_REVERSE_SYM_TABLE = %s\n" + % parse_sdl_attrs("SDLK", all_names) + ) + + f.write("\n# --- SDL keyboard modifiers ---\n") + f.write( + "%s\n_REVERSE_MOD_TABLE = %s\n" + % parse_sdl_attrs("KMOD", all_names) + ) + + f.write("\n# --- SDL wheel ---\n") + f.write( + "%s\n_REVERSE_WHEEL_TABLE = %s\n" + % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names) + ) all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) From 841bd0f18d7b239e67b58898ea041481b51dc81c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 19 Jan 2019 22:22:38 -0800 Subject: [PATCH 0032/1101] Update libtcod to 1.10.3 --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b5c6e39..302ed5ca 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Changed + - Updated libtcod to 1.10.3 Fixed - Fixed libtcodpy `struct_add_value_list` function. diff --git a/libtcod b/libtcod index 64388d72..4e1fb18b 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 64388d72fea2855a27619155898cd444c9733358 +Subproject commit 4e1fb18bcb1cbb9e7d6666aeda5df6beb3aa1f93 From 0e44153b9b9509d961f92381dd6efcba6d39888b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 20 Jan 2019 00:19:09 -0800 Subject: [PATCH 0033/1101] Set pytest output capture to "sys" only. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 04171cf8..abc39e65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ test=pytest [tool:pytest] -addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl +addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl --capture=sys [flake8] ignore = E203 W503 From 27c61355442be8b8f3be674dae7f8a549b4b5179 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 20 Jan 2019 00:35:14 -0800 Subject: [PATCH 0034/1101] Add xvfb service to TravisCI settings. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ce714c04..f2b0fca2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,8 @@ addons: packages: - g++-8 - libsdl2-dev +services: + - xvfb before_install: - source .travis/before_install.sh From 8a2a9176efe86a6d4682ddaef65d073b979bdefb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 21 Jan 2019 18:41:57 -0800 Subject: [PATCH 0035/1101] Modernize tcod.event module. --- tcod/event.py | 92 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 6f1e432f..b91b8e2a 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -16,12 +16,14 @@ As a general guideline for turn-based rouge-likes, you should use KeyDown.sym for commands, and TextInput.text for name entry fields. """ -from typing import Any, Dict, Optional, Iterator +import collections +from typing import Any, Dict, Optional, Iterator, Tuple import tcod import tcod.event_constants from tcod.event_constants import * + def _describe_bitmask( bits: int, table: Dict[Any, str], default: Any = "0" ) -> str: @@ -45,6 +47,15 @@ def _describe_bitmask( return "|".join(result) +def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: + """Convert pixel coordinates to tile coordinates.""" + xy = tcod.ffi.new("double[2]", (x, y)) + tcod.lib.TCOD_sys_pixel_to_tile(xy, xy + 1) + return xy[0], xy[1] + + +Point = collections.namedtuple("Point", ["x", "y"]) + # manually define names for SDL macros BUTTON_LEFT = 1 BUTTON_MIDDLE = 2 @@ -105,7 +116,9 @@ def __repr__(self): class KeyboardEvent(Event): - def __init__(self, scancode, sym, mod, repeat=False): + def __init__( + self, scancode: int, sym: int, mod: int, repeat: bool = False + ): self.scancode = scancode self.sym = sym self.mod = mod @@ -123,7 +136,9 @@ def __repr__(self): self.__class__.__name__, tcod.event_constants._REVERSE_SCANCODE_TABLE[self.scancode], tcod.event_constants._REVERSE_SYM_TABLE[self.sym], - _describe_bitmask(self.mod, tcod.event_constants._REVERSE_MOD_TABLE), + _describe_bitmask( + self.mod, tcod.event_constants._REVERSE_MOD_TABLE + ), ", repeat=True" if self.repeat else "", ) @@ -137,45 +152,69 @@ class KeyUp(KeyboardEvent): class MouseMotion(Event): - def __init__(self, x, y, xrel, yrel, state): - self.x = x - self.y = y - self.xrel = xrel - self.yrel = yrel + def __init__( + self, + pixel: Tuple[int, int], + pixel_motion: Tuple[int, int], + tile: Tuple[int, int], + tile_motion: Tuple[int, int], + state: int, + ): + self.pixel = Point(*pixel) + self.pixel_motion = Point(*pixel_motion) + self.tile = Point(*tile) + self.tile_motion = Point(*tile_motion) self.state = state @classmethod def from_sdl_event(cls, sdl_event): motion = sdl_event.motion - return cls(motion.x, motion.y, motion.xrel, motion.yrel, motion.state) + + pixel = motion.x, motion.y + pixel_motion = motion.xrel, motion.yrel + subtile = _pixel_to_tile(*pixel) + tile = int(subtile[0]), int(subtile[1]) + prev_pixel = pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1] + prev_subtile = _pixel_to_tile(*prev_pixel) + prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) + tile_motion = tile[0] - prev_tile[0], tile[1] - prev_tile[1] + return cls(pixel, pixel_motion, tile, tile_motion, motion.state) def __repr__(self): - return "tcod.event.%s(x=%i, y=%i, xrel=%i, yrel=%i, state=%s)" % ( + return ( + "tcod.event.%s(pixel=%r, pixel_motion=%r, " + "tile=%r, tile_motion=%r, state=%s)" + ) % ( self.__class__.__name__, - self.x, - self.y, - self.xrel, - self.yrel, + self.pixel, + self.pixel_motion, + self.tile, + self.tile_motion, _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), ) class MouseButtonEvent(Event): - def __init__(self, x, y, button): - self.x = x - self.y = y + def __init__( + self, pixel: Tuple[int, int], tile: Tuple[int, int], button: int + ): + self.pixel = Point(*pixel) + self.tile = Point(*tile) self.button = button @classmethod def from_sdl_event(cls, sdl_event): button = sdl_event.button - return cls(button.x, button.y, button.button) + pixel = button.x, button.y + subtile = _pixel_to_tile(*pixel) + tile = int(subtile[0]), int(subtile[1]) + return cls(pixel, tile, button.button) def __repr__(self): - return "tcod.event.%s(x=%i, y=%i, button=%s)" % ( + return "tcod.event.%s(pixel=%r, tile=%r, button=%s)" % ( self.__class__.__name__, - self.x, - self.y, + self.pixel, + self.tile, _REVERSE_BUTTON_TABLE[self.button], ) @@ -189,10 +228,10 @@ class MouseButtonUp(MouseButtonEvent): class MouseWheel(Event): - def __init__(self, x, y, direction): + def __init__(self, x: int, y: int, flipped: bool = False): self.x = x self.y = y - self.direction = direction + self.flipped = flipped @classmethod def from_sdl_event(cls, sdl_event): @@ -200,16 +239,16 @@ def from_sdl_event(cls, sdl_event): return cls(wheel.x, wheel.y, wheel.direction) def __repr__(self): - return "tcod.event.%s(x=%i, y=%i, direction=%s)" % ( + return "tcod.event.%s(x=%i, y=%i%s)" % ( self.__class__.__name__, self.x, self.y, - tcod.event_constants._REVERSE_WHEEL_TABLE[self.direction], + ", flipped=True" if self.flipped else "", ) class TextInput(Event): - def __init__(self, text): + def __init__(self, text: str): self.text = text @classmethod @@ -267,6 +306,7 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: __all__ = [ + "Point", "Event", "Quit", "KeynoardEvent", From e36e9312fcf5fc271a82f9440d660e6a60d50517 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 21 Jan 2019 20:18:15 -0800 Subject: [PATCH 0036/1101] Normalize formatting for setup.py script. Also changed format of auto-generated version modules. --- setup.py | 168 +++++++++++++++++++++++++++---------------------------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/setup.py b/setup.py index 58897db3..d5368f7e 100755 --- a/setup.py +++ b/setup.py @@ -9,70 +9,80 @@ from distutils.unixccompiler import UnixCCompiler -C_STANDARD = '-std=c99' -CPP_STANDARD = '-std=c++14' +C_STANDARD = "-std=c99" +CPP_STANDARD = "-std=c++14" old_compile = UnixCCompiler._compile + def new_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): cc_args = list(cc_args) - if UnixCCompiler.language_map[ext] == 'c': + if UnixCCompiler.language_map[ext] == "c": cc_args.append(C_STANDARD) - elif UnixCCompiler.language_map[ext] == 'c++': + elif UnixCCompiler.language_map[ext] == "c++": cc_args.append(CPP_STANDARD) return old_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts) + UnixCCompiler._compile = new_compile + def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" try: - tag = check_output(['git', 'describe', '--abbrev=0'], - universal_newlines=True).strip() - assert not tag.startswith('v') + tag = check_output( + ["git", "describe", "--abbrev=0"], universal_newlines=True + ).strip() + assert not tag.startswith("v") version = tag # add .devNN if needed - log = check_output(['git', 'log', '%s..HEAD' % tag, '--oneline'], - universal_newlines=True) - commits_since_tag = log.count('\n') + log = check_output( + ["git", "log", "%s..HEAD" % tag, "--oneline"], + universal_newlines=True, + ) + commits_since_tag = log.count("\n") if commits_since_tag: - version += '.dev%i' % commits_since_tag + version += ".dev%i" % commits_since_tag # update tcod/version.py - open('tcod/version.py', 'w').write('__version__ = %r\n' % version) + open("tcod/version.py", "w").write('__version__ = "%s"\n' % version) return version except: - exec(open('tcod/version.py').read(), globals()) + exec(open("tcod/version.py").read(), globals()) return __version__ -is_pypy = platform.python_implementation() == 'PyPy' + +is_pypy = platform.python_implementation() == "PyPy" + def get_package_data(): - '''get data files which will be included in the main tcod/ directory''' + """get data files which will be included in the main tcod/ directory""" BITSIZE, LINKAGE = platform.architecture() files = [ - 'lib/LIBTCOD-CREDITS.txt', - 'lib/LIBTCOD-LICENSE.txt', - 'lib/README-SDL.txt' - ] - if 'win32' in sys.platform: - if BITSIZE == '32bit': - files += ['x86/SDL2.dll'] + "lib/LIBTCOD-CREDITS.txt", + "lib/LIBTCOD-LICENSE.txt", + "lib/README-SDL.txt", + ] + if "win32" in sys.platform: + if BITSIZE == "32bit": + files += ["x86/SDL2.dll"] else: - files += ['x64/SDL2.dll'] - if sys.platform == 'darwin': - files += ['SDL2.framework/Versions/A/SDL2'] + files += ["x64/SDL2.dll"] + if sys.platform == "darwin": + files += ["SDL2.framework/Versions/A/SDL2"] return files + def get_long_description(): """Return this projects description.""" - with open('README.rst', 'r') as f: + with open("README.rst", "r") as f: readme = f.read() - with open('CHANGELOG.rst', 'r') as f: + with open("CHANGELOG.rst", "r") as f: changelog = f.read() - changelog = changelog.replace('\nUnreleased\n------------------', '') - return '\n'.join([readme, changelog]) + changelog = changelog.replace("\nUnreleased\n------------------", "") + return "\n".join([readme, changelog]) + if sys.version_info < (3, 5): error = """ @@ -80,69 +90,57 @@ def get_long_description(): The last version supporting Python 2.7/3.4 was 'tcod==6.0.7' Python {py} detected. - """.format(py='.'.join([str(v) for v in sys.version_info[:3]])) + """.format( + py=".".join([str(v) for v in sys.version_info[:3]]) + ) print(error) sys.exit(1) -needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) -pytest_runner = ['pytest-runner'] if needs_pytest else [] +needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) +pytest_runner = ["pytest-runner"] if needs_pytest else [] setup( - name='tcod', + name="tcod", version=get_version(), - author='Kyle Stewart', - author_email='4B796C65+tdl@gmail.com', - description='Pythonic cffi port of libtcod.', + author="Kyle Stewart", + author_email="4B796C65+tdl@gmail.com", + description="Pythonic cffi port of libtcod.", long_description=get_long_description(), - url='https://github.com/libtcod/python-tcod', - py_modules=['libtcodpy'], - packages=['tdl', 'tcod'], - package_data={ - 'tdl': ['*.png'], - 'tcod': get_package_data(), - }, - python_requires='>=3.5', + url="https://github.com/libtcod/python-tcod", + py_modules=["libtcodpy"], + packages=["tdl", "tcod"], + package_data={"tdl": ["*.png"], "tcod": get_package_data()}, + python_requires=">=3.5", install_requires=[ - 'cffi>=1.8.1,<2', - 'numpy>=1.10,<2' if not is_pypy else '', - ], - cffi_modules=['build_libtcod.py:ffi'], - setup_requires=[ - 'cffi>=1.8.1,<2', - 'pycparser>=2.14,<3', - ] + pytest_runner, - tests_require=[ - 'pytest', - 'pytest-cov', - 'pytest-benchmark', - ], + "cffi>=1.8.1,<2", + "numpy>=1.10,<2" if not is_pypy else "", + ], + cffi_modules=["build_libtcod.py:ffi"], + setup_requires=["cffi>=1.8.1,<2", "pycparser>=2.14,<3"] + pytest_runner, + tests_require=["pytest", "pytest-cov", "pytest-benchmark"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Win32 (MS Windows)', - 'Environment :: MacOS X', - 'Environment :: X11 Applications', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Operating System :: POSIX', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Games/Entertainment', - 'Topic :: Multimedia :: Graphics', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - keywords='roguelike cffi Unicode libtcod fov heightmap namegen', - platforms=[ - 'Windows', - 'MacOS', - 'Linux', - ], - license='Simplified BSD License', - ) + "Development Status :: 5 - Production/Stable", + "Environment :: Win32 (MS Windows)", + "Environment :: MacOS X", + "Environment :: X11 Applications", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Games/Entertainment", + "Topic :: Multimedia :: Graphics", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords="roguelike cffi Unicode libtcod fov heightmap namegen", + platforms=["Windows", "MacOS", "Linux"], + license="Simplified BSD License", +) From 36982c684982698a1e73caa1e3032451f0b11e4f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 23 Jan 2019 00:14:56 -0800 Subject: [PATCH 0037/1101] Add tcod.event.EventDispatch, clean up tcod.event. --- tcod/event.py | 74 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index b91b8e2a..e98e4baf 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -15,13 +15,14 @@ As a general guideline for turn-based rouge-likes, you should use KeyDown.sym for commands, and TextInput.text for name entry fields. + +.. versionadded:: 8.4 """ -import collections -from typing import Any, Dict, Optional, Iterator, Tuple +from typing import Any, Dict, NamedTuple, Optional, Iterator, Tuple import tcod import tcod.event_constants -from tcod.event_constants import * +from tcod.event_constants import * # noqa: F4 def _describe_bitmask( @@ -54,7 +55,7 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: return xy[0], xy[1] -Point = collections.namedtuple("Point", ["x", "y"]) +Point = NamedTuple("Point", [("x", float), ("y", float)]) # manually define names for SDL macros BUTTON_LEFT = 1 @@ -89,16 +90,14 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: class Event: """The base event class.""" + def __init__(self, type: str = ""): + self.type = type if type else self.__class__.__name__.upper() + @classmethod def from_sdl_event(cls, sdl_event): """Return a class instance from a cffi SDL_Event pointer.""" raise NotImplementedError() - @property - def type(self): - """All event types are just the class name, but all upper-case.""" - return self.__class__.__name__.upper() - class Quit(Event): """An application quit request event. @@ -119,6 +118,7 @@ class KeyboardEvent(Event): def __init__( self, scancode: int, sym: int, mod: int, repeat: bool = False ): + super().__init__() self.scancode = scancode self.sym = sym self.mod = mod @@ -160,6 +160,7 @@ def __init__( tile_motion: Tuple[int, int], state: int, ): + super().__init__() self.pixel = Point(*pixel) self.pixel_motion = Point(*pixel_motion) self.tile = Point(*tile) @@ -198,6 +199,7 @@ class MouseButtonEvent(Event): def __init__( self, pixel: Tuple[int, int], tile: Tuple[int, int], button: int ): + super().__init__() self.pixel = Point(*pixel) self.tile = Point(*tile) self.button = button @@ -229,6 +231,7 @@ class MouseButtonUp(MouseButtonEvent): class MouseWheel(Event): def __init__(self, x: int, y: int, flipped: bool = False): + super().__init__() self.x = x self.y = y self.flipped = flipped @@ -236,7 +239,7 @@ def __init__(self, x: int, y: int, flipped: bool = False): @classmethod def from_sdl_event(cls, sdl_event): wheel = sdl_event.wheel - return cls(wheel.x, wheel.y, wheel.direction) + return cls(wheel.x, wheel.y, bool(wheel.direction)) def __repr__(self): return "tcod.event.%s(x=%i, y=%i%s)" % ( @@ -249,6 +252,7 @@ def __repr__(self): class TextInput(Event): def __init__(self, text: str): + super().__init__() self.text = text @classmethod @@ -305,11 +309,58 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: return get() +class EventDispatch: + def dispatch(self, event: Any): + getattr(self, "ev_%s" % (event.type.lower(),))(event) + + def event_get(self): + for event in get(): + self.dispatch(event) + + def event_wait(self, timeout: Optional[float]): + wait(timeout) + self.event_get() + + def ev_quit(self, event: Quit): + pass + + def ev_keydown(self, event: KeyDown): + pass + + def ev_keyup(self, event: KeyUp): + pass + + def ev_mousemotion(self, event: MouseMotion): + pass + + def ev_mousebuttondown(self, event: MouseButtonDown): + pass + + def ev_mousebuttonup(self, event: MouseButtonUp): + pass + + def ev_mousewheel(self, event: MouseWheel): + pass + + def ev_textinput(self, event: TextInput): + pass + + __all__ = [ "Point", + "BUTTON_LEFT", + "BUTTON_MIDDLE", + "BUTTON_RIGHT", + "BUTTON_X1", + "BUTTON_X2", + "BUTTON_LMASK", + "BUTTON_MMASK", + "BUTTON_RMASK", + "BUTTON_X1MASK", + "BUTTON_X2MASK", "Event", "Quit", - "KeynoardEvent", + "KeyboardEvent", "KeyDown", "KeyUp", "MouseMotion", @@ -320,4 +371,5 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: "TextInput", "get", "wait", + "EventDispatch", ] + tcod.event_constants.__all__ From 5d76e8568ba17bc11f9c7de30d3568bfb79d681a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 25 Jan 2019 02:46:12 -0800 Subject: [PATCH 0038/1101] Add class for undefined handling of SDL events. --- CHANGELOG.rst | 2 ++ examples/sdlevent.py | 4 ++-- tcod/event.py | 53 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 302ed5ca..7a4b706c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Added + - Added tcod.event module, based off of the sdlevent.py shim. Changed - Updated libtcod to 1.10.3 Fixed diff --git a/examples/sdlevent.py b/examples/sdlevent.py index acfc01c5..112a7c5c 100644 --- a/examples/sdlevent.py +++ b/examples/sdlevent.py @@ -19,11 +19,11 @@ def main(): raise SystemExit() elif event.type == "MOUSEMOTION": console.ch[:, -1] = 0 - console.print_(0, HEIGHT - 1, repr(event)) + console.print_(0, HEIGHT - 1, str(event)) else: console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) console.ch[:, -3] = 0 - console.print_(0, HEIGHT - 3, repr(event)) + console.print_(0, HEIGHT - 3, str(event)) if __name__ == "__main__": diff --git a/tcod/event.py b/tcod/event.py index e98e4baf..5ad40b26 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -90,8 +90,11 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: class Event: """The base event class.""" - def __init__(self, type: str = ""): - self.type = type if type else self.__class__.__name__.upper() + def __init__(self, type: Optional[str] = None): + if type is None: + type = self.__class__.__name__.upper() + self.type = type + self.sdl_event = None @classmethod def from_sdl_event(cls, sdl_event): @@ -108,7 +111,9 @@ class Quit(Event): @classmethod def from_sdl_event(cls, sdl_event): - return cls() + self = cls() + self.sdl_event = sdl_event + return self def __repr__(self): return "tcod.event.%s()" % self.__class__.__name__ @@ -127,9 +132,11 @@ def __init__( @classmethod def from_sdl_event(cls, sdl_event): keysym = sdl_event.key.keysym - return cls( + self = cls( keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat) ) + self.sdl_event = sdl_event + return self def __repr__(self): return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( @@ -179,7 +186,9 @@ def from_sdl_event(cls, sdl_event): prev_subtile = _pixel_to_tile(*prev_pixel) prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) tile_motion = tile[0] - prev_tile[0], tile[1] - prev_tile[1] - return cls(pixel, pixel_motion, tile, tile_motion, motion.state) + self = cls(pixel, pixel_motion, tile, tile_motion, motion.state) + self.sdl_event = sdl_event + return self def __repr__(self): return ( @@ -210,7 +219,9 @@ def from_sdl_event(cls, sdl_event): pixel = button.x, button.y subtile = _pixel_to_tile(*pixel) tile = int(subtile[0]), int(subtile[1]) - return cls(pixel, tile, button.button) + self = cls(pixel, tile, button.button) + self.sdl_event = sdl_event + return self def __repr__(self): return "tcod.event.%s(pixel=%r, tile=%r, button=%s)" % ( @@ -239,7 +250,9 @@ def __init__(self, x: int, y: int, flipped: bool = False): @classmethod def from_sdl_event(cls, sdl_event): wheel = sdl_event.wheel - return cls(wheel.x, wheel.y, bool(wheel.direction)) + self = cls(wheel.x, wheel.y, bool(wheel.direction)) + self.sdl_event = sdl_event + return self def __repr__(self): return "tcod.event.%s(x=%i, y=%i%s)" % ( @@ -257,12 +270,30 @@ def __init__(self, text: str): @classmethod def from_sdl_event(cls, sdl_event): - return cls(tcod.ffi.string(sdl_event.text.text, 32).decode("utf8")) + self = cls(tcod.ffi.string(sdl_event.text.text, 32).decode("utf8")) + self.sdl_event = sdl_event + return self def __repr__(self): return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) +class Undefined(Event): + """This class is a place holder for SDL events without a Python class.""" + + def __init__(self): + super().__init__("") + + @classmethod + def from_sdl_event(cls, sdl_event): + self = cls() + self.sdl_event = sdl_event + return self + + def __str__(self): + return "" % self.sdl_event.type + + _SDL_TO_CLASS_TABLE = { tcod.lib.SDL_QUIT: Quit, tcod.lib.SDL_KEYDOWN: KeyDown, @@ -286,6 +317,8 @@ def get() -> Iterator[Any]: while tcod.lib.SDL_PollEvent(sdl_event): if sdl_event.type in _SDL_TO_CLASS_TABLE: yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) + else: + yield Undefined.from_sdl_event(sdl_event) def wait(timeout: Optional[float] = None) -> Iterator[Any]: @@ -311,7 +344,8 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: class EventDispatch: def dispatch(self, event: Any): - getattr(self, "ev_%s" % (event.type.lower(),))(event) + if event.type: + getattr(self, "ev_%s" % (event.type.lower(),))(event) def event_get(self): for event in get(): @@ -369,6 +403,7 @@ def ev_textinput(self, event: TextInput): "MouseButtonUp", "MouseWheel", "TextInput", + "Undefined", "get", "wait", "EventDispatch", From 81e03ef7b987c0d25cd0329cb8a8cb9e9c44fd45 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 25 Jan 2019 03:07:40 -0800 Subject: [PATCH 0039/1101] Clean up some typing. --- tcod/bsp.py | 4 ++-- tcod/libtcodpy.py | 9 ++++---- tcod/path.py | 59 ++++++++++++++++++++++++++--------------------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/tcod/bsp.py b/tcod/bsp.py index b6e58546..a2cd5c03 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -23,7 +23,7 @@ else: print('Dig a room for %s.' % node) """ -from typing import Iterator, Optional +from typing import Iterator, List, Optional, Union # noqa: F401 from tcod.libtcod import lib, ffi from tcod._internal import deprecate @@ -65,7 +65,7 @@ def __init__(self, x: int, y: int, width: int, height: int): self.horizontal = False # type: bool self.parent = None # type: Optional['BSP'] - self.children = () # type: Union[Tuple[], Tuple['BSP', 'BSP']] + self.children = () # type: Union[Tuple[()], Tuple['BSP', 'BSP']] @property def w(self) -> int: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b4e25bfe..ab23d57d 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1420,8 +1420,7 @@ def console_set_key_color(con, col): con.set_key_color(col) -def console_delete(con): - # type: (Console) -> None +def console_delete(con: tcod.console.Console) -> None: """Closes the window if `con` is the root console. libtcod objects are automatically garbage collected once they go out of @@ -1429,7 +1428,6 @@ def console_delete(con): This function exists for backwards compatibility. """ - # type: (Any) -> None con = _console(con) if con == ffi.NULL: lib.TCOD_console_delete(con) @@ -2627,8 +2625,9 @@ def line_iter(xo, yo, xd, yd): yield (x[0], y[0]) -def line_where(x1, y1, x2, y2, inclusive=True): - # type: (int, int, int, int, bool) -> tuple[np.ndarray, np.ndarray] +def line_where( + x1: int, y1: int, x2: int, y2: int, inclusive: bool = True +) -> Tuple[np.ndarray, np.ndarray]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. diff --git a/tcod/path.py b/tcod/path.py index 2de918b4..6373e359 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -41,41 +41,46 @@ array is used.) """ import numpy as np +from typing import Any, Callable, List, Tuple, Union from tcod.libtcod import lib, ffi +import tcod.map @ffi.def_extern() -def _pycall_path_old(x1, y1, x2, y2, handle): - # type: (int, int, int, int, cffi.CData]) -> float +def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: """libtcodpy style callback, needs to preserve the old userData issue.""" func, userData = ffi.from_handle(handle) return func(x1, y1, x2, y2, userData) @ffi.def_extern() -def _pycall_path_simple(x1, y1, x2, y2, handle): - # type: (int, int, int, int, cffi.CData]) -> float +def _pycall_path_simple( + x1: int, y1: int, x2: int, y2: int, handle: Any +) -> float: """Does less and should run faster, just calls the handle function.""" return ffi.from_handle(handle)(x1, y1, x2, y2) @ffi.def_extern() -def _pycall_path_swap_src_dest(x1, y1, x2, y2, handle): - # type: (int, int, int, int, cffi.CData]) -> float +def _pycall_path_swap_src_dest( + x1: int, y1: int, x2: int, y2: int, handle: Any +) -> float: """A TDL function dest comes first to match up with a dest only call.""" return ffi.from_handle(handle)(x2, y2, x1, y1) @ffi.def_extern() -def _pycall_path_dest_only(x1, y1, x2, y2, handle): - # type: (int, int, int, int, cffi.CData]) -> float +def _pycall_path_dest_only( + x1: int, y1: int, x2: int, y2: int, handle: Any +) -> float: """A TDL function which samples the dest coordinate only.""" return ffi.from_handle(handle)(x2, y2) -def _get_pathcost_func(name): - # type: (str) -> Callable[[int, int, int, int, cffi.CData], float] +def _get_pathcost_func( + name: str +) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) @@ -90,13 +95,11 @@ class _EdgeCostFunc(object): _CALLBACK_P = lib._pycall_path_old - def __init__(self, userdata, shape): - # type: (Any, Tuple[int, int]) -> None + def __init__(self, userdata: Any, shape: Tuple[int, int]) -> None: self._userdata = userdata self.shape = shape - def get_tcod_path_ffi(self): - # type: () -> Tuple[cffi.CData, cffi.CData, Tuple[int, int]] + def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: """Return (C callback, userdata handle, shape)""" return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape @@ -123,8 +126,11 @@ class EdgeCostCallback(_EdgeCostFunc): _CALLBACK_P = lib._pycall_path_simple - def __init__(self, callback, shape): - # type: (Callable[[int, int, int, int], float], Tuple[int, int]) + def __init__( + self, + callback: Callable[[int, int, int, int], float], + shape: Tuple[int, int], + ): self.callback = callback super(EdgeCostCallback, self).__init__(callback, shape) @@ -158,8 +164,7 @@ def __repr__(self): repr(self.view(np.ndarray)), ) - def get_tcod_path_ffi(self): - # type: () -> Tuple[cffi.CData, cffi.CData, Tuple[int, int]] + def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: if len(self.shape) != 2: raise ValueError( "Array must have a 2d shape, shape is %r" % (self.shape,) @@ -181,8 +186,11 @@ def get_tcod_path_ffi(self): class _PathFinder(object): """A class sharing methods used by AStar and Dijkstra.""" - def __init__(self, cost, diagonal=1.41): - # type: (Union[tcod.map.Map, numpy.ndarray, Any], float) + def __init__( + self, + cost: Union[tcod.map.Map, np.ndarray, Any], + diagonal: float = 1.41, + ): self.cost = cost self.diagonal = diagonal self._path_c = None @@ -249,8 +257,9 @@ class AStar(_PathFinder): A value of 0 will disable diagonal movement entirely. """ - def get_path(self, start_x, start_y, goal_x, goal_y): - # type: (int, int, int, int) -> List[Tuple[int, int]] + def get_path( + self, start_x: int, start_y: int, goal_x: int, goal_y: int + ) -> List[Tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible. Args: @@ -283,14 +292,12 @@ class Dijkstra(_PathFinder): _path_new_using_function = lib.TCOD_dijkstra_new_using_function _path_delete = lib.TCOD_dijkstra_delete - def set_goal(self, x, y): - # type: (int, int) -> None + def set_goal(self, x: int, y: int) -> None: """Set the goal point and recompute the Dijkstra path-finder. """ lib.TCOD_dijkstra_compute(self._path_c, x, y) - def get_path(self, x, y): - # type: (int, int) -> List[Tuple[int, int]] + def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible. """ lib.TCOD_dijkstra_path_set(self._path_c, x, y) From 8f0dfe3946dc322979ad7549eaef7345a5129250 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 25 Jan 2019 16:13:28 -0800 Subject: [PATCH 0040/1101] Use less ambiguous 'import libtcodpy' warning. --- libtcodpy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libtcodpy.py b/libtcodpy.py index 1c352389..bb1a8cc8 100644 --- a/libtcodpy.py +++ b/libtcodpy.py @@ -1,4 +1,9 @@ """This module just an alias for tcod""" import warnings -warnings.warn("`import tcod` is preferred.", DeprecationWarning, stacklevel=2) -from tcod import * + +warnings.warn( + "'import tcod as libtcodpy' is preferred.", + DeprecationWarning, + stacklevel=2, +) +from tcod import * # noqa: F4 From d8f238b6343bbeac0775ab3034d0f5cd8047d66a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 28 Jan 2019 16:44:55 -0800 Subject: [PATCH 0041/1101] Add window event handling. --- tcod/event.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/tcod/event.py b/tcod/event.py index 5ad40b26..054aee38 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -278,6 +278,54 @@ def __repr__(self): return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) +class WindowEvent(Event): + def __init__(self, type: str, x: Optional[int] = 0, y: Optional[int] = 0): + super().__init__(type) + self.x = x # type(Optional[int]) + self.y = y # type(Optional[int]) + + @classmethod + def from_sdl_event(cls, sdl_event): + if sdl_event.window.event not in cls.__WINDOW_TYPES: + return Undefined.from_sdl_event(sdl_event) + self = cls( + cls.__WINDOW_TYPES[sdl_event.window.event], + sdl_event.window.data1, + sdl_event.window.data2, + ) + self.sdl_event = sdl_event + return self + + def __repr__(self): + params = "" + if self.x or self.y: + params = ", x=%r, y=%r" % (self.x, self.y) + return "tcod.event.%s(type=%r%s)" % ( + self.__class__.__name__, + self.type, + params, + ) + + __WINDOW_TYPES = { + tcod.lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", + tcod.lib.SDL_WINDOWEVENT_HIDDEN: "WindowHidden", + tcod.lib.SDL_WINDOWEVENT_EXPOSED: "WindowExposed", + tcod.lib.SDL_WINDOWEVENT_MOVED: "WindowMoved", + tcod.lib.SDL_WINDOWEVENT_RESIZED: "WindowResized", + tcod.lib.SDL_WINDOWEVENT_SIZE_CHANGED: "WindowSizeChanged", + tcod.lib.SDL_WINDOWEVENT_MINIMIZED: "WindowMinimized", + tcod.lib.SDL_WINDOWEVENT_MAXIMIZED: "WindowMaximized", + tcod.lib.SDL_WINDOWEVENT_RESTORED: "WindowRestored", + tcod.lib.SDL_WINDOWEVENT_ENTER: "WindowEnter", + tcod.lib.SDL_WINDOWEVENT_LEAVE: "WindowLeave", + tcod.lib.SDL_WINDOWEVENT_FOCUS_GAINED: "WindowFocusGained", + tcod.lib.SDL_WINDOWEVENT_FOCUS_LOST: "WindowFocusLost", + tcod.lib.SDL_WINDOWEVENT_CLOSE: "WindowClose", + tcod.lib.SDL_WINDOWEVENT_TAKE_FOCUS: "WindowTakeFocus", + tcod.lib.SDL_WINDOWEVENT_HIT_TEST: "WindowHitTest", + } + + class Undefined(Event): """This class is a place holder for SDL events without a Python class.""" @@ -303,6 +351,7 @@ def __str__(self): tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp, tcod.lib.SDL_MOUSEWHEEL: MouseWheel, tcod.lib.SDL_TEXTINPUT: TextInput, + tcod.lib.SDL_WINDOWEVENT: WindowEvent, } @@ -379,6 +428,51 @@ def ev_mousewheel(self, event: MouseWheel): def ev_textinput(self, event: TextInput): pass + def ev_windowshown(self, event: WindowEvent): + pass + + def ev_windowhidden(self, event: WindowEvent): + pass + + def ev_windowexposed(self, event: WindowEvent): + pass + + def ev_windowresized(self, event: WindowEvent): + pass + + def ev_windowsizechanged(self, event: WindowEvent): + pass + + def ev_windowminimized(self, event: WindowEvent): + pass + + def ev_windowmaximized(self, event: WindowEvent): + pass + + def ev_windowrestored(self, event: WindowEvent): + pass + + def ev_windowenter(self, event: WindowEvent): + pass + + def ev_windowleave(self, event: WindowEvent): + pass + + def ev_windowfocusgained(self, event: WindowEvent): + pass + + def ev_windowfocuslost(self, event: WindowEvent): + pass + + def ev_windowclose(self, event: WindowEvent): + pass + + def ev_windowtakefocus(self, event: WindowEvent): + pass + + def ev_windowhittest(self, event: WindowEvent): + pass + __all__ = [ "Point", @@ -403,6 +497,7 @@ def ev_textinput(self, event: TextInput): "MouseButtonUp", "MouseWheel", "TextInput", + "WindowEvent", "Undefined", "get", "wait", From cc6b088e660f4fc6ae34fa476de6f42f7d2c9278 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 28 Jan 2019 17:00:23 -0800 Subject: [PATCH 0042/1101] Ignore mypy cache. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a3676581..70a30b12 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ debian/files debian/python* .pytest_cache Thumbs.db +.mypy_cache/ From 8ebdc1987a8b73880a738d3bde3f1d5612efd82a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 02:34:28 -0800 Subject: [PATCH 0043/1101] Add missing dispatch method and default event values. --- tcod/event.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 054aee38..0b5fe0d5 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -161,11 +161,11 @@ class KeyUp(KeyboardEvent): class MouseMotion(Event): def __init__( self, - pixel: Tuple[int, int], - pixel_motion: Tuple[int, int], - tile: Tuple[int, int], - tile_motion: Tuple[int, int], - state: int, + pixel: Tuple[int, int] = (0, 0), + pixel_motion: Tuple[int, int] = (0, 0), + tile: Tuple[int, int] = (0, 0), + tile_motion: Tuple[int, int] = (0, 0), + state: int = 0, ): super().__init__() self.pixel = Point(*pixel) @@ -206,7 +206,10 @@ def __repr__(self): class MouseButtonEvent(Event): def __init__( - self, pixel: Tuple[int, int], tile: Tuple[int, int], button: int + self, + pixel: Tuple[int, int] = (0, 0), + tile: Tuple[int, int] = (0, 0), + button: int = 0, ): super().__init__() self.pixel = Point(*pixel) @@ -437,6 +440,9 @@ def ev_windowhidden(self, event: WindowEvent): def ev_windowexposed(self, event: WindowEvent): pass + def ev_windowmoved(self, event: WindowEvent): + pass + def ev_windowresized(self, event: WindowEvent): pass From 1d75c41589eccd976513eb3159607d8aa76abd00 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 02:35:15 -0800 Subject: [PATCH 0044/1101] Refactor samples_tcod.py --- examples/samples_tcod.py | 1460 +++++++++++++++++++++++--------------- 1 file changed, 885 insertions(+), 575 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 6861fcd2..fcf0efef 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -9,22 +9,28 @@ import os import math +import time import numpy as np import tcod as libtcod +import tcod.event SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -font = os.path.join('data/fonts/consolas10x10_gs_tc.png') +font = os.path.join("data/fonts/consolas10x10_gs_tc.png") libtcod.console_set_custom_font( - font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) + font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD +) root_console = libtcod.console_init_root(80, 50, "tcod python sample", False) -sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) +sample_console = tcod.console.Console( + SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT +) -class Sample(): - def __init__(self, name='', func=None): + +class Sample(tcod.event.EventDispatch): + def __init__(self, name="", func=None): self.name = name self.func = func @@ -40,24 +46,60 @@ def on_key(self, key): def on_mouse(self, mouse): pass + def ev_keydown(self, event): + global cur_sample + if event.sym == tcod.event.K_DOWN: + cur_sample = (cur_sample + 1) % len(SAMPLES) + SAMPLES[cur_sample].on_enter() + draw_samples_menu() + elif event.sym == tcod.event.K_UP: + cur_sample = (cur_sample - 1) % len(SAMPLES) + SAMPLES[cur_sample].on_enter() + draw_samples_menu() + elif ( + event.sym == tcod.event.K_RETURN + and event.mod & tcod.event.KMOD_LALT + ): + libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) + elif event.sym == tcod.event.K_PRINTSCREEN or event.sym == ord("p"): + print("screenshot") + if event.mod & tcod.event.KMOD_LALT: + libtcod.console_save_apf(None, "samples.apf") + print("apf") + else: + libtcod.sys_save_screenshot() + print("png") + elif event.sym == tcod.event.K_ESCAPE: + raise SystemExit() + elif event.sym in RENDERER_KEYS: + libtcod.sys_set_renderer(RENDERER_KEYS[event.sym]) + draw_renderer_menu() + + def ev_quit(self, event): + raise SystemExit() + + ############################################# # true color sample ############################################# class TrueColorSample(Sample): - def __init__(self): self.name = "True colors" # corner colors - self.colors = np.array([(50, 40, 150), (240, 85, 5), - (50, 35, 240), (10, 200, 130)], dtype=np.int16) + self.colors = np.array( + [(50, 40, 150), (240, 85, 5), (50, 35, 240), (10, 200, 130)], + dtype=np.int16, + ) # color shift direction - self.slide_dir = np.array([[1, 1, 1], [-1, -1, 1], - [1, -1, 1], [1, 1, -1]], dtype=np.int16) + self.slide_dir = np.array( + [[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16 + ) # corner indexes self.corners = np.array([0, 1, 2, 3]) # sample screen mesh-grid - self.mgrid = np.mgrid[0:1:SAMPLE_SCREEN_HEIGHT * 1j, - 0:1:SAMPLE_SCREEN_WIDTH * 1j] + self.mgrid = np.mgrid[ + 0 : 1 : SAMPLE_SCREEN_HEIGHT * 1j, 0 : 1 : SAMPLE_SCREEN_WIDTH * 1j + ] def on_enter(self): libtcod.sys_set_fps(0) @@ -75,8 +117,9 @@ def slide_corner_colors(self): rand_channels = np.random.randint(low=0, high=3, size=4) # shift picked color channels in the direction of slide_dir - self.colors[self.corners, rand_channels] += \ + self.colors[self.corners, rand_channels] += ( self.slide_dir[self.corners, rand_channels] * 5 + ) # reverse slide_dir values when limits are reached self.slide_dir[self.colors[:] == 255] = -1 @@ -84,11 +127,13 @@ def slide_corner_colors(self): def interpolate_corner_colors(self): # interpolate corner colors across the sample console - for i in range(3): # for each color channel - left = ((self.colors[2, i] - self.colors[0, i]) * self.mgrid[0] + - self.colors[0, i]) - right = ((self.colors[3, i] - self.colors[1, i]) * self.mgrid[0] + - self.colors[1, i]) + for i in range(3): # for each color channel + left = (self.colors[2, i] - self.colors[0, i]) * self.mgrid[ + 0 + ] + self.colors[0, i] + right = (self.colors[3, i] - self.colors[1, i]) * self.mgrid[ + 0 + ] + self.colors[1, i] sample_console.bg[:, :, i] = (right - left) * self.mgrid[1] + left def darken_background_characters(self): @@ -99,34 +144,41 @@ def darken_background_characters(self): def randomize_sample_conole(self): # randomize sample console characters sample_console.ch[:] = np.random.randint( - low=ord('a'), high=ord('z') + 1, + low=ord("a"), + high=ord("z") + 1, size=sample_console.ch.size, dtype=np.intc, - ).reshape(sample_console.ch.shape) + ).reshape(sample_console.ch.shape) def print_banner(self): # print text on top of samples sample_console.default_bg = libtcod.grey sample_console.print_rect( - x=sample_console.width // 2, y=5, - width=sample_console.width - 2, height=sample_console.height - 1, + x=sample_console.width // 2, + y=5, + width=sample_console.width - 2, + height=sample_console.height - 1, string="The Doryen library uses 24 bits colors, for both " - "background and foreground.", - bg_blend=libtcod.BKGND_MULTIPLY, alignment=libtcod.CENTER, - ) + "background and foreground.", + bg_blend=libtcod.BKGND_MULTIPLY, + alignment=libtcod.CENTER, + ) + ############################################# # offscreen console sample ############################################# -class OffscreenConsoleSample(Sample): +class OffscreenConsoleSample(Sample): def __init__(self): - self.name = 'Offscreen console' - self.secondary = libtcod.console.Console(sample_console.width // 2, - sample_console.height // 2) - self.screenshot = libtcod.console.Console(sample_console.width, - sample_console.height) + self.name = "Offscreen console" + self.secondary = libtcod.console.Console( + sample_console.width // 2, sample_console.height // 2 + ) + self.screenshot = libtcod.console.Console( + sample_console.width, sample_console.height + ) self.counter = 0 self.x = 0 self.y = 0 @@ -134,25 +186,38 @@ def __init__(self): self.ydir = 1 self.secondary.print_frame( - 0, 0, sample_console.width // 2, sample_console.height // 2, + 0, + 0, + sample_console.width // 2, + sample_console.height // 2, "Offscreen console", False, - libtcod.BKGND_NONE - ) + libtcod.BKGND_NONE, + ) self.secondary.print_rect( - sample_console.width // 4, 2, - sample_console.width // 2 - 2, sample_console.height // 2, + sample_console.width // 4, + 2, + sample_console.width // 2 - 2, + sample_console.height // 2, "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", - libtcod.BKGND_NONE, libtcod.CENTER - ) + libtcod.BKGND_NONE, + libtcod.CENTER, + ) def on_enter(self): libtcod.sys_set_fps(0) # get a "screenshot" of the current sample screen - sample_console.blit(0, 0, sample_console.width, sample_console.height, - self.screenshot, 0, 0) + sample_console.blit( + self.screenshot, + 0, + 0, + 0, + 0, + sample_console.width, + sample_console.height, + ) def on_draw(self, delta_time): self.counter += delta_time * 1.5 @@ -168,133 +233,168 @@ def on_draw(self, delta_time): self.ydir = -1 elif self.y == -5: self.ydir = 1 - self.screenshot.blit(0, 0, sample_console.width, sample_console.height, - sample_console, 0, 0) + self.screenshot.blit( + sample_console, + 0, + 0, + 0, + 0, + sample_console.width, + sample_console.height, + ) self.secondary.blit( - 0, 0, sample_console.width // 2, sample_console.height // 2, - sample_console, self.x, self.y, 1.0, 0.75 - ) + sample_console, + self.x, + self.y, + 0, + 0, + sample_console.width // 2, + sample_console.height // 2, + 1.0, + 0.75, + ) + ############################################# # line drawing sample ############################################# + class LineDrawingSample(Sample): FLAG_NAMES = [ - 'BKGND_NONE', - 'BKGND_SET', - 'BKGND_MULTIPLY', - 'BKGND_LIGHTEN', - 'BKGND_DARKEN', - 'BKGND_SCREEN', - 'BKGND_COLOR_DODGE', - 'BKGND_COLOR_BURN', - 'BKGND_ADD', - 'BKGND_ADDALPHA', - 'BKGND_BURN', - 'BKGND_OVERLAY', - 'BKGND_ALPHA', - ] + "BKGND_NONE", + "BKGND_SET", + "BKGND_MULTIPLY", + "BKGND_LIGHTEN", + "BKGND_DARKEN", + "BKGND_SCREEN", + "BKGND_COLOR_DODGE", + "BKGND_COLOR_BURN", + "BKGND_ADD", + "BKGND_ADDALPHA", + "BKGND_BURN", + "BKGND_OVERLAY", + "BKGND_ALPHA", + ] def __init__(self): - self.name = 'Line drawing' + self.name = "Line drawing" self.mk_flag = libtcod.BKGND_SET self.bk_flag = libtcod.BKGND_SET - self.bk = libtcod.console_new(sample_console.width, - sample_console.height) + self.bk = libtcod.console_new( + sample_console.width, sample_console.height + ) # initialize the colored background for x in range(sample_console.width): for y in range(sample_console.height): col = libtcod.Color( x * 255 // (sample_console.width - 1), - (x + y) * 255 // (sample_console.width - 1 + - sample_console.height - 1), - y * 255 // (sample_console.height-1), + (x + y) + * 255 + // (sample_console.width - 1 + sample_console.height - 1), + y * 255 // (sample_console.height - 1), ) self.bk.bg[y, x] = col - self.bk.ch[:] = ord(' ') - + self.bk.ch[:] = ord(" ") - def on_key(self, key): - if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER): + def ev_keydown(self, event): + if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): self.bk_flag += 1 - if (self.bk_flag & 0xff) > libtcod.BKGND_ALPH: + if (self.bk_flag & 0xFF) > libtcod.BKGND_ALPH: self.bk_flag = libtcod.BKGND_NONE + else: + super().ev_keydown(event) def on_enter(self): libtcod.sys_set_fps(0) - libtcod.console_set_default_foreground(sample_console, libtcod.white) + sample_console.default_fg = libtcod.white def on_draw(self, delta_time): alpha = 0.0 - if (self.bk_flag & 0xff) == libtcod.BKGND_ALPH: + if (self.bk_flag & 0xFF) == libtcod.BKGND_ALPH: # for the alpha mode, update alpha every frame alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 self.bk_flag = libtcod.BKGND_ALPHA(alpha) - elif (self.bk_flag & 0xff) == libtcod.BKGND_ADDA: + elif (self.bk_flag & 0xFF) == libtcod.BKGND_ADDA: # for the add alpha mode, update alpha every frame alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 self.bk_flag = libtcod.BKGND_ADDALPHA(alpha) - self.bk.blit(0, 0, sample_console.width, - sample_console.height, sample_console, 0, 0) - recty = int((sample_console.height - 2) - * ((1.0 + math.cos(libtcod.sys_elapsed_seconds())) / 2.0)) + self.bk.blit( + sample_console, + 0, + 0, + 0, + 0, + sample_console.width, + sample_console.height, + ) + recty = int( + (sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0) + ) for x in range(sample_console.width): - col = libtcod.Color(x * 255 // sample_console.width, - x * 255 // sample_console.width, - x * 255 // sample_console.width) + col = libtcod.Color( + x * 255 // sample_console.width, + x * 255 // sample_console.width, + x * 255 // sample_console.width, + ) libtcod.console_set_char_background( - sample_console, x, recty, col, self.bk_flag) + sample_console, x, recty, col, self.bk_flag + ) libtcod.console_set_char_background( - sample_console, x, recty + 1, col, self.bk_flag) + sample_console, x, recty + 1, col, self.bk_flag + ) libtcod.console_set_char_background( - sample_console, x, recty + 2, col, self.bk_flag) - angle = libtcod.sys_elapsed_seconds() * 2.0 + sample_console, x, recty + 2, col, self.bk_flag + ) + angle = time.time() * 2.0 cos_angle = math.cos(angle) sin_angle = math.sin(angle) xo = int(sample_console.width // 2 * (1 + cos_angle)) - yo = int(sample_console.height // 2 - + sin_angle * sample_console.width // 2) + yo = int( + sample_console.height // 2 + sin_angle * sample_console.width // 2 + ) xd = int(sample_console.width // 2 * (1 - cos_angle)) - yd = int(sample_console.height // 2 - - sin_angle * sample_console.width // 2) + yd = int( + sample_console.height // 2 - sin_angle * sample_console.width // 2 + ) # draw the line # in python the easiest way is to use the line iterator for x, y in libtcod.line_iter(xo, yo, xd, yd): - if 0 <= x < sample_console.width and \ - 0 <= y < sample_console.height: + if ( + 0 <= x < sample_console.width + and 0 <= y < sample_console.height + ): libtcod.console_set_char_background( - sample_console, x, y, libtcod.light_blue, self.bk_flag) + sample_console, x, y, libtcod.light_blue, self.bk_flag + ) sample_console.print_( - 2, 2, - '%s (ENTER to change)' % self.FLAG_NAMES[self.bk_flag & 0xff] - ) + 2, 2, "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF] + ) + ############################################# # noise sample ############################################# -NOISE_OPTIONS = [ # [name, algorithm, implementation], - ['perlin noise', libtcod.NOISE_PERLIN, libtcod.noise.SIMPLE], - ['simplex noise', libtcod.NOISE_SIMPLEX, libtcod.noise.SIMPLE], - ['wavelet noise', libtcod.NOISE_WAVELET, libtcod.noise.SIMPLE], - ['perlin fbm', libtcod.NOISE_PERLIN, libtcod.noise.FBM], - ['perlin turbulence', libtcod.NOISE_PERLIN, libtcod.noise.TURBULENCE], - ['simplex fbm', libtcod.NOISE_SIMPLEX, libtcod.noise.FBM], - ['simplex turbulence', - libtcod.NOISE_SIMPLEX, libtcod.noise.TURBULENCE], - ['wavelet fbm', libtcod.NOISE_WAVELET, libtcod.noise.FBM], - ['wavelet turbulence', - libtcod.NOISE_WAVELET, libtcod.noise.TURBULENCE], - ] +NOISE_OPTIONS = [ # [name, algorithm, implementation], + ["perlin noise", libtcod.NOISE_PERLIN, libtcod.noise.SIMPLE], + ["simplex noise", libtcod.NOISE_SIMPLEX, libtcod.noise.SIMPLE], + ["wavelet noise", libtcod.NOISE_WAVELET, libtcod.noise.SIMPLE], + ["perlin fbm", libtcod.NOISE_PERLIN, libtcod.noise.FBM], + ["perlin turbulence", libtcod.NOISE_PERLIN, libtcod.noise.TURBULENCE], + ["simplex fbm", libtcod.NOISE_SIMPLEX, libtcod.noise.FBM], + ["simplex turbulence", libtcod.NOISE_SIMPLEX, libtcod.noise.TURBULENCE], + ["wavelet fbm", libtcod.NOISE_WAVELET, libtcod.noise.FBM], + ["wavelet turbulence", libtcod.NOISE_WAVELET, libtcod.noise.TURBULENCE], +] -class NoiseSample(Sample): +class NoiseSample(Sample): def __init__(self): - self.name = 'Noise' + self.name = "Noise" self.func = 0 self.dx = 0.0 self.dy = 0.0 @@ -303,8 +403,9 @@ def __init__(self): self.hurst = libtcod.NOISE_DEFAULT_HURST self.lacunarity = libtcod.NOISE_DEFAULT_LACUNARITY self.noise = self.get_noise() - self.img = libtcod.image_new(SAMPLE_SCREEN_WIDTH * 2, - SAMPLE_SCREEN_HEIGHT * 2) + self.img = libtcod.image_new( + SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2 + ) @property def algorithm(self): @@ -323,7 +424,7 @@ def get_noise(self): self.lacunarity, self.octaves, seed=None, - ) + ) def on_enter(self): libtcod.sys_set_fps(0) @@ -334,8 +435,10 @@ def on_draw(self, delta_time): self.dy += delta_time * 0.25 for y in range(2 * sample_console.height): for x in range(2 * sample_console.width): - f = [self.zoom * x / (2 * sample_console.width) + self.dx, - self.zoom * y / (2 * sample_console.height) + self.dy] + f = [ + self.zoom * x / (2 * sample_console.width) + self.dx, + self.zoom * y / (2 * sample_console.height) + self.dy, + ] value = self.noise.get_point(*f) c = int((value + 1.0) / 2.0 * 255) c = max(0, min(c, 255)) @@ -348,58 +451,63 @@ def on_draw(self, delta_time): self.img.blit_2x(sample_console, 0, 0) sample_console.default_bg = libtcod.grey sample_console.rect(2, 2, rectw, recth, False, libtcod.BKGND_MULTIPLY) - sample_console.fg[2:2+recth, 2:2+rectw] = \ - (sample_console.fg[2:2+recth, 2:2+rectw] * - sample_console.default_bg / 255) + sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] = ( + sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] + * sample_console.default_bg + / 255 + ) for curfunc in range(len(NOISE_OPTIONS)): - text = '%i : %s' % (curfunc + 1, NOISE_OPTIONS[curfunc][0]) + text = "%i : %s" % (curfunc + 1, NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: sample_console.default_fg = libtcod.white sample_console.default_bg = libtcod.light_blue - sample_console.print_(2, 2 + curfunc, text, - libtcod.BKGND_SET, libtcod.LEFT) + sample_console.print_( + 2, 2 + curfunc, text, libtcod.BKGND_SET, libtcod.LEFT + ) else: sample_console.default_fg = libtcod.grey sample_console.print_(2, 2 + curfunc, text) sample_console.default_fg = libtcod.white - sample_console.print_(2, 11, 'Y/H : zoom (%2.1f)' % self.zoom) + sample_console.print_(2, 11, "Y/H : zoom (%2.1f)" % self.zoom) if self.implementation != libtcod.noise.SIMPLE: - sample_console.print_(2, 12, 'E/D : hurst (%2.1f)' % self.hurst) - sample_console.print_(2, 13, - 'R/F : lacunarity (%2.1f)' % - self.lacunarity) - sample_console.print_(2, 14, - 'T/G : octaves (%2.1f)' % self.octaves) + sample_console.print_(2, 12, "E/D : hurst (%2.1f)" % self.hurst) + sample_console.print_( + 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity + ) + sample_console.print_( + 2, 14, "T/G : octaves (%2.1f)" % self.octaves + ) - def on_key(self, key): - if key.vk == libtcod.KEY_NONE: - return - if ord('9') >= key.c >= ord('1'): - self.func = key.c - ord('1') + def ev_keydown(self, event): + if ord("9") >= event.sym >= ord("1"): + self.func = event.sym - ord("1") self.noise = self.get_noise() - elif key.c in (ord('E'), ord('e')): + elif event.sym == ord("e"): self.hurst += 0.1 self.noise = self.get_noise() - elif key.c in (ord('D'), ord('d')): + elif event.sym == ord("d"): self.hurst -= 0.1 self.noise = self.get_noise() - elif key.c in (ord('R'), ord('r')): + elif event.sym == ord("r"): self.lacunarity += 0.5 self.noise = self.get_noise() - elif key.c in (ord('F'), ord('f')): + elif event.sym == ord("f"): self.lacunarity -= 0.5 self.noise = self.get_noise() - elif key.c in (ord('T'), ord('t')): + elif event.sym == ord("t"): self.octaves += 0.5 self.noise.octaves = self.octaves - elif key.c in (ord('G'), ord('g')): + elif event.sym == ord("g"): self.octaves -= 0.5 self.noise.octaves = self.octaves - elif key.c in (ord('Y'), ord('y')): + elif event.sym == ord("y"): self.zoom += 0.2 - elif key.c in (ord('H'), ord('h')): + elif event.sym == ord("h"): self.zoom -= 0.2 + else: + super().ev_keydown(event) + ############################################# # field of view sample @@ -410,53 +518,53 @@ def on_key(self, key): LIGHT_GROUND = libtcod.Color(200, 180, 50) SAMPLE_MAP = [ - '##############################################', - '####################### #################', - '##################### # ###############', - '###################### ### ###########', - '################## ##### ####', - '################ ######## ###### ####', - '############### #################### ####', - '################ ###### ##', - '######## ####### ###### # # # ##', - '######## ###### ### ##', - '######## ##', - '#### ###### ### # # # ##', - '#### ### ########## #### ##', - '#### ### ########## ###########=##########', - '#### ################## ##### #####', - '#### ### #### ##### #####', - '#### # #### #####', - '######## # #### ##### #####', - '######## ##### ####################', - '##############################################', - ] + "##############################################", + "####################### #################", + "##################### # ###############", + "###################### ### ###########", + "################## ##### ####", + "################ ######## ###### ####", + "############### #################### ####", + "################ ###### ##", + "######## ####### ###### # # # ##", + "######## ###### ### ##", + "######## ##", + "#### ###### ### # # # ##", + "#### ### ########## #### ##", + "#### ### ########## ###########=##########", + "#### ################## ##### #####", + "#### ### #### ##### #####", + "#### # #### #####", + "######## # #### ##### #####", + "######## ##### ####################", + "##############################################", +] SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP]) FOV_ALGO_NAMES = [ - 'BASIC ', - 'DIAMOND ', - 'SHADOW ', - 'PERMISSIVE0', - 'PERMISSIVE1', - 'PERMISSIVE2', - 'PERMISSIVE3', - 'PERMISSIVE4', - 'PERMISSIVE5', - 'PERMISSIVE6', - 'PERMISSIVE7', - 'PERMISSIVE8', - 'RESTRICTIVE', - ] + "BASIC ", + "DIAMOND ", + "SHADOW ", + "PERMISSIVE0", + "PERMISSIVE1", + "PERMISSIVE2", + "PERMISSIVE3", + "PERMISSIVE4", + "PERMISSIVE5", + "PERMISSIVE6", + "PERMISSIVE7", + "PERMISSIVE8", + "RESTRICTIVE", +] TORCH_RADIUS = 10 SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS -class FOVSample(Sample): +class FOVSample(Sample): def __init__(self): - self.name = 'Field of view' + self.name = "Field of view" self.px = 20 self.py = 10 @@ -471,28 +579,34 @@ def __init__(self): self.noise = libtcod.noise_new(1, 1.0, 1.0) self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) - self.map.walkable[:] = SAMPLE_MAP[:] == ' ' - self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == '=') + self.map.walkable[:] = SAMPLE_MAP[:] == " " + self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == "=") - self.light_map_bg = np.full(SAMPLE_MAP.shape + (3,), LIGHT_GROUND, - dtype=np.uint8) - self.light_map_bg[SAMPLE_MAP[:] == '#'] = LIGHT_WALL - self.dark_map_bg = np.full(SAMPLE_MAP.shape + (3,), DARK_GROUND, - dtype=np.uint8) - self.dark_map_bg[SAMPLE_MAP[:] == '#'] = DARK_WALL + self.light_map_bg = np.full( + SAMPLE_MAP.shape + (3,), LIGHT_GROUND, dtype=np.uint8 + ) + self.light_map_bg[SAMPLE_MAP[:] == "#"] = LIGHT_WALL + self.dark_map_bg = np.full( + SAMPLE_MAP.shape + (3,), DARK_GROUND, dtype=np.uint8 + ) + self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL def draw_ui(self): libtcod.console_set_default_foreground(sample_console, libtcod.white) libtcod.console_print( - sample_console, 1, 1, + sample_console, + 1, + 1, "IJKL : move around\n" "T : torch fx %s\n" "W : light walls %s\n" - "+-: algo %s" % ('on ' if self.torch else 'off', - 'on ' if self.light_walls else 'off', - FOV_ALGO_NAMES[self.algo_num], - ), - ) + "+-: algo %s" + % ( + "on " if self.torch else "off", + "on " if self.light_walls else "off", + FOV_ALGO_NAMES[self.algo_num], + ), + ) libtcod.console_set_default_foreground(sample_console, libtcod.black) def on_enter(self): @@ -503,11 +617,12 @@ def on_enter(self): # draw the help text & player @ libtcod.console_clear(sample_console) self.draw_ui() - libtcod.console_put_char(sample_console, self.px, self.py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + ) # draw windows - sample_console.ch[np.where(SAMPLE_MAP == '=')] = libtcod.CHAR_DHLINE - sample_console.fg[np.where(SAMPLE_MAP == '=')] = libtcod.black + sample_console.ch[np.where(SAMPLE_MAP == "=")] = libtcod.CHAR_DHLINE + sample_console.fg[np.where(SAMPLE_MAP == "=")] = libtcod.black def on_draw(self, delta_time): dx = 0.0 @@ -520,29 +635,34 @@ def on_draw(self, delta_time): self.py, TORCH_RADIUS if self.torch else 0, self.light_walls, - self.algo_num - ) + self.algo_num, + ) sample_console.bg[:] = self.dark_map_bg[:] if self.torch: # slightly change the perlin noise parameter self.torchx += 0.1 # randomize the light position between -1.5 and 1.5 tdx = [self.torchx + 20.0] - dx = libtcod.noise_get(self.noise, tdx, - libtcod.NOISE_SIMPLEX) * 1.5 + dx = ( + libtcod.noise_get(self.noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 + ) tdx[0] += 30.0 - dy = libtcod.noise_get(self.noise, tdx, - libtcod.NOISE_SIMPLEX) * 1.5 - di = 0.2 * libtcod.noise_get(self.noise, [self.torchx], - libtcod.NOISE_SIMPLEX) - #where_fov = np.where(self.map.fov[:]) + dy = ( + libtcod.noise_get(self.noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 + ) + di = 0.2 * libtcod.noise_get( + self.noise, [self.torchx], libtcod.NOISE_SIMPLEX + ) + # where_fov = np.where(self.map.fov[:]) mgrid = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH] # get squared distance - light = ((mgrid[0] - self.py + dy) ** 2 + - (mgrid[1] - self.px + dx) ** 2) + light = (mgrid[0] - self.py + dy) ** 2 + ( + mgrid[1] - self.px + dx + ) ** 2 light = light.astype(np.float16) - where_visible = np.where((light < SQUARED_TORCH_RADIUS) & - self.map.fov[:]) + where_visible = np.where( + (light < SQUARED_TORCH_RADIUS) & self.map.fov[:] + ) light[where_visible] = SQUARED_TORCH_RADIUS - light[where_visible] light[where_visible] /= SQUARED_TORCH_RADIUS light[where_visible] += di @@ -553,51 +673,56 @@ def on_draw(self, delta_time): tuple(self.dark_map_bg[yx]), tuple(self.light_map_bg[yx]), light[yx], - ) + ) else: where_fov = np.where(self.map.fov[:]) sample_console.bg[where_fov] = self.light_map_bg[where_fov] - - def on_key(self, key): + def ev_keydown(self, event): MOVE_KEYS = { - ord('i'): (0, -1), - ord('j'): (-1, 0), - ord('k'): (0, 1), - ord('l'): (1, 0), + ord("i"): (0, -1), + ord("j"): (-1, 0), + ord("k"): (0, 1), + ord("l"): (1, 0), } - FOV_SELECT_KEYS = {ord('-'): -1, ord('='): 1} - if key.c in MOVE_KEYS: - x, y = MOVE_KEYS[key.c] - if SAMPLE_MAP[self.py + y][self.px + x] == ' ': - libtcod.console_put_char(sample_console, self.px, self.py, ' ', - libtcod.BKGND_NONE) + FOV_SELECT_KEYS = {ord("-"): -1, ord("="): 1} + if event.sym in MOVE_KEYS: + x, y = MOVE_KEYS[event.sym] + if SAMPLE_MAP[self.py + y][self.px + x] == " ": + libtcod.console_put_char( + sample_console, self.px, self.py, " ", libtcod.BKGND_NONE + ) self.px += x self.py += y - libtcod.console_put_char(sample_console, self.px, self.py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + ) self.recompute = True - elif key.c == ord('t'): + elif event.sym == ord("t"): self.torch = not self.torch self.draw_ui() self.recompute = True - elif key.c == ord('w'): + elif event.sym == ord("w"): self.light_walls = not self.light_walls self.draw_ui() self.recompute = True - elif key.c in FOV_SELECT_KEYS: - self.algo_num += FOV_SELECT_KEYS[key.c] + elif event.sym in FOV_SELECT_KEYS: + self.algo_num += FOV_SELECT_KEYS[event.sym] self.algo_num %= libtcod.NB_FOV_ALGORITHMS self.draw_ui() self.recompute = True + else: + super().ev_keydown(event) + ############################################# # pathfinding sample ############################################# + class PathfindingSample(Sample): def __init__(self): - self.name = 'Path finding' + self.name = "Path finding" self.px = 20 self.py = 10 @@ -610,15 +735,15 @@ def __init__(self): self.dijk = None self.recalculate = False self.busy = 0.0 - self.oldchar = ' ' + self.oldchar = " " self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == ' ': + if SAMPLE_MAP[y][x] == " ": # ground libtcod.map_set_properties(self.map, x, y, True, True) - elif SAMPLE_MAP[y][x] == '=': + elif SAMPLE_MAP[y][x] == "=": # window libtcod.map_set_properties(self.map, x, y, True, False) self.path = libtcod.path_new_using_map(self.map) @@ -632,28 +757,38 @@ def on_enter(self): # draw the help text & player @ libtcod.console_clear(sample_console) libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - libtcod.console_put_char(sample_console, self.px, self.py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + libtcod.console_put_char( + sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + ) libtcod.console_print( - sample_console, 1, 1, - "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra") + sample_console, + 1, + 1, + "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", + ) libtcod.console_print(sample_console, 1, 4, "Using : A*") # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == '=': - libtcod.console_put_char(sample_console, x, y, - libtcod.CHAR_DHLINE, - libtcod.BKGND_NONE) + if SAMPLE_MAP[y][x] == "=": + libtcod.console_put_char( + sample_console, + x, + y, + libtcod.CHAR_DHLINE, + libtcod.BKGND_NONE, + ) self.recalculate = True def on_draw(self, delta_time): if self.recalculate: if self.using_astar: - libtcod.path_compute(self.path, self.px, self.py, - self.dx, self.dy) + libtcod.path_compute( + self.path, self.px, self.py, self.dx, self.dy + ) else: self.dijk_dist = 0.0 # compute dijkstra grid (distance from px,py) @@ -671,35 +806,45 @@ def on_draw(self, delta_time): # draw the dungeon for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == '#': + if SAMPLE_MAP[y][x] == "#": libtcod.console_set_char_background( - sample_console, x, y, DARK_WALL, libtcod.BKGND_SET) + sample_console, x, y, DARK_WALL, libtcod.BKGND_SET + ) else: libtcod.console_set_char_background( - sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET) + sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET + ) # draw the path if self.using_astar: for i in range(libtcod.path_size(self.path)): x, y = libtcod.path_get(self.path, i) libtcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET) + sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET + ) else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] != '#': + if SAMPLE_MAP[y][x] != "#": libtcod.console_set_char_background( - sample_console, x, y, + sample_console, + x, + y, libtcod.color_lerp( - LIGHT_GROUND, DARK_GROUND, - 0.9* libtcod.dijkstra_get_distance(self.dijk, - x, y) - / self.dijk_dist), + LIGHT_GROUND, + DARK_GROUND, + 0.9 + * libtcod.dijkstra_get_distance( + self.dijk, x, y + ) + / self.dijk_dist, + ), libtcod.BKGND_SET, - ) + ) for i in range(libtcod.dijkstra_size(self.dijk)): x, y = libtcod.dijkstra_get(self.dijk, i) libtcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET) + sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET + ) # move the creature self.busy -= libtcod.sys_get_last_frame_length() @@ -707,91 +852,150 @@ def on_draw(self, delta_time): self.busy = 0.2 if self.using_astar: if not libtcod.path_is_empty(self.path): - libtcod.console_put_char(sample_console, self.px, self.py, - ' ', libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.px, + self.py, + " ", + libtcod.BKGND_NONE, + ) self.px, self.py = libtcod.path_walk(self.path, True) - libtcod.console_put_char(sample_console, self.px, self.py, - '@', libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.px, + self.py, + "@", + libtcod.BKGND_NONE, + ) else: if not libtcod.dijkstra_is_empty(self.dijk): - libtcod.console_put_char(sample_console, self.px, self.py, - ' ', libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.px, + self.py, + " ", + libtcod.BKGND_NONE, + ) self.px, self.py = libtcod.dijkstra_path_walk(self.dijk) - libtcod.console_put_char(sample_console, self.px, self.py, - '@', libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.px, + self.py, + "@", + libtcod.BKGND_NONE, + ) self.recalculate = True - def on_key(self, key): - if key.c in (ord('I'), ord('i')) and self.dy > 0: + def ev_keydown(self, event): + if event.sym == ord("i") and self.dy > 0: # destination move north - libtcod.console_put_char(sample_console, self.dx, self.dy, - self.oldchar, libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.dx, + self.dy, + self.oldchar, + libtcod.BKGND_NONE, + ) self.dy -= 1 - self.oldchar = libtcod.console_get_char(sample_console, self.dx, - self.dy) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - if SAMPLE_MAP[self.dy][self.dx] == ' ': + self.oldchar = libtcod.console_get_char( + sample_console, self.dx, self.dy + ) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True - elif (key.c in (ord('K'), ord('k')) - and self.dy < SAMPLE_SCREEN_HEIGHT - 1): + elif event.sym == ord("k") and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - libtcod.console_put_char(sample_console, self.dx, self.dy, - self.oldchar, libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.dx, + self.dy, + self.oldchar, + libtcod.BKGND_NONE, + ) self.dy += 1 - self.oldchar = libtcod.console_get_char(sample_console, self.dx, - self.dy) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - if SAMPLE_MAP[self.dy][self.dx] == ' ': + self.oldchar = libtcod.console_get_char( + sample_console, self.dx, self.dy + ) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True - elif key.c in (ord('J'), ord('j')) and self.dx > 0: + elif event.sym == ord("j") and self.dx > 0: # destination move west - libtcod.console_put_char(sample_console, self.dx, self.dy, - self.oldchar, libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.dx, + self.dy, + self.oldchar, + libtcod.BKGND_NONE, + ) self.dx -= 1 - self.oldchar = libtcod.console_get_char(sample_console, self.dx, - self.dy) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - if SAMPLE_MAP[self.dy][self.dx] == ' ': + self.oldchar = libtcod.console_get_char( + sample_console, self.dx, self.dy + ) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True - elif (key.c in (ord('L'), ord('l')) and - self.dx < SAMPLE_SCREEN_WIDTH - 1): + elif event.sym == ord("l") and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - libtcod.console_put_char(sample_console, self.dx, self.dy, - self.oldchar, libtcod.BKGND_NONE) + libtcod.console_put_char( + sample_console, + self.dx, + self.dy, + self.oldchar, + libtcod.BKGND_NONE, + ) self.dx += 1 - self.oldchar = libtcod.console_get_char(sample_console, self.dx, - self.dy) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - if SAMPLE_MAP[self.dy][self.dx] == ' ': + self.oldchar = libtcod.console_get_char( + sample_console, self.dx, self.dy + ) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True - elif key.vk == libtcod.KEY_TAB: + elif event.sym == tcod.event.K_TAB: self.using_astar = not self.using_astar if self.using_astar: libtcod.console_print(sample_console, 1, 4, "Using : A* ") else: libtcod.console_print(sample_console, 1, 4, "Using : Dijkstra") self.recalculate = True - - def on_mouse(self, mouse): - mx = mouse.cx - SAMPLE_SCREEN_X - my = mouse.cy - SAMPLE_SCREEN_Y - if (0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT - and (self.dx != mx or self.dy != my)): - libtcod.console_put_char(sample_console, self.dx, self.dy, - self.oldchar, libtcod.BKGND_NONE) + else: + super().ev_keydown(event) + + def ev_mousemotion(self, event): + mx = event.tile.x - SAMPLE_SCREEN_X + my = event.tile.y - SAMPLE_SCREEN_Y + if ( + 0 <= mx < SAMPLE_SCREEN_WIDTH + and 0 <= my < SAMPLE_SCREEN_HEIGHT + and (self.dx != mx or self.dy != my) + ): + libtcod.console_put_char( + sample_console, + self.dx, + self.dy, + self.oldchar, + libtcod.BKGND_NONE, + ) self.dx = mx self.dy = my - self.oldchar = libtcod.console_get_char(sample_console, self.dx, - self.dy) - libtcod.console_put_char(sample_console, self.dx, self.dy, '+', - libtcod.BKGND_NONE) - if SAMPLE_MAP[self.dy][self.dx] == ' ': + self.oldchar = libtcod.console_get_char( + sample_console, self.dx, self.dy + ) + libtcod.console_put_char( + sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + ) + if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True + ############################################# # bsp sample ############################################# @@ -809,18 +1013,21 @@ def vline(m, x, y1, y2): for y in range(y1, y2 + 1): m[x][y] = True + # draw a vertical line up until we reach an empty space def vline_up(m, x, y): while y >= 0 and not m[x][y]: m[x][y] = True y -= 1 + # draw a vertical line down until we reach an empty space def vline_down(m, x, y): while y < SAMPLE_SCREEN_HEIGHT and not m[x][y]: m[x][y] = True y += 1 + # draw a horizontal line def hline(m, x1, y, x2): if x1 > x2: @@ -828,22 +1035,25 @@ def hline(m, x1, y, x2): for x in range(x1, x2 + 1): m[x][y] = True + # draw a horizontal line left until we reach an empty space def hline_left(m, x, y): while x >= 0 and not m[x][y]: m[x][y] = True x -= 1 + # draw a horizontal line right until we reach an empty space def hline_right(m, x, y): while x < SAMPLE_SCREEN_WIDTH and not m[x][y]: m[x][y] = True x += 1 + # the class building the dungeon from the bsp nodes def traverse_node(node, *dat): global bsp_map - if libtcod.bsp_is_leaf(node): + if not node.children: # calculate the room size minx = node.x + 1 maxx = node.x + node.w - 1 @@ -859,14 +1069,18 @@ def traverse_node(node, *dat): if maxy == SAMPLE_SCREEN_HEIGHT - 1: maxy -= 1 if bsp_random_room: - minx = libtcod.random_get_int(None, - minx, maxx - bsp_min_room_size + 1) - miny = libtcod.random_get_int(None, - miny, maxy - bsp_min_room_size + 1) - maxx = libtcod.random_get_int(None, - minx + bsp_min_room_size - 1, maxx) - maxy = libtcod.random_get_int(None, - miny + bsp_min_room_size - 1, maxy) + minx = libtcod.random_get_int( + None, minx, maxx - bsp_min_room_size + 1 + ) + miny = libtcod.random_get_int( + None, miny, maxy - bsp_min_room_size + 1 + ) + maxx = libtcod.random_get_int( + None, minx + bsp_min_room_size - 1, maxx + ) + maxy = libtcod.random_get_int( + None, miny + bsp_min_room_size - 1, maxy + ) # resize the node to fit the room node.x = minx node.y = miny @@ -878,8 +1092,7 @@ def traverse_node(node, *dat): bsp_map[x][y] = True else: # resize the node to fit its sons - left = libtcod.bsp_left(node) - right = libtcod.bsp_right(node) + left, right = node.children node.x = min(left.x, right.x) node.y = min(left.y, right.y) node.w = max(left.x + left.w, right.x + right.w) - node.x @@ -890,8 +1103,9 @@ def traverse_node(node, *dat): if left.x + left.w - 1 < right.x or right.x + right.w - 1 < left.x: # no overlapping zone. we need a Z shaped corridor x1 = libtcod.random_get_int(None, left.x, left.x + left.w - 1) - x2 = libtcod.random_get_int(None, - right.x, right.x + right.w - 1) + x2 = libtcod.random_get_int( + None, right.x, right.x + right.w - 1 + ) y = libtcod.random_get_int(None, left.y + left.h, right.y) vline_up(bsp_map, x1, y - 1) hline(bsp_map, x1, y, x2) @@ -908,8 +1122,9 @@ def traverse_node(node, *dat): if left.y + left.h - 1 < right.y or right.y + right.h - 1 < left.y: # no overlapping zone. we need a Z shaped corridor y1 = libtcod.random_get_int(None, left.y, left.y + left.h - 1) - y2 = libtcod.random_get_int(None, - right.y, right.y + right.h - 1) + y2 = libtcod.random_get_int( + None, right.y, right.y + right.h - 1 + ) x = libtcod.random_get_int(None, left.x + left.w, right.x) hline_left(bsp_map, x - 1, y1) vline(bsp_map, x, y1, y2) @@ -923,12 +1138,15 @@ def traverse_node(node, *dat): hline_right(bsp_map, right.x, y) return True + bsp = None bsp_generate = True bsp_refresh = False + + class BSPSample(Sample): def __init__(self): - self.name = 'Bsp toolkit' + self.name = "Bsp toolkit" def on_draw(self, delta_time): global bsp, bsp_generate, bsp_refresh, bsp_map @@ -937,98 +1155,122 @@ def on_draw(self, delta_time): # dungeon generation if bsp is None: # create the bsp - bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT) + bsp = libtcod.bsp_new_with_size( + 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT + ) else: # restore the nodes size - libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT) + bsp.x = 0 + bsp.y = 0 + bsp.width = SAMPLE_SCREEN_WIDTH + bsp.height = SAMPLE_SCREEN_HEIGHT bsp_map = list() for x in range(SAMPLE_SCREEN_WIDTH): bsp_map.append([False] * SAMPLE_SCREEN_HEIGHT) if bsp_generate: # build a new random bsp tree - libtcod.bsp_remove_sons(bsp) + bsp.children = () if bsp_room_walls: - libtcod.bsp_split_recursive(bsp, 0, bsp_depth, - bsp_min_room_size + 1, - bsp_min_room_size + 1, 1.5, 1.5) + libtcod.bsp_split_recursive( + bsp, + 0, + bsp_depth, + bsp_min_room_size + 1, + bsp_min_room_size + 1, + 1.5, + 1.5, + ) else: - libtcod.bsp_split_recursive(bsp, 0, bsp_depth, - bsp_min_room_size, - bsp_min_room_size, 1.5, 1.5) + libtcod.bsp_split_recursive( + bsp, + 0, + bsp_depth, + bsp_min_room_size, + bsp_min_room_size, + 1.5, + 1.5, + ) # create the dungeon from the bsp - libtcod.bsp_traverse_inverted_level_order(bsp, traverse_node) + for node in bsp.inverted_level_order(): + traverse_node(node) bsp_generate = False bsp_refresh = False - libtcod.console_clear(sample_console) - libtcod.console_set_default_foreground(sample_console, libtcod.white) - rooms = 'OFF' + sample_console.clear() + sample_console.default_fg = libtcod.white + rooms = "OFF" if bsp_random_room: - rooms = 'ON' - libtcod.console_print(sample_console, 1, 1, - "ENTER : rebuild bsp\n" - "SPACE : rebuild dungeon\n" - "+-: bsp depth %d\n" - "*/: room size %d\n" - "1 : random room size %s" - % (bsp_depth, bsp_min_room_size, rooms)) + rooms = "ON" + sample_console.print_( + 1, + 1, + "ENTER : rebuild bsp\n" + "SPACE : rebuild dungeon\n" + "+-: bsp depth %d\n" + "*/: room size %d\n" + "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), + ) if bsp_random_room: - walls = 'OFF' + walls = "OFF" if bsp_room_walls: - walls = 'ON' - libtcod.console_print(sample_console, 1, 6, - '2 : room walls %s' % walls) + walls = "ON" + sample_console.print_(1, 6, "2 : room walls %s" % walls) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if not bsp_map[x][y]: libtcod.console_set_char_background( - sample_console, x, y, DARK_WALL, libtcod.BKGND_SET) + sample_console, x, y, DARK_WALL, libtcod.BKGND_SET + ) else: libtcod.console_set_char_background( - sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET) + sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET + ) - def on_key(self, key): + def ev_keydown(self, event): global bsp, bsp_generate, bsp_refresh, bsp_map global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size - if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER): + if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): bsp_generate = True - elif key.c == ord(' '): + elif event.sym == ord(" "): bsp_refresh = True - elif key.c == ord('='): + elif event.sym == ord("="): bsp_depth += 1 bsp_generate = True - elif key.c == ord('-') and bsp_depth > 1: + elif event.sym == ord("-") and bsp_depth > 1: bsp_depth -= 1 bsp_generate = True - elif key.c == ord('*'): + elif event.sym == ord("*"): bsp_min_room_size += 1 bsp_generate = True - elif key.c == ord('/') and bsp_min_room_size > 2: + elif event.sym == ord("/") and bsp_min_room_size > 2: bsp_min_room_size -= 1 bsp_generate = True - elif key.c == ord('1') or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1): + elif event.sym in (tcod.event.K_1, tcod.event.K_KP_1): bsp_random_room = not bsp_random_room if not bsp_random_room: bsp_room_walls = True bsp_refresh = True - elif key.c == ord('2') or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2): + elif event.sym in (tcod.event.K_2, tcod.event.K_KP_2): bsp_room_walls = not bsp_room_walls bsp_refresh = True + else: + super().ev_keydown(event) + ############################################# # image sample ############################################# img_blue = libtcod.Color(0, 0, 255) img_green = libtcod.Color(0, 255, 0) + + class ImageSample(Sample): def __init__(self): - self.name = 'Image toolkit' + self.name = "Image toolkit" - self.img = libtcod.image_load('data/img/skull.png') + self.img = libtcod.image_load("data/img/skull.png") self.img.set_key_color(libtcod.black) - self.circle = libtcod.image_load('data/img/circle.png') + self.circle = libtcod.image_load("data/img/circle.png") def on_enter(self): libtcod.sys_set_fps(0) @@ -1036,108 +1278,130 @@ def on_enter(self): def on_draw(self, delta_time): sample_console.default_bg = libtcod.black sample_console.clear() - x = (sample_console.width / 2 - + math.cos(libtcod.sys_elapsed_seconds()) * 10.0) - y = float(sample_console.height / 2) - scalex = (0.2 + 1.8 - * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0) + x = sample_console.width / 2 + math.cos(time.time()) * 10.0 + y = sample_console.height / 2 + scalex = 0.2 + 1.8 * (1.0 + math.cos(time.time() / 2)) / 2.0 scaley = scalex - angle = libtcod.sys_elapsed_seconds() - elapsed = libtcod.sys_elapsed_milli() // 2000 - if elapsed & 1 != 0: + angle = time.perf_counter() + if int(time.time() / 2): # split the color channels of circle.png # the red channel sample_console.default_bg = libtcod.red sample_console.rect(0, 3, 15, 15, False, libtcod.BKGND_SET) - self.circle.blit_rect(sample_console, 0, 3, -1, -1, - libtcod.BKGND_MULTIPLY) + self.circle.blit_rect( + sample_console, 0, 3, -1, -1, libtcod.BKGND_MULTIPLY + ) # the green channel sample_console.default_bg = img_green sample_console.rect(15, 3, 15, 15, False, libtcod.BKGND_SET) - self.circle.blit_rect(sample_console, - 15, 3, -1, -1, libtcod.BKGND_MULTIPLY) + self.circle.blit_rect( + sample_console, 15, 3, -1, -1, libtcod.BKGND_MULTIPLY + ) # the blue channel sample_console.default_bg = img_blue sample_console.rect(30, 3, 15, 15, False, libtcod.BKGND_SET) - self.circle.blit_rect(sample_console, - 30, 3, -1, -1, libtcod.BKGND_MULTIPLY) + self.circle.blit_rect( + sample_console, 30, 3, -1, -1, libtcod.BKGND_MULTIPLY + ) else: # render circle.png with normal blitting - self.circle.blit_rect(sample_console, - 0, 3, -1, -1, libtcod.BKGND_SET) - self.circle.blit_rect(sample_console, - 15, 3, -1, -1, libtcod.BKGND_SET) - self.circle.blit_rect(sample_console, - 30, 3, -1, -1, libtcod.BKGND_SET) - self.img.blit(sample_console, x, y, - libtcod.BKGND_SET, scalex, scaley, angle) + self.circle.blit_rect( + sample_console, 0, 3, -1, -1, libtcod.BKGND_SET + ) + self.circle.blit_rect( + sample_console, 15, 3, -1, -1, libtcod.BKGND_SET + ) + self.circle.blit_rect( + sample_console, 30, 3, -1, -1, libtcod.BKGND_SET + ) + self.img.blit( + sample_console, x, y, libtcod.BKGND_SET, scalex, scaley, angle + ) + ############################################# # mouse sample ############################################# -butstatus = ('OFF', 'ON') +butstatus = ("OFF", "ON") + class MouseSample(Sample): def __init__(self): - self.name = 'Mouse support' + self.name = "Mouse support" + self.motion = tcod.event.MouseMotion() self.lbut = self.mbut = self.rbut = 0 + self.log = [] def on_enter(self): - libtcod.console_set_default_background(sample_console, libtcod.grey) - libtcod.console_set_default_foreground(sample_console, - libtcod.light_yellow) + sample_console.default_bg = libtcod.grey + sample_console.default_fg = libtcod.light_yellow libtcod.mouse_move(320, 200) libtcod.mouse_show_cursor(True) libtcod.sys_set_fps(60) - def on_mouse(self, mouse): - libtcod.console_clear(sample_console) - if mouse.lbutton_pressed: - self.lbut = not self.lbut - if mouse.rbutton_pressed: - self.rbut = not self.rbut - if mouse.mbutton_pressed: - self.mbut = not self.mbut - wheel = "" - if mouse.wheel_up: - wheel = "UP" - elif mouse.wheel_down: - wheel = "DOWN" + def ev_mousemotion(self, event): + self.motion = event + + def ev_mousebuttondown(self, event): + if event.button == tcod.event.BUTTON_LEFT: + self.lbut = True + elif event.button == tcod.event.BUTTON_MIDDLE: + self.mbut = True + elif event.button == tcod.event.BUTTON_RIGHT: + self.rbut = True + + def ev_mousebuttonup(self, event): + if event.button == tcod.event.BUTTON_LEFT: + self.lbut = False + elif event.button == tcod.event.BUTTON_MIDDLE: + self.mbut = False + elif event.button == tcod.event.BUTTON_RIGHT: + self.rbut = False + + def on_draw(self, delta_time): + sample_console.clear() sample_console.print_( - 1, 1, + 1, + 1, "Mouse position : %4dx%4d\n" "Mouse cell : %4dx%4d\n" "Mouse movement : %4dx%4d\n" - "Left button : %s (toggle %s)\n" - "Right button : %s (toggle %s)\n" - "Middle button : %s (toggle %s)\n" - "Wheel : %s" - % (mouse.x, mouse.y, - mouse.cx, mouse.cy, - mouse.dx, mouse.dy, - butstatus[mouse.lbutton], butstatus[self.lbut], - butstatus[mouse.rbutton], butstatus[self.rbut], - butstatus[mouse.mbutton], butstatus[self.mbut], - wheel, - ) - ) + "Left button : %s\n" + "Right button : %s\n" + "Middle button : %s\n" + % ( + self.motion.pixel.x, + self.motion.pixel.y, + self.motion.tile.x, + self.motion.tile.y, + self.motion.tile_motion.x, + self.motion.tile_motion.y, + butstatus[self.lbut], + butstatus[self.rbut], + butstatus[self.mbut], + ), + ) sample_console.print_(1, 10, "1 : Hide cursor\n2 : Show cursor") - def on_key(self, key): - if key.c == ord('1'): + def ev_keydown(self, event): + if event.sym == ord("1"): libtcod.mouse_show_cursor(False) - elif key.c == ord('2'): + elif event.sym == ord("2"): libtcod.mouse_show_cursor(True) + else: + super().ev_keydown(event) + ############################################# # name generator sample ############################################# + class NameGeneratorSample(Sample): def __init__(self): - self.name = 'Name generator' + self.name = "Name generator" self.curset = 0 self.nbsets = 0 @@ -1151,213 +1415,258 @@ def on_enter(self): def on_draw(self, delta_time): if self.nbsets == 0: # parse all *.cfg files in data/namegen - for file in os.listdir(b'data/namegen'): - if file.find(b'.cfg') > 0: + for file in os.listdir(b"data/namegen"): + if file.find(b".cfg") > 0: libtcod.namegen_parse( - os.path.join(b'data', b'namegen', file)) + os.path.join(b"data", b"namegen", file) + ) # get the sets list self.sets = libtcod.namegen_get_sets() print(self.sets) self.nbsets = len(self.sets) while len(self.names) > 15: self.names.pop(0) - libtcod.console_clear(sample_console) - libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, 1, - "%s\n\n+ : next generator\n- : prev generator" - % self.sets[self.curset]) + sample_console.clear() + sample_console.default_fg = libtcod.white + sample_console.print_( + 1, + 1, + "%s\n\n+ : next generator\n- : prev generator" + % self.sets[self.curset], + ) for i in range(len(self.names)): - libtcod.console_print_ex( - sample_console, SAMPLE_SCREEN_WIDTH-2, 2+i, - libtcod.BKGND_NONE, libtcod.RIGHT, self.names[i]) + sample_console.print_( + SAMPLE_SCREEN_WIDTH - 2, + 2 + i, + self.names[i], + libtcod.BKGND_NONE, + libtcod.RIGHT, + ) self.delay += libtcod.sys_get_last_frame_length() if self.delay > 0.5: self.delay -= 0.5 self.names.append(libtcod.namegen_generate(self.sets[self.curset])) - def on_key(self, key): - if key.c == ord('='): + def ev_keydown(self, event): + if event.sym == ord("="): self.curset += 1 if self.curset == self.nbsets: self.curset = 0 self.names.append("======") - elif key.c == ord('-'): + elif event.sym == ord("-"): self.curset -= 1 if self.curset < 0: - self.curset = self.nbsets-1 + self.curset = self.nbsets - 1 self.names.append("======") + else: + super().ev_keydown(event) + ############################################# # python fast render sample ############################################# numpy_available = True -use_numpy = numpy_available #default option +use_numpy = numpy_available # default option SCREEN_W = SAMPLE_SCREEN_WIDTH SCREEN_H = SAMPLE_SCREEN_HEIGHT HALF_W = SCREEN_W // 2 HALF_H = SCREEN_H // 2 -RES_U = 80 #texture resolution +RES_U = 80 # texture resolution RES_V = 80 -TEX_STRETCH = 5 #texture stretching with tunnel depth +TEX_STRETCH = 5 # texture stretching with tunnel depth SPEED = 15 -LIGHT_BRIGHTNESS = 3.5 #brightness multiplier for all lights (changes their radius) -LIGHTS_CHANCE = 0.07 #chance of a light appearing +LIGHT_BRIGHTNESS = ( + 3.5 +) # brightness multiplier for all lights (changes their radius) +LIGHTS_CHANCE = 0.07 # chance of a light appearing MAX_LIGHTS = 6 MIN_LIGHT_STRENGTH = 0.2 -LIGHT_UPDATE = 0.05 #how much the ambient light changes to reflect current light sources -AMBIENT_LIGHT = 0.8 #brightness of tunnel texture - -#the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen) -#xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] -#yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] +LIGHT_UPDATE = ( + 0.05 +) # how much the ambient light changes to reflect current light sources +AMBIENT_LIGHT = 0.8 # brightness of tunnel texture + +# the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen) +# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] +# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] if numpy_available: (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) - #translate coordinates of all pixels to center + # translate coordinates of all pixels to center xc = xc - HALF_W yc = yc - HALF_H noise2d = libtcod.noise_new(2, 0.5, 2.0) -if numpy_available: #the texture starts empty +if numpy_available: # the texture starts empty texture = np.zeros((RES_U, RES_V)) + class Light: def __init__(self, x, y, z, r, g, b, strength): - self.x, self.y, self.z = x, y, z #pos. - self.r, self.g, self.b = r, g, b #color - self.strength = strength #between 0 and 1, defines brightness + self.x, self.y, self.z = x, y, z # pos. + self.r, self.g, self.b = r, g, b # color + self.strength = strength # between 0 and 1, defines brightness + class FastRenderSample(Sample): def __init__(self): - self.name = 'Python fast render' + self.name = "Python fast render" def on_enter(self): global frac_t, abs_t, lights, tex_r, tex_g, tex_b libtcod.sys_set_fps(0) - libtcod.console_clear(sample_console) #render status message - libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, SCREEN_H - 3, - "Renderer: NumPy") + sample_console.clear() # render status message + sample_console.default_fg = libtcod.white + sample_console.print_(1, SCREEN_H - 3, "Renderer: NumPy") - frac_t = RES_V - 1 #time is represented in number of pixels of the texture, start later in time to initialize texture + frac_t = ( + RES_V - 1 + ) # time is represented in number of pixels of the texture, start later in time to initialize texture abs_t = RES_V - 1 - lights = [] #lights list, and current color of the tunnel texture + lights = [] # lights list, and current color of the tunnel texture tex_r, tex_g, tex_b = 0, 0, 0 def on_draw(self, delta_time): global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2 - time_delta = libtcod.sys_get_last_frame_length() * SPEED #advance time - frac_t += time_delta #increase fractional (always < 1.0) time - abs_t += time_delta #increase absolute elapsed time - int_t = int(frac_t) #integer time units that passed this frame (number of texture pixels to advance) - frac_t -= int_t #keep this < 1.0 - - #change texture color according to presence of lights (basically, sum them - #to get ambient light and smoothly change the current color into that) - ambient_r = (AMBIENT_LIGHT - * sum(light.r * light.strength for light in lights)) - ambient_g = (AMBIENT_LIGHT - * sum(light.g * light.strength for light in lights)) - ambient_b = (AMBIENT_LIGHT - * sum(light.b * light.strength for light in lights)) + time_delta = ( + libtcod.sys_get_last_frame_length() * SPEED + ) # advance time + frac_t += time_delta # increase fractional (always < 1.0) time + abs_t += time_delta # increase absolute elapsed time + int_t = int( + frac_t + ) # integer time units that passed this frame (number of texture pixels to advance) + frac_t -= int_t # keep this < 1.0 + + # change texture color according to presence of lights (basically, sum them + # to get ambient light and smoothly change the current color into that) + ambient_r = AMBIENT_LIGHT * sum( + light.r * light.strength for light in lights + ) + ambient_g = AMBIENT_LIGHT * sum( + light.g * light.strength for light in lights + ) + ambient_b = AMBIENT_LIGHT * sum( + light.b * light.strength for light in lights + ) alpha = LIGHT_UPDATE * time_delta tex_r = tex_r * (1 - alpha) + ambient_r * alpha tex_g = tex_g * (1 - alpha) + ambient_g * alpha tex_b = tex_b * (1 - alpha) + ambient_b * alpha - if int_t >= 1: #roll texture (ie, advance in tunnel) according to int_t - int_t = int_t % RES_V #can't roll more than the texture's size (can happen when time_delta is large) - int_abs_t = int(abs_t) #new pixels are based on absolute elapsed time + if ( + int_t >= 1 + ): # roll texture (ie, advance in tunnel) according to int_t + int_t = ( + int_t % RES_V + ) # can't roll more than the texture's size (can happen when time_delta is large) + int_abs_t = int( + abs_t + ) # new pixels are based on absolute elapsed time texture = np.roll(texture, -int_t, 1) - #replace new stretch of texture with new values + # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(0, RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u, v] = ( - libtcod.noise_get_fbm( - noise2d, [u/float(RES_U), tex_v], 32.0) - + libtcod.noise_get_fbm( - noise2d, [1 - u/float(RES_U), tex_v], 32.0) - ) + texture[u, v] = libtcod.noise_get_fbm( + noise2d, [u / float(RES_U), tex_v], 32.0 + ) + libtcod.noise_get_fbm( + noise2d, [1 - u / float(RES_U), tex_v], 32.0 + ) # squared distance from center, # clipped to sensible minimum and maximum values sqr_dist = xc ** 2 + yc ** 2 sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2) - #one coordinate into the texture, represents depth in the tunnel + # one coordinate into the texture, represents depth in the tunnel v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t v = v.clip(0, RES_V - 1) - #another coordinate, represents rotation around the tunnel + # another coordinate, represents rotation around the tunnel u = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U) - #retrieve corresponding pixels from texture + # retrieve corresponding pixels from texture brightness = texture[u.astype(int), v.astype(int)] / 4.0 + 0.5 - #use the brightness map to compose the final color of the tunnel + # use the brightness map to compose the final color of the tunnel R = brightness * tex_r G = brightness * tex_g B = brightness * tex_b - #create new light source - if (libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE and - len(lights) < MAX_LIGHTS): + # create new light source + if ( + libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE + and len(lights) < MAX_LIGHTS + ): x = libtcod.random_get_float(0, -0.5, 0.5) y = libtcod.random_get_float(0, -0.5, 0.5) strength = libtcod.random_get_float(0, MIN_LIGHT_STRENGTH, 1.0) - color = libtcod.Color(0, 0, 0) #create bright colors with random hue + color = libtcod.Color( + 0, 0, 0 + ) # create bright colors with random hue hue = libtcod.random_get_float(0, 0, 360) libtcod.color_set_hsv(color, hue, 0.5, strength) - lights.append(Light(x, y, TEX_STRETCH, - color.r, color.g, color.b, strength)) + lights.append( + Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength) + ) - #eliminate lights that are going to be out of view - lights = [light for light in lights - if light.z - time_delta > 1.0 / RES_V] + # eliminate lights that are going to be out of view + lights = [ + light for light in lights if light.z - time_delta > 1.0 / RES_V + ] - for light in lights: #render lights - #move light's Z coordinate with time, then project its XYZ coordinates to screen-space + for light in lights: # render lights + # move light's Z coordinate with time, then project its XYZ coordinates to screen-space light.z -= float(time_delta) / TEX_STRETCH xl = light.x / light.z * SCREEN_H yl = light.y / light.z * SCREEN_H - #calculate brightness of light according to distance from viewer and strength, - #then calculate brightness of each pixel with inverse square distance law - light_brightness = (LIGHT_BRIGHTNESS * light.strength - * (1.0 - light.z / TEX_STRETCH)) + # calculate brightness of light according to distance from viewer and strength, + # then calculate brightness of each pixel with inverse square distance law + light_brightness = ( + LIGHT_BRIGHTNESS + * light.strength + * (1.0 - light.z / TEX_STRETCH) + ) brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2) - #make all pixels shine around this light + # make all pixels shine around this light R += brightness * light.r G += brightness * light.g B += brightness * light.b - #truncate values + # truncate values R = R.clip(0, 255) G = G.clip(0, 255) B = B.clip(0, 255) - #fill the screen with these background colors + # fill the screen with these background colors sample_console.bg.transpose()[:] = [R.T, G.T, B.T] + ############################################# # main loop ############################################# RENDERER_KEYS = { - libtcod.KEY_F1: libtcod.RENDERER_GLSL, - libtcod.KEY_F2: libtcod.RENDERER_OPENGL, - libtcod.KEY_F3: libtcod.RENDERER_SDL, - libtcod.KEY_F4: libtcod.RENDERER_SDL2, - libtcod.KEY_F5: libtcod.RENDERER_OPENGL2, - } - -RENDERER_NAMES = ('F1 GLSL ', 'F2 OPENGL ', 'F3 SDL ', 'F4 SDL2 ', - 'F5 OPENGL2') + tcod.event.K_F1: libtcod.RENDERER_GLSL, + tcod.event.K_F2: libtcod.RENDERER_OPENGL, + tcod.event.K_F3: libtcod.RENDERER_SDL, + tcod.event.K_F4: libtcod.RENDERER_SDL2, + tcod.event.K_F5: libtcod.RENDERER_OPENGL2, +} + +RENDERER_NAMES = ( + "F1 GLSL ", + "F2 OPENGL ", + "F3 SDL ", + "F4 SDL2 ", + "F5 OPENGL2", +) SAMPLES = ( TrueColorSample(), @@ -1370,11 +1679,12 @@ def on_draw(self, delta_time): ImageSample(), MouseSample(), NameGeneratorSample(), - FastRenderSample() - ) + FastRenderSample(), +) cur_sample = 0 + def main(): global cur_sample credits_end = False @@ -1394,44 +1704,24 @@ def main(): # render the sample SAMPLES[cur_sample].on_draw(libtcod.sys_get_last_frame_length()) - sample_console.blit(0, 0, sample_console.width, sample_console.height, - root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) + sample_console.blit( + root_console, + SAMPLE_SCREEN_X, + SAMPLE_SCREEN_Y, + 0, + 0, + sample_console.width, + sample_console.height, + ) draw_stats() - handle_events() libtcod.console_flush() + handle_events() + def handle_events(): - global cur_sample - key = libtcod.Key() - mouse = libtcod.Mouse() - EVENT_MASK = libtcod.EVENT_MOUSE | libtcod.EVENT_KEY_PRESS - while libtcod.sys_check_for_event(EVENT_MASK, key, mouse): - SAMPLES[cur_sample].on_mouse(mouse) - SAMPLES[cur_sample].on_key(key) - # key handler - if key.vk == libtcod.KEY_DOWN: - cur_sample = (cur_sample + 1) % len(SAMPLES) - SAMPLES[cur_sample].on_enter() - draw_samples_menu() - elif key.vk == libtcod.KEY_UP: - cur_sample = (cur_sample - 1) % len(SAMPLES) - SAMPLES[cur_sample].on_enter() - draw_samples_menu() - elif key.vk == libtcod.KEY_ENTER and key.lalt: - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) - elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == 'p': - print("screenshot") - if key.lalt: - libtcod.console_save_apf(None, "samples.apf") - print("apf") - else: - libtcod.sys_save_screenshot() - print("png") - elif key.vk == libtcod.KEY_ESCAPE: - raise SystemExit() - elif key.vk in RENDERER_KEYS: - libtcod.sys_set_renderer(RENDERER_KEYS[key.vk]) - draw_renderer_menu() + for event in tcod.event.get(): + SAMPLES[cur_sample].dispatch(event) + def draw_samples_menu(): for i, sample in enumerate(SAMPLES): @@ -1441,33 +1731,49 @@ def draw_samples_menu(): else: root_console.default_fg = libtcod.grey root_console.default_bg = libtcod.black - root_console.print_(2, 46 - (len(SAMPLES) - i), - ' %s' % sample.name.ljust(19), - libtcod.BKGND_SET, libtcod.LEFT) + root_console.print_( + 2, + 46 - (len(SAMPLES) - i), + " %s" % sample.name.ljust(19), + libtcod.BKGND_SET, + libtcod.LEFT, + ) + def draw_stats(): root_console.default_fg = libtcod.grey root_console.print_( - 79, 46, - ' last frame : %3d ms (%3d fps)' % ( + 79, + 46, + " last frame : %3d ms (%3d fps)" + % ( libtcod.sys_get_last_frame_length() * 1000.0, libtcod.sys_get_fps(), - ), - libtcod.BKGND_NONE, libtcod.RIGHT - ) + ), + libtcod.BKGND_NONE, + libtcod.RIGHT, + ) root_console.print_( - 79, 47, - 'elapsed : %8d ms %4.2fs' % (libtcod.sys_elapsed_milli(), - libtcod.sys_elapsed_seconds()), - libtcod.BKGND_NONE, libtcod.RIGHT, - ) + 79, + 47, + "elapsed : %8d ms %4.2fs" + % (time.perf_counter() * 1000, time.perf_counter()), + libtcod.BKGND_NONE, + libtcod.RIGHT, + ) + def draw_renderer_menu(): current_renderer = libtcod.sys_get_renderer() root_console.default_fg = libtcod.grey root_console.default_bg = libtcod.black - root_console.print_(42, 46 - (libtcod.NB_RENDERERS + 1), - "Renderer :", libtcod.BKGND_SET, libtcod.LEFT) + root_console.print_( + 42, + 46 - (libtcod.NB_RENDERERS + 1), + "Renderer :", + libtcod.BKGND_SET, + libtcod.LEFT, + ) for i, name in enumerate(RENDERER_NAMES): if i == current_renderer: root_console.default_fg = libtcod.white @@ -1476,9 +1782,13 @@ def draw_renderer_menu(): root_console.default_fg = libtcod.grey root_console.default_bg = libtcod.black root_console.print_( - 42, 46 - (libtcod.NB_RENDERERS - i), - name, libtcod.BKGND_SET, libtcod.LEFT - ) + 42, + 46 - (libtcod.NB_RENDERERS - i), + name, + libtcod.BKGND_SET, + libtcod.LEFT, + ) + -if __name__ == '__main__': +if __name__ == "__main__": main() From 7bba5e3c58a0a511afd03c3695092fd48763dff6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 03:59:44 -0800 Subject: [PATCH 0045/1101] Refactor BSP sample. --- examples/samples_tcod.py | 198 +++++++++++++++------------------------ 1 file changed, 74 insertions(+), 124 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index fcf0efef..81bd35e5 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -8,7 +8,9 @@ import os +import copy import math +import random import time import numpy as np @@ -1005,7 +1007,6 @@ def ev_mousemotion(self, event): bsp_random_room = False # if true, there is always a wall on north & west side of a room bsp_room_walls = True -bsp_map = None # draw a vertical line def vline(m, x, y1, y2): if y1 > y2: @@ -1051,44 +1052,25 @@ def hline_right(m, x, y): # the class building the dungeon from the bsp nodes -def traverse_node(node, *dat): - global bsp_map +def traverse_node(bsp_map, node): if not node.children: # calculate the room size - minx = node.x + 1 - maxx = node.x + node.w - 1 - miny = node.y + 1 - maxy = node.y + node.h - 1 - if not bsp_room_walls: - if minx > 1: - minx -= 1 - if miny > 1: - miny -= 1 - if maxx == SAMPLE_SCREEN_WIDTH - 1: - maxx -= 1 - if maxy == SAMPLE_SCREEN_HEIGHT - 1: - maxy -= 1 + if bsp_room_walls: + node.width -= 1 + node.height -= 1 if bsp_random_room: - minx = libtcod.random_get_int( - None, minx, maxx - bsp_min_room_size + 1 + new_width = random.randint( + min(node.width, bsp_min_room_size), node.width ) - miny = libtcod.random_get_int( - None, miny, maxy - bsp_min_room_size + 1 + new_height = random.randint( + min(node.height, bsp_min_room_size), node.height ) - maxx = libtcod.random_get_int( - None, minx + bsp_min_room_size - 1, maxx - ) - maxy = libtcod.random_get_int( - None, miny + bsp_min_room_size - 1, maxy - ) - # resize the node to fit the room - node.x = minx - node.y = miny - node.w = maxx - minx + 1 - node.h = maxy - miny + 1 + node.x += random.randint(0, node.width - new_width) + node.y += random.randint(0, node.height - new_height) + node.width, node.height = new_width, new_height # dig the room - for x in range(minx, maxx + 1): - for y in range(miny, maxy + 1): + for x in range(node.x, node.x + node.width): + for y in range(node.y, node.y + node.height): bsp_map[x][y] = True else: # resize the node to fit its sons @@ -1102,11 +1084,9 @@ def traverse_node(node, *dat): # vertical corridor if left.x + left.w - 1 < right.x or right.x + right.w - 1 < left.x: # no overlapping zone. we need a Z shaped corridor - x1 = libtcod.random_get_int(None, left.x, left.x + left.w - 1) - x2 = libtcod.random_get_int( - None, right.x, right.x + right.w - 1 - ) - y = libtcod.random_get_int(None, left.y + left.h, right.y) + x1 = random.randint(left.x, left.x + left.w - 1) + x2 = random.randint(right.x, right.x + right.w - 1) + y = random.randint(left.y + left.h, right.y) vline_up(bsp_map, x1, y - 1) hline(bsp_map, x1, y, x2) vline_down(bsp_map, x2, y + 1) @@ -1114,18 +1094,16 @@ def traverse_node(node, *dat): # straight vertical corridor minx = max(left.x, right.x) maxx = min(left.x + left.w - 1, right.x + right.w - 1) - x = libtcod.random_get_int(None, minx, maxx) + x = random.randint(minx, maxx) vline_down(bsp_map, x, right.y) vline_up(bsp_map, x, right.y - 1) else: # horizontal corridor if left.y + left.h - 1 < right.y or right.y + right.h - 1 < left.y: # no overlapping zone. we need a Z shaped corridor - y1 = libtcod.random_get_int(None, left.y, left.y + left.h - 1) - y2 = libtcod.random_get_int( - None, right.y, right.y + right.h - 1 - ) - x = libtcod.random_get_int(None, left.x + left.w, right.x) + y1 = random.randint(left.y, left.y + left.h - 1) + y2 = random.randint(right.y, right.y + right.h - 1) + x = random.randint(left.x + left.w, right.x) hline_left(bsp_map, x - 1, y1) vline(bsp_map, x, y1, y2) hline_right(bsp_map, x + 1, y2) @@ -1133,68 +1111,45 @@ def traverse_node(node, *dat): # straight horizontal corridor miny = max(left.y, right.y) maxy = min(left.y + left.h - 1, right.y + right.h - 1) - y = libtcod.random_get_int(None, miny, maxy) + y = random.randint(miny, maxy) hline_left(bsp_map, right.x - 1, y) hline_right(bsp_map, right.x, y) return True -bsp = None -bsp_generate = True -bsp_refresh = False - - class BSPSample(Sample): def __init__(self): self.name = "Bsp toolkit" + self.bsp = tcod.bsp.BSP( + 1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1 + ) + self.bsp_map = np.zeros( + (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F" + ) + self.bsp_generate() + + def bsp_generate(self): + self.bsp.children = () + if bsp_room_walls: + self.bsp.split_recursive( + bsp_depth, + bsp_min_room_size + 1, + bsp_min_room_size + 1, + 1.5, + 1.5, + ) + else: + self.bsp.split_recursive( + bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5 + ) + self.bsp_refresh() + + def bsp_refresh(self): + self.bsp_map[...] = False + for node in copy.deepcopy(self.bsp).inverted_level_order(): + traverse_node(self.bsp_map, node) def on_draw(self, delta_time): - global bsp, bsp_generate, bsp_refresh, bsp_map - global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size - if bsp_generate or bsp_refresh: - # dungeon generation - if bsp is None: - # create the bsp - bsp = libtcod.bsp_new_with_size( - 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT - ) - else: - # restore the nodes size - bsp.x = 0 - bsp.y = 0 - bsp.width = SAMPLE_SCREEN_WIDTH - bsp.height = SAMPLE_SCREEN_HEIGHT - bsp_map = list() - for x in range(SAMPLE_SCREEN_WIDTH): - bsp_map.append([False] * SAMPLE_SCREEN_HEIGHT) - if bsp_generate: - # build a new random bsp tree - bsp.children = () - if bsp_room_walls: - libtcod.bsp_split_recursive( - bsp, - 0, - bsp_depth, - bsp_min_room_size + 1, - bsp_min_room_size + 1, - 1.5, - 1.5, - ) - else: - libtcod.bsp_split_recursive( - bsp, - 0, - bsp_depth, - bsp_min_room_size, - bsp_min_room_size, - 1.5, - 1.5, - ) - # create the dungeon from the bsp - for node in bsp.inverted_level_order(): - traverse_node(node) - bsp_generate = False - bsp_refresh = False sample_console.clear() sample_console.default_fg = libtcod.white rooms = "OFF" @@ -1217,42 +1172,37 @@ def on_draw(self, delta_time): # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if not bsp_map[x][y]: - libtcod.console_set_char_background( - sample_console, x, y, DARK_WALL, libtcod.BKGND_SET - ) - else: - libtcod.console_set_char_background( - sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET - ) + color = DARK_GROUND if self.bsp_map[x][y] else DARK_WALL + libtcod.console_set_char_background( + sample_console, x, y, color, libtcod.BKGND_SET + ) def ev_keydown(self, event): - global bsp, bsp_generate, bsp_refresh, bsp_map global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): - bsp_generate = True + self.bsp_generate() elif event.sym == ord(" "): - bsp_refresh = True - elif event.sym == ord("="): + self.bsp_refresh() + elif event.sym in (tcod.event.K_EQUALS, tcod.event.K_KP_PLUS): bsp_depth += 1 - bsp_generate = True - elif event.sym == ord("-") and bsp_depth > 1: - bsp_depth -= 1 - bsp_generate = True - elif event.sym == ord("*"): + self.bsp_generate() + elif event.sym in (tcod.event.K_MINUS, tcod.event.K_KP_MINUS): + bsp_depth = max(1, bsp_depth - 1) + self.bsp_generate() + elif event.sym in (tcod.event.K_8, tcod.event.K_KP_MULTIPLY): bsp_min_room_size += 1 - bsp_generate = True - elif event.sym == ord("/") and bsp_min_room_size > 2: - bsp_min_room_size -= 1 - bsp_generate = True + self.bsp_generate() + elif event.sym in (tcod.event.K_SLASH, tcod.event.K_KP_DIVIDE): + bsp_min_room_size = max(2, bsp_min_room_size - 1) + self.bsp_generate() elif event.sym in (tcod.event.K_1, tcod.event.K_KP_1): bsp_random_room = not bsp_random_room if not bsp_random_room: bsp_room_walls = True - bsp_refresh = True + self.bsp_refresh() elif event.sym in (tcod.event.K_2, tcod.event.K_KP_2): bsp_room_walls = not bsp_room_walls - bsp_refresh = True + self.bsp_refresh() else: super().ev_keydown(event) @@ -1598,17 +1548,17 @@ def on_draw(self, delta_time): # create new light source if ( - libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE + random.random() <= time_delta * LIGHTS_CHANCE and len(lights) < MAX_LIGHTS ): - x = libtcod.random_get_float(0, -0.5, 0.5) - y = libtcod.random_get_float(0, -0.5, 0.5) - strength = libtcod.random_get_float(0, MIN_LIGHT_STRENGTH, 1.0) + x = random.uniform(-0.5, 0.5) + y = random.uniform(-0.5, 0.5) + strength = random.uniform(MIN_LIGHT_STRENGTH, 1.0) color = libtcod.Color( 0, 0, 0 ) # create bright colors with random hue - hue = libtcod.random_get_float(0, 0, 360) + hue = random.uniform(0, 0, 360) libtcod.color_set_hsv(color, hue, 0.5, strength) lights.append( Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength) From 36c1be6c2e90ea47f48639d5b81a6f90f1d6e426 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 16:40:30 -0800 Subject: [PATCH 0046/1101] More samples refactoring. Update copyright banner, change libtcod to tcod, more fixes and cleanups. --- examples/samples_tcod.py | 644 ++++++++++++++++----------------------- 1 file changed, 270 insertions(+), 374 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 81bd35e5..dd2dca1d 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1,10 +1,10 @@ -#!/usr/bin/env python -# -# libtcod python samples -# This code demonstrates various usages of libtcod modules -# It's in the public domain. -# -from __future__ import division +#!/usr/bin/env python3 +""" +This code demonstrates various usages of python-tcod. +""" +# To the extent possible under law, the tcod.maintainers have waived all +# copyright and related or neighboring rights to these samples. +# https://creativecommons.org/publicdomain/zero/1.0/ import os @@ -14,41 +14,34 @@ import time import numpy as np -import tcod as libtcod +import tcod import tcod.event SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -font = os.path.join("data/fonts/consolas10x10_gs_tc.png") -libtcod.console_set_custom_font( - font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD +FONT = "data/fonts/consolas10x10_gs_tc.png" +tcod.console_set_custom_font( + FONT, tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD ) -root_console = libtcod.console_init_root(80, 50, "tcod python sample", False) +root_console = tcod.console_init_root(80, 50, "tcod python samples", False) sample_console = tcod.console.Console( SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT ) class Sample(tcod.event.EventDispatch): - def __init__(self, name="", func=None): + def __init__(self, name: str = ""): self.name = name - self.func = func def on_enter(self): pass - def on_draw(self, delta_time): - pass - - def on_key(self, key): - pass - - def on_mouse(self, mouse): + def on_draw(self, delta_time: int): pass - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): global cur_sample if event.sym == tcod.event.K_DOWN: cur_sample = (cur_sample + 1) % len(SAMPLES) @@ -62,22 +55,22 @@ def ev_keydown(self, event): event.sym == tcod.event.K_RETURN and event.mod & tcod.event.KMOD_LALT ): - libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) + tcod.console_set_fullscreen(not tcod.console_is_fullscreen()) elif event.sym == tcod.event.K_PRINTSCREEN or event.sym == ord("p"): print("screenshot") if event.mod & tcod.event.KMOD_LALT: - libtcod.console_save_apf(None, "samples.apf") + tcod.console_save_apf(None, "samples.apf") print("apf") else: - libtcod.sys_save_screenshot() + tcod.sys_save_screenshot() print("png") elif event.sym == tcod.event.K_ESCAPE: raise SystemExit() elif event.sym in RENDERER_KEYS: - libtcod.sys_set_renderer(RENDERER_KEYS[event.sym]) + tcod.sys_set_renderer(RENDERER_KEYS[event.sym]) draw_renderer_menu() - def ev_quit(self, event): + def ev_quit(self, event: tcod.event.Quit): raise SystemExit() @@ -104,7 +97,7 @@ def __init__(self): ] def on_enter(self): - libtcod.sys_set_fps(0) + tcod.sys_set_fps(0) sample_console.clear() def on_draw(self, delta_time): @@ -154,7 +147,7 @@ def randomize_sample_conole(self): def print_banner(self): # print text on top of samples - sample_console.default_bg = libtcod.grey + sample_console.default_bg = tcod.grey sample_console.print_rect( x=sample_console.width // 2, y=5, @@ -162,8 +155,8 @@ def print_banner(self): height=sample_console.height - 1, string="The Doryen library uses 24 bits colors, for both " "background and foreground.", - bg_blend=libtcod.BKGND_MULTIPLY, - alignment=libtcod.CENTER, + bg_blend=tcod.BKGND_MULTIPLY, + alignment=tcod.CENTER, ) @@ -175,10 +168,10 @@ def print_banner(self): class OffscreenConsoleSample(Sample): def __init__(self): self.name = "Offscreen console" - self.secondary = libtcod.console.Console( + self.secondary = tcod.console.Console( sample_console.width // 2, sample_console.height // 2 ) - self.screenshot = libtcod.console.Console( + self.screenshot = tcod.console.Console( sample_console.width, sample_console.height ) self.counter = 0 @@ -194,7 +187,7 @@ def __init__(self): sample_console.height // 2, "Offscreen console", False, - libtcod.BKGND_NONE, + tcod.BKGND_NONE, ) self.secondary.print_rect( @@ -204,22 +197,14 @@ def __init__(self): sample_console.height // 2, "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", - libtcod.BKGND_NONE, - libtcod.CENTER, + tcod.BKGND_NONE, + tcod.CENTER, ) def on_enter(self): - libtcod.sys_set_fps(0) + tcod.sys_set_fps(0) # get a "screenshot" of the current sample screen - sample_console.blit( - self.screenshot, - 0, - 0, - 0, - 0, - sample_console.width, - sample_console.height, - ) + sample_console.blit(self.screenshot) def on_draw(self, delta_time): self.counter += delta_time * 1.5 @@ -235,15 +220,7 @@ def on_draw(self, delta_time): self.ydir = -1 elif self.y == -5: self.ydir = 1 - self.screenshot.blit( - sample_console, - 0, - 0, - 0, - 0, - sample_console.width, - sample_console.height, - ) + self.screenshot.blit(sample_console) self.secondary.blit( sample_console, self.x, @@ -282,16 +259,14 @@ class LineDrawingSample(Sample): def __init__(self): self.name = "Line drawing" - self.mk_flag = libtcod.BKGND_SET - self.bk_flag = libtcod.BKGND_SET + self.mk_flag = tcod.BKGND_SET + self.bk_flag = tcod.BKGND_SET - self.bk = libtcod.console_new( - sample_console.width, sample_console.height - ) + self.bk = tcod.console_new(sample_console.width, sample_console.height) # initialize the colored background for x in range(sample_console.width): for y in range(sample_console.height): - col = libtcod.Color( + col = tcod.Color( x * 255 // (sample_console.width - 1), (x + y) * 255 @@ -301,54 +276,46 @@ def __init__(self): self.bk.bg[y, x] = col self.bk.ch[:] = ord(" ") - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): self.bk_flag += 1 - if (self.bk_flag & 0xFF) > libtcod.BKGND_ALPH: - self.bk_flag = libtcod.BKGND_NONE + if (self.bk_flag & 0xFF) > tcod.BKGND_ALPH: + self.bk_flag = tcod.BKGND_NONE else: super().ev_keydown(event) def on_enter(self): - libtcod.sys_set_fps(0) - sample_console.default_fg = libtcod.white + tcod.sys_set_fps(0) + sample_console.default_fg = tcod.white def on_draw(self, delta_time): alpha = 0.0 - if (self.bk_flag & 0xFF) == libtcod.BKGND_ALPH: + if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: # for the alpha mode, update alpha every frame - alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 - self.bk_flag = libtcod.BKGND_ALPHA(alpha) - elif (self.bk_flag & 0xFF) == libtcod.BKGND_ADDA: + alpha = (1.0 + math.cos(tcod.sys_elapsed_seconds() * 2)) / 2.0 + self.bk_flag = tcod.BKGND_ALPHA(alpha) + elif (self.bk_flag & 0xFF) == tcod.BKGND_ADDA: # for the add alpha mode, update alpha every frame - alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 - self.bk_flag = libtcod.BKGND_ADDALPHA(alpha) + alpha = (1.0 + math.cos(tcod.sys_elapsed_seconds() * 2)) / 2.0 + self.bk_flag = tcod.BKGND_ADDALPHA(alpha) - self.bk.blit( - sample_console, - 0, - 0, - 0, - 0, - sample_console.width, - sample_console.height, - ) + self.bk.blit(sample_console) recty = int( (sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0) ) for x in range(sample_console.width): - col = libtcod.Color( + col = tcod.Color( x * 255 // sample_console.width, x * 255 // sample_console.width, x * 255 // sample_console.width, ) - libtcod.console_set_char_background( + tcod.console_set_char_background( sample_console, x, recty, col, self.bk_flag ) - libtcod.console_set_char_background( + tcod.console_set_char_background( sample_console, x, recty + 1, col, self.bk_flag ) - libtcod.console_set_char_background( + tcod.console_set_char_background( sample_console, x, recty + 2, col, self.bk_flag ) angle = time.time() * 2.0 @@ -364,13 +331,13 @@ def on_draw(self, delta_time): ) # draw the line # in python the easiest way is to use the line iterator - for x, y in libtcod.line_iter(xo, yo, xd, yd): + for x, y in tcod.line_iter(xo, yo, xd, yd): if ( 0 <= x < sample_console.width and 0 <= y < sample_console.height ): - libtcod.console_set_char_background( - sample_console, x, y, libtcod.light_blue, self.bk_flag + tcod.console_set_char_background( + sample_console, x, y, tcod.light_blue, self.bk_flag ) sample_console.print_( 2, 2, "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF] @@ -382,15 +349,15 @@ def on_draw(self, delta_time): ############################################# NOISE_OPTIONS = [ # [name, algorithm, implementation], - ["perlin noise", libtcod.NOISE_PERLIN, libtcod.noise.SIMPLE], - ["simplex noise", libtcod.NOISE_SIMPLEX, libtcod.noise.SIMPLE], - ["wavelet noise", libtcod.NOISE_WAVELET, libtcod.noise.SIMPLE], - ["perlin fbm", libtcod.NOISE_PERLIN, libtcod.noise.FBM], - ["perlin turbulence", libtcod.NOISE_PERLIN, libtcod.noise.TURBULENCE], - ["simplex fbm", libtcod.NOISE_SIMPLEX, libtcod.noise.FBM], - ["simplex turbulence", libtcod.NOISE_SIMPLEX, libtcod.noise.TURBULENCE], - ["wavelet fbm", libtcod.NOISE_WAVELET, libtcod.noise.FBM], - ["wavelet turbulence", libtcod.NOISE_WAVELET, libtcod.noise.TURBULENCE], + ["perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE], + ["simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE], + ["wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE], + ["perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM], + ["perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE], + ["simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM], + ["simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE], + ["wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM], + ["wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE], ] @@ -402,10 +369,10 @@ def __init__(self): self.dy = 0.0 self.octaves = 4.0 self.zoom = 3.0 - self.hurst = libtcod.NOISE_DEFAULT_HURST - self.lacunarity = libtcod.NOISE_DEFAULT_LACUNARITY + self.hurst = tcod.NOISE_DEFAULT_HURST + self.lacunarity = tcod.NOISE_DEFAULT_LACUNARITY self.noise = self.get_noise() - self.img = libtcod.image_new( + self.img = tcod.image_new( SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2 ) @@ -418,7 +385,7 @@ def implementation(self): return NOISE_OPTIONS[self.func][2] def get_noise(self): - return libtcod.noise.Noise( + return tcod.noise.Noise( 2, self.algorithm, self.implementation, @@ -429,7 +396,7 @@ def get_noise(self): ) def on_enter(self): - libtcod.sys_set_fps(0) + tcod.sys_set_fps(0) def on_draw(self, delta_time): sample_console.clear() @@ -445,14 +412,14 @@ def on_draw(self, delta_time): c = int((value + 1.0) / 2.0 * 255) c = max(0, min(c, 255)) self.img.put_pixel(x, y, (c // 2, c // 2, c)) - sample_console.default_bg = libtcod.grey + sample_console.default_bg = tcod.grey rectw = 24 recth = 13 - if self.implementation == libtcod.noise.SIMPLE: + if self.implementation == tcod.noise.SIMPLE: recth = 10 self.img.blit_2x(sample_console, 0, 0) - sample_console.default_bg = libtcod.grey - sample_console.rect(2, 2, rectw, recth, False, libtcod.BKGND_MULTIPLY) + sample_console.default_bg = tcod.grey + sample_console.rect(2, 2, rectw, recth, False, tcod.BKGND_MULTIPLY) sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] = ( sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] * sample_console.default_bg @@ -462,17 +429,17 @@ def on_draw(self, delta_time): for curfunc in range(len(NOISE_OPTIONS)): text = "%i : %s" % (curfunc + 1, NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: - sample_console.default_fg = libtcod.white - sample_console.default_bg = libtcod.light_blue + sample_console.default_fg = tcod.white + sample_console.default_bg = tcod.light_blue sample_console.print_( - 2, 2 + curfunc, text, libtcod.BKGND_SET, libtcod.LEFT + 2, 2 + curfunc, text, tcod.BKGND_SET, tcod.LEFT ) else: - sample_console.default_fg = libtcod.grey + sample_console.default_fg = tcod.grey sample_console.print_(2, 2 + curfunc, text) - sample_console.default_fg = libtcod.white + sample_console.default_fg = tcod.white sample_console.print_(2, 11, "Y/H : zoom (%2.1f)" % self.zoom) - if self.implementation != libtcod.noise.SIMPLE: + if self.implementation != tcod.noise.SIMPLE: sample_console.print_(2, 12, "E/D : hurst (%2.1f)" % self.hurst) sample_console.print_( 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity @@ -481,7 +448,7 @@ def on_draw(self, delta_time): 2, 14, "T/G : octaves (%2.1f)" % self.octaves ) - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): if ord("9") >= event.sym >= ord("1"): self.func = event.sym - ord("1") self.noise = self.get_noise() @@ -514,10 +481,10 @@ def ev_keydown(self, event): ############################################# # field of view sample ############################################# -DARK_WALL = libtcod.Color(0, 0, 100) -LIGHT_WALL = libtcod.Color(130, 110, 50) -DARK_GROUND = libtcod.Color(50, 50, 150) -LIGHT_GROUND = libtcod.Color(200, 180, 50) +DARK_WALL = tcod.Color(0, 0, 100) +LIGHT_WALL = tcod.Color(130, 110, 50) +DARK_GROUND = tcod.Color(50, 50, 150) +LIGHT_GROUND = tcod.Color(200, 180, 50) SAMPLE_MAP = [ "##############################################", @@ -578,9 +545,9 @@ def __init__(self): self.light_walls = True self.algo_num = 0 # 1d noise for the torch flickering - self.noise = libtcod.noise_new(1, 1.0, 1.0) + self.noise = tcod.noise_new(1, 1.0, 1.0) - self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + self.map = tcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) self.map.walkable[:] = SAMPLE_MAP[:] == " " self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == "=") @@ -594,8 +561,8 @@ def __init__(self): self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL def draw_ui(self): - libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print( + tcod.console_set_default_foreground(sample_console, tcod.white) + tcod.console_print( sample_console, 1, 1, @@ -609,22 +576,22 @@ def draw_ui(self): FOV_ALGO_NAMES[self.algo_num], ), ) - libtcod.console_set_default_foreground(sample_console, libtcod.black) + tcod.console_set_default_foreground(sample_console, tcod.black) def on_enter(self): - libtcod.sys_set_fps(60) + tcod.sys_set_fps(60) # we draw the foreground only the first time. # during the player movement, only the @ is redrawn. # the rest impacts only the background color # draw the help text & player @ - libtcod.console_clear(sample_console) + tcod.console_clear(sample_console) self.draw_ui() - libtcod.console_put_char( - sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.px, self.py, "@", tcod.BKGND_NONE ) # draw windows - sample_console.ch[np.where(SAMPLE_MAP == "=")] = libtcod.CHAR_DHLINE - sample_console.fg[np.where(SAMPLE_MAP == "=")] = libtcod.black + sample_console.ch[np.where(SAMPLE_MAP == "=")] = tcod.CHAR_DHLINE + sample_console.fg[np.where(SAMPLE_MAP == "=")] = tcod.black def on_draw(self, delta_time): dx = 0.0 @@ -645,15 +612,11 @@ def on_draw(self, delta_time): self.torchx += 0.1 # randomize the light position between -1.5 and 1.5 tdx = [self.torchx + 20.0] - dx = ( - libtcod.noise_get(self.noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 - ) + dx = tcod.noise_get(self.noise, tdx, tcod.NOISE_SIMPLEX) * 1.5 tdx[0] += 30.0 - dy = ( - libtcod.noise_get(self.noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 - ) - di = 0.2 * libtcod.noise_get( - self.noise, [self.torchx], libtcod.NOISE_SIMPLEX + dy = tcod.noise_get(self.noise, tdx, tcod.NOISE_SIMPLEX) * 1.5 + di = 0.2 * tcod.noise_get( + self.noise, [self.torchx], tcod.NOISE_SIMPLEX ) # where_fov = np.where(self.map.fov[:]) mgrid = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH] @@ -671,7 +634,7 @@ def on_draw(self, delta_time): light[where_visible] = light[where_visible].clip(0, 1) for yx in zip(*where_visible): - sample_console.bg[yx] = libtcod.color_lerp( + sample_console.bg[yx] = tcod.color_lerp( tuple(self.dark_map_bg[yx]), tuple(self.light_map_bg[yx]), light[yx], @@ -680,7 +643,7 @@ def on_draw(self, delta_time): where_fov = np.where(self.map.fov[:]) sample_console.bg[where_fov] = self.light_map_bg[where_fov] - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): MOVE_KEYS = { ord("i"): (0, -1), ord("j"): (-1, 0), @@ -691,13 +654,13 @@ def ev_keydown(self, event): if event.sym in MOVE_KEYS: x, y = MOVE_KEYS[event.sym] if SAMPLE_MAP[self.py + y][self.px + x] == " ": - libtcod.console_put_char( - sample_console, self.px, self.py, " ", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.px, self.py, " ", tcod.BKGND_NONE ) self.px += x self.py += y - libtcod.console_put_char( - sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.px, self.py, "@", tcod.BKGND_NONE ) self.recompute = True elif event.sym == ord("t"): @@ -710,7 +673,7 @@ def ev_keydown(self, event): self.recompute = True elif event.sym in FOV_SELECT_KEYS: self.algo_num += FOV_SELECT_KEYS[event.sym] - self.algo_num %= libtcod.NB_FOV_ALGORITHMS + self.algo_num %= tcod.NB_FOV_ALGORITHMS self.draw_ui() self.recompute = True else: @@ -739,239 +702,201 @@ def __init__(self): self.busy = 0.0 self.oldchar = " " - self.map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + self.map = tcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[y][x] == " ": # ground - libtcod.map_set_properties(self.map, x, y, True, True) + tcod.map_set_properties(self.map, x, y, True, True) elif SAMPLE_MAP[y][x] == "=": # window - libtcod.map_set_properties(self.map, x, y, True, False) - self.path = libtcod.path_new_using_map(self.map) - self.dijk = libtcod.dijkstra_new(self.map) + tcod.map_set_properties(self.map, x, y, True, False) + self.path = tcod.path_new_using_map(self.map) + self.dijk = tcod.dijkstra_new(self.map) def on_enter(self): - libtcod.sys_set_fps(60) + tcod.sys_set_fps(60) # we draw the foreground only the first time. # during the player movement, only the @ is redrawn. # the rest impacts only the background color # draw the help text & player @ - libtcod.console_clear(sample_console) - libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_clear(sample_console) + tcod.console_set_default_foreground(sample_console, tcod.white) + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - libtcod.console_put_char( - sample_console, self.px, self.py, "@", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.px, self.py, "@", tcod.BKGND_NONE ) - libtcod.console_print( + tcod.console_print( sample_console, 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", ) - libtcod.console_print(sample_console, 1, 4, "Using : A*") + tcod.console_print(sample_console, 1, 4, "Using : A*") # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[y][x] == "=": - libtcod.console_put_char( - sample_console, - x, - y, - libtcod.CHAR_DHLINE, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, x, y, tcod.CHAR_DHLINE, tcod.BKGND_NONE ) self.recalculate = True def on_draw(self, delta_time): if self.recalculate: if self.using_astar: - libtcod.path_compute( + tcod.path_compute( self.path, self.px, self.py, self.dx, self.dy ) else: self.dijk_dist = 0.0 # compute dijkstra grid (distance from px,py) - libtcod.dijkstra_compute(self.dijk, self.px, self.py) + tcod.dijkstra_compute(self.dijk, self.px, self.py) # get the maximum distance (needed for rendering) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - d = libtcod.dijkstra_get_distance(self.dijk, x, y) + d = tcod.dijkstra_get_distance(self.dijk, x, y) if d > self.dijk_dist: self.dijk_dist = d # compute path from px,py to dx,dy - libtcod.dijkstra_path_set(self.dijk, self.dx, self.dy) + tcod.dijkstra_path_set(self.dijk, self.dx, self.dy) self.recalculate = False self.busy = 0.2 # draw the dungeon for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[y][x] == "#": - libtcod.console_set_char_background( - sample_console, x, y, DARK_WALL, libtcod.BKGND_SET + tcod.console_set_char_background( + sample_console, x, y, DARK_WALL, tcod.BKGND_SET ) else: - libtcod.console_set_char_background( - sample_console, x, y, DARK_GROUND, libtcod.BKGND_SET + tcod.console_set_char_background( + sample_console, x, y, DARK_GROUND, tcod.BKGND_SET ) # draw the path if self.using_astar: - for i in range(libtcod.path_size(self.path)): - x, y = libtcod.path_get(self.path, i) - libtcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET + for i in range(tcod.path_size(self.path)): + x, y = tcod.path_get(self.path, i) + tcod.console_set_char_background( + sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET ) else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[y][x] != "#": - libtcod.console_set_char_background( + tcod.console_set_char_background( sample_console, x, y, - libtcod.color_lerp( + tcod.color_lerp( LIGHT_GROUND, DARK_GROUND, 0.9 - * libtcod.dijkstra_get_distance( - self.dijk, x, y - ) + * tcod.dijkstra_get_distance(self.dijk, x, y) / self.dijk_dist, ), - libtcod.BKGND_SET, + tcod.BKGND_SET, ) - for i in range(libtcod.dijkstra_size(self.dijk)): - x, y = libtcod.dijkstra_get(self.dijk, i) - libtcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, libtcod.BKGND_SET + for i in range(tcod.dijkstra_size(self.dijk)): + x, y = tcod.dijkstra_get(self.dijk, i) + tcod.console_set_char_background( + sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET ) # move the creature - self.busy -= libtcod.sys_get_last_frame_length() + self.busy -= tcod.sys_get_last_frame_length() if self.busy <= 0.0: self.busy = 0.2 if self.using_astar: - if not libtcod.path_is_empty(self.path): - libtcod.console_put_char( - sample_console, - self.px, - self.py, - " ", - libtcod.BKGND_NONE, + if not tcod.path_is_empty(self.path): + tcod.console_put_char( + sample_console, self.px, self.py, " ", tcod.BKGND_NONE ) - self.px, self.py = libtcod.path_walk(self.path, True) - libtcod.console_put_char( - sample_console, - self.px, - self.py, - "@", - libtcod.BKGND_NONE, + self.px, self.py = tcod.path_walk(self.path, True) + tcod.console_put_char( + sample_console, self.px, self.py, "@", tcod.BKGND_NONE ) else: - if not libtcod.dijkstra_is_empty(self.dijk): - libtcod.console_put_char( - sample_console, - self.px, - self.py, - " ", - libtcod.BKGND_NONE, + if not tcod.dijkstra_is_empty(self.dijk): + tcod.console_put_char( + sample_console, self.px, self.py, " ", tcod.BKGND_NONE ) - self.px, self.py = libtcod.dijkstra_path_walk(self.dijk) - libtcod.console_put_char( - sample_console, - self.px, - self.py, - "@", - libtcod.BKGND_NONE, + self.px, self.py = tcod.dijkstra_path_walk(self.dijk) + tcod.console_put_char( + sample_console, self.px, self.py, "@", tcod.BKGND_NONE ) self.recalculate = True - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): if event.sym == ord("i") and self.dy > 0: # destination move north - libtcod.console_put_char( - sample_console, - self.dx, - self.dy, - self.oldchar, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dy -= 1 - self.oldchar = libtcod.console_get_char( + self.oldchar = tcod.console_get_char( sample_console, self.dx, self.dy ) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True elif event.sym == ord("k") and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - libtcod.console_put_char( - sample_console, - self.dx, - self.dy, - self.oldchar, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dy += 1 - self.oldchar = libtcod.console_get_char( + self.oldchar = tcod.console_get_char( sample_console, self.dx, self.dy ) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True elif event.sym == ord("j") and self.dx > 0: # destination move west - libtcod.console_put_char( - sample_console, - self.dx, - self.dy, - self.oldchar, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dx -= 1 - self.oldchar = libtcod.console_get_char( + self.oldchar = tcod.console_get_char( sample_console, self.dx, self.dy ) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True elif event.sym == ord("l") and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - libtcod.console_put_char( - sample_console, - self.dx, - self.dy, - self.oldchar, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dx += 1 - self.oldchar = libtcod.console_get_char( + self.oldchar = tcod.console_get_char( sample_console, self.dx, self.dy ) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True elif event.sym == tcod.event.K_TAB: self.using_astar = not self.using_astar if self.using_astar: - libtcod.console_print(sample_console, 1, 4, "Using : A* ") + tcod.console_print(sample_console, 1, 4, "Using : A* ") else: - libtcod.console_print(sample_console, 1, 4, "Using : Dijkstra") + tcod.console_print(sample_console, 1, 4, "Using : Dijkstra") self.recalculate = True else: super().ev_keydown(event) - def ev_mousemotion(self, event): + def ev_mousemotion(self, event: tcod.event.MouseMotion): mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if ( @@ -979,20 +904,16 @@ def ev_mousemotion(self, event): and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my) ): - libtcod.console_put_char( - sample_console, - self.dx, - self.dy, - self.oldchar, - libtcod.BKGND_NONE, + tcod.console_put_char( + sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dx = mx self.dy = my - self.oldchar = libtcod.console_get_char( + self.oldchar = tcod.console_get_char( sample_console, self.dx, self.dy ) - libtcod.console_put_char( - sample_console, self.dx, self.dy, "+", libtcod.BKGND_NONE + tcod.console_put_char( + sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) if SAMPLE_MAP[self.dy][self.dx] == " ": self.recalculate = True @@ -1151,7 +1072,7 @@ def bsp_refresh(self): def on_draw(self, delta_time): sample_console.clear() - sample_console.default_fg = libtcod.white + sample_console.default_fg = tcod.white rooms = "OFF" if bsp_random_room: rooms = "ON" @@ -1173,11 +1094,11 @@ def on_draw(self, delta_time): for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): color = DARK_GROUND if self.bsp_map[x][y] else DARK_WALL - libtcod.console_set_char_background( - sample_console, x, y, color, libtcod.BKGND_SET + tcod.console_set_char_background( + sample_console, x, y, color, tcod.BKGND_SET ) - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): self.bsp_generate() @@ -1210,23 +1131,21 @@ def ev_keydown(self, event): ############################################# # image sample ############################################# -img_blue = libtcod.Color(0, 0, 255) -img_green = libtcod.Color(0, 255, 0) class ImageSample(Sample): def __init__(self): self.name = "Image toolkit" - self.img = libtcod.image_load("data/img/skull.png") - self.img.set_key_color(libtcod.black) - self.circle = libtcod.image_load("data/img/circle.png") + self.img = tcod.image_load("data/img/skull.png") + self.img.set_key_color(tcod.black) + self.circle = tcod.image_load("data/img/circle.png") def on_enter(self): - libtcod.sys_set_fps(0) + tcod.sys_set_fps(0) def on_draw(self, delta_time): - sample_console.default_bg = libtcod.black + sample_console.default_bg = tcod.black sample_console.clear() x = sample_console.width / 2 + math.cos(time.time()) * 10.0 y = sample_console.height / 2 @@ -1236,37 +1155,35 @@ def on_draw(self, delta_time): if int(time.time() / 2): # split the color channels of circle.png # the red channel - sample_console.default_bg = libtcod.red + sample_console.default_bg = (255, 0, 0) - sample_console.rect(0, 3, 15, 15, False, libtcod.BKGND_SET) + sample_console.rect(0, 3, 15, 15, False, tcod.BKGND_SET) self.circle.blit_rect( - sample_console, 0, 3, -1, -1, libtcod.BKGND_MULTIPLY + sample_console, 0, 3, -1, -1, tcod.BKGND_MULTIPLY ) # the green channel - sample_console.default_bg = img_green - sample_console.rect(15, 3, 15, 15, False, libtcod.BKGND_SET) + sample_console.default_bg = (0, 255, 0) + sample_console.rect(15, 3, 15, 15, False, tcod.BKGND_SET) self.circle.blit_rect( - sample_console, 15, 3, -1, -1, libtcod.BKGND_MULTIPLY + sample_console, 15, 3, -1, -1, tcod.BKGND_MULTIPLY ) # the blue channel - sample_console.default_bg = img_blue - sample_console.rect(30, 3, 15, 15, False, libtcod.BKGND_SET) + sample_console.default_bg = (0, 0, 255) + sample_console.rect(30, 3, 15, 15, False, tcod.BKGND_SET) self.circle.blit_rect( - sample_console, 30, 3, -1, -1, libtcod.BKGND_MULTIPLY + sample_console, 30, 3, -1, -1, tcod.BKGND_MULTIPLY ) else: # render circle.png with normal blitting + self.circle.blit_rect(sample_console, 0, 3, -1, -1, tcod.BKGND_SET) self.circle.blit_rect( - sample_console, 0, 3, -1, -1, libtcod.BKGND_SET - ) - self.circle.blit_rect( - sample_console, 15, 3, -1, -1, libtcod.BKGND_SET + sample_console, 15, 3, -1, -1, tcod.BKGND_SET ) self.circle.blit_rect( - sample_console, 30, 3, -1, -1, libtcod.BKGND_SET + sample_console, 30, 3, -1, -1, tcod.BKGND_SET ) self.img.blit( - sample_console, x, y, libtcod.BKGND_SET, scalex, scaley, angle + sample_console, x, y, tcod.BKGND_SET, scalex, scaley, angle ) @@ -1285,16 +1202,16 @@ def __init__(self): self.log = [] def on_enter(self): - sample_console.default_bg = libtcod.grey - sample_console.default_fg = libtcod.light_yellow - libtcod.mouse_move(320, 200) - libtcod.mouse_show_cursor(True) - libtcod.sys_set_fps(60) + sample_console.default_bg = tcod.grey + sample_console.default_fg = tcod.light_yellow + tcod.mouse_move(320, 200) + tcod.mouse_show_cursor(True) + tcod.sys_set_fps(60) - def ev_mousemotion(self, event): + def ev_mousemotion(self, event: tcod.event.MouseMotion): self.motion = event - def ev_mousebuttondown(self, event): + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown): if event.button == tcod.event.BUTTON_LEFT: self.lbut = True elif event.button == tcod.event.BUTTON_MIDDLE: @@ -1302,7 +1219,7 @@ def ev_mousebuttondown(self, event): elif event.button == tcod.event.BUTTON_RIGHT: self.rbut = True - def ev_mousebuttonup(self, event): + def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp): if event.button == tcod.event.BUTTON_LEFT: self.lbut = False elif event.button == tcod.event.BUTTON_MIDDLE: @@ -1335,11 +1252,11 @@ def on_draw(self, delta_time): ) sample_console.print_(1, 10, "1 : Hide cursor\n2 : Show cursor") - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): if event.sym == ord("1"): - libtcod.mouse_show_cursor(False) + tcod.mouse_show_cursor(False) elif event.sym == ord("2"): - libtcod.mouse_show_cursor(True) + tcod.mouse_show_cursor(True) else: super().ev_keydown(event) @@ -1360,24 +1277,22 @@ def __init__(self): self.sets = None def on_enter(self): - libtcod.sys_set_fps(60) + tcod.sys_set_fps(60) def on_draw(self, delta_time): if self.nbsets == 0: # parse all *.cfg files in data/namegen - for file in os.listdir(b"data/namegen"): - if file.find(b".cfg") > 0: - libtcod.namegen_parse( - os.path.join(b"data", b"namegen", file) - ) + for file in os.listdir("data/namegen"): + if file.find(".cfg") > 0: + tcod.namegen_parse(os.path.join("data/namegen", file)) # get the sets list - self.sets = libtcod.namegen_get_sets() + self.sets = tcod.namegen_get_sets() print(self.sets) self.nbsets = len(self.sets) while len(self.names) > 15: self.names.pop(0) sample_console.clear() - sample_console.default_fg = libtcod.white + sample_console.default_fg = tcod.white sample_console.print_( 1, 1, @@ -1389,15 +1304,15 @@ def on_draw(self, delta_time): SAMPLE_SCREEN_WIDTH - 2, 2 + i, self.names[i], - libtcod.BKGND_NONE, - libtcod.RIGHT, + tcod.BKGND_NONE, + tcod.RIGHT, ) - self.delay += libtcod.sys_get_last_frame_length() + self.delay += tcod.sys_get_last_frame_length() if self.delay > 0.5: self.delay -= 0.5 - self.names.append(libtcod.namegen_generate(self.sets[self.curset])) + self.names.append(tcod.namegen_generate(self.sets[self.curset])) - def ev_keydown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown): if event.sym == ord("="): self.curset += 1 if self.curset == self.nbsets: @@ -1446,7 +1361,7 @@ def ev_keydown(self, event): xc = xc - HALF_W yc = yc - HALF_H -noise2d = libtcod.noise_new(2, 0.5, 2.0) +noise2d = tcod.noise_new(2, 0.5, 2.0) if numpy_available: # the texture starts empty texture = np.zeros((RES_U, RES_V)) @@ -1464,9 +1379,9 @@ def __init__(self): def on_enter(self): global frac_t, abs_t, lights, tex_r, tex_g, tex_b - libtcod.sys_set_fps(0) + tcod.sys_set_fps(0) sample_console.clear() # render status message - sample_console.default_fg = libtcod.white + sample_console.default_fg = tcod.white sample_console.print_(1, SCREEN_H - 3, "Renderer: NumPy") frac_t = ( @@ -1479,9 +1394,7 @@ def on_enter(self): def on_draw(self, delta_time): global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2 - time_delta = ( - libtcod.sys_get_last_frame_length() * SPEED - ) # advance time + time_delta = tcod.sys_get_last_frame_length() * SPEED # advance time frac_t += time_delta # increase fractional (always < 1.0) time abs_t += time_delta # increase absolute elapsed time int_t = int( @@ -1520,9 +1433,9 @@ def on_draw(self, delta_time): for v in range(RES_V - int_t, RES_V): for u in range(0, RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u, v] = libtcod.noise_get_fbm( + texture[u, v] = tcod.noise_get_fbm( noise2d, [u / float(RES_U), tex_v], 32.0 - ) + libtcod.noise_get_fbm( + ) + tcod.noise_get_fbm( noise2d, [1 - u / float(RES_U), tex_v], 32.0 ) @@ -1555,11 +1468,9 @@ def on_draw(self, delta_time): y = random.uniform(-0.5, 0.5) strength = random.uniform(MIN_LIGHT_STRENGTH, 1.0) - color = libtcod.Color( - 0, 0, 0 - ) # create bright colors with random hue - hue = random.uniform(0, 0, 360) - libtcod.color_set_hsv(color, hue, 0.5, strength) + color = tcod.Color(0, 0, 0) # create bright colors with random hue + hue = random.uniform(0, 360) + tcod.color_set_hsv(color, hue, 0.5, strength) lights.append( Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength) ) @@ -1603,11 +1514,11 @@ def on_draw(self, delta_time): ############################################# RENDERER_KEYS = { - tcod.event.K_F1: libtcod.RENDERER_GLSL, - tcod.event.K_F2: libtcod.RENDERER_OPENGL, - tcod.event.K_F3: libtcod.RENDERER_SDL, - tcod.event.K_F4: libtcod.RENDERER_SDL2, - tcod.event.K_F5: libtcod.RENDERER_OPENGL2, + tcod.event.K_F1: tcod.RENDERER_GLSL, + tcod.event.K_F2: tcod.RENDERER_OPENGL, + tcod.event.K_F3: tcod.RENDERER_SDL, + tcod.event.K_F4: tcod.RENDERER_SDL2, + tcod.event.K_F5: tcod.RENDERER_OPENGL2, } RENDERER_NAMES = ( @@ -1642,7 +1553,7 @@ def main(): draw_samples_menu() draw_renderer_menu() - while not libtcod.console_is_window_closed(): + while not tcod.console_is_window_closed(): root_console.default_fg = (255, 255, 255) root_console.default_bg = (0, 0, 0) root_console.clear() @@ -1650,21 +1561,13 @@ def main(): draw_renderer_menu() # render credits if not credits_end: - credits_end = libtcod.console_credits_render(60, 43, 0) + credits_end = tcod.console_credits_render(60, 43, 0) # render the sample - SAMPLES[cur_sample].on_draw(libtcod.sys_get_last_frame_length()) - sample_console.blit( - root_console, - SAMPLE_SCREEN_X, - SAMPLE_SCREEN_Y, - 0, - 0, - sample_console.width, - sample_console.height, - ) + SAMPLES[cur_sample].on_draw(tcod.sys_get_last_frame_length()) + sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() - libtcod.console_flush() + tcod.console_flush() handle_events() @@ -1676,67 +1579,60 @@ def handle_events(): def draw_samples_menu(): for i, sample in enumerate(SAMPLES): if i == cur_sample: - root_console.default_fg = libtcod.white - root_console.default_bg = libtcod.light_blue + root_console.default_fg = tcod.white + root_console.default_bg = tcod.light_blue else: - root_console.default_fg = libtcod.grey - root_console.default_bg = libtcod.black + root_console.default_fg = tcod.grey + root_console.default_bg = tcod.black root_console.print_( 2, 46 - (len(SAMPLES) - i), " %s" % sample.name.ljust(19), - libtcod.BKGND_SET, - libtcod.LEFT, + tcod.BKGND_SET, + tcod.LEFT, ) def draw_stats(): - root_console.default_fg = libtcod.grey + root_console.default_fg = tcod.grey root_console.print_( 79, 46, " last frame : %3d ms (%3d fps)" - % ( - libtcod.sys_get_last_frame_length() * 1000.0, - libtcod.sys_get_fps(), - ), - libtcod.BKGND_NONE, - libtcod.RIGHT, + % (tcod.sys_get_last_frame_length() * 1000.0, tcod.sys_get_fps()), + tcod.BKGND_NONE, + tcod.RIGHT, ) root_console.print_( 79, 47, "elapsed : %8d ms %4.2fs" % (time.perf_counter() * 1000, time.perf_counter()), - libtcod.BKGND_NONE, - libtcod.RIGHT, + tcod.BKGND_NONE, + tcod.RIGHT, ) def draw_renderer_menu(): - current_renderer = libtcod.sys_get_renderer() - root_console.default_fg = libtcod.grey - root_console.default_bg = libtcod.black + current_renderer = tcod.sys_get_renderer() + root_console.default_fg = tcod.grey + root_console.default_bg = tcod.black root_console.print_( 42, - 46 - (libtcod.NB_RENDERERS + 1), + 46 - (tcod.NB_RENDERERS + 1), "Renderer :", - libtcod.BKGND_SET, - libtcod.LEFT, + tcod.BKGND_SET, + tcod.LEFT, ) for i, name in enumerate(RENDERER_NAMES): if i == current_renderer: - root_console.default_fg = libtcod.white - root_console.default_bg = libtcod.light_blue + root_console.default_fg = tcod.white + root_console.default_bg = tcod.light_blue else: - root_console.default_fg = libtcod.grey - root_console.default_bg = libtcod.black + root_console.default_fg = tcod.grey + root_console.default_bg = tcod.black root_console.print_( - 42, - 46 - (libtcod.NB_RENDERERS - i), - name, - libtcod.BKGND_SET, - libtcod.LEFT, + 42, 46 - (tcod.NB_RENDERERS - i), name, tcod.BKGND_SET, tcod.LEFT ) From 1d70662e94f6df331add75c2c2d86c23a225c406 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 20:03:59 -0800 Subject: [PATCH 0047/1101] Remove delta_time parameter from samples. --- examples/samples_tcod.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index dd2dca1d..f0ee9fec 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -38,7 +38,7 @@ def __init__(self, name: str = ""): def on_enter(self): pass - def on_draw(self, delta_time: int): + def on_draw(self): pass def ev_keydown(self, event: tcod.event.KeyDown): @@ -100,7 +100,7 @@ def on_enter(self): tcod.sys_set_fps(0) sample_console.clear() - def on_draw(self, delta_time): + def on_draw(self): self.slide_corner_colors() self.interpolate_corner_colors() self.darken_background_characters() @@ -202,14 +202,14 @@ def __init__(self): ) def on_enter(self): + self.counter = time.perf_counter() tcod.sys_set_fps(0) # get a "screenshot" of the current sample screen - sample_console.blit(self.screenshot) + sample_console.blit(dest=self.screenshot) - def on_draw(self, delta_time): - self.counter += delta_time * 1.5 - if self.counter >= 1: - self.counter -= 1 + def on_draw(self): + if time.perf_counter() - self.counter >= 1: + self.counter = time.perf_counter() self.x += self.xdir self.y += self.ydir if self.x == sample_console.width / 2 + 5: @@ -288,7 +288,7 @@ def on_enter(self): tcod.sys_set_fps(0) sample_console.default_fg = tcod.white - def on_draw(self, delta_time): + def on_draw(self): alpha = 0.0 if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: # for the alpha mode, update alpha every frame @@ -398,10 +398,10 @@ def get_noise(self): def on_enter(self): tcod.sys_set_fps(0) - def on_draw(self, delta_time): + def on_draw(self): sample_console.clear() - self.dx += delta_time * 0.25 - self.dy += delta_time * 0.25 + self.dx = time.perf_counter() * 0.25 + self.dy = time.perf_counter() * 0.25 for y in range(2 * sample_console.height): for x in range(2 * sample_console.width): f = [ @@ -593,7 +593,7 @@ def on_enter(self): sample_console.ch[np.where(SAMPLE_MAP == "=")] = tcod.CHAR_DHLINE sample_console.fg[np.where(SAMPLE_MAP == "=")] = tcod.black - def on_draw(self, delta_time): + def on_draw(self): dx = 0.0 dy = 0.0 di = 0.0 @@ -744,7 +744,7 @@ def on_enter(self): ) self.recalculate = True - def on_draw(self, delta_time): + def on_draw(self): if self.recalculate: if self.using_astar: tcod.path_compute( @@ -1070,7 +1070,7 @@ def bsp_refresh(self): for node in copy.deepcopy(self.bsp).inverted_level_order(): traverse_node(self.bsp_map, node) - def on_draw(self, delta_time): + def on_draw(self): sample_console.clear() sample_console.default_fg = tcod.white rooms = "OFF" @@ -1144,7 +1144,7 @@ def __init__(self): def on_enter(self): tcod.sys_set_fps(0) - def on_draw(self, delta_time): + def on_draw(self): sample_console.default_bg = tcod.black sample_console.clear() x = sample_console.width / 2 + math.cos(time.time()) * 10.0 @@ -1227,7 +1227,7 @@ def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp): elif event.button == tcod.event.BUTTON_RIGHT: self.rbut = False - def on_draw(self, delta_time): + def on_draw(self): sample_console.clear() sample_console.print_( 1, @@ -1279,7 +1279,7 @@ def __init__(self): def on_enter(self): tcod.sys_set_fps(60) - def on_draw(self, delta_time): + def on_draw(self): if self.nbsets == 0: # parse all *.cfg files in data/namegen for file in os.listdir("data/namegen"): @@ -1391,7 +1391,7 @@ def on_enter(self): lights = [] # lights list, and current color of the tunnel texture tex_r, tex_g, tex_b = 0, 0, 0 - def on_draw(self, delta_time): + def on_draw(self): global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2 time_delta = tcod.sys_get_last_frame_length() * SPEED # advance time @@ -1564,7 +1564,7 @@ def main(): credits_end = tcod.console_credits_render(60, 43, 0) # render the sample - SAMPLES[cur_sample].on_draw(tcod.sys_get_last_frame_length()) + SAMPLES[cur_sample].on_draw() sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() tcod.console_flush() From 60066f30f07303a0cb7092b760a8e661330a63b9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 29 Jan 2019 21:05:22 -0800 Subject: [PATCH 0048/1101] Add more typing, fix most Mypy and Flake8 warnings. --- setup.cfg | 3 ++ tcod/__init__.py | 1 + tcod/_internal.py | 16 +++++++--- tcod/bsp.py | 16 +++++----- tcod/color.py | 14 ++++----- tcod/console.py | 36 +++++++++++---------- tcod/event.py | 76 ++++++++++++++++++++++---------------------- tcod/libtcod.py | 8 +++-- tcod/libtcodpy.py | 80 +++++++++++++++++++++++++++++------------------ tcod/map.py | 23 +++++++------- tcod/path.py | 10 ++---- tcod/tcod.py | 4 +-- 12 files changed, 161 insertions(+), 126 deletions(-) diff --git a/setup.cfg b/setup.cfg index abc39e65..30f09380 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,3 +6,6 @@ addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl --capture=sys [flake8] ignore = E203 W503 + +[mypy-numpy] +ignore_missing_imports = True diff --git a/tcod/__init__.py b/tcod/__init__.py index ca73e7f0..9afe2f81 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -19,6 +19,7 @@ import warnings +from tcod.libtcod import lib, ffi # noqa: F4 from tcod.libtcodpy import * # noqa: F4 try: diff --git a/tcod/_internal.py b/tcod/_internal.py index d37a501a..89baa1c5 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,27 +1,33 @@ import functools +from typing import Any, Callable, TypeVar, cast import warnings +FuncType = Callable[..., Any] +F = TypeVar("F", bound=FuncType) -def deprecate(message, category=DeprecationWarning, stacklevel=0): - def decorator(func): + +def deprecate( + message: str, category: Any = DeprecationWarning, stacklevel: int = 0 +) -> Callable[[F], F]: + def decorator(func: F) -> F: @functools.wraps(func) def wrapper(*args, **kargs): warnings.warn(message, category, stacklevel=stacklevel + 2) return func(*args, **kargs) - return wrapper + return cast(F, wrapper) return decorator -def verify_order(order): +def verify_order(order: str) -> str: order = order.upper() if order not in ("C", "F"): raise TypeError("order must be 'C' or 'F', not %r" % (order,)) return order -def handle_order(shape, order): +def handle_order(shape: Any, order: str) -> Any: order = verify_order(order) if order == "C": return shape diff --git a/tcod/bsp.py b/tcod/bsp.py index a2cd5c03..9d15b6db 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -23,7 +23,7 @@ else: print('Dig a room for %s.' % node) """ -from typing import Iterator, List, Optional, Union # noqa: F401 +from typing import Any, Iterator, List, Optional, Tuple, Union # noqa: F401 from tcod.libtcod import lib, ffi from tcod._internal import deprecate @@ -43,9 +43,9 @@ class BSP(object): position (int): The integer of where the node was split. horizontal (bool): This nodes split orientation. parent (Optional[BSP]): This nodes parent or None - children (Optional[Tuple[BSP, BSP]]): + children (Union[Tuple[()], Tuple[BSP, BSP]]): A tuple of (left, right) BSP instances, or - None if this BSP has no children. + an empty tuple if this BSP has no children. Args: x (int): Rectangle left coordinate. @@ -55,10 +55,10 @@ class BSP(object): """ def __init__(self, x: int, y: int, width: int, height: int): - self.x = x # type: int - self.y = y # type: int - self.width = width # type: int - self.height = height # type: int + self.x = x + self.y = y + self.width = width + self.height = height self.level = 0 # type: int self.position = 0 # type: int @@ -83,7 +83,7 @@ def h(self) -> int: def h(self, value: int): self.height = value - def _as_cdata(self): + def _as_cdata(self) -> Any: cdata = ffi.gc( lib.TCOD_bsp_new_with_size( self.x, self.y, self.width, self.height diff --git a/tcod/color.py b/tcod/color.py index 0f81aa43..97f2e21e 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -13,34 +13,34 @@ class Color(list): b (int): Blue value, from 0 to 255. """ - def __init__(self, r=0, g=0, b=0): + def __init__(self, r: int = 0, g: int = 0, b: int = 0): self[:] = (r & 0xFF, g & 0xFF, b & 0xFF) @property - def r(self): + def r(self) -> int: """int: Red value, always normalised to 0-255.""" return self[0] @r.setter - def r(self, value): + def r(self, value: int): self[0] = value & 0xFF @property - def g(self): + def g(self) -> int: """int: Green value, always normalised to 0-255.""" return self[1] @g.setter - def g(self, value): + def g(self, value: int): self[1] = value & 0xFF @property - def b(self): + def b(self) -> int: """int: Blue value, always normalised to 0-255.""" return self[2] @b.setter - def b(self, value): + def b(self, value: int): self[2] = value & 0xFF @classmethod diff --git a/tcod/console.py b/tcod/console.py index 5c835313..a1271312 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -23,7 +23,7 @@ # The window is closed here, after the above context exits. """ -from typing import Optional +from typing import Any, Optional, Tuple # noqa: F401 import warnings import numpy as np @@ -95,7 +95,7 @@ def _from_cdata(cls, cdata, order="C"): return self @classmethod - def _get_root(cls, order=None): + def _get_root(cls, order: Optional[str] = None) -> "Console": """Return a root console singleton with valid buffers. This function will also update an already active root console. @@ -110,7 +110,7 @@ def _get_root(cls, order=None): self._init_setup_console_data(self._order) return self - def _init_setup_console_data(self, order="C"): + def _init_setup_console_data(self, order: str = "C") -> None: """Setup numpy arrays over libtcod data buffers.""" global _root_console self._key_color = None @@ -122,7 +122,7 @@ def _init_setup_console_data(self, order="C"): "struct TCOD_Console*", self.console_c ) - def unpack_color(color_data): + def unpack_color(color_data: Any) -> np.array: """return a (height, width, 3) shaped array from an image struct""" color_buffer = ffi.buffer(color_data[0 : self.width * self.height]) array = np.frombuffer(color_buffer, np.uint8) @@ -140,17 +140,17 @@ def unpack_color(color_data): self._order = tcod._internal.verify_order(order) @property - def width(self): + def width(self) -> int: """int: The width of this Console. (read-only)""" return lib.TCOD_console_get_width(self.console_c) @property - def height(self): + def height(self) -> int: """int: The height of this Console. (read-only)""" return lib.TCOD_console_get_height(self.console_c) @property - def bg(self): + def bg(self) -> np.array: """A uint8 array with the shape (height, width, 3). You can change the consoles background colors by using this array. @@ -162,7 +162,7 @@ def bg(self): return self._bg.transpose(1, 0, 2) if self._order == "F" else self._bg @property - def fg(self): + def fg(self) -> np.array: """A uint8 array with the shape (height, width, 3). You can change the consoles foreground colors by using this array. @@ -173,7 +173,7 @@ def fg(self): return self._fg.transpose(1, 0, 2) if self._order == "F" else self._fg @property - def ch(self): + def ch(self) -> np.array: """An integer array with the shape (height, width). You can change the consoles character codes by using this array. @@ -184,7 +184,7 @@ def ch(self): return self._ch.T if self._order == "F" else self._ch @property - def default_bg(self): + def default_bg(self) -> Tuple[int, int, int]: """Tuple[int, int, int]: The default background color.""" color = self._console_data.back return color.r, color.g, color.b @@ -255,9 +255,8 @@ def print_( alignment (Optional[int]): Text alignment. """ alignment = self.default_alignment if alignment is None else alignment - string = _fmt(string) lib.TCOD_console_printf_ex( - self.console_c, x, y, bg_blend, alignment, string + self.console_c, x, y, bg_blend, alignment, _fmt(string) ) def print_rect( @@ -289,9 +288,15 @@ def print_rect( int: The number of lines of text once word-wrapped. """ alignment = self.default_alignment if alignment is None else alignment - string = _fmt(string) return lib.TCOD_console_printf_rect_ex( - self.console_c, x, y, width, height, bg_blend, alignment, string + self.console_c, + x, + y, + width, + height, + bg_blend, + alignment, + _fmt(string), ) def get_height_rect( @@ -309,9 +314,8 @@ def get_height_rect( Returns: int: The number of lines of text once word-wrapped. """ - string = _fmt(string) return lib.TCOD_console_get_height_rect_fmt( - self.console_c, x, y, width, height, string + self.console_c, x, y, width, height, _fmt(string) ) def rect( diff --git a/tcod/event.py b/tcod/event.py index 0b5fe0d5..7daa80e2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -277,18 +277,18 @@ def from_sdl_event(cls, sdl_event): self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) class WindowEvent(Event): def __init__(self, type: str, x: Optional[int] = 0, y: Optional[int] = 0): super().__init__(type) - self.x = x # type(Optional[int]) - self.y = y # type(Optional[int]) + self.x = x + self.y = y @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any): if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) self = cls( @@ -299,7 +299,7 @@ def from_sdl_event(cls, sdl_event): self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: params = "" if self.x or self.y: params = ", x=%r, y=%r" % (self.x, self.y) @@ -332,17 +332,19 @@ def __repr__(self): class Undefined(Event): """This class is a place holder for SDL events without a Python class.""" - def __init__(self): + def __init__(self) -> None: super().__init__("") @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> "Undefined": self = cls() self.sdl_event = sdl_event return self - def __str__(self): - return "" % self.sdl_event.type + def __str__(self) -> str: + if self.sdl_event: + return "" % self.sdl_event.type + return "" _SDL_TO_CLASS_TABLE = { @@ -355,7 +357,7 @@ def __str__(self): tcod.lib.SDL_MOUSEWHEEL: MouseWheel, tcod.lib.SDL_TEXTINPUT: TextInput, tcod.lib.SDL_WINDOWEVENT: WindowEvent, -} +} # type: Dict[int, Any] def get() -> Iterator[Any]: @@ -395,88 +397,88 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: class EventDispatch: - def dispatch(self, event: Any): + def dispatch(self, event: Any) -> None: if event.type: getattr(self, "ev_%s" % (event.type.lower(),))(event) - def event_get(self): + def event_get(self) -> None: for event in get(): self.dispatch(event) - def event_wait(self, timeout: Optional[float]): + def event_wait(self, timeout: Optional[float]) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: Quit): + def ev_quit(self, event: Quit) -> None: pass - def ev_keydown(self, event: KeyDown): + def ev_keydown(self, event: KeyDown) -> None: pass - def ev_keyup(self, event: KeyUp): + def ev_keyup(self, event: KeyUp) -> None: pass - def ev_mousemotion(self, event: MouseMotion): + def ev_mousemotion(self, event: MouseMotion) -> None: pass - def ev_mousebuttondown(self, event: MouseButtonDown): + def ev_mousebuttondown(self, event: MouseButtonDown) -> None: pass - def ev_mousebuttonup(self, event: MouseButtonUp): + def ev_mousebuttonup(self, event: MouseButtonUp) -> None: pass - def ev_mousewheel(self, event: MouseWheel): + def ev_mousewheel(self, event: MouseWheel) -> None: pass - def ev_textinput(self, event: TextInput): + def ev_textinput(self, event: TextInput) -> None: pass - def ev_windowshown(self, event: WindowEvent): + def ev_windowshown(self, event: WindowEvent) -> None: pass - def ev_windowhidden(self, event: WindowEvent): + def ev_windowhidden(self, event: WindowEvent) -> None: pass - def ev_windowexposed(self, event: WindowEvent): + def ev_windowexposed(self, event: WindowEvent) -> None: pass - def ev_windowmoved(self, event: WindowEvent): + def ev_windowmoved(self, event: WindowEvent) -> None: pass - def ev_windowresized(self, event: WindowEvent): + def ev_windowresized(self, event: WindowEvent) -> None: pass - def ev_windowsizechanged(self, event: WindowEvent): + def ev_windowsizechanged(self, event: WindowEvent) -> None: pass - def ev_windowminimized(self, event: WindowEvent): + def ev_windowminimized(self, event: WindowEvent) -> None: pass - def ev_windowmaximized(self, event: WindowEvent): + def ev_windowmaximized(self, event: WindowEvent) -> None: pass - def ev_windowrestored(self, event: WindowEvent): + def ev_windowrestored(self, event: WindowEvent) -> None: pass - def ev_windowenter(self, event: WindowEvent): + def ev_windowenter(self, event: WindowEvent) -> None: pass - def ev_windowleave(self, event: WindowEvent): + def ev_windowleave(self, event: WindowEvent) -> None: pass - def ev_windowfocusgained(self, event: WindowEvent): + def ev_windowfocusgained(self, event: WindowEvent) -> None: pass - def ev_windowfocuslost(self, event: WindowEvent): + def ev_windowfocuslost(self, event: WindowEvent) -> None: pass - def ev_windowclose(self, event: WindowEvent): + def ev_windowclose(self, event: WindowEvent) -> None: pass - def ev_windowtakefocus(self, event: WindowEvent): + def ev_windowtakefocus(self, event: WindowEvent) -> None: pass - def ev_windowhittest(self, event: WindowEvent): + def ev_windowhittest(self, event: WindowEvent) -> None: pass diff --git a/tcod/libtcod.py b/tcod/libtcod.py index 8793c87b..02c9dcf6 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -4,8 +4,9 @@ import os import platform +from typing import Any # noqa: F401 -from tcod import __path__ +from tcod import __path__ # type: ignore if sys.platform == "win32": # add Windows dll's to PATH @@ -39,11 +40,14 @@ def __str__(self): return "?" +lib = None # type: Any +ffi = None # type: Any + if os.environ.get("READTHEDOCS"): # Mock the lib and ffi objects needed to compile docs for readthedocs.io # Allows an import without building the cffi module first. lib = ffi = _Mock() else: - from tcod._libtcod import lib, ffi # noqa: F401 + from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 __all__ = ["ffi", "lib"] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index ab23d57d..ac3999d6 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -436,7 +436,16 @@ class Mouse(_CDataWrapper): """ def __init__( - self, x=0, y=0, dx=0, dy=0, cx=0, cy=0, dcx=0, dcy=0, **kargs + self, + x: int = 0, + y: int = 0, + dx: int = 0, + dy: int = 0, + cx: int = 0, + cy: int = 0, + dcx: int = 0, + dcy: int = 0, + **kargs ): if isinstance(x, ffi.CData): self.cdata = x @@ -771,7 +780,7 @@ def color_gen_map(colors, indexes): def console_init_root( w: int, h: int, - title: Optional[AnyStr] = None, + title: Optional[str] = None, fullscreen: bool = False, renderer: Optional[int] = None, order: str = "C", @@ -810,8 +819,7 @@ def console_init_root( title = os.path.basename(sys.argv[0]) if renderer is None: renderer = RENDERER_GLSL # Stable for now. - title = _bytes(title) - lib.TCOD_console_init_root(w, h, title, fullscreen, renderer) + lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer) return tcod.console.Console._get_root(order) @@ -846,9 +854,8 @@ def console_set_custom_font( raise RuntimeError( "File not found:\n\t%s" % (os.path.realpath(fontFile),) ) - fontFile = _bytes(fontFile) lib.TCOD_console_set_custom_font( - fontFile, flags, nb_char_horiz, nb_char_vertic + _bytes(fontFile), flags, nb_char_horiz, nb_char_vertic ) @@ -1506,8 +1513,8 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]): """ if isinstance(arr, np.ndarray): # numpy arrays, use numpy's ctypes functions - arr = np.ascontiguousarray(arr, dtype=np.intc) - carr = ffi.cast("int *", arr.ctypes.data) + np_array = np.ascontiguousarray(arr, dtype=np.intc) + carr = ffi.cast("int *", np_array.ctypes.data) else: # otherwise convert using the ffi module carr = ffi.new("int[]", arr) @@ -2351,7 +2358,7 @@ def heightmap_get_normal( """ cn = ffi.new("float[3]") lib.TCOD_heightmap_get_normal(_heightmap_cdata(hm), x, y, cn, waterLevel) - return tuple(cn) + return tuple(cn) # type: ignore @deprecate("This function is deprecated, see documentation.") @@ -2627,7 +2634,7 @@ def line_iter(xo, yo, xd, yd): def line_where( x1: int, y1: int, x2: int, y2: int, inclusive: bool = True -) -> Tuple[np.ndarray, np.ndarray]: +) -> Tuple[np.array, np.array]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. @@ -2654,7 +2661,7 @@ def line_where( lib.LineWhere(x1, y1, x2, y2, x, y) if not inclusive: array = array[:, 1:] - return tuple(array) + return tuple(array) # type: ignore def map_new(w, h): @@ -2677,8 +2684,10 @@ def map_copy(source, dest): array attributes manually. """ if source.width != dest.width or source.height != dest.height: - dest.__init__(source.width, source.height, source._order) - dest._Map__buffer[:] = source._Map__buffer[:] + dest.__init__( # type: ignore + source.width, source.height, source._order + ) + dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore def map_set_properties(m, x, y, isTrans, isWalk): @@ -2706,8 +2715,14 @@ def map_clear(m, transparent=False, walkable=False): m.walkable[:] = walkable -def map_compute_fov(m, x, y, radius=0, light_walls=True, algo=FOV_RESTRICTIVE): - # type: (tcod.map.Map, int, int, int, bool, int) -> None +def map_compute_fov( + m: tcod.map.Map, + x: int, + y: int, + radius: int = 0, + light_walls: bool = True, + algo: int = FOV_RESTRICTIVE, +) -> None: """Compute the field-of-view for a map instance. .. deprecated:: 4.5 @@ -2836,7 +2851,10 @@ def namegen_destroy(): def noise_new( - dim, h=NOISE_DEFAULT_HURST, l=NOISE_DEFAULT_LACUNARITY, random=None + dim, + h=NOISE_DEFAULT_HURST, + l=NOISE_DEFAULT_LACUNARITY, # noqa: E741 + random=None, ): """Return a new Noise instance. @@ -3308,7 +3326,7 @@ def struct_get_type(struct, name): # high precision time functions -def sys_set_fps(fps): +def sys_set_fps(fps: int) -> None: """Set the maximum frame rate. You can disable the frame limit again by setting fps to 0. @@ -3319,7 +3337,7 @@ def sys_set_fps(fps): lib.TCOD_sys_set_fps(fps) -def sys_get_fps(): +def sys_get_fps() -> int: """Return the current frames per second. This the actual frame rate, not the frame limit set by @@ -3333,7 +3351,7 @@ def sys_get_fps(): return lib.TCOD_sys_get_fps() -def sys_get_last_frame_length(): +def sys_get_last_frame_length() -> float: """Return the delta time of the last rendered frame in seconds. Returns: @@ -3343,7 +3361,7 @@ def sys_get_last_frame_length(): @deprecate("Use Python's standard 'time' module instead of this function.") -def sys_sleep_milli(val): +def sys_sleep_milli(val: int) -> None: """Sleep for 'val' milliseconds. Args: @@ -3356,7 +3374,7 @@ def sys_sleep_milli(val): @deprecate("Use Python's standard 'time' module instead of this function.") -def sys_elapsed_milli(): +def sys_elapsed_milli() -> int: """Get number of milliseconds since the start of the program. Returns: @@ -3369,7 +3387,7 @@ def sys_elapsed_milli(): @deprecate("Use Python's standard 'time' module instead of this function.") -def sys_elapsed_seconds(): +def sys_elapsed_seconds() -> float: """Get number of seconds since the start of the program. Returns: @@ -3381,7 +3399,7 @@ def sys_elapsed_seconds(): return lib.TCOD_sys_elapsed_seconds() -def sys_set_renderer(renderer): +def sys_set_renderer(renderer: int) -> None: """Change the current rendering mode to renderer. .. deprecated:: 2.0 @@ -3392,7 +3410,7 @@ def sys_set_renderer(renderer): tcod.console.Console._get_root() -def sys_get_renderer(): +def sys_get_renderer() -> int: """Return the current rendering mode. """ @@ -3400,7 +3418,7 @@ def sys_get_renderer(): # easy screenshots -def sys_save_screenshot(name=None): +def sys_save_screenshot(name: Optional[str] = None) -> None: """Save a screenshot to a file. By default this will automatically save screenshots in the working @@ -3412,13 +3430,13 @@ def sys_save_screenshot(name=None): Args: file Optional[AnyStr]: File path to save screenshot. """ - if name is not None: - name = _bytes(name) - lib.TCOD_sys_save_screenshot(name or ffi.NULL) + lib.TCOD_sys_save_screenshot( + _bytes(name) if name is not None else ffi.NULL + ) # custom fullscreen resolution -def sys_force_fullscreen_resolution(width, height): +def sys_force_fullscreen_resolution(width: int, height: int) -> None: """Force a specific resolution in fullscreen. Will use the smallest available resolution so that: @@ -3435,7 +3453,7 @@ def sys_force_fullscreen_resolution(width, height): lib.TCOD_sys_force_fullscreen_resolution(width, height) -def sys_get_current_resolution(): +def sys_get_current_resolution() -> Tuple[int, int]: """Return the current resolution as (width, height) Returns: @@ -3447,7 +3465,7 @@ def sys_get_current_resolution(): return w[0], h[0] -def sys_get_char_size(): +def sys_get_char_size() -> Tuple[int, int]: """Return the current fonts character size as (width, height) Returns: diff --git a/tcod/map.py b/tcod/map.py index 4311ce7c..84945578 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -30,6 +30,8 @@ False """ +from typing import Any + import numpy as np from tcod.libtcod import lib, ffi @@ -60,7 +62,7 @@ class Map(object): """ - def __init__(self, width, height, order="C"): + def __init__(self, width: int, height: int, order: str = "C"): self.width = width self.height = height self._order = tcod._internal.verify_order(order) @@ -68,7 +70,7 @@ def __init__(self, width, height, order="C"): self.__buffer = np.zeros((height, width, 3), dtype=np.bool_) self.map_c = self.__as_cdata() - def __as_cdata(self): + def __as_cdata(self) -> Any: return ffi.new( "struct TCOD_Map*", ( @@ -80,29 +82,28 @@ def __as_cdata(self): ) @property - def transparent(self): + def transparent(self) -> np.array: buffer = self.__buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property - def walkable(self): + def walkable(self) -> np.array: buffer = self.__buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property - def fov(self): + def fov(self) -> np.array: buffer = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer def compute_fov( self, - x, - y, - radius=0, - light_walls=True, - algorithm=tcod.constants.FOV_RESTRICTIVE, + x: int, + y: int, + radius: int = 0, + light_walls: bool = True, + algorithm: int = tcod.constants.FOV_RESTRICTIVE, ): - # type (int, int, int, bool, int) -> None """Compute a field-of-view on the current instance. Args: diff --git a/tcod/path.py b/tcod/path.py index 6373e359..4bedcf61 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -41,10 +41,10 @@ array is used.) """ import numpy as np -from typing import Any, Callable, List, Tuple, Union +from typing import Any, Callable, List, Tuple, Union # noqa: F401 from tcod.libtcod import lib, ffi -import tcod.map +import tcod.map # noqa: F401 @ffi.def_extern() @@ -186,11 +186,7 @@ def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: class _PathFinder(object): """A class sharing methods used by AStar and Dijkstra.""" - def __init__( - self, - cost: Union[tcod.map.Map, np.ndarray, Any], - diagonal: float = 1.41, - ): + def __init__(self, cost: Any, diagonal: float = 1.41): self.cost = cost self.diagonal = diagonal self._path_c = None diff --git a/tcod/tcod.py b/tcod/tcod.py index 0b77a15b..caf5290a 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -6,7 +6,7 @@ from tcod.libtcod import ffi -def _unpack_char_p(char_p): +def _unpack_char_p(char_p: Any) -> str: if char_p == ffi.NULL: return "" return ffi.string(char_p).decode() @@ -135,7 +135,7 @@ def __setattr__(self, attr, value): super(_CDataWrapper, self).__setattr__(attr, value) -def _console(console): +def _console(console: Any) -> Any: """Return a cffi console.""" try: return console.console_c From b937a8ffe49bb8b2e19e0aeb73f7e004376845f5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 31 Jan 2019 15:59:12 -0800 Subject: [PATCH 0049/1101] Refactor WindowEvent into sub-classes. --- tcod/event.py | 72 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 7daa80e2..e8f108aa 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -282,32 +282,28 @@ def __repr__(self) -> str: class WindowEvent(Event): - def __init__(self, type: str, x: Optional[int] = 0, y: Optional[int] = 0): - super().__init__(type) - self.x = x - self.y = y - @classmethod - def from_sdl_event(cls, sdl_event: Any): + def from_sdl_event(cls, sdl_event: Any) -> Any: if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) - self = cls( - cls.__WINDOW_TYPES[sdl_event.window.event], - sdl_event.window.data1, - sdl_event.window.data2, - ) + event_type = cls.__WINDOW_TYPES[sdl_event.window.event] + self = None # type: Any + if sdl_event.window.event == tcod.lib.SDL_WINDOWEVENT_MOVED: + self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) + elif sdl_event.window.event in ( + tcod.lib.SDL_WINDOWEVENT_RESIZED, + tcod.lib.SDL_WINDOWEVENT_SIZE_CHANGED, + ): + self = WindowResized( + event_type, sdl_event.window.data1, sdl_event.window.data2 + ) + else: + self = cls(event_type) self.sdl_event = sdl_event return self def __repr__(self) -> str: - params = "" - if self.x or self.y: - params = ", x=%r, y=%r" % (self.x, self.y) - return "tcod.event.%s(type=%r%s)" % ( - self.__class__.__name__, - self.type, - params, - ) + return "tcod.event.%s(type=%r)" % (self.__class__.__name__, self.type) __WINDOW_TYPES = { tcod.lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", @@ -329,6 +325,36 @@ def __repr__(self) -> str: } +class WindowMoved(WindowEvent): + def __init__(self, x: int, y: int) -> None: + super().__init__(None) + self.x = x + self.y = y + + def __repr__(self) -> str: + return "tcod.event.%s(type=%r, x=%r, y=%r)" % ( + self.__class__.__name__, + self.type, + self.x, + self.y, + ) + + +class WindowResized(WindowEvent): + def __init__(self, type: str, width: int, height: int) -> None: + super().__init__(type) + self.width = width + self.height = height + + def __repr__(self) -> str: + return "tcod.event.%s(type=%r, width=%r, height=%r)" % ( + self.__class__.__name__, + self.type, + self.width, + self.height, + ) + + class Undefined(Event): """This class is a place holder for SDL events without a Python class.""" @@ -442,13 +468,13 @@ def ev_windowhidden(self, event: WindowEvent) -> None: def ev_windowexposed(self, event: WindowEvent) -> None: pass - def ev_windowmoved(self, event: WindowEvent) -> None: + def ev_windowmoved(self, event: WindowMoved) -> None: pass - def ev_windowresized(self, event: WindowEvent) -> None: + def ev_windowresized(self, event: WindowResized) -> None: pass - def ev_windowsizechanged(self, event: WindowEvent) -> None: + def ev_windowsizechanged(self, event: WindowResized) -> None: pass def ev_windowminimized(self, event: WindowEvent) -> None: @@ -506,6 +532,8 @@ def ev_windowhittest(self, event: WindowEvent) -> None: "MouseWheel", "TextInput", "WindowEvent", + "WindowMoved", + "WindowResized", "Undefined", "get", "wait", From 9b6897a89bbfeb39b565d5d692517059fc7b5525 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 31 Jan 2019 18:18:56 -0800 Subject: [PATCH 0050/1101] Fix samples line length. --- examples/samples_tcod.py | 49 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f0ee9fec..c7127481 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -928,6 +928,8 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion): bsp_random_room = False # if true, there is always a wall on north & west side of a room bsp_room_walls = True + + # draw a vertical line def vline(m, x, y1, y2): if y1 > y2: @@ -1352,7 +1354,8 @@ def ev_keydown(self, event: tcod.event.KeyDown): ) # how much the ambient light changes to reflect current light sources AMBIENT_LIGHT = 0.8 # brightness of tunnel texture -# the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen) +# the coordinates of all tiles in the screen, as numpy arrays. +# example: (4x3 pixels screen) # xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] if numpy_available: @@ -1384,26 +1387,28 @@ def on_enter(self): sample_console.default_fg = tcod.white sample_console.print_(1, SCREEN_H - 3, "Renderer: NumPy") - frac_t = ( - RES_V - 1 - ) # time is represented in number of pixels of the texture, start later in time to initialize texture + # time is represented in number of pixels of the texture, start later + # in time to initialize texture + frac_t = RES_V - 1 abs_t = RES_V - 1 lights = [] # lights list, and current color of the tunnel texture tex_r, tex_g, tex_b = 0, 0, 0 def on_draw(self): - global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2 + global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc + global texture, texture2, brightness2, R2, G2, B2 time_delta = tcod.sys_get_last_frame_length() * SPEED # advance time frac_t += time_delta # increase fractional (always < 1.0) time abs_t += time_delta # increase absolute elapsed time - int_t = int( - frac_t - ) # integer time units that passed this frame (number of texture pixels to advance) + # integer time units that passed this frame (number of texture pixels + # to advance) + int_t = int(frac_t) frac_t -= int_t # keep this < 1.0 - # change texture color according to presence of lights (basically, sum them - # to get ambient light and smoothly change the current color into that) + # change texture color according to presence of lights (basically, sum + # them to get ambient light and smoothly change the current color into + # that) ambient_r = AMBIENT_LIGHT * sum( light.r * light.strength for light in lights ) @@ -1418,15 +1423,13 @@ def on_draw(self): tex_g = tex_g * (1 - alpha) + ambient_g * alpha tex_b = tex_b * (1 - alpha) + ambient_b * alpha - if ( - int_t >= 1 - ): # roll texture (ie, advance in tunnel) according to int_t - int_t = ( - int_t % RES_V - ) # can't roll more than the texture's size (can happen when time_delta is large) - int_abs_t = int( - abs_t - ) # new pixels are based on absolute elapsed time + if int_t >= 1: + # roll texture (ie, advance in tunnel) according to int_t + # can't roll more than the texture's size (can happen when + # time_delta is large) + int_t = int_t % RES_V + # new pixels are based on absolute elapsed time + int_abs_t = int(abs_t) texture = np.roll(texture, -int_t, 1) # replace new stretch of texture with new values @@ -1481,13 +1484,15 @@ def on_draw(self): ] for light in lights: # render lights - # move light's Z coordinate with time, then project its XYZ coordinates to screen-space + # move light's Z coordinate with time, then project its XYZ + # coordinates to screen-space light.z -= float(time_delta) / TEX_STRETCH xl = light.x / light.z * SCREEN_H yl = light.y / light.z * SCREEN_H - # calculate brightness of light according to distance from viewer and strength, - # then calculate brightness of each pixel with inverse square distance law + # calculate brightness of light according to distance from viewer + # and strength, then calculate brightness of each pixel with + # inverse square distance law light_brightness = ( LIGHT_BRIGHTNESS * light.strength From 4dbca07df695195211926f338cf6d2208fcde270 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 31 Jan 2019 23:17:47 -0800 Subject: [PATCH 0051/1101] Prepare 8.4.0 release. --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7a4b706c..53483351 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,12 +9,16 @@ v2.0.0 Unreleased ------------------ + +8.4.0 - 2019-01-31 +------------------ Added - Added tcod.event module, based off of the sdlevent.py shim. Changed - Updated libtcod to 1.10.3 Fixed - Fixed libtcodpy `struct_add_value_list` function. + - Fixed all issues with tcod.Mouse attributes: cx, cy, dcx, dcy. 8.3.2 - 2018-12-28 ------------------ From 9555d14d725f8b7858e01b0131e5f7cb8285094a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Feb 2019 15:42:08 -0800 Subject: [PATCH 0052/1101] Import changelog updates from libtcod. [skip ci] --- CHANGELOG.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 53483351..e35cff1e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,7 +18,9 @@ Changed - Updated libtcod to 1.10.3 Fixed - Fixed libtcodpy `struct_add_value_list` function. - - Fixed all issues with tcod.Mouse attributes: cx, cy, dcx, dcy. + - Use correct math for tile-based delta in mouse events. + - New renderers now support tile-based mouse coordinates. + - SDL2 renderer will now properly refresh after the window is resized. 8.3.2 - 2018-12-28 ------------------ From 8d2ca03f09162ffd74256878ad22065513899e3a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Feb 2019 17:28:02 -0800 Subject: [PATCH 0053/1101] Make sure window event types are upper-case. --- CHANGELOG.rst | 2 ++ tcod/event.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e35cff1e..52f94e2e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Window event types were not upper-case. 8.4.0 - 2019-01-31 ------------------ diff --git a/tcod/event.py b/tcod/event.py index e8f108aa..fde52cc0 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -286,7 +286,7 @@ class WindowEvent(Event): def from_sdl_event(cls, sdl_event: Any) -> Any: if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) - event_type = cls.__WINDOW_TYPES[sdl_event.window.event] + event_type = cls.__WINDOW_TYPES[sdl_event.window.event].upper() self = None # type: Any if sdl_event.window.event == tcod.lib.SDL_WINDOWEVENT_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) From f7f7b95e0a49c82d80df8cff1d33020db597d143 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Feb 2019 18:43:27 -0800 Subject: [PATCH 0054/1101] Add documentation for tcod.event module. --- docs/index.rst | 1 + docs/tcod/event.rst | 5 + tcod/event.py | 292 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 231 insertions(+), 67 deletions(-) create mode 100644 docs/tcod/event.rst diff --git a/docs/index.rst b/docs/index.rst index a0a14574..5efcec75 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,7 @@ Contents: :caption: python-tcod API tcod + tcod/event libtcodpy .. toctree:: diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst new file mode 100644 index 00000000..20297694 --- /dev/null +++ b/docs/tcod/event.rst @@ -0,0 +1,5 @@ +tcod.event +========== + +.. automodule:: tcod.event + :members: diff --git a/tcod/event.py b/tcod/event.py index fde52cc0..e77d18be 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1,20 +1,17 @@ """ -An alternative, more direct implementation of event handling using cffi -calls to SDL functions. The current code is incomplete, but can be -extended easily by following the official SDL documentation. - -This module be run directly like any other event get example, but is meant -to be copied into your code base. Then you can use the tcod.event.get and -tcod.event.wait functions in your code. +An alternative, more direct implementation of event handling based on using +cffi calls to SDL functions. The current code is partially incomplete. Printing any event will tell you its attributes in a human readable format. -An events type attr is just the classes name with all letters upper-case. +An events type attribute if omitted is just the classes name with all letters +upper-case. Do not use :any:`isinstance` to tell events apart as that method +won't be forward compatible. -Like in tdl, events use a type attribute to tell events apart. Unlike tdl -and tcod the names and values used are directly derived from SDL. +As a general guideline, you should use :any:`KeyboardEvent.sym` for command +inputs, and :any:`TextInput.text` for name entry fields. -As a general guideline for turn-based rouge-likes, you should use -KeyDown.sym for commands, and TextInput.text for name entry fields. +Remember to add the line ``import tcod.event``, as importing this module is not +implied by ``import tcod``. .. versionadded:: 8.4 """ @@ -26,7 +23,7 @@ def _describe_bitmask( - bits: int, table: Dict[Any, str], default: Any = "0" + bits: int, table: Dict[Any, str], default: str = "0" ) -> str: """Returns a bitmask in human readable form. @@ -69,6 +66,13 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: BUTTON_X1MASK = 0x08 BUTTON_X2MASK = 0x10 +KMOD_SHIFT = ( + tcod.event_constants.KMOD_LSHIFT | tcod.event_constants.KMOD_RSHIFT +) +KMOD_CTRL = tcod.event_constants.KMOD_LCTRL | tcod.event_constants.KMOD_RCTRL +KMOD_ALT = tcod.event_constants.KMOD_LALT | tcod.event_constants.KMOD_RALT +KMOD_GUI = tcod.event_constants.KMOD_LGUI | tcod.event_constants.KMOD_RGUI + # reverse tables are used to get the tcod.event name from the value. _REVERSE_BUTTON_TABLE = { BUTTON_LEFT: "tcod.event.BUTTON_LEFT", @@ -88,7 +92,13 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: class Event: - """The base event class.""" + """The base event class. + + Attributes: + type (str): This events type. + sdl_event: When available, this holds a python-cffi 'SDL_Event*' + pointer. All sub-classes have this attribute. + """ def __init__(self, type: Optional[str] = None): if type is None: @@ -97,8 +107,8 @@ def __init__(self, type: Optional[str] = None): self.sdl_event = None @classmethod - def from_sdl_event(cls, sdl_event): - """Return a class instance from a cffi SDL_Event pointer.""" + def from_sdl_event(cls, sdl_event: Any) -> Any: + """Return a class instance from a python-cffi 'SDL_Event*' pointer.""" raise NotImplementedError() @@ -107,19 +117,32 @@ class Quit(Event): For more info on when this event is triggered see: https://wiki.libsdl.org/SDL_EventType#SDL_QUIT + + Attributes: + type (str): Always "QUIT". """ @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> "Quit": self = cls() self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return "tcod.event.%s()" % self.__class__.__name__ class KeyboardEvent(Event): + """ + Attributes: + type (str): Will be "KEYDOWN" or "KEYUP", depending on the event. + scancode (int): The keyboard scan-code, this is the physical location + of the key on the keyboard rather than the keys symbol. + sym (int): The keyboard symbol. + mod (int): A bitmask of modifier keys. + repeat (bool): True if this event exists because of key repeat. + """ + def __init__( self, scancode: int, sym: int, mod: int, repeat: bool = False ): @@ -130,7 +153,7 @@ def __init__( self.repeat = repeat @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> Any: keysym = sdl_event.key.keysym self = cls( keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat) @@ -138,7 +161,7 @@ def from_sdl_event(cls, sdl_event): self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( self.__class__.__name__, tcod.event_constants._REVERSE_SCANCODE_TABLE[self.scancode], @@ -159,6 +182,24 @@ class KeyUp(KeyboardEvent): class MouseMotion(Event): + """ + Attributes: + type (str): Always "MOUSEMOTION". + pixel (Point): The pixel coordinates of the mouse. + pixel_motion (Point): The pixel delta. + tile (Point): The integer tile coordinates of the mouse on the screen. + tile_motion (Point): The integer tile delta. + state (int): A bitmask of which mouse buttons are currently held. + + Will be a combination of the following names: + + * tcod.event.BUTTON_LMASK + * tcod.event.BUTTON_MMASK + * tcod.event.BUTTON_RMASK + * tcod.event.BUTTON_X1MASK + * tcod.event.BUTTON_X2MASK + """ + def __init__( self, pixel: Tuple[int, int] = (0, 0), @@ -175,7 +216,7 @@ def __init__( self.state = state @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": motion = sdl_event.motion pixel = motion.x, motion.y @@ -190,7 +231,7 @@ def from_sdl_event(cls, sdl_event): self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return ( "tcod.event.%s(pixel=%r, pixel_motion=%r, " "tile=%r, tile_motion=%r, state=%s)" @@ -205,6 +246,23 @@ def __repr__(self): class MouseButtonEvent(Event): + """ + Attributes: + type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", + depending on the event. + pixel (Point): The pixel coordinates of the mouse. + tile (Point): The integer tile coordinates of the mouse on the screen. + button (int): Which mouse button. + + This will be one of the following names: + + * tcod.event.BUTTON_LEFT + * tcod.event.BUTTON_MIDDLE + * tcod.event.BUTTON_RIGHT + * tcod.event.BUTTON_X1 + * tcod.event.BUTTON_X2 + """ + def __init__( self, pixel: Tuple[int, int] = (0, 0), @@ -217,7 +275,7 @@ def __init__( self.button = button @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> Any: button = sdl_event.button pixel = button.x, button.y subtile = _pixel_to_tile(*pixel) @@ -226,7 +284,7 @@ def from_sdl_event(cls, sdl_event): self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return "tcod.event.%s(pixel=%r, tile=%r, button=%s)" % ( self.__class__.__name__, self.pixel, @@ -244,6 +302,16 @@ class MouseButtonUp(MouseButtonEvent): class MouseWheel(Event): + """ + Attributes: + type (str): Always "MOUSEWHEEL". + x (int): Horizontal scrolling. A positive value means scrolling right. + y (int): Vertical scrolling. A positive value means scrolling away from + the user. + flipped (bool): If True then the values of `x` and `y` are the opposite + of their usual values. + """ + def __init__(self, x: int, y: int, flipped: bool = False): super().__init__() self.x = x @@ -251,13 +319,13 @@ def __init__(self, x: int, y: int, flipped: bool = False): self.flipped = flipped @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> "MouseWheel": wheel = sdl_event.wheel self = cls(wheel.x, wheel.y, bool(wheel.direction)) self.sdl_event = sdl_event return self - def __repr__(self): + def __repr__(self) -> str: return "tcod.event.%s(x=%i, y=%i%s)" % ( self.__class__.__name__, self.x, @@ -267,12 +335,18 @@ def __repr__(self): class TextInput(Event): + """ + Attributes: + type (str): Always "TEXTINPUT". + text (str): A Unicode string with the input. + """ + def __init__(self, text: str): super().__init__() self.text = text @classmethod - def from_sdl_event(cls, sdl_event): + def from_sdl_event(cls, sdl_event: Any) -> "TextInput": self = cls(tcod.ffi.string(sdl_event.text.text, 32).decode("utf8")) self.sdl_event = sdl_event return self @@ -282,6 +356,11 @@ def __repr__(self) -> str: class WindowEvent(Event): + """ + Attributes: + type (str): A window event could mean various event types. + """ + @classmethod def from_sdl_event(cls, sdl_event: Any) -> Any: if sdl_event.window.event not in cls.__WINDOW_TYPES: @@ -326,6 +405,13 @@ def __repr__(self) -> str: class WindowMoved(WindowEvent): + """ + Attributes: + type (str): Always "WINDOWMOVED". + x (int): Movement on the x-axis. + x (int): Movement on the y-axis. + """ + def __init__(self, x: int, y: int) -> None: super().__init__(None) self.x = x @@ -341,6 +427,13 @@ def __repr__(self) -> str: class WindowResized(WindowEvent): + """ + Attributes: + type (str): "WINDOWRESIZED" or "WINDOWSIZECHANGED" + width (int): The current width of the window. + height (int): The current height of the window. + """ + def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) self.width = width @@ -356,7 +449,9 @@ def __repr__(self) -> str: class Undefined(Event): - """This class is a place holder for SDL events without a Python class.""" + """This class is a place holder for SDL events without their own tcod.event + class. + """ def __init__(self) -> None: super().__init__("") @@ -387,11 +482,25 @@ def __str__(self) -> str: def get() -> Iterator[Any]: - """Iterate over all pending events. - - Returns: - Iterator[tcod.event.Event]: - An iterator of Event subclasses. + """Return an iterator for all pending events. + + Events are processed as the iterator is consumed. Breaking out of, or + discarding the iterator will leave the remaining events on the event queue. + + Example:: + + for event in tcod.event.get(): + if event.type == "QUIT": + print(event) + raise SystemExit() + elif event.type == "KEYDOWN": + print(event) + elif event.type == "MOUSEBUTTONDOWN": + print(event) + elif event.type == "MOUSEMOTION": + print(event) + else: + print(event) """ sdl_event = tcod.ffi.new("SDL_Event*") while tcod.lib.SDL_PollEvent(sdl_event): @@ -402,18 +511,27 @@ def get() -> Iterator[Any]: def wait(timeout: Optional[float] = None) -> Iterator[Any]: - """Block until events exist, then iterate over all events. - - Keep in mind that this function will wake even for events not handled by - this module. - - Args: - timeout (Optional[float]): - Maximum number of seconds to wait, or None to wait forever. - Has millisecond percision. - - Returns: - Iterator[tcod.event.Event]: Same iterator as a call to tcod.event.get + """Block until events exist, then return an event iterator. + + `timeout` is the maximum number of seconds to wait as a floating point + number with millisecond precision, or it can be None to wait forever. + + Returns the same iterator as a call to :any:`tcod.event.get`. + + Example:: + + for event in tcod.event.wait(): + if event.type == "QUIT": + print(event) + raise SystemExit() + elif event.type == "KEYDOWN": + print(event) + elif event.type == "MOUSEBUTTONDOWN": + print(event) + elif event.type == "MOUSEMOTION": + print(event) + else: + print(event) """ if timeout is not None: tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, int(timeout * 1000)) @@ -423,7 +541,44 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: class EventDispatch: + """This class dispatches events to methods depending on the events type + attribute. + + To use this class, make a sub-class and override the relevant `ev_*` + methods. Then send events to the dispatch method. + + Example:: + + import tcod + import tcod.event + + class State(tcod.event.EventDispatch): + def ev_quit(self, event): + raise SystemExit() + + def ev_keydown(self, event): + print(event) + + def ev_mousebuttondown(self, event): + print(event) + + def ev_mousemotion(self, event): + print(event) + + root_console = tcod.console_init_root(80, 60) + state = State() + while True: + for event in tcod.event.wait() + state.dispatch(event) + """ + def dispatch(self, event: Any) -> None: + """Send an event to an `ev_*` method. + + `*` will be the events type converted to lower-case. + + If `event.type` is an empty string or None then it will be ignored. + """ if event.type: getattr(self, "ev_%s" % (event.type.lower(),))(event) @@ -436,70 +591,73 @@ def event_wait(self, timeout: Optional[float]) -> None: self.event_get() def ev_quit(self, event: Quit) -> None: - pass + """Called when the termination of the program is requested.""" def ev_keydown(self, event: KeyDown) -> None: - pass + """Called when a keyboard key is pressed or repeated.""" def ev_keyup(self, event: KeyUp) -> None: - pass + """Called when a keyboard key is released.""" def ev_mousemotion(self, event: MouseMotion) -> None: - pass + """Called when the mouse is moved.""" def ev_mousebuttondown(self, event: MouseButtonDown) -> None: - pass + """Called when a mouse button is pressed.""" def ev_mousebuttonup(self, event: MouseButtonUp) -> None: - pass + """Called when a mouse button is released.""" def ev_mousewheel(self, event: MouseWheel) -> None: - pass + """Called when the mouse wheel is scrolled.""" def ev_textinput(self, event: TextInput) -> None: - pass + """Called to handle Unicode input.""" def ev_windowshown(self, event: WindowEvent) -> None: - pass + """Called when the window is shown.""" def ev_windowhidden(self, event: WindowEvent) -> None: - pass + """Called when the window is hidden.""" def ev_windowexposed(self, event: WindowEvent) -> None: - pass + """Called when a window is exposed, and needs to be refreshed. + + This usually means a call to :any:`tcod.console_flush` is necessary. + """ def ev_windowmoved(self, event: WindowMoved) -> None: - pass + """Called when the window is moved.""" def ev_windowresized(self, event: WindowResized) -> None: - pass + """Called when the window is resized.""" def ev_windowsizechanged(self, event: WindowResized) -> None: - pass + """Called when the system or user changes the size of the window.""" def ev_windowminimized(self, event: WindowEvent) -> None: - pass + """Called when the window is minimized.""" def ev_windowmaximized(self, event: WindowEvent) -> None: - pass + """Called when the window is maximized.""" def ev_windowrestored(self, event: WindowEvent) -> None: - pass + """Called when the window is restored.""" def ev_windowenter(self, event: WindowEvent) -> None: - pass + """Called when the window gains mouse focus.""" def ev_windowleave(self, event: WindowEvent) -> None: - pass + """Called when the window loses mouse focus.""" def ev_windowfocusgained(self, event: WindowEvent) -> None: - pass + """Called when the window gains keyboard focus.""" def ev_windowfocuslost(self, event: WindowEvent) -> None: - pass + """Called when the window loses keyboard focus.""" def ev_windowclose(self, event: WindowEvent) -> None: - pass + """Called when the window manager requests the window to be closed.""" def ev_windowtakefocus(self, event: WindowEvent) -> None: pass From 1e119b0d4914e1dfd1a9829caba84fb2a285c12a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Feb 2019 19:02:52 -0800 Subject: [PATCH 0055/1101] Fix libtcodpy mouse wheel regression. Fixes #62 --- CHANGELOG.rst | 1 + libtcod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52f94e2e..beda6dce 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased ------------------ Fixed - Window event types were not upper-case. + - Fixed regression where libtcodpy mouse wheel events unset mouse coordinates. 8.4.0 - 2019-01-31 ------------------ diff --git a/libtcod b/libtcod index 4e1fb18b..72d0c4fa 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 4e1fb18bcb1cbb9e7d6666aeda5df6beb3aa1f93 +Subproject commit 72d0c4fa2dc0a4490dec801dfb27b28ff1eb53e0 From 30f92e45dc95044a8c050c9b51e3909265330d15 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Feb 2019 19:54:27 -0800 Subject: [PATCH 0056/1101] Prepare 8.4.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index beda6dce..7062f9d7 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +8.4.1 - 2019-02-01 +------------------ Fixed - Window event types were not upper-case. - Fixed regression where libtcodpy mouse wheel events unset mouse coordinates. From f083572e56be056750c9d840b7a12c8957a4ef0a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 04:21:14 -0800 Subject: [PATCH 0057/1101] Deprecated the tdl module. --- CHANGELOG.rst | 2 ++ tdl/__init__.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7062f9d7..c28f6c0e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Deprecated + - The tdl module has been deprecated. 8.4.1 - 2019-02-01 ------------------ diff --git a/tdl/__init__.py b/tdl/__init__.py index 494027c2..30c7d730 100755 --- a/tdl/__init__.py +++ b/tdl/__init__.py @@ -1,4 +1,7 @@ """ + .. deprecated:: 8.4 + This module has been deprecated. + Getting Started =============== Once the library is imported you can load the font you want to use with @@ -72,6 +75,12 @@ from tcod import __version__ +_warnings.warn( + "The tdl module has been deprecated.", + DeprecationWarning, + stacklevel=2, +) + _IS_PYTHON3 = (_sys.version_info[0] == 3) if _IS_PYTHON3: # some type lists to use with isinstance From d156a5ccab7079dbcf1f703e04e53944e4674514 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 04:47:53 -0800 Subject: [PATCH 0058/1101] Update tcod.console type hints. --- tcod/_internal.py | 2 +- tcod/console.py | 122 ++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/tcod/_internal.py b/tcod/_internal.py index 89baa1c5..44b905f5 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -11,7 +11,7 @@ def deprecate( ) -> Callable[[F], F]: def decorator(func: F) -> F: @functools.wraps(func) - def wrapper(*args, **kargs): + def wrapper(*args, **kargs): # type: ignore warnings.warn(message, category, stacklevel=stacklevel + 2) return func(*args, **kargs) diff --git a/tcod/console.py b/tcod/console.py index a1271312..73e875ec 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -41,7 +41,7 @@ def _fmt(string: str) -> bytes: _root_console = None -class Console(object): +class Console: """A console object containing a grid of characters with foreground/background colors. @@ -57,8 +57,8 @@ class Console(object): console_c (CData): A cffi pointer to a TCOD_console_t object. """ - def __init__(self, width, height, order="C"): - self._key_color = None + def __init__(self, width: int, height: int, order: str = "C"): + self._key_color = None # type: Optional[Tuple[int, int, int]] self._ch = np.full((height, width), 0x20, dtype=np.intc) self._fg = np.zeros((height, width), dtype="(3,)u1") self._bg = np.zeros((height, width), dtype="(3,)u1") @@ -86,10 +86,10 @@ def __init__(self, width, height, order="C"): ) @classmethod - def _from_cdata(cls, cdata, order="C"): + def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": if isinstance(cdata, cls): return cdata - self = object.__new__(cls) + self = object.__new__(cls) # type: Console self.console_c = cdata self._init_setup_console_data(order) return self @@ -103,7 +103,7 @@ def _get_root(cls, order: Optional[str] = None) -> "Console": global _root_console if _root_console is None: _root_console = object.__new__(cls) - self = _root_console + self = _root_console # type: Console if order is not None: self._order = order self.console_c = ffi.NULL @@ -142,12 +142,12 @@ def unpack_color(color_data: Any) -> np.array: @property def width(self) -> int: """int: The width of this Console. (read-only)""" - return lib.TCOD_console_get_width(self.console_c) + return lib.TCOD_console_get_width(self.console_c) # type: ignore @property def height(self) -> int: """int: The height of this Console. (read-only)""" - return lib.TCOD_console_get_height(self.console_c) + return lib.TCOD_console_get_height(self.console_c) # type: ignore @property def bg(self) -> np.array: @@ -190,43 +190,49 @@ def default_bg(self) -> Tuple[int, int, int]: return color.r, color.g, color.b @default_bg.setter - def default_bg(self, color): + def default_bg(self, color: Tuple[int, int, int]) -> None: self._console_data.back = color @property - def default_fg(self): + def default_fg(self) -> Tuple[int, int, int]: """Tuple[int, int, int]: The default foreground color.""" color = self._console_data.fore return color.r, color.g, color.b @default_fg.setter - def default_fg(self, color): + def default_fg(self, color: Tuple[int, int, int]) -> None: self._console_data.fore = color @property - def default_bg_blend(self): + def default_bg_blend(self) -> int: """int: The default blending mode.""" - return self._console_data.bkgnd_flag + return self._console_data.bkgnd_flag # type: ignore @default_bg_blend.setter - def default_bg_blend(self, value): + def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @property - def default_alignment(self): + def default_alignment(self) -> int: """int: The default text alignment.""" - return self._console_data.alignment + return self._console_data.alignment # type: ignore @default_alignment.setter - def default_alignment(self, value): + def default_alignment(self, value: int) -> None: self._console_data.alignment = value - def clear(self): + def clear(self) -> None: """Reset this console to its default colors and the space character. """ lib.TCOD_console_clear(self.console_c) - def put_char(self, x, y, ch, bg_blend=tcod.constants.BKGND_DEFAULT): + def put_char( + self, + x: int, + y: int, + ch: int, + bg_blend: int = tcod.constants.BKGND_DEFAULT, + ) -> None: """Draw the character c at x,y using the default colors and a blend mode. Args: @@ -250,7 +256,7 @@ def print_( Args: x (int): The x coordinate from the left. y (int): The y coordinate from the top. - string (str): A Unicode string optionaly using color codes. + string (str): A Unicode string optionally using color codes. bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT. alignment (Optional[int]): Text alignment. """ @@ -288,7 +294,7 @@ def print_rect( int: The number of lines of text once word-wrapped. """ alignment = self.default_alignment if alignment is None else alignment - return lib.TCOD_console_printf_rect_ex( + return lib.TCOD_console_printf_rect_ex( # type: ignore self.console_c, x, y, @@ -314,7 +320,7 @@ def get_height_rect( Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_get_height_rect_fmt( + return lib.TCOD_console_get_height_rect_fmt( # type: ignore self.console_c, x, y, width, height, _fmt(string) ) @@ -344,7 +350,13 @@ def rect( self.console_c, x, y, width, height, clear, bg_blend ) - def hline(self, x, y, width, bg_blend=tcod.constants.BKGND_DEFAULT): + def hline( + self, + x: int, + y: int, + width: int, + bg_blend: int = tcod.constants.BKGND_DEFAULT, + ) -> None: """Draw a horizontal line on the console. This always uses the character 196, the horizontal line character. @@ -352,12 +364,18 @@ def hline(self, x, y, width, bg_blend=tcod.constants.BKGND_DEFAULT): Args: x (int): The x coordinate from the left. y (int): The y coordinate from the top. - width (int): The horozontal length of this line. + width (int): The horizontal length of this line. bg_blend (int): The background blending flag. """ lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend) - def vline(self, x, y, height, bg_blend=tcod.constants.BKGND_DEFAULT): + def vline( + self, + x: int, + y: int, + height: int, + bg_blend: int = tcod.constants.BKGND_DEFAULT, + ) -> None: """Draw a vertical line on the console. This always uses the character 179, the vertical line character. @@ -379,7 +397,7 @@ def print_frame( string: str = "", clear: bool = True, bg_blend: int = tcod.constants.BKGND_DEFAULT, - ): + ) -> None: """Draw a framed rectangle with optinal text. This uses the default background color and blend mode to fill the @@ -408,17 +426,17 @@ def print_frame( def blit( self, - dest, - dest_x=0, - dest_y=0, - src_x=0, - src_y=0, - width=0, - height=0, - fg_alpha=1.0, - bg_alpha=1.0, - key_color=None, - ): + dest: "Console", + dest_x: int = 0, + dest_y: int = 0, + src_x: int = 0, + src_y: int = 0, + width: int = 0, + height: int = 0, + fg_alpha: float = 1.0, + bg_alpha: float = 1.0, + key_color: Optional[Tuple[int, int, int]] = None, + ) -> None: """Blit from this console onto the ``dest`` console. Args: @@ -446,15 +464,15 @@ def blit( """ # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): - src_x, src_y, width, height, dest, dest_x, dest_y = ( - dest, - dest_x, - dest_y, - src_x, + ( + src_x, # type: ignore src_y, width, height, - ) + dest, # type: ignore + dest_x, + dest_y, + ) = (dest, dest_x, dest_y, src_x, src_y, width, height) warnings.warn( "Parameter names have been moved around, see documentation.", DeprecationWarning, @@ -490,7 +508,7 @@ def blit( bg_alpha, ) - def set_key_color(self, color): + def set_key_color(self, color: Tuple[int, int, int]) -> None: """Set a consoles blit transparent color. Args: @@ -498,7 +516,7 @@ def set_key_color(self, color): """ self._key_color = color - def __enter__(self): + def __enter__(self) -> "Console": """Returns this console in a managed context. When the root console is used as a context, the graphical window will @@ -512,23 +530,23 @@ def __enter__(self): raise NotImplementedError("Only the root console has a context.") return self - def __exit__(self, *args): + def __exit__(self, *args: Any) -> None: """Closes the graphical window on exit. - Some tcod functions may have undefined behavior after this point. + Some tcod functions may have undefined behaviour after this point. """ lib.TCOD_console_delete(self.console_c) - def __bool__(self): + def __bool__(self) -> bool: """Returns False if this is the root console. - This mimics libtcodpy behavior. + This mimics libtcodpy behaviour. """ - return self.console_c != ffi.NULL + return bool(self.console_c != ffi.NULL) __nonzero__ = __bool__ - def __getstate__(self): + def __getstate__(self) -> Any: state = self.__dict__.copy() del state["console_c"] state["_console_data"] = { @@ -545,7 +563,7 @@ def __getstate__(self): state["_bg"] = np.copy(self._bg) return state - def __setstate__(self, state): + def __setstate__(self, state: Any) -> None: self._key_color = None self.__dict__.update(state) self._console_data.update( From 7b1a7e598bf73c67995b0348e9297904d76857ab Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 05:12:39 -0800 Subject: [PATCH 0059/1101] Warn if the root console is implicitly deleted. --- tcod/libtcodpy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index ac3999d6..eb9dc980 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3,6 +3,7 @@ import os import sys +import atexit import threading from typing import Any, AnyStr, Optional, Sequence, Tuple import warnings @@ -3573,3 +3574,17 @@ def sys_clipboard_get(): This function does not provide reliable access to the clipboard. """ return ffi.string(lib.TCOD_sys_clipboard_get()).decode("utf-8") + + +@atexit.register +def _atexit_verify() -> None: + """Warns if the libtcod root console is implicitly deleted.""" + if lib.TCOD_ctx.root: + warnings.warn( + "The libtcod root console was implicitly deleted.\n" + "Make sure the 'with' statement is used with the root console to" + " ensure that it closes properly.", + ResourceWarning, + stacklevel=2, + ) + lib.TCOD_console_delete(ffi.NULL) From c6f1ec9c34d8a566c25a6370ab502d38758ee6bc Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 19:46:50 -0800 Subject: [PATCH 0060/1101] Add full type hinting and deprecate parser functions. Functions were fixed while adding type hints. --- CHANGELOG.rst | 4 + tcod/bsp.py | 10 +- tcod/color.py | 34 +- tcod/image.py | 102 ++-- tcod/libtcod.py | 8 +- tcod/libtcodpy.py | 1105 ++++++++++++++++++++++++--------------- tcod/map.py | 6 +- tcod/noise.py | 65 +-- tcod/path.py | 41 +- tcod/random.py | 35 +- tcod/tcod.py | 27 +- tests/test_libtcodpy.py | 2 +- tests/test_parser.py | 2 +- 13 files changed, 879 insertions(+), 562 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c28f6c0e..b23f174b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,10 @@ Unreleased ------------------ Deprecated - The tdl module has been deprecated. + - The libtcodpy parser functions have been deprecated. +Fixed + - `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return + values. 8.4.1 - 2019-02-01 ------------------ diff --git a/tcod/bsp.py b/tcod/bsp.py index 9d15b6db..69c9f4f3 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -72,7 +72,7 @@ def w(self) -> int: return self.width @w.setter - def w(self, value: int): + def w(self, value: int) -> None: self.width = value @property @@ -80,7 +80,7 @@ def h(self) -> int: return self.height @h.setter - def h(self, value: int): + def h(self, value: int) -> None: self.height = value def _as_cdata(self) -> Any: @@ -93,7 +93,7 @@ def _as_cdata(self) -> Any: cdata.level = self.level return cdata - def __str__(self): + def __str__(self) -> str: """Provide a useful readout when printed.""" status = "leaf" if self.children: @@ -112,7 +112,7 @@ def __str__(self): status, ) - def _unpack_bsp_tree(self, cdata) -> None: + def _unpack_bsp_tree(self, cdata: Any) -> None: self.x = cdata.x self.y = cdata.y self.width = cdata.w @@ -266,7 +266,7 @@ def find_node(self, x: int, y: int) -> Optional["BSP"]: if not self.contains(x, y): return None for child in self.children: - found = child.find_node(x, y) + found = child.find_node(x, y) # type: Optional["BSP"] if found: return found return self diff --git a/tcod/color.py b/tcod/color.py index 97f2e21e..edb4003f 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,10 +1,12 @@ """ """ +from typing import Any, List + from tcod.libtcod import lib -class Color(list): +class Color(List[int]): """ Args: @@ -22,7 +24,7 @@ def r(self) -> int: return self[0] @r.setter - def r(self, value: int): + def r(self, value: int) -> None: self[0] = value & 0xFF @property @@ -31,7 +33,7 @@ def g(self) -> int: return self[1] @g.setter - def g(self, value: int): + def g(self, value: int) -> None: self[1] = value & 0xFF @property @@ -40,27 +42,27 @@ def b(self) -> int: return self[2] @b.setter - def b(self, value: int): + def b(self, value: int) -> None: self[2] = value & 0xFF @classmethod - def _new_from_cdata(cls, cdata): + def _new_from_cdata(cls, cdata: Any) -> "Color": """new in libtcod-cffi""" return cls(cdata.r, cdata.g, cdata.b) - def __getitem__(self, index): + def __getitem__(self, index: Any) -> int: # type: ignore try: - return list.__getitem__(self, index) + return super().__getitem__(index) # type: ignore except TypeError: - return list.__getitem__(self, "rgb".index(index)) + return super().__getitem__("rgb".index(index)) - def __setitem__(self, index, value): + def __setitem__(self, index: Any, value: Any) -> None: try: - list.__setitem__(self, index, value) + super().__setitem__(index, value) except TypeError: - list.__setitem__(self, "rgb".index(index), value) + super().__setitem__("rgb".index(index), value) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: """Compare equality between colors. Also compares with standard sequences such as 3-item tuples or lists. @@ -70,15 +72,15 @@ def __eq__(self, other): except TypeError: return False - def __add__(self, other): + def __add__(self, other: Any) -> "Color": """Add two colors together.""" return Color._new_from_cdata(lib.TCOD_color_add(self, other)) - def __sub__(self, other): + def __sub__(self, other: Any) -> "Color": """Subtract one color from another.""" return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) - def __mul__(self, other): + def __mul__(self, other: Any) -> "Color": """Multiply with a scaler or another color.""" if isinstance(other, (Color, list, tuple)): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) @@ -87,7 +89,7 @@ def __mul__(self, other): lib.TCOD_color_multiply_scalar(self, other) ) - def __repr__(self): + def __repr__(self) -> str: """Return a printable representation of the current color.""" return "%s(%i, %i, %i)" % ( self.__class__.__name__, diff --git a/tcod/image.py b/tcod/image.py index bad81f94..42363759 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,28 +1,31 @@ +from typing import Any, Tuple + import numpy as np +import tcod.console from tcod.libtcod import ffi, lib from tcod.tcod import _console -class _ImageBufferArray(np.ndarray): - def __new__(cls, image): +class _ImageBufferArray(np.ndarray): # type: ignore + def __new__(cls, image: Any) -> "_ImageBufferArray": size = image.height * image.width self = np.frombuffer( ffi.buffer(lib.TCOD_image_get_colors()[size]), np.uint8 ) self = self.reshape((image.height, image.width, 3)).view(cls) self._image_c = image.cdata - return self + return self # type: ignore - def __array_finalize__(self, obj): + def __array_finalize__(self, obj: Any) -> None: if obj is None: return self._image_c = getattr(obj, "_image_c", None) - def __repr__(self): + def __repr__(self) -> str: return repr(self.view(np.ndarray)) - def __setitem__(self, index, value): + def __setitem__(self, index: Any, value: Any) -> None: """Must invalidate mipmaps on any write.""" np.ndarray.__setitem__(self, index, value) if self._image_c is not None: @@ -40,20 +43,20 @@ class Image(object): height (int): Read only height of this Image. """ - def __init__(self, width, height): + def __init__(self, width: int, height: int): self.width, self.height = width, height self.image_c = ffi.gc( lib.TCOD_image_new(width, height), lib.TCOD_image_delete ) @classmethod - def _from_cdata(cls, cdata): - self = object.__new__(cls) + def _from_cdata(cls, cdata: Any) -> "Image": + self = object.__new__(cls) # type: "Image" self.image_c = cdata self.width, self.height = self._get_size() return self - def clear(self, color): + def clear(self, color: Tuple[int, int, int]) -> None: """Fill this entire Image with color. Args: @@ -62,15 +65,15 @@ def clear(self, color): """ lib.TCOD_image_clear(self.image_c, color) - def invert(self): + def invert(self) -> None: """Invert all colors in this Image.""" lib.TCOD_image_invert(self.image_c) - def hflip(self): + def hflip(self) -> None: """Horizontally flip this Image.""" lib.TCOD_image_hflip(self.image_c) - def rotate90(self, rotations=1): + def rotate90(self, rotations: int = 1) -> None: """Rotate this Image clockwise in 90 degree steps. Args: @@ -78,11 +81,11 @@ def rotate90(self, rotations=1): """ lib.TCOD_image_rotate90(self.image_c, rotations) - def vflip(self): + def vflip(self) -> None: """Vertically flip this Image.""" lib.TCOD_image_vflip(self.image_c) - def scale(self, width, height): + def scale(self, width: int, height: int) -> None: """Scale this Image to the new width and height. Args: @@ -92,7 +95,7 @@ def scale(self, width, height): lib.TCOD_image_scale(self.image_c, width, height) self.width, self.height = width, height - def set_key_color(self, color): + def set_key_color(self, color: Tuple[int, int, int]) -> None: """Set a color to be transparent during blitting functions. Args: @@ -101,7 +104,7 @@ def set_key_color(self, color): """ lib.TCOD_image_set_key_color(self.image_c, color) - def get_alpha(self, x, y): + def get_alpha(self, x: int, y: int) -> int: """Get the Image alpha of the pixel at x, y. Args: @@ -112,9 +115,9 @@ def get_alpha(self, x, y): int: The alpha value of the pixel. With 0 being fully transparent and 255 being fully opaque. """ - return lib.TCOD_image_get_alpha(self.image_c, x, y) + return lib.TCOD_image_get_alpha(self.image_c, x, y) # type: ignore - def refresh_console(self, console): + def refresh_console(self, console: tcod.console.Console) -> None: """Update an Image created with :any:`tcod.image_from_console`. The console used with this function should have the same width and @@ -128,7 +131,7 @@ def refresh_console(self, console): """ lib.TCOD_image_refresh_console(self.image_c, _console(console)) - def _get_size(self): + def _get_size(self) -> Tuple[int, int]: """Return the (width, height) for this Image. Returns: @@ -139,7 +142,7 @@ def _get_size(self): lib.TCOD_image_get_size(self.image_c, w, h) return w[0], h[0] - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> Tuple[int, int, int]: """Get the color of a pixel in this Image. Args: @@ -154,7 +157,9 @@ def get_pixel(self, x, y): color = lib.TCOD_image_get_pixel(self.image_c, x, y) return color.r, color.g, color.b - def get_mipmap_pixel(self, left, top, right, bottom): + def get_mipmap_pixel( + self, left: float, top: float, right: float, bottom: float + ) -> Tuple[int, int, int]: """Get the average color of a rectangle in this Image. Parameters should stay within the following limits: @@ -162,10 +167,10 @@ def get_mipmap_pixel(self, left, top, right, bottom): * 0 <= top < bottom < Image.height Args: - left (int): Left corner of the region. - top (int): Top corner of the region. - right (int): Right corner of the region. - bottom (int): Bottom corner of the region. + left (float): Left corner of the region. + top (float): Top corner of the region. + right (float): Right corner of the region. + bottom (float): Bottom corner of the region. Returns: Tuple[int, int, int]: @@ -177,7 +182,7 @@ def get_mipmap_pixel(self, left, top, right, bottom): ) return (color.r, color.g, color.b) - def put_pixel(self, x, y, color): + def put_pixel(self, x: int, y: int, color: Tuple[int, int, int]) -> None: """Change a pixel on this Image. Args: @@ -188,13 +193,22 @@ def put_pixel(self, x, y, color): """ lib.TCOD_image_put_pixel(self.image_c, x, y, color) - def blit(self, console, x, y, bg_blend, scale_x, scale_y, angle): + def blit( + self, + console: tcod.console.Console, + x: float, + y: float, + bg_blend: int, + scale_x: float, + scale_y: float, + angle: float, + ) -> None: """Blit onto a Console using scaling and rotation. Args: console (Console): Blit destination Console. - x (int): Console X position for the center of the Image blit. - y (int): Console Y position for the center of the Image blit. + x (float): Console X position for the center of the Image blit. + y (float): Console Y position for the center of the Image blit. The Image blit is centered on this position. bg_blend (int): Background blending mode to use. scale_x (float): Scaling along Image x axis. @@ -214,7 +228,15 @@ def blit(self, console, x, y, bg_blend, scale_x, scale_y, angle): angle, ) - def blit_rect(self, console, x, y, width, height, bg_blend): + def blit_rect( + self, + console: tcod.console.Console, + x: int, + y: int, + width: int, + height: int, + bg_blend: int, + ) -> None: """Blit onto a Console without scaling or rotation. Args: @@ -231,14 +253,14 @@ def blit_rect(self, console, x, y, width, height, bg_blend): def blit_2x( self, - console, - dest_x, - dest_y, - img_x=0, - img_y=0, - img_width=-1, - img_height=-1, - ): + console: tcod.console.Console, + dest_x: int, + dest_y: int, + img_x: int = 0, + img_y: int = 0, + img_width: int = -1, + img_height: int = -1, + ) -> None: """Blit onto a Console with double resolution. Args: @@ -263,7 +285,7 @@ def blit_2x( img_height, ) - def save_as(self, filename): + def save_as(self, filename: str) -> None: """Save the Image to a 32-bit .bmp or .png file. Args: diff --git a/tcod/libtcod.py b/tcod/libtcod.py index 02c9dcf6..8f68017a 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -23,19 +23,19 @@ class _Mock(object): CData = () # This gets passed to an isinstance call. @staticmethod - def def_extern(): + def def_extern() -> Any: """Pass def_extern call silently.""" return lambda func: func - def __getattr__(self, attr): + def __getattr__(self, attr: Any) -> Any: """This object pretends to have everything.""" return self - def __call__(self, *args, **kargs): + def __call__(self, *args: Any, **kargs: Any) -> Any: """Suppress any other calls""" return self - def __str__(self): + def __str__(self) -> Any: """Just have ? in case anything leaks as a parameter default.""" return "?" diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index eb9dc980..a3fdb893 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -5,7 +5,19 @@ import atexit import threading -from typing import Any, AnyStr, Optional, Sequence, Tuple +from typing import ( + Any, + AnyStr, + Callable, + Hashable, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, +) import warnings import numpy as np @@ -51,15 +63,15 @@ NOISE_DEFAULT_LACUNARITY = 2.0 -def FOV_PERMISSIVE(p): +def FOV_PERMISSIVE(p: int) -> int: return FOV_PERMISSIVE_0 + p -def BKGND_ALPHA(a): +def BKGND_ALPHA(a: int) -> int: return BKGND_ALPH | (int(a * 255) << 8) -def BKGND_ADDALPHA(a): +def BKGND_ADDALPHA(a: int) -> int: return BKGND_ADDA | (int(a * 255) << 8) @@ -84,16 +96,16 @@ class ConsoleBuffer(object): def __init__( self, - width, - height, - back_r=0, - back_g=0, - back_b=0, - fore_r=0, - fore_g=0, - fore_b=0, - char=" ", - ): + width: int, + height: int, + back_r: int = 0, + back_g: int = 0, + back_b: int = 0, + fore_r: int = 0, + fore_g: int = 0, + fore_b: int = 0, + char: str = " ", + ) -> None: """initialize with given width and height. values to fill the buffer are optional, defaults to black with no characters. """ @@ -108,14 +120,14 @@ def __init__( def clear( self, - back_r=0, - back_g=0, - back_b=0, - fore_r=0, - fore_g=0, - fore_b=0, - char=" ", - ): + back_r: int = 0, + back_g: int = 0, + back_b: int = 0, + fore_r: int = 0, + fore_g: int = 0, + fore_b: int = 0, + char: str = " ", + ) -> None: """Clears the console. Values to fill it with are optional, defaults to black with no characters. @@ -137,13 +149,13 @@ def clear( self.fore_b = [fore_b] * n self.char = [ord(char)] * n - def copy(self): + def copy(self) -> "ConsoleBuffer": """Returns a copy of this ConsoleBuffer. Returns: ConsoleBuffer: A new ConsoleBuffer copy. """ - other = ConsoleBuffer(0, 0) + other = ConsoleBuffer(0, 0) # type: "ConsoleBuffer" other.width = self.width other.height = self.height other.back_r = list(self.back_r) # make explicit copies of all lists @@ -155,7 +167,9 @@ def copy(self): other.char = list(self.char) return other - def set_fore(self, x, y, r, g, b, char): + def set_fore( + self, x: int, y: int, r: int, g: int, b: int, char: str + ) -> None: """Set the character and foreground color of one cell. Args: @@ -172,7 +186,7 @@ def set_fore(self, x, y, r, g, b, char): self.fore_b[i] = b self.char[i] = ord(char) - def set_back(self, x, y, r, g, b): + def set_back(self, x: int, y: int, r: int, g: int, b: int) -> None: """Set the background color of one cell. Args: @@ -181,14 +195,24 @@ def set_back(self, x, y, r, g, b): r (int): Red background color, from 0 to 255. g (int): Green background color, from 0 to 255. b (int): Blue background color, from 0 to 255. - char (AnyStr): A single character str or bytes object. """ i = self.width * y + x self.back_r[i] = r self.back_g[i] = g self.back_b[i] = b - def set(self, x, y, back_r, back_g, back_b, fore_r, fore_g, fore_b, char): + def set( + self, + x: int, + y: int, + back_r: int, + back_g: int, + back_b: int, + fore_r: int, + fore_g: int, + fore_b: int, + char: str, + ) -> None: """Set the background color, foreground color and character of one cell. Args: @@ -211,7 +235,12 @@ def set(self, x, y, back_r, back_g, back_b, fore_r, fore_g, fore_b, char): self.fore_b[i] = fore_b self.char[i] = ord(char) - def blit(self, dest, fill_fore=True, fill_back=True): + def blit( + self, + dest: tcod.console.Console, + fill_fore: bool = True, + fill_back: bool = True, + ) -> None: """Use libtcod's "fill" functions to write the buffer to a console. Args: @@ -257,7 +286,7 @@ class Dice(_CDataWrapper): which is tied to a CData object. """ - def __init__(self, *args, **kargs): + def __init__(self, *args: Any, **kargs: Any) -> None: warnings.warn( "Using this class is not recommended.", DeprecationWarning, @@ -267,22 +296,28 @@ def __init__(self, *args, **kargs): if self.cdata == ffi.NULL: self._init(*args, **kargs) - def _init(self, nb_dices=0, nb_faces=0, multiplier=0, addsub=0): + def _init( + self, + nb_dices: int = 0, + nb_faces: int = 0, + multiplier: float = 0, + addsub: float = 0, + ) -> None: self.cdata = ffi.new("TCOD_dice_t*") self.nb_dices = nb_dices self.nb_faces = nb_faces self.multiplier = multiplier self.addsub = addsub - def _get_nb_dices(self): + @property + def nb_dices(self) -> int: return self.nb_rolls - def _set_nb_dices(self, value): + @nb_dices.setter + def nb_dices(self, value: int) -> None: self.nb_rolls = value - nb_dices = property(_get_nb_dices, _set_nb_dices) - - def __str__(self): + def __str__(self) -> str: add = "+(%s)" % self.addsub if self.addsub != 0 else "" return "%id%ix%s%s" % ( self.nb_dices, @@ -291,7 +326,7 @@ def __str__(self): add, ) - def __repr__(self): + def __repr__(self) -> str: return "%s(nb_dices=%r,nb_faces=%r,multiplier=%r,addsub=%r)" % ( self.__class__.__name__, self.nb_dices, @@ -341,17 +376,17 @@ class Key(_CDataWrapper): def __init__( self, - vk=0, - c=0, - text="", - pressed=False, - lalt=False, - lctrl=False, - lmeta=False, - ralt=False, - rctrl=False, - rmeta=False, - shift=False, + vk: int = 0, + c: int = 0, + text: str = "", + pressed: bool = False, + lalt: bool = False, + lctrl: bool = False, + lmeta: bool = False, + ralt: bool = False, + rctrl: bool = False, + rmeta: bool = False, + shift: bool = False, ): if isinstance(vk, ffi.CData): self.cdata = vk @@ -369,7 +404,7 @@ def __init__( self.rmeta = rmeta self.shift = shift - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: if attr in self._BOOL_ATTRIBUTES: return bool(getattr(self.cdata, attr)) if attr == "c": @@ -378,7 +413,7 @@ def __getattr__(self, attr): return ffi.string(self.cdata.text).decode() return super(Key, self).__getattr__(attr) - def __setattr__(self, attr, value): + def __setattr__(self, attr: str, value: Any) -> None: if attr == "c": self.cdata.c = chr(value).encode("latin-1") elif attr == "text": @@ -386,7 +421,7 @@ def __setattr__(self, attr, value): else: super(Key, self).__setattr__(attr, value) - def __repr__(self): + def __repr__(self) -> str: """Return a representation of this Key object.""" params = [] params.append( @@ -410,7 +445,7 @@ def __repr__(self): return "tcod.Key(%s)" % ", ".join(params) @property - def key_p(self): + def key_p(self) -> Any: return self.cdata @@ -446,7 +481,7 @@ def __init__( cy: int = 0, dcx: int = 0, dcy: int = 0, - **kargs + **kargs: Any ): if isinstance(x, ffi.CData): self.cdata = x @@ -463,7 +498,7 @@ def __init__( for attr, value in kargs.items(): setattr(self, attr, value) - def __repr__(self): + def __repr__(self) -> str: """Return a representation of this Mouse object.""" params = [] for attr in ["x", "y", "dx", "dy", "cx", "cy", "dcx", "dcy"]: @@ -485,11 +520,11 @@ def __repr__(self): return "tcod.Mouse(%s)" % ", ".join(params) @property - def mouse_p(self): + def mouse_p(self) -> Any: return self.cdata -def bsp_new_with_size(x, y, w, h): +def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: """Create a new BSP instance with the given rectangle. Args: @@ -507,7 +542,9 @@ def bsp_new_with_size(x, y, w, h): return Bsp(x, y, w, h) -def bsp_split_once(node, horizontal, position): +def bsp_split_once( + node: tcod.bsp.BSP, horizontal: bool, position: int +) -> None: """ .. deprecated:: 2.0 Use :any:`BSP.split_once` instead. @@ -516,8 +553,14 @@ def bsp_split_once(node, horizontal, position): def bsp_split_recursive( - node, randomizer, nb, minHSize, minVSize, maxHRatio, maxVRatio -): + node: tcod.bsp.BSP, + randomizer: Optional[tcod.random.Random], + nb: int, + minHSize: int, + minVSize: int, + maxHRatio: int, + maxVRatio: int, +) -> None: """ .. deprecated:: 2.0 Use :any:`BSP.split_recursive` instead. @@ -528,7 +571,7 @@ def bsp_split_recursive( @deprecate("Assign values via attribute instead.") -def bsp_resize(node, x, y, w, h): +def bsp_resize(node: tcod.bsp.BSP, x: int, y: int, w: int, h: int) -> None: """ .. deprecated:: 2.0 Assign directly to :any:`BSP` attributes instead. @@ -540,7 +583,7 @@ def bsp_resize(node, x, y, w, h): @deprecate("Access children with 'node.children' instead.") -def bsp_left(node): +def bsp_left(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: """ .. deprecated:: 2.0 Use :any:`BSP.children` instead. @@ -549,7 +592,7 @@ def bsp_left(node): @deprecate("Access children with 'node.children' instead.") -def bsp_right(node): +def bsp_right(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: """ .. deprecated:: 2.0 Use :any:`BSP.children` instead. @@ -558,7 +601,7 @@ def bsp_right(node): @deprecate("Get the parent with 'node.parent' instead.") -def bsp_father(node): +def bsp_father(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: """ .. deprecated:: 2.0 Use :any:`BSP.parent` instead. @@ -567,7 +610,7 @@ def bsp_father(node): @deprecate("Check for children with 'bool(node.children)' instead.") -def bsp_is_leaf(node): +def bsp_is_leaf(node: tcod.bsp.BSP) -> bool: """ .. deprecated:: 2.0 Use :any:`BSP.children` instead. @@ -576,7 +619,7 @@ def bsp_is_leaf(node): @deprecate("Use 'node.contains' instead.") -def bsp_contains(node, cx, cy): +def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool: """ .. deprecated:: 2.0 Use :any:`BSP.contains` instead. @@ -585,7 +628,9 @@ def bsp_contains(node, cx, cy): @deprecate("Use 'node.find_node' instead.") -def bsp_find_node(node, cx, cy): +def bsp_find_node( + node: tcod.bsp.BSP, cx: int, cy: int +) -> Optional[tcod.bsp.BSP]: """ .. deprecated:: 2.0 Use :any:`BSP.find_node` instead. @@ -593,7 +638,11 @@ def bsp_find_node(node, cx, cy): return node.find_node(cx, cy) -def _bsp_traverse(node_iter, callback, userData): +def _bsp_traverse( + node_iter: Iterable[tcod.bsp.BSP], + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any, +) -> None: """pack callback into a handle for use with the callback _pycall_bsp_callback """ @@ -602,7 +651,11 @@ def _bsp_traverse(node_iter, callback, userData): @deprecate("Iterate over nodes using 'for n in node.pre_order():' instead.") -def bsp_traverse_pre_order(node, callback, userData=0): +def bsp_traverse_pre_order( + node: tcod.bsp.BSP, + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any = 0, +) -> None: """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 @@ -612,7 +665,11 @@ def bsp_traverse_pre_order(node, callback, userData=0): @deprecate("Iterate over nodes using 'for n in node.in_order():' instead.") -def bsp_traverse_in_order(node, callback, userData=0): +def bsp_traverse_in_order( + node: tcod.bsp.BSP, + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any = 0, +) -> None: """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 @@ -622,7 +679,11 @@ def bsp_traverse_in_order(node, callback, userData=0): @deprecate("Iterate over nodes using 'for n in node.post_order():' instead.") -def bsp_traverse_post_order(node, callback, userData=0): +def bsp_traverse_post_order( + node: tcod.bsp.BSP, + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any = 0, +) -> None: """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 @@ -632,7 +693,11 @@ def bsp_traverse_post_order(node, callback, userData=0): @deprecate("Iterate over nodes using 'for n in node.level_order():' instead.") -def bsp_traverse_level_order(node, callback, userData=0): +def bsp_traverse_level_order( + node: tcod.bsp.BSP, + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any = 0, +) -> None: """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 @@ -645,7 +710,11 @@ def bsp_traverse_level_order(node, callback, userData=0): "Iterate over nodes using " "'for n in node.inverted_level_order():' instead." ) -def bsp_traverse_inverted_level_order(node, callback, userData=0): +def bsp_traverse_inverted_level_order( + node: tcod.bsp.BSP, + callback: Callable[[tcod.bsp.BSP, Any], None], + userData: Any = 0, +) -> None: """Traverse this nodes hierarchy with a callback. .. deprecated:: 2.0 @@ -655,7 +724,7 @@ def bsp_traverse_inverted_level_order(node, callback, userData=0): @deprecate("Delete bsp children using 'node.children = ()' instead.") -def bsp_remove_sons(node): +def bsp_remove_sons(node: tcod.bsp.BSP) -> None: """Delete all children of a given node. Not recommended. .. note:: @@ -669,8 +738,7 @@ def bsp_remove_sons(node): @deprecate("libtcod objects are deleted automatically.") -def bsp_delete(node): - # type: (Any) -> None +def bsp_delete(node: tcod.bsp.BSP) -> None: """Exists for backward compatibility. Does nothing. BSP's created by this library are automatically garbage collected once @@ -682,7 +750,9 @@ def bsp_delete(node): """ -def color_lerp(c1, c2, a): +def color_lerp( + c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float +) -> Color: """Return the linear interpolation between two colors. ``a`` is the interpolation value, with 0 returing ``c1``, @@ -701,7 +771,7 @@ def color_lerp(c1, c2, a): return Color._new_from_cdata(lib.TCOD_color_lerp(c1, c2, a)) -def color_set_hsv(c, h, s, v): +def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: """Set a color using: hue, saturation, and value parameters. Does not return a new Color. ``c`` is modified inplace. @@ -717,7 +787,7 @@ def color_set_hsv(c, h, s, v): c[:] = new_color.r, new_color.g, new_color.b -def color_get_hsv(c): +def color_get_hsv(c: Tuple[int, int, int]) -> Tuple[float, float, float]: """Return the (hue, saturation, value) of a color. Args: @@ -733,7 +803,7 @@ def color_get_hsv(c): return hsv[0], hsv[1], hsv[2] -def color_scale_HSV(c, scoef, vcoef): +def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: """Scale a color's saturation and value. Does not return a new Color. ``c`` is modified inplace. @@ -751,7 +821,9 @@ def color_scale_HSV(c, scoef, vcoef): c[:] = color_p.r, color_p.g, color_p.b -def color_gen_map(colors, indexes): +def color_gen_map( + colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int] +) -> List[Color]: """Return a smoothly defined scale of colors. If ``indexes`` is [0, 3, 9] for example, the first color from ``colors`` @@ -774,7 +846,7 @@ def color_gen_map(colors, indexes): ccolors = ffi.new("TCOD_color_t[]", colors) cindexes = ffi.new("int[]", indexes) cres = ffi.new("TCOD_color_t[]", max(indexes) + 1) - lib.TCOD_color_gen_map(cres, len(colors), ccolors, cindexes) + lib.TCOD_color_gen_map(cres, len(ccolors), ccolors, cindexes) return [Color._new_from_cdata(cdata) for cdata in cres] @@ -829,7 +901,7 @@ def console_set_custom_font( flags: int = FONT_LAYOUT_ASCII_INCOL, nb_char_horiz: int = 0, nb_char_vertic: int = 0, -): +) -> None: """Load the custom font file at `fontFile`. Call this before function before calling :any:`tcod.console_init_root`. @@ -860,7 +932,7 @@ def console_set_custom_font( ) -def console_get_width(con): +def console_get_width(con: tcod.console.Console) -> int: """Return the width of a console. Args: @@ -872,10 +944,10 @@ def console_get_width(con): .. deprecated:: 2.0 Use `Console.width` instead. """ - return lib.TCOD_console_get_width(_console(con)) + return int(lib.TCOD_console_get_width(_console(con))) -def console_get_height(con): +def console_get_height(con: tcod.console.Console) -> int: """Return the height of a console. Args: @@ -887,10 +959,12 @@ def console_get_height(con): .. deprecated:: 2.0 Use `Console.height` instead. """ - return lib.TCOD_console_get_height(_console(con)) + return int(lib.TCOD_console_get_height(_console(con))) -def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY): +def console_map_ascii_code_to_font( + asciiCode: int, fontCharX: int, fontCharY: int +) -> None: """Set a character code to new coordinates on the tile-set. `asciiCode` must be within the bounds created during the initialization of @@ -910,8 +984,8 @@ def console_map_ascii_code_to_font(asciiCode, fontCharX, fontCharY): def console_map_ascii_codes_to_font( - firstAsciiCode, nbCodes, fontCharX, fontCharY -): + firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int +) -> None: """Remap a contiguous set of codes to a contiguous set of tiles. Both the tile-set and character codes must be contiguous to use this @@ -932,7 +1006,7 @@ def console_map_ascii_codes_to_font( ) -def console_map_string_to_font(s, fontCharX, fontCharY): +def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: """Remap a string of codes to a contiguous set of tiles. Args: @@ -947,7 +1021,7 @@ def console_map_string_to_font(s, fontCharX, fontCharY): lib.TCOD_console_map_string_to_font_utf(_unicode(s), fontCharX, fontCharY) -def console_is_fullscreen(): +def console_is_fullscreen() -> bool: """Returns True if the display is fullscreen. Returns: @@ -956,7 +1030,7 @@ def console_is_fullscreen(): return bool(lib.TCOD_console_is_fullscreen()) -def console_set_fullscreen(fullscreen): +def console_set_fullscreen(fullscreen: bool) -> None: """Change the display to be fullscreen or windowed. Args: @@ -966,50 +1040,51 @@ def console_set_fullscreen(fullscreen): lib.TCOD_console_set_fullscreen(fullscreen) -def console_is_window_closed(): +def console_is_window_closed() -> bool: """Returns True if the window has received and exit event.""" - return lib.TCOD_console_is_window_closed() + return bool(lib.TCOD_console_is_window_closed()) -def console_has_mouse_focus(): +def console_has_mouse_focus() -> bool: """Return True if the window has mouse focus.""" - return lib.TCOD_console_has_mouse_focus() + return bool(lib.TCOD_console_has_mouse_focus()) -def console_is_active(): +def console_is_active() -> bool: """Return True if the window has keyboard focus.""" - return lib.TCOD_console_is_active() + return bool(lib.TCOD_console_is_active()) -def console_set_window_title(title): +def console_set_window_title(title: str) -> None: """Change the current title bar string. Args: title (AnyStr): A string to change the title bar to. """ - title = _bytes(title) - lib.TCOD_console_set_window_title(title) + lib.TCOD_console_set_window_title(_bytes(title)) -def console_credits(): +def console_credits() -> None: lib.TCOD_console_credits() -def console_credits_reset(): +def console_credits_reset() -> None: lib.TCOD_console_credits_reset() -def console_credits_render(x, y, alpha): - return lib.TCOD_console_credits_render(x, y, alpha) +def console_credits_render(x: int, y: int, alpha: bool) -> bool: + return bool(lib.TCOD_console_credits_render(x, y, alpha)) -def console_flush(): +def console_flush() -> None: """Update the display to represent the root consoles current state.""" lib.TCOD_console_flush() # drawing on a console -def console_set_default_background(con, col): +def console_set_default_background( + con: tcod.console.Console, col: Tuple[int, int, int] +) -> None: """Change the default background color for a console. Args: @@ -1020,7 +1095,9 @@ def console_set_default_background(con, col): lib.TCOD_console_set_default_background(_console(con), col) -def console_set_default_foreground(con, col): +def console_set_default_foreground( + con: tcod.console.Console, col: Tuple[int, int, int] +) -> None: """Change the default foreground color for a console. Args: @@ -1031,7 +1108,7 @@ def console_set_default_foreground(con, col): lib.TCOD_console_set_default_foreground(_console(con), col) -def console_clear(con): +def console_clear(con: tcod.console.Console) -> None: """Reset a console to its default colors and the space character. Args: @@ -1041,10 +1118,16 @@ def console_clear(con): :any:`console_set_default_background` :any:`console_set_default_foreground` """ - return lib.TCOD_console_clear(_console(con)) + lib.TCOD_console_clear(_console(con)) -def console_put_char(con, x, y, c, flag=BKGND_DEFAULT): +def console_put_char( + con: tcod.console.Console, + x: int, + y: int, + c: Union[int, str], + flag: int = BKGND_DEFAULT, +) -> None: """Draw the character c at x,y using the default colors and a blend mode. Args: @@ -1057,7 +1140,14 @@ def console_put_char(con, x, y, c, flag=BKGND_DEFAULT): lib.TCOD_console_put_char(_console(con), x, y, _int(c), flag) -def console_put_char_ex(con, x, y, c, fore, back): +def console_put_char_ex( + con: tcod.console.Console, + x: int, + y: int, + c: Union[int, str], + fore: Tuple[int, int, int], + back: Tuple[int, int, int], +) -> None: """Draw the character c at x,y using the colors fore and back. Args: @@ -1073,7 +1163,13 @@ def console_put_char_ex(con, x, y, c, fore, back): lib.TCOD_console_put_char_ex(_console(con), x, y, _int(c), fore, back) -def console_set_char_background(con, x, y, col, flag=BKGND_SET): +def console_set_char_background( + con: tcod.console.Console, + x: int, + y: int, + col: Tuple[int, int, int], + flag: int = BKGND_SET, +) -> None: """Change the background color of x,y to col using a blend mode. Args: @@ -1088,7 +1184,9 @@ def console_set_char_background(con, x, y, col, flag=BKGND_SET): @deprecate("Directly access a consoles foreground color with `console.fg`") -def console_set_char_foreground(con, x, y, col): +def console_set_char_foreground( + con: tcod.console.Console, x: int, y: int, col: Tuple[int, int, int] +) -> None: """Change the foreground color of x,y to col. Args: @@ -1102,7 +1200,9 @@ def console_set_char_foreground(con, x, y, col): @deprecate("Directly access a consoles characters with `console.ch`") -def console_set_char(con, x, y, c): +def console_set_char( + con: tcod.console.Console, x: int, y: int, c: Union[int, str] +) -> None: """Change the character at x,y to c, keeping the current colors. Args: @@ -1114,7 +1214,7 @@ def console_set_char(con, x, y, c): lib.TCOD_console_set_char(_console(con), x, y, _int(c)) -def console_set_background_flag(con, flag): +def console_set_background_flag(con: tcod.console.Console, flag: int) -> None: """Change the default blend mode for this console. Args: @@ -1124,16 +1224,16 @@ def console_set_background_flag(con, flag): lib.TCOD_console_set_background_flag(_console(con), flag) -def console_get_background_flag(con): +def console_get_background_flag(con: tcod.console.Console) -> int: """Return this consoles current blend mode. Args: con (Console): Any Console instance. """ - return lib.TCOD_console_get_background_flag(_console(con)) + return int(lib.TCOD_console_get_background_flag(_console(con))) -def console_set_alignment(con, alignment): +def console_set_alignment(con: tcod.console.Console, alignment: int) -> None: """Change this consoles current alignment mode. * tcod.LEFT @@ -1147,13 +1247,13 @@ def console_set_alignment(con, alignment): lib.TCOD_console_set_alignment(_console(con), alignment) -def console_get_alignment(con): +def console_get_alignment(con: tcod.console.Console) -> int: """Return this consoles current alignment mode. Args: con (Console): Any Console instance. """ - return lib.TCOD_console_get_alignment(_console(con)) + return int(lib.TCOD_console_get_alignment(_console(con))) def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: @@ -1200,7 +1300,9 @@ def console_print_rect( Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)) + return int( + lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)) + ) def console_print_rect_ex( @@ -1218,8 +1320,10 @@ def console_print_rect_ex( Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_printf_rect_ex( - _console(con), x, y, w, h, flag, alignment, _fmt(fmt) + return int( + lib.TCOD_console_printf_rect_ex( + _console(con), x, y, w, h, flag, alignment, _fmt(fmt) + ) ) @@ -1231,8 +1335,10 @@ def console_get_height_rect( Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_get_height_rect_fmt( - _console(con), x, y, w, h, _fmt(fmt) + return int( + lib.TCOD_console_get_height_rect_fmt( + _console(con), x, y, w, h, _fmt(fmt) + ) ) @@ -1272,7 +1378,7 @@ def console_vline( y: int, l: int, flag: int = BKGND_DEFAULT, -): +) -> None: """Draw a vertical line on the console. This always uses the character 179, the vertical line character. @@ -1281,7 +1387,7 @@ def console_vline( def console_print_frame( - con, + con: tcod.console.Console, x: int, y: int, w: int, @@ -1305,7 +1411,9 @@ def console_print_frame( lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) -def console_set_color_control(con, fore, back): +def console_set_color_control( + con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int] +) -> None: """Configure :any:`color controls`. Args: @@ -1318,14 +1426,14 @@ def console_set_color_control(con, fore, back): lib.TCOD_console_set_color_control(con, fore, back) -def console_get_default_background(con): +def console_get_default_background(con: tcod.console.Console) -> Color: """Return this consoles default background color.""" return Color._new_from_cdata( lib.TCOD_console_get_default_background(_console(con)) ) -def console_get_default_foreground(con): +def console_get_default_foreground(con: tcod.console.Console) -> Color: """Return this consoles default foreground color.""" return Color._new_from_cdata( lib.TCOD_console_get_default_foreground(_console(con)) @@ -1333,7 +1441,9 @@ def console_get_default_foreground(con): @deprecate("Directly access a consoles background color with `console.bg`") -def console_get_char_background(con, x, y): +def console_get_char_background( + con: tcod.console.Console, x: int, y: int +) -> Color: """Return the background color at the x,y of this console.""" return Color._new_from_cdata( lib.TCOD_console_get_char_background(_console(con), x, y) @@ -1341,7 +1451,9 @@ def console_get_char_background(con, x, y): @deprecate("Directly access a consoles foreground color with `console.fg`") -def console_get_char_foreground(con, x, y): +def console_get_char_foreground( + con: tcod.console.Console, x: int, y: int +) -> Color: """Return the foreground color at the x,y of this console.""" return Color._new_from_cdata( lib.TCOD_console_get_char_foreground(_console(con), x, y) @@ -1349,25 +1461,25 @@ def console_get_char_foreground(con, x, y): @deprecate("Directly access a consoles characters with `console.ch`") -def console_get_char(con, x, y): +def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: """Return the character at the x,y of this console.""" - return lib.TCOD_console_get_char(_console(con), x, y) + return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore -def console_set_fade(fade, fadingColor): +def console_set_fade(fade: int, fadingColor: Tuple[int, int, int]) -> None: lib.TCOD_console_set_fade(fade, fadingColor) -def console_get_fade(): - return lib.TCOD_console_get_fade() +def console_get_fade() -> int: + return int(lib.TCOD_console_get_fade()) -def console_get_fading_color(): +def console_get_fading_color() -> Color: return Color._new_from_cdata(lib.TCOD_console_get_fading_color()) # handling keyboard input -def console_wait_for_keypress(flush): +def console_wait_for_keypress(flush: bool) -> Key: """Block until the user presses a key, then returns a new Key. Args: @@ -1382,23 +1494,23 @@ def console_wait_for_keypress(flush): return key -def console_check_for_keypress(flags=KEY_RELEASED): +def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: key = Key() lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags) return key -def console_is_key_pressed(key): - return lib.TCOD_console_is_key_pressed(key) +def console_is_key_pressed(key: int) -> bool: + return bool(lib.TCOD_console_is_key_pressed(key)) # using offscreen consoles -def console_new(w, h): +def console_new(w: int, h: int) -> tcod.console.Console: """Return an offscreen console of size: w,h.""" return tcod.console.Console(w, h) -def console_from_file(filename): +def console_from_file(filename: str) -> tcod.console.Console: """Return a new console object from a filename. The file format is automactially determined. This can load REXPaint `.xp`, @@ -1414,14 +1526,27 @@ def console_from_file(filename): ) -def console_blit(src, x, y, w, h, dst, xdst, ydst, ffade=1.0, bfade=1.0): +def console_blit( + src: tcod.console.Console, + x: int, + y: int, + w: int, + h: int, + dst: tcod.console.Console, + xdst: int, + ydst: int, + ffade: float = 1.0, + bfade: float = 1.0, +) -> None: """Blit the console src from x,y,w,h to console dst at xdst,ydst.""" lib.TCOD_console_blit( _console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade ) -def console_set_key_color(con, col): +def console_set_key_color( + con: tcod.console.Console, col: Tuple[int, int, int] +) -> None: """Set a consoles blit transparent color.""" lib.TCOD_console_set_key_color(_console(con), col) if hasattr(con, "set_key_color"): @@ -1442,7 +1567,12 @@ def console_delete(con: tcod.console.Console) -> None: # fast color filling -def console_fill_foreground(con, r, g, b): +def console_fill_foreground( + con: tcod.console.Console, + r: Sequence[int], + g: Sequence[int], + b: Sequence[int], +) -> None: """Fill the foregound of a console with r,g,b. Args: @@ -1459,12 +1589,12 @@ def console_fill_foreground(con, r, g, b): and isinstance(b, np.ndarray) ): # numpy arrays, use numpy's ctypes functions - r = np.ascontiguousarray(r, dtype=np.intc) - g = np.ascontiguousarray(g, dtype=np.intc) - b = np.ascontiguousarray(b, dtype=np.intc) - cr = ffi.cast("int *", r.ctypes.data) - cg = ffi.cast("int *", g.ctypes.data) - cb = ffi.cast("int *", b.ctypes.data) + r_ = np.ascontiguousarray(r, dtype=np.intc) + g_ = np.ascontiguousarray(g, dtype=np.intc) + b_ = np.ascontiguousarray(b, dtype=np.intc) + cr = ffi.cast("int *", r_.ctypes.data) + cg = ffi.cast("int *", g_.ctypes.data) + cb = ffi.cast("int *", b_.ctypes.data) else: # otherwise convert using ffi arrays cr = ffi.new("int[]", r) @@ -1474,7 +1604,12 @@ def console_fill_foreground(con, r, g, b): lib.TCOD_console_fill_foreground(_console(con), cr, cg, cb) -def console_fill_background(con, r, g, b): +def console_fill_background( + con: tcod.console.Console, + r: Sequence[int], + g: Sequence[int], + b: Sequence[int], +) -> None: """Fill the backgound of a console with r,g,b. Args: @@ -1491,12 +1626,12 @@ def console_fill_background(con, r, g, b): and isinstance(b, np.ndarray) ): # numpy arrays, use numpy's ctypes functions - r = np.ascontiguousarray(r, dtype=np.intc) - g = np.ascontiguousarray(g, dtype=np.intc) - b = np.ascontiguousarray(b, dtype=np.intc) - cr = ffi.cast("int *", r.ctypes.data) - cg = ffi.cast("int *", g.ctypes.data) - cb = ffi.cast("int *", b.ctypes.data) + r_ = np.ascontiguousarray(r, dtype=np.intc) + g_ = np.ascontiguousarray(g, dtype=np.intc) + b_ = np.ascontiguousarray(b, dtype=np.intc) + cr = ffi.cast("int *", r_.ctypes.data) + cg = ffi.cast("int *", g_.ctypes.data) + cb = ffi.cast("int *", b_.ctypes.data) else: # otherwise convert using ffi arrays cr = ffi.new("int[]", r) @@ -1506,7 +1641,7 @@ def console_fill_background(con, r, g, b): lib.TCOD_console_fill_background(_console(con), cr, cg, cb) -def console_fill_char(con: tcod.console.Console, arr: Sequence[int]): +def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: """Fill the character tiles of a console with an array. `arr` is an array of integers with a length of the consoles width and @@ -1523,46 +1658,62 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]): lib.TCOD_console_fill_char(_console(con), carr) -def console_load_asc(con, filename): +def console_load_asc(con: tcod.console.Console, filename: str) -> bool: """Update a console from a non-delimited ASCII `.asc` file.""" - return lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8")) + return bool( + lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8")) + ) -def console_save_asc(con, filename): +def console_save_asc(con: tcod.console.Console, filename: str) -> bool: """Save a console to a non-delimited ASCII `.asc` file.""" - return lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8")) + return bool( + lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8")) + ) -def console_load_apf(con, filename): +def console_load_apf(con: tcod.console.Console, filename: str) -> bool: """Update a console from an ASCII Paint `.apf` file.""" - return lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8")) + return bool( + lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8")) + ) -def console_save_apf(con, filename): +def console_save_apf(con: tcod.console.Console, filename: str) -> bool: """Save a console to an ASCII Paint `.apf` file.""" - return lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8")) + return bool( + lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8")) + ) -def console_load_xp(con, filename): +def console_load_xp(con: tcod.console.Console, filename: str) -> bool: """Update a console from a REXPaint `.xp` file.""" - return lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8")) + return bool( + lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8")) + ) -def console_save_xp(con, filename, compress_level=9): +def console_save_xp( + con: tcod.console.Console, filename: str, compress_level: int = 9 +) -> bool: """Save a console to a REXPaint `.xp` file.""" - return lib.TCOD_console_save_xp( - _console(con), filename.encode("utf-8"), compress_level + return bool( + lib.TCOD_console_save_xp( + _console(con), filename.encode("utf-8"), compress_level + ) ) -def console_from_xp(filename): +def console_from_xp(filename: str) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file.""" return tcod.console.Console._from_cdata( lib.TCOD_console_from_xp(filename.encode("utf-8")) ) -def console_list_load_xp(filename): +def console_list_load_xp( + filename: str +) -> Optional[List[tcod.console.Console]]: """Return a list of consoles from a REXPaint `.xp` file.""" tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) if tcod_list == ffi.NULL: @@ -1579,20 +1730,28 @@ def console_list_load_xp(filename): lib.TCOD_list_delete(tcod_list) -def console_list_save_xp(console_list, filename, compress_level=9): +def console_list_save_xp( + console_list: Sequence[tcod.console.Console], + filename: str, + compress_level: int = 9, +) -> bool: """Save a list of consoles to a REXPaint `.xp` file.""" tcod_list = lib.TCOD_list_new() try: for console in console_list: lib.TCOD_list_push(tcod_list, _console(console)) - return lib.TCOD_console_list_save_xp( - tcod_list, filename.encode("utf-8"), compress_level + return bool( + lib.TCOD_console_list_save_xp( + tcod_list, filename.encode("utf-8"), compress_level + ) ) finally: lib.TCOD_list_delete(tcod_list) -def path_new_using_map(m, dcost=1.41): +def path_new_using_map( + m: tcod.map.Map, dcost: float = 1.41 +) -> tcod.path.AStar: """Return a new AStar using the given Map. Args: @@ -1605,7 +1764,13 @@ def path_new_using_map(m, dcost=1.41): return tcod.path.AStar(m, dcost) -def path_new_using_function(w, h, func, userData=0, dcost=1.41): +def path_new_using_function( + w: int, + h: int, + func: Callable[[int, int, int, int, Any], float], + userData: Any = 0, + dcost: float = 1.41, +) -> tcod.path.AStar: """Return a new AStar using the given callable function. Args: @@ -1623,7 +1788,9 @@ def path_new_using_function(w, h, func, userData=0, dcost=1.41): ) -def path_compute(p, ox, oy, dx, dy): +def path_compute( + p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int +) -> bool: """Find a path from (ox, oy) to (dx, dy). Return True if path is found. Args: @@ -1635,10 +1802,10 @@ def path_compute(p, ox, oy, dx, dy): Returns: bool: True if a valid path was found. Otherwise False. """ - return lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy) + return bool(lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy)) -def path_get_origin(p): +def path_get_origin(p: tcod.path.AStar) -> Tuple[int, int]: """Get the current origin position. This point moves when :any:`path_walk` returns the next x,y step. @@ -1654,7 +1821,7 @@ def path_get_origin(p): return x[0], y[0] -def path_get_destination(p): +def path_get_destination(p: tcod.path.AStar) -> Tuple[int, int]: """Get the current destination position. Args: @@ -1668,7 +1835,7 @@ def path_get_destination(p): return x[0], y[0] -def path_size(p): +def path_size(p: tcod.path.AStar) -> int: """Return the current length of the computed path. Args: @@ -1676,10 +1843,10 @@ def path_size(p): Returns: int: Length of the path. """ - return lib.TCOD_path_size(p._path_c) + return int(lib.TCOD_path_size(p._path_c)) -def path_reverse(p): +def path_reverse(p: tcod.path.AStar) -> None: """Reverse the direction of a path. This effectively swaps the origin and destination points. @@ -1690,7 +1857,7 @@ def path_reverse(p): lib.TCOD_path_reverse(p._path_c) -def path_get(p, idx): +def path_get(p: tcod.path.AStar, idx: int) -> Tuple[int, int]: """Get a point on a path. Args: @@ -1703,7 +1870,7 @@ def path_get(p, idx): return x[0], y[0] -def path_is_empty(p): +def path_is_empty(p: tcod.path.AStar) -> bool: """Return True if a path is empty. Args: @@ -1711,10 +1878,12 @@ def path_is_empty(p): Returns: bool: True if a path is empty. Otherwise False. """ - return lib.TCOD_path_is_empty(p._path_c) + return bool(lib.TCOD_path_is_empty(p._path_c)) -def path_walk(p, recompute): +def path_walk( + p: tcod.path.AStar, recompute: bool +) -> Union[Tuple[int, int], Tuple[None, None]]: """Return the next (x, y) point in a path, or (None, None) if it's empty. When ``recompute`` is True and a previously valid path reaches a point @@ -1735,56 +1904,63 @@ def path_walk(p, recompute): @deprecate("libtcod objects are deleted automatically.") -def path_delete(p): - # type (Any) -> None +def path_delete(p: tcod.path.AStar) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. This function exists for backwards compatibility with libtcodpy. """ -def dijkstra_new(m, dcost=1.41): +def dijkstra_new(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.Dijkstra: return tcod.path.Dijkstra(m, dcost) -def dijkstra_new_using_function(w, h, func, userData=0, dcost=1.41): +def dijkstra_new_using_function( + w: int, + h: int, + func: Callable[[int, int, int, int, Any], float], + userData: Any = 0, + dcost: float = 1.41, +) -> tcod.path.Dijkstra: return tcod.path.Dijkstra( tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost ) -def dijkstra_compute(p, ox, oy): +def dijkstra_compute(p: tcod.path.Dijkstra, ox: int, oy: int) -> None: lib.TCOD_dijkstra_compute(p._path_c, ox, oy) -def dijkstra_path_set(p, x, y): - return lib.TCOD_dijkstra_path_set(p._path_c, x, y) +def dijkstra_path_set(p: tcod.path.Dijkstra, x: int, y: int) -> bool: + return bool(lib.TCOD_dijkstra_path_set(p._path_c, x, y)) -def dijkstra_get_distance(p, x, y): - return lib.TCOD_dijkstra_get_distance(p._path_c, x, y) +def dijkstra_get_distance(p: tcod.path.Dijkstra, x: int, y: int) -> int: + return int(lib.TCOD_dijkstra_get_distance(p._path_c, x, y)) -def dijkstra_size(p): - return lib.TCOD_dijkstra_size(p._path_c) +def dijkstra_size(p: tcod.path.Dijkstra) -> int: + return int(lib.TCOD_dijkstra_size(p._path_c)) -def dijkstra_reverse(p): +def dijkstra_reverse(p: tcod.path.Dijkstra) -> None: lib.TCOD_dijkstra_reverse(p._path_c) -def dijkstra_get(p, idx): +def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> Tuple[int, int]: x = ffi.new("int *") y = ffi.new("int *") lib.TCOD_dijkstra_get(p._path_c, idx, x, y) return x[0], y[0] -def dijkstra_is_empty(p): - return lib.TCOD_dijkstra_is_empty(p._path_c) +def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool: + return bool(lib.TCOD_dijkstra_is_empty(p._path_c)) -def dijkstra_path_walk(p): +def dijkstra_path_walk( + p: tcod.path.Dijkstra +) -> Union[Tuple[int, int], Tuple[None, None]]: x = ffi.new("int *") y = ffi.new("int *") if lib.TCOD_dijkstra_path_walk(p._path_c, x, y): @@ -1793,8 +1969,7 @@ def dijkstra_path_walk(p): @deprecate("libtcod objects are deleted automatically.") -def dijkstra_delete(p): - # type (Any) -> None +def dijkstra_delete(p: tcod.path.Dijkstra) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. This function exists for backwards compatibility with libtcodpy. @@ -2299,14 +2474,14 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: DeprecationWarning, stacklevel=2, ) - return hm[y, x] + return hm[y, x] # type: ignore elif hm.flags["F_CONTIGUOUS"]: warnings.warn( "Get a value from this heightmap with hm[x,y]", DeprecationWarning, stacklevel=2, ) - return hm[x, y] + return hm[x, y] # type: ignore else: raise ValueError("This array is not contiguous.") @@ -2324,8 +2499,8 @@ def heightmap_get_interpolated_value( Returns: float: The value at ``x``, ``y``. """ - return lib.TCOD_heightmap_get_interpolated_value( - _heightmap_cdata(hm), x, y + return float( + lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), x, y) ) @@ -2340,7 +2515,7 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: Returns: float: The steepness at ``x``, ``y``. From 0 to (pi / 2) """ - return lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y) + return float(lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y)) def heightmap_get_normal( @@ -2378,7 +2553,7 @@ def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: Can be replaced by an equivalent NumPy function such as: ``numpy.count_nonzero((mi <= hm) & (hm < ma))`` """ - return lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma) + return int(lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma)) def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: @@ -2391,8 +2566,8 @@ def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: Returns: bool: True if the map edges are below ``waterlevel``, otherwise False. """ - return lib.TCOD_heightmap_has_land_on_border( - _heightmap_cdata(hm), waterlevel + return bool( + lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm), waterlevel) ) @@ -2426,59 +2601,62 @@ def heightmap_delete(hm: Any) -> None: """ -def image_new(width, height): +def image_new(width: int, height: int) -> tcod.image.Image: return tcod.image.Image(width, height) -def image_clear(image, col): +def image_clear(image: tcod.image.Image, col: Tuple[int, int, int]) -> None: image.clear(col) -def image_invert(image): +def image_invert(image: tcod.image.Image) -> None: image.invert() -def image_hflip(image): +def image_hflip(image: tcod.image.Image) -> None: image.hflip() -def image_rotate90(image, num=1): +def image_rotate90(image: tcod.image.Image, num: int = 1) -> None: image.rotate90(num) -def image_vflip(image): +def image_vflip(image: tcod.image.Image) -> None: image.vflip() -def image_scale(image, neww, newh): +def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: image.scale(neww, newh) -def image_set_key_color(image, col): +def image_set_key_color( + image: tcod.image.Image, col: Tuple[int, int, int] +) -> None: image.set_key_color(col) -def image_get_alpha(image, x, y): - image.get_alpha(x, y) +def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int: + return image.get_alpha(x, y) -def image_is_pixel_transparent(image, x, y): - lib.TCOD_image_is_pixel_transparent(image.image_c, x, y) +def image_is_pixel_transparent( + image: tcod.image.Image, x: int, y: int +) -> bool: + return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) -def image_load(filename): +def image_load(filename: str) -> tcod.image.Image: """Load an image file into an Image instance and return it. Args: filename (AnyStr): Path to a .bmp or .png image file. """ - filename = _bytes(filename) return tcod.image.Image._from_cdata( - ffi.gc(lib.TCOD_image_load(filename), lib.TCOD_image_delete) + ffi.gc(lib.TCOD_image_load(_bytes(filename)), lib.TCOD_image_delete) ) -def image_from_console(console): +def image_from_console(console: tcod.console.Console) -> tcod.image.Image: """Return an Image with a Consoles pixel data. This effectively takes a screen-shot of the Console. @@ -2494,52 +2672,85 @@ def image_from_console(console): ) -def image_refresh_console(image, console): +def image_refresh_console( + image: tcod.image.Image, console: tcod.console.Console +) -> None: image.refresh_console(console) -def image_get_size(image): +def image_get_size(image: tcod.image.Image) -> Tuple[int, int]: return image.width, image.height -def image_get_pixel(image, x, y): +def image_get_pixel( + image: tcod.image.Image, x: int, y: int +) -> Tuple[int, int, int]: return image.get_pixel(x, y) -def image_get_mipmap_pixel(image, x0, y0, x1, y1): +def image_get_mipmap_pixel( + image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float +) -> Tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) -def image_put_pixel(image, x, y, col): +def image_put_pixel( + image: tcod.image.Image, x: int, y: int, col: Tuple[int, int, int] +) -> None: image.put_pixel(x, y, col) -def image_blit(image, console, x, y, bkgnd_flag, scalex, scaley, angle): +def image_blit( + image: tcod.image.Image, + console: tcod.console.Console, + x: float, + y: float, + bkgnd_flag: int, + scalex: float, + scaley: float, + angle: float, +) -> None: image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle) -def image_blit_rect(image, console, x, y, w, h, bkgnd_flag): +def image_blit_rect( + image: tcod.image.Image, + console: tcod.console.Console, + x: int, + y: int, + w: int, + h: int, + bkgnd_flag: int, +) -> None: image.blit_rect(console, x, y, w, h, bkgnd_flag) -def image_blit_2x(image, console, dx, dy, sx=0, sy=0, w=-1, h=-1): +def image_blit_2x( + image: tcod.image.Image, + console: tcod.console.Console, + dx: int, + dy: int, + sx: int = 0, + sy: int = 0, + w: int = -1, + h: int = -1, +) -> None: image.blit_2x(console, dx, dy, sx, sy, w, h) -def image_save(image, filename): +def image_save(image: tcod.image.Image, filename: str) -> None: image.save_as(filename) @deprecate("libtcod objects are deleted automatically.") -def image_delete(image): - # type (Any) -> None +def image_delete(image: tcod.image.Image) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. This function exists for backwards compatibility with libtcodpy. """ -def line_init(xo, yo, xd, yd): +def line_init(xo: int, yo: int, xd: int, yd: int) -> None: """Initilize a line whose points will be returned by `line_step`. This function does not return anything on its own. @@ -2558,7 +2769,7 @@ def line_init(xo, yo, xd, yd): lib.TCOD_line_init(xo, yo, xd, yd) -def line_step(): +def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: """After calling line_init returns (x, y) points of the line. Once all points are exhausted this function will return (None, None) @@ -2579,7 +2790,9 @@ def line_step(): return None, None -def line(xo, yo, xd, yd, py_callback): +def line( + xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool] +) -> bool: """ Iterate over a line using a callback function. Your callback function will take x and y parameters and return True to @@ -2610,10 +2823,10 @@ def line(xo, yo, xd, yd, py_callback): return False -def line_iter(xo, yo, xd, yd): - """ returns an iterator +def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: + """ returns an Iterable - This iterator does not include the origin point. + This Iterable does not include the origin point. Args: xo (int): X starting point. @@ -2622,7 +2835,7 @@ def line_iter(xo, yo, xd, yd): yd (int): Y destination point. Returns: - Iterator[Tuple[int,int]]: An iterator of (x,y) points. + Iterable[Tuple[int,int]]: An Iterable of (x,y) points. """ data = ffi.new("TCOD_bresenham_data_t *") lib.TCOD_line_init_mt(xo, yo, xd, yd, data) @@ -2665,8 +2878,7 @@ def line_where( return tuple(array) # type: ignore -def map_new(w, h): - # type: (int, int) -> tcod.map.Map +def map_new(w: int, h: int) -> tcod.map.Map: """Return a :any:`tcod.map.Map` with a width and height. .. deprecated:: 4.5 @@ -2676,8 +2888,7 @@ def map_new(w, h): return tcod.map.Map(w, h) -def map_copy(source, dest): - # type: (tcod.map.Map, tcod.map.Map) -> None +def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: """Copy map data from `source` to `dest`. .. deprecated:: 4.5 @@ -2691,8 +2902,9 @@ def map_copy(source, dest): dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore -def map_set_properties(m, x, y, isTrans, isWalk): - # type: (tcod.map.Map, int, int, bool, bool) -> None +def map_set_properties( + m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool +) -> None: """Set the properties of a single cell. .. note:: @@ -2704,8 +2916,9 @@ def map_set_properties(m, x, y, isTrans, isWalk): lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk) -def map_clear(m, transparent=False, walkable=False): - # type: (tcod.map.Map, bool, bool) -> None +def map_clear( + m: tcod.map.Map, transparent: bool = False, walkable: bool = False +) -> None: """Change all map cells to a specific value. .. deprecated:: 4.5 @@ -2732,8 +2945,7 @@ def map_compute_fov( m.compute_fov(x, y, radius, light_walls, algo) -def map_is_in_fov(m, x, y): - # type: (tcod.map.Map, int, int) -> bool +def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: """Return True if the cell at x,y is lit by the last field-of-view algorithm. @@ -2742,42 +2954,38 @@ def map_is_in_fov(m, x, y): .. deprecated:: 4.5 Use :any:`tcod.map.Map.fov` to check this property. """ - return lib.TCOD_map_is_in_fov(m.map_c, x, y) + return bool(lib.TCOD_map_is_in_fov(m.map_c, x, y)) -def map_is_transparent(m, x, y): - # type: (tcod.map.Map, int, int) -> bool +def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: """ .. note:: This function is slow. .. deprecated:: 4.5 Use :any:`tcod.map.Map.transparent` to check this property. """ - return lib.TCOD_map_is_transparent(m.map_c, x, y) + return bool(lib.TCOD_map_is_transparent(m.map_c, x, y)) -def map_is_walkable(m, x, y): - # type: (tcod.map.Map, int, int) -> bool +def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: """ .. note:: This function is slow. .. deprecated:: 4.5 Use :any:`tcod.map.Map.walkable` to check this property. """ - return lib.TCOD_map_is_walkable(m.map_c, x, y) + return bool(lib.TCOD_map_is_walkable(m.map_c, x, y)) @deprecate("libtcod objects are deleted automatically.") -def map_delete(m): - # type (Any) -> None +def map_delete(m: tcod.map.Map) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. This function exists for backwards compatibility with libtcodpy. """ -def map_get_width(map): - # type: (tcod.map.Map) -> int +def map_get_width(map: tcod.map.Map) -> int: """Return the width of a map. .. deprecated:: 4.5 @@ -2786,8 +2994,7 @@ def map_get_width(map): return map.width -def map_get_height(map): - # type: (tcod.map.Map) -> int +def map_get_height(map: tcod.map.Map) -> int: """Return the height of a map. .. deprecated:: 4.5 @@ -2796,45 +3003,41 @@ def map_get_height(map): return map.height -def mouse_show_cursor(visible): - # type: (bool) -> None +def mouse_show_cursor(visible: bool) -> None: """Change the visibility of the mouse cursor.""" lib.TCOD_mouse_show_cursor(visible) -def mouse_is_cursor_visible(): - # type: () -> bool +def mouse_is_cursor_visible() -> bool: """Return True if the mouse cursor is visible.""" - return lib.TCOD_mouse_is_cursor_visible() + return bool(lib.TCOD_mouse_is_cursor_visible()) -def mouse_move(x, y): - # type (int, int) -> None +def mouse_move(x: int, y: int) -> None: lib.TCOD_mouse_move(x, y) -def mouse_get_status(): - # type: () -> Mouse +def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) -def namegen_parse(filename, random=None): - filename = _bytes(filename) - lib.TCOD_namegen_parse(filename, random or ffi.NULL) +def namegen_parse( + filename: str, random: Optional[tcod.random.Random] = None +) -> None: + lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) -def namegen_generate(name): - name = _bytes(name) - return _unpack_char_p(lib.TCOD_namegen_generate(name, False)) +def namegen_generate(name: str) -> str: + return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) -def namegen_generate_custom(name, rule): - name = _bytes(name) - rule = _bytes(rule) - return _unpack_char_p(lib.TCOD_namegen_generate(name, rule, False)) +def namegen_generate_custom(name: str, rule: str) -> str: + return _unpack_char_p( + lib.TCOD_namegen_generate(_bytes(name), _bytes(rule), False) + ) -def namegen_get_sets(): +def namegen_get_sets() -> List[str]: sets = lib.TCOD_namegen_get_sets() try: lst = [] @@ -2847,16 +3050,16 @@ def namegen_get_sets(): return lst -def namegen_destroy(): +def namegen_destroy() -> None: lib.TCOD_namegen_destroy() def noise_new( - dim, - h=NOISE_DEFAULT_HURST, - l=NOISE_DEFAULT_LACUNARITY, # noqa: E741 - random=None, -): + dim: int, + h: float = NOISE_DEFAULT_HURST, + l: float = NOISE_DEFAULT_LACUNARITY, # noqa: E741 + random: Optional[tcod.random.Random] = None, +) -> tcod.noise.Noise: """Return a new Noise instance. Args: @@ -2871,7 +3074,7 @@ def noise_new( return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random) -def noise_set_type(n, typ): +def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: """Set a Noise objects default noise algorithm. Args: @@ -2880,7 +3083,9 @@ def noise_set_type(n, typ): n.algorithm = typ -def noise_get(n, f, typ=NOISE_DEFAULT): +def noise_get( + n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT +) -> float: """Return the noise value sampled from the ``f`` coordinate. ``f`` should be a tuple or list with a length matching @@ -2896,10 +3101,15 @@ def noise_get(n, f, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ) + return float(lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ)) -def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT): +def noise_get_fbm( + n: tcod.noise.Noise, + f: Sequence[float], + oc: float, + typ: int = NOISE_DEFAULT, +) -> float: """Return the fractal Brownian motion sampled from the ``f`` coordinate. Args: @@ -2911,12 +3121,17 @@ def noise_get_fbm(n, f, oc, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_fbm_ex( - n.noise_c, ffi.new("float[4]", f), oc, typ + return float( + lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ) ) -def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): +def noise_get_turbulence( + n: tcod.noise.Noise, + f: Sequence[float], + oc: float, + typ: int = NOISE_DEFAULT, +) -> float: """Return the turbulence noise sampled from the ``f`` coordinate. Args: @@ -2928,13 +3143,15 @@ def noise_get_turbulence(n, f, oc, typ=NOISE_DEFAULT): Returns: float: The sampled noise value. """ - return lib.TCOD_noise_get_turbulence_ex( - n.noise_c, ffi.new("float[4]", f), oc, typ + return float( + lib.TCOD_noise_get_turbulence_ex( + n.noise_c, ffi.new("float[4]", f), oc, typ + ) ) @deprecate("libtcod objects are deleted automatically.") -def noise_delete(n): +def noise_delete(n: tcod.noise.Noise) -> None: # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -2942,7 +3159,7 @@ def noise_delete(n): """ -def _unpack_union(type_, union): +def _unpack_union(type_: int, union: Any) -> Any: """ unpack items from parser new_property (value_converter) """ @@ -2969,58 +3186,61 @@ def _unpack_union(type_, union): raise RuntimeError("Unknown libtcod type: %i" % type_) -def _convert_TCODList(clist, type_): +def _convert_TCODList(clist: Any, type_: int) -> Any: return [ _unpack_union(type_, lib.TDL_list_get_union(clist, i)) for i in range(lib.TCOD_list_size(clist)) ] -def parser_new(): +@deprecate("Parser functions have been deprecated.") +def parser_new() -> Any: return ffi.gc(lib.TCOD_parser_new(), lib.TCOD_parser_delete) -def parser_new_struct(parser, name): +@deprecate("Parser functions have been deprecated.") +def parser_new_struct(parser: Any, name: str) -> Any: return lib.TCOD_parser_new_struct(parser, name) # prevent multiple threads from messing with def_extern callbacks _parser_callback_lock = threading.Lock() -_parser_listener = None # temporary global pointer to a listener instance +# temporary global pointer to a listener instance +_parser_listener = None # type: Any -@ffi.def_extern() -def _pycall_parser_new_struct(struct, name): +@ffi.def_extern() # type: ignore +def _pycall_parser_new_struct(struct: Any, name: str) -> Any: return _parser_listener.new_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() -def _pycall_parser_new_flag(name): +@ffi.def_extern() # type: ignore +def _pycall_parser_new_flag(name: str) -> Any: return _parser_listener.new_flag(_unpack_char_p(name)) -@ffi.def_extern() -def _pycall_parser_new_property(propname, type, value): +@ffi.def_extern() # type: ignore +def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: return _parser_listener.new_property( _unpack_char_p(propname), type, _unpack_union(type, value) ) -@ffi.def_extern() -def _pycall_parser_end_struct(struct, name): +@ffi.def_extern() # type: ignore +def _pycall_parser_end_struct(struct: Any, name: Any) -> Any: return _parser_listener.end_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() -def _pycall_parser_error(msg): +@ffi.def_extern() # type: ignore +def _pycall_parser_error(msg: Any) -> None: _parser_listener.error(_unpack_char_p(msg)) -def parser_run(parser, filename, listener=None): +@deprecate("Parser functions have been deprecated.") +def parser_run(parser: Any, filename: str, listener: Any = None) -> None: global _parser_listener - filename = _bytes(filename) if not listener: - lib.TCOD_parser_run(parser, filename, ffi.NULL) + lib.TCOD_parser_run(parser, _bytes(filename), ffi.NULL) return propagate_manager = _PropagateException() @@ -3039,11 +3259,11 @@ def parser_run(parser, filename, listener=None): with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, filename, clistener) + lib.TCOD_parser_run(parser, _bytes(filename), clistener) @deprecate("libtcod objects are deleted automatically.") -def parser_delete(parser): +def parser_delete(parser: Any) -> None: # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -3051,48 +3271,50 @@ def parser_delete(parser): """ -def parser_get_bool_property(parser, name): - name = _bytes(name) - return bool(lib.TCOD_parser_get_bool_property(parser, name)) +@deprecate("Parser functions have been deprecated.") +def parser_get_bool_property(parser: Any, name: str) -> bool: + return bool(lib.TCOD_parser_get_bool_property(parser, _bytes(name))) -def parser_get_int_property(parser, name): - name = _bytes(name) - return lib.TCOD_parser_get_int_property(parser, name) +@deprecate("Parser functions have been deprecated.") +def parser_get_int_property(parser: Any, name: str) -> int: + return int(lib.TCOD_parser_get_int_property(parser, _bytes(name))) -def parser_get_char_property(parser, name): - name = _bytes(name) - return chr(lib.TCOD_parser_get_char_property(parser, name)) +@deprecate("Parser functions have been deprecated.") +def parser_get_char_property(parser: Any, name: str) -> str: + return chr(lib.TCOD_parser_get_char_property(parser, _bytes(name))) -def parser_get_float_property(parser, name): - name = _bytes(name) - return lib.TCOD_parser_get_float_property(parser, name) +@deprecate("Parser functions have been deprecated.") +def parser_get_float_property(parser: Any, name: str) -> float: + return float(lib.TCOD_parser_get_float_property(parser, _bytes(name))) -def parser_get_string_property(parser, name): - name = _bytes(name) - return _unpack_char_p(lib.TCOD_parser_get_string_property(parser, name)) +@deprecate("Parser functions have been deprecated.") +def parser_get_string_property(parser: Any, name: str) -> str: + return _unpack_char_p( + lib.TCOD_parser_get_string_property(parser, _bytes(name)) + ) -def parser_get_color_property(parser, name): - name = _bytes(name) +@deprecate("Parser functions have been deprecated.") +def parser_get_color_property(parser: Any, name: str) -> Color: return Color._new_from_cdata( - lib.TCOD_parser_get_color_property(parser, name) + lib.TCOD_parser_get_color_property(parser, _bytes(name)) ) -def parser_get_dice_property(parser, name): +@deprecate("Parser functions have been deprecated.") +def parser_get_dice_property(parser: Any, name: str) -> Dice: d = ffi.new("TCOD_dice_t *") - name = _bytes(name) - lib.TCOD_parser_get_dice_property_py(parser, name, d) + lib.TCOD_parser_get_dice_property_py(parser, _bytes(name), d) return Dice(d) -def parser_get_list_property(parser, name, type): - name = _bytes(name) - clist = lib.TCOD_parser_get_list_property(parser, name, type) +@deprecate("Parser functions have been deprecated.") +def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: + clist = lib.TCOD_parser_get_list_property(parser, _bytes(name), type) return _convert_TCODList(clist, type) @@ -3106,7 +3328,7 @@ def parser_get_list_property(parser, name, type): DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4 -def random_get_instance(): +def random_get_instance() -> tcod.random.Random: """Return the default Random instance. Returns: @@ -3117,7 +3339,7 @@ def random_get_instance(): ) -def random_new(algo=RNG_CMWC): +def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: """Return a new Random instance. Using ``algo``. Args: @@ -3129,7 +3351,9 @@ def random_new(algo=RNG_CMWC): return tcod.random.Random(algo) -def random_new_from_seed(seed, algo=RNG_CMWC): +def random_new_from_seed( + seed: Hashable, algo: int = RNG_CMWC +) -> tcod.random.Random: """Return a new Random instance. Using the given ``seed`` and ``algo``. Args: @@ -3143,7 +3367,9 @@ def random_new_from_seed(seed, algo=RNG_CMWC): return tcod.random.Random(algo, seed) -def random_set_distribution(rnd, dist): +def random_set_distribution( + rnd: Optional[tcod.random.Random], dist: int +) -> None: """Change the distribution mode of a random number generator. Args: @@ -3153,7 +3379,7 @@ def random_set_distribution(rnd, dist): lib.TCOD_random_set_distribution(rnd.random_c if rnd else ffi.NULL, dist) -def random_get_int(rnd, mi, ma): +def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: """Return a random integer in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3166,10 +3392,14 @@ def random_get_int(rnd, mi, ma): Returns: int: A random integer in the range ``mi`` <= n <= ``ma``. """ - return lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma) + return int( + lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma) + ) -def random_get_float(rnd, mi, ma): +def random_get_float( + rnd: Optional[tcod.random.Random], mi: float, ma: float +) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3183,24 +3413,29 @@ def random_get_float(rnd, mi, ma): float: A random double precision float in the range ``mi`` <= n <= ``ma``. """ - return lib.TCOD_random_get_double( - rnd.random_c if rnd else ffi.NULL, mi, ma + return float( + lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma) ) -def random_get_double(rnd, mi, ma): +@deprecate("Call tcod.random_get_float instead.") +def random_get_double( + rnd: Optional[tcod.random.Random], mi: float, ma: float +) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 Use :any:`random_get_float` instead. Both funtions return a double precision float. """ - return lib.TCOD_random_get_double( - rnd.random_c if rnd else ffi.NULL, mi, ma + return float( + lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma) ) -def random_get_int_mean(rnd, mi, ma, mean): +def random_get_int_mean( + rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int +) -> int: """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3214,12 +3449,16 @@ def random_get_int_mean(rnd, mi, ma, mean): Returns: int: A random weighted integer in the range ``mi`` <= n <= ``ma``. """ - return lib.TCOD_random_get_int_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean + return int( + lib.TCOD_random_get_int_mean( + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) ) -def random_get_float_mean(rnd, mi, ma, mean): +def random_get_float_mean( + rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float +) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3234,31 +3473,37 @@ def random_get_float_mean(rnd, mi, ma, mean): float: A random weighted double precision float in the range ``mi`` <= n <= ``ma``. """ - return lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean + return float( + lib.TCOD_random_get_double_mean( + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) ) -def random_get_double_mean(rnd, mi, ma, mean): +@deprecate("Call tcod.random_get_float_mean instead.") +def random_get_double_mean( + rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float +) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 Use :any:`random_get_float_mean` instead. Both funtions return a double precision float. """ - return lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean + return float( + lib.TCOD_random_get_double_mean( + rnd.random_c if rnd else ffi.NULL, mi, ma, mean + ) ) -def random_save(rnd): +@deprecate("Use the standard library 'copy' module instead.") +def random_save(rnd: Optional[tcod.random.Random]) -> tcod.random.Random: """Return a copy of a random number generator. - Args: - rnd (Optional[Random]): A Random instance, or None to use the default. - - Returns: - Random: A Random instance with a copy of the random generator. + .. deprecated:: 8.4 + You can use the standard library copy and pickle modules to save a + random state. """ return tcod.random.Random._new_from_cdata( ffi.gc( @@ -3271,34 +3516,43 @@ def random_save(rnd): ) -def random_restore(rnd, backup): +@deprecate("This function is deprecated.") +def random_restore( + rnd: Optional[tcod.random.Random], backup: tcod.random.Random +) -> None: """Restore a random number generator from a backed up copy. Args: rnd (Optional[Random]): A Random instance, or None to use the default. backup (Random): The Random instance which was used as a backup. + + .. deprecated:: 8.4 + You can use the standard library copy and pickle modules to save a + random state. """ lib.TCOD_random_restore(rnd.random_c if rnd else ffi.NULL, backup.random_c) @deprecate("libtcod objects are deleted automatically.") -def random_delete(rnd): - # type (Any) -> None +def random_delete(rnd: tcod.random.Random) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. This function exists for backwards compatibility with libtcodpy. """ -def struct_add_flag(struct, name): +@deprecate("This function is deprecated.") +def struct_add_flag(struct, name): # type: ignore lib.TCOD_struct_add_flag(struct, name) -def struct_add_property(struct, name, typ, mandatory): +@deprecate("This function is deprecated.") +def struct_add_property(struct, name, typ, mandatory): # type: ignore lib.TCOD_struct_add_property(struct, name, typ, mandatory) -def struct_add_value_list(struct, name, value_list, mandatory): +@deprecate("This function is deprecated.") +def struct_add_value_list(struct, name, value_list, mandatory): # type: ignore c_strings = [ ffi.new("char[]", value.encode("utf-8")) for value in value_list ] @@ -3306,23 +3560,28 @@ def struct_add_value_list(struct, name, value_list, mandatory): lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory) -def struct_add_list_property(struct, name, typ, mandatory): +@deprecate("This function is deprecated.") +def struct_add_list_property(struct, name, typ, mandatory): # type: ignore lib.TCOD_struct_add_list_property(struct, name, typ, mandatory) -def struct_add_structure(struct, sub_struct): +@deprecate("This function is deprecated.") +def struct_add_structure(struct, sub_struct): # type: ignore lib.TCOD_struct_add_structure(struct, sub_struct) -def struct_get_name(struct): +@deprecate("This function is deprecated.") +def struct_get_name(struct): # type: ignore return _unpack_char_p(lib.TCOD_struct_get_name(struct)) -def struct_is_mandatory(struct, name): +@deprecate("This function is deprecated.") +def struct_is_mandatory(struct, name): # type: ignore return lib.TCOD_struct_is_mandatory(struct, name) -def struct_get_type(struct, name): +@deprecate("This function is deprecated.") +def struct_get_type(struct, name): # type: ignore return lib.TCOD_struct_get_type(struct, name) @@ -3349,7 +3608,7 @@ def sys_get_fps() -> int: Returns: int: The currently measured frame rate. """ - return lib.TCOD_sys_get_fps() + return int(lib.TCOD_sys_get_fps()) def sys_get_last_frame_length() -> float: @@ -3358,7 +3617,7 @@ def sys_get_last_frame_length() -> float: Returns: float: The delta time of the last rendered frame. """ - return lib.TCOD_sys_get_last_frame_length() + return float(lib.TCOD_sys_get_last_frame_length()) @deprecate("Use Python's standard 'time' module instead of this function.") @@ -3384,7 +3643,7 @@ def sys_elapsed_milli() -> int: .. deprecated:: 2.0 Use :any:`time.clock` instead. """ - return lib.TCOD_sys_elapsed_milli() + return int(lib.TCOD_sys_elapsed_milli()) @deprecate("Use Python's standard 'time' module instead of this function.") @@ -3397,7 +3656,7 @@ def sys_elapsed_seconds() -> float: .. deprecated:: 2.0 Use :any:`time.clock` instead. """ - return lib.TCOD_sys_elapsed_seconds() + return float(lib.TCOD_sys_elapsed_seconds()) def sys_set_renderer(renderer: int) -> None: @@ -3415,7 +3674,7 @@ def sys_get_renderer() -> int: """Return the current rendering mode. """ - return lib.TCOD_sys_get_renderer() + return int(lib.TCOD_sys_get_renderer()) # easy screenshots @@ -3479,8 +3738,15 @@ def sys_get_char_size() -> Tuple[int, int]: # update font bitmap -def sys_update_char(asciiCode, fontx, fonty, img, x, y): - """Dynamically update the current frot with img. +def sys_update_char( + asciiCode: int, + fontx: int, + fonty: int, + img: tcod.image.Image, + x: int, + y: int, +) -> None: + """Dynamically update the current font with img. All cells using this asciiCode will be updated at the next call to :any:`tcod.console_flush`. @@ -3498,7 +3764,7 @@ def sys_update_char(asciiCode, fontx, fonty, img, x, y): lib.TCOD_sys_update_char(_int(asciiCode), fontx, fonty, img, x, y) -def sys_register_SDL_renderer(callback): +def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: """Register a custom randering function with libtcod. Note: @@ -3515,14 +3781,16 @@ def sys_register_SDL_renderer(callback): """ with _PropagateException() as propagate: - @ffi.def_extern(onerror=propagate) - def _pycall_sdl_hook(sdl_surface): + @ffi.def_extern(onerror=propagate) # type: ignore + def _pycall_sdl_hook(sdl_surface: Any) -> None: callback(sdl_surface) lib.TCOD_sys_register_SDL_renderer(lib._pycall_sdl_hook) -def sys_check_for_event(mask, k, m): +def sys_check_for_event( + mask: int, k: Optional[Key], m: Optional[Mouse] +) -> int: """Check for and return an event. Args: @@ -3532,12 +3800,16 @@ def sys_check_for_event(mask, k, m): m (Optional[Mouse]): A tcod.Mouse instance which might be updated with an event. Can be None. """ - return lib.TCOD_sys_check_for_event( - mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL + return int( + lib.TCOD_sys_check_for_event( + mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL + ) ) -def sys_wait_for_event(mask, k, m, flush): +def sys_wait_for_event( + mask: int, k: Optional[Key], m: Optional[Mouse], flush: bool +) -> int: """Wait for an event then return. If flush is True then the buffer will be cleared before waiting. Otherwise @@ -3551,29 +3823,34 @@ def sys_wait_for_event(mask, k, m, flush): with an event. Can be None. flush (bool): Clear the event buffer before waiting. """ - return lib.TCOD_sys_wait_for_event( - mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL, flush + return int( + lib.TCOD_sys_wait_for_event( + mask, + k.key_p if k else ffi.NULL, + m.mouse_p if m else ffi.NULL, + flush, + ) ) @deprecate("This function does not provide reliable access to the clipboard.") -def sys_clipboard_set(text): +def sys_clipboard_set(text: str) -> bool: """Sets the clipboard to `text`. .. deprecated:: 6.0 This function does not provide reliable access to the clipboard. """ - return lib.TCOD_sys_clipboard_set(text.encode("utf-8")) + return bool(lib.TCOD_sys_clipboard_set(text.encode("utf-8"))) @deprecate("This function does not provide reliable access to the clipboard.") -def sys_clipboard_get(): +def sys_clipboard_get() -> str: """Return the current value of the clipboard. .. deprecated:: 6.0 This function does not provide reliable access to the clipboard. """ - return ffi.string(lib.TCOD_sys_clipboard_get()).decode("utf-8") + return str(ffi.string(lib.TCOD_sys_clipboard_get()).decode("utf-8")) @atexit.register diff --git a/tcod/map.py b/tcod/map.py index 84945578..2a7659a9 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -103,7 +103,7 @@ def compute_fov( radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, - ): + ) -> None: """Compute a field-of-view on the current instance. Args: @@ -119,7 +119,7 @@ def compute_fov( self.map_c, x, y, radius, light_walls, algorithm ) - def __setstate__(self, state): + def __setstate__(self, state: Any) -> None: if "_Map__buffer" not in state: # deprecated # remove this check on major version update self.__buffer = np.zeros( @@ -135,7 +135,7 @@ def __setstate__(self, state): self.__dict__.update(state) self.map_c = self.__as_cdata() - def __getstate__(self): + def __getstate__(self) -> Any: state = self.__dict__.copy() del state["map_c"] return state diff --git a/tcod/noise.py b/tcod/noise.py index 67b3aa3d..ac687638 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -33,10 +33,13 @@ samples = noise.sample_ogrid(ogrid) print(samples) """ +from typing import Any, Optional + import numpy as np from tcod.libtcod import ffi, lib import tcod.constants +import tcod.random """Noise implementation constants""" SIMPLE = 0 @@ -71,13 +74,13 @@ class Noise(object): def __init__( self, - dimensions, - algorithm=2, - implementation=SIMPLE, - hurst=0.5, - lacunarity=2.0, - octaves=4, - seed=None, + dimensions: int, + algorithm: int = 2, + implementation: int = SIMPLE, + hurst: float = 0.5, + lacunarity: float = 2.0, + octaves: float = 4, + seed: Optional[tcod.random.Random] = None, ): if not 0 < dimensions <= 4: raise ValueError( @@ -100,48 +103,50 @@ def __init__( self.implementation = implementation # sanity check @property - def dimensions(self): - return self._tdl_noise_c.dimensions + def dimensions(self) -> int: + return int(self._tdl_noise_c.dimensions) @property - def dimentions(self): # deprecated + def dimentions(self) -> int: # deprecated return self.dimensions @property - def algorithm(self): - return self.noise_c.noise_type + def algorithm(self) -> int: + return int(self.noise_c.noise_type) @algorithm.setter - def algorithm(self, value): + def algorithm(self, value: int) -> None: lib.TCOD_noise_set_type(self.noise_c, value) @property - def implementation(self): - return self._tdl_noise_c.implementation + def implementation(self) -> int: + return int(self._tdl_noise_c.implementation) @implementation.setter - def implementation(self, value): + def implementation(self, value: int) -> None: if not 0 <= value < 3: raise ValueError("%r is not a valid implementation. " % (value,)) self._tdl_noise_c.implementation = value @property - def hurst(self): - return self.noise_c.H + def hurst(self) -> float: + return float(self.noise_c.H) @property - def lacunarity(self): - return self.noise_c.lacunarity + def lacunarity(self) -> float: + return float(self.noise_c.lacunarity) @property - def octaves(self): - return self._tdl_noise_c.octaves + def octaves(self) -> float: + return float(self._tdl_noise_c.octaves) @octaves.setter - def octaves(self, value): + def octaves(self, value: float) -> None: self._tdl_noise_c.octaves = value - def get_point(self, x=0, y=0, z=0, w=0): + def get_point( + self, x: float = 0, y: float = 0, z: float = 0, w: float = 0 + ) -> float: """Return the noise value at the (x, y, z, w) point. Args: @@ -150,9 +155,9 @@ def get_point(self, x=0, y=0, z=0, w=0): z (float): The position on the 3rd axis. w (float): The position on the 4th axis. """ - return lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w)) + return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) - def sample_mgrid(self, mgrid): + def sample_mgrid(self, mgrid: np.array) -> np.array: """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -188,7 +193,7 @@ def sample_mgrid(self, mgrid): ) return out - def sample_ogrid(self, ogrid): + def sample_ogrid(self, ogrid: np.array) -> np.array: """Sample an open mesh-grid array and return the result. Args @@ -217,7 +222,7 @@ def sample_ogrid(self, ogrid): ) return out - def __getstate__(self): + def __getstate__(self) -> Any: state = self.__dict__.copy() if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # Trigger a side effect of wavelet, so that copies will be synced. @@ -250,7 +255,7 @@ def __getstate__(self): } return state - def __setstate__(self, state): + def __setstate__(self, state: Any) -> None: if isinstance(state, tuple): # deprecated format return self._setstate_old(state) # unpack wavelet tile data if it exists @@ -271,7 +276,7 @@ def __setstate__(self, state): state["_tdl_noise_c"] = ffi.new("TDLNoise*", state["_tdl_noise_c"]) self.__dict__.update(state) - def _setstate_old(self, state): + def _setstate_old(self, state: Any) -> None: self._random = state[0] self.noise_c = ffi.new("struct TCOD_Noise*") self.noise_c.ndim = state[3] diff --git a/tcod/path.py b/tcod/path.py index 4bedcf61..3085c46e 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -40,49 +40,52 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ -import numpy as np from typing import Any, Callable, List, Tuple, Union # noqa: F401 +import numpy as np + from tcod.libtcod import lib, ffi import tcod.map # noqa: F401 -@ffi.def_extern() +@ffi.def_extern() # type: ignore def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: """libtcodpy style callback, needs to preserve the old userData issue.""" func, userData = ffi.from_handle(handle) - return func(x1, y1, x2, y2, userData) + return func(x1, y1, x2, y2, userData) # type: ignore -@ffi.def_extern() +@ffi.def_extern() # type: ignore def _pycall_path_simple( x1: int, y1: int, x2: int, y2: int, handle: Any ) -> float: """Does less and should run faster, just calls the handle function.""" - return ffi.from_handle(handle)(x1, y1, x2, y2) + return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore -@ffi.def_extern() +@ffi.def_extern() # type: ignore def _pycall_path_swap_src_dest( x1: int, y1: int, x2: int, y2: int, handle: Any ) -> float: """A TDL function dest comes first to match up with a dest only call.""" - return ffi.from_handle(handle)(x2, y2, x1, y1) + return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore -@ffi.def_extern() +@ffi.def_extern() # type: ignore def _pycall_path_dest_only( x1: int, y1: int, x2: int, y2: int, handle: Any ) -> float: """A TDL function which samples the dest coordinate only.""" - return ffi.from_handle(handle)(x2, y2) + return ffi.from_handle(handle)(x2, y2) # type: ignore def _get_pathcost_func( name: str ) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" - return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) + return ffi.cast( # type: ignore + "TCOD_path_func_t", ffi.addressof(lib, name) + ) class _EdgeCostFunc(object): @@ -103,7 +106,7 @@ def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: """Return (C callback, userdata handle, shape)""" return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape - def __repr__(self): + def __repr__(self) -> str: return "%s(%r, shape=%r)" % ( self.__class__.__name__, self._userdata, @@ -135,7 +138,7 @@ def __init__( super(EdgeCostCallback, self).__init__(callback, shape) -class NodeCostArray(np.ndarray): +class NodeCostArray(np.ndarray): # type: ignore """Calculate cost from a numpy array of nodes. `array` is a NumPy array holding the path-cost of each node. @@ -153,12 +156,12 @@ class NodeCostArray(np.ndarray): np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), } - def __new__(cls, array): + def __new__(cls, array: np.array) -> "NodeCostArray": """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) - return self + return self # type: ignore - def __repr__(self): + def __repr__(self) -> str: return "%s(%r)" % ( self.__class__.__name__, repr(self.view(np.ndarray)), @@ -221,14 +224,14 @@ def __init__(self, cost: Any, diagonal: float = 1.41): self._path_delete, ) - def __repr__(self): + def __repr__(self) -> str: return "%s(cost=%r, diagonal=%r)" % ( self.__class__.__name__, self.cost, self.diagonal, ) - def __getstate__(self): + def __getstate__(self) -> Any: state = self.__dict__.copy() del state["_path_c"] del state["shape"] @@ -236,9 +239,9 @@ def __getstate__(self): del state["_userdata"] return state - def __setstate__(self, state): + def __setstate__(self, state: Any) -> None: self.__dict__.update(state) - self.__init__(self.cost, self.diagonal) + self.__init__(self.cost, self.diagonal) # type: ignore _path_new_using_map = lib.TCOD_path_new_using_map _path_new_using_function = lib.TCOD_path_new_using_function diff --git a/tcod/random.py b/tcod/random.py index 73185b8d..11b0e3ef 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -2,6 +2,7 @@ Random module docs. """ import random +from typing import Any, Hashable, Optional from tcod.libtcod import ffi, lib import tcod.constants @@ -27,7 +28,7 @@ class Random(object): random_c (CData): A cffi pointer to a TCOD_random_t object. """ - def __init__(self, algorithm, seed=None): + def __init__(self, algorithm: int, seed: Optional[Hashable] = None): """Create a new instance using this algorithm and seed.""" if seed is None: seed = random.getrandbits(32) @@ -42,13 +43,13 @@ def __init__(self, algorithm, seed=None): ) @classmethod - def _new_from_cdata(cls, cdata): + def _new_from_cdata(cls, cdata: Any) -> "Random": """Return a new instance encapsulating this cdata.""" - self = object.__new__(cls) + self = object.__new__(cls) # type: "Random" self.random_c = cdata return self - def randint(self, low, high): + def randint(self, low: int, high: int) -> int: """Return a random integer within the linear range: low <= n <= high. Args: @@ -58,21 +59,21 @@ def randint(self, low, high): Returns: int: A random integer. """ - return lib.TCOD_random_get_i(self.random_c, low, high) + return int(lib.TCOD_random_get_i(self.random_c, low, high)) - def uniform(self, low, high): + def uniform(self, low: float, high: float) -> float: """Return a random floating number in the range: low <= n <= high. Args: - low (int): The lower bound of the random range. - high (int): The upper bound of the random range. + low (float): The lower bound of the random range. + high (float): The upper bound of the random range. Returns: float: A random float. """ - return lib.TCOD_random_get_double(self.random_c, low, high) + return float(lib.TCOD_random_get_double(self.random_c, low, high)) - def guass(self, mu, sigma): + def guass(self, mu: float, sigma: float) -> float: """Return a random number using Gaussian distribution. Args: @@ -82,9 +83,11 @@ def guass(self, mu, sigma): Returns: float: A random float. """ - return lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma) + return float( + lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma) + ) - def inverse_guass(self, mu, sigma): + def inverse_guass(self, mu: float, sigma: float) -> float: """Return a random Gaussian number using the Box-Muller transform. Args: @@ -94,11 +97,11 @@ def inverse_guass(self, mu, sigma): Returns: float: A random float. """ - return lib.TCOD_random_get_gaussian_double_inv( - self.random_c, mu, sigma + return float( + lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma) ) - def __getstate__(self): + def __getstate__(self) -> Any: """Pack the self.random_c attribute into a portable state.""" state = self.__dict__.copy() state["random_c"] = { @@ -112,7 +115,7 @@ def __getstate__(self): } return state - def __setstate__(self, state): + def __setstate__(self, state: Any) -> None: """Create a new cdata object with the stored paramaters.""" try: cdata = state["random_c"] diff --git a/tcod/tcod.py b/tcod/tcod.py index caf5290a..3102b1ab 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -1,6 +1,6 @@ """This module focuses on improvements to the Python libtcod API. """ -from typing import Any, AnyStr +from typing import Any, AnyStr, Callable import warnings from tcod.libtcod import ffi @@ -9,7 +9,7 @@ def _unpack_char_p(char_p: Any) -> str: if char_p == ffi.NULL: return "" - return ffi.string(char_p).decode() + return ffi.string(char_p).decode() # type: ignore def _int(int_or_str: Any) -> int: @@ -66,10 +66,11 @@ class _PropagateException: # give propagate as onerror parameter for ffi.def_extern """ - def __init__(self): - self.exc_info = None # (exception, exc_value, traceback) + def __init__(self) -> None: + # (exception, exc_value, traceback) + self.exc_info = None # type: Any - def propagate(self, *exc_info): + def propagate(self, *exc_info: Any) -> None: """ set an exception to be raised once this context exits if multiple errors are caught, only keep the first exception raised @@ -77,13 +78,13 @@ def propagate(self, *exc_info): if not self.exc_info: self.exc_info = exc_info - def __enter__(self): + def __enter__(self) -> Callable[[Any], None]: """ once in context, only the propagate call is needed to use this class effectively """ return self.propagate - def __exit__(self, type, value, traceback): + def __exit__(self, type: Any, value: Any, traceback: Any) -> None: """ if we're holding on to an exception, raise it now prefers our held exception over any current raising error @@ -101,34 +102,34 @@ def __exit__(self, type, value, traceback): class _CDataWrapper(object): - def __init__(self, *args, **kargs): + def __init__(self, *args: Any, **kargs: Any): self.cdata = self._get_cdata_from_args(*args, **kargs) if self.cdata is None: self.cdata = ffi.NULL super(_CDataWrapper, self).__init__() @staticmethod - def _get_cdata_from_args(*args, **kargs): + def _get_cdata_from_args(*args: Any, **kargs: Any) -> Any: if len(args) == 1 and isinstance(args[0], ffi.CData) and not kargs: return args[0] else: return None - def __hash__(self): + def __hash__(self) -> int: return hash(self.cdata) - def __eq__(self, other): + def __eq__(self, other: Any) -> Any: try: return self.cdata == other.cdata except AttributeError: return NotImplemented - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: if "cdata" in self.__dict__: return getattr(self.__dict__["cdata"], attr) raise AttributeError(attr) - def __setattr__(self, attr, value): + def __setattr__(self, attr: str, value: Any) -> None: if hasattr(self, "cdata") and hasattr(self.cdata, attr): setattr(self.cdata, attr, value) else: diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index dd020d68..2d5dd87a 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -502,7 +502,7 @@ def test_noise(): libtcodpy.noise_get_turbulence(noise, [0], 4) libtcodpy.noise_delete(noise) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") +@pytest.mark.filterwarnings("ignore:.*") def test_random(): rand = libtcodpy.random_get_instance() rand = libtcodpy.random_new() diff --git a/tests/test_parser.py b/tests/test_parser.py index c5a9a115..cc7c9934 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -6,7 +6,7 @@ import tcod as libtcod -@pytest.mark.filterwarnings("ignore:Using this class is not recommended.") +@pytest.mark.filterwarnings("ignore:.*") def test_parser(): print ('***** File Parser test *****') parser=libtcod.parser_new() From d4e70a8973dbc5cfa5495bbfdf40858f90336206 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 20:56:57 -0800 Subject: [PATCH 0061/1101] Transpose tcod sample arrays to order="F". --- examples/samples_tcod.py | 72 +++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index c7127481..b45c1cd4 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -22,12 +22,10 @@ SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 FONT = "data/fonts/consolas10x10_gs_tc.png" -tcod.console_set_custom_font( - FONT, tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD -) -root_console = tcod.console_init_root(80, 50, "tcod python samples", False) + +root_console = None sample_console = tcod.console.Console( - SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT + SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F" ) @@ -93,7 +91,7 @@ def __init__(self): self.corners = np.array([0, 1, 2, 3]) # sample screen mesh-grid self.mgrid = np.mgrid[ - 0 : 1 : SAMPLE_SCREEN_HEIGHT * 1j, 0 : 1 : SAMPLE_SCREEN_WIDTH * 1j + 0 : 1 : SAMPLE_SCREEN_WIDTH * 1j, 0 : 1 : SAMPLE_SCREEN_HEIGHT * 1j ] def on_enter(self): @@ -420,8 +418,8 @@ def on_draw(self): self.img.blit_2x(sample_console, 0, 0) sample_console.default_bg = tcod.grey sample_console.rect(2, 2, rectw, recth, False, tcod.BKGND_MULTIPLY) - sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] = ( - sample_console.fg[2 : 2 + recth, 2 : 2 + rectw] + sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = ( + sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * sample_console.default_bg / 255 ) @@ -509,7 +507,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): "##############################################", ] -SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP]) +SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP]).transpose() FOV_ALGO_NAMES = [ "BASIC ", @@ -547,7 +545,9 @@ def __init__(self): # 1d noise for the torch flickering self.noise = tcod.noise_new(1, 1.0, 1.0) - self.map = tcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + self.map = tcod.map.Map( + SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F" + ) self.map.walkable[:] = SAMPLE_MAP[:] == " " self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == "=") @@ -653,7 +653,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): FOV_SELECT_KEYS = {ord("-"): -1, ord("="): 1} if event.sym in MOVE_KEYS: x, y = MOVE_KEYS[event.sym] - if SAMPLE_MAP[self.py + y][self.px + x] == " ": + if SAMPLE_MAP[self.px + x, self.py + y] == " ": tcod.console_put_char( sample_console, self.px, self.py, " ", tcod.BKGND_NONE ) @@ -705,10 +705,10 @@ def __init__(self): self.map = tcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == " ": + if SAMPLE_MAP[x, y] == " ": # ground tcod.map_set_properties(self.map, x, y, True, True) - elif SAMPLE_MAP[y][x] == "=": + elif SAMPLE_MAP[x, y] == "=": # window tcod.map_set_properties(self.map, x, y, True, False) self.path = tcod.path_new_using_map(self.map) @@ -738,7 +738,7 @@ def on_enter(self): # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == "=": + if SAMPLE_MAP[x, y] == "=": tcod.console_put_char( sample_console, x, y, tcod.CHAR_DHLINE, tcod.BKGND_NONE ) @@ -767,7 +767,7 @@ def on_draw(self): # draw the dungeon for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] == "#": + if SAMPLE_MAP[x, y] == "#": tcod.console_set_char_background( sample_console, x, y, DARK_WALL, tcod.BKGND_SET ) @@ -785,7 +785,7 @@ def on_draw(self): else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[y][x] != "#": + if SAMPLE_MAP[x, y] != "#": tcod.console_set_char_background( sample_console, x, @@ -836,13 +836,11 @@ def ev_keydown(self, event: tcod.event.KeyDown): sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dy -= 1 - self.oldchar = tcod.console_get_char( - sample_console, self.dx, self.dy - ) + self.oldchar = sample_console.ch[self.dx, self.dy] tcod.console_put_char( sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - if SAMPLE_MAP[self.dy][self.dx] == " ": + if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("k") and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south @@ -850,13 +848,11 @@ def ev_keydown(self, event: tcod.event.KeyDown): sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dy += 1 - self.oldchar = tcod.console_get_char( - sample_console, self.dx, self.dy - ) + self.oldchar = sample_console.ch[self.dx, self.dy] tcod.console_put_char( sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - if SAMPLE_MAP[self.dy][self.dx] == " ": + if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("j") and self.dx > 0: # destination move west @@ -864,13 +860,11 @@ def ev_keydown(self, event: tcod.event.KeyDown): sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dx -= 1 - self.oldchar = tcod.console_get_char( - sample_console, self.dx, self.dy - ) + self.oldchar = sample_console.ch[self.dx, self.dy] tcod.console_put_char( sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - if SAMPLE_MAP[self.dy][self.dx] == " ": + if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("l") and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east @@ -878,13 +872,11 @@ def ev_keydown(self, event: tcod.event.KeyDown): sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE ) self.dx += 1 - self.oldchar = tcod.console_get_char( - sample_console, self.dx, self.dy - ) + self.oldchar = sample_console.ch[self.dx, self.dy] tcod.console_put_char( sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - if SAMPLE_MAP[self.dy][self.dx] == " ": + if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.K_TAB: self.using_astar = not self.using_astar @@ -909,13 +901,11 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion): ) self.dx = mx self.dy = my - self.oldchar = tcod.console_get_char( - sample_console, self.dx, self.dy - ) + self.oldchar = sample_console.ch[self.dx, self.dy] tcod.console_put_char( sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE ) - if SAMPLE_MAP[self.dy][self.dx] == " ": + if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True @@ -1511,7 +1501,7 @@ def on_draw(self): B = B.clip(0, 255) # fill the screen with these background colors - sample_console.bg.transpose()[:] = [R.T, G.T, B.T] + sample_console.bg.transpose(2, 1, 0)[...] = (R, G, B) ############################################# @@ -1552,7 +1542,13 @@ def on_draw(self): def main(): - global cur_sample + global cur_sample, root_console + tcod.console_set_custom_font( + FONT, tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD + ) + root_console = tcod.console_init_root( + 80, 50, "tcod python samples", False, order="F" + ) credits_end = False SAMPLES[cur_sample].on_enter() draw_samples_menu() From c0d381b745cc054f0ba1468394a04eb8a7319bf1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Feb 2019 23:13:15 -0800 Subject: [PATCH 0062/1101] More refactoring on tcod samples. Convert loops to NumPy and fix errors. --- examples/samples_tcod.py | 166 ++++++++++++++------------------------- 1 file changed, 58 insertions(+), 108 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index b45c1cd4..ae6d4ce1 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -2,7 +2,7 @@ """ This code demonstrates various usages of python-tcod. """ -# To the extent possible under law, the tcod.maintainers have waived all +# To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ @@ -72,9 +72,6 @@ def ev_quit(self, event: tcod.event.Quit): raise SystemExit() -############################################# -# true color sample -############################################# class TrueColorSample(Sample): def __init__(self): self.name = "True colors" @@ -89,8 +86,8 @@ def __init__(self): ) # corner indexes self.corners = np.array([0, 1, 2, 3]) - # sample screen mesh-grid - self.mgrid = np.mgrid[ + # sample screen interpolation mesh-grid + self.interp_x, self.interp_y = np.mgrid[ 0 : 1 : SAMPLE_SCREEN_WIDTH * 1j, 0 : 1 : SAMPLE_SCREEN_HEIGHT * 1j ] @@ -121,13 +118,13 @@ def slide_corner_colors(self): def interpolate_corner_colors(self): # interpolate corner colors across the sample console for i in range(3): # for each color channel - left = (self.colors[2, i] - self.colors[0, i]) * self.mgrid[ - 0 - ] + self.colors[0, i] - right = (self.colors[3, i] - self.colors[1, i]) * self.mgrid[ - 0 - ] + self.colors[1, i] - sample_console.bg[:, :, i] = (right - left) * self.mgrid[1] + left + left = ( + self.colors[2, i] - self.colors[0, i] + ) * self.interp_y + self.colors[0, i] + right = ( + self.colors[3, i] - self.colors[1, i] + ) * self.interp_y + self.colors[1, i] + sample_console.bg[:, :, i] = (right - left) * self.interp_x + left def darken_background_characters(self): # darken background characters @@ -158,11 +155,6 @@ def print_banner(self): ) -############################################# -# offscreen console sample -############################################# - - class OffscreenConsoleSample(Sample): def __init__(self): self.name = "Offscreen console" @@ -232,11 +224,6 @@ def on_draw(self): ) -############################################# -# line drawing sample -############################################# - - class LineDrawingSample(Sample): FLAG_NAMES = [ @@ -260,19 +247,16 @@ def __init__(self): self.mk_flag = tcod.BKGND_SET self.bk_flag = tcod.BKGND_SET - self.bk = tcod.console_new(sample_console.width, sample_console.height) + self.bk = tcod.console.Console( + sample_console.width, sample_console.height, order="F" + ) # initialize the colored background - for x in range(sample_console.width): - for y in range(sample_console.height): - col = tcod.Color( - x * 255 // (sample_console.width - 1), - (x + y) - * 255 - // (sample_console.width - 1 + sample_console.height - 1), - y * 255 // (sample_console.height - 1), - ) - self.bk.bg[y, x] = col - self.bk.ch[:] = ord(" ") + self.bk.bg[:, :, 0].T[:] = np.linspace(0, 255, self.bk.width) + self.bk.bg[:, :, 2] = np.linspace(0, 255, self.bk.height) + self.bk.bg[:, :, 1] = ( + self.bk.bg[:, :, 0].astype(int) + self.bk.bg[:, :, 2] + ) / 2 + self.bk.ch[:] = ord(" ") def ev_keydown(self, event: tcod.event.KeyDown): if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): @@ -290,11 +274,11 @@ def on_draw(self): alpha = 0.0 if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: # for the alpha mode, update alpha every frame - alpha = (1.0 + math.cos(tcod.sys_elapsed_seconds() * 2)) / 2.0 + alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 self.bk_flag = tcod.BKGND_ALPHA(alpha) elif (self.bk_flag & 0xFF) == tcod.BKGND_ADDA: # for the add alpha mode, update alpha every frame - alpha = (1.0 + math.cos(tcod.sys_elapsed_seconds() * 2)) / 2.0 + alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 self.bk_flag = tcod.BKGND_ADDALPHA(alpha) self.bk.blit(sample_console) @@ -302,11 +286,7 @@ def on_draw(self): (sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0) ) for x in range(sample_console.width): - col = tcod.Color( - x * 255 // sample_console.width, - x * 255 // sample_console.width, - x * 255 // sample_console.width, - ) + col = [x * 255 // sample_console.width] * 3 tcod.console_set_char_background( sample_console, x, recty, col, self.bk_flag ) @@ -342,24 +322,19 @@ def on_draw(self): ) -############################################# -# noise sample -############################################# - -NOISE_OPTIONS = [ # [name, algorithm, implementation], - ["perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE], - ["simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE], - ["wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE], - ["perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM], - ["perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE], - ["simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM], - ["simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE], - ["wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM], - ["wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE], -] - - class NoiseSample(Sample): + NOISE_OPTIONS = [ # [name, algorithm, implementation], + ["perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE], + ["simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE], + ["wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE], + ["perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM], + ["perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE], + ["simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM], + ["simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE], + ["wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM], + ["wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE], + ] + def __init__(self): self.name = "Noise" self.func = 0 @@ -376,11 +351,11 @@ def __init__(self): @property def algorithm(self): - return NOISE_OPTIONS[self.func][1] + return self.NOISE_OPTIONS[self.func][1] @property def implementation(self): - return NOISE_OPTIONS[self.func][2] + return self.NOISE_OPTIONS[self.func][2] def get_noise(self): return tcod.noise.Noise( @@ -424,8 +399,8 @@ def on_draw(self): / 255 ) - for curfunc in range(len(NOISE_OPTIONS)): - text = "%i : %s" % (curfunc + 1, NOISE_OPTIONS[curfunc][0]) + for curfunc in range(len(self.NOISE_OPTIONS)): + text = "%i : %s" % (curfunc + 1, self.NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: sample_console.default_fg = tcod.white sample_console.default_bg = tcod.light_blue @@ -479,10 +454,10 @@ def ev_keydown(self, event: tcod.event.KeyDown): ############################################# # field of view sample ############################################# -DARK_WALL = tcod.Color(0, 0, 100) -LIGHT_WALL = tcod.Color(130, 110, 50) -DARK_GROUND = tcod.Color(50, 50, 150) -LIGHT_GROUND = tcod.Color(200, 180, 50) +DARK_WALL = (0, 0, 100) +LIGHT_WALL = (130, 110, 50) +DARK_GROUND = (50, 50, 150) +LIGHT_GROUND = (200, 180, 50) SAMPLE_MAP = [ "##############################################", @@ -619,26 +594,22 @@ def on_draw(self): self.noise, [self.torchx], tcod.NOISE_SIMPLEX ) # where_fov = np.where(self.map.fov[:]) - mgrid = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH] + mgrid = np.mgrid[:SAMPLE_SCREEN_WIDTH, :SAMPLE_SCREEN_HEIGHT] # get squared distance - light = (mgrid[0] - self.py + dy) ** 2 + ( - mgrid[1] - self.px + dx + light = (mgrid[0] - self.px + dx) ** 2 + ( + mgrid[1] - self.py + dy ) ** 2 light = light.astype(np.float16) - where_visible = np.where( - (light < SQUARED_TORCH_RADIUS) & self.map.fov[:] - ) - light[where_visible] = SQUARED_TORCH_RADIUS - light[where_visible] - light[where_visible] /= SQUARED_TORCH_RADIUS - light[where_visible] += di - light[where_visible] = light[where_visible].clip(0, 1) - - for yx in zip(*where_visible): - sample_console.bg[yx] = tcod.color_lerp( - tuple(self.dark_map_bg[yx]), - tuple(self.light_map_bg[yx]), - light[yx], - ) + visible = (light < SQUARED_TORCH_RADIUS) & self.map.fov[:] + light[...] = SQUARED_TORCH_RADIUS - light + light[...] /= SQUARED_TORCH_RADIUS + light[...] += di + light[...] = light.clip(0, 1) + light[~visible] = 0 + + sample_console.bg[...] = ( + self.light_map_bg.astype(np.float16) - self.dark_map_bg + ) * light[..., np.newaxis] + self.dark_map_bg else: where_fov = np.where(self.map.fov[:]) sample_console.bg[where_fov] = self.light_map_bg[where_fov] @@ -680,11 +651,6 @@ def ev_keydown(self, event: tcod.event.KeyDown): super().ev_keydown(event) -############################################# -# pathfinding sample -############################################# - - class PathfindingSample(Sample): def __init__(self): self.name = "Path finding" @@ -1120,11 +1086,6 @@ def ev_keydown(self, event: tcod.event.KeyDown): super().ev_keydown(event) -############################################# -# image sample -############################################# - - class ImageSample(Sample): def __init__(self): self.name = "Image toolkit" @@ -1179,12 +1140,6 @@ def on_draw(self): ) -############################################# -# mouse sample -############################################# -butstatus = ("OFF", "ON") - - class MouseSample(Sample): def __init__(self): self.name = "Mouse support" @@ -1237,9 +1192,9 @@ def on_draw(self): self.motion.tile.y, self.motion.tile_motion.x, self.motion.tile_motion.y, - butstatus[self.lbut], - butstatus[self.rbut], - butstatus[self.mbut], + ("OFF", "ON")[self.lbut], + ("OFF", "ON")[self.rbut], + ("OFF", "ON")[self.mbut], ), ) sample_console.print_(1, 10, "1 : Hide cursor\n2 : Show cursor") @@ -1253,11 +1208,6 @@ def ev_keydown(self, event: tcod.event.KeyDown): super().ev_keydown(event) -############################################# -# name generator sample -############################################# - - class NameGeneratorSample(Sample): def __init__(self): self.name = "Name generator" From d27ce2d98e976a1a17ecf262082076341e727d9f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 Feb 2019 15:08:09 -0800 Subject: [PATCH 0063/1101] Add an experimental path finding module. --- build_libtcod.py | 9 ++++++++- tcod/path.cpp | 2 ++ tcod/path.h | 35 +++++++++++++++++++++++++++++++++++ tcod/path2.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tcod/path2.py diff --git a/build_libtcod.py b/build_libtcod.py index a5310f66..e157e3eb 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -4,6 +4,7 @@ import sys import glob +import re from typing import List, Tuple, Any, Dict, Iterator from cffi import FFI @@ -243,7 +244,13 @@ def visit_FuncDef(self, node): def get_cdef(): generator = c_generator.CGenerator() - return generator.visit(get_ast()) + cdef = generator.visit(get_ast()) + cdef = re.sub( + pattern=r"typedef int (ptrdiff_t);", + repl=r"typedef int... \1;", + string=cdef, + ) + return cdef def get_ast(): diff --git a/tcod/path.cpp b/tcod/path.cpp index 185b9c78..78e21a24 100644 --- a/tcod/path.cpp +++ b/tcod/path.cpp @@ -1,5 +1,7 @@ #include "path.h" +#include "../libtcod/src/libtcod/pathfinding/generic.h" + #include static char* PickArrayValue(const struct PathCostArray *map, int i, int j) { diff --git a/tcod/path.h b/tcod/path.h index 487245dd..626c2969 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -1,9 +1,44 @@ #ifndef PYTHON_TCOD_PATH_H_ #define PYTHON_TCOD_PATH_H_ +#include + #ifdef __cplusplus extern "C" { #endif +/** + * Common NumPy data types. + */ +enum NP_Type { + np_undefined = 0, + np_int8, + np_int16, + np_int32, + np_int64, + np_uint8, + np_uint16, + np_uint32, + np_uint64, + np_float16, + np_float32, + np_float64, +}; +/** + * A simple 4D NumPy array ctype. + */ +struct NArray4 { + enum NP_Type type; + char *data; + ptrdiff_t shape[4]; + ptrdiff_t strides[4]; +}; + +struct EdgeRule { + int vector[4]; + int cost; + struct NArray4 dest_cost; + struct NArray4 staging; +}; struct PathCostArray { char *array; diff --git a/tcod/path2.py b/tcod/path2.py new file mode 100644 index 00000000..033d5e82 --- /dev/null +++ b/tcod/path2.py @@ -0,0 +1,30 @@ +from typing import Iterator, Sequence + +import numpy as np + + +def get_2d_edges(cardinal: float, diagonal: float) -> np.array: + return ( + np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) * cardinal + + np.array([[1, 0, 1], [0, 0, 0], [1, 0, 1]]) * diagonal + ) + + +def get_hex_edges(cost: float) -> np.array: + return np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) * cost + + +class EdgeRule: + def __init__(self, vector: Sequence[int], destination: np.array): + self.vector = vector + self.destination = destination + + +def new_rule(edges: np.array) -> Iterator[EdgeRule]: + i_center = (edges.shape[0] - 1) // 2 + j_center = (edges.shape[1] - 1) // 2 + for i in range(edges.shape[0]): + for j in range(edges.shape[1]): + if edges[i, j] == 0: + continue + yield EdgeRule((i - i_center, j - j_center), edges[i, j]) From 842bff204cc08e737902b14e6fe23e1238c961ec Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 Feb 2019 15:10:28 -0800 Subject: [PATCH 0064/1101] Deprecate several outdated libtcodpy functions. --- tcod/libtcodpy.py | 30 +++++++++++++++++++++++++++++- tcod/noise.py | 6 ++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a3fdb893..463f6a95 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -524,6 +524,7 @@ def mouse_p(self) -> Any: return self.cdata +@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.") def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: """Create a new BSP instance with the given rectangle. @@ -542,6 +543,7 @@ def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: return Bsp(x, y, w, h) +@deprecate("Call node.split_once instead.") def bsp_split_once( node: tcod.bsp.BSP, horizontal: bool, position: int ) -> None: @@ -552,6 +554,7 @@ def bsp_split_once( node.split_once(horizontal, position) +@deprecate("Call node.split_recursive instead.") def bsp_split_recursive( node: tcod.bsp.BSP, randomizer: Optional[tcod.random.Random], @@ -1566,7 +1569,7 @@ def console_delete(con: tcod.console.Console) -> None: lib.TCOD_console_delete(con) -# fast color filling +@deprecate("Assign to the console.fg array instead.") def console_fill_foreground( con: tcod.console.Console, r: Sequence[int], @@ -1580,6 +1583,9 @@ def console_fill_foreground( r (Sequence[int]): An array of integers with a length of width*height. g (Sequence[int]): An array of integers with a length of width*height. b (Sequence[int]): An array of integers with a length of width*height. + + .. deprecated:: 8.4 + You should assign to :any:`tcod.console.Console.fg` instead. """ if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") @@ -1604,6 +1610,7 @@ def console_fill_foreground( lib.TCOD_console_fill_foreground(_console(con), cr, cg, cb) +@deprecate("Assign to the console.bg array instead.") def console_fill_background( con: tcod.console.Console, r: Sequence[int], @@ -1617,6 +1624,9 @@ def console_fill_background( r (Sequence[int]): An array of integers with a length of width*height. g (Sequence[int]): An array of integers with a length of width*height. b (Sequence[int]): An array of integers with a length of width*height. + + .. deprecated:: 8.4 + You should assign to :any:`tcod.console.Console.bg` instead. """ if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") @@ -1641,11 +1651,15 @@ def console_fill_background( lib.TCOD_console_fill_background(_console(con), cr, cg, cb) +@deprecate("Assign to the console.ch array instead.") def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: """Fill the character tiles of a console with an array. `arr` is an array of integers with a length of the consoles width and height. + + .. deprecated:: 8.4 + You should assign to :any:`tcod.console.Console.ch` instead. """ if isinstance(arr, np.ndarray): # numpy arrays, use numpy's ctypes functions @@ -2461,6 +2475,7 @@ def heightmap_dig_bezier( ) +@deprecate("This object can be accessed as a NumPy array.") def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: """Return the value at ``x``, ``y`` in a heightmap. @@ -2750,6 +2765,7 @@ def image_delete(image: tcod.image.Image) -> None: """ +@deprecate("Use tcod.line_iter instead.") def line_init(xo: int, yo: int, xd: int, yd: int) -> None: """Initilize a line whose points will be returned by `line_step`. @@ -2769,6 +2785,7 @@ def line_init(xo: int, yo: int, xd: int, yd: int) -> None: lib.TCOD_line_init(xo, yo, xd, yd) +@deprecate("Use tcod.line_iter instead.") def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: """After calling line_init returns (x, y) points of the line. @@ -2790,6 +2807,7 @@ def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: return None, None +@deprecate("Use tcod.line_iter instead.") def line( xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool] ) -> bool: @@ -2878,6 +2896,7 @@ def line_where( return tuple(array) # type: ignore +@deprecate("Call tcod.map.Map(width, height) instead.") def map_new(w: int, h: int) -> tcod.map.Map: """Return a :any:`tcod.map.Map` with a width and height. @@ -2888,6 +2907,7 @@ def map_new(w: int, h: int) -> tcod.map.Map: return tcod.map.Map(w, h) +@deprecate("Use Python's standard copy module instead.") def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: """Copy map data from `source` to `dest`. @@ -2902,6 +2922,7 @@ def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore +@deprecate("Set properties using the m.transparent and m.walkable arrays.") def map_set_properties( m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool ) -> None: @@ -2916,6 +2937,7 @@ def map_set_properties( lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk) +@deprecate("Clear maps using NumPy broadcast rules instead.") def map_clear( m: tcod.map.Map, transparent: bool = False, walkable: bool = False ) -> None: @@ -2929,6 +2951,7 @@ def map_clear( m.walkable[:] = walkable +@deprecate("Call the map.compute_fov method instead.") def map_compute_fov( m: tcod.map.Map, x: int, @@ -2945,6 +2968,7 @@ def map_compute_fov( m.compute_fov(x, y, radius, light_walls, algo) +@deprecate("Use map.fov to check for this property.") def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: """Return True if the cell at x,y is lit by the last field-of-view algorithm. @@ -2957,6 +2981,7 @@ def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_in_fov(m.map_c, x, y)) +@deprecate("Use map.transparent to check for this property.") def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: """ .. note:: @@ -2967,6 +2992,7 @@ def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_transparent(m.map_c, x, y)) +@deprecate("Use map.walkable to check for this property.") def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: """ .. note:: @@ -2985,6 +3011,7 @@ def map_delete(m: tcod.map.Map) -> None: """ +@deprecate("Check the map.width attribute instead.") def map_get_width(map: tcod.map.Map) -> int: """Return the width of a map. @@ -2994,6 +3021,7 @@ def map_get_width(map: tcod.map.Map) -> int: return map.width +@deprecate("Check the map.height attribute instead.") def map_get_height(map: tcod.map.Map) -> int: """Return the height of a map. diff --git a/tcod/noise.py b/tcod/noise.py index ac687638..2d86a94a 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -37,6 +37,7 @@ import numpy as np +from tcod._internal import deprecate from tcod.libtcod import ffi, lib import tcod.constants import tcod.random @@ -106,8 +107,9 @@ def __init__( def dimensions(self) -> int: return int(self._tdl_noise_c.dimensions) - @property - def dimentions(self) -> int: # deprecated + @property # type: ignore + @deprecate("This is a misspelling of 'dimensions'.") + def dimentions(self) -> int: return self.dimensions @property From 42ef70343506443b511cc643615c83a4366fb8a5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 Feb 2019 15:13:18 -0800 Subject: [PATCH 0065/1101] Replace transpose with newaxis in samples. --- examples/samples_tcod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index ae6d4ce1..07bb6146 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -251,7 +251,7 @@ def __init__(self): sample_console.width, sample_console.height, order="F" ) # initialize the colored background - self.bk.bg[:, :, 0].T[:] = np.linspace(0, 255, self.bk.width) + self.bk.bg[:, :, 0] = np.linspace(0, 255, self.bk.width)[:, np.newaxis] self.bk.bg[:, :, 2] = np.linspace(0, 255, self.bk.height) self.bk.bg[:, :, 1] = ( self.bk.bg[:, :, 0].astype(int) + self.bk.bg[:, :, 2] From 823937d978dcc7433e2920c867014b842265be0d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 5 Feb 2019 03:10:08 -0800 Subject: [PATCH 0066/1101] Update hello world tutorial. --- tcod/console.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 73e875ec..5c3725cb 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -6,21 +6,23 @@ Example:: # Make sure 'arial10x10.png' is in the same directory as this script. - import time - import tcod + import tcod.event # Setup the font. tcod.console_set_custom_font( - 'arial10x10.png', - tcod.FONT_LAYOUT_TCOD, - ) + "arial10x10.png", + tcod.FONT_LAYOUT_TCOD | tcod.FONT_TYPE_GREYSCALE, + ) # Initialize the root console in a context. - with tcod.console_init_root(80, 60, 'title') as root_console: + with tcod.console_init_root(80, 60) as root_console: root_console.print_(x=0, y=0, string='Hello World!') - tcod.console_flush() # Show the console. - time.sleep(3) # Wait 3 seconds. - # The window is closed here, after the above context exits. + while True: + tcod.console_flush() # Show the console. + for event in tcod.event.wait(): + if event.type == "QUIT": + raise SystemExit() + # The libtcod window will be closed at the end of this with-block. """ from typing import Any, Optional, Tuple # noqa: F401 From c1bb3507beebe8f56a9ee0a6fc8fb454647df408 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 5 Feb 2019 03:12:17 -0800 Subject: [PATCH 0067/1101] Update libtcod. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b23f174b..4d61e6c8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,8 @@ Deprecated Fixed - `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return values. + - `Console.print_frame` was clearing tiles outside if it's bounds. + - The `FONT_LAYOUT_CP437` layout was incorrect. 8.4.1 - 2019-02-01 ------------------ diff --git a/libtcod b/libtcod index 72d0c4fa..95ba1bec 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 72d0c4fa2dc0a4490dec801dfb27b28ff1eb53e0 +Subproject commit 95ba1becac1d7078f99a7257405ab04145c96056 From 126dae241a85930c7483a077f00c4c54df4da37e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 5 Feb 2019 04:05:26 -0800 Subject: [PATCH 0068/1101] Prepare 8.4.2 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4d61e6c8..7e193c26 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,13 +9,16 @@ v2.0.0 Unreleased ------------------ + +8.4.2 - 2019-02-05 +------------------ Deprecated - The tdl module has been deprecated. - The libtcodpy parser functions have been deprecated. Fixed - `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return values. - - `Console.print_frame` was clearing tiles outside if it's bounds. + - `Console.print_frame` was clearing tiles outside if its bounds. - The `FONT_LAYOUT_CP437` layout was incorrect. 8.4.1 - 2019-02-01 From ebfb2567c07bdff7b742edb336b4e2c66c8434e0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 6 Feb 2019 02:47:09 -0800 Subject: [PATCH 0069/1101] Prepare 8.4.3 and update libtcod. --- CHANGELOG.rst | 6 ++++++ libtcod | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7e193c26..3f467083 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,12 @@ v2.0.0 Unreleased ------------------ +8.4.3 - 2019-02-06 +------------------ +Changed + - Updated libtcod to 1.10.5 + - The SDL2/OPENGL2 renderers will now auto-detect a custom fonts key-color. + 8.4.2 - 2019-02-05 ------------------ Deprecated diff --git a/libtcod b/libtcod index 95ba1bec..44a6216b 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 95ba1becac1d7078f99a7257405ab04145c96056 +Subproject commit 44a6216bcfec3503b680509fb1547c5ee1af24b3 From 5e57ac15f0c711044e115354c12a6d162fe34244 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Feb 2019 17:23:22 -0800 Subject: [PATCH 0070/1101] Remove unneeded Python 2 code. --- tcod/__init__.py | 9 --------- tcod/console.py | 2 -- 2 files changed, 11 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 9afe2f81..27d7776c 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,10 +15,6 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ -import sys - -import warnings - from tcod.libtcod import lib, ffi # noqa: F4 from tcod.libtcodpy import * # noqa: F4 @@ -26,8 +22,3 @@ from tcod.version import __version__ except ImportError: # Gets imported without version.py by ReadTheDocs __version__ = "" - -if sys.version_info[0] == 2: - warnings.warn( - "python-tcod has dropped support for Python 2.7.", DeprecationWarning - ) diff --git a/tcod/console.py b/tcod/console.py index 5c3725cb..f15f126d 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -546,8 +546,6 @@ def __bool__(self) -> bool: """ return bool(self.console_c != ffi.NULL) - __nonzero__ = __bool__ - def __getstate__(self) -> Any: state = self.__dict__.copy() del state["console_c"] From b275925f928721cf23560184d1085be1a766d305 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Feb 2019 20:53:28 -0800 Subject: [PATCH 0071/1101] Update Console constructor to include buffer and default values. Adds __str__ and __repr__ methods to Console. Update relevant tests. Resolves libtcod/libtcod#10 --- CHANGELOG.rst | 5 ++ tcod/console.py | 146 +++++++++++++++++++++++++++++++++++------- tests/test_console.py | 121 ++++++++++++++++++++++++++++++++++ tests/test_tcod.py | 76 ---------------------- 4 files changed, 248 insertions(+), 100 deletions(-) create mode 100644 tests/test_console.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3f467083..0d36fc6f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +Added + - `tcod.console.Console` now supports `str` and `repr`. +Changed + - You can now give default values or an array when initializing a + `tcod.console.Console` instance. 8.4.3 - 2019-02-06 ------------------ diff --git a/tcod/console.py b/tcod/console.py index f15f126d..21949a57 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -15,7 +15,7 @@ tcod.FONT_LAYOUT_TCOD | tcod.FONT_TYPE_GREYSCALE, ) # Initialize the root console in a context. - with tcod.console_init_root(80, 60) as root_console: + with tcod.console_init_root(80, 60, order="F") as root_console: root_console.print_(x=0, y=0, string='Hello World!') while True: tcod.console_flush() # Show the console. @@ -47,30 +47,95 @@ class Console: """A console object containing a grid of characters with foreground/background colors. + `width` and `height` are the size of the console (in tiles.) + + `order` determines how the axes of NumPy array attributes are arraigned. + `order="F"` will swap the first two axes which allows for more intuitive + `[x, y]` indexing. + + With `buffer` the console can be initialized from another array. The + `buffer` should be compatible with the `width`, `height`, and `order` + given; and should also have a dtype compatible with :any:`Console.DTYPE`. + + `copy` is a placeholder. In the future this will determine if the + `buffer` is copied or used as-is. So set it to None or True as + appropriate. + + `default_bg`, `default_bg`, `default_bg_blend`, and `default_alignment` are + the default values used in some methods. The `default_bg` and `default_bg` + will affect the starting foreground and background color of the console. + .. versionchanged:: 4.3 Added `order` parameter. - Args: - width (int): Width of the new Console. - height (int): Height of the new Console. - order (str): Which numpy memory order to use. + .. versionchanged:: 8.5 + Added `buffer`, `copy`, and default parameters. + Arrays are initialized as if the :any:`clear` method was called. Attributes: - console_c (CData): A cffi pointer to a TCOD_console_t object. + console_c: A python-cffi "TCOD_Console*" object. + DTYPE: + A class attribute which provides a dtype compatible with this + class. + + ``[("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")]`` + + Example:: + + >>> buffer = np.zeros( + ... shape=(20, 3), + ... dtype=tcod.console.Console.DTYPE, + ... order="F", + ... ) + >>> buffer["ch"] = ord(' ') + >>> buffer["ch"][:, 1] = ord('x') + >>> c = tcod.console.Console(20, 3, order="F", buffer=buffer) + >>> print(c) + < | + |xxxxxxxxxxxxxxxxxxxx| + | > + + .. versionadded:: 8.5 """ - def __init__(self, width: int, height: int, order: str = "C"): + DTYPE = [("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")] # type: Any + + def __init__( + self, + width: int, + height: int, + order: str = "C", + buffer: Optional[np.array] = None, + copy: Optional[bool] = None, + default_bg: Tuple[int, int, int] = (0, 0, 0), + default_fg: Tuple[int, int, int] = (255, 255, 255), + default_bg_blend: Optional[int] = None, + default_alignment: Optional[int] = None, + ): self._key_color = None # type: Optional[Tuple[int, int, int]] - self._ch = np.full((height, width), 0x20, dtype=np.intc) - self._fg = np.zeros((height, width), dtype="(3,)u1") - self._bg = np.zeros((height, width), dtype="(3,)u1") self._order = tcod._internal.verify_order(order) + if copy is not None and not copy: + raise ValueError("copy=False is not supported in this version.") + if buffer is not None: + if self._order == "F": + buffer = buffer.transpose() + self._ch = np.ascontiguousarray(buffer["ch"], np.intc) + self._fg = np.ascontiguousarray(buffer["fg"], "u1") + self._bg = np.ascontiguousarray(buffer["bg"], "u1") + else: + self._ch = np.ndarray((height, width), dtype=np.intc) + self._fg = np.ndarray((height, width), dtype="(3,)u1") + self._bg = np.ndarray((height, width), dtype="(3,)u1") # libtcod uses the root console for defaults. - bkgnd_flag = alignment = 0 - if lib.TCOD_ctx.root != ffi.NULL: - bkgnd_flag = lib.TCOD_ctx.root.bkgnd_flag - alignment = lib.TCOD_ctx.root.alignment + if default_bg_blend is None: + default_bg_blend = 0 + if lib.TCOD_ctx.root != ffi.NULL: + default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag + if default_alignment is None: + default_alignment = 0 + if lib.TCOD_ctx.root != ffi.NULL: + default_alignment = lib.TCOD_ctx.root.alignment self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", @@ -80,15 +145,20 @@ def __init__(self, width: int, height: int, order: str = "C"): "ch_array": ffi.cast("int*", self._ch.ctypes.data), "fg_array": ffi.cast("TCOD_color_t*", self._fg.ctypes.data), "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), - "bkgnd_flag": bkgnd_flag, - "alignment": alignment, - "fore": (255, 255, 255), - "back": (0, 0, 0), + "bkgnd_flag": default_bg_blend, + "alignment": default_alignment, + "fore": default_fg, + "back": default_bg, }, ) + if buffer is None: + self.clear() + @classmethod def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": + """Return a Console instance which wraps this `TCOD_Console*` object. + """ if isinstance(cdata, cls): return cdata self = object.__new__(cls) # type: Console @@ -157,8 +227,8 @@ def bg(self) -> np.array: You can change the consoles background colors by using this array. - Index this array with ``console.bg[i, j, channel] # order='C'`` or - ``console.bg[x, y, channel] # order='F'``. + Index this array with ``console.bg[i, j, channel] # order='C'`` or + ``console.bg[x, y, channel] # order='F'``. """ return self._bg.transpose(1, 0, 2) if self._order == "F" else self._bg @@ -169,8 +239,8 @@ def fg(self) -> np.array: You can change the consoles foreground colors by using this array. - Index this array with ``console.fg[i, j, channel] # order='C'`` or - ``console.fg[x, y, channel] # order='F'``. + Index this array with ``console.fg[i, j, channel] # order='C'`` or + ``console.fg[x, y, channel] # order='F'``. """ return self._fg.transpose(1, 0, 2) if self._order == "F" else self._fg @@ -180,8 +250,8 @@ def ch(self) -> np.array: You can change the consoles character codes by using this array. - Index this array with ``console.ch[i, j] # order='C'`` or - ``console.ch[x, y] # order='F'``. + Index this array with ``console.ch[i, j] # order='C'`` or + ``console.ch[x, y] # order='F'``. """ return self._ch.T if self._order == "F" else self._ch @@ -576,3 +646,31 @@ def __setstate__(self, state: Any) -> None: self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", self._console_data ) + + def __repr__(self) -> str: + """Return a string representation of this console.""" + buffer = np.ndarray((self.height, self.width), dtype=self.DTYPE) + buffer["ch"] = self.ch + buffer["fg"] = self.fg + buffer["bg"] = self.bg + return ( + "tcod.console.Console(width=%i, height=%i, " + "order=%r,buffer=\n%r,\ndefault_bg=%r, default_fg=%r, " + "default_bg_blend=%s, default_alignment=%s)" + % ( + self.width, + self.height, + self._order, + buffer, + self.default_bg, + self.default_fg, + self.default_bg_blend, + self.default_alignment, + ) + ) + + def __str__(self) -> str: + """Return a simplified representation of this consoles contents.""" + return "<%s>" % "|\n|".join( + "".join(chr(c) for c in line) for line in self._ch + ) diff --git a/tests/test_console.py b/tests/test_console.py new file mode 100644 index 00000000..f719573c --- /dev/null +++ b/tests/test_console.py @@ -0,0 +1,121 @@ +import pickle + +import numpy as np +from numpy import array +import pytest + +import tcod + + +@pytest.mark.filterwarnings("ignore:Directly access a consoles") +def test_array_read_write(): + console = tcod.console.Console(width=12, height=10) + FG = (255, 254, 253) + BG = (1, 2, 3) + CH = ord('&') + tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) + assert console.ch[0, 0] == CH + assert tuple(console.fg[0, 0]) == FG + assert tuple(console.bg[0, 0]) == BG + + tcod.console_put_char_ex(console, 1, 2, CH, FG, BG) + assert console.ch[2, 1] == CH + assert tuple(console.fg[2, 1]) == FG + assert tuple(console.bg[2, 1]) == BG + + console.clear() + assert console.ch[1, 1] == ord(' ') + assert tuple(console.fg[1, 1]) == (255, 255, 255) + assert tuple(console.bg[1, 1]) == (0, 0, 0) + + ch_slice = console.ch[1, :] + ch_slice[2] = CH + console.fg[1, ::2] = FG + console.bg[...] = BG + + assert tcod.console_get_char(console, 2, 1) == CH + assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG + assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG + + +def test_console_defaults(): + console = tcod.console.Console( + width=12, + height=10, + default_bg=(3, 6, 9), + default_fg=(12, 15, 18), + default_bg_blend=tcod.BKGND_MULTIPLY, + default_alignment=tcod.LEFT, + ) + assert console.default_bg == (3, 6, 9) + assert console.default_fg == (12, 15, 18) + assert console.default_bg_blend == tcod.BKGND_MULTIPLY + assert console.default_alignment == tcod.LEFT + + console.default_bg = [2, 3, 4] + assert console.default_bg == (2, 3, 4) + + console.default_fg = (4, 5, 6) + assert console.default_fg == (4, 5, 6) + + console.default_bg_blend = tcod.BKGND_ADD + assert console.default_bg_blend == tcod.BKGND_ADD + + console.default_alignment = tcod.RIGHT + assert console.default_alignment == tcod.RIGHT + + +@pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") +def test_console_methods(): + console = tcod.console.Console(width=12, height=10) + console.put_char(0, 0, ord('@')) + console.print_(0, 0, 'Test') + console.print_rect(0, 0, 2, 8, 'a b c d e f') + console.get_height_rect(0, 0, 2, 8, 'a b c d e f') + console.rect(0, 0, 2, 2, True) + console.hline(0, 1, 10) + console.vline(1, 0, 10) + console.print_frame(0, 0, 8, 8, 'Frame') + console.blit(0, 0, 0, 0, console, 0, 0) + console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) + console.set_key_color((254, 0, 254)) + + +def test_console_pickle(): + console = tcod.console.Console(width=12, height=10) + console.ch[...] = ord('.') + console.fg[...] = (10, 20, 30) + console.bg[...] = (1, 2, 3) + console2 = pickle.loads(pickle.dumps(console)) + assert (console.ch == console2.ch).all() + assert (console.fg == console2.fg).all() + assert (console.bg == console2.bg).all() + + +def test_console_pickle_fortran(): + console = tcod.console.Console(2, 3, order='F') + console2 = pickle.loads(pickle.dumps(console)) + assert console.ch.strides == console2.ch.strides + assert console.fg.strides == console2.fg.strides + assert console.bg.strides == console2.bg.strides + + +def test_console_repr(): + array # Needed for eval. + eval(repr(tcod.console.Console(10, 2))) + + +def test_console_str(): + console = tcod.console.Console(10, 2) + console.print_(0, 0, "Test") + assert str(console) == ("") + + +def test_console_fortran_buffer(): + tcod.console.Console( + width=1, + height=2, + order="F", + buffer=np.zeros((1, 2), order="F", dtype=tcod.console.Console.DTYPE), + ) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index b1be18d6..378a1f48 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -53,82 +53,6 @@ def test_tcod_bsp(): str(bsp) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") -def test_array_read_write(console): - FG = (255, 254, 253) - BG = (1, 2, 3) - CH = ord('&') - tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) - assert console.ch[0, 0] == CH - assert tuple(console.fg[0, 0]) == FG - assert tuple(console.bg[0, 0]) == BG - - tcod.console_put_char_ex(console, 1, 2, CH, FG, BG) - assert console.ch[2, 1] == CH - assert tuple(console.fg[2, 1]) == FG - assert tuple(console.bg[2, 1]) == BG - - console.clear() - assert console.ch[1, 1] == ord(' ') - assert tuple(console.fg[1, 1]) == (255, 255, 255) - assert tuple(console.bg[1, 1]) == (0, 0, 0) - - ch_slice = console.ch[1, :] - ch_slice[2] = CH - console.fg[1, ::2] = FG - console.bg[...] = BG - - assert tcod.console_get_char(console, 2, 1) == CH - assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG - assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG - - -def test_console_defaults(console): - console.default_bg = [2, 3, 4] - assert console.default_bg == (2, 3, 4) - - console.default_fg = (4, 5, 6) - assert console.default_fg == (4, 5, 6) - - console.default_bg_blend = tcod.BKGND_ADD - assert console.default_bg_blend == tcod.BKGND_ADD - - console.default_alignment = tcod.RIGHT - assert console.default_alignment == tcod.RIGHT - -@pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") -def test_console_methods(console): - console.put_char(0, 0, ord('@')) - console.print_(0, 0, 'Test') - console.print_rect(0, 0, 2, 8, 'a b c d e f') - console.get_height_rect(0, 0, 2, 8, 'a b c d e f') - console.rect(0, 0, 2, 2, True) - console.hline(0, 1, 10) - console.vline(1, 0, 10) - console.print_frame(0, 0, 8, 8, 'Frame') - console.blit(0, 0, 0, 0, console, 0, 0) - console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) - console.set_key_color((254, 0, 254)) - - -def test_console_pickle(console): - console.ch[...] = ord('.') - console.fg[...] = (10, 20, 30) - console.bg[...] = (1, 2, 3) - console2 = pickle.loads(pickle.dumps(console)) - assert (console.ch == console2.ch).all() - assert (console.fg == console2.fg).all() - assert (console.bg == console2.bg).all() - - -def test_console_pickle_fortran(): - console = tcod.console.Console(2, 3, order='F') - console2 = pickle.loads(pickle.dumps(console)) - assert console.ch.strides == console2.ch.strides - assert console.fg.strides == console2.fg.strides - assert console.bg.strides == console2.bg.strides - - def test_tcod_map_set_bits(): map_ = tcod.map.Map(2,2) From e9d6e166d70fe4579be183a6dc4c284c8470975b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Feb 2019 22:57:00 -0800 Subject: [PATCH 0072/1101] Deprecate libtcodpy console functions. --- CHANGELOG.rst | 3 ++ tcod/libtcodpy.py | 131 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d36fc6f..affb3184 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,9 @@ Added Changed - You can now give default values or an array when initializing a `tcod.console.Console` instance. +Deprecated + - Most libtcodpy console functions have been replaced by the tcod.console + module. 8.4.3 - 2019-02-06 ------------------ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 463f6a95..f399c995 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -935,6 +935,7 @@ def console_set_custom_font( ) +@deprecate("Check `con.width` instead.") def console_get_width(con: tcod.console.Console) -> int: """Return the width of a console. @@ -950,6 +951,7 @@ def console_get_width(con: tcod.console.Console) -> int: return int(lib.TCOD_console_get_width(_console(con))) +@deprecate("Check `con.height` instead.") def console_get_height(con: tcod.console.Console) -> int: """Return the height of a console. @@ -1085,6 +1087,7 @@ def console_flush() -> None: # drawing on a console +@deprecate("Set the `con.default_bg` attribute instead.") def console_set_default_background( con: tcod.console.Console, col: Tuple[int, int, int] ) -> None: @@ -1094,10 +1097,14 @@ def console_set_default_background( con (Console): Any Console instance. col (Union[Tuple[int, int, int], Sequence[int]]): An (r, g, b) sequence or Color instance. + + .. deprecated:: 8.5 + Use :any:`Console.default_bg` instead. """ lib.TCOD_console_set_default_background(_console(con), col) +@deprecate("Set the `con.default_fg` attribute instead.") def console_set_default_foreground( con: tcod.console.Console, col: Tuple[int, int, int] ) -> None: @@ -1107,10 +1114,14 @@ def console_set_default_foreground( con (Console): Any Console instance. col (Union[Tuple[int, int, int], Sequence[int]]): An (r, g, b) sequence or Color instance. + + .. deprecated:: 8.5 + Use :any:`Console.default_fg` instead. """ lib.TCOD_console_set_default_foreground(_console(con), col) +@deprecate("Call the `con.clear()` method instead.") def console_clear(con: tcod.console.Console) -> None: """Reset a console to its default colors and the space character. @@ -1120,6 +1131,9 @@ def console_clear(con: tcod.console.Console) -> None: .. seealso:: :any:`console_set_default_background` :any:`console_set_default_foreground` + + .. deprecated:: 8.5 + Call the :any:`Console.clear` method instead. """ lib.TCOD_console_clear(_console(con)) @@ -1198,6 +1212,10 @@ def console_set_char_foreground( y (int): Character y position from the top. col (Union[Tuple[int, int, int], Sequence[int]]): An (r, g, b) sequence or Color instance. + + .. deprecated:: 8.4 + Array access performs significantly faster than using this function. + See :any:`Console.fg`. """ lib.TCOD_console_set_char_foreground(_console(con), x, y, col) @@ -1213,29 +1231,42 @@ def console_set_char( x (int): Character x position from the left. y (int): Character y position from the top. c (Union[int, AnyStr]): Character to draw, can be an integer or string. + + .. deprecated:: 8.4 + Array access performs significantly faster than using this function. + See :any:`Console.ch`. """ lib.TCOD_console_set_char(_console(con), x, y, _int(c)) +@deprecate("Set the `con.default_bg_blend` attribute instead.") def console_set_background_flag(con: tcod.console.Console, flag: int) -> None: """Change the default blend mode for this console. Args: con (Console): Any Console instance. flag (int): Blend mode to use by default. + + .. deprecated:: 8.5 + Set :any:`Console.default_bg_blend` instead. """ lib.TCOD_console_set_background_flag(_console(con), flag) +@deprecate("Check the `con.default_bg_blend` attribute instead.") def console_get_background_flag(con: tcod.console.Console) -> int: """Return this consoles current blend mode. Args: con (Console): Any Console instance. + + .. deprecated:: 8.5 + Check :any:`Console.default_bg_blend` instead. """ return int(lib.TCOD_console_get_background_flag(_console(con))) +@deprecate("Set the `con.default_alignment` attribute instead.") def console_set_alignment(con: tcod.console.Console, alignment: int) -> None: """Change this consoles current alignment mode. @@ -1246,19 +1277,27 @@ def console_set_alignment(con: tcod.console.Console, alignment: int) -> None: Args: con (Console): Any Console instance. alignment (int): + + .. deprecated:: 8.5 + Set :any:`Console.default_alignment` instead. """ lib.TCOD_console_set_alignment(_console(con), alignment) +@deprecate("Check the `con.default_alignment` attribute instead.") def console_get_alignment(con: tcod.console.Console) -> int: """Return this consoles current alignment mode. Args: con (Console): Any Console instance. + + .. deprecated:: 8.5 + Check :any:`Console.default_alignment` instead. """ return int(lib.TCOD_console_get_alignment(_console(con))) +@deprecate("Call the `con.print_` method instead.") def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: """Print a color formatted string on a console. @@ -1267,10 +1306,14 @@ def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: x (int): Character x position from the left. y (int): Character y position from the top. fmt (AnyStr): A unicode or bytes string optionaly using color codes. + + .. deprecated:: 8.5 + Use :any:`Console.print_` instead. """ lib.TCOD_console_printf(_console(con), x, y, _fmt(fmt)) +@deprecate("Call the `con.print_` method instead.") def console_print_ex( con: tcod.console.Console, x: int, @@ -1285,10 +1328,14 @@ def console_print_ex( con (Console): Any Console instance. x (int): Character x position from the left. y (int): Character y position from the top. + + .. deprecated:: 8.5 + Use :any:`Console.print_` instead. """ lib.TCOD_console_printf_ex(_console(con), x, y, flag, alignment, _fmt(fmt)) +@deprecate("Call the `con.print_rect` method instead.") def console_print_rect( con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str ) -> int: @@ -1302,12 +1349,16 @@ def console_print_rect( Returns: int: The number of lines of text once word-wrapped. + + .. deprecated:: 8.5 + Use :any:`Console.print_rect` instead. """ return int( lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)) ) +@deprecate("Call the `con.print_rect` method instead.") def console_print_rect_ex( con: tcod.console.Console, x: int, @@ -1322,6 +1373,9 @@ def console_print_rect_ex( Returns: int: The number of lines of text once word-wrapped. + + .. deprecated:: 8.5 + Use :any:`Console.print_rect` instead. """ return int( lib.TCOD_console_printf_rect_ex( @@ -1330,6 +1384,7 @@ def console_print_rect_ex( ) +@deprecate("Call the `con.get_height_rect` method instead.") def console_get_height_rect( con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str ) -> int: @@ -1337,6 +1392,9 @@ def console_get_height_rect( Returns: int: The number of lines of text once word-wrapped. + + .. deprecated:: 8.5 + Use :any:`Console.get_height_rect` instead. """ return int( lib.TCOD_console_get_height_rect_fmt( @@ -1345,6 +1403,7 @@ def console_get_height_rect( ) +@deprecate("Call the `con.rect` method instead.") def console_rect( con: tcod.console.Console, x: int, @@ -1357,10 +1416,14 @@ def console_rect( """Draw a the background color on a rect optionally clearing the text. If clr is True the affected tiles are changed to space character. + + .. deprecated:: 8.5 + Use :any:`Console.rect` instead. """ lib.TCOD_console_rect(_console(con), x, y, w, h, clr, flag) +@deprecate("Call the `con.hline` method instead.") def console_hline( con: tcod.console.Console, x: int, @@ -1371,10 +1434,14 @@ def console_hline( """Draw a horizontal line on the console. This always uses the character 196, the horizontal line character. + + .. deprecated:: 8.5 + Use :any:`Console.hline` instead. """ lib.TCOD_console_hline(_console(con), x, y, l, flag) +@deprecate("Call the `con.vline` method instead.") def console_vline( con: tcod.console.Console, x: int, @@ -1385,10 +1452,14 @@ def console_vline( """Draw a vertical line on the console. This always uses the character 179, the vertical line character. + + .. deprecated:: 8.5 + Use :any:`Console.vline` instead. """ lib.TCOD_console_vline(_console(con), x, y, l, flag) +@deprecate("Call the `con.print_frame` method instead.") def console_print_frame( con: tcod.console.Console, x: int, @@ -1409,6 +1480,9 @@ def console_print_frame( .. versionchanged:: 8.2 Now supports Unicode strings. + + .. deprecated:: 8.5 + Use :any:`Console.print_frame` instead. """ fmt = _fmt(fmt) if fmt else ffi.NULL lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) @@ -1429,15 +1503,25 @@ def console_set_color_control( lib.TCOD_console_set_color_control(con, fore, back) +@deprecate("Check the `con.default_bg` attribute instead.") def console_get_default_background(con: tcod.console.Console) -> Color: - """Return this consoles default background color.""" + """Return this consoles default background color. + + .. deprecated:: 8.5 + Use :any:`Console.default_bg` instead. + """ return Color._new_from_cdata( lib.TCOD_console_get_default_background(_console(con)) ) +@deprecate("Check the `con.default_fg` attribute instead.") def console_get_default_foreground(con: tcod.console.Console) -> Color: - """Return this consoles default foreground color.""" + """Return this consoles default foreground color. + + .. deprecated:: 8.5 + Use :any:`Console.default_fg` instead. + """ return Color._new_from_cdata( lib.TCOD_console_get_default_foreground(_console(con)) ) @@ -1447,7 +1531,12 @@ def console_get_default_foreground(con: tcod.console.Console) -> Color: def console_get_char_background( con: tcod.console.Console, x: int, y: int ) -> Color: - """Return the background color at the x,y of this console.""" + """Return the background color at the x,y of this console. + + .. deprecated:: 8.4 + Array access performs significantly faster than using this function. + See :any:`Console.bg`. + """ return Color._new_from_cdata( lib.TCOD_console_get_char_background(_console(con), x, y) ) @@ -1457,7 +1546,12 @@ def console_get_char_background( def console_get_char_foreground( con: tcod.console.Console, x: int, y: int ) -> Color: - """Return the foreground color at the x,y of this console.""" + """Return the foreground color at the x,y of this console. + + .. deprecated:: 8.4 + Array access performs significantly faster than using this function. + See :any:`Console.fg`. + """ return Color._new_from_cdata( lib.TCOD_console_get_char_foreground(_console(con), x, y) ) @@ -1465,7 +1559,12 @@ def console_get_char_foreground( @deprecate("Directly access a consoles characters with `console.ch`") def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: - """Return the character at the x,y of this console.""" + """Return the character at the x,y of this console. + + .. deprecated:: 8.4 + Array access performs significantly faster than using this function. + See :any:`Console.ch`. + """ return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore @@ -1508,8 +1607,14 @@ def console_is_key_pressed(key: int) -> bool: # using offscreen consoles +@deprecate("Create a console using `tcod.console.Console(...)` instead.") def console_new(w: int, h: int) -> tcod.console.Console: - """Return an offscreen console of size: w,h.""" + """Return an offscreen console of size: w,h. + + .. deprecated:: 8.5 + Create new consoles using :any:`tcod.console.Console` instead of this + function. + """ return tcod.console.Console(w, h) @@ -1529,6 +1634,7 @@ def console_from_file(filename: str) -> tcod.console.Console: ) +@deprecate("Call the `Console.blit` method instead.") def console_blit( src: tcod.console.Console, x: int, @@ -1541,16 +1647,25 @@ def console_blit( ffade: float = 1.0, bfade: float = 1.0, ) -> None: - """Blit the console src from x,y,w,h to console dst at xdst,ydst.""" + """Blit the console src from x,y,w,h to console dst at xdst,ydst. + + .. deprecated:: 8.5 + Call the :any:`Console.blit` method instead. + """ lib.TCOD_console_blit( _console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade ) +@deprecate("Call the `Console.set_key_color` method instead.") def console_set_key_color( con: tcod.console.Console, col: Tuple[int, int, int] ) -> None: - """Set a consoles blit transparent color.""" + """Set a consoles blit transparent color. + + .. deprecated:: 8.5 + Call the :any:`Console.set_key_color` method instead. + """ lib.TCOD_console_set_key_color(_console(con), col) if hasattr(con, "set_key_color"): con.set_key_color(col) From 35f32dee6f6ca87fd8e9fbb49b4e4f747da9b9c5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Feb 2019 23:21:37 -0800 Subject: [PATCH 0073/1101] Clear warnings from tests. Don't test all renderers, those are tested by libtcod itself. --- tests/conftest.py | 2 +- tests/test_libtcodpy.py | 150 +++++++++++++++++++++++++--------------- tests/test_tcod.py | 1 + tests/test_tdl.py | 2 +- 4 files changed, 96 insertions(+), 59 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 78859118..6058a5f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ def pytest_addoption(parser): parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") -@pytest.fixture(scope="session", params=['SDL', 'OPENGL', 'GLSL']) +@pytest.fixture(scope="session", params=['SDL', 'SDL2']) def session_console(request): if(pytest.config.getoption("--no-window")): pytest.skip("This test needs a rendering context.") diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 2d5dd87a..83215210 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -2,24 +2,26 @@ import pytest -try: - import numpy -except ImportError: - numpy = None - +import numpy +import numpy as np import tcod as libtcodpy +import tcod + def test_console_behaviour(console): assert not console + @pytest.mark.skip('takes too long') def test_credits_long(console): libtcodpy.console_credits() + def test_credits(console): libtcodpy.console_credits_render(0, 0, True) libtcodpy.console_credits_reset() + def assert_char(console, x, y, ch=None, fg=None, bg=None): if ch is not None: try: @@ -32,35 +34,46 @@ def assert_char(console, x, y, ch=None, fg=None, bg=None): if bg is not None: assert (console.bg[y, x] == bg).all() + +@pytest.mark.filterwarnings("ignore:.") def test_console_defaults(console, fg, bg): libtcodpy.console_set_default_foreground(console, fg) libtcodpy.console_set_default_background(console, bg) libtcodpy.console_clear(console) assert_char(console, 0, 0, None, fg, bg) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") + +@pytest.mark.filterwarnings("ignore:.") def test_console_set_char_background(console, bg): libtcodpy.console_set_char_background(console, 0, 0, bg, libtcodpy.BKGND_SET) assert_char(console, 0, 0, bg=bg) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") + +@pytest.mark.filterwarnings("ignore:.") def test_console_set_char_foreground(console, fg): libtcodpy.console_set_char_foreground(console, 0, 0, fg) assert_char(console, 0, 0, fg=fg) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") + +@pytest.mark.filterwarnings("ignore:.") def test_console_set_char(console, ch): libtcodpy.console_set_char(console, 0, 0, ch) assert_char(console, 0, 0, ch=ch) + +@pytest.mark.filterwarnings("ignore:.") def test_console_put_char(console, ch): libtcodpy.console_put_char(console, 0, 0, ch, libtcodpy.BKGND_SET) assert_char(console, 0, 0, ch=ch) + +@pytest.mark.filterwarnings("ignore:.") def console_put_char_ex(console, ch, fg, bg): libtcodpy.console_put_char_ex(console, 0, 0, ch, fg, bg) assert_char(console, 0, 0, ch=ch, fg=fg, bg=bg) + +@pytest.mark.filterwarnings("ignore:.") def test_console_printing(console, fg, bg): libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET) @@ -88,16 +101,24 @@ def test_console_printing(console, fg, bg): libtcodpy.console_set_color_control(libtcodpy.COLCTRL_1, fg, bg) + +@pytest.mark.filterwarnings("ignore:.") def test_console_rect(console): libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) + +@pytest.mark.filterwarnings("ignore:.") def test_console_lines(console): libtcodpy.console_hline(console, 0, 0, 4) libtcodpy.console_vline(console, 0, 0, 4) + +@pytest.mark.filterwarnings("ignore:.") def test_console_print_frame(console): libtcodpy.console_print_frame(console, 0, 0, 9, 9) + +@pytest.mark.filterwarnings("ignore:.") def test_console_fade(console): libtcodpy.console_set_fade(0, libtcodpy.Color(0, 0, 0)) libtcodpy.console_get_fade() @@ -110,12 +131,15 @@ def assertConsolesEqual(a, b): (a.ch[:] == b.ch[:]).all()) +@pytest.mark.filterwarnings("ignore:.") def test_console_blit(console, offscreen): libtcodpy.console_print(offscreen, 0, 0, 'test') libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1) assertConsolesEqual(console, offscreen) libtcodpy.console_set_key_color(offscreen, libtcodpy.black) + +@pytest.mark.filterwarnings("ignore:.") def test_console_asc_read_write(console, offscreen, tmpdir): libtcodpy.console_print(console, 0, 0, 'test') @@ -124,6 +148,8 @@ def test_console_asc_read_write(console, offscreen, tmpdir): assert libtcodpy.console_load_asc(offscreen, asc_file) assertConsolesEqual(console, offscreen) + +@pytest.mark.filterwarnings("ignore:.") def test_console_apf_read_write(console, offscreen, tmpdir): libtcodpy.console_print(console, 0, 0, 'test') @@ -132,7 +158,8 @@ def test_console_apf_read_write(console, offscreen, tmpdir): assert libtcodpy.console_load_apf(offscreen, apf_file) assertConsolesEqual(console, offscreen) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") + +@pytest.mark.filterwarnings("ignore:.") def test_console_rexpaint_load_test_file(console): xp_console = libtcodpy.console_from_xp('libtcod/data/rexpaint/test.xp') assert xp_console @@ -145,7 +172,8 @@ def test_console_rexpaint_load_test_file(console): assert (libtcodpy.console_get_char_background(xp_console, 2, 1) == libtcodpy.Color(0, 0, 255)) -@pytest.mark.filterwarnings("ignore:Falsy console parameters are deprecated") + +@pytest.mark.filterwarnings("ignore:.") def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): libtcodpy.console_print(console, 0, 0, 'test') libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) @@ -157,6 +185,8 @@ def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): assert libtcodpy.console_load_xp(None, xp_file) assertConsolesEqual(console, xp_console) + +@pytest.mark.filterwarnings("ignore:.") def test_console_rexpaint_list_save_load(console, tmpdir): con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) @@ -169,20 +199,24 @@ def test_console_rexpaint_list_save_load(console, tmpdir): libtcodpy.console_delete(a) libtcodpy.console_delete(b) + def test_console_fullscreen(console): libtcodpy.console_set_fullscreen(False) + def test_console_key_input(console): libtcodpy.console_check_for_keypress() libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER) + +@pytest.mark.filterwarnings("ignore:.") def test_console_fill_errors(console): with pytest.raises(TypeError): libtcodpy.console_fill_background(console, [0], [], []) with pytest.raises(TypeError): libtcodpy.console_fill_foreground(console, [0], [], []) -@pytest.mark.filterwarnings("ignore:Directly access a consoles") +@pytest.mark.filterwarnings("ignore:.") def test_console_fill(console): width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) @@ -202,8 +236,8 @@ def test_console_fill(console): assert fill == fg assert fill == ch -@pytest.mark.skipif(not numpy, reason='requires numpy module') -@pytest.mark.filterwarnings("ignore:Directly access a consoles") + +@pytest.mark.filterwarnings("ignore:.") def test_console_fill_numpy(console): width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) @@ -229,7 +263,7 @@ def test_console_fill_numpy(console): assert fill == fg.tolist() assert fill == ch.tolist() -@pytest.mark.filterwarnings("ignore:Console array attributes perform better") +@pytest.mark.filterwarnings("ignore:.") def test_console_buffer(console): buffer = libtcodpy.ConsoleBuffer( libtcodpy.console_get_width(console), @@ -331,6 +365,8 @@ def test_clipboard(console, sample): (2, 7), (3, 8), (4, 9), (5, 10)] INCLUSIVE_RESULTS = [(-5, 0)] + EXCLUSIVE_RESULTS + +@pytest.mark.filterwarnings("ignore:.") def test_line_step(): """ libtcodpy.line_init and libtcodpy.line_step @@ -340,6 +376,8 @@ def test_line_step(): assert libtcodpy.line_step() == expected_xy assert libtcodpy.line_step() == (None, None) + +@pytest.mark.filterwarnings("ignore:.") def test_line(): """ tests normal use, lazy evaluation, and error propagation @@ -360,19 +398,15 @@ def return_false(*test_xy): assert libtcodpy.line(*LINE_ARGS, py_callback=return_false) == 0 assert test_result == INCLUSIVE_RESULTS[:1] + def test_line_iter(): """ libtcodpy.line_iter """ assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS -@pytest.mark.filterwarnings("ignore:Assign values via attribute instead.") -@pytest.mark.filterwarnings("ignore:Use 'node.contains' instead.") -@pytest.mark.filterwarnings("ignore:Check for children with") -@pytest.mark.filterwarnings("ignore:Iterate over nodes using") -@pytest.mark.filterwarnings("ignore:Access children with") -@pytest.mark.filterwarnings("ignore:Delete bsp children using") -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_bsp(): """ commented out statements work in libtcod-cffi @@ -434,7 +468,8 @@ def traverse(node, user_data): libtcodpy.bsp_delete(bsp) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_map(): map = libtcodpy.map_new(16, 16) assert libtcodpy.map_get_width(map) == 16 @@ -447,6 +482,7 @@ def test_map(): libtcodpy.map_is_in_fov(map, 0, 0) libtcodpy.map_delete(map) + def test_color(): color_a = libtcodpy.Color(0, 1, 2) assert list(color_a) == [0, 1, 2] @@ -468,11 +504,13 @@ def test_color(): libtcodpy.color_get_hsv(color) libtcodpy.color_scale_HSV(color, 0, 0) + def test_color_repr(): Color = libtcodpy.Color col = Color(0, 1, 2) assert eval(repr(col)) == col + def test_color_math(): color_a = libtcodpy.Color(0, 1, 2) color_b = libtcodpy.Color(0, 10, 20) @@ -482,17 +520,20 @@ def test_color_math(): assert libtcodpy.Color(255, 255, 255) * color_a == color_a assert color_a * 100 == libtcodpy.Color(0, 100, 200) + def test_color_gen_map(): colors = libtcodpy.color_gen_map([(0, 0, 0), (255, 255, 255)], [0, 8]) assert colors[0] == libtcodpy.Color(0, 0, 0) assert colors[-1] == libtcodpy.Color(255, 255, 255) + def test_namegen_parse(): libtcodpy.namegen_parse('libtcod/data/namegen/jice_celtic.cfg') assert libtcodpy.namegen_generate('Celtic female') assert libtcodpy.namegen_get_sets() libtcodpy.namegen_destroy() + @pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") def test_noise(): noise = libtcodpy.noise_new(1) @@ -502,7 +543,8 @@ def test_noise(): libtcodpy.noise_get_turbulence(noise, [0], 4) libtcodpy.noise_delete(noise) -@pytest.mark.filterwarnings("ignore:.*") + +@pytest.mark.filterwarnings("ignore:.") def test_random(): rand = libtcodpy.random_get_instance() rand = libtcodpy.random_new() @@ -523,19 +565,7 @@ def test_random(): libtcodpy.random_delete(backup) -@pytest.mark.filterwarnings("ignore:Assign to this heightmap with") -@pytest.mark.filterwarnings("ignore:Add a scalar to an array using") -@pytest.mark.filterwarnings("ignore:Multiply an array with a scaler using `hm") -@pytest.mark.filterwarnings("ignore:Clear an array with") -@pytest.mark.filterwarnings("ignore:Clamp array values using") -@pytest.mark.filterwarnings("ignore:Copy an array using") -@pytest.mark.filterwarnings("ignore:Add 2 arrays using") -@pytest.mark.filterwarnings("ignore:Multiply 2 arrays using") -@pytest.mark.filterwarnings("ignore:Arrays of noise should be sampled using t") -@pytest.mark.filterwarnings("ignore:Get a value from this heightmap with") -@pytest.mark.filterwarnings("ignore:This function is deprecated, see document") -@pytest.mark.filterwarnings("ignore:Use `hm.min\\(\\)` and `hm.max\\(\\)`") -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") +@pytest.mark.filterwarnings("ignore:.") def test_heightmap(): hmap = libtcodpy.heightmap_new(16, 16) repr(hmap) @@ -579,17 +609,18 @@ def test_heightmap(): libtcodpy.heightmap_delete(hmap) MAP = ( - '############', - '# ### #', - '# ### #', - '# ### ####', - '## #### # ##', - '## ####', - '############', - ) - -MAP_WIDTH = len(MAP[0]) -MAP_HEIGHT = len(MAP) + '############', + '# ### #', + '# ### #', + '# ### ####', + '## #### # ##', + '## ####', + '############', +) + +MAP = np.array([list(line) for line in MAP]) + +MAP_HEIGHT, MAP_WIDTH = MAP.shape POINT_A = (2, 2) POINT_B = (9, 2) @@ -598,28 +629,30 @@ def test_heightmap(): POINTS_AB = POINT_A + POINT_B # valid path POINTS_AC = POINT_A + POINT_C # invalid path + @pytest.fixture() def map_(): - map_ = libtcodpy.map_new(MAP_WIDTH, MAP_HEIGHT) - for y, line in enumerate(MAP): - for x, ch in enumerate(line): - libtcodpy.map_set_properties(map_, x, y, ch == ' ', ch == ' ') + map_ = tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) + map_.walkable[...] = map_.transparent[...] = MAP[...] == ' ' yield map_ libtcodpy.map_delete(map_) + @pytest.fixture() def path_callback(map_): def callback(ox, oy, dx, dy, user_data): - if libtcodpy.map_is_walkable(map_, dx, dy): + if map_.walkable[dy, dx]: return 1 return 0 return callback -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_map_fov(map_): libtcodpy.map_compute_fov(map_, *POINT_A) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_astar(map_): astar = libtcodpy.path_new_using_map(map_) @@ -644,7 +677,8 @@ def test_astar(map_): libtcodpy.path_delete(astar) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_astar_callback(map_, path_callback): astar = libtcodpy.path_new_using_function( libtcodpy.map_get_width(map_), @@ -654,7 +688,8 @@ def test_astar_callback(map_, path_callback): libtcodpy.path_compute(astar, *POINTS_AB) libtcodpy.path_delete(astar) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_dijkstra(map_): path = libtcodpy.dijkstra_new(map_) @@ -677,7 +712,8 @@ def test_dijkstra(map_): libtcodpy.dijkstra_delete(path) -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") + +@pytest.mark.filterwarnings("ignore:.") def test_dijkstra_callback(map_, path_callback): path = libtcodpy.dijkstra_new_using_function( libtcodpy.map_get_width(map_), diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 378a1f48..f9301f37 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -53,6 +53,7 @@ def test_tcod_bsp(): str(bsp) +@pytest.mark.filterwarnings("ignore:Use map.+ to check for this") def test_tcod_map_set_bits(): map_ = tcod.map.Map(2,2) diff --git a/tests/test_tdl.py b/tests/test_tdl.py index 9ccc3aea..b838659c 100755 --- a/tests/test_tdl.py +++ b/tests/test_tdl.py @@ -24,7 +24,7 @@ class TDLTemplate(unittest.TestCase): @classmethod def setUpClass(cls): - cls.console = tdl.init(WIDTH, HEIGHT, 'TDL UnitTest', False, renderer='GLSL') + cls.console = tdl.init(WIDTH, HEIGHT, 'TDL UnitTest', False, renderer='SDL') # make a small window in the corner cls.window = tdl.Window(cls.console, 0, 0, WINWIDTH, WINHEIGHT) From cb1fa48bf2c99ada10c151b3ef1c593bd5aa57bd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 Feb 2019 02:16:38 -0800 Subject: [PATCH 0074/1101] Fix Console.blit key color. Fixes libtcod/libtcod#11 --- CHANGELOG.rst | 3 +++ tcod/console.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index affb3184..b74842e0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,9 @@ Changed Deprecated - Most libtcodpy console functions have been replaced by the tcod.console module. +Fixed + - `tcod.console.Console.blit` was ignoring the key color set by + `Console.set_key_color`. 8.4.3 - 2019-02-06 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 21949a57..f88dfcbc 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -551,7 +551,8 @@ def blit( stacklevel=2, ) - if key_color or self._key_color: + key_color = key_color or self._key_color + if key_color: key_color = ffi.new("TCOD_color_t*", key_color) lib.TCOD_console_blit_key_color( self.console_c, @@ -580,11 +581,10 @@ def blit( bg_alpha, ) - def set_key_color(self, color: Tuple[int, int, int]) -> None: + def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: """Set a consoles blit transparent color. - Args: - color (Tuple[int, int, int]): + `color` is the (r, g, b) color, or None to disable key color. """ self._key_color = color From ac941f27c3a27dabd7951227d36e339d1a99b539 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 Feb 2019 02:48:06 -0800 Subject: [PATCH 0075/1101] Extend Console.clear function. --- CHANGELOG.rst | 1 + tcod/console.py | 15 ++++++++++++++- tests/test_console.py | 11 +++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b74842e0..0f8a8924 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added Changed - You can now give default values or an array when initializing a `tcod.console.Console` instance. + - `Console.clear` can now take `fg` and `bg` parameters. Deprecated - Most libtcodpy console functions have been replaced by the tcod.console module. diff --git a/tcod/console.py b/tcod/console.py index f88dfcbc..9e8aa870 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -293,9 +293,22 @@ def default_alignment(self) -> int: def default_alignment(self, value: int) -> None: self._console_data.alignment = value - def clear(self) -> None: + def clear( + self, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, + ) -> None: """Reset this console to its default colors and the space character. + + If `fg` or `bg` are provided they will become the new default colors. + + .. versionchanged:: 8.5 + Added the `fg` and `bg` parameters. """ + if fg: + self.default_fg = fg + if bg: + self.default_bg = bg lib.TCOD_console_clear(self.console_c) def put_char( diff --git a/tests/test_console.py b/tests/test_console.py index f719573c..208d1a33 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -119,3 +119,14 @@ def test_console_fortran_buffer(): order="F", buffer=np.zeros((1, 2), order="F", dtype=tcod.console.Console.DTYPE), ) + + +def test_console_clear(): + console = tcod.console.Console( + 1, 1, default_fg=(1, 2, 3), default_bg=(4, 5, 6) + ) + assert console.fg[0, 0].tolist() == [1, 2, 3] + assert console.bg[0, 0].tolist() == [4, 5, 6] + console.clear(fg=(7, 8, 9), bg=(10, 11, 12)) + assert console.fg[0, 0].tolist() == [7, 8, 9] + assert console.bg[0, 0].tolist() == [10, 11, 12] From 4d0e37182d4f1f1bc50660b2b2ef78e3e7cd9a4a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 Feb 2019 03:27:27 -0800 Subject: [PATCH 0076/1101] Deprecate set_key_color functions. --- CHANGELOG.rst | 2 ++ tcod/console.py | 8 ++++++++ tcod/libtcodpy.py | 7 +++++-- tests/test_console.py | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0f8a8924..ae9b29df 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,8 @@ Changed Deprecated - Most libtcodpy console functions have been replaced by the tcod.console module. + - Deprecated the `set_key_color` functions. You can pass key colors to + `Console.blit` instead. Fixed - `tcod.console.Console.blit` was ignoring the key color set by `Console.set_key_color`. diff --git a/tcod/console.py b/tcod/console.py index 9e8aa870..587e5719 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -33,6 +33,7 @@ import tcod.constants from tcod.libtcod import ffi, lib import tcod._internal +from tcod._internal import deprecate def _fmt(string: str) -> bytes: @@ -594,10 +595,17 @@ def blit( bg_alpha, ) + @deprecate( + "Pass the key color to Console.blit instead of calling this function." + ) def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: """Set a consoles blit transparent color. `color` is the (r, g, b) color, or None to disable key color. + + .. deprecated:: 8.5 + Pass the key color to :any:`Console.blit` instead of calling this + function. """ self._key_color = color diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index f399c995..67ebd91e 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1657,14 +1657,17 @@ def console_blit( ) -@deprecate("Call the `Console.set_key_color` method instead.") +@deprecate( + "Pass the key color to `Console.blit` instead of calling this function." +) def console_set_key_color( con: tcod.console.Console, col: Tuple[int, int, int] ) -> None: """Set a consoles blit transparent color. .. deprecated:: 8.5 - Call the :any:`Console.set_key_color` method instead. + Pass the key color to :any:`tcod.console.Console.blit` instead of + calling this function. """ lib.TCOD_console_set_key_color(_console(con), col) if hasattr(con, "set_key_color"): diff --git a/tests/test_console.py b/tests/test_console.py index 208d1a33..9a60aa95 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -66,6 +66,7 @@ def test_console_defaults(): @pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") +@pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instea") def test_console_methods(): console = tcod.console.Console(width=12, height=10) console.put_char(0, 0, ord('@')) From ef0dcb8ad5a688c19a608b9420e959f223a649d3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 12 Feb 2019 00:19:34 -0800 Subject: [PATCH 0077/1101] Add parameters to the Console.clear method. --- CHANGELOG.rst | 4 +++- tcod/console.py | 50 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ae9b29df..ba4812a8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,12 +14,14 @@ Added Changed - You can now give default values or an array when initializing a `tcod.console.Console` instance. - - `Console.clear` can now take `fg` and `bg` parameters. + - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. Deprecated - Most libtcodpy console functions have been replaced by the tcod.console module. - Deprecated the `set_key_color` functions. You can pass key colors to `Console.blit` instead. + - `Console.clear` should be given the colors to clear with as parameters, + rather than by using `default_fg` or `default_bg`. Fixed - `tcod.console.Console.blit` was ignoring the key color set by `Console.set_key_color`. diff --git a/tcod/console.py b/tcod/console.py index 587e5719..07c87fde 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -154,7 +154,7 @@ def __init__( ) if buffer is None: - self.clear() + self.clear(fg=default_fg, bg=default_bg) @classmethod def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": @@ -294,23 +294,49 @@ def default_alignment(self) -> int: def default_alignment(self, value: int) -> None: self._console_data.alignment = value - def clear( + def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: + """Raise a warning for bad default values during calls to clear.""" + warnings.warn( + "Clearing with the console default values is deprecated.\n" + "Add %s=%r to this call." % (name, value), + DeprecationWarning, + stacklevel=3, + ) + + def clear( # type: ignore self, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + ch: int = ord(" "), + fg: Tuple[int, int, int] = ..., + bg: Tuple[int, int, int] = ..., ) -> None: - """Reset this console to its default colors and the space character. + """Reset all values in this console to a single value. + + `ch` is the character to clear the console with. Defaults to the space + character. + + `fg` and `bg` are the colors to clear the console with. Defaults to + white-on-black if the console defaults are untouched. - If `fg` or `bg` are provided they will become the new default colors. + .. note:: + If `fg`/`bg` are not set, they will default to + :any:`default_fg`/:any:`default_bg`. + However, default values other than white-on-back are deprecated. .. versionchanged:: 8.5 - Added the `fg` and `bg` parameters. + Added the `ch`, `fg`, and `bg` parameters. + Non-white-on-black default values are deprecated. """ - if fg: - self.default_fg = fg - if bg: - self.default_bg = bg - lib.TCOD_console_clear(self.console_c) + if fg is ...: + fg = self.default_fg + if fg != (255, 255, 255): + self.__clear_warning("fg", fg) + if bg is ...: + bg = self.default_bg + if bg != (0, 0, 0): + self.__clear_warning("bg", bg) + self.ch[...] = ch + self.fg[...] = fg + self.bg[...] = bg def put_char( self, From c0dd07042389bb45d05d2183548e2f307ea36575 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 13 Feb 2019 20:19:19 -0800 Subject: [PATCH 0078/1101] Add pypy3 build to TravisCI. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index f2b0fca2..faad2b9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,11 @@ matrix: dist: xenial sudo: required env: CC=gcc-8 CXX=g++-8 + - os: linux + python: pypy3.5-7.0 + dist: xenial + sudo: required + env: CC=gcc-8 CXX=g++-8 - os: linux python: nightly dist: xenial @@ -22,6 +27,7 @@ matrix: env: MB_PYTHON_VERSION=3.7.1 allow_failures: - python: nightly + - python: pypy3.5-7.0 fast_finish: true From 1adea413b2d7b241d4aa32d163af27e4b495b6eb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 13 Feb 2019 20:56:33 -0800 Subject: [PATCH 0079/1101] Require "virtualenv>=16" on AppVeyor builds. --- .appveyor/install_python.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index fc82eff1..d320ab2d 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -18,7 +18,8 @@ if ($env:WEB_PYTHON) { Start-Process $PYTHON_INSTALLER -Wait -ArgumentList "/quiet InstallAllUsers=1 TargetDir=C:\UserPython Include_doc=0 Include_launcher=0 Include_test=0 Shortcuts=0" $env:PYTHON = 'C:\UserPython\python.exe' } -& $env:PYTHON -m pip install --disable-pip-version-check virtualenv +& $env:PYTHON -m pip install --disable-pip-version-check --upgrade pip +& $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=16" & $env:PYTHON -m virtualenv venv if ($env:PYPY) { From dfdb19e239ed892a0a0b4ac9baec03a69f423534 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 13 Feb 2019 21:20:16 -0800 Subject: [PATCH 0080/1101] Add PyPy3 to Appveyor builds. --- .appveyor/install_python.ps1 | 1 + appveyor.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index d320ab2d..18fea56c 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -5,6 +5,7 @@ if ($env:PYPY -or $env:PYPY3) { } else { $env:PYPY_EXE='pypy.exe' } + $env:PYPY = $env:PYPY + '-win32' $env:PYTHON = 'C:\' + $env:PYPY + '\' + $env:PYPY_EXE $env:PATH += ';' + 'C:\' + $env:PYPY + '\' $PYPY_DOWNLOAD = 'https://bitbucket.org/pypy/pypy/downloads/' + $env:PYPY + '.zip' diff --git a/appveyor.yml b/appveyor.yml index 34b3b214..4308ba08 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,8 @@ environment: platform: x64 - PYTHON: C:\Python37\python.exe platform: Any CPU + - PYPY3: pypy3.5-v7.0.0 + platform: Any CPU matrix: allow_failures: @@ -38,7 +40,6 @@ install: - cmd: "python --version" - cmd: "set PATH=%PATH%;C:\\MinGW\\bin" - cmd: "pip install --upgrade setuptools" -- cmd: "if defined PYPY pip install git+https://bitbucket.org/pypy/numpy.git" - cmd: "pip install --requirement requirements.txt" - cmd: "python setup.py build sdist develop bdist_wheel" build: off From 2e56f004c639c38741265d68a572da1058d0640e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 13 Feb 2019 22:21:49 -0800 Subject: [PATCH 0081/1101] Update libtcod submodule. --- libtcod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtcod b/libtcod index 44a6216b..a6c8d7e7 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 44a6216bcfec3503b680509fb1547c5ee1af24b3 +Subproject commit a6c8d7e75806a3c799d5a1f2980edbe3a73f9074 From b3c047afed50368f9c4b9258d6b55f0547a84a3c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 14 Feb 2019 00:14:42 -0800 Subject: [PATCH 0082/1101] Add PyPy3 builds for MacOS. --- .travis.yml | 3 +++ .travis/install_python.sh | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index faad2b9a..bf22c3da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,9 @@ matrix: - os: osx language: generic env: MB_PYTHON_VERSION=3.7.1 + - os: osx + language: generic + env: PYPY3_VERSION=pypy3.5-v7.0.0 allow_failures: - python: nightly - python: pypy3.5-7.0 diff --git a/.travis/install_python.sh b/.travis/install_python.sh index b265b61b..67c22ea9 100755 --- a/.travis/install_python.sh +++ b/.travis/install_python.sh @@ -215,7 +215,7 @@ function install_mac_pypy3 { # Version given in full major.minor.micro e.g "3.4.1". # sets $PYTHON_EXE variable to python executable local py_version=$1 - local py_build=$(get_pypy3_build_prefix $py_version)$py_version-osx64 + local py_build=$py_version-osx64 local py_zip=$py_build.tar.bz2 local zip_path=$DOWNLOADS_SDIR/$py_zip mkdir -p $DOWNLOADS_SDIR @@ -229,6 +229,7 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then install_python if [[ -n "$PYTHON_EXE" ]]; then # Default Python install is missing virtualenv. + $PYTHON_EXE -m ensurepip $PYTHON_EXE -m pip install --upgrade pip virtualenv $PYTHON_EXE -m virtualenv venv -p $PYTHON_EXE else From 6e1c664a3c46266d359414b7b233e83f8f3d987c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 14 Feb 2019 03:21:01 -0800 Subject: [PATCH 0083/1101] Update to newer print functions. --- libtcod | 2 +- tcod/console.py | 167 +++++++++++++++++++++++++++++++++++++++++++----- tcod/tcod.c | 22 ------- tcod/tcod.cpp | 98 ++++++++++++++++++++++++++++ tcod/tcod.h | 63 +++++++++++++++++- 5 files changed, 313 insertions(+), 39 deletions(-) delete mode 100644 tcod/tcod.c create mode 100644 tcod/tcod.cpp diff --git a/libtcod b/libtcod index a6c8d7e7..ead42535 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit a6c8d7e75806a3c799d5a1f2980edbe3a73f9074 +Subproject commit ead425352b21c25af3362529f17ea382995868f2 diff --git a/tcod/console.py b/tcod/console.py index 07c87fde..8a78fab1 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -406,15 +406,17 @@ def print_rect( int: The number of lines of text once word-wrapped. """ alignment = self.default_alignment if alignment is None else alignment - return lib.TCOD_console_printf_rect_ex( # type: ignore - self.console_c, - x, - y, - width, - height, - bg_blend, - alignment, - _fmt(string), + return int( + lib.TCOD_console_printf_rect_ex( + self.console_c, + x, + y, + width, + height, + bg_blend, + alignment, + _fmt(string), + ) ) def get_height_rect( @@ -432,8 +434,11 @@ def get_height_rect( Returns: int: The number of lines of text once word-wrapped. """ - return lib.TCOD_console_get_height_rect_fmt( # type: ignore - self.console_c, x, y, width, height, _fmt(string) + string_ = string.encode("utf-8") + return int( + lib.get_height_rect( + self.console_c, x, y, width, height, string_, len(string_) + ) ) def rect( @@ -447,7 +452,7 @@ def rect( ) -> None: """Draw a the background color on a rect optionally clearing the text. - If clr is True the affected tiles are changed to space character. + If `clear` is True the affected tiles are changed to space character. Args: x (int): The x coordinate from the left. @@ -471,7 +476,7 @@ def hline( ) -> None: """Draw a horizontal line on the console. - This always uses the character 196, the horizontal line character. + This always uses ord('─'), the horizontal line character. Args: x (int): The x coordinate from the left. @@ -490,7 +495,7 @@ def vline( ) -> None: """Draw a vertical line on the console. - This always uses the character 179, the vertical line character. + This always uses ord('│'), the vertical line character. Args: x (int): The x coordinate from the left. @@ -510,7 +515,7 @@ def print_frame( clear: bool = True, bg_blend: int = tcod.constants.BKGND_DEFAULT, ) -> None: - """Draw a framed rectangle with optinal text. + """Draw a framed rectangle with optional text. This uses the default background color and blend mode to fill the rectangle and the default foreground to draw the outline. @@ -721,3 +726,135 @@ def __str__(self) -> str: return "<%s>" % "|\n|".join( "".join(chr(c) for c in line) for line in self._ch ) + + def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: + if x < 0: + x += self.width + if y < 0: + y += self.height + return x, y + + def print( + self, + x: int, + y: int, + string: str, + fg: Optional[Tuple[int, int, int]] = (255, 255, 255), + bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + bg_blend: int = tcod.constants.BKGND_SET, + alignment: int = tcod.constants.LEFT, + ) -> None: + """Print a string on a console with manual line breaks. + + `x` and `y` are the starting tile, with ``0,0`` as the upper left + corner of the console. + + `string` is a Unicode string. + + .. versionadded:: 8.5 + """ + x, y = self._pythonic_index(x, y) + string_ = string.encode("utf-8") # type: bytes + lib.console_print( + self.console_c, + x, + y, + string_, + len(string_), + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + alignment, + ) + + def print_box( + self, + x: int, + y: int, + width: int, + height: int, + string: str, + fg: Optional[Tuple[int, int, int]] = (255, 255, 255), + bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + bg_blend: int = tcod.constants.BKGND_DEFAULT, + alignment: int = tcod.constants.LEFT, + ) -> int: + """Print a string constrained to a rectangle. + + .. versionadded:: 8.5 + """ + x, y = self._pythonic_index(x, y) + string_ = string.encode("utf-8") # type: bytes + return int( + lib.print_rect( + self.console_c, + x, + y, + width, + height, + string_, + len(string_), + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + alignment, + ) + ) + + def draw_frame( + self, + x: int, + y: int, + width: int, + height: int, + title: str = "", + fg: Optional[Tuple[int, int, int]] = (255, 255, 255), + bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + bg_blend: int = tcod.constants.BKGND_SET, + clear: bool = True, + ) -> None: + """Draw a framed rectangle with an optional title. + """ + x, y = self._pythonic_index(x, y) + title_ = title.encode("utf-8") # type: bytes + lib.print_frame( + self.console_c, + x, + y, + width, + height, + title_, + len(title_), + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + clear, + ) + + def draw_rect( + self, + x: int, + y: int, + width: int, + height: int, + ch: int, + fg: Optional[Tuple[int, int, int]] = (255, 255, 255), + bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + bg_blend: int = tcod.constants.BKGND_SET, + ) -> None: + """Draw characters and colors over a rectangular region. + + .. versionadded:: 8.5 + """ + x, y = self._pythonic_index(x, y) + lib.draw_rect( + self.console_c, + x, + y, + width, + height, + ch, + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + ) diff --git a/tcod/tcod.c b/tcod/tcod.c deleted file mode 100644 index 98194872..00000000 --- a/tcod/tcod.c +++ /dev/null @@ -1,22 +0,0 @@ - -#include "tcod.h" - -#include "../libtcod/src/libtcod/bresenham.h" - -/** - * Write a Bresenham line to the `x_out` and `y_out` arrays. - * - * `x_out` and `y_out` must be large enough to contain the entire line that - * this will output. Typically `max(abs(x1 - x2), abs(y1 - y2)) + 1`. - * - * This function includes both endpoints. - */ -int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out) { - TCOD_bresenham_data_t bresenham; - *x_out = x1; - *y_out = y1; - if (x1 == x2 && y1 == y2) { return 0; } - TCOD_line_init_mt(x1, y1, x2, y2, &bresenham); - while (!TCOD_line_step_mt(++x_out, ++y_out, &bresenham)) {} - return 0; -} diff --git a/tcod/tcod.cpp b/tcod/tcod.cpp new file mode 100644 index 00000000..df62c130 --- /dev/null +++ b/tcod/tcod.cpp @@ -0,0 +1,98 @@ + +#include + +#include "tcod.h" + +#include "../libtcod/src/libtcod/bresenham.h" +#include "../libtcod/src/libtcod/console/drawing.h" +#include "../libtcod/src/libtcod/console/printing.h" +/** + * Write a Bresenham line to the `x_out` and `y_out` arrays. + * + * `x_out` and `y_out` must be large enough to contain the entire line that + * this will output. Typically `max(abs(x1 - x2), abs(y1 - y2)) + 1`. + * + * This function includes both endpoints. + */ +int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out) { + TCOD_bresenham_data_t bresenham; + *x_out = x1; + *y_out = y1; + if (x1 == x2 && y1 == y2) { return 0; } + TCOD_line_init_mt(x1, y1, x2, y2, &bresenham); + while (!TCOD_line_step_mt(++x_out, ++y_out, &bresenham)) {} + return 0; +} + +void draw_rect( + TCOD_Console* console, + int x, + int y, + int width, + int height, + int ch, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag) +{ + tcod::console::draw_rect(console, x, y, width, height, ch, fg, bg, flag); +} +void console_print( + TCOD_Console* console, + int x, + int y, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + TCOD_alignment_t alignment) +{ + tcod::console::print(console, x, y, std::string(str, str_n), + fg, bg, flag, alignment); +} +int print_rect( + TCOD_Console* console, + int x, + int y, + int width, + int height, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + TCOD_alignment_t alignment) +{ + return tcod::console::print_rect(console, x, y, width, height, + std::string(str, str_n), fg, bg, + flag, alignment); +} +int get_height_rect( + TCOD_Console* console, + int x, + int y, + int width, + int height, + const char* str, + int str_n) +{ + return tcod::console::get_height_rect(console, x, y, width, height, + std::string(str, str_n)); +} +void print_frame( + TCOD_Console* console, + int x, + int y, + int width, + int height, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + bool empty) +{ + tcod::console::print_frame(console, x, y, width, height, + std::string(str, str_n), fg, bg, flag, empty); +} diff --git a/tcod/tcod.h b/tcod/tcod.h index f64cc0a9..831e462b 100644 --- a/tcod/tcod.h +++ b/tcod/tcod.h @@ -2,6 +2,67 @@ #ifndef TCOD_TCOD_H_ #define TCOD_TCOD_H_ +#include "../libtcod/src/libtcod/color.h" +#include "../libtcod/src/libtcod/console.h" + +#ifdef __cplusplus +extern "C" { +#endif int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out); -#endif /* TCOD_TCOD_H_ */ \ No newline at end of file +void draw_rect( + TCOD_Console* console, + int x, + int y, + int width, + int height, + int ch, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag); +void console_print( + TCOD_Console* console, + int x, + int y, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + TCOD_alignment_t alignment); +int print_rect( + TCOD_Console *con, + int x, + int y, + int width, + int height, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + TCOD_alignment_t alignment); +int get_height_rect( + TCOD_Console *con, + int x, + int y, + int width, + int height, + const char* str, + int str_n); +void print_frame( + TCOD_Console *con, + int x, + int y, + int width, + int height, + const char* str, + int str_n, + const TCOD_color_t* fg, + const TCOD_color_t* bg, + TCOD_bkgnd_flag_t flag, + bool empty); +#ifdef __cplusplus +} // extern "C" +#endif +#endif /* TCOD_TCOD_H_ */ From 883a8d0b3194a11f75786806a3a80cf045faac41 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 Feb 2019 05:48:23 -0800 Subject: [PATCH 0084/1101] Update libtcod submodule. --- libtcod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtcod b/libtcod index ead42535..3598e725 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ead425352b21c25af3362529f17ea382995868f2 +Subproject commit 3598e725ec826b945fd7f7b88014035ebdb367dd From 96f59779334624bed75fb60971b3c1a5adbeaaa8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 Feb 2019 10:43:27 -0800 Subject: [PATCH 0085/1101] Deprecate console default values. Remove default values from the python-tcod samples. --- CHANGELOG.rst | 4 + examples/samples_tcod.py | 195 ++++++++++++++--------------- tcod/console.py | 263 ++++++++++++++++++++++++++++++++------- tests/test_console.py | 22 +--- 4 files changed, 320 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba4812a8..cc7c5ed5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased ------------------ Added - `tcod.console.Console` now supports `str` and `repr`. + - Added new Console methods which are independent from the console defaults. Changed - You can now give default values or an array when initializing a `tcod.console.Console` instance. @@ -22,6 +23,9 @@ Deprecated `Console.blit` instead. - `Console.clear` should be given the colors to clear with as parameters, rather than by using `default_fg` or `default_bg`. + - Most functions which depend on console default values have been deprecated. + The new deprecation warnings will give details on how to make default values + explicit. Fixed - `tcod.console.Console.blit` was ignoring the key color set by `Console.set_key_color`. diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 07bb6146..190e9440 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -93,7 +93,6 @@ def __init__(self): def on_enter(self): tcod.sys_set_fps(0) - sample_console.clear() def on_draw(self): self.slide_corner_colors() @@ -142,14 +141,14 @@ def randomize_sample_conole(self): def print_banner(self): # print text on top of samples - sample_console.default_bg = tcod.grey - sample_console.print_rect( + sample_console.print_box( x=sample_console.width // 2, y=5, width=sample_console.width - 2, height=sample_console.height - 1, string="The Doryen library uses 24 bits colors, for both " "background and foreground.", + bg=tcod.grey, bg_blend=tcod.BKGND_MULTIPLY, alignment=tcod.CENTER, ) @@ -170,25 +169,24 @@ def __init__(self): self.xdir = 1 self.ydir = 1 - self.secondary.print_frame( + self.secondary.draw_frame( 0, 0, sample_console.width // 2, sample_console.height // 2, "Offscreen console", False, - tcod.BKGND_NONE, ) - self.secondary.print_rect( + self.secondary.print_box( sample_console.width // 4, 2, sample_console.width // 2 - 2, sample_console.height // 2, "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", - tcod.BKGND_NONE, - tcod.CENTER, + bg=None, + alignment=tcod.CENTER, ) def on_enter(self): @@ -268,7 +266,6 @@ def ev_keydown(self, event: tcod.event.KeyDown): def on_enter(self): tcod.sys_set_fps(0) - sample_console.default_fg = tcod.white def on_draw(self): alpha = 0.0 @@ -317,8 +314,11 @@ def on_draw(self): tcod.console_set_char_background( sample_console, x, y, tcod.light_blue, self.bk_flag ) - sample_console.print_( - 2, 2, "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF] + sample_console.print( + 2, + 2, + "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF], + bg=None, ) @@ -372,7 +372,6 @@ def on_enter(self): tcod.sys_set_fps(0) def on_draw(self): - sample_console.clear() self.dx = time.perf_counter() * 0.25 self.dy = time.perf_counter() * 0.25 for y in range(2 * sample_console.height): @@ -385,40 +384,45 @@ def on_draw(self): c = int((value + 1.0) / 2.0 * 255) c = max(0, min(c, 255)) self.img.put_pixel(x, y, (c // 2, c // 2, c)) - sample_console.default_bg = tcod.grey rectw = 24 recth = 13 if self.implementation == tcod.noise.SIMPLE: recth = 10 self.img.blit_2x(sample_console, 0, 0) - sample_console.default_bg = tcod.grey - sample_console.rect(2, 2, rectw, recth, False, tcod.BKGND_MULTIPLY) + sample_console.draw_rect( + 2, + 2, + rectw, + recth, + ch=0, + fg=None, + bg=tcod.grey, + bg_blend=tcod.BKGND_MULTIPLY, + ) sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = ( - sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] - * sample_console.default_bg - / 255 + sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * tcod.grey / 255 ) for curfunc in range(len(self.NOISE_OPTIONS)): text = "%i : %s" % (curfunc + 1, self.NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: - sample_console.default_fg = tcod.white - sample_console.default_bg = tcod.light_blue - sample_console.print_( - 2, 2 + curfunc, text, tcod.BKGND_SET, tcod.LEFT + sample_console.print( + 2, 2 + curfunc, text, fg=tcod.white, bg=tcod.light_blue ) else: - sample_console.default_fg = tcod.grey - sample_console.print_(2, 2 + curfunc, text) - sample_console.default_fg = tcod.white - sample_console.print_(2, 11, "Y/H : zoom (%2.1f)" % self.zoom) + sample_console.print( + 2, 2 + curfunc, text, fg=tcod.grey, bg=None + ) + sample_console.print(2, 11, "Y/H : zoom (%2.1f)" % self.zoom, bg=None) if self.implementation != tcod.noise.SIMPLE: - sample_console.print_(2, 12, "E/D : hurst (%2.1f)" % self.hurst) - sample_console.print_( - 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity + sample_console.print( + 2, 12, "E/D : hurst (%2.1f)" % self.hurst, bg=None ) - sample_console.print_( - 2, 14, "T/G : octaves (%2.1f)" % self.octaves + sample_console.print( + 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity, bg=None + ) + sample_console.print( + 2, 14, "T/G : octaves (%2.1f)" % self.octaves, bg=None ) def ev_keydown(self, event: tcod.event.KeyDown): @@ -536,9 +540,7 @@ def __init__(self): self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL def draw_ui(self): - tcod.console_set_default_foreground(sample_console, tcod.white) - tcod.console_print( - sample_console, + sample_console.print( 1, 1, "IJKL : move around\n" @@ -550,8 +552,8 @@ def draw_ui(self): "on " if self.light_walls else "off", FOV_ALGO_NAMES[self.algo_num], ), + bg=None, ) - tcod.console_set_default_foreground(sample_console, tcod.black) def on_enter(self): tcod.sys_set_fps(60) @@ -559,7 +561,7 @@ def on_enter(self): # during the player movement, only the @ is redrawn. # the rest impacts only the background color # draw the help text & player @ - tcod.console_clear(sample_console) + sample_console.clear() self.draw_ui() tcod.console_put_char( sample_console, self.px, self.py, "@", tcod.BKGND_NONE @@ -686,21 +688,18 @@ def on_enter(self): # during the player movement, only the @ is redrawn. # the rest impacts only the background color # draw the help text & player @ - tcod.console_clear(sample_console) - tcod.console_set_default_foreground(sample_console, tcod.white) - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) - tcod.console_put_char( - sample_console, self.px, self.py, "@", tcod.BKGND_NONE - ) - tcod.console_print( - sample_console, + sample_console.clear() + sample_console.ch[self.dx, self.dy] = ord("+") + sample_console.fg[self.dx, self.dy] = tcod.white + sample_console.ch[self.px, self.py] = ord("@") + sample_console.fg[self.px, self.py] = tcod.white + sample_console.print( 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", + bg=None, ) - tcod.console_print(sample_console, 1, 4, "Using : A*") + sample_console.print(1, 4, "Using : A*", bg=None) # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -1030,11 +1029,10 @@ def bsp_refresh(self): def on_draw(self): sample_console.clear() - sample_console.default_fg = tcod.white rooms = "OFF" if bsp_random_room: rooms = "ON" - sample_console.print_( + sample_console.print( 1, 1, "ENTER : rebuild bsp\n" @@ -1042,6 +1040,7 @@ def on_draw(self): "+-: bsp depth %d\n" "*/: room size %d\n" "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), + bg=None, ) if bsp_random_room: walls = "OFF" @@ -1098,31 +1097,26 @@ def on_enter(self): tcod.sys_set_fps(0) def on_draw(self): - sample_console.default_bg = tcod.black sample_console.clear() x = sample_console.width / 2 + math.cos(time.time()) * 10.0 y = sample_console.height / 2 scalex = 0.2 + 1.8 * (1.0 + math.cos(time.time() / 2)) / 2.0 scaley = scalex angle = time.perf_counter() - if int(time.time() / 2): + if int(time.time()) % 2: # split the color channels of circle.png # the red channel - sample_console.default_bg = (255, 0, 0) - - sample_console.rect(0, 3, 15, 15, False, tcod.BKGND_SET) + sample_console.draw_rect(0, 3, 15, 15, 0, None, (255, 0, 0)) self.circle.blit_rect( sample_console, 0, 3, -1, -1, tcod.BKGND_MULTIPLY ) # the green channel - sample_console.default_bg = (0, 255, 0) - sample_console.rect(15, 3, 15, 15, False, tcod.BKGND_SET) + sample_console.draw_rect(15, 3, 15, 15, 0, None, (0, 255, 0)) self.circle.blit_rect( sample_console, 15, 3, -1, -1, tcod.BKGND_MULTIPLY ) # the blue channel - sample_console.default_bg = (0, 0, 255) - sample_console.rect(30, 3, 15, 15, False, tcod.BKGND_SET) + sample_console.draw_rect(30, 3, 15, 15, 0, None, (0, 0, 255)) self.circle.blit_rect( sample_console, 30, 3, -1, -1, tcod.BKGND_MULTIPLY ) @@ -1149,8 +1143,6 @@ def __init__(self): self.log = [] def on_enter(self): - sample_console.default_bg = tcod.grey - sample_console.default_fg = tcod.light_yellow tcod.mouse_move(320, 200) tcod.mouse_show_cursor(True) tcod.sys_set_fps(60) @@ -1175,8 +1167,8 @@ def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp): self.rbut = False def on_draw(self): - sample_console.clear() - sample_console.print_( + sample_console.clear(bg=tcod.grey) + sample_console.print( 1, 1, "Mouse position : %4dx%4d\n" @@ -1196,8 +1188,16 @@ def on_draw(self): ("OFF", "ON")[self.rbut], ("OFF", "ON")[self.mbut], ), + fg=tcod.light_yellow, + bg=None, + ) + sample_console.print( + 1, + 10, + "1 : Hide cursor\n2 : Show cursor", + fg=tcod.light_yellow, + bg=None, ) - sample_console.print_(1, 10, "1 : Hide cursor\n2 : Show cursor") def ev_keydown(self, event: tcod.event.KeyDown): if event.sym == ord("1"): @@ -1233,21 +1233,21 @@ def on_draw(self): self.nbsets = len(self.sets) while len(self.names) > 15: self.names.pop(0) - sample_console.clear() - sample_console.default_fg = tcod.white - sample_console.print_( + sample_console.clear(bg=tcod.grey) + sample_console.print( 1, 1, "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.curset], + bg=None, ) for i in range(len(self.names)): - sample_console.print_( + sample_console.print( SAMPLE_SCREEN_WIDTH - 2, 2 + i, self.names[i], - tcod.BKGND_NONE, - tcod.RIGHT, + bg=None, + alignment=tcod.RIGHT, ) self.delay += tcod.sys_get_last_frame_length() if self.delay > 0.5: @@ -1324,8 +1324,7 @@ def on_enter(self): global frac_t, abs_t, lights, tex_r, tex_g, tex_b tcod.sys_set_fps(0) sample_console.clear() # render status message - sample_console.default_fg = tcod.white - sample_console.print_(1, SCREEN_H - 3, "Renderer: NumPy") + sample_console.print(1, SCREEN_H - 3, "Renderer: NumPy", bg=None) # time is represented in number of pixels of the texture, start later # in time to initialize texture @@ -1497,7 +1496,7 @@ def main(): FONT, tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD ) root_console = tcod.console_init_root( - 80, 50, "tcod python samples", False, order="F" + 80, 50, "python-tcod samples", False, tcod.RENDERER_SDL2, order="F" ) credits_end = False SAMPLES[cur_sample].on_enter() @@ -1505,8 +1504,6 @@ def main(): draw_renderer_menu() while not tcod.console_is_window_closed(): - root_console.default_fg = (255, 255, 255) - root_console.default_bg = (0, 0, 0) root_console.clear() draw_samples_menu() draw_renderer_menu() @@ -1530,61 +1527,59 @@ def handle_events(): def draw_samples_menu(): for i, sample in enumerate(SAMPLES): if i == cur_sample: - root_console.default_fg = tcod.white - root_console.default_bg = tcod.light_blue + fg = tcod.white + bg = tcod.light_blue else: - root_console.default_fg = tcod.grey - root_console.default_bg = tcod.black - root_console.print_( + fg = tcod.grey + bg = tcod.black + root_console.print( 2, 46 - (len(SAMPLES) - i), " %s" % sample.name.ljust(19), - tcod.BKGND_SET, - tcod.LEFT, + fg, + bg, + alignment=tcod.LEFT, ) def draw_stats(): - root_console.default_fg = tcod.grey - root_console.print_( + root_console.print( 79, 46, " last frame : %3d ms (%3d fps)" % (tcod.sys_get_last_frame_length() * 1000.0, tcod.sys_get_fps()), - tcod.BKGND_NONE, - tcod.RIGHT, + fg=tcod.grey, + bg=None, + alignment=tcod.RIGHT, ) - root_console.print_( + root_console.print( 79, 47, "elapsed : %8d ms %4.2fs" % (time.perf_counter() * 1000, time.perf_counter()), - tcod.BKGND_NONE, - tcod.RIGHT, + fg=tcod.grey, + bg=None, + alignment=tcod.RIGHT, ) def draw_renderer_menu(): current_renderer = tcod.sys_get_renderer() - root_console.default_fg = tcod.grey - root_console.default_bg = tcod.black - root_console.print_( + root_console.print( 42, 46 - (tcod.NB_RENDERERS + 1), "Renderer :", - tcod.BKGND_SET, - tcod.LEFT, + fg=tcod.grey, + bg=tcod.black, ) for i, name in enumerate(RENDERER_NAMES): if i == current_renderer: - root_console.default_fg = tcod.white - root_console.default_bg = tcod.light_blue + fg = tcod.white + bg = tcod.light_blue else: - root_console.default_fg = tcod.grey - root_console.default_bg = tcod.black - root_console.print_( - 42, 46 - (tcod.NB_RENDERERS - i), name, tcod.BKGND_SET, tcod.LEFT - ) + fg = tcod.grey + bg = tcod.black + root_console.print(42, 46 - tcod.NB_RENDERERS + i, name, fg, bg) if __name__ == "__main__": diff --git a/tcod/console.py b/tcod/console.py index 8a78fab1..45b9ab01 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -58,14 +58,6 @@ class Console: `buffer` should be compatible with the `width`, `height`, and `order` given; and should also have a dtype compatible with :any:`Console.DTYPE`. - `copy` is a placeholder. In the future this will determine if the - `buffer` is copied or used as-is. So set it to None or True as - appropriate. - - `default_bg`, `default_bg`, `default_bg_blend`, and `default_alignment` are - the default values used in some methods. The `default_bg` and `default_bg` - will affect the starting foreground and background color of the console. - .. versionchanged:: 4.3 Added `order` parameter. @@ -107,16 +99,9 @@ def __init__( height: int, order: str = "C", buffer: Optional[np.array] = None, - copy: Optional[bool] = None, - default_bg: Tuple[int, int, int] = (0, 0, 0), - default_fg: Tuple[int, int, int] = (255, 255, 255), - default_bg_blend: Optional[int] = None, - default_alignment: Optional[int] = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] self._order = tcod._internal.verify_order(order) - if copy is not None and not copy: - raise ValueError("copy=False is not supported in this version.") if buffer is not None: if self._order == "F": buffer = buffer.transpose() @@ -129,14 +114,11 @@ def __init__( self._bg = np.ndarray((height, width), dtype="(3,)u1") # libtcod uses the root console for defaults. - if default_bg_blend is None: - default_bg_blend = 0 - if lib.TCOD_ctx.root != ffi.NULL: - default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag - if default_alignment is None: - default_alignment = 0 - if lib.TCOD_ctx.root != ffi.NULL: - default_alignment = lib.TCOD_ctx.root.alignment + default_bg_blend = 0 + default_alignment = 0 + if lib.TCOD_ctx.root != ffi.NULL: + default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag + default_alignment = lib.TCOD_ctx.root.alignment self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", @@ -148,13 +130,13 @@ def __init__( "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, - "fore": default_fg, - "back": default_bg, + "fore": (255, 255, 255), + "back": (0, 0, 0), }, ) if buffer is None: - self.clear(fg=default_fg, bg=default_bg) + self.clear() @classmethod def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": @@ -262,7 +244,8 @@ def default_bg(self) -> Tuple[int, int, int]: color = self._console_data.back return color.r, color.g, color.b - @default_bg.setter + @default_bg.setter # type: ignore + @deprecate("Console defaults have been deprecated.") def default_bg(self, color: Tuple[int, int, int]) -> None: self._console_data.back = color @@ -272,7 +255,8 @@ def default_fg(self) -> Tuple[int, int, int]: color = self._console_data.fore return color.r, color.g, color.b - @default_fg.setter + @default_fg.setter # type: ignore + @deprecate("Console defaults have been deprecated.") def default_fg(self, color: Tuple[int, int, int]) -> None: self._console_data.fore = color @@ -281,7 +265,8 @@ def default_bg_blend(self) -> int: """int: The default blending mode.""" return self._console_data.bkgnd_flag # type: ignore - @default_bg_blend.setter + @default_bg_blend.setter # type: ignore + @deprecate("Console defaults have been deprecated.") def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @@ -290,7 +275,8 @@ def default_alignment(self) -> int: """int: The default text alignment.""" return self._console_data.alignment # type: ignore - @default_alignment.setter + @default_alignment.setter # type: ignore + @deprecate("Console defaults have been deprecated.") def default_alignment(self, value: int) -> None: self._console_data.alignment = value @@ -355,6 +341,84 @@ def put_char( """ lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend) + __ALIGNMENT_LOOKUP = {0: "tcod.LEFT", 1: "tcod.RIGHT", 2: "tcod.CENTER"} + + __BG_BLEND_LOOKUP = { + 0: "tcod.BKGND_NONE", + 1: "tcod.BKGND_SET", + 2: "tcod.BKGND_MULTIPLY", + 3: "tcod.BKGND_LIGHTEN", + 4: "tcod.BKGND_DARKEN", + 5: "tcod.BKGND_SCREEN", + 6: "tcod.BKGND_COLOR_DODGE", + 7: "tcod.BKGND_COLOR_BURN", + 8: "tcod.BKGND_ADD", + 9: "tcod.BKGND_ADDA", + 10: "tcod.BKGND_BURN", + 11: "tcod.BKGND_OVERLAY", + 12: "tcod.BKGND_ALPH", + 13: "tcod.BKGND_DEFAULT", + } + + def __deprecate_defaults( + self, + new_func: str, + bg_blend: Any, + alignment: Any = ..., + clear: Any = ..., + ) -> None: + """Return the parameters needed to recreate the current default state. + """ + fg = None # type: Any + bg = None # type: Any + fg = self.default_fg if self.default_fg != (255, 255, 255) else None + bg = self.default_bg if self.default_bg != (0, 0, 0) else None + if bg_blend == tcod.constants.BKGND_NONE: + bg = "None" + if bg_blend == tcod.constants.BKGND_DEFAULT: + bg_blend = self.default_bg_blend + else: + bg_blend = None + if bg_blend == tcod.constants.BKGND_NONE: + bg = "None" + bg_blend = None + if bg_blend == tcod.constants.BKGND_SET: + bg_blend = None + if alignment is None: + alignment = self.default_alignment + if alignment == tcod.constants.LEFT: + alignment = None + else: + alignment = None + if clear is not ...: + fg = "None" + params = [] + if clear is True: + params.append('ch=ord(" ")') + if clear is False: + params.append("ch=0") + if fg is not None: + params.append("fg=%s" % (fg,)) + if bg is not None: + params.append("bg=%s" % (bg,)) + if bg_blend is not None: + params.append("bg_blend=%s" % (self.__BG_BLEND_LOOKUP[bg_blend],)) + if alignment is not None: + params.append( + "alignment=%s" % (self.__ALIGNMENT_LOOKUP[alignment],) + ) + param_str = ", ".join(params) + if not param_str: + param_str = "." + else: + param_str = " and add the following parameters:\n%s" % (param_str,) + warnings.warn( + "Console functions using default values have been deprecated.\n" + "Replace this method with `Console.%s`%s" % (new_func, param_str), + DeprecationWarning, + stacklevel=3, + ) + def print_( self, x: int, @@ -371,7 +435,14 @@ def print_( string (str): A Unicode string optionally using color codes. bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT. alignment (Optional[int]): Text alignment. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.print` instead, calling this function will print + a warning detailing which default values need to be made explicit. """ + self.__deprecate_defaults("print", bg_blend, alignment) alignment = self.default_alignment if alignment is None else alignment lib.TCOD_console_printf_ex( self.console_c, x, y, bg_blend, alignment, _fmt(string) @@ -404,7 +475,15 @@ def print_rect( Returns: int: The number of lines of text once word-wrapped. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.print_box` instead, calling this function will + print a warning detailing which default values need to be made + explicit. """ + self.__deprecate_defaults("print_box", bg_blend, alignment) alignment = self.default_alignment if alignment is None else alignment return int( lib.TCOD_console_printf_rect_ex( @@ -462,7 +541,15 @@ def rect( clear (bool): If True all text in the affected area will be removed. bg_blend (int): Background blending flag. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.draw_rect` instead, calling this function will + print a warning detailing which default values need to be made + explicit. """ + self.__deprecate_defaults("draw_rect", bg_blend, clear=bool(clear)) lib.TCOD_console_rect( self.console_c, x, y, width, height, clear, bg_blend ) @@ -483,7 +570,15 @@ def hline( y (int): The y coordinate from the top. width (int): The horizontal length of this line. bg_blend (int): The background blending flag. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.draw_rect` instead, calling this function will + print a warning detailing which default values need to be made + explicit. """ + self.__deprecate_defaults("draw_rect", bg_blend) lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend) def vline( @@ -502,7 +597,15 @@ def vline( y (int): The y coordinate from the top. height (int): The horozontal length of this line. bg_blend (int): The background blending flag. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.draw_rect` instead, calling this function will + print a warning detailing which default values need to be made + explicit. """ + self.__deprecate_defaults("draw_rect", bg_blend) lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) def print_frame( @@ -535,7 +638,15 @@ def print_frame( .. versionchanged:: 8.2 Now supports Unicode strings. + + .. deprecated:: 8.5 + Console methods which depend on console defaults have been + deprecated. + Use :any:`Console.draw_frame` instead, calling this function will + print a warning detailing which default values need to be made + explicit. """ + self.__deprecate_defaults("draw_frame", bg_blend) string = _fmt(string) if string else ffi.NULL lib.TCOD_console_printf_frame( self.console_c, x, y, width, height, clear, bg_blend, string @@ -707,18 +818,8 @@ def __repr__(self) -> str: buffer["bg"] = self.bg return ( "tcod.console.Console(width=%i, height=%i, " - "order=%r,buffer=\n%r,\ndefault_bg=%r, default_fg=%r, " - "default_bg_blend=%s, default_alignment=%s)" - % ( - self.width, - self.height, - self._order, - buffer, - self.default_bg, - self.default_fg, - self.default_bg_blend, - self.default_alignment, - ) + "order=%r,buffer=\n%r)" + % (self.width, self.height, self._order, buffer) ) def __str__(self) -> str: @@ -746,10 +847,22 @@ def print( ) -> None: """Print a string on a console with manual line breaks. - `x` and `y` are the starting tile, with ``0,0`` as the upper left - corner of the console. + `x` and `y` are the starting tile, with ``0,0`` as the upper-left + corner of the console. You can use negative numbers if you want to + start printing relative to the bottom-right corner. - `string` is a Unicode string. + `string` is a Unicode string which may include color control + characters. Strings which are too long will be truncated until the + next newline character ``"\n"``. + + `fg` and `bg` are the foreground text color and background tile color + respectfully. This is a 3-item tuple with (r, g, b) color values from + 0 to 255. These parameters can also be set to `None` to leave the + colors unchanged. + + `bg_blend` is the blend type used by libtcod. + + `alignment` can be `tcod.LEFT`, `tcod.CENTER`, or `tcod.RIGHT`. .. versionadded:: 8.5 """ @@ -779,7 +892,28 @@ def print_box( bg_blend: int = tcod.constants.BKGND_DEFAULT, alignment: int = tcod.constants.LEFT, ) -> int: - """Print a string constrained to a rectangle. + """Print a string constrained to a rectangle and return the height. + + `x` and `y` are the starting tile, with ``0,0`` as the upper-left + corner of the console. You can use negative numbers if you want to + start printing relative to the bottom-right corner. + + `width` and `height` determine the bounds of the rectangle, the text + will automatically be broken to fit within these bounds. + + `string` is a Unicode string which may include color control + characters. + + `fg` and `bg` are the foreground text color and background tile color + respectfully. This is a 3-item tuple with (r, g, b) color values from + 0 to 255. These parameters can also be set to `None` to leave the + colors unchanged. + + `bg_blend` is the blend type used by libtcod. + + `alignment` can be `tcod.LEFT`, `tcod.CENTER`, or `tcod.RIGHT`. + + Returns the actual height of the printed area. .. versionadded:: 8.5 """ @@ -808,12 +942,31 @@ def draw_frame( width: int, height: int, title: str = "", + clear: bool = True, fg: Optional[Tuple[int, int, int]] = (255, 255, 255), bg: Optional[Tuple[int, int, int]] = (0, 0, 0), bg_blend: int = tcod.constants.BKGND_SET, - clear: bool = True, ) -> None: """Draw a framed rectangle with an optional title. + + `x` and `y` are the starting tile, with ``0,0`` as the upper-left + corner of the console. You can use negative numbers if you want to + start printing relative to the bottom-right corner. + + `width` and `height` determine the size of the frame. + + `title` is a Unicode string. + + If `clear` is True than the region inside of the frame will be cleared. + + `fg` and `bg` are the foreground text color and background tile color + respectfully. This is a 3-item tuple with (r, g, b) color values from + 0 to 255. These parameters can also be set to `None` to leave the + colors unchanged. + + `bg_blend` is the blend type used by libtcod. + + .. versionadded:: 8.5 """ x, y = self._pythonic_index(x, y) title_ = title.encode("utf-8") # type: bytes @@ -844,6 +997,22 @@ def draw_rect( ) -> None: """Draw characters and colors over a rectangular region. + `x` and `y` are the starting tile, with ``0,0`` as the upper-left + corner of the console. You can use negative numbers if you want to + start printing relative to the bottom-right corner. + + `width` and `height` determine the size of the rectangle. + + `ch` is a Unicode integer. You can use 0 to leave the current + characters unchanged. + + `fg` and `bg` are the foreground text color and background tile color + respectfully. This is a 3-item tuple with (r, g, b) color values from + 0 to 255. These parameters can also be set to `None` to leave the + colors unchanged. + + `bg_blend` is the blend type used by libtcod. + .. versionadded:: 8.5 """ x, y = self._pythonic_index(x, y) diff --git a/tests/test_console.py b/tests/test_console.py index 9a60aa95..3ec9d656 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -38,19 +38,9 @@ def test_array_read_write(): assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG +@pytest.mark.filterwarnings("ignore:.") def test_console_defaults(): - console = tcod.console.Console( - width=12, - height=10, - default_bg=(3, 6, 9), - default_fg=(12, 15, 18), - default_bg_blend=tcod.BKGND_MULTIPLY, - default_alignment=tcod.LEFT, - ) - assert console.default_bg == (3, 6, 9) - assert console.default_fg == (12, 15, 18) - assert console.default_bg_blend == tcod.BKGND_MULTIPLY - assert console.default_alignment == tcod.LEFT + console = tcod.console.Console(width=12, height=10) console.default_bg = [2, 3, 4] assert console.default_bg == (2, 3, 4) @@ -123,11 +113,9 @@ def test_console_fortran_buffer(): def test_console_clear(): - console = tcod.console.Console( - 1, 1, default_fg=(1, 2, 3), default_bg=(4, 5, 6) - ) - assert console.fg[0, 0].tolist() == [1, 2, 3] - assert console.bg[0, 0].tolist() == [4, 5, 6] + console = tcod.console.Console(1, 1) + assert console.fg[0, 0].tolist() == [255, 255, 255] + assert console.bg[0, 0].tolist() == [0, 0, 0] console.clear(fg=(7, 8, 9), bg=(10, 11, 12)) assert console.fg[0, 0].tolist() == [7, 8, 9] assert console.bg[0, 0].tolist() == [10, 11, 12] From 7418513ab09bfb868898f85a57479e08ce5f3cc7 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 Feb 2019 11:54:09 -0800 Subject: [PATCH 0086/1101] Prepare 8.5.0 release. --- CHANGELOG.rst | 6 ++++++ libtcod | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cc7c5ed5..9029bb9f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,10 +9,15 @@ v2.0.0 Unreleased ------------------ + +8.5.0 - 2019-02-15 +------------------ Added - `tcod.console.Console` now supports `str` and `repr`. - Added new Console methods which are independent from the console defaults. Changed + - Updated libtcod to 1.10.6 + - Printing generates more compact layouts. - You can now give default values or an array when initializing a `tcod.console.Console` instance. - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. @@ -29,6 +34,7 @@ Deprecated Fixed - `tcod.console.Console.blit` was ignoring the key color set by `Console.set_key_color`. + - The `SDL2` and `OPENGL2` renders can now large numbers of tiles. 8.4.3 - 2019-02-06 ------------------ diff --git a/libtcod b/libtcod index 3598e725..c2a4c341 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 3598e725ec826b945fd7f7b88014035ebdb367dd +Subproject commit c2a4c341ef2439e4391475e249e265c42cf69644 From fecfe22e103cc9664e093af1ef9e19ebcd8f1f7e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 Feb 2019 13:09:33 -0800 Subject: [PATCH 0087/1101] Update changelog. --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9029bb9f..6709ba18 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,8 +18,8 @@ Added Changed - Updated libtcod to 1.10.6 - Printing generates more compact layouts. - - You can now give default values or an array when initializing a - `tcod.console.Console` instance. + - You can now give an array when initializing a `tcod.console.Console` + instance. - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. Deprecated - Most libtcodpy console functions have been replaced by the tcod.console From ac5fc96a408b5d70cdd175fdee54e146f4cd62f8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 16 Feb 2019 10:09:33 -0800 Subject: [PATCH 0088/1101] Escape new-line character in the documentation. --- tcod/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index 45b9ab01..84a35518 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -853,7 +853,7 @@ def print( `string` is a Unicode string which may include color control characters. Strings which are too long will be truncated until the - next newline character ``"\n"``. + next newline character ``"\\n"``. `fg` and `bg` are the foreground text color and background tile color respectfully. This is a 3-item tuple with (r, g, b) color values from From 80d7ef50edf3dd22f2eab03fbf175f46a3e5f43f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 16 Feb 2019 12:52:47 -0800 Subject: [PATCH 0089/1101] New console methods now default to None. Resolves #65 --- CHANGELOG.rst | 8 +++++--- tcod/console.py | 40 +++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6709ba18..bdbbf125 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,18 +9,20 @@ v2.0.0 Unreleased ------------------ +Changed +- New console methods now default to an `fg` and `bg` of None. 8.5.0 - 2019-02-15 ------------------ Added - `tcod.console.Console` now supports `str` and `repr`. - Added new Console methods which are independent from the console defaults. -Changed - - Updated libtcod to 1.10.6 - - Printing generates more compact layouts. - You can now give an array when initializing a `tcod.console.Console` instance. - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. +Changed + - Updated libtcod to 1.10.6 + - Printing generates more compact layouts. Deprecated - Most libtcodpy console functions have been replaced by the tcod.console module. diff --git a/tcod/console.py b/tcod/console.py index 84a35518..39cb6380 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -369,18 +369,16 @@ def __deprecate_defaults( ) -> None: """Return the parameters needed to recreate the current default state. """ - fg = None # type: Any - bg = None # type: Any - fg = self.default_fg if self.default_fg != (255, 255, 255) else None - bg = self.default_bg if self.default_bg != (0, 0, 0) else None + fg = self.default_fg # type: Any + bg = self.default_bg # type: Any if bg_blend == tcod.constants.BKGND_NONE: - bg = "None" + bg = None if bg_blend == tcod.constants.BKGND_DEFAULT: bg_blend = self.default_bg_blend else: bg_blend = None if bg_blend == tcod.constants.BKGND_NONE: - bg = "None" + bg = None bg_blend = None if bg_blend == tcod.constants.BKGND_SET: bg_blend = None @@ -391,7 +389,7 @@ def __deprecate_defaults( else: alignment = None if clear is not ...: - fg = "None" + fg = None params = [] if clear is True: params.append('ch=ord(" ")') @@ -840,8 +838,8 @@ def print( x: int, y: int, string: str, - fg: Optional[Tuple[int, int, int]] = (255, 255, 255), - bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, ) -> None: @@ -865,6 +863,9 @@ def print( `alignment` can be `tcod.LEFT`, `tcod.CENTER`, or `tcod.RIGHT`. .. versionadded:: 8.5 + + .. versionchanged:: 9.0 + `fg` and `bg` now default to `None`. """ x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes @@ -887,8 +888,8 @@ def print_box( width: int, height: int, string: str, - fg: Optional[Tuple[int, int, int]] = (255, 255, 255), - bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, bg_blend: int = tcod.constants.BKGND_DEFAULT, alignment: int = tcod.constants.LEFT, ) -> int: @@ -916,6 +917,9 @@ def print_box( Returns the actual height of the printed area. .. versionadded:: 8.5 + + .. versionchanged:: 9.0 + `fg` and `bg` now default to `None`. """ x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes @@ -943,8 +947,8 @@ def draw_frame( height: int, title: str = "", clear: bool = True, - fg: Optional[Tuple[int, int, int]] = (255, 255, 255), - bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, bg_blend: int = tcod.constants.BKGND_SET, ) -> None: """Draw a framed rectangle with an optional title. @@ -967,6 +971,9 @@ def draw_frame( `bg_blend` is the blend type used by libtcod. .. versionadded:: 8.5 + + .. versionchanged:: 9.0 + `fg` and `bg` now default to `None`. """ x, y = self._pythonic_index(x, y) title_ = title.encode("utf-8") # type: bytes @@ -991,8 +998,8 @@ def draw_rect( width: int, height: int, ch: int, - fg: Optional[Tuple[int, int, int]] = (255, 255, 255), - bg: Optional[Tuple[int, int, int]] = (0, 0, 0), + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, bg_blend: int = tcod.constants.BKGND_SET, ) -> None: """Draw characters and colors over a rectangular region. @@ -1014,6 +1021,9 @@ def draw_rect( `bg_blend` is the blend type used by libtcod. .. versionadded:: 8.5 + + .. versionchanged:: 9.0 + `fg` and `bg` now default to `None`. """ x, y = self._pythonic_index(x, y) lib.draw_rect( From af66f6a45b5b6873cfc9287d9bc61fa6b2d70443 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 17 Feb 2019 01:04:26 -0800 Subject: [PATCH 0090/1101] Update changed method parameters in samples. --- CHANGELOG.rst | 3 ++- examples/samples_tcod.py | 42 +++++++++++++++++++++++++++++++++------- tcod/console.py | 8 ++++---- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bdbbf125..558e08b9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,8 @@ v2.0.0 Unreleased ------------------ Changed -- New console methods now default to an `fg` and `bg` of None. +- New console methods now default to an `fg` and `bg` of None instead of + white-on-black. 8.5.0 - 2019-02-15 ------------------ diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 190e9440..04de0fef 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -148,6 +148,7 @@ def print_banner(self): height=sample_console.height - 1, string="The Doryen library uses 24 bits colors, for both " "background and foreground.", + fg=tcod.white, bg=tcod.grey, bg_blend=tcod.BKGND_MULTIPLY, alignment=tcod.CENTER, @@ -176,6 +177,8 @@ def __init__(self): sample_console.height // 2, "Offscreen console", False, + fg=tcod.white, + bg=tcod.black, ) self.secondary.print_box( @@ -185,6 +188,7 @@ def __init__(self): sample_console.height // 2, "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", + fg=tcod.white, bg=None, alignment=tcod.CENTER, ) @@ -318,6 +322,7 @@ def on_draw(self): 2, 2, "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF], + fg=tcod.white, bg=None, ) @@ -413,16 +418,30 @@ def on_draw(self): sample_console.print( 2, 2 + curfunc, text, fg=tcod.grey, bg=None ) - sample_console.print(2, 11, "Y/H : zoom (%2.1f)" % self.zoom, bg=None) + sample_console.print( + 2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=tcod.white, bg=None + ) if self.implementation != tcod.noise.SIMPLE: sample_console.print( - 2, 12, "E/D : hurst (%2.1f)" % self.hurst, bg=None + 2, + 12, + "E/D : hurst (%2.1f)" % self.hurst, + fg=tcod.white, + bg=None, ) sample_console.print( - 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity, bg=None + 2, + 13, + "R/F : lacunarity (%2.1f)" % self.lacunarity, + fg=tcod.white, + bg=None, ) sample_console.print( - 2, 14, "T/G : octaves (%2.1f)" % self.octaves, bg=None + 2, + 14, + "T/G : octaves (%2.1f)" % self.octaves, + fg=tcod.white, + bg=None, ) def ev_keydown(self, event: tcod.event.KeyDown): @@ -552,6 +571,7 @@ def draw_ui(self): "on " if self.light_walls else "off", FOV_ALGO_NAMES[self.algo_num], ), + fg=tcod.white, bg=None, ) @@ -697,9 +717,10 @@ def on_enter(self): 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", + fg=tcod.white, bg=None, ) - sample_console.print(1, 4, "Using : A*", bg=None) + sample_console.print(1, 4, "Using : A*", fg=tcod.white, bg=None) # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -1040,13 +1061,16 @@ def on_draw(self): "+-: bsp depth %d\n" "*/: room size %d\n" "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), + fg=tcod.white, bg=None, ) if bsp_random_room: walls = "OFF" if bsp_room_walls: walls = "ON" - sample_console.print_(1, 6, "2 : room walls %s" % walls) + sample_console.print( + 1, 6, "2 : room walls %s" % walls, fg=tcod.white, bg=None + ) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -1239,6 +1263,7 @@ def on_draw(self): 1, "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.curset], + fg=tcod.white, bg=None, ) for i in range(len(self.names)): @@ -1246,6 +1271,7 @@ def on_draw(self): SAMPLE_SCREEN_WIDTH - 2, 2 + i, self.names[i], + fg=tcod.white, bg=None, alignment=tcod.RIGHT, ) @@ -1324,7 +1350,9 @@ def on_enter(self): global frac_t, abs_t, lights, tex_r, tex_g, tex_b tcod.sys_set_fps(0) sample_console.clear() # render status message - sample_console.print(1, SCREEN_H - 3, "Renderer: NumPy", bg=None) + sample_console.print( + 1, SCREEN_H - 3, "Renderer: NumPy", fg=tcod.white, bg=None + ) # time is represented in number of pixels of the texture, start later # in time to initialize texture diff --git a/tcod/console.py b/tcod/console.py index 39cb6380..b61ca43d 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -865,7 +865,7 @@ def print( .. versionadded:: 8.5 .. versionchanged:: 9.0 - `fg` and `bg` now default to `None`. + `fg` and `bg` now default to `None` instead of white-on-black. """ x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes @@ -919,7 +919,7 @@ def print_box( .. versionadded:: 8.5 .. versionchanged:: 9.0 - `fg` and `bg` now default to `None`. + `fg` and `bg` now default to `None` instead of white-on-black. """ x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes @@ -973,7 +973,7 @@ def draw_frame( .. versionadded:: 8.5 .. versionchanged:: 9.0 - `fg` and `bg` now default to `None`. + `fg` and `bg` now default to `None` instead of white-on-black. """ x, y = self._pythonic_index(x, y) title_ = title.encode("utf-8") # type: bytes @@ -1023,7 +1023,7 @@ def draw_rect( .. versionadded:: 8.5 .. versionchanged:: 9.0 - `fg` and `bg` now default to `None`. + `fg` and `bg` now default to `None` instead of white-on-black. """ x, y = self._pythonic_index(x, y) lib.draw_rect( From b182f0d85914a5035750bc27120fcfe8d301bd66 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 17 Feb 2019 07:14:41 -0800 Subject: [PATCH 0091/1101] Deploy before uploading coverage data. Try to fix PyPy3 issue with pyOpenSSL. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf22c3da..425ba029 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,10 +69,11 @@ before_script: script: - pytest -v after_success: +- if [[ -n "$TRAVIS_TAG" ]]; then pip install --upgrade twine; fi +- if [[ -n "$TRAVIS_TAG" && -z "$PYPY3_VERSION" ]]; then pip install --upgrade pyOpenSSL; fi +- if [[ -n "$TRAVIS_TAG" && "$TRAVIS_OS_NAME" == "osx" ]]; then twine upload --skip-existing dist/*; fi - pip install codacy-coverage python-coveralls codecov - codecov - coveralls - coverage xml - if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml; fi -- if [[ -n "$TRAVIS_TAG" ]]; then pip install --upgrade twine pyOpenSSL; fi -- if [[ -n "$TRAVIS_TAG" && "$TRAVIS_OS_NAME" == "osx" ]]; then twine upload --skip-existing dist/*; fi From e8811b091d5a8240e7e86adcdb77796722d6c3f0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 17 Feb 2019 12:54:23 -0800 Subject: [PATCH 0092/1101] Prepare 9.0.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 558e08b9..260e12a5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.0.0 - 2019-02-17 +------------------ Changed - New console methods now default to an `fg` and `bg` of None instead of white-on-black. From 1da6b8f678d23795fd189ead32028d9b05a9bed2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 Feb 2019 18:53:02 -0800 Subject: [PATCH 0093/1101] Reduce warning overhead on optimized Python scripts. --- CHANGELOG.rst | 3 +++ tcod/_internal.py | 3 +++ tcod/console.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 260e12a5..c5f603c1 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ +Changed +- The overhead for warnings has been reduced when running Python with the + optimize `-O` flag. 9.0.0 - 2019-02-17 ------------------ diff --git a/tcod/_internal.py b/tcod/_internal.py index 44b905f5..edae7287 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -10,6 +10,9 @@ def deprecate( message: str, category: Any = DeprecationWarning, stacklevel: int = 0 ) -> Callable[[F], F]: def decorator(func: F) -> F: + if not __debug__: + return func + @functools.wraps(func) def wrapper(*args, **kargs): # type: ignore warnings.warn(message, category, stacklevel=stacklevel + 2) diff --git a/tcod/console.py b/tcod/console.py index b61ca43d..d227ae32 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -369,6 +369,9 @@ def __deprecate_defaults( ) -> None: """Return the parameters needed to recreate the current default state. """ + if not __debug__: + return + fg = self.default_fg # type: Any bg = self.default_bg # type: Any if bg_blend == tcod.constants.BKGND_NONE: From 0b457d055026c200c8b4869776b7a250e4404392 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 23 Feb 2019 04:16:59 -0800 Subject: [PATCH 0094/1101] Update random module constants and documentation. --- CHANGELOG.rst | 3 +++ tcod/random.py | 30 +++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5f603c1..8a35ef41 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,9 +9,12 @@ v2.0.0 Unreleased ------------------ +Added +- Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. Changed - The overhead for warnings has been reduced when running Python with the optimize `-O` flag. +- `tcod.random.Random` now provides a default algorithm. 9.0.0 - 2019-02-17 ------------------ diff --git a/tcod/random.py b/tcod/random.py index 11b0e3ef..1b3fef09 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -1,5 +1,9 @@ """ - Random module docs. +Usually it's recommend to the Python's standard library `random` module +instead of this one. + +However, you will need to use these generators to get deterministic results +from the :any:`Noise` and :any:`BSP` classes. """ import random from typing import Any, Hashable, Optional @@ -9,26 +13,34 @@ MERSENNE_TWISTER = tcod.constants.RNG_MT COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC +MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC class Random(object): """The libtcod random number generator. - If all you need is a random number generator then it's recommended - that you use the :any:`random` module from the Python standard library. + `algorithm` defaults to Mersenne Twister, it can be one of: - If ``seed`` is None then a random seed will be generated. + * tcod.random.MERSENNE_TWISTER + * tcod.random.MULTIPLY_WITH_CARRY - Args: - algorithm (int): The algorithm to use. - seed (Optional[Hashable]): - Could be a 32-bit integer, but any hashable object is accepted. + `seed` is a 32-bit number or any Python hashable object like a string. + Using the same seed will cause the generator to return deterministic + values. The default `seed` of None will generate a random seed instead. Attributes: random_c (CData): A cffi pointer to a TCOD_random_t object. + + .. versionchanged:: 9.1 + Added `tcod.random.MULTIPLY_WITH_CARRY` constant. + `algorithm` parameter now defaults to `tcod.random.MERSENNE_TWISTER`. """ - def __init__(self, algorithm: int, seed: Optional[Hashable] = None): + def __init__( + self, + algorithm: int = MERSENNE_TWISTER, + seed: Optional[Hashable] = None, + ): """Create a new instance using this algorithm and seed.""" if seed is None: seed = random.getrandbits(32) From 011b61de8ba9c67929c8821d53a612924c676369 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 23 Feb 2019 04:41:51 -0800 Subject: [PATCH 0095/1101] Prepare 9.1.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8a35ef41..130d61e2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.1.0 - 2019-02-23 +------------------ Added - Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. Changed From cced9810d161e294eb2ca64ee53ee18f647e92ce Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 11:36:05 -0800 Subject: [PATCH 0096/1101] Switch builder back to using libtcod's static sources. --- build_libtcod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index e157e3eb..d1c05678 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -95,10 +95,10 @@ def unpack_sdl2(version): sources += walk_sources("tcod/") sources += walk_sources("tdl/") -sources += walk_sources("libtcod/src/libtcod") +sources += ["libtcod/src/libtcod_c.c"] +sources += ["libtcod/src/libtcod.cpp"] sources += ["libtcod/src/vendor/glad.c"] sources += ["libtcod/src/vendor/lodepng.cpp"] -sources += ["libtcod/src/vendor/stb.c"] sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] sources += glob.glob("libtcod/src/vendor/zlib/*.c") From b7d15a01b56a1cc561c7f5633db68a94bcd9c824 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 12:39:38 -0800 Subject: [PATCH 0097/1101] Update submodule, export new get_height_rect function. Fixes #66 Resolves #67 --- CHANGELOG.rst | 7 +++++++ examples/samples_tcod.py | 4 ++-- libtcod | 2 +- tcod/console.py | 13 +++++++++++++ tcod/tcod.cpp | 7 +++++++ tcod/tcod.h | 4 ++++ 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 130d61e2..f2683613 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,13 @@ v2.0.0 Unreleased ------------------ +Added +- New `tcod.console.get_height_rect` function, which can be used to get the + height of a print call without an existing console. +Fixed +- The new print methods now handle alignment according to how they were + documented. +- `SDL2` and `OPENGL2` now support screenshots. 9.1.0 - 2019-02-23 ------------------ diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 04de0fef..92264b21 100644 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -142,7 +142,7 @@ def randomize_sample_conole(self): def print_banner(self): # print text on top of samples sample_console.print_box( - x=sample_console.width // 2, + x=1, y=5, width=sample_console.width - 2, height=sample_console.height - 1, @@ -182,7 +182,7 @@ def __init__(self): ) self.secondary.print_box( - sample_console.width // 4, + 1, 2, sample_console.width // 2 - 2, sample_console.height // 2, diff --git a/libtcod b/libtcod index c2a4c341..a72d2d04 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit c2a4c341ef2439e4391475e249e265c42cf69644 +Subproject commit a72d2d0423f121b9f2c3e0d8d410e87e5836120b diff --git a/tcod/console.py b/tcod/console.py index d227ae32..594c8721 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1040,3 +1040,16 @@ def draw_rect( (bg,) if bg is not None else ffi.NULL, bg_blend, ) + + +def get_height_rect(width: int, string: str) -> int: + """Return the number of lines which would be printed from these parameters. + + `width` is the width of the print boundary. + + `string` is a Unicode string which may include color control characters. + + .. versionadded:: 9.2 + """ + string_ = string.encode("utf-8") # type: bytes + return int(lib.get_height_rect2(width, string_, len(string_))) diff --git a/tcod/tcod.cpp b/tcod/tcod.cpp index df62c130..b2be7a09 100644 --- a/tcod/tcod.cpp +++ b/tcod/tcod.cpp @@ -80,6 +80,13 @@ int get_height_rect( return tcod::console::get_height_rect(console, x, y, width, height, std::string(str, str_n)); } +int get_height_rect2( + int width, + const char* str, + int str_n) +{ + return tcod::console::get_height_rect(width, std::string(str, str_n)); +} void print_frame( TCOD_Console* console, int x, diff --git a/tcod/tcod.h b/tcod/tcod.h index 831e462b..4946a8c9 100644 --- a/tcod/tcod.h +++ b/tcod/tcod.h @@ -50,6 +50,10 @@ int get_height_rect( int height, const char* str, int str_n); +int get_height_rect2( + int width, + const char* str, + int str_n); void print_frame( TCOD_Console *con, int x, From 48c64398be080f80d7f57001fe76061b20afcc01 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 13:03:30 -0800 Subject: [PATCH 0098/1101] Parse and bundle different versions of SDL. --- CHANGELOG.rst | 3 +++ build_libtcod.py | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2683613..5c40b116 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,9 @@ Fixed - The new print methods now handle alignment according to how they were documented. - `SDL2` and `OPENGL2` now support screenshots. +- Windows and MacOS builds now restrict exported SDL2 symbols to only + SDL 2.0.5; This will avoid hard to debug import errors when the wrong + version of SDL is dynamically linked. 9.1.0 - 2019-02-23 ------------------ diff --git a/build_libtcod.py b/build_libtcod.py index d1c05678..0ceb4fac 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -20,7 +20,11 @@ except ImportError: from urllib.request import urlretrieve -SDL2_VERSION = os.environ.get("SDL_VERSION", "2.0.9") +# The SDL2 version to parse and export symbols from. +SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") +# The SDL2 version to include in binary distributions. +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.9") + TDL_NO_SDL2_EXPORTS = os.environ.get("TDL_NO_SDL2_EXPORTS", "0") == "1" CFFI_HEADER = "tcod/cffi.h" @@ -122,13 +126,15 @@ def unpack_sdl2(version): # included SDL headers are for whatever OS's don't easily come with them if sys.platform in ["win32", "darwin"]: - SDL2_PATH = unpack_sdl2(SDL2_VERSION) + SDL2_PARSE_PATH = unpack_sdl2(SDL2_PARSE_VERSION) + SDL2_BUNDLE_PATH = unpack_sdl2(SDL2_BUNDLE_VERSION) include_dirs.append("libtcod/src/zlib/") if sys.platform == "win32": - include_dirs.append(os.path.join(SDL2_PATH, "include")) + include_dirs.append(os.path.join(SDL2_PARSE_PATH, "include")) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = os.path.join(SDL2_PATH, "lib/", ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DIR = os.path.join(SDL2_BUNDLE_PATH, "lib/", + ARCH_MAPPING[BITSIZE]) library_dirs.append(SDL2_LIB_DIR) SDL2_LIB_DEST = os.path.join("tcod", ARCH_MAPPING[BITSIZE]) if not os.path.exists(SDL2_LIB_DEST): @@ -152,12 +158,12 @@ def fix_header(filepath): if sys.platform == "darwin": - HEADER_DIR = os.path.join(SDL2_PATH, "Headers") + HEADER_DIR = os.path.join(SDL2_PARSE_PATH, "Headers") fix_header(os.path.join(HEADER_DIR, "SDL_assert.h")) fix_header(os.path.join(HEADER_DIR, "SDL_config_macosx.h")) include_dirs.append(HEADER_DIR) - extra_link_args += ["-F%s/.." % SDL2_PATH] - extra_link_args += ["-rpath", "%s/.." % SDL2_PATH] + extra_link_args += ["-F%s/.." % SDL2_BUNDLE_PATH] + extra_link_args += ["-rpath", "%s/.." % SDL2_BUNDLE_PATH] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] if sys.platform not in ["win32", "darwin"]: @@ -256,9 +262,9 @@ def get_cdef(): def get_ast(): global extra_parse_args if "win32" in sys.platform: - extra_parse_args += [r"-I%s/include" % SDL2_PATH] + extra_parse_args += [r"-I%s/include" % SDL2_PARSE_PATH] if "darwin" in sys.platform: - extra_parse_args += [r"-I%s/Headers" % SDL2_PATH] + extra_parse_args += [r"-I%s/Headers" % SDL2_PARSE_PATH] ast = parse_file( filename=CFFI_HEADER, From c3e8fae8440d83a293af0d8ef4319f18eb73f065 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 13:15:42 -0800 Subject: [PATCH 0099/1101] Update submodule. Removes a poor use of constexpr. --- libtcod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtcod b/libtcod index a72d2d04..52ac7b46 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit a72d2d0423f121b9f2c3e0d8d410e87e5836120b +Subproject commit 52ac7b465bca13564c679b17b7c2c2eb27f16eb8 From 7eefe2546613288f56e9d03f6c5992b4ab1be93b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 14:57:16 -0800 Subject: [PATCH 0100/1101] Add basic TrueType font support. --- CHANGELOG.rst | 1 + docs/index.rst | 1 + docs/tcod/tileset.rst | 5 +++++ tcod/cffi.h | 1 + tcod/tileset.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 36 insertions(+) create mode 100644 docs/tcod/tileset.rst create mode 100644 tcod/tileset.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5c40b116..0426cb58 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Unreleased Added - New `tcod.console.get_height_rect` function, which can be used to get the height of a print call without an existing console. +- New `tcod.tileset` module, with a `set_truetype_font` function. Fixed - The new print methods now handle alignment according to how they were documented. diff --git a/docs/index.rst b/docs/index.rst index 5efcec75..dbecfc83 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,7 @@ Contents: tcod tcod/event + tcod/tileset libtcodpy .. toctree:: diff --git a/docs/tcod/tileset.rst b/docs/tcod/tileset.rst new file mode 100644 index 00000000..f05de8c6 --- /dev/null +++ b/docs/tcod/tileset.rst @@ -0,0 +1,5 @@ +tcod.tileset +============ + +.. automodule:: tcod.tileset + :members: diff --git a/tcod/cffi.h b/tcod/cffi.h index 4d0443e9..923a8797 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -11,6 +11,7 @@ #include "../libtcod/src/libtcod/libtcod.h" #include "../libtcod/src/libtcod/libtcod_int.h" #include "../libtcod/src/libtcod/wrappers.h" +#include "../libtcod/src/libtcod/tileset/truetype.h" #include "noise.h" #include "path.h" diff --git a/tcod/tileset.py b/tcod/tileset.py new file mode 100644 index 00000000..fb055a70 --- /dev/null +++ b/tcod/tileset.py @@ -0,0 +1,28 @@ +"""Tileset and font related functions. +""" +import os + +from tcod.libtcod import lib + + +def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: + """Set the default tileset from a `.ttf` or `.otf` file. + + `path` is the file path for the font file. + + `tile_width` and `tile_height` are the desired size of the tiles in the new + tileset. The font will be scaled to fit the `tile_height` and may be + clipped to fit inside of the `tile_width`. + + This function will only affect the `SDL2` and `OPENGL2` renderers. + + This function must be called before :any:`tcod.console_init_root`. Once + the root console is setup you may call this funtion again to change the + font. The tileset can be changed but the window will not be resized + automatically. + + .. versionadded:: 9.2 + """ + if not os.path.exists(path): + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) + lib.TCOD_tileset_load_truetype_(path.encode(), tile_width, tile_height) From 6022fa5d7e39a83449f5dcfe6419b2a82e4ee745 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 15:35:55 -0800 Subject: [PATCH 0101/1101] Change default renderer to SDL2, clear the root console on start. --- CHANGELOG.rst | 1 + tcod/libtcodpy.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0426cb58..5a01cbf3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,7 @@ Fixed - Windows and MacOS builds now restrict exported SDL2 symbols to only SDL 2.0.5; This will avoid hard to debug import errors when the wrong version of SDL is dynamically linked. +- The root console now starts with a white foreground. 9.1.0 - 2019-02-23 ------------------ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 67ebd91e..a30e5766 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -35,7 +35,6 @@ FOV_PERMISSIVE_0, NOISE_DEFAULT, KEY_RELEASED, - RENDERER_GLSL, ) from tcod._internal import deprecate @@ -894,9 +893,11 @@ def console_init_root( # Use the scripts filename as the title. title = os.path.basename(sys.argv[0]) if renderer is None: - renderer = RENDERER_GLSL # Stable for now. + renderer = tcod.constants.RENDERER_SDL2 lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer) - return tcod.console.Console._get_root(order) + console = tcod.console.Console._get_root(order) + console.clear() + return console def console_set_custom_font( From 1d0840628946128218706d484c01f11f5fa2a92b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 24 Feb 2019 15:37:15 -0800 Subject: [PATCH 0102/1101] Prepare 9.2.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5a01cbf3..3a900cdd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.2.0 - 2019-02-24 +------------------ Added - New `tcod.console.get_height_rect` function, which can be used to get the height of a print call without an existing console. From 91e1daea75fbb21dff898bb67a0c7ef2d26cf7e3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 25 Feb 2019 14:26:36 -0800 Subject: [PATCH 0103/1101] Update libtcod. Fixes #68 --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3a900cdd..aa1643e4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed +- `tcod.sys_get_char_size` fixed on the new renderers. 9.2.0 - 2019-02-24 ------------------ diff --git a/libtcod b/libtcod index 52ac7b46..471eaf5b 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 52ac7b465bca13564c679b17b7c2c2eb27f16eb8 +Subproject commit 471eaf5b02122dd59bf901cc2350a1f1f9427dbd From 7a63e5f9f4d98533643053f76b529f42ab9af60d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 25 Feb 2019 14:28:07 -0800 Subject: [PATCH 0104/1101] Prepare 9.2.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa1643e4..de8504ee 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.2.1 - 2019-02-25 +------------------ Fixed - `tcod.sys_get_char_size` fixed on the new renderers. From 528da4dc4a269e9aa4b4c1d6569610c5d3d28798 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 26 Feb 2019 09:18:36 -0800 Subject: [PATCH 0105/1101] Add debug flags to all gcc builds. --- build_libtcod.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build_libtcod.py b/build_libtcod.py index 0ceb4fac..f3d9a9d5 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -298,7 +298,10 @@ def get_ast(): MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-"]} MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} -GCC_CFLAGS = {"DEBUG": ["-O0"], "RELEASE": ["-flto", "-O3", "-fPIC"]} +GCC_CFLAGS = { + "DEBUG": ["-Og", "-g", "-fPIC"], + "RELEASE": ["-flto", "-O3", "-g", "-fPIC"], +} if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv: extra_compile_args.extend(MSVC_CFLAGS[tdl_build]) From 87351fc02d9c31b74c5ce4aad2ce9f43ba613a71 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 26 Feb 2019 09:37:21 -0800 Subject: [PATCH 0106/1101] Mark scripts as Python 3 executables. --- examples/eventget_tcod.py | 2 +- examples/eventget_tdl.py | 2 +- examples/samples_libtcodpy.py | 0 examples/samples_tcod.py | 0 examples/sdlevent.py | 0 examples/thread_jobs.py | 2 +- setup.py | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 examples/eventget_tcod.py mode change 100644 => 100755 examples/samples_libtcodpy.py mode change 100644 => 100755 examples/samples_tcod.py mode change 100644 => 100755 examples/sdlevent.py mode change 100644 => 100755 examples/thread_jobs.py diff --git a/examples/eventget_tcod.py b/examples/eventget_tcod.py old mode 100644 new mode 100755 index 3e31dd7d..5934bd78 --- a/examples/eventget_tcod.py +++ b/examples/eventget_tcod.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import tcod diff --git a/examples/eventget_tdl.py b/examples/eventget_tdl.py index 4941c5de..66d8dfb9 100755 --- a/examples/eventget_tdl.py +++ b/examples/eventget_tdl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ An interactive example of what events are available. """ diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py old mode 100644 new mode 100755 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py old mode 100644 new mode 100755 diff --git a/examples/sdlevent.py b/examples/sdlevent.py old mode 100644 new mode 100755 diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py old mode 100644 new mode 100755 index 9afd123c..24131a9a --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/setup.py b/setup.py index d5368f7e..30a651ed 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys From 6246c0055ac08f12bd7ffc6c844d5f723ee8c45e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 26 Feb 2019 11:27:58 -0800 Subject: [PATCH 0107/1101] Fix Console.print_box not setting background colors. Fixes #69 --- CHANGELOG.rst | 2 ++ tcod/console.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de8504ee..0526a963 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed +- `Console.print_box` wasn't setting the background color by default. 9.2.1 - 2019-02-25 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 594c8721..38f1818c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -893,7 +893,7 @@ def print_box( string: str, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None, - bg_blend: int = tcod.constants.BKGND_DEFAULT, + bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, ) -> int: """Print a string constrained to a rectangle and return the height. From 727437c4a5b4bd29db81bfe308e039857275098a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 26 Feb 2019 11:58:46 -0800 Subject: [PATCH 0108/1101] Prepare 9.2.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0526a963..b0e8dfb3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.2.2 - 2019-02-26 +------------------ Fixed - `Console.print_box` wasn't setting the background color by default. From 8a69ba3ed8c18468eacbfcac6fb714746bf5095f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Mar 2019 09:23:11 -0800 Subject: [PATCH 0109/1101] Note how negative indexing may change in the future. --- CHANGELOG.rst | 3 +++ tcod/console.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b0e8dfb3..42dcd66d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ +Deprecated + - The behavior for negative indexes on the new print functions may change in + the future. 9.2.2 - 2019-02-26 ------------------ diff --git a/tcod/console.py b/tcod/console.py index 38f1818c..e9e1e388 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -830,6 +830,12 @@ def __str__(self) -> str: ) def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: + if __debug__ and (x < 0 or y < 0): + warnings.warn( + "How negative indexes are handled my change in the future.", + PendingDeprecationWarning, + stacklevel=3, + ) if x < 0: x += self.width if y < 0: @@ -850,7 +856,8 @@ def print( `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner. + start printing relative to the bottom-right corner, but this behavior + may change in future versions. `string` is a Unicode string which may include color control characters. Strings which are too long will be truncated until the @@ -900,7 +907,8 @@ def print_box( `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner. + start printing relative to the bottom-right corner, but this behavior + may change in future versions. `width` and `height` determine the bounds of the rectangle, the text will automatically be broken to fit within these bounds. @@ -958,7 +966,8 @@ def draw_frame( `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner. + start printing relative to the bottom-right corner, but this behavior + may change in future versions. `width` and `height` determine the size of the frame. @@ -1009,7 +1018,8 @@ def draw_rect( `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner. + start printing relative to the bottom-right corner, but this behavior + may change in future versions. `width` and `height` determine the size of the rectangle. From e5f02d4d68022f6d57f713c9e2b485785a0ec24c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Mar 2019 10:27:31 -0800 Subject: [PATCH 0110/1101] Deprecate tcod.Color features which prevent it from being a tuple. --- CHANGELOG.rst | 2 ++ tcod/color.py | 62 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 42dcd66d..4dee9577 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,8 @@ Unreleased Deprecated - The behavior for negative indexes on the new print functions may change in the future. + - Methods and functionality preventing `tcod.Color` from behaving like a tuple + have been deprecated. 9.2.2 - 2019-02-26 ------------------ diff --git a/tcod/color.py b/tcod/color.py index edb4003f..ceb886f9 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -2,7 +2,9 @@ """ from typing import Any, List +import warnings +from tcod._internal import deprecate from tcod.libtcod import lib @@ -20,28 +22,43 @@ def __init__(self, r: int = 0, g: int = 0, b: int = 0): @property def r(self) -> int: - """int: Red value, always normalised to 0-255.""" + """int: Red value, always normalised to 0-255. + + .. deprecated:: 9.2 + Color attributes will not be mutable in the future. + """ return self[0] - @r.setter + @r.setter # type: ignore + @deprecate("Setting color attributes has been deprecated.") def r(self, value: int) -> None: self[0] = value & 0xFF @property def g(self) -> int: - """int: Green value, always normalised to 0-255.""" + """int: Green value, always normalised to 0-255. + + .. deprecated:: 9.2 + Color attributes will not be mutable in the future. + """ return self[1] - @g.setter + @g.setter # type: ignore + @deprecate("Setting color attributes has been deprecated.") def g(self, value: int) -> None: self[1] = value & 0xFF @property def b(self) -> int: - """int: Blue value, always normalised to 0-255.""" + """int: Blue value, always normalised to 0-255. + + .. deprecated:: 9.2 + Color attributes will not be mutable in the future. + """ return self[2] - @b.setter + @b.setter # type: ignore + @deprecate("Setting color attributes has been deprecated.") def b(self, value: int) -> None: self[2] = value & 0xFF @@ -51,11 +68,21 @@ def _new_from_cdata(cls, cdata: Any) -> "Color": return cls(cdata.r, cdata.g, cdata.b) def __getitem__(self, index: Any) -> int: # type: ignore + """ + .. deprecated:: 9.2 + Accessing colors via a letter index is deprecated. + """ try: - return super().__getitem__(index) # type: ignore + return super().__getitem__(index) except TypeError: + warnings.warn( + "Accessing colors via a letter index is deprecated", + DeprecationWarning, + stacklevel=2, + ) return super().__getitem__("rgb".index(index)) + @deprecate("This class will not be mutable in the future.") def __setitem__(self, index: Any, value: Any) -> None: try: super().__setitem__(index, value) @@ -72,16 +99,31 @@ def __eq__(self, other: Any) -> bool: except TypeError: return False + @deprecate("Use NumPy instead for color math operations.") def __add__(self, other: Any) -> "Color": - """Add two colors together.""" + """Add two colors together. + + .. deprecated:: 9.2 + Use NumPy instead for color math operations. + """ return Color._new_from_cdata(lib.TCOD_color_add(self, other)) + @deprecate("Use NumPy instead for color math operations.") def __sub__(self, other: Any) -> "Color": - """Subtract one color from another.""" + """Subtract one color from another. + + .. deprecated:: 9.2 + Use NumPy instead for color math operations. + """ return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) + @deprecate("Use NumPy instead for color math operations.") def __mul__(self, other: Any) -> "Color": - """Multiply with a scaler or another color.""" + """Multiply with a scaler or another color. + + .. deprecated:: 9.2 + Use NumPy instead for color math operations. + """ if isinstance(other, (Color, list, tuple)): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) else: From 863f4c9529eeb678b830638421f768e657a9f20f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Mar 2019 11:30:33 -0800 Subject: [PATCH 0111/1101] Ignore Codacy coverage issues. --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 425ba029..bb5e6c5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,4 +76,4 @@ after_success: - codecov - coveralls - coverage xml -- if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml; fi +- if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml || true; fi diff --git a/appveyor.yml b/appveyor.yml index 4308ba08..893e711b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,7 +51,7 @@ test_script: on_success: - pip install codacy-coverage python-coveralls - coverage xml -- if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml +- if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml || cd . deploy_script: - "if defined APPVEYOR_REPO_TAG_NAME pip install twine" From f093b0815698fc652d8199e5327e7e502b15b760 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 Mar 2019 14:14:39 -0800 Subject: [PATCH 0112/1101] Prepare 9.2.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4dee9577..ac38a22e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.2.3 - 2019-03-01 +------------------ Deprecated - The behavior for negative indexes on the new print functions may change in the future. From 4317827961b3ad156ad2c579c68800e84739cebf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Mar 2019 10:17:36 -0800 Subject: [PATCH 0113/1101] Mark tcod as a typed package. --- CHANGELOG.rst | 2 ++ setup.py | 1 + tcod/py.typed | 0 3 files changed, 3 insertions(+) create mode 100644 tcod/py.typed diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ac38a22e..870bfae0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Added + - The `tcod` package is now compatible with mypy. 9.2.3 - 2019-03-01 ------------------ diff --git a/setup.py b/setup.py index 30a651ed..675fd717 100755 --- a/setup.py +++ b/setup.py @@ -60,6 +60,7 @@ def get_package_data(): """get data files which will be included in the main tcod/ directory""" BITSIZE, LINKAGE = platform.architecture() files = [ + "py.typed", "lib/LIBTCOD-CREDITS.txt", "lib/LIBTCOD-LICENSE.txt", "lib/README-SDL.txt", diff --git a/tcod/py.typed b/tcod/py.typed new file mode 100644 index 00000000..e69de29b From 57b21402b54c5a61dda5f5b92256e987dd3b6aa9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 Mar 2019 20:07:46 -0800 Subject: [PATCH 0114/1101] Prepare 9.2.4 release. --- CHANGELOG.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 870bfae0..72d4df52 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,8 +9,11 @@ v2.0.0 Unreleased ------------------ -Added - - The `tcod` package is now compatible with mypy. + +9.2.4 - 2019-03-02 +------------------ +Fixed + - The `tcod` package is has been marked as typed and will now work with MyPy. 9.2.3 - 2019-03-01 ------------------ From d7745ef1fbc7471a9c3b72b2a3ba14f0f824d11f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 3 Mar 2019 19:06:04 -0800 Subject: [PATCH 0115/1101] Clean up license and update copyright year. --- LICENSE.txt | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index a81b877c..7665b7fb 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,6 @@ -Copyright (c) 2009-2018, Kyle Stewart +BSD 2-Clause License + +Copyright (c) 2009-2019, Kyle Stewart and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -6,21 +8,18 @@ modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 9f6c655d2de9b835a6fc5ff6254f0555a568bf7a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 Mar 2019 11:03:56 -0800 Subject: [PATCH 0116/1101] Fix tcod.namegen_generate_custom function. Was calling the wrong libtcod function. Fixes #70 --- CHANGELOG.rst | 2 ++ tcod/libtcodpy.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 72d4df52..3dc25ef0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed `tcod.namegen_generate_custom`. 9.2.4 - 2019-03-02 ------------------ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a30e5766..d58f6d33 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3180,7 +3180,7 @@ def namegen_generate(name: str) -> str: def namegen_generate_custom(name: str, rule: str) -> str: return _unpack_char_p( - lib.TCOD_namegen_generate(_bytes(name), _bytes(rule), False) + lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False) ) From d5c9631b323dcabd8bd46050155a3b2fc30d11b9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 Mar 2019 11:15:50 -0800 Subject: [PATCH 0117/1101] Prepare 9.2.5 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3dc25ef0..fa18a352 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ + +9.2.5 - 2019-03-04 +------------------ Fixed - Fixed `tcod.namegen_generate_custom`. From 694079ea54929a73e4b1f730851d362dcee212c4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 7 Mar 2019 09:15:45 -0800 Subject: [PATCH 0118/1101] Cache the RGB dtype. --- tcod/console.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index e9e1e388..8b90658e 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -92,6 +92,7 @@ class Console: """ DTYPE = [("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")] # type: Any + _DTYPE_RGB = np.dtype("(3,)u1") # type: Any def __init__( self, @@ -110,8 +111,8 @@ def __init__( self._bg = np.ascontiguousarray(buffer["bg"], "u1") else: self._ch = np.ndarray((height, width), dtype=np.intc) - self._fg = np.ndarray((height, width), dtype="(3,)u1") - self._bg = np.ndarray((height, width), dtype="(3,)u1") + self._fg = np.ndarray((height, width), dtype=self._DTYPE_RGB) + self._bg = np.ndarray((height, width), dtype=self._DTYPE_RGB) # libtcod uses the root console for defaults. default_bg_blend = 0 From cb362c21c642133232a10b9440fb12d02f9abadf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Mar 2019 15:41:43 -0700 Subject: [PATCH 0119/1101] Update setup and import error messages. --- setup.py | 7 +++++-- tcod/libtcod.py | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 675fd717..03967a31 100755 --- a/setup.py +++ b/setup.py @@ -87,8 +87,11 @@ def get_long_description(): if sys.version_info < (3, 5): error = """ - python-tcod supports Python 3.5 and above. - The last version supporting Python 2.7/3.4 was 'tcod==6.0.7' + This version of python-tcod only supports Python 3.5 and above. + The last version supporting Python 2.7/3.4 was 'tcod==6.0.7'. + + The end-of-life for Python 2 is the year 2020. + https://pythonclock.org/ Python {py} detected. """.format( diff --git a/tcod/libtcod.py b/tcod/libtcod.py index 8f68017a..e9a6473c 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -8,11 +8,17 @@ from tcod import __path__ # type: ignore + +def get_architecture() -> str: + """Return the Windows architecture, one of "x86" or "x64".""" + return "x86" if platform.architecture()[0] == "32bit" else "x64" + + if sys.platform == "win32": # add Windows dll's to PATH _bits, _linkage = platform.architecture() os.environ["PATH"] = "%s;%s" % ( - os.path.join(__path__[0], "x86" if _bits == "32bit" else "x64"), + os.path.join(__path__[0], get_architecture()), os.environ["PATH"], ) @@ -48,6 +54,19 @@ def __str__(self) -> Any: # Allows an import without building the cffi module first. lib = ffi = _Mock() else: - from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 + try: + from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 + except ImportError as exc: + if "The specified module could not be found." in exc.args[0]: + print( + "You may need to install 'vc_redist.{arch}.exe'" + " from Microsoft at:\n" + "https://support.microsoft.com/en-us/help/2977003/" + "the-latest-supported-visual-c-downloads\n".format( + arch=get_architecture() + ), + file=sys.stderr, + ) + raise __all__ = ["ffi", "lib"] From e8ce18290c9691012d5b62c1a1342604c3ae4024 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Mar 2019 15:44:48 -0700 Subject: [PATCH 0120/1101] Set renderers in some examples. --- examples/eventget_tcod.py | 3 ++- examples/sdlevent.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/eventget_tcod.py b/examples/eventget_tcod.py index 5934bd78..5f2c4a5c 100755 --- a/examples/eventget_tcod.py +++ b/examples/eventget_tcod.py @@ -7,7 +7,8 @@ key = tcod.Key() mouse = tcod.Mouse() -with tcod.console_init_root(WIDTH, HEIGHT, 'tcod events example') as console: +with tcod.console_init_root(WIDTH, HEIGHT, 'tcod events example', + renderer=tcod.RENDERER_SDL) as console: tcod.sys_set_fps(24) while not tcod.console_is_window_closed(): ev = tcod.sys_wait_for_event(tcod.EVENT_ANY, key, mouse, False) diff --git a/examples/sdlevent.py b/examples/sdlevent.py index 112a7c5c..cfbf3f11 100755 --- a/examples/sdlevent.py +++ b/examples/sdlevent.py @@ -9,7 +9,8 @@ def main(): WIDTH, HEIGHT = 120, 60 TITLE = None - with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order="F") as console: + with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order="F", + renderer=tcod.RENDERER_SDL) as console: tcod.sys_set_fps(24) while True: tcod.console_flush() From db9fa00650198058568a884a2bfca7c6ac7bdc35 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Mar 2019 15:45:51 -0700 Subject: [PATCH 0121/1101] Update documentation. Fix changelog formatting. Add installation instructions. Update Sphinx configuration. --- CHANGELOG.rst | 41 +++++++++++----------- docs/conf.py | 12 +++---- docs/index.rst | 8 ++--- docs/installation.rst | 82 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 docs/installation.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa18a352..8ee90519 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,7 @@ =========== Changelog =========== -Changes relevant for users of the the tdl and tcod packages are documented -here. +Changes relevant to the users of python-tcod are documented here. This project adheres to `Semantic Versioning `_ since v2.0.0 @@ -31,42 +30,42 @@ Deprecated 9.2.2 - 2019-02-26 ------------------ Fixed -- `Console.print_box` wasn't setting the background color by default. + - `Console.print_box` wasn't setting the background color by default. 9.2.1 - 2019-02-25 ------------------ Fixed -- `tcod.sys_get_char_size` fixed on the new renderers. + - `tcod.sys_get_char_size` fixed on the new renderers. 9.2.0 - 2019-02-24 ------------------ Added -- New `tcod.console.get_height_rect` function, which can be used to get the - height of a print call without an existing console. -- New `tcod.tileset` module, with a `set_truetype_font` function. -Fixed -- The new print methods now handle alignment according to how they were - documented. -- `SDL2` and `OPENGL2` now support screenshots. -- Windows and MacOS builds now restrict exported SDL2 symbols to only - SDL 2.0.5; This will avoid hard to debug import errors when the wrong - version of SDL is dynamically linked. -- The root console now starts with a white foreground. + - New `tcod.console.get_height_rect` function, which can be used to get the + height of a print call without an existing console. + - New `tcod.tileset` module, with a `set_truetype_font` function. +Fixed + - The new print methods now handle alignment according to how they were + documented. + - `SDL2` and `OPENGL2` now support screenshots. + - Windows and MacOS builds now restrict exported SDL2 symbols to only + SDL 2.0.5; This will avoid hard to debug import errors when the wrong + version of SDL is dynamically linked. + - The root console now starts with a white foreground. 9.1.0 - 2019-02-23 ------------------ Added -- Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. + - Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. Changed -- The overhead for warnings has been reduced when running Python with the - optimize `-O` flag. -- `tcod.random.Random` now provides a default algorithm. + - The overhead for warnings has been reduced when running Python with the + optimize `-O` flag. + - `tcod.random.Random` now provides a default algorithm. 9.0.0 - 2019-02-17 ------------------ Changed -- New console methods now default to an `fg` and `bg` of None instead of - white-on-black. + - New console methods now default to an `fg` and `bg` of None instead of + white-on-black. 8.5.0 - 2019-02-15 ------------------ diff --git a/docs/conf.py b/docs/conf.py index 0db27d4b..96f2c81c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,7 +55,7 @@ master_doc = 'index' # General information about the project. -project = u'python-tcod/tdl' +project = u'python-tcod' copyright = u'2009-2018, Kyle Stewart' author = u'Kyle Stewart' @@ -247,7 +247,7 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'tdldoc' +htmlhelp_basename = 'tcoddoc' # -- Options for LaTeX output --------------------------------------------- @@ -273,7 +273,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'tdl.tex', u'tdl Documentation', + (master_doc, 'python-tcod.tex', u'python-tcod Documentation', u'Kyle Stewart', 'manual'), ] @@ -315,7 +315,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'tdl', u'tdl Documentation', + (master_doc, 'python-tcod', u'python-tcod Documentation', [author], 1) ] @@ -330,8 +330,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'tdl', u'tdl Documentation', - author, 'tdl', 'One line description of project.', + (master_doc, 'python-tcod', u'python-tcod Documentation', + author, 'python-tcod', 'libtcod for Python.', 'Miscellaneous'), ] diff --git a/docs/index.rst b/docs/index.rst index dbecfc83..c48bcc18 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,10 +1,10 @@ -.. tdl documentation master file, created by +.. tcod documentation master file, created by sphinx-quickstart on Fri Nov 25 12:49:46 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to the python-tcod/tdl documentation! -============================================== +Welcome to the python-tcod documentation! +========================================= .. module:: tcod @@ -16,7 +16,7 @@ Contents: :maxdepth: 2 :caption: Readme - readme + installation changelog glossary diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 00000000..2b4d94a6 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,82 @@ +Installation +============ +Python 3.5 or above is required. + +The recommended way to install is by using pip. Older versions of pip will +have issues installing tcod, so make sure it's up-to-date. + +Windows +------- +`First install a recent version of Python 3. +`_ +Make sure Python is added to the Windows `PATH`. + +If you don't already have it, then install the latest +`Microsoft Visual C++ Redistributable +`_. +`vc_redist.x86.exe` for a 32-bit install of Python, or `vc_redist.x64.exe` for +a 64-bit install. You'll need to keep this in mind when distributing any +libtcod program to end-users. + +Then to install python-tcod run the following from a Windows command line:: + + py -m pip install tcod + +MacOS +----- +The latest version of python-tcod only supports MacOS 10.9 (Mavericks) or +later. + +`First install a recent version of Python 3. +`_ + +Then to install using pip, use the following command:: + + python3 -m pip install tcod + +Linux (Debian-based) +-------------------- +On Linux python-tcod will need to be built from source. +You can run this command to download python-tcod's dependencies with `apt`:: + + sudo apt install g++ python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev libomp5 + +Then you can build and install python-tcod using `pip` in a user environment:: + + python3 -m pip install --user tcod + +Upgrading python-tcod +--------------------- +`python-tcod` is updated often, you can re-run pip with the ``--upgrade`` flag +to ensure you have the latest version, for example:: + + python3 -m pip install --upgrade tcod + +Upgrading from libtcodpy to python-tcod +--------------------------------------- +`libtcodpy` is no longer maintained and using it can make it difficult to +collaborate with developers across multiple operating systems, or to distribute +to those platforms. +New API features are only available on `python-tcod`. + +You can recognise a libtcodpy program because it includes this file structure:: + + libtcodpy/ + libtcod.dll + SDL2.dll + +First make sure your `libtcodpy` project works in Python 3. `libtcodpy` +supports both 2 and 3 so you don't need to worry about having to update +libtcodpy itself, but you will need to worry about bit-size. If you're using a +32-bit version of Python 2 then you'll need to upgrade to a 32-bit version of +Python 3 until libtcodpy is removed. + +Once you've installed python-tcod you can safely delete the ``libtcodpy/`` +folder and all DLL files of a libtcodpy program, python-tcod will seamlessly +take the place of `libtcodpy`'s API. + +From then on anyone can follow the instructions to install python-tcod and your +project will work for them regardless of their platform or bit-size. + +`python-tcod` projects can be distributed using +`PyInstaller `._ From d271cc9892a6bdcd931f7a9984fffc754170b36f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 10 Mar 2019 18:39:52 -0700 Subject: [PATCH 0122/1101] Update installation instructions and point to them. Plus other documentation updates. --- README.rst | 29 +++++---------------- docs/glossary.rst | 4 ++- docs/index.rst | 7 +++-- docs/installation.rst | 59 ++++++++++++++++++++++++++++++------------- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/README.rst b/README.rst index 5ca28cc7..8149d726 100755 --- a/README.rst +++ b/README.rst @@ -32,35 +32,18 @@ project. Guides and Tutorials for libtcodpy should work with the tcod module. -The latest documentation can be found -`here `_. +The latest documentation can be found here: +https://python-tcod.readthedocs.io/en/latest/ ============== Installation ============== -The recommended way to install is by using pip. Older versions of pip will -have issues installing tcod, so make sure it's up-to-date. +Detailed installation instructions are here: +https://python-tcod.readthedocs.io/en/latest/installation.html -Windows / MacOS ---------------- -To install using pip, use the following command:: +For the most part it's just:: - > python -m pip install tcod - -If you get the error "ImportError: DLL load failed: The specified module could -not be found." when trying to import tcod/tdl then you may need the latest -`Microsoft Visual C runtime -`_. - -Linux ------ -On Linux python-tcod will need to be built from source. -Assuming you have Python, pip, and apt-get, then you'll run these commands to -install python-tcod and its dependencies to your user environment:: - - $ sudo apt-get install gcc python-dev python3-dev libsdl2-dev libffi-dev libomp5 - $ pip2 install tcod - $ pip3 install tcod + pip3 install tcod ============== Requirements diff --git a/docs/glossary.rst b/docs/glossary.rst index 8b6bbabf..3e9c2747 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -8,6 +8,8 @@ Glossary The default values implied by any Console print or put functions which don't explicitly ask for them as parameters. + These have been deprecated since version `8.5`. + libtcod-cffi This is the `cffi` implementation of libtcodpy, the original was made using `ctypes` which was more difficult to maintain. @@ -37,7 +39,7 @@ Glossary libtcodpy `libtcodpy` is more or less a direct port of `libtcod`'s C API to Python. This caused a handful of issues including instances needing - to be freed manually or a memory leak will occur and some functions + to be freed manually or a memory leak will occur, and some functions performing badly in Python due to the need to call them frequently. These issues are fixed in :term:`python-tcod` which implements the full diff --git a/docs/index.rst b/docs/index.rst index c48bcc18..aa11d1af 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,12 +14,15 @@ Contents: .. toctree:: :maxdepth: 2 - :caption: Readme installation - changelog glossary +.. toctree:: + :maxdepth: 1 + + changelog + .. toctree:: :maxdepth: 2 :caption: python-tcod API diff --git a/docs/installation.rst b/docs/installation.rst index 2b4d94a6..8527f058 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,9 +1,13 @@ Installation ============ -Python 3.5 or above is required. +Once installed, you'll be able to import the `tcod` and `libtcodpy` modules, +as well as the deprecated `tdl` module. -The recommended way to install is by using pip. Older versions of pip will -have issues installing tcod, so make sure it's up-to-date. +Python 3.5 or above is required for a normal install. +These instructions include installing Python if you don't have it yet. + +There are known issues in very old versions of pip. +If pip fails to install python-tcod then try updating it first. Windows ------- @@ -14,14 +18,17 @@ Make sure Python is added to the Windows `PATH`. If you don't already have it, then install the latest `Microsoft Visual C++ Redistributable `_. -`vc_redist.x86.exe` for a 32-bit install of Python, or `vc_redist.x64.exe` for -a 64-bit install. You'll need to keep this in mind when distributing any +**vc_redist.x86.exe** for a 32-bit install of Python, or **vc_redist.x64.exe** +for a 64-bit install. You'll need to keep this in mind when distributing any libtcod program to end-users. Then to install python-tcod run the following from a Windows command line:: py -m pip install tcod +If Python was installed for all users then you may need to add the ``--user`` +flag to pip. + MacOS ----- The latest version of python-tcod only supports MacOS 10.9 (Mavericks) or @@ -30,24 +37,24 @@ later. `First install a recent version of Python 3. `_ -Then to install using pip, use the following command:: +Then to install using pip in a user environment, use the following command:: - python3 -m pip install tcod + python3 -m pip install --user tcod Linux (Debian-based) -------------------- On Linux python-tcod will need to be built from source. -You can run this command to download python-tcod's dependencies with `apt`:: +You can run this command to download python-tcod's dependencies with apt:: sudo apt install g++ python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev libomp5 -Then you can build and install python-tcod using `pip` in a user environment:: +Then you can build and install python-tcod using pip in a user environment:: python3 -m pip install --user tcod Upgrading python-tcod --------------------- -`python-tcod` is updated often, you can re-run pip with the ``--upgrade`` flag +python-tcod is updated often, you can re-run pip with the ``--upgrade`` flag to ensure you have the latest version, for example:: python3 -m pip install --upgrade tcod @@ -65,18 +72,34 @@ You can recognise a libtcodpy program because it includes this file structure:: libtcod.dll SDL2.dll -First make sure your `libtcodpy` project works in Python 3. `libtcodpy` -supports both 2 and 3 so you don't need to worry about having to update -libtcodpy itself, but you will need to worry about bit-size. If you're using a +First make sure your libtcodpy project works in Python 3. libtcodpy +already supports both 2 and 3 so you don't need to worry about updating it, +but you will need to worry about bit-size. If you're using a 32-bit version of Python 2 then you'll need to upgrade to a 32-bit version of -Python 3 until libtcodpy is removed. +Python 3 until libtcodpy can be completely removed. Once you've installed python-tcod you can safely delete the ``libtcodpy/`` -folder and all DLL files of a libtcodpy program, python-tcod will seamlessly -take the place of `libtcodpy`'s API. +folder and all DLL files of a libtcodpy program, python-tcod will +seamlessly take the place of libtcodpy's API. From then on anyone can follow the instructions to install python-tcod and your project will work for them regardless of their platform or bit-size. -`python-tcod` projects can be distributed using -`PyInstaller `._ +Distributing +------------ +Once your project is finished, it can be distributed using +`PyInstaller `_. + +Python 2.7 +---------- +While it's not recommended, you can still install `python-tcod` on +`Python 2.7`. + +`Keep in mind the Python 2's end-of-life is the year 2020. You should not be +starting any new projects in Python 2! +`_ + +Follow the instructions for your platform normally. When it comes to +install with pip, tell it to get python-tcod version 6:: + + python2 -m pip install tcod==6.0.7 From 566a7b80032d6d324ab7bdb5a6105d8adfb8eb7c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 Mar 2019 16:24:55 -0700 Subject: [PATCH 0123/1101] Update libtcod submodule. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8ee90519..41f8d0e6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Added + - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none + are provided. 9.2.5 - 2019-03-04 ------------------ diff --git a/libtcod b/libtcod index 471eaf5b..93da7649 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 471eaf5b02122dd59bf901cc2350a1f1f9427dbd +Subproject commit 93da7649c7021297026061c4b5b6016250477c84 From 54ce7bc262febd1e7e7d4625c489f9716fc2297c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 12 Mar 2019 01:20:57 -0700 Subject: [PATCH 0124/1101] Add new get mouse state function. Refactor tcod.event classes. --- CHANGELOG.rst | 3 ++ tcod/event.py | 79 +++++++++++++++++++++++++++++++++++++++-------- tcod/libtcodpy.py | 1 + 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 41f8d0e6..840ba2dd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,9 @@ Unreleased Added - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none are provided. + - New function `tcod.event.get_mouse_state`. +Deprecated + - `tcod.mouse_get_status` has been deprecated. 9.2.5 - 2019-03-04 ------------------ diff --git a/tcod/event.py b/tcod/event.py index e77d18be..a964c554 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -181,7 +181,46 @@ class KeyUp(KeyboardEvent): pass -class MouseMotion(Event): +class MouseState(Event): + """ + Attributes: + type (str): Always "MOUSESTATE". + pixel (Point): The pixel coordinates of the mouse. + tile (Point): The integer tile coordinates of the mouse on the screen. + state (int): A bitmask of which mouse buttons are currently held. + + Will be a combination of the following names: + + * tcod.event.BUTTON_LMASK + * tcod.event.BUTTON_MMASK + * tcod.event.BUTTON_RMASK + * tcod.event.BUTTON_X1MASK + * tcod.event.BUTTON_X2MASK + + .. addedversion:: 9.3 + """ + + def __init__( + self, + pixel: Tuple[int, int] = (0, 0), + tile: Tuple[int, int] = (0, 0), + state: int = 0, + ): + super().__init__() + self.pixel = Point(*pixel) + self.tile = Point(*tile) + self.state = state + + def __repr__(self) -> str: + return ("tcod.event.%s(pixel=%r, tile=%r, state=%s)") % ( + self.__class__.__name__, + self.pixel, + self.tile, + _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), + ) + + +class MouseMotion(MouseState): """ Attributes: type (str): Always "MOUSEMOTION". @@ -208,12 +247,9 @@ def __init__( tile_motion: Tuple[int, int] = (0, 0), state: int = 0, ): - super().__init__() - self.pixel = Point(*pixel) + super().__init__(pixel, tile, state) self.pixel_motion = Point(*pixel_motion) - self.tile = Point(*tile) self.tile_motion = Point(*tile_motion) - self.state = state @classmethod def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": @@ -245,7 +281,7 @@ def __repr__(self) -> str: ) -class MouseButtonEvent(Event): +class MouseButtonEvent(MouseState): """ Attributes: type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", @@ -269,10 +305,15 @@ def __init__( tile: Tuple[int, int] = (0, 0), button: int = 0, ): - super().__init__() - self.pixel = Point(*pixel) - self.tile = Point(*tile) - self.button = button + super().__init__(pixel, tile, button) + + @property + def button(self) -> int: + return self.state + + @button.setter + def button(self, value: int) -> None: + self.state = value @classmethod def from_sdl_event(cls, sdl_event: Any) -> Any: @@ -294,11 +335,11 @@ def __repr__(self) -> str: class MouseButtonDown(MouseButtonEvent): - pass + """Same as MouseButtonEvent but with ``type="MouseButtonDown"``.""" class MouseButtonUp(MouseButtonEvent): - pass + """Same as MouseButtonEvent but with ``type="MouseButtonUp"``.""" class MouseWheel(Event): @@ -309,7 +350,8 @@ class MouseWheel(Event): y (int): Vertical scrolling. A positive value means scrolling away from the user. flipped (bool): If True then the values of `x` and `y` are the opposite - of their usual values. + of their usual values. This depends on the settings of + the Operating System. """ def __init__(self, x: int, y: int, flipped: bool = False): @@ -666,6 +708,17 @@ def ev_windowhittest(self, event: WindowEvent) -> None: pass +def get_mouse_state() -> MouseState: + """Return the current state of the mouse. + + .. addedversion:: 9.3 + """ + xy = tcod.ffi.new("int[2]") + buttons = tcod.lib.SDL_GetMouseState(xy, xy + 1) + x, y = _pixel_to_tile(*xy) + return MouseState((xy[0], xy[1]), (int(x), int(y)), buttons) + + __all__ = [ "Point", "BUTTON_LEFT", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d58f6d33..6dae1176 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3164,6 +3164,7 @@ def mouse_move(x: int, y: int) -> None: lib.TCOD_mouse_move(x, y) +@deprecate("Use tcod.event.get_mouse_state() instead.") def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) From 09734ec55a9d9d40bcdd6d7117b169f75c0aedaf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 12 Mar 2019 13:41:01 -0700 Subject: [PATCH 0125/1101] More deprecation. Update tests to ignore/fix more warnings. --- CHANGELOG.rst | 12 ++- tcod/_internal.py | 12 +++ tcod/libtcodpy.py | 158 +++++++++++++++++++++++++++++++++++++++- tests/common.py | 5 -- tests/conftest.py | 13 ++-- tests/test_console.py | 2 + tests/test_libtcodpy.py | 18 ++++- tests/test_tcod.py | 1 + 8 files changed, 203 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 840ba2dd..5367a2ae 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,7 +13,17 @@ Added are provided. - New function `tcod.event.get_mouse_state`. Deprecated - - `tcod.mouse_get_status` has been deprecated. + - The following functions and classes have been deprecated. + - `tcod.Key` + - `tcod.Mouse` + - `tcod.mouse_get_status` + - `tcod.console_is_window_closed` + - `tcod.console_check_for_keypress` + - `tcod.console_wait_for_keypress` + - `tcod.console_delete` + - `tcod.sys_check_for_event` + - `tcod.sys_wait_for_event` + - Many libtcodpy functions have been marked with PendingDeprecationWarning's. 9.2.5 - 2019-03-04 ------------------ diff --git a/tcod/_internal.py b/tcod/_internal.py index edae7287..dda24398 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -9,6 +9,7 @@ def deprecate( message: str, category: Any = DeprecationWarning, stacklevel: int = 0 ) -> Callable[[F], F]: + """Return a decorator which adds a warning to functions.""" def decorator(func: F) -> F: if not __debug__: return func @@ -23,6 +24,17 @@ def wrapper(*args, **kargs): # type: ignore return decorator +def pending_deprecate( + message: str = "This function may be deprecated in the future." + " Consider raising an issue on GitHub if you need this feature.", + category: Any = PendingDeprecationWarning, + stacklevel: int = 0, +) -> Callable[[F], F]: + """Like deprecate, but the default parameters are filled out for a generic + pending deprecation warning.""" + return deprecate(message, category, stacklevel) + + def verify_order(order: str) -> str: order = order.upper() if order not in ("C", "F"): diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 6dae1176..5810bd82 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -37,7 +37,7 @@ KEY_RELEASED, ) -from tcod._internal import deprecate +from tcod._internal import deprecate, pending_deprecate from tcod.tcod import _int, _unpack_char_p from tcod.tcod import _bytes, _unicode, _fmt @@ -360,6 +360,9 @@ class Key(_CDataWrapper): rctrl (bool): True when right control is held. rmeta (bool): True when right meta key is held. shift (bool): True when any shift is held. + + .. deprecated:: 9.3 + Use events from the :any:`tcod.event` module instead. """ _BOOL_ATTRIBUTES = ( @@ -468,6 +471,9 @@ class Mouse(_CDataWrapper): mbutton_pressed (bool): Middle button pressed event. wheel_up (bool): Wheel up event. wheel_down (bool): Wheel down event. + + .. deprecated:: 9.3 + Use events from the :any:`tcod.event` module instead. """ def __init__( @@ -752,6 +758,7 @@ def bsp_delete(node: tcod.bsp.BSP) -> None: """ +@pending_deprecate() def color_lerp( c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float ) -> Color: @@ -773,6 +780,7 @@ def color_lerp( return Color._new_from_cdata(lib.TCOD_color_lerp(c1, c2, a)) +@pending_deprecate() def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: """Set a color using: hue, saturation, and value parameters. @@ -789,6 +797,7 @@ def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: c[:] = new_color.r, new_color.g, new_color.b +@pending_deprecate() def color_get_hsv(c: Tuple[int, int, int]) -> Tuple[float, float, float]: """Return the (hue, saturation, value) of a color. @@ -805,6 +814,7 @@ def color_get_hsv(c: Tuple[int, int, int]) -> Tuple[float, float, float]: return hsv[0], hsv[1], hsv[2] +@pending_deprecate() def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: """Scale a color's saturation and value. @@ -823,6 +833,7 @@ def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: c[:] = color_p.r, color_p.g, color_p.b +@pending_deprecate() def color_gen_map( colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int] ) -> List[Color]: @@ -968,6 +979,7 @@ def console_get_height(con: tcod.console.Console) -> int: return int(lib.TCOD_console_get_height(_console(con))) +@pending_deprecate() def console_map_ascii_code_to_font( asciiCode: int, fontCharX: int, fontCharY: int ) -> None: @@ -989,6 +1001,7 @@ def console_map_ascii_code_to_font( ) +@pending_deprecate() def console_map_ascii_codes_to_font( firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int ) -> None: @@ -1012,6 +1025,7 @@ def console_map_ascii_codes_to_font( ) +@pending_deprecate() def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: """Remap a string of codes to a contiguous set of tiles. @@ -1046,16 +1060,23 @@ def console_set_fullscreen(fullscreen: bool) -> None: lib.TCOD_console_set_fullscreen(fullscreen) +@deprecate('Use the tcod.event module to check for "QUIT" type events.') def console_is_window_closed() -> bool: - """Returns True if the window has received and exit event.""" + """Returns True if the window has received and exit event. + + .. deprecated:: 9.3 + Use the :any:`tcod.event` module to check for "QUIT" type events. + """ return bool(lib.TCOD_console_is_window_closed()) +@pending_deprecate() def console_has_mouse_focus() -> bool: """Return True if the window has mouse focus.""" return bool(lib.TCOD_console_has_mouse_focus()) +@pending_deprecate() def console_is_active() -> bool: """Return True if the window has keyboard focus.""" return bool(lib.TCOD_console_is_active()) @@ -1070,6 +1091,7 @@ def console_set_window_title(title: str) -> None: lib.TCOD_console_set_window_title(_bytes(title)) +@pending_deprecate() def console_credits() -> None: lib.TCOD_console_credits() @@ -1139,6 +1161,7 @@ def console_clear(con: tcod.console.Console) -> None: lib.TCOD_console_clear(_console(con)) +@pending_deprecate() def console_put_char( con: tcod.console.Console, x: int, @@ -1158,6 +1181,7 @@ def console_put_char( lib.TCOD_console_put_char(_console(con), x, y, _int(c), flag) +@pending_deprecate() def console_put_char_ex( con: tcod.console.Console, x: int, @@ -1181,6 +1205,7 @@ def console_put_char_ex( lib.TCOD_console_put_char_ex(_console(con), x, y, _int(c), fore, back) +@pending_deprecate() def console_set_char_background( con: tcod.console.Console, x: int, @@ -1489,6 +1514,7 @@ def console_print_frame( lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) +@pending_deprecate() def console_set_color_control( con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int] ) -> None: @@ -1569,19 +1595,23 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore +@pending_deprecate() def console_set_fade(fade: int, fadingColor: Tuple[int, int, int]) -> None: lib.TCOD_console_set_fade(fade, fadingColor) +@pending_deprecate() def console_get_fade() -> int: return int(lib.TCOD_console_get_fade()) +@pending_deprecate() def console_get_fading_color() -> Color: return Color._new_from_cdata(lib.TCOD_console_get_fading_color()) # handling keyboard input +@deprecate("Use the tcod.event.wait function to wait for events.") def console_wait_for_keypress(flush: bool) -> Key: """Block until the user presses a key, then returns a new Key. @@ -1591,18 +1621,27 @@ def console_wait_for_keypress(flush: bool) -> Key: Returns: Key: A new Key instance. + + .. deprecated:: 9.3 + Use the :any:`tcod.event.wait` function to wait for events. """ key = Key() lib.TCOD_console_wait_for_keypress_wrapper(key.key_p, flush) return key +@deprecate("Use the tcod.event.get function to check for events.") def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: + """ + .. deprecated:: 9.3 + Use the :any:`tcod.event.get` function to check for events. + """ key = Key() lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags) return key +@pending_deprecate() def console_is_key_pressed(key: int) -> bool: return bool(lib.TCOD_console_is_key_pressed(key)) @@ -1682,10 +1721,30 @@ def console_delete(con: tcod.console.Console) -> None: scope. This function exists for backwards compatibility. + + .. deprecated:: 9.3 + This function is not needed for normal :any:`tcod.console.Console`'s. + The root console should be used in a with statement instead to ensure + that it closes. """ con = _console(con) if con == ffi.NULL: lib.TCOD_console_delete(con) + warnings.warn( + "Instead of this call you should use a with statement to ensure" + " the root console closes, for example:" + "\n with tcod.console_init_root(...) as root_console:" + "\n ...", + DeprecationWarning, + stacklevel=2 + ) + else: + warnings.warn( + "You no longer need to make this call, " + "Console's are deleted when they go out of scope.", + DeprecationWarning, + stacklevel=2 + ) @deprecate("Assign to the console.fg array instead.") @@ -1791,6 +1850,7 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: lib.TCOD_console_fill_char(_console(con), carr) +@pending_deprecate() def console_load_asc(con: tcod.console.Console, filename: str) -> bool: """Update a console from a non-delimited ASCII `.asc` file.""" return bool( @@ -1798,6 +1858,7 @@ def console_load_asc(con: tcod.console.Console, filename: str) -> bool: ) +@pending_deprecate() def console_save_asc(con: tcod.console.Console, filename: str) -> bool: """Save a console to a non-delimited ASCII `.asc` file.""" return bool( @@ -1805,6 +1866,7 @@ def console_save_asc(con: tcod.console.Console, filename: str) -> bool: ) +@pending_deprecate() def console_load_apf(con: tcod.console.Console, filename: str) -> bool: """Update a console from an ASCII Paint `.apf` file.""" return bool( @@ -1812,6 +1874,7 @@ def console_load_apf(con: tcod.console.Console, filename: str) -> bool: ) +@pending_deprecate() def console_save_apf(con: tcod.console.Console, filename: str) -> bool: """Save a console to an ASCII Paint `.apf` file.""" return bool( @@ -1882,6 +1945,7 @@ def console_list_save_xp( lib.TCOD_list_delete(tcod_list) +@pending_deprecate() def path_new_using_map( m: tcod.map.Map, dcost: float = 1.41 ) -> tcod.path.AStar: @@ -1897,6 +1961,7 @@ def path_new_using_map( return tcod.path.AStar(m, dcost) +@pending_deprecate() def path_new_using_function( w: int, h: int, @@ -1921,6 +1986,7 @@ def path_new_using_function( ) +@pending_deprecate() def path_compute( p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int ) -> bool: @@ -1938,6 +2004,7 @@ def path_compute( return bool(lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy)) +@pending_deprecate() def path_get_origin(p: tcod.path.AStar) -> Tuple[int, int]: """Get the current origin position. @@ -1954,6 +2021,7 @@ def path_get_origin(p: tcod.path.AStar) -> Tuple[int, int]: return x[0], y[0] +@pending_deprecate() def path_get_destination(p: tcod.path.AStar) -> Tuple[int, int]: """Get the current destination position. @@ -1968,6 +2036,7 @@ def path_get_destination(p: tcod.path.AStar) -> Tuple[int, int]: return x[0], y[0] +@pending_deprecate() def path_size(p: tcod.path.AStar) -> int: """Return the current length of the computed path. @@ -1979,6 +2048,7 @@ def path_size(p: tcod.path.AStar) -> int: return int(lib.TCOD_path_size(p._path_c)) +@pending_deprecate() def path_reverse(p: tcod.path.AStar) -> None: """Reverse the direction of a path. @@ -1990,6 +2060,7 @@ def path_reverse(p: tcod.path.AStar) -> None: lib.TCOD_path_reverse(p._path_c) +@pending_deprecate() def path_get(p: tcod.path.AStar, idx: int) -> Tuple[int, int]: """Get a point on a path. @@ -2003,6 +2074,7 @@ def path_get(p: tcod.path.AStar, idx: int) -> Tuple[int, int]: return x[0], y[0] +@pending_deprecate() def path_is_empty(p: tcod.path.AStar) -> bool: """Return True if a path is empty. @@ -2014,6 +2086,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: return bool(lib.TCOD_path_is_empty(p._path_c)) +@pending_deprecate() def path_walk( p: tcod.path.AStar, recompute: bool ) -> Union[Tuple[int, int], Tuple[None, None]]: @@ -2044,10 +2117,12 @@ def path_delete(p: tcod.path.AStar) -> None: """ +@pending_deprecate() def dijkstra_new(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.Dijkstra: return tcod.path.Dijkstra(m, dcost) +@pending_deprecate() def dijkstra_new_using_function( w: int, h: int, @@ -2060,26 +2135,32 @@ def dijkstra_new_using_function( ) +@pending_deprecate() def dijkstra_compute(p: tcod.path.Dijkstra, ox: int, oy: int) -> None: lib.TCOD_dijkstra_compute(p._path_c, ox, oy) +@pending_deprecate() def dijkstra_path_set(p: tcod.path.Dijkstra, x: int, y: int) -> bool: return bool(lib.TCOD_dijkstra_path_set(p._path_c, x, y)) +@pending_deprecate() def dijkstra_get_distance(p: tcod.path.Dijkstra, x: int, y: int) -> int: return int(lib.TCOD_dijkstra_get_distance(p._path_c, x, y)) +@pending_deprecate() def dijkstra_size(p: tcod.path.Dijkstra) -> int: return int(lib.TCOD_dijkstra_size(p._path_c)) +@pending_deprecate() def dijkstra_reverse(p: tcod.path.Dijkstra) -> None: lib.TCOD_dijkstra_reverse(p._path_c) +@pending_deprecate() def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> Tuple[int, int]: x = ffi.new("int *") y = ffi.new("int *") @@ -2087,10 +2168,12 @@ def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> Tuple[int, int]: return x[0], y[0] +@pending_deprecate() def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool: return bool(lib.TCOD_dijkstra_is_empty(p._path_c)) +@pending_deprecate() def dijkstra_path_walk( p: tcod.path.Dijkstra ) -> Union[Tuple[int, int], Tuple[None, None]]: @@ -2125,6 +2208,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) +@pending_deprecate() def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: """Return a new numpy.ndarray formatted for use with heightmap functions. @@ -2151,6 +2235,7 @@ def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: raise ValueError("Invalid order parameter, should be 'C' or 'F'.") +@deprecate("Assign to heightmaps as a NumPy array instead.") def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None: """Set the value of a point on a heightmap. @@ -2246,6 +2331,7 @@ def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: hm2[:] = hm1[:] +@pending_deprecate() def heightmap_normalize( hm: np.ndarray, mi: float = 0.0, ma: float = 1.0 ) -> None: @@ -2258,6 +2344,7 @@ def heightmap_normalize( lib.TCOD_heightmap_normalize(_heightmap_cdata(hm), mi, ma) +@pending_deprecate() def heightmap_lerp_hm( hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: float ) -> None: @@ -2315,6 +2402,7 @@ def heightmap_multiply_hm( hm3[:] = hm1[:] * hm2[:] +@pending_deprecate() def heightmap_add_hill( hm: np.ndarray, x: float, y: float, radius: float, height: float ) -> None: @@ -2332,6 +2420,7 @@ def heightmap_add_hill( lib.TCOD_heightmap_add_hill(_heightmap_cdata(hm), x, y, radius, height) +@pending_deprecate() def heightmap_dig_hill( hm: np.ndarray, x: float, y: float, radius: float, height: float ) -> None: @@ -2353,6 +2442,7 @@ def heightmap_dig_hill( lib.TCOD_heightmap_dig_hill(_heightmap_cdata(hm), x, y, radius, height) +@pending_deprecate() def heightmap_rain_erosion( hm: np.ndarray, nbDrops: int, @@ -2381,6 +2471,7 @@ def heightmap_rain_erosion( ) +@pending_deprecate() def heightmap_kernel_transform( hm: np.ndarray, kernelsize: int, @@ -2439,6 +2530,7 @@ def heightmap_kernel_transform( ) +@pending_deprecate() def heightmap_add_voronoi( hm: np.ndarray, nbPoints: Any, @@ -2561,6 +2653,7 @@ def heightmap_scale_fbm( ) +@pending_deprecate() def heightmap_dig_bezier( hm: np.ndarray, px: Tuple[int, int, int, int], @@ -2620,6 +2713,7 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: raise ValueError("This array is not contiguous.") +@pending_deprecate() def heightmap_get_interpolated_value( hm: np.ndarray, x: float, y: float ) -> float: @@ -2638,6 +2732,7 @@ def heightmap_get_interpolated_value( ) +@pending_deprecate() def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: """Return the slope between 0 and (pi / 2) at given coordinates. @@ -2652,6 +2747,7 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: return float(lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y)) +@pending_deprecate() def heightmap_get_normal( hm: np.ndarray, x: float, y: float, waterLevel: float ) -> Tuple[float, float, float]: @@ -2690,6 +2786,7 @@ def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: return int(lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma)) +@pending_deprecate() def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: """Returns True if the map edges are below ``waterlevel``, otherwise False. @@ -2735,50 +2832,61 @@ def heightmap_delete(hm: Any) -> None: """ +@pending_deprecate() def image_new(width: int, height: int) -> tcod.image.Image: return tcod.image.Image(width, height) +@pending_deprecate() def image_clear(image: tcod.image.Image, col: Tuple[int, int, int]) -> None: image.clear(col) +@pending_deprecate() def image_invert(image: tcod.image.Image) -> None: image.invert() +@pending_deprecate() def image_hflip(image: tcod.image.Image) -> None: image.hflip() +@pending_deprecate() def image_rotate90(image: tcod.image.Image, num: int = 1) -> None: image.rotate90(num) +@pending_deprecate() def image_vflip(image: tcod.image.Image) -> None: image.vflip() +@pending_deprecate() def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: image.scale(neww, newh) +@pending_deprecate() def image_set_key_color( image: tcod.image.Image, col: Tuple[int, int, int] ) -> None: image.set_key_color(col) +@pending_deprecate() def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int: return image.get_alpha(x, y) +@pending_deprecate() def image_is_pixel_transparent( image: tcod.image.Image, x: int, y: int ) -> bool: return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) +@pending_deprecate() def image_load(filename: str) -> tcod.image.Image: """Load an image file into an Image instance and return it. @@ -2790,6 +2898,7 @@ def image_load(filename: str) -> tcod.image.Image: ) +@pending_deprecate() def image_from_console(console: tcod.console.Console) -> tcod.image.Image: """Return an Image with a Consoles pixel data. @@ -2806,34 +2915,40 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: ) +@pending_deprecate() def image_refresh_console( image: tcod.image.Image, console: tcod.console.Console ) -> None: image.refresh_console(console) +@pending_deprecate() def image_get_size(image: tcod.image.Image) -> Tuple[int, int]: return image.width, image.height +@pending_deprecate() def image_get_pixel( image: tcod.image.Image, x: int, y: int ) -> Tuple[int, int, int]: return image.get_pixel(x, y) +@pending_deprecate() def image_get_mipmap_pixel( image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float ) -> Tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) +@pending_deprecate() def image_put_pixel( image: tcod.image.Image, x: int, y: int, col: Tuple[int, int, int] ) -> None: image.put_pixel(x, y, col) +@pending_deprecate() def image_blit( image: tcod.image.Image, console: tcod.console.Console, @@ -2847,6 +2962,7 @@ def image_blit( image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle) +@pending_deprecate() def image_blit_rect( image: tcod.image.Image, console: tcod.console.Console, @@ -2859,6 +2975,7 @@ def image_blit_rect( image.blit_rect(console, x, y, w, h, bkgnd_flag) +@pending_deprecate() def image_blit_2x( image: tcod.image.Image, console: tcod.console.Console, @@ -2872,6 +2989,7 @@ def image_blit_2x( image.blit_2x(console, dx, dy, sx, sy, w, h) +@pending_deprecate() def image_save(image: tcod.image.Image, filename: str) -> None: image.save_as(filename) @@ -3150,16 +3268,19 @@ def map_get_height(map: tcod.map.Map) -> int: return map.height +@pending_deprecate() def mouse_show_cursor(visible: bool) -> None: """Change the visibility of the mouse cursor.""" lib.TCOD_mouse_show_cursor(visible) +@pending_deprecate() def mouse_is_cursor_visible() -> bool: """Return True if the mouse cursor is visible.""" return bool(lib.TCOD_mouse_is_cursor_visible()) +@pending_deprecate() def mouse_move(x: int, y: int) -> None: lib.TCOD_mouse_move(x, y) @@ -3169,22 +3290,26 @@ def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) +@pending_deprecate() def namegen_parse( filename: str, random: Optional[tcod.random.Random] = None ) -> None: lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) +@pending_deprecate() def namegen_generate(name: str) -> str: return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) +@pending_deprecate() def namegen_generate_custom(name: str, rule: str) -> str: return _unpack_char_p( lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False) ) +@pending_deprecate() def namegen_get_sets() -> List[str]: sets = lib.TCOD_namegen_get_sets() try: @@ -3198,10 +3323,12 @@ def namegen_get_sets() -> List[str]: return lst +@pending_deprecate() def namegen_destroy() -> None: lib.TCOD_namegen_destroy() +@pending_deprecate() def noise_new( dim: int, h: float = NOISE_DEFAULT_HURST, @@ -3222,6 +3349,7 @@ def noise_new( return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random) +@pending_deprecate() def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: """Set a Noise objects default noise algorithm. @@ -3231,6 +3359,7 @@ def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: n.algorithm = typ +@pending_deprecate() def noise_get( n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT ) -> float: @@ -3252,6 +3381,7 @@ def noise_get( return float(lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ)) +@pending_deprecate() def noise_get_fbm( n: tcod.noise.Noise, f: Sequence[float], @@ -3274,6 +3404,7 @@ def noise_get_fbm( ) +@pending_deprecate() def noise_get_turbulence( n: tcod.noise.Noise, f: Sequence[float], @@ -3476,6 +3607,7 @@ def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4 +@pending_deprecate() def random_get_instance() -> tcod.random.Random: """Return the default Random instance. @@ -3487,6 +3619,7 @@ def random_get_instance() -> tcod.random.Random: ) +@pending_deprecate() def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: """Return a new Random instance. Using ``algo``. @@ -3499,6 +3632,7 @@ def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: return tcod.random.Random(algo) +@pending_deprecate() def random_new_from_seed( seed: Hashable, algo: int = RNG_CMWC ) -> tcod.random.Random: @@ -3515,6 +3649,7 @@ def random_new_from_seed( return tcod.random.Random(algo, seed) +@pending_deprecate() def random_set_distribution( rnd: Optional[tcod.random.Random], dist: int ) -> None: @@ -3527,10 +3662,11 @@ def random_set_distribution( lib.TCOD_random_set_distribution(rnd.random_c if rnd else ffi.NULL, dist) +@pending_deprecate() def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: """Return a random integer in the range: ``mi`` <= n <= ``ma``. - The result is affacted by calls to :any:`random_set_distribution`. + The result is affected by calls to :any:`random_set_distribution`. Args: rnd (Optional[Random]): A Random instance, or None to use the default. @@ -3545,12 +3681,13 @@ def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: ) +@pending_deprecate() def random_get_float( rnd: Optional[tcod.random.Random], mi: float, ma: float ) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. - The result is affacted by calls to :any:`random_set_distribution`. + The result is affected by calls to :any:`random_set_distribution`. Args: rnd (Optional[Random]): A Random instance, or None to use the default. @@ -3581,6 +3718,7 @@ def random_get_double( ) +@pending_deprecate() def random_get_int_mean( rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int ) -> int: @@ -3604,6 +3742,7 @@ def random_get_int_mean( ) +@pending_deprecate() def random_get_float_mean( rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float ) -> float: @@ -3844,6 +3983,7 @@ def sys_save_screenshot(name: Optional[str] = None) -> None: # custom fullscreen resolution +@pending_deprecate() def sys_force_fullscreen_resolution(width: int, height: int) -> None: """Force a specific resolution in fullscreen. @@ -3886,6 +4026,7 @@ def sys_get_char_size() -> Tuple[int, int]: # update font bitmap +@pending_deprecate() def sys_update_char( asciiCode: int, fontx: int, @@ -3912,6 +4053,7 @@ def sys_update_char( lib.TCOD_sys_update_char(_int(asciiCode), fontx, fonty, img, x, y) +@pending_deprecate() def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: """Register a custom randering function with libtcod. @@ -3936,6 +4078,7 @@ def _pycall_sdl_hook(sdl_surface: Any) -> None: lib.TCOD_sys_register_SDL_renderer(lib._pycall_sdl_hook) +@deprecate("Use tcod.event.get to check for events.") def sys_check_for_event( mask: int, k: Optional[Key], m: Optional[Mouse] ) -> int: @@ -3947,6 +4090,9 @@ def sys_check_for_event( an event. Can be None. m (Optional[Mouse]): A tcod.Mouse instance which might be updated with an event. Can be None. + + .. deprecated:: 9.3 + Use the :any:`tcod.event.get` function to check for events. """ return int( lib.TCOD_sys_check_for_event( @@ -3955,6 +4101,7 @@ def sys_check_for_event( ) +@deprecate("Use tcod.event.wait to wait for events.") def sys_wait_for_event( mask: int, k: Optional[Key], m: Optional[Mouse], flush: bool ) -> int: @@ -3970,6 +4117,9 @@ def sys_wait_for_event( m (Optional[Mouse]): A tcod.Mouse instance which might be updated with an event. Can be None. flush (bool): Clear the event buffer before waiting. + + .. deprecated:: 9.3 + Use the :any:`tcod.event.wait` function to wait for events. """ return int( lib.TCOD_sys_wait_for_event( diff --git a/tests/common.py b/tests/common.py index ce5f4066..051b6c44 100644 --- a/tests/common.py +++ b/tests/common.py @@ -6,8 +6,3 @@ def raise_Exception(*args): raise Exception('testing exception') - -needs_window = pytest.mark.skipif( - pytest.config.getoption("--no-window"), - reason="This test needs a rendering context." -) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 6058a5f1..22deade3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import random +import warnings import pytest @@ -11,7 +12,7 @@ def pytest_addoption(parser): @pytest.fixture(scope="session", params=['SDL', 'SDL2']) def session_console(request): - if(pytest.config.getoption("--no-window")): + if(request.config.getoption("--no-window")): pytest.skip("This test needs a rendering context.") FONT_FILE = 'libtcod/terminal.png' WIDTH = 12 @@ -29,10 +30,12 @@ def session_console(request): def console(session_console): console = session_console tcod.console_flush() - console.default_fg = (255, 255, 255) - console.default_bg = (0, 0, 0) - console.default_blend = tcod.BKGND_SET - console.default_alignment = tcod.LEFT + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + console.default_fg = (255, 255, 255) + console.default_bg = (0, 0, 0) + console.default_blend = tcod.BKGND_SET + console.default_alignment = tcod.LEFT console.clear() return console diff --git a/tests/test_console.py b/tests/test_console.py index 3ec9d656..7b3af473 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -8,6 +8,7 @@ @pytest.mark.filterwarnings("ignore:Directly access a consoles") +@pytest.mark.filterwarnings("ignore:This function may be deprecated in the fu") def test_array_read_write(): console = tcod.console.Console(width=12, height=10) FG = (255, 254, 253) @@ -96,6 +97,7 @@ def test_console_repr(): eval(repr(tcod.console.Console(10, 2))) +@pytest.mark.filterwarnings("ignore:.") def test_console_str(): console = tcod.console.Console(10, 2) console.print_(0, 0, "Test") diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 83215210..90836ca8 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -13,6 +13,7 @@ def test_console_behaviour(console): @pytest.mark.skip('takes too long') +@pytest.mark.filterwarnings("ignore:.") def test_credits_long(console): libtcodpy.console_credits() @@ -200,10 +201,12 @@ def test_console_rexpaint_list_save_load(console, tmpdir): libtcodpy.console_delete(b) +@pytest.mark.filterwarnings("ignore:.") def test_console_fullscreen(console): libtcodpy.console_set_fullscreen(False) +@pytest.mark.filterwarnings("ignore:.") def test_console_key_input(console): libtcodpy.console_check_for_keypress() libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER) @@ -281,11 +284,13 @@ def test_console_buffer_error(console): with pytest.raises(ValueError): buffer.blit(console) +@pytest.mark.filterwarnings("ignore:.") def test_console_font_mapping(console): libtcodpy.console_map_ascii_code_to_font('@', 1, 1) libtcodpy.console_map_ascii_codes_to_font('@', 1, 0, 0) libtcodpy.console_map_string_to_font('@', 0, 0) +@pytest.mark.filterwarnings("ignore:.") def test_mouse(console): libtcodpy.mouse_show_cursor(True) libtcodpy.mouse_is_cursor_visible() @@ -302,9 +307,11 @@ def test_sys_time(console): libtcodpy.sys_elapsed_milli() libtcodpy.sys_elapsed_seconds() +@pytest.mark.filterwarnings("ignore:.") def test_sys_screenshot(console, tmpdir): libtcodpy.sys_save_screenshot(tmpdir.join('test.png').strpath) +@pytest.mark.filterwarnings("ignore:.") def test_sys_custom_render(console): if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL: pytest.xfail(reason='Only supports SDL') @@ -317,7 +324,7 @@ def sdl_callback(sdl_surface): libtcodpy.console_flush() assert escape, 'proof that sdl_callback was called' -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") +@pytest.mark.filterwarnings("ignore:.") def test_image(console, tmpdir): img = libtcodpy.image_new(16, 16) libtcodpy.image_clear(img, libtcodpy.Color(0, 0, 0)) @@ -349,7 +356,7 @@ def test_image(console, tmpdir): @pytest.mark.parametrize('sample', ['@', u'\u2603']) # Unicode snowman @pytest.mark.xfail(reason='Unreliable') -@pytest.mark.filterwarnings("ignore:This function does not provide reliable") +@pytest.mark.filterwarnings("ignore:.") def test_clipboard(console, sample): saved = libtcodpy.sys_clipboard_get() try: @@ -483,6 +490,7 @@ def test_map(): libtcodpy.map_delete(map) +@pytest.mark.filterwarnings("ignore:.") def test_color(): color_a = libtcodpy.Color(0, 1, 2) assert list(color_a) == [0, 1, 2] @@ -511,6 +519,7 @@ def test_color_repr(): assert eval(repr(col)) == col +@pytest.mark.filterwarnings("ignore:.") def test_color_math(): color_a = libtcodpy.Color(0, 1, 2) color_b = libtcodpy.Color(0, 10, 20) @@ -521,12 +530,14 @@ def test_color_math(): assert color_a * 100 == libtcodpy.Color(0, 100, 200) +@pytest.mark.filterwarnings("ignore:.") def test_color_gen_map(): colors = libtcodpy.color_gen_map([(0, 0, 0), (255, 255, 255)], [0, 8]) assert colors[0] == libtcodpy.Color(0, 0, 0) assert colors[-1] == libtcodpy.Color(255, 255, 255) +@pytest.mark.filterwarnings("ignore:.") def test_namegen_parse(): libtcodpy.namegen_parse('libtcod/data/namegen/jice_celtic.cfg') assert libtcodpy.namegen_generate('Celtic female') @@ -534,7 +545,7 @@ def test_namegen_parse(): libtcodpy.namegen_destroy() -@pytest.mark.filterwarnings("ignore:libtcod objects are deleted automatically") +@pytest.mark.filterwarnings("ignore:.") def test_noise(): noise = libtcodpy.noise_new(1) libtcodpy.noise_set_type(noise, libtcodpy.NOISE_SIMPLEX) @@ -724,6 +735,7 @@ def test_dijkstra_callback(map_, path_callback): libtcodpy.dijkstra_delete(path) +@pytest.mark.filterwarnings("ignore:.") def test_alpha_blend(console): for i in range(256): libtcodpy.console_put_char(console, 0, 0, 'x', diff --git a/tests/test_tcod.py b/tests/test_tcod.py index f9301f37..6d58fc3e 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -152,6 +152,7 @@ def test_noise_copy(): noise2.sample_ogrid(np.ogrid[:3,:1])).all() +@pytest.mark.filterwarnings("ignore:.*") def test_color_class(): assert tcod.black == tcod.black assert tcod.black == (0, 0, 0) From a2827fcd9de1f2919c37b99df4105b85886a08e4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 12 Mar 2019 15:29:17 -0700 Subject: [PATCH 0126/1101] Resolve issues with the default renderer. SDL is now default, but raises a warning. All old renderers raise warnings. --- CHANGELOG.rst | 4 ++++ tcod/_internal.py | 1 + tcod/libtcodpy.py | 50 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5367a2ae..5055222e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,7 +23,11 @@ Deprecated - `tcod.console_delete` - `tcod.sys_check_for_event` - `tcod.sys_wait_for_event` + - The SDL, OPENGL, and GLSL renderers have been deprecated. - Many libtcodpy functions have been marked with PendingDeprecationWarning's. +Fixed + - To be more compatible with libtcodpy `tcod.console_init_root` will default + to the SDL render, but will raise warnings when an old renderer is used. 9.2.5 - 2019-03-04 ------------------ diff --git a/tcod/_internal.py b/tcod/_internal.py index dda24398..4e1c57a9 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -10,6 +10,7 @@ def deprecate( message: str, category: Any = DeprecationWarning, stacklevel: int = 0 ) -> Callable[[F], F]: """Return a decorator which adds a warning to functions.""" + def decorator(func: F) -> F: if not __debug__: return func diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 5810bd82..786c7d62 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -878,16 +878,25 @@ def console_init_root( `title` is an optional string to display on the windows title bar. `fullscreen` determines if the window will start in fullscreen. Fullscreen - mode is unreliable unless the renderer is set to `RENDERER_SDL2` or - `RENDERER_OPENGL2`. - - `renderer` is the rendering back-end that libtcod will use. Options are: - - * `tcod.RENDERER_SDL` - * `tcod.RENDERER_OPENGL` - * `tcod.RENDERER_GLSL` - * `tcod.RENDERER_SDL2` - * `tcod.RENDERER_OPENGL2` + mode is unreliable unless the renderer is set to `tcod.RENDERER_SDL2` or + `tcod.RENDERER_OPENGL2`. + + `renderer` is the rendering back-end that libtcod will use. + If you don't know which to pick, then use `tcod.RENDERER_SDL2`. + Options are: + + * `tcod.RENDERER_SDL`: + A deprecated software/SDL2 renderer. + * `tcod.RENDERER_OPENGL`: + A deprecated SDL2/OpenGL1 renderer. + * `tcod.RENDERER_GLSL`: + A deprecated SDL2/OpenGL2 renderer. + * `tcod.RENDERER_SDL2`: + The recommended SDL2 renderer. Rendering is decided by SDL2 and can be + changed by using an SDL2 hint. + * `tcod.RENDERER_OPENGL2`: + An SDL2/OPENGL2 renderer. Usually faster than regular SDL2. + Requires OpenGL 2.0 Core. `order` will affect how the array attributes of the returned root console are indexed. `order='C'` is the default, but `order='F'` is recommended. @@ -904,7 +913,22 @@ def console_init_root( # Use the scripts filename as the title. title = os.path.basename(sys.argv[0]) if renderer is None: - renderer = tcod.constants.RENDERER_SDL2 + warnings.warn( + "A renderer should be given, see the online documentation.", + DeprecationWarning, + stacklevel=2, + ) + renderer = tcod.constants.RENDERER_SDL + elif renderer in ( + tcod.constants.RENDERER_SDL, + tcod.constants.RENDERER_OPENGL, + tcod.constants.RENDERER_GLSL, + ): + warnings.warn( + "The SDL, OPENGL, and GLSL renderers are deprecated.", + DeprecationWarning, + stacklevel=2, + ) lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer) console = tcod.console.Console._get_root(order) console.clear() @@ -1736,14 +1760,14 @@ def console_delete(con: tcod.console.Console) -> None: "\n with tcod.console_init_root(...) as root_console:" "\n ...", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) else: warnings.warn( "You no longer need to make this call, " "Console's are deleted when they go out of scope.", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) From afd3fac9ec20129101639d88e9d7959bd80b6abd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 12 Mar 2019 17:53:17 -0700 Subject: [PATCH 0127/1101] Add tcod.map.compute_fov function. Refactor a large amount of the tcod docs. --- CHANGELOG.rst | 2 + docs/index.rst | 8 ++- docs/tcod.rst | 56 +++++-------------- docs/tcod/bsp.rst | 5 ++ docs/tcod/console.rst | 5 ++ docs/tcod/image.rst | 5 ++ docs/tcod/map.rst | 5 ++ docs/tcod/noise.rst | 5 ++ docs/tcod/path.rst | 5 ++ docs/tcod/random.rst | 5 ++ tcod/map.py | 124 ++++++++++++++++++++++++++++++++---------- 11 files changed, 154 insertions(+), 71 deletions(-) create mode 100644 docs/tcod/bsp.rst create mode 100644 docs/tcod/console.rst create mode 100644 docs/tcod/image.rst create mode 100644 docs/tcod/map.rst create mode 100644 docs/tcod/noise.rst create mode 100644 docs/tcod/path.rst create mode 100644 docs/tcod/random.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5055222e..26f74c0d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,8 @@ Added - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none are provided. - New function `tcod.event.get_mouse_state`. + - New function `tcod.map.compute_fov` lets you get a visibility array directly + from a transparency array. Deprecated - The following functions and classes have been deprecated. - `tcod.Key` diff --git a/docs/index.rst b/docs/index.rst index aa11d1af..5741959a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,8 +27,14 @@ Contents: :maxdepth: 2 :caption: python-tcod API - tcod + tcod/bsp + tcod/console tcod/event + tcod/image + tcod/map + tcod/noise + tcod/path + tcod/random tcod/tileset libtcodpy diff --git a/docs/tcod.rst b/docs/tcod.rst index c1a1c574..7a7f6331 100644 --- a/docs/tcod.rst +++ b/docs/tcod.rst @@ -1,42 +1,14 @@ - -tcod.console -============ - -.. automodule:: tcod.console - :members: - -tcod.map -======== - -.. automodule:: tcod.map - :members: - -tcod.bsp -========= - -.. automodule:: tcod.bsp - :members: - -tcod.path -========= - -.. automodule:: tcod.path - :members: - -tcod.image -========== - -.. automodule:: tcod.image - :members: - -tcod.random -=========== - -.. automodule:: tcod.random - :members: - -tcod.noise -========== - -.. automodule:: tcod.noise - :members: +.. toctree:: + :maxdepth: 1 + :caption: python-tcod API + + tcod/bsp + tcod/console + tcod/event + tcod/image + tcod/map + tcod/noise + tcod/path + tcod/random + tcod/tileset + libtcodpy diff --git a/docs/tcod/bsp.rst b/docs/tcod/bsp.rst new file mode 100644 index 00000000..0de72ff1 --- /dev/null +++ b/docs/tcod/bsp.rst @@ -0,0 +1,5 @@ +tcod.bsp +========= + +.. automodule:: tcod.bsp + :members: diff --git a/docs/tcod/console.rst b/docs/tcod/console.rst new file mode 100644 index 00000000..f2217687 --- /dev/null +++ b/docs/tcod/console.rst @@ -0,0 +1,5 @@ +tcod.console +============ + +.. automodule:: tcod.console + :members: diff --git a/docs/tcod/image.rst b/docs/tcod/image.rst new file mode 100644 index 00000000..b902d9ff --- /dev/null +++ b/docs/tcod/image.rst @@ -0,0 +1,5 @@ +tcod.image +========== + +.. automodule:: tcod.image + :members: diff --git a/docs/tcod/map.rst b/docs/tcod/map.rst new file mode 100644 index 00000000..aa2e0662 --- /dev/null +++ b/docs/tcod/map.rst @@ -0,0 +1,5 @@ +tcod.map +======== + +.. automodule:: tcod.map + :members: diff --git a/docs/tcod/noise.rst b/docs/tcod/noise.rst new file mode 100644 index 00000000..55c9e707 --- /dev/null +++ b/docs/tcod/noise.rst @@ -0,0 +1,5 @@ +tcod.noise +========== + +.. automodule:: tcod.noise + :members: diff --git a/docs/tcod/path.rst b/docs/tcod/path.rst new file mode 100644 index 00000000..e3c750e0 --- /dev/null +++ b/docs/tcod/path.rst @@ -0,0 +1,5 @@ +tcod.path +========= + +.. automodule:: tcod.path + :members: diff --git a/docs/tcod/random.rst b/docs/tcod/random.rst new file mode 100644 index 00000000..47fdfb3e --- /dev/null +++ b/docs/tcod/random.rst @@ -0,0 +1,5 @@ +tcod.random +=========== + +.. automodule:: tcod.random + :members: diff --git a/tcod/map.py b/tcod/map.py index 2a7659a9..f35111d5 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -1,33 +1,5 @@ """libtcod map attributes and field-of-view functions. -Example:: - - >>> import tcod.map - >>> m = tcod.map.Map(width=3, height=4) - >>> m.walkable - array([[False, False, False], - [False, False, False], - [False, False, False], - [False, False, False]]...) - - # Like the rest of the tcod modules, all arrays here are - # in row-major order and are addressed with [y,x] - >>> m.transparent[:] = True # Sets all to True. - >>> m.transparent[1:3,0] = False # Sets (1, 0) and (2, 0) to False. - >>> m.transparent - array([[ True, True, True], - [False, True, True], - [False, True, True], - [ True, True, True]]...) - - >>> m.compute_fov(0, 0) - >>> m.fov - array([[ True, True, True], - [ True, True, True], - [False, True, True], - [False, False, True]]...) - >>> m.fov[3,1] - False """ from typing import Any @@ -60,6 +32,34 @@ class Map(object): walkable: A boolean array of walkable cells. fov: A boolean array of the cells lit by :any:'compute_fov'. + Example:: + + >>> import tcod.map + >>> m = tcod.map.Map(width=3, height=4) + >>> m.walkable + array([[False, False, False], + [False, False, False], + [False, False, False], + [False, False, False]]...) + + # Like the rest of the tcod modules, all arrays here are + # in row-major order and are addressed with [y,x] + >>> m.transparent[:] = True # Sets all to True. + >>> m.transparent[1:3,0] = False # Sets (1, 0) and (2, 0) to False. + >>> m.transparent + array([[ True, True, True], + [False, True, True], + [False, True, True], + [ True, True, True]]...) + + >>> m.compute_fov(0, 0) + >>> m.fov + array([[ True, True, True], + [ True, True, True], + [False, True, True], + [False, False, True]]...) + >>> m.fov[3,1] + False """ def __init__(self, width: int, height: int, order: str = "C"): @@ -114,6 +114,9 @@ def compute_fov( A value of `0` will give an infinite distance. light_walls (bool): Light up walls, or only the floor. algorithm (int): Defaults to tcod.FOV_RESTRICTIVE + + If you already have transparency in a NumPy array then you could use + :any:`tcod.map_compute_fov` instead. """ lib.TCOD_map_compute_fov( self.map_c, x, y, radius, light_walls, algorithm @@ -139,3 +142,68 @@ def __getstate__(self) -> Any: state = self.__dict__.copy() del state["map_c"] return state + + +def compute_fov( + transparency: np.array, + x: int, + y: int, + radius: int = 0, + light_walls: bool = True, + algorithm: int = tcod.constants.FOV_RESTRICTIVE, +) -> np.array: + """Return the visible area of a field-of-view computation. + + `transparency` is a 2 dimensional array where all non-zero values are + considered transparent. The returned array will match the shape of this + array. + + `x` and `y` are the 1st and 2nd coordinates of the origin point. Areas + are visible when they can be seen from this point-of-view. + + `radius` is the maximum view distance from `x`/`y`. If this is zero then + the maximum distance is used. + + If `light_walls` is True then visible obstacles will be returned, otherwise + only transparent areas will be. + + `algorithm` is the field-of-view algorithm to run. The default value is + `tcod.FOV_RESTRICTIVE`. + The options are: + + * `tcod.FOV_BASIC`: + Simple ray-cast implementation. + * `tcod.FOV_DIAMOND` + * `tcod.FOV_SHADOW`: + Recursive shadow caster. + * `tcod.FOV_PERMISSIVE(n)`: + `n` starts at 0 (most restrictive) and goes up to 8 (most permissive.) + * `tcod.FOV_RESTRICTIVE` + + .. versionadded:: 9.3 + + Example:: + + >>> explored = np.zeros((3, 5), dtype=bool, order="F") + >>> transparency = np.ones((3, 5), dtype=bool, order="F") + >>> transparency[:2, 2] = False + >>> transparency # Transparent area. + array([[ True, True, False, True, True], + [ True, True, False, True, True], + [ True, True, True, True, True]]...) + >>> visible = tcod.map.compute_fov(transparency, 0, 0) + >>> visible # Visible area. + array([[ True, True, True, False, False], + [ True, True, True, False, False], + [ True, True, True, True, False]]...) + >>> explored |= visible # Keep track of an explored area. + """ + if len(transparency.shape) != 2: + raise TypeError( + "transparency must be an array of 2 dimensions" + " (shape is %r)" % transparency.shape + ) + map_ = Map(transparency.shape[1], transparency.shape[0]) + map_.transparent[...] = transparency + map_.compute_fov(x, y, radius, light_walls, algorithm) + return map_.fov From c342e660ab481436340d0cd7a2e3765cb4d5bbea Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 Mar 2019 17:05:27 -0700 Subject: [PATCH 0128/1101] Prepare 9.3.0 release. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 26f74c0d..62b26f3b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +9.3.0 - 2019-03-15 +------------------ Added - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none are provided. diff --git a/libtcod b/libtcod index 93da7649..c570ca2a 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 93da7649c7021297026061c4b5b6016250477c84 +Subproject commit c570ca2a7e5ff63174d7135760642e8e1d793f7f From 9be3fdecda7b47edc6a435a642e576b5fbc9cf2f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 28 Mar 2019 14:58:45 -0700 Subject: [PATCH 0129/1101] Upgrade libtcod. Console updated to use new data types. The latest libtcod fixes #71 --- CHANGELOG.rst | 6 +++ libtcod | 2 +- tcod/console.py | 115 ++++++++++++++++++++++++++++-------------------- 3 files changed, 74 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 62b26f3b..85c5d834 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Added + - New `Console.tiles` array attribute. +Changed + - `Console.DTYPE` changed to add alpha to its color types. +Fixed + - Console printing was ignoring color codes at the beginning of a string. 9.3.0 - 2019-03-15 ------------------ diff --git a/libtcod b/libtcod index c570ca2a..ab49ad79 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit c570ca2a7e5ff63174d7135760642e8e1d793f7f +Subproject commit ab49ad79663da6925354bdd9cad8ebc5e92a0a48 diff --git a/tcod/console.py b/tcod/console.py index 8b90658e..6a5ee2f8 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -65,13 +65,16 @@ class Console: Added `buffer`, `copy`, and default parameters. Arrays are initialized as if the :any:`clear` method was called. + .. versionchanged:: 10.0 + `DTYPE` changed, `buffer` now requires colors with an alpha channel. + Attributes: console_c: A python-cffi "TCOD_Console*" object. DTYPE: A class attribute which provides a dtype compatible with this class. - ``[("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")]`` + ``[("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")]`` Example:: @@ -89,10 +92,14 @@ class Console: | > .. versionadded:: 8.5 + + .. versionchanged:: 10.0 + Added an alpha channel to the color types. """ - DTYPE = [("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")] # type: Any - _DTYPE_RGB = np.dtype("(3,)u1") # type: Any + DTYPE = np.dtype( # type: Any + [("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")] + ) def __init__( self, @@ -106,13 +113,9 @@ def __init__( if buffer is not None: if self._order == "F": buffer = buffer.transpose() - self._ch = np.ascontiguousarray(buffer["ch"], np.intc) - self._fg = np.ascontiguousarray(buffer["fg"], "u1") - self._bg = np.ascontiguousarray(buffer["bg"], "u1") + self._tiles = np.ascontiguousarray(buffer, self.DTYPE) else: - self._ch = np.ndarray((height, width), dtype=np.intc) - self._fg = np.ndarray((height, width), dtype=self._DTYPE_RGB) - self._bg = np.ndarray((height, width), dtype=self._DTYPE_RGB) + self._tiles = np.ndarray((height, width), dtype=self.DTYPE) # libtcod uses the root console for defaults. default_bg_blend = 0 @@ -126,9 +129,8 @@ def __init__( { "w": width, "h": height, - "ch_array": ffi.cast("int*", self._ch.ctypes.data), - "fg_array": ffi.cast("TCOD_color_t*", self._fg.ctypes.data), - "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), + "tiles": ffi.cast("struct TCOD_ConsoleTile*", + self._tiles.ctypes.data), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, "fore": (255, 255, 255), @@ -178,20 +180,10 @@ def _init_setup_console_data(self, order: str = "C") -> None: "struct TCOD_Console*", self.console_c ) - def unpack_color(color_data: Any) -> np.array: - """return a (height, width, 3) shaped array from an image struct""" - color_buffer = ffi.buffer(color_data[0 : self.width * self.height]) - array = np.frombuffer(color_buffer, np.uint8) - return array.reshape((self.height, self.width, 3)) - - self._fg = unpack_color(self._console_data.fg_array) - self._bg = unpack_color(self._console_data.bg_array) - - buf = self._console_data.ch_array - buf = ffi.buffer(buf[0 : self.width * self.height]) - self._ch = np.frombuffer(buf, np.intc).reshape( - (self.height, self.width) - ) + self._tiles = np.frombuffer( + ffi.buffer(self._console_data.tiles[0: self.width * self.height]), + dtype=self.DTYPE, + ).reshape((self.height, self.width)) self._order = tcod._internal.verify_order(order) @@ -215,7 +207,10 @@ def bg(self) -> np.array: ``console.bg[x, y, channel] # order='F'``. """ - return self._bg.transpose(1, 0, 2) if self._order == "F" else self._bg + bg = self._tiles["bg"][..., :3] + if self._order == "F": + bg = bg.transpose(1, 0, 2) + return bg @property def fg(self) -> np.array: @@ -226,7 +221,10 @@ def fg(self) -> np.array: Index this array with ``console.fg[i, j, channel] # order='C'`` or ``console.fg[x, y, channel] # order='F'``. """ - return self._fg.transpose(1, 0, 2) if self._order == "F" else self._fg + fg = self._tiles["fg"][..., :3] + if self._order == "F": + fg = fg.transpose(1, 0, 2) + return fg @property def ch(self) -> np.array: @@ -237,7 +235,29 @@ def ch(self) -> np.array: Index this array with ``console.ch[i, j] # order='C'`` or ``console.ch[x, y] # order='F'``. """ - return self._ch.T if self._order == "F" else self._ch + return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] + + @property + def tiles(self) -> np.array: + """An array of this consoles tile data. + + This acts as a combination of the `ch`, `fg`, and `bg` attributes. + Colors include an alpha channel but how alpha works is currently + undefined. + + Example:: + >>> con = tcod.console.Console(10, 2, order="F") + >>> con.tiles[0, 0] = ( + ... ord("X"), + ... (*tcod.white, 255), + ... (*tcod.black, 255), + ... ) + >>> con.tiles[0, 0] + (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) + + .. versionadded:: 10.0 + """ + return self._tiles.T if self._order == "F" else self._tiles @property def default_bg(self) -> Tuple[int, int, int]: @@ -321,9 +341,7 @@ def clear( # type: ignore bg = self.default_bg if bg != (0, 0, 0): self.__clear_warning("bg", bg) - self.ch[...] = ch - self.fg[...] = fg - self.bg[...] = bg + self._tiles[...] = ch, (*fg, 255), (*bg, 255) def put_char( self, @@ -793,41 +811,42 @@ def __getstate__(self) -> Any: "back": self.default_bg, } if self.console_c == ffi.NULL: - state["_ch"] = np.copy(self._ch) - state["_fg"] = np.copy(self._fg) - state["_bg"] = np.copy(self._bg) + state["_tiles"] = np.copy(self._tiles) return state def __setstate__(self, state: Any) -> None: self._key_color = None + if "_tiles" not in state: + tiles = np.ndarray((self.height, self.width), dtype=self.DTYPE) + tiles["ch"] = state["_ch"] + tiles["fg"][..., :3] = state["_fg"] + tiles["fg"][..., 3] = 255 + tiles["bg"][..., :3] = state["_bg"] + tiles["bg"][..., 3] = 255 + state["_tiles"] = tiles + del state["_ch"] + del state["_fg"] + del state["_bg"] + self.__dict__.update(state) - self._console_data.update( - { - "ch_array": ffi.cast("int*", self._ch.ctypes.data), - "fg_array": ffi.cast("TCOD_color_t*", self._fg.ctypes.data), - "bg_array": ffi.cast("TCOD_color_t*", self._bg.ctypes.data), - } - ) + self._console_data["tiles"] = ffi.cast("struct TCOD_ConsoleTile*", + self._tiles.ctypes.data) self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", self._console_data ) def __repr__(self) -> str: """Return a string representation of this console.""" - buffer = np.ndarray((self.height, self.width), dtype=self.DTYPE) - buffer["ch"] = self.ch - buffer["fg"] = self.fg - buffer["bg"] = self.bg return ( "tcod.console.Console(width=%i, height=%i, " "order=%r,buffer=\n%r)" - % (self.width, self.height, self._order, buffer) + % (self.width, self.height, self._order, self.tiles) ) def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" return "<%s>" % "|\n|".join( - "".join(chr(c) for c in line) for line in self._ch + "".join(chr(c) for c in line) for line in self._tiles["ch"] ) def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: From 7e580aed1642722bcb97fc5df0fa79a141590ccb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 28 Mar 2019 17:00:14 -0700 Subject: [PATCH 0130/1101] Add PyPy 7.1 to CI. --- .travis.yml | 3 +++ appveyor.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index bb5e6c5d..327d4db8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,9 @@ matrix: - os: osx language: generic env: PYPY3_VERSION=pypy3.5-v7.0.0 + - os: osx + language: generic + env: PYPY3_VERSION=pypy3.6-v7.1.0 allow_failures: - python: nightly - python: pypy3.5-7.0 diff --git a/appveyor.yml b/appveyor.yml index 893e711b..b7b00ab5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,10 +21,13 @@ environment: platform: Any CPU - PYPY3: pypy3.5-v7.0.0 platform: Any CPU + - PYPY3: pypy3.6-v7.1.0 + platform: Any CPU matrix: allow_failures: - DEPLOY_ONLY: true + - PYPY3: pypy3.6-v7.1.0 clone_depth: 50 cache: From 914f30e8f9f487f48af5c615eb583fa5e75740ad Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 29 Mar 2019 15:09:32 -0700 Subject: [PATCH 0131/1101] Add tag_release.py script. --- scripts/README.md | 1 + scripts/tag_release.py | 73 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 scripts/README.md create mode 100644 scripts/tag_release.py diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..148ae426 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1 @@ +This directory contains scripts which help with the development of python-tcod. diff --git a/scripts/tag_release.py b/scripts/tag_release.py new file mode 100644 index 00000000..e9b55080 --- /dev/null +++ b/scripts/tag_release.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import sys + +import argparse +import datetime +import re +import subprocess +from typing import Any, Tuple + +parser = argparse.ArgumentParser( + description="Tags and releases the next version of this project." +) + +parser.add_argument("tag", help="Semantic version number to use as the tag.") + +parser.add_argument( + "-e", "--edit", action="store_true", help="Force edits of git commits." +) + +parser.add_argument( + "-n", "--dry-run", action="store_true", help="Don't modify files." +) + +parser.add_argument( + "-v", "--verbose", action="store_true", help="Print debug information." +) + + +def parse_changelog(args: Any) -> Tuple[str, str]: + """Return an updated changelog and and the list of changes.""" + with open("CHANGELOG.rst", "r") as file: + match = re.match( + pattern=r"(.*?Unreleased\n---+\n)(.+?)(\n*[^\n]+\n---+\n.*)", + string=file.read(), + flags=re.DOTALL, + ) + assert match + header, changes, tail = match.groups() + tag = "%s - %s" % (args.tag, datetime.date.today().isoformat()) + + tagged = "\n%s\n%s\n%s" % (tag, "-" * len(tag), changes) + if args.verbose: + print(tagged) + + return "".join((header, tagged, tail)), changes + + +def main() -> None: + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + + args = parser.parse_args() + if args.verbose: + print(args) + + new_changelog, changes = parse_changelog(args) + + if not args.dry_run: + with open("CHANGELOG.rst", "w") as f: + f.write(new_changelog) + edit = ["-e"] if args.edit else [] + subprocess.check_call( + ["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit + ) + subprocess.check_call( + ["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] + + edit + ) + + +if __name__ == "__main__": + main() From 85a4f8a716e1cf4189871381ba1951f445b396f4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 29 Mar 2019 15:10:27 -0700 Subject: [PATCH 0132/1101] Prepare 10.0.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 85c5d834..fcc9cfe5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +10.0.0 - 2019-03-29 +------------------- Added - New `Console.tiles` array attribute. Changed From f6eaafcbac2c16266a584eb12741f3159f891b4f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 1 Apr 2019 13:01:50 -0700 Subject: [PATCH 0133/1101] Document modifier key bitmasks. --- tcod/event.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index a964c554..701afcb2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -139,7 +139,39 @@ class KeyboardEvent(Event): scancode (int): The keyboard scan-code, this is the physical location of the key on the keyboard rather than the keys symbol. sym (int): The keyboard symbol. - mod (int): A bitmask of modifier keys. + mod (int): A bitmask of the currently held modifier keys. + + You can use the following to check if a modifier key is held: + + * `tcod.event.KMOD_LSHIFT` + Left shift bit. + * `tcod.event.KMOD_RSHIFT` + Right shift bit. + * `tcod.event.KMOD_LCTRL` + Left control bit. + * `tcod.event.KMOD_RCTRL` + Right control bit. + * `tcod.event.KMOD_LALT` + Left alt bit. + * `tcod.event.KMOD_RALT` + Right alt bit. + * `tcod.event.KMOD_LGUI` + Left meta key bit. + * `tcod.event.KMOD_RGUI` + Right meta key bit. + * `tcod.event.KMOD_SHIFT` + ``tcod.event.KMOD_LSHIFT | tcod.event.KMOD_RSHIFT`` + * `tcod.event.KMOD_CTRL` + ``tcod.event.KMOD_LCTRL | tcod.event.KMOD_RCTRL`` + * `tcod.event.KMOD_ALT` + ``tcod.event.KMOD_LALT | tcod.event.KMOD_RALT`` + * `tcod.event.KMOD_GUI` + ``tcod.event.KMOD_LGUI | tcod.event.KMOD_RGUI`` + + For example, if shift is held then + ``event.mod & tcod.event.KMOD_SHIFT`` will evaluate to a true + value. + repeat (bool): True if this event exists because of key repeat. """ From 5ee301db37164010a4832b312eda5cf8aab24a3e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 1 Apr 2019 13:32:03 -0700 Subject: [PATCH 0134/1101] Add 'byteswap.h' fake include. --- dependencies/fake_libc_include/byteswap.h | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dependencies/fake_libc_include/byteswap.h diff --git a/dependencies/fake_libc_include/byteswap.h b/dependencies/fake_libc_include/byteswap.h new file mode 100644 index 00000000..f952c1d6 --- /dev/null +++ b/dependencies/fake_libc_include/byteswap.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" From 5c0b51acfc408af14da021147c49db7114b27092 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 3 Apr 2019 13:57:31 -0700 Subject: [PATCH 0135/1101] Document the rest of the modifier keys. Resolves #72 --- tcod/event.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tcod/event.py b/tcod/event.py index 701afcb2..8a43fde2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -167,6 +167,12 @@ class KeyboardEvent(Event): ``tcod.event.KMOD_LALT | tcod.event.KMOD_RALT`` * `tcod.event.KMOD_GUI` ``tcod.event.KMOD_LGUI | tcod.event.KMOD_RGUI`` + * `tcod.event.KMOD_NUM` + Num lock bit. + * `tcod.event.KMOD_CAPS` + Caps lock bit. + * `tcod.event.KMOD_MODE` + AltGr key bit. For example, if shift is held then ``event.mod & tcod.event.KMOD_SHIFT`` will evaluate to a true From 67e6130da69f3eaf7f90b91b3c89fd17f9d166c5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 3 Apr 2019 19:53:38 -0700 Subject: [PATCH 0136/1101] Clean up new code. This wasn't run through Black first. --- tcod/console.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 6a5ee2f8..96fb8efc 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -129,8 +129,9 @@ def __init__( { "w": width, "h": height, - "tiles": ffi.cast("struct TCOD_ConsoleTile*", - self._tiles.ctypes.data), + "tiles": ffi.cast( + "struct TCOD_ConsoleTile*", self._tiles.ctypes.data + ), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, "fore": (255, 255, 255), @@ -181,7 +182,7 @@ def _init_setup_console_data(self, order: str = "C") -> None: ) self._tiles = np.frombuffer( - ffi.buffer(self._console_data.tiles[0: self.width * self.height]), + ffi.buffer(self._console_data.tiles[0 : self.width * self.height]), dtype=self.DTYPE, ).reshape((self.height, self.width)) @@ -829,8 +830,9 @@ def __setstate__(self, state: Any) -> None: del state["_bg"] self.__dict__.update(state) - self._console_data["tiles"] = ffi.cast("struct TCOD_ConsoleTile*", - self._tiles.ctypes.data) + self._console_data["tiles"] = ffi.cast( + "struct TCOD_ConsoleTile*", self._tiles.ctypes.data + ) self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", self._console_data ) From d3a1cc0dc14aa30698186f0b4ddbaf1061af9989 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 3 Apr 2019 19:54:29 -0700 Subject: [PATCH 0137/1101] Add dtype with rgb color fields. Maybe to be used at a later time. --- tcod/console.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 96fb8efc..fe80aff5 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -97,8 +97,15 @@ class Console: Added an alpha channel to the color types. """ - DTYPE = np.dtype( # type: Any - [("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")] + DTYPE = np.dtype([("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")]) + + # A structured arrays type with the added "fg_rgb" and "bg_rgb" fields. + _DTYPE_RGB = np.dtype( + { + "names": ["ch", "fg", "bg", "fg_rgb", "bg_rgb"], + "formats": [np.int32, "(4,)u1", "(4,)u1", "(3,)u1", "(3,)u1"], + "offsets": [0, 4, 8, 4, 8], + } ) def __init__( From cb577789b14d674121185a89d3e4180cc1ce1a47 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 13 Apr 2019 19:06:55 -0700 Subject: [PATCH 0138/1101] Add the __sdl_version__ variable. --- tcod/__init__.py | 2 +- tcod/libtcod.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 27d7776c..15dd2c68 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,7 +15,7 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ -from tcod.libtcod import lib, ffi # noqa: F4 +from tcod.libtcod import lib, ffi, __sdl_version__ # noqa: F4 from tcod.libtcodpy import * # noqa: F4 try: diff --git a/tcod/libtcod.py b/tcod/libtcod.py index e9a6473c..299a686a 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -8,12 +8,24 @@ from tcod import __path__ # type: ignore +__sdl_version__ = "" + def get_architecture() -> str: """Return the Windows architecture, one of "x86" or "x64".""" return "x86" if platform.architecture()[0] == "32bit" else "x64" +def get_sdl_version() -> str: + sdl_version = ffi.new("SDL_version*") + lib.SDL_GetVersion(sdl_version) + return "%s.%s.%s" % ( + sdl_version.major, + sdl_version.minor, + sdl_version.patch, + ) + + if sys.platform == "win32": # add Windows dll's to PATH _bits, _linkage = platform.architecture() @@ -68,5 +80,6 @@ def __str__(self) -> Any: file=sys.stderr, ) raise + __sdl_version__ = get_sdl_version() __all__ = ["ffi", "lib"] From ffae91cce76d9ea71f7bda4a137283926a34024a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 18 Apr 2019 06:28:51 -0700 Subject: [PATCH 0139/1101] Update libtcod, add provisional tileset features. --- CHANGELOG.rst | 3 ++ libtcod | 2 +- tcod/cdef.h | 2 +- tcod/tileset.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fcc9cfe5..cae14d49 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed horizontal alignment for TrueType fonts. + - Fixed taking screenshots with the older SDL renderer. 10.0.0 - 2019-03-29 ------------------- diff --git a/libtcod b/libtcod index ab49ad79..2d1d8b14 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ab49ad79663da6925354bdd9cad8ebc5e92a0a48 +Subproject commit 2d1d8b146d250e53b9a5b7a107a896d35317d6d3 diff --git a/tcod/cdef.h b/tcod/cdef.h index e2139b53..89600f6f 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -16,6 +16,6 @@ float _pycall_path_swap_src_dest(int x1, int y1, int x2, int y2, void *user_data); float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data); -void _pycall_sdl_hook(void *); +void _pycall_sdl_hook(struct SDL_Surface*); } diff --git a/tcod/tileset.py b/tcod/tileset.py index fb055a70..6482b9ce 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -2,7 +2,136 @@ """ import os -from tcod.libtcod import lib +from typing import Any, Tuple + +import numpy as np + +from tcod.libtcod import lib, ffi + + +class Tileset: + """A collection of graphical tiles. + + This class is provisional, the API may change in the future. + """ + + def __init__(self, tile_width: int, tile_height: int) -> None: + self._tileset_p = ffi.gc( + lib.TCOD_tileset_new(tile_width, tile_height), + lib.TCOD_tileset_delete, + ) + + @classmethod + def _claim(cls, cdata: Any) -> "Tileset": + """Return a new Tileset that owns the provided TCOD_Tileset* object.""" + self = object.__new__(cls) # type: Tileset + if cdata == ffi.NULL: + raise RuntimeError("Tileset initialized with nullptr.") + self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete) + return self + + @property + def tile_width(self) -> int: + """The width of the tile in pixels.""" + return int(lib.TCOD_tileset_get_tile_width_(self._tileset_p)) + + @property + def tile_height(self) -> int: + """The height of the tile in pixels.""" + return int(lib.TCOD_tileset_get_tile_height_(self._tileset_p)) + + @property + def tile_shape(self) -> Tuple[int, int]: + """The shape (height, width) of the tile in pixels.""" + return self.tile_height, self.tile_width + + def __contains__(self, codepoint: int) -> bool: + """Test if a tileset has a codepoint with ``n in tileset``.""" + return bool( + lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) + == 0 + ) + + def get_tile(self, codepoint: int) -> np.array: + """Return a copy of a tile for the given codepoint. + + If the tile does not exist yet then a blank array will be returned. + + The tile will have a shape of (height, width, rgba) and a dtype of + uint8. Note that most grey-scale tiles will only use the alpha + channel and will usually have a solid white color channel. + """ + tile = np.zeros(self.tile_shape + (4,), dtype=np.uint8) + lib.TCOD_tileset_get_tile_( + self._tileset_p, + codepoint, + ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data), + ) + return tile + + def set_tile(self, codepoint: int, tile: np.array) -> None: + """Upload a tile into this array. + + The tile can be in 32-bit color (height, width, rgba), or grey-scale + (height, width). The tile should have a dtype of ``np.uint8``. + + This data may need to be sent to graphics card memory, this is a slow + operation. + """ + tile = np.ascontiguousarray(tile, dtype=np.uint8) + if tile.shape == self.tile_shape: + full_tile = np.empty(self.tile_shape + (4,), dtype=np.uint8) + full_tile[:, :, :3] = 255 + full_tile[:, :, 3] = tile + return self.set_tile(codepoint, full_tile) + required = self.tile_shape + (4,) + if tile.shape != required: + raise ValueError( + "Tile shape must be %r or %r, got %r." + % (required, self.tile_shape, tile.shape) + ) + lib.TCOD_tileset_set_tile_( + self._tileset_p, + codepoint, + ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data), + ) + + +def get_default() -> Tileset: + """Return a reference to the default Tileset. + + This function is provisional. The API may change. + """ + return Tileset._claim(lib.TCOD_get_default_tileset()) + + +def set_default(tileset: Tileset) -> None: + """Set the default tileset. + + The display will use this new tileset immediately. + + This function only affects the `SDL2` and `OPENGL2` renderers. + + This function is provisional. The API may change. + """ + lib.TCOD_set_default_tileset(tileset._tileset_p) + + +def load_truetype_font( + path: str, tile_width: int, tile_height: int +) -> Tileset: + """Return a new Tileset from a `.ttf` or `.otf` file. + + Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead. + You can send this Tileset to :any:`set_default`. + + This function is provisional. The API may change. + """ + if not os.path.exists(path): + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) + return Tileset._claim( + lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) + ) def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: @@ -11,8 +140,8 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: `path` is the file path for the font file. `tile_width` and `tile_height` are the desired size of the tiles in the new - tileset. The font will be scaled to fit the `tile_height` and may be - clipped to fit inside of the `tile_width`. + tileset. The font will be scaled to fit the given `tile_height` and + `tile_width`. This function will only affect the `SDL2` and `OPENGL2` renderers. From 8ba10c5cfb813eaf3e834de971ba2d6acb7838e4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 19 Apr 2019 20:34:44 -0700 Subject: [PATCH 0140/1101] Prepare 10.0.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cae14d49..7438ae9d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +10.0.1 - 2019-04-19 +------------------- Fixed - Fixed horizontal alignment for TrueType fonts. - Fixed taking screenshots with the older SDL renderer. From db516f0b58182a09592c571c334689a9fc70aee3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 22 Apr 2019 02:28:39 -0700 Subject: [PATCH 0141/1101] Fix Color warnings for non-deprecated code. --- CHANGELOG.rst | 2 ++ tcod/color.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7438ae9d..1ab7ca40 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Resolved Color warning when importing tcod. 10.0.1 - 2019-04-19 ------------------- diff --git a/tcod/color.py b/tcod/color.py index ceb886f9..f9cd85a2 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -17,8 +17,8 @@ class Color(List[int]): b (int): Blue value, from 0 to 255. """ - def __init__(self, r: int = 0, g: int = 0, b: int = 0): - self[:] = (r & 0xFF, g & 0xFF, b & 0xFF) + def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> "Color": + list.__setitem__(self, slice(None), (r & 0xFF, g & 0xFF, b & 0xFF)) @property def r(self) -> int: From a45ad1276074bf71df17779978b90ba0021a9dac Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 26 Apr 2019 19:04:38 -0700 Subject: [PATCH 0142/1101] Prepare 10.0.2 release. --- CHANGELOG.rst | 6 +++++- libtcod | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1ab7ca40..fb874a3c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,12 @@ v2.0.0 Unreleased ------------------ + +10.0.2 - 2019-04-26 +------------------- Fixed - - Resolved Color warning when importing tcod. + - Resolved Color warnings when importing tcod. + - When compiling, fixed a name conflict with endianness macros on FreeBSD. 10.0.1 - 2019-04-19 ------------------- diff --git a/libtcod b/libtcod index 2d1d8b14..84e7c209 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 2d1d8b146d250e53b9a5b7a107a896d35317d6d3 +Subproject commit 84e7c209f53ef69d54fb06a64fa7fe9130584ea4 From 807a469a331bb9bb369ea9b4004b01f1687fe8ae Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 27 Apr 2019 00:46:30 -0700 Subject: [PATCH 0143/1101] Ignore package version when it's undetectable. --- setup.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 03967a31..71d995b4 100755 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ from subprocess import check_output import platform +import warnings from distutils.unixccompiler import UnixCCompiler @@ -49,8 +50,15 @@ def get_version(): open("tcod/version.py", "w").write('__version__ = "%s"\n' % version) return version except: - exec(open("tcod/version.py").read(), globals()) - return __version__ + try: + exec(open("tcod/version.py").read(), globals()) + return __version__ + except FileNotFoundError: + warnings.warn( + "Unknown version: " + "Not in a Git repository and not from a sdist bundle or wheel." + ) + return "0.0.0" is_pypy = platform.python_implementation() == "PyPy" From 62ad9f8c465aefebc20977cd0f90a8b77be61a58 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 27 Apr 2019 04:29:18 -0700 Subject: [PATCH 0144/1101] Force removal of alloca declaration when parsing headers. --- build_libtcod.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build_libtcod.py b/build_libtcod.py index f3d9a9d5..b3fdcc3f 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -233,7 +233,8 @@ def visit_Decl(self, node): elif ( node.name and "vsprint" in node.name - or node.name in ["SDL_vsscanf", "SDL_vsnprintf", "SDL_LogMessageV"] + or node.name + in ["SDL_vsscanf", "SDL_vsnprintf", "SDL_LogMessageV", "alloca"] ): # exclude va_list related functions self.ast.ext.remove(node) From ff3dda2388b09a688e5e51ba79777831fb8b1563 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 9 May 2019 01:59:38 -0700 Subject: [PATCH 0145/1101] Fixed Console.print_box bounding issues. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb874a3c..1da840c3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Corrected bounding box issues with the `Console.print_box` method. 10.0.2 - 2019-04-26 ------------------- diff --git a/libtcod b/libtcod index 84e7c209..7e28baf9 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 84e7c209f53ef69d54fb06a64fa7fe9130584ea4 +Subproject commit 7e28baf9791aaea64e2f30bfb3308532467d93a7 From f834caf477d2a2f22c0ab85ad296305b40903260 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 9 May 2019 02:23:42 -0700 Subject: [PATCH 0146/1101] Skip broken version of NumPy. Fixes PyPy3 builds on Unix. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d4b7b762..87e7f2fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ cffi>=1.8.1,<2 -numpy>=1.10,<2; +numpy>=1.10,<2,!=1.16.3; pycparser>=2.14,<3 -setuptools>=36.0.1 \ No newline at end of file +setuptools>=36.0.1 From 796a79239567c477ba5a7e67855fbfc5d0422e2e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 10 May 2019 19:50:56 -0700 Subject: [PATCH 0147/1101] Prepare 10.0.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1da840c3..ec65e4f0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +10.0.3 - 2019-05-10 +------------------- Fixed - Corrected bounding box issues with the `Console.print_box` method. From 53a16f734988393d64337bdfe4aaa55c52188ade Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 11 May 2019 13:26:18 -0700 Subject: [PATCH 0148/1101] Use libtcod's new error handling instead of crashing. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- tcod/libtcodpy.py | 8 +++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec65e4f0..28859d96 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - `tcod.console_init_root` and `tcod.console_set_custom_font` can now raise + exceptions instead of crashing. 10.0.3 - 2019-05-10 ------------------- diff --git a/libtcod b/libtcod index 7e28baf9..b015ea4b 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 7e28baf9791aaea64e2f30bfb3308532467d93a7 +Subproject commit b015ea4bcd1847508be02bb93625ab576bdfdc4a diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 786c7d62..083fc277 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -929,7 +929,8 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) - lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer) + if lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer): + raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) console = tcod.console.Console._get_root(order) console.clear() return console @@ -966,9 +967,10 @@ def console_set_custom_font( raise RuntimeError( "File not found:\n\t%s" % (os.path.realpath(fontFile),) ) - lib.TCOD_console_set_custom_font( + if lib.TCOD_console_set_custom_font( _bytes(fontFile), flags, nb_char_horiz, nb_char_vertic - ) + ): + raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) @deprecate("Check `con.width` instead.") From 55dc8490c8a70ba93548eb2dc327305b7ceaa157 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 13 May 2019 13:02:11 -0700 Subject: [PATCH 0149/1101] Refactor SDL2 header parser code. Fixes #74 --- CHANGELOG.rst | 4 ++ build_libtcod.py | 27 +++++-- parse_sdl2.py | 171 +++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- requirements.txt | 2 +- setup.py | 2 +- tcod/cffi.h | 8 --- tests/test_tcod.py | 6 ++ 8 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 parse_sdl2.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 28859d96..67012a65 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,9 +8,13 @@ v2.0.0 Unreleased ------------------ +Changed + - Now depends on cffi 0.12 or later. + Fixed - `tcod.console_init_root` and `tcod.console_set_custom_font` can now raise exceptions instead of crashing. + - Fixed issues preventing `tcod.event` from working on 32-bit Windows. 10.0.3 - 2019-05-10 ------------------- diff --git a/build_libtcod.py b/build_libtcod.py index b3fdcc3f..25efea3a 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -15,6 +15,8 @@ import platform import zipfile +import parse_sdl2 + try: from urllib import urlretrieve except ImportError: @@ -25,8 +27,6 @@ # The SDL2 version to include in binary distributions. SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.9") -TDL_NO_SDL2_EXPORTS = os.environ.get("TDL_NO_SDL2_EXPORTS", "0") == "1" - CFFI_HEADER = "tcod/cffi.h" CFFI_EXTRA_CDEFS = "tcod/cdef.h" @@ -106,9 +106,6 @@ def unpack_sdl2(version): sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] sources += glob.glob("libtcod/src/vendor/zlib/*.c") -if TDL_NO_SDL2_EXPORTS: - extra_parse_args.append("-DTDL_NO_SDL2_EXPORTS") - if sys.platform == "win32": libraries += ["User32", "OpenGL32"] define_macros.append(("TCODLIB_API", "")) @@ -131,7 +128,21 @@ def unpack_sdl2(version): include_dirs.append("libtcod/src/zlib/") if sys.platform == "win32": - include_dirs.append(os.path.join(SDL2_PARSE_PATH, "include")) + SDL2_INCLUDE = os.path.join(SDL2_PARSE_PATH, "include") +elif sys.platform == "darwin": + SDL2_INCLUDE = os.path.join(SDL2_PARSE_PATH, "Versions/A/Headers") +else: + match = re.match( + r".*-I(\S+)", + subprocess.check_output( + ["sdl2-config", "--cflags"], universal_newlines=True + ) + ) + assert match + SDL2_INCLUDE, = match.groups() + +if sys.platform == "win32": + include_dirs.append(SDL2_INCLUDE) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} SDL2_LIB_DIR = os.path.join(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) @@ -324,11 +335,13 @@ def get_ast(): extra_link_args.append("-fopenmp") ffi = FFI() +parse_sdl2.add_to_ffi(ffi, SDL2_INCLUDE) +#ffi.include(parse_sdl2.get_ffi(SDL2_INCLUDE)) ffi.cdef(get_cdef()) ffi.cdef(open(CFFI_EXTRA_CDEFS, "r").read()) ffi.set_source( module_name, - "#include ", + "#include \n#include ", include_dirs=include_dirs, library_dirs=library_dirs, sources=sources, diff --git a/parse_sdl2.py b/parse_sdl2.py new file mode 100644 index 00000000..3c38d07f --- /dev/null +++ b/parse_sdl2.py @@ -0,0 +1,171 @@ +import os.path +import sys + +import platform +import re +from typing import Iterator + +import cffi # type: ignore + +# Various poorly made regular expressions, these will miss code which isn't +# supported by cffi. +RE_COMMENT = re.compile(r" */\*.*?\*/", re.DOTALL) +RE_REMOVALS = re.compile( + r"#ifndef DOXYGEN_SHOULD_IGNORE_THIS.*" + r"#endif /\* DOXYGEN_SHOULD_IGNORE_THIS \*/", + re.DOTALL, +) +RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) +RE_TYPEDEF = re.compile(r"typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) +RE_DECL = re.compile(r"^extern[^#\n]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) +RE_ENDIAN = re.compile( + r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL +) +RE_DEFINE_TRUNCATE = re.compile(r"(#define\s+\w+\s+).+$", flags=re.DOTALL) +RE_TYPEDEF_TRUNCATE = re.compile( + r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE +) + + +def get_header(name: str) -> str: + """Return the source of a header in a partially preprocessed state.""" + with open(name, "r") as f: + header = f.read() + # Remove Doxygen code. + header = RE_REMOVALS.sub("", header) + # Remove comments. + header = RE_COMMENT.sub("", header) + # Deal with endianness in "SDL_audio.h". + header = RE_ENDIAN.sub( + r"\1" if sys.byteorder == "little" else r"\2", header + ) + # Ignore bad ARM compiler typedef. + header = header.replace("typedef int SDL_bool;", "") + return header + + +# Remove non-integer definitions. +DEFINE_BLACKLIST = [ + "SDL_AUDIOCVT_PACKED", + "SDL_BlitScaled", + "SDL_BlitSurface", + "SDL_Colour", +] + + +def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: + """Pull individual sections from a header, processing them as needed.""" + for define in RE_DEFINE.findall(header): + if any(item in define for item in DEFINE_BLACKLIST): + continue # Remove non-integer definitions. + if '"' in define: + continue # Ignore definitions with strings. + # Replace various definitions with "..." since cffi is limited here. + yield RE_DEFINE_TRUNCATE.sub(r"\1 ...", define) + + for typedef in RE_TYPEDEF.findall(header): + # Special case for SDL window flags enum. + if "SDL_WINDOW_FULLSCREEN_DESKTOP" in typedef: + typedef = typedef.replace( + "( SDL_WINDOW_FULLSCREEN | 0x00001000 )", "..." + ) + # Detect array sizes at compile time. + typedef = typedef.replace("SDL_TEXTINPUTEVENT_TEXT_SIZE", "...") + typedef = typedef.replace("SDL_TEXTEDITINGEVENT_TEXT_SIZE", "...") + typedef = typedef.replace("SDL_AUDIOCVT_MAX_FILTERS + 1", "...") + + typedef = typedef.replace("SDLCALL ", "") + typedef = typedef.replace("SDL_AUDIOCVT_PACKED ", "") + + if NEEDS_PACK4 and "typedef struct SDL_AudioCVT" in typedef: + typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) + if NEEDS_PACK4 and "typedef struct SDL_TouchFingerEvent" in typedef: + typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) + if NEEDS_PACK4 and "typedef struct SDL_MultiGestureEvent" in typedef: + typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) + if NEEDS_PACK4 and "typedef struct SDL_DollarGestureEvent" in typedef: + typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) + yield typedef + + for decl in RE_DECL.findall(header): + if "SDL_RWops" in decl: + continue # Ignore SDL_RWops functions. + if "va_list" in decl: + continue + decl = re.sub(r"SDL_PRINTF_VARARG_FUNC\(\w*\)", "", decl) + decl = decl.replace("extern DECLSPEC ", "") + decl = decl.replace("SDLCALL ", "") + yield decl.replace("SDL_PRINTF_FORMAT_STRING ", "") + + +# Parsed headers excluding "SDL_stdinc.h" +HEADERS = [ + "SDL_rect.h", + "SDL_pixels.h", + "SDL_blendmode.h", + "SDL_error.h", + "SDL_surface.h", + "SDL_video.h", + "SDL_render.h", + "SDL_audio.h", + "SDL_clipboard.h", + "SDL_touch.h", + "SDL_gesture.h", + "SDL_hints.h", + "SDL_joystick.h", + "SDL_haptic.h", + "SDL_gamecontroller.h", + "SDL_power.h", + "SDL_log.h", + "SDL_messagebox.h", + "SDL_mouse.h", + "SDL_timer.h", + "SDL_keycode.h", + "SDL_scancode.h", + "SDL_keyboard.h", + "SDL_events.h", + "SDL.h", + "SDL_version.h", +] + + +def add_to_ffi(ffi: cffi.FFI, path: str) -> None: + BITS, _ = platform.architecture() + cdef_args = {} + NEEDS_PACK4 = False + if sys.platform == "win32" and BITS == "32bit": + NEEDS_PACK4 = True + # The following line is required but cffi does not currently support + # it for ABI mode. + # cdef_args["pack"] = 4 + + ffi.cdef( + "\n".join( + RE_TYPEDEF.findall(get_header(os.path.join(path, "SDL_stdinc.h"))) + ).replace("SDLCALL ", ""), + **cdef_args + ) + for header in HEADERS: + try: + for code in parse( + get_header(os.path.join(path, header)), NEEDS_PACK4 + ): + if ( + "typedef struct SDL_AudioCVT" in code + and sys.platform != "win32" + and not NEEDS_PACK4 + ): + # This specific struct needs to be packed. + ffi.cdef(code, packed=1) + continue + ffi.cdef(code, **cdef_args) + except: + print("Error parsing %r code:\n%s" % (header, code)) + raise + + +def get_ffi(path: str) -> cffi.FFI: + """Return an ffi for SDL2, needs to be compiled.""" + ffi = cffi.FFI() + add_to_ffi(ffi, path) + return ffi diff --git a/pyproject.toml b/pyproject.toml index 0aeaca41..21235e52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools", "wheel", - "cffi>=1.8.1,<2", + "cffi>=1.12.0,<2", "pycparser>=2.14,<3", ] diff --git a/requirements.txt b/requirements.txt index 87e7f2fb..c7292a91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cffi>=1.8.1,<2 +cffi>=1.12.0,<2 numpy>=1.10,<2,!=1.16.3; pycparser>=2.14,<3 setuptools>=36.0.1 diff --git a/setup.py b/setup.py index 71d995b4..44725ad1 100755 --- a/setup.py +++ b/setup.py @@ -125,7 +125,7 @@ def get_long_description(): package_data={"tdl": ["*.png"], "tcod": get_package_data()}, python_requires=">=3.5", install_requires=[ - "cffi>=1.8.1,<2", + "cffi>=1.12.0,<2", "numpy>=1.10,<2" if not is_pypy else "", ], cffi_modules=["build_libtcod.py:ffi"], diff --git a/tcod/cffi.h b/tcod/cffi.h index 923a8797..da766a46 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -1,13 +1,5 @@ - /* This header is the entry point for the cffi parser. Anything included here will be accessible from tcod.libtcod.lib */ - -#ifndef TDL_NO_SDL2_EXPORTS -/* Ignore headers with issues. */ -#define SDL_thread_h_ -#include -#endif - #include "../libtcod/src/libtcod/libtcod.h" #include "../libtcod/src/libtcod/libtcod_int.h" #include "../libtcod/src/libtcod/wrappers.h" diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 6d58fc3e..c5690ce2 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -225,3 +225,9 @@ def test_mouse_repr(): mouse_copy = eval(repr(mouse)) assert mouse.x == mouse_copy.x assert mouse.lbutton == mouse_copy.lbutton + + +def test_cffi_structs(): + # Make sure cffi structures are the correct size. + tcod.ffi.new("SDL_Event*") + tcod.ffi.new("SDL_AudioCVT*") From f5cf8e52cad813c2f7c65c7f890d5e66101b152f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 16 May 2019 08:06:47 -0700 Subject: [PATCH 0150/1101] Added a fixed aspect example. --- examples/fixed_aspect.py | 58 ++++++++++++++++++++++++++++++++++++++++ libtcod | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 examples/fixed_aspect.py diff --git a/examples/fixed_aspect.py b/examples/fixed_aspect.py new file mode 100644 index 00000000..78216454 --- /dev/null +++ b/examples/fixed_aspect.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Render a console with a fixed aspect ratio. + +This script bypasses tcod.console_flush to manually adjust where the console +will be rendered. It uses an FFI to call libtcod and SDL2 functions directly. +""" +from typing import Any, Tuple + +import tcod +import tcod.event + + +def get_renderer_size(sdl_renderer: Any) -> Tuple[int, int]: + """Return the renderer size as a (width, height) tuple.""" + renderer_size = tcod.ffi.new("int[2]") + tcod.lib.SDL_GetRendererOutputSize( + sdl_renderer, renderer_size, renderer_size + 1 + ) + return renderer_size[0], renderer_size[1] + + +def get_viewport(sdl_renderer: Any, aspect: Tuple[int, int]) -> Any: + """Return an SDL_Rect object that will fit this renderer with this aspect + ratio.""" + current_size = get_renderer_size(sdl_renderer) + scale = min(x / y for x, y in zip(current_size, aspect)) + view_size = [round(x * scale) for x in aspect] + view_offset = [(x - y) // 2 for x, y in zip(current_size, view_size)] + return tcod.ffi.new("SDL_Rect*", (*view_offset, *view_size)) + + +def main() -> None: + with tcod.console_init_root(20, 4, renderer=tcod.RENDERER_SDL2) as console: + # Get the SDL2 objects setup by libtcod. + sdl_window = tcod.lib.TCOD_sys_get_sdl_window() + sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() + # Aspect is generally console_size * tile_size. + aspect: Tuple[int, int] = get_renderer_size(sdl_renderer) + console.print_box(0, 0, 0, 0, "Console with a fixed aspect ratio.") + while True: + # Clear background with white. + tcod.lib.SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, 255) + tcod.lib.SDL_RenderClear(sdl_renderer) + # Accumulate console graphics. + # This next function is provisional, the API is not stable. + tcod.lib.TCOD_sys_accumulate_console_( + console.console_c, get_viewport(sdl_renderer, aspect) + ) + # Present the SDL2 renderer to the display. + tcod.lib.SDL_RenderPresent(sdl_renderer) + + for event in tcod.event.wait(): + if event.type == "QUIT": + raise SystemExit() + + +if __name__ == "__main__": + main() diff --git a/libtcod b/libtcod index b015ea4b..b45dffeb 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit b015ea4bcd1847508be02bb93625ab576bdfdc4a +Subproject commit b45dffeb3dfca6f0f00c3ead8ab94a632b10b0c9 From da297fba9c2976e3ccc7af9ac1d670daa340800f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 16 May 2019 13:32:08 -0700 Subject: [PATCH 0151/1101] Fix PyPy job on AppVeyor. --- .appveyor/install_python.ps1 | 6 +----- appveyor.yml | 7 +++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index 18fea56c..a8260765 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -23,10 +23,6 @@ if ($env:WEB_PYTHON) { & $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=16" & $env:PYTHON -m virtualenv venv -if ($env:PYPY) { - $env:ACTIVATE_VENV='venv\bin\activate.bat' -} else { - $env:ACTIVATE_VENV='venv\Scripts\activate.bat' -} +$env:ACTIVATE_VENV='venv\Scripts\activate.bat' if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } diff --git a/appveyor.yml b/appveyor.yml index b7b00ab5..8b4426b3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,9 +30,6 @@ matrix: - PYPY3: pypy3.6-v7.1.0 clone_depth: 50 -cache: - - '%localappdata%\pip\cache -> setup.py' - init: - cmd: "if %APPVEYOR_REPO_TAG%==false if %DEPLOY_ONLY%==true exit /b 1" @@ -40,8 +37,10 @@ install: - cmd: "git submodule update --init --recursive" - ps: ". .appveyor/install_python.ps1" - cmd: "%ACTIVATE_VENV%" -- cmd: "python --version" +- cmd: "set PATH=%APPVEYOR_BUILD_FOLDER%\\venv\\bin;%PATH%" - cmd: "set PATH=%PATH%;C:\\MinGW\\bin" +- cmd: "python --version" +- cmd: "pip --version" - cmd: "pip install --upgrade setuptools" - cmd: "pip install --requirement requirements.txt" - cmd: "python setup.py build sdist develop bdist_wheel" From 9f461f66cce2bab79c4225312f1c9eff4e4f5588 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 May 2019 07:45:28 -0700 Subject: [PATCH 0152/1101] Update custom renderer example. --- examples/custrender.py | 147 +++++++++++++++++++++++++++++++++++++++ examples/fixed_aspect.py | 58 --------------- 2 files changed, 147 insertions(+), 58 deletions(-) create mode 100644 examples/custrender.py delete mode 100644 examples/fixed_aspect.py diff --git a/examples/custrender.py b/examples/custrender.py new file mode 100644 index 00000000..15bb1215 --- /dev/null +++ b/examples/custrender.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for this script. This work is +# published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +"""A custom rendering engine for python-tcod. + +This module bypasses tcod.console_flush to manually adjust where the console +will be rendered. It uses the FFI to call libtcod and SDL2 functions directly. + +It can be extend to allow arbitrary rendering on top of the SDL renderer. + +It is also designed to be copied into your own project and imported as a +module. +""" +from typing import Optional, Tuple + +import tcod +import tcod.tileset +import tcod.event + +assert tcod.__version__ > "10.0.3", tcod.__version__ + + +def get_renderer_size() -> Tuple[int, int]: + """Return the renderer output size as a (width, height) tuple.""" + sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() + assert sdl_renderer + renderer_size = tcod.ffi.new("int[2]") + tcod.lib.SDL_GetRendererOutputSize( + sdl_renderer, renderer_size, renderer_size + 1 + ) + return renderer_size[0], renderer_size[1] + + +def get_viewport( + console: tcod.console.Console, + correct_aspect: bool = False, + integer_scale: bool = False, +) -> Tuple[int, int, int, int]: + """Return a viewport which follows the given constants. + + `console` is a Console object, it is used as reference for what the correct + aspect should be. The default tileset from `tcod.tileset` is also used as + a reference for the current font size. + + If `correct_aspect` is True then the viewport will be letter-boxed to fit + the screen instead of stretched. + + If `integer_scale` is True then the viewport to be scaled in integer + proportions, this is ignored when the screen is too small. + """ + assert tcod.sys_get_renderer() == tcod.RENDERER_SDL2 + sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() + assert sdl_renderer + tileset = tcod.tileset.get_default() + aspect = (console.width * tileset.tile_width, + console.height * tileset.tile_height) + renderer_size = get_renderer_size() + scale = renderer_size[0] / aspect[0], renderer_size[1] / aspect[1] + if correct_aspect: + scale = min(scale), min(scale) + if integer_scale: + scale = (int(scale[0]) if scale[0] >= 1 else scale[0], + int(scale[1]) if scale[1] >= 1 else scale[1]) + view_size = aspect[0] * scale[0], aspect[1] * scale[1] + view_offset = ((renderer_size[0] - view_size[0]) // 2, + (renderer_size[1] - view_size[1]) // 2) + return tuple(int(x) for x in (*view_offset, *view_size)) # type: ignore + # https://github.com/python/mypy/issues/224 + + +def clear(color: Tuple[int, int, int]) -> None: + """Clear the SDL renderer held by libtcod with a clear color.""" + sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() + assert sdl_renderer + tcod.lib.SDL_SetRenderDrawColor(sdl_renderer, *color, 255) + tcod.lib.SDL_RenderClear(sdl_renderer) + + +def present() -> None: + """Present the SDL renderer held by libtcod to the screen.""" + sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() + assert sdl_renderer + tcod.lib.SDL_RenderPresent(sdl_renderer) + + +def accumulate( + console: tcod.console.Console, + viewport: Optional[Tuple[int, int, int, int]] = None, +) -> None: + """Render a console to SDL's renderer. + + `console` is the console to renderer. Background alpha is supported and + well defined. Foregound alpha is also supported, but not as well-defined. + The `default tileset` will be used for graphics. + + `viewport` is where to draw the console on the screen. If it is None then + the console will be stretched over the full screen. You can use + `get_viewport` to make a viewport with specific constraints. + + You will need to call `present` yourself to show the rendered console, if + the viewport does not cover the full screen then you'll need to call + `clear` beforehand to clear the pixels outside of the viewport. + + This function can be called multiple times, but the current implementation + is optimized to handle only one console. Keep this in mind when rendering + multiple different consoles. + + This function depends on a provisional function of the libtcod API. You + may want to pin your exact version of python-tcod to prevent a break. + """ + assert tcod.sys_get_renderer() \ + in (tcod.RENDERER_SDL2, tcod.RENDERER_OPENGL2) + if viewport is None: + viewport = tcod.ffi.NULL + else: + viewport = tcod.ffi.new("struct SDL_Rect*", viewport) + tcod.lib.TCOD_sys_accumulate_console_(console.console_c, viewport) + + +def main() -> None: + """An example of of the use of this module.""" + with tcod.console_init_root(20, 4, renderer=tcod.RENDERER_SDL2) as console: + TEXT = "Console with a fixed aspect ratio and integer scaling." + console.print_box(0, 0, 0, 0, TEXT) + while True: + # Clear background with white. + clear((255, 255, 255)) + # Draw the console to SDL's buffer. + accumulate(console, get_viewport(console, True, True)) + # If you want you can use the FFI to do additional drawing here: + ... + # Present the SDL2 renderer to the display. + present() + + for event in tcod.event.wait(): + if event.type == "QUIT": + raise SystemExit() + elif event.type == "WINDOWRESIZED": + # You can change to a console of a different size in + # response to a WINDOWRESIZED event if you want. + ... + + +if __name__ == "__main__": + main() diff --git a/examples/fixed_aspect.py b/examples/fixed_aspect.py deleted file mode 100644 index 78216454..00000000 --- a/examples/fixed_aspect.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -"""Render a console with a fixed aspect ratio. - -This script bypasses tcod.console_flush to manually adjust where the console -will be rendered. It uses an FFI to call libtcod and SDL2 functions directly. -""" -from typing import Any, Tuple - -import tcod -import tcod.event - - -def get_renderer_size(sdl_renderer: Any) -> Tuple[int, int]: - """Return the renderer size as a (width, height) tuple.""" - renderer_size = tcod.ffi.new("int[2]") - tcod.lib.SDL_GetRendererOutputSize( - sdl_renderer, renderer_size, renderer_size + 1 - ) - return renderer_size[0], renderer_size[1] - - -def get_viewport(sdl_renderer: Any, aspect: Tuple[int, int]) -> Any: - """Return an SDL_Rect object that will fit this renderer with this aspect - ratio.""" - current_size = get_renderer_size(sdl_renderer) - scale = min(x / y for x, y in zip(current_size, aspect)) - view_size = [round(x * scale) for x in aspect] - view_offset = [(x - y) // 2 for x, y in zip(current_size, view_size)] - return tcod.ffi.new("SDL_Rect*", (*view_offset, *view_size)) - - -def main() -> None: - with tcod.console_init_root(20, 4, renderer=tcod.RENDERER_SDL2) as console: - # Get the SDL2 objects setup by libtcod. - sdl_window = tcod.lib.TCOD_sys_get_sdl_window() - sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() - # Aspect is generally console_size * tile_size. - aspect: Tuple[int, int] = get_renderer_size(sdl_renderer) - console.print_box(0, 0, 0, 0, "Console with a fixed aspect ratio.") - while True: - # Clear background with white. - tcod.lib.SDL_SetRenderDrawColor(sdl_renderer, 255, 255, 255, 255) - tcod.lib.SDL_RenderClear(sdl_renderer) - # Accumulate console graphics. - # This next function is provisional, the API is not stable. - tcod.lib.TCOD_sys_accumulate_console_( - console.console_c, get_viewport(sdl_renderer, aspect) - ) - # Present the SDL2 renderer to the display. - tcod.lib.SDL_RenderPresent(sdl_renderer) - - for event in tcod.event.wait(): - if event.type == "QUIT": - raise SystemExit() - - -if __name__ == "__main__": - main() From 778da4a620fed622bb89617a6befc1098218df0d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 May 2019 08:55:32 -0700 Subject: [PATCH 0153/1101] Prepare 10.0.4 release. --- CHANGELOG.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 67012a65..f8525b7a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,12 +8,15 @@ v2.0.0 Unreleased ------------------ + +10.0.4 - 2019-05-17 +------------------- Changed - Now depends on cffi 0.12 or later. Fixed - - `tcod.console_init_root` and `tcod.console_set_custom_font` can now raise - exceptions instead of crashing. + - `tcod.console_init_root` and `tcod.console_set_custom_font` will raise + exceptions instead of terminating. - Fixed issues preventing `tcod.event` from working on 32-bit Windows. 10.0.3 - 2019-05-10 From e9f710d01c34b0f999269d832ec52a9734e030dd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 May 2019 09:07:39 -0700 Subject: [PATCH 0154/1101] Better formatting for custrenderer.py. --- examples/custrender.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/custrender.py b/examples/custrender.py index 15bb1215..3807ae07 100644 --- a/examples/custrender.py +++ b/examples/custrender.py @@ -54,18 +54,24 @@ def get_viewport( sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() assert sdl_renderer tileset = tcod.tileset.get_default() - aspect = (console.width * tileset.tile_width, - console.height * tileset.tile_height) + aspect = ( + console.width * tileset.tile_width, + console.height * tileset.tile_height, + ) renderer_size = get_renderer_size() scale = renderer_size[0] / aspect[0], renderer_size[1] / aspect[1] if correct_aspect: scale = min(scale), min(scale) if integer_scale: - scale = (int(scale[0]) if scale[0] >= 1 else scale[0], - int(scale[1]) if scale[1] >= 1 else scale[1]) + scale = ( + int(scale[0]) if scale[0] >= 1 else scale[0], + int(scale[1]) if scale[1] >= 1 else scale[1], + ) view_size = aspect[0] * scale[0], aspect[1] * scale[1] - view_offset = ((renderer_size[0] - view_size[0]) // 2, - (renderer_size[1] - view_size[1]) // 2) + view_offset = ( + (renderer_size[0] - view_size[0]) // 2, + (renderer_size[1] - view_size[1]) // 2, + ) return tuple(int(x) for x in (*view_offset, *view_size)) # type: ignore # https://github.com/python/mypy/issues/224 @@ -110,8 +116,10 @@ def accumulate( This function depends on a provisional function of the libtcod API. You may want to pin your exact version of python-tcod to prevent a break. """ - assert tcod.sys_get_renderer() \ - in (tcod.RENDERER_SDL2, tcod.RENDERER_OPENGL2) + assert tcod.sys_get_renderer() in ( + tcod.RENDERER_SDL2, + tcod.RENDERER_OPENGL2, + ) if viewport is None: viewport = tcod.ffi.NULL else: From b60ef81c912975d2dc36d72a9b62b3989cc58ace Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 May 2019 14:29:21 -0700 Subject: [PATCH 0155/1101] Better error handling for TTF support. --- libtcod | 2 +- tcod/tileset.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libtcod b/libtcod index b45dffeb..60d05025 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit b45dffeb3dfca6f0f00c3ead8ab94a632b10b0c9 +Subproject commit 60d05025da900a8deae4f35be89588f57a5b94c6 diff --git a/tcod/tileset.py b/tcod/tileset.py index 6482b9ce..1f6dbf45 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -129,9 +129,10 @@ def load_truetype_font( """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - return Tileset._claim( - lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) - ) + cdata = lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) + if not cdata: + raise RuntimeError(ffi.string(lib.TCOD_get_error())) + return Tileset._claim(cdata) def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: @@ -154,4 +155,5 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - lib.TCOD_tileset_load_truetype_(path.encode(), tile_width, tile_height) + if lib.TCOD_tileset_load_truetype_(path.encode(), tile_width, tile_height): + raise RuntimeError(ffi.string(lib.TCOD_get_error())) From 690d6844a5a166ba94edb19cf2761aaa354b5947 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 May 2019 17:43:33 -0700 Subject: [PATCH 0156/1101] Prepare 10.0.5 release. --- CHANGELOG.rst | 5 +++++ examples/custrender.py | 4 ++-- libtcod | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f8525b7a..91248691 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +10.0.5 - 2019-05-17 +------------------- + - Fixed shader compilation issues in the OPENGL2 renderer. + - Fallback fonts should fail less on Linux. + 10.0.4 - 2019-05-17 ------------------- Changed diff --git a/examples/custrender.py b/examples/custrender.py index 3807ae07..7c0a98c9 100644 --- a/examples/custrender.py +++ b/examples/custrender.py @@ -19,7 +19,7 @@ import tcod.tileset import tcod.event -assert tcod.__version__ > "10.0.3", tcod.__version__ +assert tcod.__version__ > "10.0.4", tcod.__version__ def get_renderer_size() -> Tuple[int, int]: @@ -128,7 +128,7 @@ def accumulate( def main() -> None: - """An example of of the use of this module.""" + """An example for the use of this module.""" with tcod.console_init_root(20, 4, renderer=tcod.RENDERER_SDL2) as console: TEXT = "Console with a fixed aspect ratio and integer scaling." console.print_box(0, 0, 0, 0, TEXT) diff --git a/libtcod b/libtcod index 60d05025..0ea514fd 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 60d05025da900a8deae4f35be89588f57a5b94c6 +Subproject commit 0ea514fdf90a2bf8ea849d3eaef6b0e14752d3e2 From 8995c44b31c11d4e6400d0e72d4a925055ab83dd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 20 May 2019 15:37:05 -0700 Subject: [PATCH 0157/1101] Hide deprecated functions. --- build_libtcod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_libtcod.py b/build_libtcod.py index 25efea3a..9ceccee5 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -312,7 +312,7 @@ def get_ast(): MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} GCC_CFLAGS = { "DEBUG": ["-Og", "-g", "-fPIC"], - "RELEASE": ["-flto", "-O3", "-g", "-fPIC"], + "RELEASE": ["-flto", "-O3", "-g", "-fPIC", "-Wno-deprecated-declarations"], } if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv: From 8d9127dab08adefae20ab3708804999ab7289c9d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 21 May 2019 21:39:38 -0700 Subject: [PATCH 0158/1101] Added instructions for a common Linux install issue. --- docs/installation.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8527f058..e50c18d3 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -46,9 +46,13 @@ Linux (Debian-based) On Linux python-tcod will need to be built from source. You can run this command to download python-tcod's dependencies with apt:: - sudo apt install g++ python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev libomp5 + sudo apt install build-essential python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev libomp5 -Then you can build and install python-tcod using pip in a user environment:: +If your GCC version is less than 6.1, or your SDL version is less than 2.0.5, +then you will need to perform a distribution upgrade before continuing. + +Once dependences are resolved you can build and install python-tcod using pip +in a user environment:: python3 -m pip install --user tcod From ddc34c67aed771bbd639dd4e8f3545784e923288 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 21 May 2019 22:19:07 -0700 Subject: [PATCH 0159/1101] Add vsync option to console_init_root. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- tcod/libtcodpy.py | 21 ++++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 91248691..b21b89c7 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,9 +8,12 @@ v2.0.0 Unreleased ------------------ +Changed + - `tcod.console_init_root` now has an optional `vsync` parameter. 10.0.5 - 2019-05-17 ------------------- +Fixed - Fixed shader compilation issues in the OPENGL2 renderer. - Fallback fonts should fail less on Linux. diff --git a/libtcod b/libtcod index 0ea514fd..2d82827f 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 0ea514fdf90a2bf8ea849d3eaef6b0e14752d3e2 +Subproject commit 2d82827f8aa7c96c84086e23d3c38ef6667e0eed diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 083fc277..fdd6a92c 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -870,6 +870,7 @@ def console_init_root( fullscreen: bool = False, renderer: Optional[int] = None, order: str = "C", + vsync: Optional[bool] = None, ) -> tcod.console.Console: """Set up the primary display and return the root console. @@ -901,6 +902,11 @@ def console_init_root( `order` will affect how the array attributes of the returned root console are indexed. `order='C'` is the default, but `order='F'` is recommended. + If `vsync` is True then the frame-rate will be synchronized to the monitors + vertical refresh rate. This prevents screen tearing and avoids wasting + computing power on overdraw. If `vsync` is False then the frame-rate will + be uncapped. The default is False but will change to True in the future. + .. versionchanged:: 4.3 Added `order` parameter. `title` parameter is now optional. @@ -908,6 +914,9 @@ def console_init_root( .. versionchanged:: 8.0 The default `renderer` is now automatic instead of always being `RENDERER_SDL`. + + .. versionchanged:: 10.1 + Added the `vsync` parameter. """ if title is None: # Use the scripts filename as the title. @@ -929,7 +938,17 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) - if lib.TCOD_console_init_root(w, h, _bytes(title), fullscreen, renderer): + if vsync is None: + vsync = False + warnings.warn( + "vsync defaults to False, but the default will change to True in " + "the future. Provide a value for vsync to suppress this warning.", + DeprecationWarning, + stacklevel=2, + ) + if lib.TCOD_console_init_root_( + w, h, _bytes(title), fullscreen, renderer, vsync + ): raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) console = tcod.console.Console._get_root(order) console.clear() From 5ed8653ea5caa42d6811348b966a3e2b78144197 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 22 May 2019 08:12:17 -0700 Subject: [PATCH 0160/1101] Add a resizable console example. --- examples/README.md | 12 ++++++ examples/{ => experimental}/custrender.py | 4 +- examples/experimental/resizable_console.py | 49 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 examples/README.md rename examples/{ => experimental}/custrender.py (98%) create mode 100644 examples/experimental/resizable_console.py diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..48fc5209 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +This directory contains a few example scripts for using python-tcod. + +`samples_tcod.py` is the mail example which uses most of the newer API. This +can be compared to `samokes_libtcodpy.py` which mostly uses deprecated +functions from the old API. + +Examples in the `distribution/` folder show how to distribute projects made +using python-tcod. + +Examples in the `experimental/` folder show off features that might later be +added the python-tcod API. You can use those features by copying those modules +into your own project. diff --git a/examples/custrender.py b/examples/experimental/custrender.py similarity index 98% rename from examples/custrender.py rename to examples/experimental/custrender.py index 7c0a98c9..0582a147 100644 --- a/examples/custrender.py +++ b/examples/experimental/custrender.py @@ -129,7 +129,9 @@ def accumulate( def main() -> None: """An example for the use of this module.""" - with tcod.console_init_root(20, 4, renderer=tcod.RENDERER_SDL2) as console: + with tcod.console_init_root( + 20, 4, renderer=tcod.RENDERER_SDL2, vsync=True + ) as console: TEXT = "Console with a fixed aspect ratio and integer scaling." console.print_box(0, 0, 0, 0, TEXT) while True: diff --git a/examples/experimental/resizable_console.py b/examples/experimental/resizable_console.py new file mode 100644 index 00000000..7e278b53 --- /dev/null +++ b/examples/experimental/resizable_console.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for this script. This work is +# published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +"""An example showing the console being resized to fit the window.""" +import tcod +import tcod.event +import tcod.tileset + +import custrender # Using the custom renderer engine. + + +def main() -> None: + with tcod.console_init_root( + 20, 4, renderer=tcod.RENDERER_SDL2, vsync=True + ) as console: + TEXT = "Resizable console with no stretching." + while True: + console.clear() + + # Draw the checkerboard pattern. + console.tiles["bg"][::2, ::2] = (32, 32, 32, 255) + console.tiles["bg"][1::2, 1::2] = (32, 32, 32, 255) + + console.print_box(0, 0, 0, 0, TEXT) + + # These functions are explained in `custrender.py`. + custrender.clear((0, 0, 0)) + custrender.accumulate( + console, custrender.get_viewport(console, True, True) + ) + custrender.present() + + for event in tcod.event.wait(): + if event.type == "QUIT": + raise SystemExit() + elif event.type == "WINDOWRESIZED": + # Use the current active tileset as a reference. + tileset = tcod.tileset.get_default() + # Replace `console` with a new one of the correct size. + console = tcod.console.Console( + event.width // tileset.tile_width, + event.height // tileset.tile_height, + ) + + +if __name__ == "__main__": + main() From 660a7a5f5e7f90d0b956d88dc52e132998e4ce72 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 23 May 2019 14:57:51 -0700 Subject: [PATCH 0161/1101] Update PyInstaller example. --- examples/distribution/PyInstaller/README.rst | 17 +++----- .../distribution/PyInstaller/hello_world.py | 38 +++++++++++++----- .../distribution/PyInstaller/hook-tcod.py | 9 ----- examples/distribution/PyInstaller/hook-tdl.py | 9 ----- .../PyInstaller/terminal8x8_gs_ro.png | Bin 0 -> 3696 bytes 5 files changed, 33 insertions(+), 40 deletions(-) delete mode 100644 examples/distribution/PyInstaller/hook-tcod.py delete mode 100644 examples/distribution/PyInstaller/hook-tdl.py create mode 100644 examples/distribution/PyInstaller/terminal8x8_gs_ro.png diff --git a/examples/distribution/PyInstaller/README.rst b/examples/distribution/PyInstaller/README.rst index a5b71208..677b9d76 100644 --- a/examples/distribution/PyInstaller/README.rst +++ b/examples/distribution/PyInstaller/README.rst @@ -1,26 +1,19 @@ PyInstaller Example =================== -First, install the packages: ``tdl`` and ``PyInstaller``. +First, install the packages: ``tcod`` and ``PyInstaller``. On Windows you must also install the ``pywin32`` package (named ``pypiwin32`` if you're using pip install.) -Next, download the `hook-tcod.py` and `hook-tdl.py` files from this repository. -Give PyInstaller the location of these files with the `--additional-hooks-dir` -argument. - -`hook-tcod.py` is always needed. `hook-tdl.py` only installs the default -font used by the tdl module and is optional if a custom font is used. - Then run the PyInstaller script with this command:: - PyInstaller hello_world.py --additional-hooks-dir=. + PyInstaller hello_world.py --add-data "terminal8x8_gs_ro.png;." -The finished build will be placed at ``dist/hello_world``. You should see references to the `hook-tdl` in the output. +The finished build will be placed in the ``dist/`` directory. -You can also build to one file with the command:: +You can also build to one executable file using the following command:: - PyInstaller hello_world.py --additional-hooks-dir=. -F + PyInstaller hello_world.py --add-data "terminal8x8_gs_ro.png;." --onefile The PyInstaller manual can be found at: https://pythonhosted.org/PyInstaller/ diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/hello_world.py index eb1943ab..75d915d9 100644 --- a/examples/distribution/PyInstaller/hello_world.py +++ b/examples/distribution/PyInstaller/hello_world.py @@ -1,18 +1,36 @@ -#!/usr/bin/env python - -import tdl +#!/usr/bin/env python3 +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for the "hello world" PyInstaller +# example script. This work is published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +import sys +import os.path +import tcod +import tcod.event WIDTH, HEIGHT = 80, 60 -console = None + +# The base directory, this is sys._MEIPASS when in one-file mode. +BASE_DIR = getattr(sys, "_MEIPASS", ".") + +FONT_PATH = os.path.join(BASE_DIR, "terminal8x8_gs_ro.png") + def main(): - global console - console = tdl.init(WIDTH, HEIGHT) - console.draw_str(0, 0, "Hello World") - tdl.flush() - tdl.event.key_wait() + tcod.console_set_custom_font(FONT_PATH, tcod.FONT_LAYOUT_CP437) + with tcod.console_init_root( + WIDTH, HEIGHT, renderer=tcod.RENDERER_SDL2, vsync=True + ) as console: + while True: + console.clear() + console.print(0, 0, "Hello World") + tcod.console_flush() + + for event in tcod.event.wait(): + if event.type == "QUIT": + raise SystemExit() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/distribution/PyInstaller/hook-tcod.py b/examples/distribution/PyInstaller/hook-tcod.py deleted file mode 100644 index e3d1bec8..00000000 --- a/examples/distribution/PyInstaller/hook-tcod.py +++ /dev/null @@ -1,9 +0,0 @@ -""" - Hook for https://github.com/libtcod/python-tcod -""" -from PyInstaller.utils.hooks import collect_dynamic_libs - -hiddenimports = ['_cffi_backend'] - -# Install shared libraries to the working directory. -binaries = collect_dynamic_libs('tcod', destdir='.') diff --git a/examples/distribution/PyInstaller/hook-tdl.py b/examples/distribution/PyInstaller/hook-tdl.py deleted file mode 100644 index 09386571..00000000 --- a/examples/distribution/PyInstaller/hook-tdl.py +++ /dev/null @@ -1,9 +0,0 @@ -""" - Hook for https://github.com/libtcod/python-tcod - - You should skip this hook if you're using a custom font. -""" -from PyInstaller.utils.hooks import collect_data_files - -# Package tdl's 'default' font file. -datas = collect_data_files('tdl') diff --git a/examples/distribution/PyInstaller/terminal8x8_gs_ro.png b/examples/distribution/PyInstaller/terminal8x8_gs_ro.png new file mode 100644 index 0000000000000000000000000000000000000000..e496e2bc51e13930320d5938f18b67728f75f02e GIT binary patch literal 3696 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 Date: Fri, 24 May 2019 07:56:03 -0700 Subject: [PATCH 0162/1101] Prepare 10.1.0 release. --- CHANGELOG.rst | 5 ++++- libtcod | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b21b89c7..ab365442 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,10 @@ v2.0.0 Unreleased ------------------ -Changed + +10.1.0 - 2019-05-24 +------------------- +Added - `tcod.console_init_root` now has an optional `vsync` parameter. 10.0.5 - 2019-05-17 diff --git a/libtcod b/libtcod index 2d82827f..de3b645d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 2d82827f8aa7c96c84086e23d3c38ef6667e0eed +Subproject commit de3b645dbf69703fa44e74fea5f81c3991c17e3b From 0e966a8f3dd00b5d4fa3558c0196d8fcb862fcac Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 29 May 2019 10:49:32 -0700 Subject: [PATCH 0163/1101] Make it clear that vsync is only for the new renderers. --- tcod/libtcodpy.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index fdd6a92c..47c15639 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -906,6 +906,8 @@ def console_init_root( vertical refresh rate. This prevents screen tearing and avoids wasting computing power on overdraw. If `vsync` is False then the frame-rate will be uncapped. The default is False but will change to True in the future. + This option only works with the SDL2 or OPENGL2 renderers, any other + renderer will always have `vsync` disabled. .. versionchanged:: 4.3 Added `order` parameter. @@ -938,7 +940,11 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) - if vsync is None: + NEW_RENDERER = renderer in ( + tcod.constants.RENDERER_SDL2, + tcod.constants.RENDERER_OPENGL2, + ) + if vsync is None and NEW_RENDERER: vsync = False warnings.warn( "vsync defaults to False, but the default will change to True in " @@ -946,6 +952,8 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) + if not NEW_RENDERER: + vsync = False if lib.TCOD_console_init_root_( w, h, _bytes(title), fullscreen, renderer, vsync ): From 4d36258eea414768e547f52d7d5646dcab6460ff Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 31 May 2019 12:00:59 -0700 Subject: [PATCH 0164/1101] Update threaded jobs example. --- examples/thread_jobs.py | 197 ++++++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 88 deletions(-) diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index 24131a9a..7afe4a4f 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -1,18 +1,28 @@ #!/usr/bin/env python3 - +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for this example. This work is +# published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +"""A parallelization example for the field-of-view and path-finding tasks. + +Because of the Python GIL you can have only one thread in Python at a time. +However, many C functions with long computations will release the GIL for their +duration, allowing another Python thread to call into another C function. + +This script tests the viability of running python-tcod tasks in parallel. +Typically the field-of-view tasks run good but not great, and the path-finding +tasks run poorly. +""" import sys +import concurrent.futures import multiprocessing import platform -import threading -try: - import Queue as queue -except ImportError: - import queue import timeit +from typing import List, Tuple, Callable import tcod -#import libtcodpy as tcod + THREADS = multiprocessing.cpu_count() @@ -20,85 +30,96 @@ MAP_HEIGHT = 100 MAP_NUMBER = 500 -PATH_NUMBER = 500 - -class JobConsumer(threading.Thread): - - def __init__(self, i): - threading.Thread.__init__(self) - self.daemon = True - self.astar = tcod.path_new_using_map(maps[i]) - - def run(self): - while 1: - job, obj = jobs.get() - if job == 'fov': - tcod.map_compute_fov(obj, MAP_WIDTH // 2, MAP_HEIGHT // 2) - elif job == 'astar': - tcod.path_compute(self.astar, - 0, 0, MAP_WIDTH - 1 , MAP_HEIGHT - 1) - x, y = tcod.path_walk(self.astar, False) - while x is not None: - x, y = tcod.path_walk(self.astar, False) - jobs.task_done() - -maps = [tcod.map_new(MAP_WIDTH, MAP_HEIGHT) for i in range(MAP_NUMBER)] -jobs = queue.Queue() -threads = [JobConsumer(i) for i in range(THREADS)] - -def test_fov_single(): - for m in maps: - tcod.map_compute_fov(m, MAP_WIDTH // 2, MAP_HEIGHT // 2) - -def test_fov_threads(): - for m in maps: - jobs.put(('fov', m)) - jobs.join() - -def test_astar_single(): - astar = tcod.path_new_using_map(maps[0]) - for _ in range(PATH_NUMBER): - tcod.path_compute(astar, 0, 0, MAP_WIDTH - 1 , MAP_HEIGHT - 1) - x, y = tcod.path_walk(astar, False) - while x is not None: - x, y = tcod.path_walk(astar, False) - -def test_astar_threads(): - for _ in range(PATH_NUMBER): - jobs.put(('astar', None)) - jobs.join() - -def main(): - for m in maps: - for y in range(MAP_HEIGHT): - for x in range(MAP_WIDTH): - tcod.map_set_properties(m, x, y, True, True) - - for thread in threads: - thread.start() - - print('Python %s\n%s\n%s' % (sys.version, platform.platform(), - platform.processor())) - - print('\nComputing field-of-view for %i empty %ix%i maps.' % - (len(maps), MAP_WIDTH, MAP_HEIGHT)) - single_time = min(timeit.repeat(test_fov_single, number=1)) - print('1 thread: %.2fms' % (single_time * 1000)) - - multi_time = min(timeit.repeat(test_fov_threads, number=1)) - print('%i threads: %.2fms' % (THREADS, multi_time * 1000)) - print('%.2f%% efficiency' % - (single_time / (multi_time * THREADS) * 100)) - - print('\nComputing AStar from corner to corner %i times on seperate empty' - ' %ix%i maps.' % (PATH_NUMBER, MAP_WIDTH, MAP_HEIGHT)) - single_time = min(timeit.repeat(test_astar_single, number=1)) - print('1 thread: %.2fms' % (single_time * 1000)) - - multi_time = min(timeit.repeat(test_astar_threads, number=1)) - print('%i threads: %.2fms' % (THREADS, multi_time * 1000)) - print('%.2f%% efficiency' % - (single_time / (multi_time * THREADS) * 100)) - -if __name__ == '__main__': +REPEAT = 10 # Number to times to run a test. Only the fastest result is shown. + + +def test_fov(map_: tcod.map.Map) -> tcod.map.Map: + map_.compute_fov(MAP_WIDTH // 2, MAP_HEIGHT // 2) + return map_ + + +def test_fov_single(maps: List[tcod.map.Map]) -> None: + for map_ in maps: + test_fov(map_) + + +def test_fov_threads( + executor: concurrent.futures.Executor, maps: List[tcod.map.Map] +) -> None: + for result in executor.map(test_fov, maps): + pass + + +def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: + astar = tcod.path.AStar(map_) + return astar.get_path(0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1) + + +def test_astar_single(maps: List[tcod.map.Map]) -> None: + for map_ in maps: + test_astar(map_) + + +def test_astar_threads( + executor: concurrent.futures.Executor, maps: List[tcod.map.Map] +) -> None: + for result in executor.map(test_astar, maps): + pass + + +def run_test( + maps: List[tcod.map.Map], + single_func: Callable[[List[tcod.map.Map]], None], + multi_func: Callable[ + [concurrent.futures.Executor, List[tcod.map.Map]], None + ], +) -> None: + """Run a function designed for a single thread and compare it to a threaded + version. + + This prints the results of these tests. + """ + single_time = min( + timeit.repeat(lambda: single_func(maps), number=1, repeat=REPEAT) + ) + print(f"Single threaded: {single_time * 1000:.2f}ms") + + for i in range(1, THREADS + 1): + executor = concurrent.futures.ThreadPoolExecutor(i) + multi_time = min( + timeit.repeat( + lambda: multi_func(executor, maps), number=1, repeat=REPEAT + ) + ) + print( + f"{i} threads: {multi_time * 1000:.2f}ms, " + f"{single_time / (multi_time * i) * 100:.2f}% efficiency" + ) + + +def main() -> None: + """Setup and run tests.""" + maps = [tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) for i in range(MAP_NUMBER)] + for map_ in maps: + map_.walkable[...] = True + map_.transparent[...] = True + + print( + f"Python {sys.version}\n{platform.platform()}\n{platform.processor()}" + ) + + print( + f"\nComputing field-of-view for " + f"{len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps." + ) + run_test(maps, test_fov_single, test_fov_threads) + + print( + f"\nComputing AStar from corner to corner {len(maps)} times " + f"on separate empty {MAP_WIDTH}x{MAP_HEIGHT} maps." + ) + run_test(maps, test_astar_single, test_astar_threads) + + +if __name__ == "__main__": main() From 5dc50fa3178d1a4a77b8f37f735631d33963e640 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 2 Jun 2019 00:55:39 -0700 Subject: [PATCH 0165/1101] Add tcod.event __str__ descriptions. Ran into and fixed several other issues related to SDL name handling. Added a cffi callback for later use. --- build_libtcod.py | 2 +- examples/sdlevent.py | 19 +- parse_sdl2.py | 16 +- tcod/cdef.h | 1 + tcod/event.py | 195 ++++++-- tcod/event_constants.py | 1019 +++++++++++++++++++-------------------- 6 files changed, 682 insertions(+), 570 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 9ceccee5..8d8f73fa 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -399,7 +399,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: ): all_names.append(name) names.append("%s = %s" % (name, value)) - lookup.append('%s: "tcod.event.%s"' % (value, name)) + lookup.append('%s: "%s"' % (value, name)) names = "\n".join(names) lookup = "{\n %s,\n}" % (",\n ".join(lookup),) return names, lookup diff --git a/examples/sdlevent.py b/examples/sdlevent.py index cfbf3f11..5e020dbe 100755 --- a/examples/sdlevent.py +++ b/examples/sdlevent.py @@ -6,25 +6,30 @@ def main(): """Example program for tcod.event""" - WIDTH, HEIGHT = 120, 60 + WIDTH, HEIGHT = 120, 40 TITLE = None - with tcod.console_init_root(WIDTH, HEIGHT, TITLE, order="F", - renderer=tcod.RENDERER_SDL) as console: - tcod.sys_set_fps(24) + with tcod.console_init_root( + WIDTH, + HEIGHT, + TITLE, + order="F", + renderer=tcod.RENDERER_SDL2, + vsync=True, + ) as console: while True: tcod.console_flush() for event in tcod.event.wait(): - print(event) + print(repr(event)) if event.type == "QUIT": raise SystemExit() elif event.type == "MOUSEMOTION": console.ch[:, -1] = 0 - console.print_(0, HEIGHT - 1, str(event)) + console.print(0, HEIGHT - 1, str(event)) else: console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) console.ch[:, -3] = 0 - console.print_(0, HEIGHT - 3, str(event)) + console.print(0, HEIGHT - 3, str(event)) if __name__ == "__main__": diff --git a/parse_sdl2.py b/parse_sdl2.py index 3c38d07f..4ec1ae5d 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -16,15 +16,22 @@ re.DOTALL, ) RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) -RE_TYPEDEF = re.compile(r"typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) +RE_TYPEDEF = re.compile(r"^typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) +RE_ENUM = re.compile(r"^enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) RE_DECL = re.compile(r"^extern[^#\n]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) RE_ENDIAN = re.compile( r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL ) +RE_ENDIAN2 = re.compile( + r"#if SDL_BYTEORDER == SDL_BIG_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL +) RE_DEFINE_TRUNCATE = re.compile(r"(#define\s+\w+\s+).+$", flags=re.DOTALL) RE_TYPEDEF_TRUNCATE = re.compile( r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE ) +RE_ENUM_TRUNCATE = re.compile( + r"(\w+\s*=).+?(?=,$|})(?![^(']*\))", re.MULTILINE | re.DOTALL +) def get_header(name: str) -> str: @@ -39,6 +46,10 @@ def get_header(name: str) -> str: header = RE_ENDIAN.sub( r"\1" if sys.byteorder == "little" else r"\2", header ) + header = RE_ENDIAN2.sub( + r"\1" if sys.byteorder != "little" else r"\2", header + ) + # Ignore bad ARM compiler typedef. header = header.replace("typedef int SDL_bool;", "") return header @@ -87,6 +98,9 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) yield typedef + for enum in RE_ENUM.findall(header): + yield RE_ENUM_TRUNCATE.sub(r"\1 ...", enum) + for decl in RE_DECL.findall(header): if "SDL_RWops" in decl: continue # Ignore SDL_RWops functions. diff --git a/tcod/cdef.h b/tcod/cdef.h index 89600f6f..08c4bd93 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -18,4 +18,5 @@ float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data); void _pycall_sdl_hook(struct SDL_Surface*); +int _pycall_event_watch(void* userdata, union SDL_Event* event); } diff --git a/tcod/event.py b/tcod/event.py index 8a43fde2..8fab3309 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -15,26 +15,40 @@ .. versionadded:: 8.4 """ -from typing import Any, Dict, NamedTuple, Optional, Iterator, Tuple +from typing import Any, Dict, Mapping, NamedTuple, Optional, Iterator, Tuple import tcod import tcod.event_constants from tcod.event_constants import * # noqa: F4 +from tcod.event_constants import KMOD_SHIFT, KMOD_CTRL, KMOD_ALT, KMOD_GUI + + +class _ConstantsWithPrefix(Mapping[int, str]): + def __init__(self, constants: Mapping[int, str]): + self.constants = constants + + def __getitem__(self, key: int) -> str: + return "tcod.event." + self.constants[key] + + def __len__(self) -> int: + return len(self.constants) + + def __iter__(self) -> Iterator[int]: + return iter(self.constants) def _describe_bitmask( - bits: int, table: Dict[Any, str], default: str = "0" + bits: int, table: Mapping[int, str], default: str = "0" ) -> str: - """Returns a bitmask in human readable form. + """Return a bitmask in human readable form. This is a private function, used internally. - Args: - bits (int): The bitmask to be represented. - table (Dict[Any,str]): A reverse lookup table. - default (Any): A default return value when bits is 0. + `bits` is the bitmask to be represented. + + `table` is a reverse lookup table. - Returns: str: A printable version of the bits variable. + `default` is returned when no other bits can be represented. """ result = [] for bit, name in table.items(): @@ -55,41 +69,54 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: Point = NamedTuple("Point", [("x", float), ("y", float)]) # manually define names for SDL macros -BUTTON_LEFT = 1 -BUTTON_MIDDLE = 2 -BUTTON_RIGHT = 3 -BUTTON_X1 = 4 -BUTTON_X2 = 5 -BUTTON_LMASK = 0x01 -BUTTON_MMASK = 0x02 -BUTTON_RMASK = 0x04 -BUTTON_X1MASK = 0x08 -BUTTON_X2MASK = 0x10 - -KMOD_SHIFT = ( - tcod.event_constants.KMOD_LSHIFT | tcod.event_constants.KMOD_RSHIFT -) -KMOD_CTRL = tcod.event_constants.KMOD_LCTRL | tcod.event_constants.KMOD_RCTRL -KMOD_ALT = tcod.event_constants.KMOD_LALT | tcod.event_constants.KMOD_RALT -KMOD_GUI = tcod.event_constants.KMOD_LGUI | tcod.event_constants.KMOD_RGUI +BUTTON_LEFT = tcod.lib.SDL_BUTTON_LEFT +BUTTON_MIDDLE = tcod.lib.SDL_BUTTON_MIDDLE +BUTTON_RIGHT = tcod.lib.SDL_BUTTON_RIGHT +BUTTON_X1 = tcod.lib.SDL_BUTTON_X1 +BUTTON_X2 = tcod.lib.SDL_BUTTON_X2 +BUTTON_LMASK = tcod.lib.SDL_BUTTON_LMASK +BUTTON_MMASK = tcod.lib.SDL_BUTTON_MMASK +BUTTON_RMASK = tcod.lib.SDL_BUTTON_RMASK +BUTTON_X1MASK = tcod.lib.SDL_BUTTON_X1MASK +BUTTON_X2MASK = tcod.lib.SDL_BUTTON_X2MASK # reverse tables are used to get the tcod.event name from the value. _REVERSE_BUTTON_TABLE = { - BUTTON_LEFT: "tcod.event.BUTTON_LEFT", - BUTTON_MIDDLE: "tcod.event.BUTTON_MIDDLE", - BUTTON_RIGHT: "tcod.event.BUTTON_RIGHT", - BUTTON_X1: "tcod.event.BUTTON_X1", - BUTTON_X2: "tcod.event.BUTTON_X2", + BUTTON_LEFT: "BUTTON_LEFT", + BUTTON_MIDDLE: "BUTTON_MIDDLE", + BUTTON_RIGHT: "BUTTON_RIGHT", + BUTTON_X1: "BUTTON_X1", + BUTTON_X2: "BUTTON_X2", } _REVERSE_BUTTON_MASK_TABLE = { - BUTTON_LMASK: "tcod.event.BUTTON_LMASK", - BUTTON_MMASK: "tcod.event.BUTTON_MMASK", - BUTTON_RMASK: "tcod.event.BUTTON_RMASK", - BUTTON_X1MASK: "tcod.event.BUTTON_X1MASK", - BUTTON_X2MASK: "tcod.event.BUTTON_X2MASK", + BUTTON_LMASK: "BUTTON_LMASK", + BUTTON_MMASK: "BUTTON_MMASK", + BUTTON_RMASK: "BUTTON_RMASK", + BUTTON_X1MASK: "BUTTON_X1MASK", + BUTTON_X2MASK: "BUTTON_X2MASK", } +_REVERSE_BUTTON_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_TABLE) +_REVERSE_BUTTON_MASK_TABLE_PREFIX = _ConstantsWithPrefix( + _REVERSE_BUTTON_MASK_TABLE +) +_REVERSE_SCANCODE_TABLE_PREFIX = _ConstantsWithPrefix( + tcod.event_constants._REVERSE_SCANCODE_TABLE +) +_REVERSE_SYM_TABLE_PREFIX = _ConstantsWithPrefix( + tcod.event_constants._REVERSE_SYM_TABLE +) + + +_REVERSE_MOD_TABLE = tcod.event_constants._REVERSE_MOD_TABLE.copy() +del _REVERSE_MOD_TABLE[KMOD_SHIFT] +del _REVERSE_MOD_TABLE[KMOD_CTRL] +del _REVERSE_MOD_TABLE[KMOD_ALT] +del _REVERSE_MOD_TABLE[KMOD_GUI] + +_REVERSE_MOD_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_MOD_TABLE) + class Event: """The base event class. @@ -111,6 +138,9 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: """Return a class instance from a python-cffi 'SDL_Event*' pointer.""" raise NotImplementedError() + def __str__(self) -> str: + return "" % (self.type,) + class Quit(Event): """An application quit request event. @@ -129,7 +159,7 @@ def from_sdl_event(cls, sdl_event: Any) -> "Quit": return self def __repr__(self) -> str: - return "tcod.event.%s()" % self.__class__.__name__ + return "tcod.event.%s()" % (self.__class__.__name__,) class KeyboardEvent(Event): @@ -202,12 +232,19 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: def __repr__(self) -> str: return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( self.__class__.__name__, + _REVERSE_SCANCODE_TABLE_PREFIX[self.scancode], + _REVERSE_SYM_TABLE_PREFIX[self.sym], + _describe_bitmask(self.mod, _REVERSE_MOD_TABLE_PREFIX), + ", repeat=True" if self.repeat else "", + ) + + def __str__(self) -> str: + return "<%s, scancode=%s, sym=%s, mod=%s, repeat=%r>" % ( + super().__str__().strip("<>"), tcod.event_constants._REVERSE_SCANCODE_TABLE[self.scancode], tcod.event_constants._REVERSE_SYM_TABLE[self.sym], - _describe_bitmask( - self.mod, tcod.event_constants._REVERSE_MOD_TABLE - ), - ", repeat=True" if self.repeat else "", + _describe_bitmask(self.mod, _REVERSE_MOD_TABLE), + self.repeat, ) @@ -252,8 +289,16 @@ def __init__( def __repr__(self) -> str: return ("tcod.event.%s(pixel=%r, tile=%r, state=%s)") % ( self.__class__.__name__, - self.pixel, - self.tile, + tuple(self.pixel), + tuple(self.tile), + _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), + ) + + def __str__(self) -> str: + return ("<%s, pixel=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( + super().__str__().strip("<>"), + *self.pixel, + *self.tile, _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), ) @@ -311,11 +356,20 @@ def __repr__(self) -> str: "tile=%r, tile_motion=%r, state=%s)" ) % ( self.__class__.__name__, - self.pixel, - self.pixel_motion, - self.tile, - self.tile_motion, - _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), + tuple(self.pixel), + tuple(self.pixel_motion), + tuple(self.tile), + tuple(self.tile_motion), + _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), + ) + + def __str__(self) -> str: + return ( + "<%s, pixel_motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>" + ) % ( + super().__str__().strip("<>"), + *self.pixel_motion, + *self.tile_motion, ) @@ -366,9 +420,20 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: def __repr__(self) -> str: return "tcod.event.%s(pixel=%r, tile=%r, button=%s)" % ( self.__class__.__name__, - self.pixel, - self.tile, - _REVERSE_BUTTON_TABLE[self.button], + tuple(self.pixel), + tuple(self.tile), + _REVERSE_BUTTON_TABLE_PREFIX[self.button], + ) + + def __str__(self) -> str: + return ( + " str: ", flipped=True" if self.flipped else "", ) + def __str__(self) -> str: + return "<%s, x=%i, y=%i, flipped=%r)" % ( + super().__str__().strip("<>"), + self.x, + self.y, + self.flipped, + ) + class TextInput(Event): """ @@ -434,6 +507,9 @@ def from_sdl_event(cls, sdl_event: Any) -> "TextInput": def __repr__(self) -> str: return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) + def __str__(self) -> str: + return "<%s, text=%r)" % (super().__str__().strip("<>"), self.text) + class WindowEvent(Event): """ @@ -505,6 +581,13 @@ def __repr__(self) -> str: self.y, ) + def __str__(self) -> str: + return "<%s, x=%r, y=%r)" % ( + super().__str__().strip("<>"), + self.x, + self.y, + ) + class WindowResized(WindowEvent): """ @@ -527,6 +610,13 @@ def __repr__(self) -> str: self.height, ) + def __str__(self) -> str: + return "<%s, width=%r, height=%r)" % ( + super().__str__().strip("<>"), + self.width, + self.height, + ) + class Undefined(Event): """This class is a place holder for SDL events without their own tcod.event @@ -757,6 +847,11 @@ def get_mouse_state() -> MouseState: return MouseState((xy[0], xy[1]), (int(x), int(y)), buttons) +@tcod.ffi.def_extern() # type: ignore +def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: + return 0 + + __all__ = [ "Point", "BUTTON_LEFT", diff --git a/tcod/event_constants.py b/tcod/event_constants.py index 8b01cd8f..82633eff 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -245,252 +245,248 @@ SCANCODE_SLEEP = 282 SCANCODE_APP1 = 283 SCANCODE_APP2 = 284 -SCANCODE_AUDIOREWIND = 285 -SCANCODE_AUDIOFASTFORWARD = 286 _REVERSE_SCANCODE_TABLE = { - 0: "tcod.event.SCANCODE_UNKNOWN", - 4: "tcod.event.SCANCODE_A", - 5: "tcod.event.SCANCODE_B", - 6: "tcod.event.SCANCODE_C", - 7: "tcod.event.SCANCODE_D", - 8: "tcod.event.SCANCODE_E", - 9: "tcod.event.SCANCODE_F", - 10: "tcod.event.SCANCODE_G", - 11: "tcod.event.SCANCODE_H", - 12: "tcod.event.SCANCODE_I", - 13: "tcod.event.SCANCODE_J", - 14: "tcod.event.SCANCODE_K", - 15: "tcod.event.SCANCODE_L", - 16: "tcod.event.SCANCODE_M", - 17: "tcod.event.SCANCODE_N", - 18: "tcod.event.SCANCODE_O", - 19: "tcod.event.SCANCODE_P", - 20: "tcod.event.SCANCODE_Q", - 21: "tcod.event.SCANCODE_R", - 22: "tcod.event.SCANCODE_S", - 23: "tcod.event.SCANCODE_T", - 24: "tcod.event.SCANCODE_U", - 25: "tcod.event.SCANCODE_V", - 26: "tcod.event.SCANCODE_W", - 27: "tcod.event.SCANCODE_X", - 28: "tcod.event.SCANCODE_Y", - 29: "tcod.event.SCANCODE_Z", - 30: "tcod.event.SCANCODE_1", - 31: "tcod.event.SCANCODE_2", - 32: "tcod.event.SCANCODE_3", - 33: "tcod.event.SCANCODE_4", - 34: "tcod.event.SCANCODE_5", - 35: "tcod.event.SCANCODE_6", - 36: "tcod.event.SCANCODE_7", - 37: "tcod.event.SCANCODE_8", - 38: "tcod.event.SCANCODE_9", - 39: "tcod.event.SCANCODE_0", - 40: "tcod.event.SCANCODE_RETURN", - 41: "tcod.event.SCANCODE_ESCAPE", - 42: "tcod.event.SCANCODE_BACKSPACE", - 43: "tcod.event.SCANCODE_TAB", - 44: "tcod.event.SCANCODE_SPACE", - 45: "tcod.event.SCANCODE_MINUS", - 46: "tcod.event.SCANCODE_EQUALS", - 47: "tcod.event.SCANCODE_LEFTBRACKET", - 48: "tcod.event.SCANCODE_RIGHTBRACKET", - 49: "tcod.event.SCANCODE_BACKSLASH", - 50: "tcod.event.SCANCODE_NONUSHASH", - 51: "tcod.event.SCANCODE_SEMICOLON", - 52: "tcod.event.SCANCODE_APOSTROPHE", - 53: "tcod.event.SCANCODE_GRAVE", - 54: "tcod.event.SCANCODE_COMMA", - 55: "tcod.event.SCANCODE_PERIOD", - 56: "tcod.event.SCANCODE_SLASH", - 57: "tcod.event.SCANCODE_CAPSLOCK", - 58: "tcod.event.SCANCODE_F1", - 59: "tcod.event.SCANCODE_F2", - 60: "tcod.event.SCANCODE_F3", - 61: "tcod.event.SCANCODE_F4", - 62: "tcod.event.SCANCODE_F5", - 63: "tcod.event.SCANCODE_F6", - 64: "tcod.event.SCANCODE_F7", - 65: "tcod.event.SCANCODE_F8", - 66: "tcod.event.SCANCODE_F9", - 67: "tcod.event.SCANCODE_F10", - 68: "tcod.event.SCANCODE_F11", - 69: "tcod.event.SCANCODE_F12", - 70: "tcod.event.SCANCODE_PRINTSCREEN", - 71: "tcod.event.SCANCODE_SCROLLLOCK", - 72: "tcod.event.SCANCODE_PAUSE", - 73: "tcod.event.SCANCODE_INSERT", - 74: "tcod.event.SCANCODE_HOME", - 75: "tcod.event.SCANCODE_PAGEUP", - 76: "tcod.event.SCANCODE_DELETE", - 77: "tcod.event.SCANCODE_END", - 78: "tcod.event.SCANCODE_PAGEDOWN", - 79: "tcod.event.SCANCODE_RIGHT", - 80: "tcod.event.SCANCODE_LEFT", - 81: "tcod.event.SCANCODE_DOWN", - 82: "tcod.event.SCANCODE_UP", - 83: "tcod.event.SCANCODE_NUMLOCKCLEAR", - 84: "tcod.event.SCANCODE_KP_DIVIDE", - 85: "tcod.event.SCANCODE_KP_MULTIPLY", - 86: "tcod.event.SCANCODE_KP_MINUS", - 87: "tcod.event.SCANCODE_KP_PLUS", - 88: "tcod.event.SCANCODE_KP_ENTER", - 89: "tcod.event.SCANCODE_KP_1", - 90: "tcod.event.SCANCODE_KP_2", - 91: "tcod.event.SCANCODE_KP_3", - 92: "tcod.event.SCANCODE_KP_4", - 93: "tcod.event.SCANCODE_KP_5", - 94: "tcod.event.SCANCODE_KP_6", - 95: "tcod.event.SCANCODE_KP_7", - 96: "tcod.event.SCANCODE_KP_8", - 97: "tcod.event.SCANCODE_KP_9", - 98: "tcod.event.SCANCODE_KP_0", - 99: "tcod.event.SCANCODE_KP_PERIOD", - 100: "tcod.event.SCANCODE_NONUSBACKSLASH", - 101: "tcod.event.SCANCODE_APPLICATION", - 102: "tcod.event.SCANCODE_POWER", - 103: "tcod.event.SCANCODE_KP_EQUALS", - 104: "tcod.event.SCANCODE_F13", - 105: "tcod.event.SCANCODE_F14", - 106: "tcod.event.SCANCODE_F15", - 107: "tcod.event.SCANCODE_F16", - 108: "tcod.event.SCANCODE_F17", - 109: "tcod.event.SCANCODE_F18", - 110: "tcod.event.SCANCODE_F19", - 111: "tcod.event.SCANCODE_F20", - 112: "tcod.event.SCANCODE_F21", - 113: "tcod.event.SCANCODE_F22", - 114: "tcod.event.SCANCODE_F23", - 115: "tcod.event.SCANCODE_F24", - 116: "tcod.event.SCANCODE_EXECUTE", - 117: "tcod.event.SCANCODE_HELP", - 118: "tcod.event.SCANCODE_MENU", - 119: "tcod.event.SCANCODE_SELECT", - 120: "tcod.event.SCANCODE_STOP", - 121: "tcod.event.SCANCODE_AGAIN", - 122: "tcod.event.SCANCODE_UNDO", - 123: "tcod.event.SCANCODE_CUT", - 124: "tcod.event.SCANCODE_COPY", - 125: "tcod.event.SCANCODE_PASTE", - 126: "tcod.event.SCANCODE_FIND", - 127: "tcod.event.SCANCODE_MUTE", - 128: "tcod.event.SCANCODE_VOLUMEUP", - 129: "tcod.event.SCANCODE_VOLUMEDOWN", - 133: "tcod.event.SCANCODE_KP_COMMA", - 134: "tcod.event.SCANCODE_KP_EQUALSAS400", - 135: "tcod.event.SCANCODE_INTERNATIONAL1", - 136: "tcod.event.SCANCODE_INTERNATIONAL2", - 137: "tcod.event.SCANCODE_INTERNATIONAL3", - 138: "tcod.event.SCANCODE_INTERNATIONAL4", - 139: "tcod.event.SCANCODE_INTERNATIONAL5", - 140: "tcod.event.SCANCODE_INTERNATIONAL6", - 141: "tcod.event.SCANCODE_INTERNATIONAL7", - 142: "tcod.event.SCANCODE_INTERNATIONAL8", - 143: "tcod.event.SCANCODE_INTERNATIONAL9", - 144: "tcod.event.SCANCODE_LANG1", - 145: "tcod.event.SCANCODE_LANG2", - 146: "tcod.event.SCANCODE_LANG3", - 147: "tcod.event.SCANCODE_LANG4", - 148: "tcod.event.SCANCODE_LANG5", - 149: "tcod.event.SCANCODE_LANG6", - 150: "tcod.event.SCANCODE_LANG7", - 151: "tcod.event.SCANCODE_LANG8", - 152: "tcod.event.SCANCODE_LANG9", - 153: "tcod.event.SCANCODE_ALTERASE", - 154: "tcod.event.SCANCODE_SYSREQ", - 155: "tcod.event.SCANCODE_CANCEL", - 156: "tcod.event.SCANCODE_CLEAR", - 157: "tcod.event.SCANCODE_PRIOR", - 158: "tcod.event.SCANCODE_RETURN2", - 159: "tcod.event.SCANCODE_SEPARATOR", - 160: "tcod.event.SCANCODE_OUT", - 161: "tcod.event.SCANCODE_OPER", - 162: "tcod.event.SCANCODE_CLEARAGAIN", - 163: "tcod.event.SCANCODE_CRSEL", - 164: "tcod.event.SCANCODE_EXSEL", - 176: "tcod.event.SCANCODE_KP_00", - 177: "tcod.event.SCANCODE_KP_000", - 178: "tcod.event.SCANCODE_THOUSANDSSEPARATOR", - 179: "tcod.event.SCANCODE_DECIMALSEPARATOR", - 180: "tcod.event.SCANCODE_CURRENCYUNIT", - 181: "tcod.event.SCANCODE_CURRENCYSUBUNIT", - 182: "tcod.event.SCANCODE_KP_LEFTPAREN", - 183: "tcod.event.SCANCODE_KP_RIGHTPAREN", - 184: "tcod.event.SCANCODE_KP_LEFTBRACE", - 185: "tcod.event.SCANCODE_KP_RIGHTBRACE", - 186: "tcod.event.SCANCODE_KP_TAB", - 187: "tcod.event.SCANCODE_KP_BACKSPACE", - 188: "tcod.event.SCANCODE_KP_A", - 189: "tcod.event.SCANCODE_KP_B", - 190: "tcod.event.SCANCODE_KP_C", - 191: "tcod.event.SCANCODE_KP_D", - 192: "tcod.event.SCANCODE_KP_E", - 193: "tcod.event.SCANCODE_KP_F", - 194: "tcod.event.SCANCODE_KP_XOR", - 195: "tcod.event.SCANCODE_KP_POWER", - 196: "tcod.event.SCANCODE_KP_PERCENT", - 197: "tcod.event.SCANCODE_KP_LESS", - 198: "tcod.event.SCANCODE_KP_GREATER", - 199: "tcod.event.SCANCODE_KP_AMPERSAND", - 200: "tcod.event.SCANCODE_KP_DBLAMPERSAND", - 201: "tcod.event.SCANCODE_KP_VERTICALBAR", - 202: "tcod.event.SCANCODE_KP_DBLVERTICALBAR", - 203: "tcod.event.SCANCODE_KP_COLON", - 204: "tcod.event.SCANCODE_KP_HASH", - 205: "tcod.event.SCANCODE_KP_SPACE", - 206: "tcod.event.SCANCODE_KP_AT", - 207: "tcod.event.SCANCODE_KP_EXCLAM", - 208: "tcod.event.SCANCODE_KP_MEMSTORE", - 209: "tcod.event.SCANCODE_KP_MEMRECALL", - 210: "tcod.event.SCANCODE_KP_MEMCLEAR", - 211: "tcod.event.SCANCODE_KP_MEMADD", - 212: "tcod.event.SCANCODE_KP_MEMSUBTRACT", - 213: "tcod.event.SCANCODE_KP_MEMMULTIPLY", - 214: "tcod.event.SCANCODE_KP_MEMDIVIDE", - 215: "tcod.event.SCANCODE_KP_PLUSMINUS", - 216: "tcod.event.SCANCODE_KP_CLEAR", - 217: "tcod.event.SCANCODE_KP_CLEARENTRY", - 218: "tcod.event.SCANCODE_KP_BINARY", - 219: "tcod.event.SCANCODE_KP_OCTAL", - 220: "tcod.event.SCANCODE_KP_DECIMAL", - 221: "tcod.event.SCANCODE_KP_HEXADECIMAL", - 224: "tcod.event.SCANCODE_LCTRL", - 225: "tcod.event.SCANCODE_LSHIFT", - 226: "tcod.event.SCANCODE_LALT", - 227: "tcod.event.SCANCODE_LGUI", - 228: "tcod.event.SCANCODE_RCTRL", - 229: "tcod.event.SCANCODE_RSHIFT", - 230: "tcod.event.SCANCODE_RALT", - 231: "tcod.event.SCANCODE_RGUI", - 257: "tcod.event.SCANCODE_MODE", - 258: "tcod.event.SCANCODE_AUDIONEXT", - 259: "tcod.event.SCANCODE_AUDIOPREV", - 260: "tcod.event.SCANCODE_AUDIOSTOP", - 261: "tcod.event.SCANCODE_AUDIOPLAY", - 262: "tcod.event.SCANCODE_AUDIOMUTE", - 263: "tcod.event.SCANCODE_MEDIASELECT", - 264: "tcod.event.SCANCODE_WWW", - 265: "tcod.event.SCANCODE_MAIL", - 266: "tcod.event.SCANCODE_CALCULATOR", - 267: "tcod.event.SCANCODE_COMPUTER", - 268: "tcod.event.SCANCODE_AC_SEARCH", - 269: "tcod.event.SCANCODE_AC_HOME", - 270: "tcod.event.SCANCODE_AC_BACK", - 271: "tcod.event.SCANCODE_AC_FORWARD", - 272: "tcod.event.SCANCODE_AC_STOP", - 273: "tcod.event.SCANCODE_AC_REFRESH", - 274: "tcod.event.SCANCODE_AC_BOOKMARKS", - 275: "tcod.event.SCANCODE_BRIGHTNESSDOWN", - 276: "tcod.event.SCANCODE_BRIGHTNESSUP", - 277: "tcod.event.SCANCODE_DISPLAYSWITCH", - 278: "tcod.event.SCANCODE_KBDILLUMTOGGLE", - 279: "tcod.event.SCANCODE_KBDILLUMDOWN", - 280: "tcod.event.SCANCODE_KBDILLUMUP", - 281: "tcod.event.SCANCODE_EJECT", - 282: "tcod.event.SCANCODE_SLEEP", - 283: "tcod.event.SCANCODE_APP1", - 284: "tcod.event.SCANCODE_APP2", - 285: "tcod.event.SCANCODE_AUDIOREWIND", - 286: "tcod.event.SCANCODE_AUDIOFASTFORWARD", + 0: "SCANCODE_UNKNOWN", + 4: "SCANCODE_A", + 5: "SCANCODE_B", + 6: "SCANCODE_C", + 7: "SCANCODE_D", + 8: "SCANCODE_E", + 9: "SCANCODE_F", + 10: "SCANCODE_G", + 11: "SCANCODE_H", + 12: "SCANCODE_I", + 13: "SCANCODE_J", + 14: "SCANCODE_K", + 15: "SCANCODE_L", + 16: "SCANCODE_M", + 17: "SCANCODE_N", + 18: "SCANCODE_O", + 19: "SCANCODE_P", + 20: "SCANCODE_Q", + 21: "SCANCODE_R", + 22: "SCANCODE_S", + 23: "SCANCODE_T", + 24: "SCANCODE_U", + 25: "SCANCODE_V", + 26: "SCANCODE_W", + 27: "SCANCODE_X", + 28: "SCANCODE_Y", + 29: "SCANCODE_Z", + 30: "SCANCODE_1", + 31: "SCANCODE_2", + 32: "SCANCODE_3", + 33: "SCANCODE_4", + 34: "SCANCODE_5", + 35: "SCANCODE_6", + 36: "SCANCODE_7", + 37: "SCANCODE_8", + 38: "SCANCODE_9", + 39: "SCANCODE_0", + 40: "SCANCODE_RETURN", + 41: "SCANCODE_ESCAPE", + 42: "SCANCODE_BACKSPACE", + 43: "SCANCODE_TAB", + 44: "SCANCODE_SPACE", + 45: "SCANCODE_MINUS", + 46: "SCANCODE_EQUALS", + 47: "SCANCODE_LEFTBRACKET", + 48: "SCANCODE_RIGHTBRACKET", + 49: "SCANCODE_BACKSLASH", + 50: "SCANCODE_NONUSHASH", + 51: "SCANCODE_SEMICOLON", + 52: "SCANCODE_APOSTROPHE", + 53: "SCANCODE_GRAVE", + 54: "SCANCODE_COMMA", + 55: "SCANCODE_PERIOD", + 56: "SCANCODE_SLASH", + 57: "SCANCODE_CAPSLOCK", + 58: "SCANCODE_F1", + 59: "SCANCODE_F2", + 60: "SCANCODE_F3", + 61: "SCANCODE_F4", + 62: "SCANCODE_F5", + 63: "SCANCODE_F6", + 64: "SCANCODE_F7", + 65: "SCANCODE_F8", + 66: "SCANCODE_F9", + 67: "SCANCODE_F10", + 68: "SCANCODE_F11", + 69: "SCANCODE_F12", + 70: "SCANCODE_PRINTSCREEN", + 71: "SCANCODE_SCROLLLOCK", + 72: "SCANCODE_PAUSE", + 73: "SCANCODE_INSERT", + 74: "SCANCODE_HOME", + 75: "SCANCODE_PAGEUP", + 76: "SCANCODE_DELETE", + 77: "SCANCODE_END", + 78: "SCANCODE_PAGEDOWN", + 79: "SCANCODE_RIGHT", + 80: "SCANCODE_LEFT", + 81: "SCANCODE_DOWN", + 82: "SCANCODE_UP", + 83: "SCANCODE_NUMLOCKCLEAR", + 84: "SCANCODE_KP_DIVIDE", + 85: "SCANCODE_KP_MULTIPLY", + 86: "SCANCODE_KP_MINUS", + 87: "SCANCODE_KP_PLUS", + 88: "SCANCODE_KP_ENTER", + 89: "SCANCODE_KP_1", + 90: "SCANCODE_KP_2", + 91: "SCANCODE_KP_3", + 92: "SCANCODE_KP_4", + 93: "SCANCODE_KP_5", + 94: "SCANCODE_KP_6", + 95: "SCANCODE_KP_7", + 96: "SCANCODE_KP_8", + 97: "SCANCODE_KP_9", + 98: "SCANCODE_KP_0", + 99: "SCANCODE_KP_PERIOD", + 100: "SCANCODE_NONUSBACKSLASH", + 101: "SCANCODE_APPLICATION", + 102: "SCANCODE_POWER", + 103: "SCANCODE_KP_EQUALS", + 104: "SCANCODE_F13", + 105: "SCANCODE_F14", + 106: "SCANCODE_F15", + 107: "SCANCODE_F16", + 108: "SCANCODE_F17", + 109: "SCANCODE_F18", + 110: "SCANCODE_F19", + 111: "SCANCODE_F20", + 112: "SCANCODE_F21", + 113: "SCANCODE_F22", + 114: "SCANCODE_F23", + 115: "SCANCODE_F24", + 116: "SCANCODE_EXECUTE", + 117: "SCANCODE_HELP", + 118: "SCANCODE_MENU", + 119: "SCANCODE_SELECT", + 120: "SCANCODE_STOP", + 121: "SCANCODE_AGAIN", + 122: "SCANCODE_UNDO", + 123: "SCANCODE_CUT", + 124: "SCANCODE_COPY", + 125: "SCANCODE_PASTE", + 126: "SCANCODE_FIND", + 127: "SCANCODE_MUTE", + 128: "SCANCODE_VOLUMEUP", + 129: "SCANCODE_VOLUMEDOWN", + 133: "SCANCODE_KP_COMMA", + 134: "SCANCODE_KP_EQUALSAS400", + 135: "SCANCODE_INTERNATIONAL1", + 136: "SCANCODE_INTERNATIONAL2", + 137: "SCANCODE_INTERNATIONAL3", + 138: "SCANCODE_INTERNATIONAL4", + 139: "SCANCODE_INTERNATIONAL5", + 140: "SCANCODE_INTERNATIONAL6", + 141: "SCANCODE_INTERNATIONAL7", + 142: "SCANCODE_INTERNATIONAL8", + 143: "SCANCODE_INTERNATIONAL9", + 144: "SCANCODE_LANG1", + 145: "SCANCODE_LANG2", + 146: "SCANCODE_LANG3", + 147: "SCANCODE_LANG4", + 148: "SCANCODE_LANG5", + 149: "SCANCODE_LANG6", + 150: "SCANCODE_LANG7", + 151: "SCANCODE_LANG8", + 152: "SCANCODE_LANG9", + 153: "SCANCODE_ALTERASE", + 154: "SCANCODE_SYSREQ", + 155: "SCANCODE_CANCEL", + 156: "SCANCODE_CLEAR", + 157: "SCANCODE_PRIOR", + 158: "SCANCODE_RETURN2", + 159: "SCANCODE_SEPARATOR", + 160: "SCANCODE_OUT", + 161: "SCANCODE_OPER", + 162: "SCANCODE_CLEARAGAIN", + 163: "SCANCODE_CRSEL", + 164: "SCANCODE_EXSEL", + 176: "SCANCODE_KP_00", + 177: "SCANCODE_KP_000", + 178: "SCANCODE_THOUSANDSSEPARATOR", + 179: "SCANCODE_DECIMALSEPARATOR", + 180: "SCANCODE_CURRENCYUNIT", + 181: "SCANCODE_CURRENCYSUBUNIT", + 182: "SCANCODE_KP_LEFTPAREN", + 183: "SCANCODE_KP_RIGHTPAREN", + 184: "SCANCODE_KP_LEFTBRACE", + 185: "SCANCODE_KP_RIGHTBRACE", + 186: "SCANCODE_KP_TAB", + 187: "SCANCODE_KP_BACKSPACE", + 188: "SCANCODE_KP_A", + 189: "SCANCODE_KP_B", + 190: "SCANCODE_KP_C", + 191: "SCANCODE_KP_D", + 192: "SCANCODE_KP_E", + 193: "SCANCODE_KP_F", + 194: "SCANCODE_KP_XOR", + 195: "SCANCODE_KP_POWER", + 196: "SCANCODE_KP_PERCENT", + 197: "SCANCODE_KP_LESS", + 198: "SCANCODE_KP_GREATER", + 199: "SCANCODE_KP_AMPERSAND", + 200: "SCANCODE_KP_DBLAMPERSAND", + 201: "SCANCODE_KP_VERTICALBAR", + 202: "SCANCODE_KP_DBLVERTICALBAR", + 203: "SCANCODE_KP_COLON", + 204: "SCANCODE_KP_HASH", + 205: "SCANCODE_KP_SPACE", + 206: "SCANCODE_KP_AT", + 207: "SCANCODE_KP_EXCLAM", + 208: "SCANCODE_KP_MEMSTORE", + 209: "SCANCODE_KP_MEMRECALL", + 210: "SCANCODE_KP_MEMCLEAR", + 211: "SCANCODE_KP_MEMADD", + 212: "SCANCODE_KP_MEMSUBTRACT", + 213: "SCANCODE_KP_MEMMULTIPLY", + 214: "SCANCODE_KP_MEMDIVIDE", + 215: "SCANCODE_KP_PLUSMINUS", + 216: "SCANCODE_KP_CLEAR", + 217: "SCANCODE_KP_CLEARENTRY", + 218: "SCANCODE_KP_BINARY", + 219: "SCANCODE_KP_OCTAL", + 220: "SCANCODE_KP_DECIMAL", + 221: "SCANCODE_KP_HEXADECIMAL", + 224: "SCANCODE_LCTRL", + 225: "SCANCODE_LSHIFT", + 226: "SCANCODE_LALT", + 227: "SCANCODE_LGUI", + 228: "SCANCODE_RCTRL", + 229: "SCANCODE_RSHIFT", + 230: "SCANCODE_RALT", + 231: "SCANCODE_RGUI", + 257: "SCANCODE_MODE", + 258: "SCANCODE_AUDIONEXT", + 259: "SCANCODE_AUDIOPREV", + 260: "SCANCODE_AUDIOSTOP", + 261: "SCANCODE_AUDIOPLAY", + 262: "SCANCODE_AUDIOMUTE", + 263: "SCANCODE_MEDIASELECT", + 264: "SCANCODE_WWW", + 265: "SCANCODE_MAIL", + 266: "SCANCODE_CALCULATOR", + 267: "SCANCODE_COMPUTER", + 268: "SCANCODE_AC_SEARCH", + 269: "SCANCODE_AC_HOME", + 270: "SCANCODE_AC_BACK", + 271: "SCANCODE_AC_FORWARD", + 272: "SCANCODE_AC_STOP", + 273: "SCANCODE_AC_REFRESH", + 274: "SCANCODE_AC_BOOKMARKS", + 275: "SCANCODE_BRIGHTNESSDOWN", + 276: "SCANCODE_BRIGHTNESSUP", + 277: "SCANCODE_DISPLAYSWITCH", + 278: "SCANCODE_KBDILLUMTOGGLE", + 279: "SCANCODE_KBDILLUMDOWN", + 280: "SCANCODE_KBDILLUMUP", + 281: "SCANCODE_EJECT", + 282: "SCANCODE_SLEEP", + 283: "SCANCODE_APP1", + 284: "SCANCODE_APP2", } # --- SDL keyboard symbols --- @@ -565,6 +561,7 @@ K_y = 121 K_z = 122 K_DELETE = 127 +K_SCANCODE_MASK = 1073741824 K_CAPSLOCK = 1073741881 K_F1 = 1073741882 K_F2 = 1073741883 @@ -730,281 +727,282 @@ K_KBDILLUMUP = 1073742104 K_EJECT = 1073742105 K_SLEEP = 1073742106 -K_APP1 = 1073742107 -K_APP2 = 1073742108 -K_AUDIOREWIND = 1073742109 -K_AUDIOFASTFORWARD = 1073742110 _REVERSE_SYM_TABLE = { - 0: "tcod.event.K_UNKNOWN", - 8: "tcod.event.K_BACKSPACE", - 9: "tcod.event.K_TAB", - 13: "tcod.event.K_RETURN", - 27: "tcod.event.K_ESCAPE", - 32: "tcod.event.K_SPACE", - 33: "tcod.event.K_EXCLAIM", - 34: "tcod.event.K_QUOTEDBL", - 35: "tcod.event.K_HASH", - 36: "tcod.event.K_DOLLAR", - 37: "tcod.event.K_PERCENT", - 38: "tcod.event.K_AMPERSAND", - 39: "tcod.event.K_QUOTE", - 40: "tcod.event.K_LEFTPAREN", - 41: "tcod.event.K_RIGHTPAREN", - 42: "tcod.event.K_ASTERISK", - 43: "tcod.event.K_PLUS", - 44: "tcod.event.K_COMMA", - 45: "tcod.event.K_MINUS", - 46: "tcod.event.K_PERIOD", - 47: "tcod.event.K_SLASH", - 48: "tcod.event.K_0", - 49: "tcod.event.K_1", - 50: "tcod.event.K_2", - 51: "tcod.event.K_3", - 52: "tcod.event.K_4", - 53: "tcod.event.K_5", - 54: "tcod.event.K_6", - 55: "tcod.event.K_7", - 56: "tcod.event.K_8", - 57: "tcod.event.K_9", - 58: "tcod.event.K_COLON", - 59: "tcod.event.K_SEMICOLON", - 60: "tcod.event.K_LESS", - 61: "tcod.event.K_EQUALS", - 62: "tcod.event.K_GREATER", - 63: "tcod.event.K_QUESTION", - 64: "tcod.event.K_AT", - 91: "tcod.event.K_LEFTBRACKET", - 92: "tcod.event.K_BACKSLASH", - 93: "tcod.event.K_RIGHTBRACKET", - 94: "tcod.event.K_CARET", - 95: "tcod.event.K_UNDERSCORE", - 96: "tcod.event.K_BACKQUOTE", - 97: "tcod.event.K_a", - 98: "tcod.event.K_b", - 99: "tcod.event.K_c", - 100: "tcod.event.K_d", - 101: "tcod.event.K_e", - 102: "tcod.event.K_f", - 103: "tcod.event.K_g", - 104: "tcod.event.K_h", - 105: "tcod.event.K_i", - 106: "tcod.event.K_j", - 107: "tcod.event.K_k", - 108: "tcod.event.K_l", - 109: "tcod.event.K_m", - 110: "tcod.event.K_n", - 111: "tcod.event.K_o", - 112: "tcod.event.K_p", - 113: "tcod.event.K_q", - 114: "tcod.event.K_r", - 115: "tcod.event.K_s", - 116: "tcod.event.K_t", - 117: "tcod.event.K_u", - 118: "tcod.event.K_v", - 119: "tcod.event.K_w", - 120: "tcod.event.K_x", - 121: "tcod.event.K_y", - 122: "tcod.event.K_z", - 127: "tcod.event.K_DELETE", - 1073741881: "tcod.event.K_CAPSLOCK", - 1073741882: "tcod.event.K_F1", - 1073741883: "tcod.event.K_F2", - 1073741884: "tcod.event.K_F3", - 1073741885: "tcod.event.K_F4", - 1073741886: "tcod.event.K_F5", - 1073741887: "tcod.event.K_F6", - 1073741888: "tcod.event.K_F7", - 1073741889: "tcod.event.K_F8", - 1073741890: "tcod.event.K_F9", - 1073741891: "tcod.event.K_F10", - 1073741892: "tcod.event.K_F11", - 1073741893: "tcod.event.K_F12", - 1073741894: "tcod.event.K_PRINTSCREEN", - 1073741895: "tcod.event.K_SCROLLLOCK", - 1073741896: "tcod.event.K_PAUSE", - 1073741897: "tcod.event.K_INSERT", - 1073741898: "tcod.event.K_HOME", - 1073741899: "tcod.event.K_PAGEUP", - 1073741901: "tcod.event.K_END", - 1073741902: "tcod.event.K_PAGEDOWN", - 1073741903: "tcod.event.K_RIGHT", - 1073741904: "tcod.event.K_LEFT", - 1073741905: "tcod.event.K_DOWN", - 1073741906: "tcod.event.K_UP", - 1073741907: "tcod.event.K_NUMLOCKCLEAR", - 1073741908: "tcod.event.K_KP_DIVIDE", - 1073741909: "tcod.event.K_KP_MULTIPLY", - 1073741910: "tcod.event.K_KP_MINUS", - 1073741911: "tcod.event.K_KP_PLUS", - 1073741912: "tcod.event.K_KP_ENTER", - 1073741913: "tcod.event.K_KP_1", - 1073741914: "tcod.event.K_KP_2", - 1073741915: "tcod.event.K_KP_3", - 1073741916: "tcod.event.K_KP_4", - 1073741917: "tcod.event.K_KP_5", - 1073741918: "tcod.event.K_KP_6", - 1073741919: "tcod.event.K_KP_7", - 1073741920: "tcod.event.K_KP_8", - 1073741921: "tcod.event.K_KP_9", - 1073741922: "tcod.event.K_KP_0", - 1073741923: "tcod.event.K_KP_PERIOD", - 1073741925: "tcod.event.K_APPLICATION", - 1073741926: "tcod.event.K_POWER", - 1073741927: "tcod.event.K_KP_EQUALS", - 1073741928: "tcod.event.K_F13", - 1073741929: "tcod.event.K_F14", - 1073741930: "tcod.event.K_F15", - 1073741931: "tcod.event.K_F16", - 1073741932: "tcod.event.K_F17", - 1073741933: "tcod.event.K_F18", - 1073741934: "tcod.event.K_F19", - 1073741935: "tcod.event.K_F20", - 1073741936: "tcod.event.K_F21", - 1073741937: "tcod.event.K_F22", - 1073741938: "tcod.event.K_F23", - 1073741939: "tcod.event.K_F24", - 1073741940: "tcod.event.K_EXECUTE", - 1073741941: "tcod.event.K_HELP", - 1073741942: "tcod.event.K_MENU", - 1073741943: "tcod.event.K_SELECT", - 1073741944: "tcod.event.K_STOP", - 1073741945: "tcod.event.K_AGAIN", - 1073741946: "tcod.event.K_UNDO", - 1073741947: "tcod.event.K_CUT", - 1073741948: "tcod.event.K_COPY", - 1073741949: "tcod.event.K_PASTE", - 1073741950: "tcod.event.K_FIND", - 1073741951: "tcod.event.K_MUTE", - 1073741952: "tcod.event.K_VOLUMEUP", - 1073741953: "tcod.event.K_VOLUMEDOWN", - 1073741957: "tcod.event.K_KP_COMMA", - 1073741958: "tcod.event.K_KP_EQUALSAS400", - 1073741977: "tcod.event.K_ALTERASE", - 1073741978: "tcod.event.K_SYSREQ", - 1073741979: "tcod.event.K_CANCEL", - 1073741980: "tcod.event.K_CLEAR", - 1073741981: "tcod.event.K_PRIOR", - 1073741982: "tcod.event.K_RETURN2", - 1073741983: "tcod.event.K_SEPARATOR", - 1073741984: "tcod.event.K_OUT", - 1073741985: "tcod.event.K_OPER", - 1073741986: "tcod.event.K_CLEARAGAIN", - 1073741987: "tcod.event.K_CRSEL", - 1073741988: "tcod.event.K_EXSEL", - 1073742000: "tcod.event.K_KP_00", - 1073742001: "tcod.event.K_KP_000", - 1073742002: "tcod.event.K_THOUSANDSSEPARATOR", - 1073742003: "tcod.event.K_DECIMALSEPARATOR", - 1073742004: "tcod.event.K_CURRENCYUNIT", - 1073742005: "tcod.event.K_CURRENCYSUBUNIT", - 1073742006: "tcod.event.K_KP_LEFTPAREN", - 1073742007: "tcod.event.K_KP_RIGHTPAREN", - 1073742008: "tcod.event.K_KP_LEFTBRACE", - 1073742009: "tcod.event.K_KP_RIGHTBRACE", - 1073742010: "tcod.event.K_KP_TAB", - 1073742011: "tcod.event.K_KP_BACKSPACE", - 1073742012: "tcod.event.K_KP_A", - 1073742013: "tcod.event.K_KP_B", - 1073742014: "tcod.event.K_KP_C", - 1073742015: "tcod.event.K_KP_D", - 1073742016: "tcod.event.K_KP_E", - 1073742017: "tcod.event.K_KP_F", - 1073742018: "tcod.event.K_KP_XOR", - 1073742019: "tcod.event.K_KP_POWER", - 1073742020: "tcod.event.K_KP_PERCENT", - 1073742021: "tcod.event.K_KP_LESS", - 1073742022: "tcod.event.K_KP_GREATER", - 1073742023: "tcod.event.K_KP_AMPERSAND", - 1073742024: "tcod.event.K_KP_DBLAMPERSAND", - 1073742025: "tcod.event.K_KP_VERTICALBAR", - 1073742026: "tcod.event.K_KP_DBLVERTICALBAR", - 1073742027: "tcod.event.K_KP_COLON", - 1073742028: "tcod.event.K_KP_HASH", - 1073742029: "tcod.event.K_KP_SPACE", - 1073742030: "tcod.event.K_KP_AT", - 1073742031: "tcod.event.K_KP_EXCLAM", - 1073742032: "tcod.event.K_KP_MEMSTORE", - 1073742033: "tcod.event.K_KP_MEMRECALL", - 1073742034: "tcod.event.K_KP_MEMCLEAR", - 1073742035: "tcod.event.K_KP_MEMADD", - 1073742036: "tcod.event.K_KP_MEMSUBTRACT", - 1073742037: "tcod.event.K_KP_MEMMULTIPLY", - 1073742038: "tcod.event.K_KP_MEMDIVIDE", - 1073742039: "tcod.event.K_KP_PLUSMINUS", - 1073742040: "tcod.event.K_KP_CLEAR", - 1073742041: "tcod.event.K_KP_CLEARENTRY", - 1073742042: "tcod.event.K_KP_BINARY", - 1073742043: "tcod.event.K_KP_OCTAL", - 1073742044: "tcod.event.K_KP_DECIMAL", - 1073742045: "tcod.event.K_KP_HEXADECIMAL", - 1073742048: "tcod.event.K_LCTRL", - 1073742049: "tcod.event.K_LSHIFT", - 1073742050: "tcod.event.K_LALT", - 1073742051: "tcod.event.K_LGUI", - 1073742052: "tcod.event.K_RCTRL", - 1073742053: "tcod.event.K_RSHIFT", - 1073742054: "tcod.event.K_RALT", - 1073742055: "tcod.event.K_RGUI", - 1073742081: "tcod.event.K_MODE", - 1073742082: "tcod.event.K_AUDIONEXT", - 1073742083: "tcod.event.K_AUDIOPREV", - 1073742084: "tcod.event.K_AUDIOSTOP", - 1073742085: "tcod.event.K_AUDIOPLAY", - 1073742086: "tcod.event.K_AUDIOMUTE", - 1073742087: "tcod.event.K_MEDIASELECT", - 1073742088: "tcod.event.K_WWW", - 1073742089: "tcod.event.K_MAIL", - 1073742090: "tcod.event.K_CALCULATOR", - 1073742091: "tcod.event.K_COMPUTER", - 1073742092: "tcod.event.K_AC_SEARCH", - 1073742093: "tcod.event.K_AC_HOME", - 1073742094: "tcod.event.K_AC_BACK", - 1073742095: "tcod.event.K_AC_FORWARD", - 1073742096: "tcod.event.K_AC_STOP", - 1073742097: "tcod.event.K_AC_REFRESH", - 1073742098: "tcod.event.K_AC_BOOKMARKS", - 1073742099: "tcod.event.K_BRIGHTNESSDOWN", - 1073742100: "tcod.event.K_BRIGHTNESSUP", - 1073742101: "tcod.event.K_DISPLAYSWITCH", - 1073742102: "tcod.event.K_KBDILLUMTOGGLE", - 1073742103: "tcod.event.K_KBDILLUMDOWN", - 1073742104: "tcod.event.K_KBDILLUMUP", - 1073742105: "tcod.event.K_EJECT", - 1073742106: "tcod.event.K_SLEEP", - 1073742107: "tcod.event.K_APP1", - 1073742108: "tcod.event.K_APP2", - 1073742109: "tcod.event.K_AUDIOREWIND", - 1073742110: "tcod.event.K_AUDIOFASTFORWARD", + 0: "K_UNKNOWN", + 8: "K_BACKSPACE", + 9: "K_TAB", + 13: "K_RETURN", + 27: "K_ESCAPE", + 32: "K_SPACE", + 33: "K_EXCLAIM", + 34: "K_QUOTEDBL", + 35: "K_HASH", + 36: "K_DOLLAR", + 37: "K_PERCENT", + 38: "K_AMPERSAND", + 39: "K_QUOTE", + 40: "K_LEFTPAREN", + 41: "K_RIGHTPAREN", + 42: "K_ASTERISK", + 43: "K_PLUS", + 44: "K_COMMA", + 45: "K_MINUS", + 46: "K_PERIOD", + 47: "K_SLASH", + 48: "K_0", + 49: "K_1", + 50: "K_2", + 51: "K_3", + 52: "K_4", + 53: "K_5", + 54: "K_6", + 55: "K_7", + 56: "K_8", + 57: "K_9", + 58: "K_COLON", + 59: "K_SEMICOLON", + 60: "K_LESS", + 61: "K_EQUALS", + 62: "K_GREATER", + 63: "K_QUESTION", + 64: "K_AT", + 91: "K_LEFTBRACKET", + 92: "K_BACKSLASH", + 93: "K_RIGHTBRACKET", + 94: "K_CARET", + 95: "K_UNDERSCORE", + 96: "K_BACKQUOTE", + 97: "K_a", + 98: "K_b", + 99: "K_c", + 100: "K_d", + 101: "K_e", + 102: "K_f", + 103: "K_g", + 104: "K_h", + 105: "K_i", + 106: "K_j", + 107: "K_k", + 108: "K_l", + 109: "K_m", + 110: "K_n", + 111: "K_o", + 112: "K_p", + 113: "K_q", + 114: "K_r", + 115: "K_s", + 116: "K_t", + 117: "K_u", + 118: "K_v", + 119: "K_w", + 120: "K_x", + 121: "K_y", + 122: "K_z", + 127: "K_DELETE", + 1073741824: "K_SCANCODE_MASK", + 1073741881: "K_CAPSLOCK", + 1073741882: "K_F1", + 1073741883: "K_F2", + 1073741884: "K_F3", + 1073741885: "K_F4", + 1073741886: "K_F5", + 1073741887: "K_F6", + 1073741888: "K_F7", + 1073741889: "K_F8", + 1073741890: "K_F9", + 1073741891: "K_F10", + 1073741892: "K_F11", + 1073741893: "K_F12", + 1073741894: "K_PRINTSCREEN", + 1073741895: "K_SCROLLLOCK", + 1073741896: "K_PAUSE", + 1073741897: "K_INSERT", + 1073741898: "K_HOME", + 1073741899: "K_PAGEUP", + 1073741901: "K_END", + 1073741902: "K_PAGEDOWN", + 1073741903: "K_RIGHT", + 1073741904: "K_LEFT", + 1073741905: "K_DOWN", + 1073741906: "K_UP", + 1073741907: "K_NUMLOCKCLEAR", + 1073741908: "K_KP_DIVIDE", + 1073741909: "K_KP_MULTIPLY", + 1073741910: "K_KP_MINUS", + 1073741911: "K_KP_PLUS", + 1073741912: "K_KP_ENTER", + 1073741913: "K_KP_1", + 1073741914: "K_KP_2", + 1073741915: "K_KP_3", + 1073741916: "K_KP_4", + 1073741917: "K_KP_5", + 1073741918: "K_KP_6", + 1073741919: "K_KP_7", + 1073741920: "K_KP_8", + 1073741921: "K_KP_9", + 1073741922: "K_KP_0", + 1073741923: "K_KP_PERIOD", + 1073741925: "K_APPLICATION", + 1073741926: "K_POWER", + 1073741927: "K_KP_EQUALS", + 1073741928: "K_F13", + 1073741929: "K_F14", + 1073741930: "K_F15", + 1073741931: "K_F16", + 1073741932: "K_F17", + 1073741933: "K_F18", + 1073741934: "K_F19", + 1073741935: "K_F20", + 1073741936: "K_F21", + 1073741937: "K_F22", + 1073741938: "K_F23", + 1073741939: "K_F24", + 1073741940: "K_EXECUTE", + 1073741941: "K_HELP", + 1073741942: "K_MENU", + 1073741943: "K_SELECT", + 1073741944: "K_STOP", + 1073741945: "K_AGAIN", + 1073741946: "K_UNDO", + 1073741947: "K_CUT", + 1073741948: "K_COPY", + 1073741949: "K_PASTE", + 1073741950: "K_FIND", + 1073741951: "K_MUTE", + 1073741952: "K_VOLUMEUP", + 1073741953: "K_VOLUMEDOWN", + 1073741957: "K_KP_COMMA", + 1073741958: "K_KP_EQUALSAS400", + 1073741977: "K_ALTERASE", + 1073741978: "K_SYSREQ", + 1073741979: "K_CANCEL", + 1073741980: "K_CLEAR", + 1073741981: "K_PRIOR", + 1073741982: "K_RETURN2", + 1073741983: "K_SEPARATOR", + 1073741984: "K_OUT", + 1073741985: "K_OPER", + 1073741986: "K_CLEARAGAIN", + 1073741987: "K_CRSEL", + 1073741988: "K_EXSEL", + 1073742000: "K_KP_00", + 1073742001: "K_KP_000", + 1073742002: "K_THOUSANDSSEPARATOR", + 1073742003: "K_DECIMALSEPARATOR", + 1073742004: "K_CURRENCYUNIT", + 1073742005: "K_CURRENCYSUBUNIT", + 1073742006: "K_KP_LEFTPAREN", + 1073742007: "K_KP_RIGHTPAREN", + 1073742008: "K_KP_LEFTBRACE", + 1073742009: "K_KP_RIGHTBRACE", + 1073742010: "K_KP_TAB", + 1073742011: "K_KP_BACKSPACE", + 1073742012: "K_KP_A", + 1073742013: "K_KP_B", + 1073742014: "K_KP_C", + 1073742015: "K_KP_D", + 1073742016: "K_KP_E", + 1073742017: "K_KP_F", + 1073742018: "K_KP_XOR", + 1073742019: "K_KP_POWER", + 1073742020: "K_KP_PERCENT", + 1073742021: "K_KP_LESS", + 1073742022: "K_KP_GREATER", + 1073742023: "K_KP_AMPERSAND", + 1073742024: "K_KP_DBLAMPERSAND", + 1073742025: "K_KP_VERTICALBAR", + 1073742026: "K_KP_DBLVERTICALBAR", + 1073742027: "K_KP_COLON", + 1073742028: "K_KP_HASH", + 1073742029: "K_KP_SPACE", + 1073742030: "K_KP_AT", + 1073742031: "K_KP_EXCLAM", + 1073742032: "K_KP_MEMSTORE", + 1073742033: "K_KP_MEMRECALL", + 1073742034: "K_KP_MEMCLEAR", + 1073742035: "K_KP_MEMADD", + 1073742036: "K_KP_MEMSUBTRACT", + 1073742037: "K_KP_MEMMULTIPLY", + 1073742038: "K_KP_MEMDIVIDE", + 1073742039: "K_KP_PLUSMINUS", + 1073742040: "K_KP_CLEAR", + 1073742041: "K_KP_CLEARENTRY", + 1073742042: "K_KP_BINARY", + 1073742043: "K_KP_OCTAL", + 1073742044: "K_KP_DECIMAL", + 1073742045: "K_KP_HEXADECIMAL", + 1073742048: "K_LCTRL", + 1073742049: "K_LSHIFT", + 1073742050: "K_LALT", + 1073742051: "K_LGUI", + 1073742052: "K_RCTRL", + 1073742053: "K_RSHIFT", + 1073742054: "K_RALT", + 1073742055: "K_RGUI", + 1073742081: "K_MODE", + 1073742082: "K_AUDIONEXT", + 1073742083: "K_AUDIOPREV", + 1073742084: "K_AUDIOSTOP", + 1073742085: "K_AUDIOPLAY", + 1073742086: "K_AUDIOMUTE", + 1073742087: "K_MEDIASELECT", + 1073742088: "K_WWW", + 1073742089: "K_MAIL", + 1073742090: "K_CALCULATOR", + 1073742091: "K_COMPUTER", + 1073742092: "K_AC_SEARCH", + 1073742093: "K_AC_HOME", + 1073742094: "K_AC_BACK", + 1073742095: "K_AC_FORWARD", + 1073742096: "K_AC_STOP", + 1073742097: "K_AC_REFRESH", + 1073742098: "K_AC_BOOKMARKS", + 1073742099: "K_BRIGHTNESSDOWN", + 1073742100: "K_BRIGHTNESSUP", + 1073742101: "K_DISPLAYSWITCH", + 1073742102: "K_KBDILLUMTOGGLE", + 1073742103: "K_KBDILLUMDOWN", + 1073742104: "K_KBDILLUMUP", + 1073742105: "K_EJECT", + 1073742106: "K_SLEEP", } # --- SDL keyboard modifiers --- KMOD_NONE = 0 KMOD_LSHIFT = 1 KMOD_RSHIFT = 2 +KMOD_SHIFT = 3 KMOD_LCTRL = 64 KMOD_RCTRL = 128 +KMOD_CTRL = 192 KMOD_LALT = 256 KMOD_RALT = 512 +KMOD_ALT = 768 KMOD_LGUI = 1024 KMOD_RGUI = 2048 +KMOD_GUI = 3072 KMOD_NUM = 4096 KMOD_CAPS = 8192 KMOD_MODE = 16384 KMOD_RESERVED = 32768 _REVERSE_MOD_TABLE = { - 0: "tcod.event.KMOD_NONE", - 1: "tcod.event.KMOD_LSHIFT", - 2: "tcod.event.KMOD_RSHIFT", - 64: "tcod.event.KMOD_LCTRL", - 128: "tcod.event.KMOD_RCTRL", - 256: "tcod.event.KMOD_LALT", - 512: "tcod.event.KMOD_RALT", - 1024: "tcod.event.KMOD_LGUI", - 2048: "tcod.event.KMOD_RGUI", - 4096: "tcod.event.KMOD_NUM", - 8192: "tcod.event.KMOD_CAPS", - 16384: "tcod.event.KMOD_MODE", - 32768: "tcod.event.KMOD_RESERVED", + 0: "KMOD_NONE", + 1: "KMOD_LSHIFT", + 2: "KMOD_RSHIFT", + 3: "KMOD_SHIFT", + 64: "KMOD_LCTRL", + 128: "KMOD_RCTRL", + 192: "KMOD_CTRL", + 256: "KMOD_LALT", + 512: "KMOD_RALT", + 768: "KMOD_ALT", + 1024: "KMOD_LGUI", + 2048: "KMOD_RGUI", + 3072: "KMOD_GUI", + 4096: "KMOD_NUM", + 8192: "KMOD_CAPS", + 16384: "KMOD_MODE", + 32768: "KMOD_RESERVED", } # --- SDL wheel --- @@ -1012,9 +1010,9 @@ MOUSEWHEEL_FLIPPED = 1 MOUSEWHEEL = 1027 _REVERSE_WHEEL_TABLE = { - 0: "tcod.event.MOUSEWHEEL_NORMAL", - 1: "tcod.event.MOUSEWHEEL_FLIPPED", - 1027: "tcod.event.MOUSEWHEEL", + 0: "MOUSEWHEEL_NORMAL", + 1: "MOUSEWHEEL_FLIPPED", + 1027: "MOUSEWHEEL", } __all__ = [ @@ -1259,8 +1257,6 @@ "SCANCODE_SLEEP", "SCANCODE_APP1", "SCANCODE_APP2", - "SCANCODE_AUDIOREWIND", - "SCANCODE_AUDIOFASTFORWARD", "K_UNKNOWN", "K_BACKSPACE", "K_TAB", @@ -1332,6 +1328,7 @@ "K_y", "K_z", "K_DELETE", + "K_SCANCODE_MASK", "K_CAPSLOCK", "K_F1", "K_F2", @@ -1497,19 +1494,19 @@ "K_KBDILLUMUP", "K_EJECT", "K_SLEEP", - "K_APP1", - "K_APP2", - "K_AUDIOREWIND", - "K_AUDIOFASTFORWARD", "KMOD_NONE", "KMOD_LSHIFT", "KMOD_RSHIFT", + "KMOD_SHIFT", "KMOD_LCTRL", "KMOD_RCTRL", + "KMOD_CTRL", "KMOD_LALT", "KMOD_RALT", + "KMOD_ALT", "KMOD_LGUI", "KMOD_RGUI", + "KMOD_GUI", "KMOD_NUM", "KMOD_CAPS", "KMOD_MODE", From 49347b5668223e3c4021814b9111ddb1d7362da1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 2 Jun 2019 14:06:24 -0700 Subject: [PATCH 0166/1101] Removed outdated examples. --- examples/{sdlevent.py => eventget.py} | 7 +- examples/eventget_tcod.py | 27 ---- examples/eventget_tdl.py | 33 ----- examples/helloWorld.py | 27 ---- examples/interactive.py | 124 ------------------ examples/life.py | 167 ------------------------ examples/tutorial/1-Basics.py | 50 ------- examples/tutorial/2-Movement.py | 77 ----------- examples/tutorial/terminal8x8_gs_ro.png | Bin 3696 -> 0 bytes 9 files changed, 6 insertions(+), 506 deletions(-) rename examples/{sdlevent.py => eventget.py} (74%) delete mode 100755 examples/eventget_tcod.py delete mode 100755 examples/eventget_tdl.py delete mode 100755 examples/helloWorld.py delete mode 100755 examples/interactive.py delete mode 100755 examples/life.py delete mode 100755 examples/tutorial/1-Basics.py delete mode 100755 examples/tutorial/2-Movement.py delete mode 100755 examples/tutorial/terminal8x8_gs_ro.png diff --git a/examples/sdlevent.py b/examples/eventget.py similarity index 74% rename from examples/sdlevent.py rename to examples/eventget.py index 5e020dbe..1c7a7f9e 100755 --- a/examples/sdlevent.py +++ b/examples/eventget.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 - +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for this example. This work is +# published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +"""An demonstration of event handling using the tcod.event module. +""" import tcod import tcod.event diff --git a/examples/eventget_tcod.py b/examples/eventget_tcod.py deleted file mode 100755 index 5f2c4a5c..00000000 --- a/examples/eventget_tcod.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -import tcod - -WIDTH, HEIGHT = 80, 60 - -key = tcod.Key() -mouse = tcod.Mouse() - -with tcod.console_init_root(WIDTH, HEIGHT, 'tcod events example', - renderer=tcod.RENDERER_SDL) as console: - tcod.sys_set_fps(24) - while not tcod.console_is_window_closed(): - ev = tcod.sys_wait_for_event(tcod.EVENT_ANY, key, mouse, False) - if ev & tcod.EVENT_KEY: - console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) - console.print_(0, HEIGHT - 3, repr(key)) - print(key) - if ev & tcod.EVENT_MOUSE_MOVE: - console.rect(0, HEIGHT - 1, WIDTH, 1, True) - console.print_(0, HEIGHT - 1, repr(mouse)) - print(mouse) - elif ev & tcod.EVENT_MOUSE: - console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) - console.print_(0, HEIGHT - 3, repr(mouse)) - print(mouse) - tcod.console_flush() diff --git a/examples/eventget_tdl.py b/examples/eventget_tdl.py deleted file mode 100755 index 66d8dfb9..00000000 --- a/examples/eventget_tdl.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 -""" - An interactive example of what events are available. -""" - -import tdl - -WIDTH, HEIGHT = 80, 60 - -console = tdl.init(WIDTH, HEIGHT) - -# the scrolling text window -textWindow = tdl.Window(console, 0, 0, WIDTH, -2) - -# slow down the program so that the user can more clearly see the motion events -tdl.set_fps(24) - -while 1: - event = tdl.event.wait() - print(event) - if event.type == 'QUIT': - raise SystemExit() - elif event.type == 'MOUSEMOTION': - # clear and print to the bottom of the console - console.draw_rect(0, HEIGHT - 1, None, None, ' ') - console.draw_str(0, HEIGHT - 1, 'MOUSEMOTION event - pos=%i,%i cell=%i,%i motion=%i,%i cellmotion=%i,%i' % (event.pos + event.cell + event.motion + event.cellmotion)) - continue # prevent scrolling - - textWindow.scroll(0, -1) - if event.type == 'KEYDOWN' or event.type == 'KEYUP': - textWindow.draw_str(0, HEIGHT-3, '%s event - char=%s key=%s alt=%i control=%i shift=%i' % (event.type.ljust(7), repr(event.char), repr(event.key), event.alt, event.control, event.shift)) - elif event.type == 'MOUSEDOWN' or event.type == 'MOUSEUP': - textWindow.draw_str(0, HEIGHT-3, '%s event - pos=%i,%i cell=%i,%i button=%s' % ((event.type.ljust(9),) + event.pos + event.cell + (repr(event.button),))) diff --git a/examples/helloWorld.py b/examples/helloWorld.py deleted file mode 100755 index a56014ee..00000000 --- a/examples/helloWorld.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -""" - Example showing some of the most basic functions needed to say "Hello World" -""" -# import the tdl library and all of it's functions, this gives us access to anything -# starting with "tdl." -import tdl - -# start the main console, this will open the window that you see and give you a Console. -# we make a small window that's 20 tiles wide and 16 tile's tall for this example. -console = tdl.init(20, 16) - -# draw the string "Hello World" at the top left corner using the default colors: -# a white forground on a black background. -console.draw_str(0, 0, 'Hello World') - -# display the changes to the console with flush. -# if you forget this part the screen will stay black emptiness forever. -tdl.flush() - -# wait for a key press, any key pressed now will cause the program flow to the next part -# which closes out of the program. -tdl.event.keyWait() - -# if you run this example in IDLE then we'll need to delete the console manually -# otherwise IDLE prevents the window from closing causing it to hang -del console diff --git a/examples/interactive.py b/examples/interactive.py deleted file mode 100755 index bfd16c3c..00000000 --- a/examples/interactive.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -""" - Not much commentary in this example. It's more of a demo. -""" -import sys -import code -import io -import time -import traceback - -import tdl - -sys.ps1 = '>>> ' -sys.ps2 = '... ' - -WIDTH, HEIGHT = 80, 50 -console = tdl.init(WIDTH, HEIGHT, 'Python Interpeter in TDL') -console.setMode('scroll') - -class TDLPrint(io.TextIOBase): - def __init__(self, fgcolor=(255, 255, 255), bgcolor=(0, 0, 0)): - self.colors = fgcolor, bgcolor - - def write(self, string): - olderr.write(string) - console.setColors(*self.colors) - console.write(string) - -#sys.stdout = TDLPrint() -sys.stdout = console -sys.stdout.move(0, HEIGHT-1) -olderr = newerr = sys.stderr -newerr = TDLPrint((255, 255, 255), (127, 0, 0)) - -def exit(): - raise SystemExit - -interpeter = code.InteractiveConsole({'tdl':tdl, - 'console':console, - 'exit':exit}) - -def main(): - print() - print('Python %s' % sys.version) - print('Press ESC to quit') - - buffer = '' - commands = [''] - banner = sys.ps1 - cursor = 0 - while 1: - console.draw_rect(0, HEIGHT-1, None, 1, ' ', (255, 255, 255), (0, 0, 0)) - console.draw_str(0, HEIGHT-1, banner + buffer) - try: - console.draw_char(len(banner) + cursor, HEIGHT-1, None, None, (0, 255, 255)) - except tdl.TDLError: - pass - tdl.flush() - - for event in tdl.event.get(): - if event.type == 'QUIT': - raise SystemExit() - if event.type == 'KEYDOWN': - if event.key == 'ENTER' or event.key == 'KPENTER': - sys.stderr = newerr - try: - console.draw_rect(0, HEIGHT-1, None, 1, None, (255, 255, 255), (0, 0, 0)) - console.scroll(0, -1) - if interpeter.push(buffer): - banner = sys.ps2 - else: - banner = sys.ps1 - except SystemExit: - raise - except: - sys.excepthook(*sys.exc_info()) - banner = sys.ps1 - finally: - sys.stderr = olderr - sys.stdout = olderr - sys.stdout = console - if buffer not in commands: - commands.append(buffer) - buffer = '' - elif event.key == 'BACKSPACE': - if cursor == 0: - continue - if buffer[:cursor][-4:] == ' ': - buffer = buffer[:cursor-4] + buffer[cursor:] - cursor -= 4 - elif buffer: - buffer = buffer[:cursor-1] + buffer[cursor:] - cursor -= 1 - elif event.key == 'DELETE': - buffer = buffer[:cursor] + buffer[cursor+1:] - elif event.key == 'LEFT': - cursor -= 1 - elif event.key == 'RIGHT': - cursor += 1 - elif event.key == 'HOME': - cursor = 0 - elif event.key == 'END': - cursor = len(buffer) - elif event.key == 'UP': - commands.insert(0, buffer) - buffer = commands.pop() - cursor = len(buffer) - elif event.key == 'DOWN': - commands.append(buffer) - buffer = commands.pop(0) - cursor = len(buffer) - elif event.key == 'TAB': - buffer = buffer[:cursor] + ' ' + buffer[cursor:] - cursor += 4 - elif event.key == 'ESCAPE': - raise SystemExit() - elif event.char: - buffer = buffer[:cursor] + event.char + buffer[cursor:] - cursor += 1 - cursor = max(0, min(cursor, len(buffer))) - time.sleep(.01) - -if __name__ == '__main__': - main() diff --git a/examples/life.py b/examples/life.py deleted file mode 100755 index d6bc8726..00000000 --- a/examples/life.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python - -import random -import time - -import tdl - -WIDTH = 80 -HEIGHT = 40 - -class LifeBoard(): - - def __init__(self, width, height): - self.width = width - self.height = height - self.live_cells = set() - self.wrap = True - - def set(self, x, y, value): - if value: - self.live_cells.add((x, y)) - else: - self.live_cells.discard((x, y)) - - def set_batch(self, x, y, batch): - for y_, line in enumerate(batch): - for x_, char in enumerate(line): - self.set(x + x_, y + y_, char != ' ') - - def get(self, x, y): - if(self.wrap is False - and not (0 <= x < self.width and 0 <= y < self.height)): - return False - return (x % self.width, y % self.height) in self.live_cells - - def clear(self): - self.live_cells.clear() - - def toggle(self, x, y): - self.live_cells.symmetric_difference_update([(x, y)]) - - def wrap_edges(self): - for x in range(-1, self.width + 1): - self.set(x, -1, self.get(x, -1)) - self.set(x, self.height, self.get(x, self.height)) - for y in range(self.height): - self.set(-1, y, self.get(-1, y)) - self.set(self.width, y, self.get(self.width, y)) - - - def get_neighbours(self, x, y): - return len(self.live_cells & {(x - 1, y - 1), (x, y - 1), - (x + 1,y - 1), (x + 1, y), - (x + 1, y + 1), (x, y + 1), - (x - 1, y + 1), (x - 1, y)}) - - def rule(self, is_alive, neighbours): - """ - 1. Any live cell with fewer than two live neighbours dies, as if caused - by under-population. - 2. Any live cell with two or three live neighbours lives on to the next - generation. - 3. Any live cell with more than three live neighbours dies, as if by - overcrowding. - 4. Any dead cell with exactly three live neighbours becomes a live - cell, as if by reproduction. - """ - if is_alive: - return 2 <= neighbours <= 3 - else: - return neighbours == 3 - - def step(self): - self.wrap_edges() - next_generation = set() - for x in range(self.width): - for y in range(self.height): - if self.rule(self.get(x, y), self.get_neighbours(x, y)): - next_generation.add((x, y)) - self.live_cells = next_generation - -def main(): - console = tdl.init(WIDTH, HEIGHT) - board = LifeBoard(WIDTH, HEIGHT - 1) - # The R-pentomino - #board.set_batch(WIDTH // 2 - 2,HEIGHT // 2 - 2, - # [' **', - # '** ', - # ' * ']) - - # Diehard - #board.set_batch(WIDTH // 2 - 5,HEIGHT // 2 - 2, - # [' * ', - # '** ', - # ' * ***']) - - # Gosper glider gun - board.set_batch(1, 1, - [' ', - ' * ', - ' * * ', - ' ** ** **', - ' * * ** **', - '** * * ** ', - '** * * ** * * ', - ' * * * ', - ' * * ', - ' ** ']) - - play = False - redraw = True - mouse_drawing = None - mouse_x = -1 - mouse_y = -1 - while True: - for event in tdl.event.get(): - if event.type == 'QUIT': - return - elif event.type == 'KEYDOWN': - if event.key == 'SPACE': - play = not play - redraw = True - elif event.char.upper() == 'S': - board.step() - redraw = True - elif event.char.upper() == 'C': - board.clear() - redraw = True - elif event.char.upper() == 'W': - board.wrap = not board.wrap - redraw = True - elif event.type == 'MOUSEDOWN': - x, y, = event.cell - board.toggle(x, y) - mouse_drawing = event.cell - redraw = True - elif event.type == 'MOUSEUP': - mouse_drawing = None - elif event.type == 'MOUSEMOTION': - if(mouse_drawing and mouse_drawing != event.cell): - x, y = mouse_drawing = event.cell - board.toggle(x, y) - mouse_x, mouse_y = event.cell - redraw = True - if play and mouse_drawing is None: - board.step() - redraw = True - if redraw: - redraw = False - console.clear() - for x, y in board.live_cells: - console.draw_char(x, y, '*') - #console.draw_rect(0, -1, None, None, None, bg=(64, 64, 80)) - console.draw_rect(0, -1, None, None, None, bg=(64, 64, 80)) - console.draw_str(0, -1, "Mouse:Toggle Cells, Space:%5s, [S]tep, [C]lear, [W]rap Turn %s" % (['Play', 'Pause'][play], ['On', 'Off'][board.wrap]), None, None) - if (mouse_x, mouse_y) in console: - console.draw_char(mouse_x, mouse_y, - None, (0, 0, 0), (255, 255, 255)) - else: - time.sleep(0.01) - tdl.flush() - tdl.set_title("Conway's Game of Life - %i FPS" % tdl.get_fps()) - - -if __name__ == '__main__': - main() - diff --git a/examples/tutorial/1-Basics.py b/examples/tutorial/1-Basics.py deleted file mode 100755 index a05d6dc0..00000000 --- a/examples/tutorial/1-Basics.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -""" - This script shows the basic use of the tdl module. - - The font is configured and the module is initialized, the script then goes - into an infinite loop where it will draw "Hello World" on the screen and - then check for an event that tells that the user has closed the window. - - When the window is closed the script quits out by raising a SystemExit - exception. -""" - -import tdl - -# Define the window size (in character tiles.) We'll pick something small. -WIDTH, HEIGHT = 40, 30 # 320x240 when the font size is taken into account - -# Set the font to the example font in the tutorial folder. This font is -# equivalent to the default font you will get if you skip this call. -# With the characters rows first and the font size included in the filename -# you won't have to specify any parameters other than the font file itself. -tdl.setFont('terminal8x8_gs_ro.png') - -# Call tdl.init to create the root console. -# We will call drawing operations on the returned object. -console = tdl.init(WIDTH, HEIGHT, 'python-tdl tutorial') - -# Start an infinite loop. Drawing and game logic will be put in this loop. -while True: - - # Reset the console to a blank slate before drawing on it. - console.clear() - - # Now draw out 'Hello World' starting at an x,y of 1,2. - console.draw_str(1, 2, 'Hello World') - - # Now to update the image on the window we make sure to call tdl.flush - # in every loop. - tdl.flush() - - # Handle events by iterating over the values returned by tdl.event.get - for event in tdl.event.get(): - # Check if this is a 'QUIT' event - if event.type == 'QUIT': - # Later we may want to save the game or confirm if the user really - # wants to quit but for now we break out of the loop by raising a - # SystemExit exception. - # The optional string parameter will be printed out on the - # terminal after the script exits. - raise SystemExit('The window has been closed.') diff --git a/examples/tutorial/2-Movement.py b/examples/tutorial/2-Movement.py deleted file mode 100755 index e970e172..00000000 --- a/examples/tutorial/2-Movement.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python -""" - -""" - -import tdl - -WIDTH, HEIGHT = 40, 30 # Defines the window size. - -# Create a dictionary that maps keys to vectors. -# Names of the available keys can be found in the online documentation: -# http://packages.python.org/tdl/tdl.event-module.html -MOVEMENT_KEYS = { - # standard arrow keys - 'UP': [0, -1], - 'DOWN': [0, 1], - 'LEFT': [-1, 0], - 'RIGHT': [1, 0], - - # diagonal keys - # keep in mind that the keypad won't use these keys even if - # num-lock is off - 'HOME': [-1, -1], - 'PAGEUP': [1, -1], - 'PAGEDOWN': [1, 1], - 'END': [-1, 1], - - # number-pad keys - # These keys will always show as KPx regardless if num-lock - # is on or off. Keep in mind that some keyboards and laptops - # may be missing a keypad entirely. - # 7 8 9 - # 4 6 - # 1 2 3 - 'KP1': [-1, 1], - 'KP2': [0, 1], - 'KP3': [1, 1], - 'KP4': [-1, 0], - 'KP6': [1, 0], - 'KP7': [-1, -1], - 'KP8': [0, -1], - 'KP9': [1, -1], - } - - -tdl.setFont('terminal8x8_gs_ro.png') # Configure the font. - -# Create the root console. -console = tdl.init(WIDTH, HEIGHT, 'python-tdl tutorial') - -# player coordinates -playerX, playerY = 1, 2 - -while True: # Continue in an infinite game loop. - - console.clear() # Blank the console. - - # Using "(x, y) in console" we can quickly check if a position is inside of - # a console. And skip a draw operation that would otherwise fail. - if (playerX, playerY) in console: - console.draw_char(playerX, playerY, '@') - - tdl.flush() # Update the window. - - for event in tdl.event.get(): # Iterate over recent events. - if event.type == 'KEYDOWN': - # We mix special keys with normal characters so we use keychar. - if event.keychar.upper() in MOVEMENT_KEYS: - # Get the vector and unpack it into these two variables. - keyX, keyY = MOVEMENT_KEYS[event.keychar.upper()] - # Then we add the vector to the current player position. - playerX += keyX - playerY += keyY - - if event.type == 'QUIT': - # Halt the script using SystemExit - raise SystemExit('The window has been closed.') diff --git a/examples/tutorial/terminal8x8_gs_ro.png b/examples/tutorial/terminal8x8_gs_ro.png deleted file mode 100755 index e496e2bc51e13930320d5938f18b67728f75f02e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3696 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 Date: Sun, 2 Jun 2019 14:22:21 -0700 Subject: [PATCH 0167/1101] Set executable flag for example files. --- examples/distribution/PyInstaller/hello_world.py | 0 examples/distribution/cx_Freeze/hello_world.py | 0 examples/distribution/cx_Freeze/setup.py | 0 examples/experimental/custrender.py | 0 examples/experimental/resizable_console.py | 0 examples/termbox/termbox.py | 0 examples/termbox/termboxtest.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/distribution/PyInstaller/hello_world.py mode change 100644 => 100755 examples/distribution/cx_Freeze/hello_world.py mode change 100644 => 100755 examples/distribution/cx_Freeze/setup.py mode change 100644 => 100755 examples/experimental/custrender.py mode change 100644 => 100755 examples/experimental/resizable_console.py mode change 100644 => 100755 examples/termbox/termbox.py mode change 100644 => 100755 examples/termbox/termboxtest.py diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/hello_world.py old mode 100644 new mode 100755 diff --git a/examples/distribution/cx_Freeze/hello_world.py b/examples/distribution/cx_Freeze/hello_world.py old mode 100644 new mode 100755 diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py old mode 100644 new mode 100755 diff --git a/examples/experimental/custrender.py b/examples/experimental/custrender.py old mode 100644 new mode 100755 diff --git a/examples/experimental/resizable_console.py b/examples/experimental/resizable_console.py old mode 100644 new mode 100755 diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py old mode 100644 new mode 100755 diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py old mode 100644 new mode 100755 From 7a23fa39f41b626ada8c95d12e24753104fa8a8e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 2 Jun 2019 20:09:10 -0700 Subject: [PATCH 0168/1101] Fixed text alignment for non-rect print functions. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab365442..9b22edf3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed regressions in text alignment for non-rectangle print functions. 10.1.0 - 2019-05-24 ------------------- diff --git a/libtcod b/libtcod index de3b645d..3d90f398 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit de3b645dbf69703fa44e74fea5f81c3991c17e3b +Subproject commit 3d90f398ce79815089a5c58953a17160d1f64277 From baf8cf10b964ae3914ed4e40460c1b4ccba1eaeb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 2 Jun 2019 20:10:43 -0700 Subject: [PATCH 0169/1101] Prepare 10.1.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9b22edf3..d08b4474 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +10.1.1 - 2019-06-02 +------------------- Fixed - Fixed regressions in text alignment for non-rectangle print functions. From 67a125e268a8edcc5fc6e297ef84bd2a218109a0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 2 Jun 2019 20:10:43 -0700 Subject: [PATCH 0170/1101] Prepare 10.1.1 release. --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9b22edf3..65710d9e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ + +10.1.1 - 2019-06-02 +------------------- +Changed + - Better string representations for `tcod.event.Event` subclasses. + Fixed - Fixed regressions in text alignment for non-rectangle print functions. From 5d4a22db03f260d216859d72ef8dec13c192fb3a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 3 Jun 2019 16:26:31 -0700 Subject: [PATCH 0171/1101] Resolve Color type issues. --- tcod/color.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tcod/color.py b/tcod/color.py index f9cd85a2..da1a19f9 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -17,7 +17,7 @@ class Color(List[int]): b (int): Blue value, from 0 to 255. """ - def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> "Color": + def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> None: list.__setitem__(self, slice(None), (r & 0xFF, g & 0xFF, b & 0xFF)) @property @@ -27,7 +27,7 @@ def r(self) -> int: .. deprecated:: 9.2 Color attributes will not be mutable in the future. """ - return self[0] + return int(self[0]) @r.setter # type: ignore @deprecate("Setting color attributes has been deprecated.") @@ -41,7 +41,7 @@ def g(self) -> int: .. deprecated:: 9.2 Color attributes will not be mutable in the future. """ - return self[1] + return int(self[1]) @g.setter # type: ignore @deprecate("Setting color attributes has been deprecated.") @@ -55,7 +55,7 @@ def b(self) -> int: .. deprecated:: 9.2 Color attributes will not be mutable in the future. """ - return self[2] + return int(self[2]) @b.setter # type: ignore @deprecate("Setting color attributes has been deprecated.") @@ -67,7 +67,7 @@ def _new_from_cdata(cls, cdata: Any) -> "Color": """new in libtcod-cffi""" return cls(cdata.r, cdata.g, cdata.b) - def __getitem__(self, index: Any) -> int: # type: ignore + def __getitem__(self, index: Any) -> Any: """ .. deprecated:: 9.2 Accessing colors via a letter index is deprecated. @@ -133,7 +133,7 @@ def __mul__(self, other: Any) -> "Color": def __repr__(self) -> str: """Return a printable representation of the current color.""" - return "%s(%i, %i, %i)" % ( + return "%s(%r, %r, %r)" % ( self.__class__.__name__, self.r, self.g, From 3a208e6106339292f7386b55f299725a9aa01a5b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 5 Jun 2019 12:02:39 -0700 Subject: [PATCH 0172/1101] Improve custom renderer. Can now start the display using pixel resolution and window flags. --- examples/experimental/custrender.py | 65 ++++++++++++++++++++-- examples/experimental/resizable_console.py | 25 ++++++--- libtcod | 2 +- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/examples/experimental/custrender.py b/examples/experimental/custrender.py index 0582a147..4682ae53 100755 --- a/examples/experimental/custrender.py +++ b/examples/experimental/custrender.py @@ -13,13 +13,63 @@ It is also designed to be copied into your own project and imported as a module. """ -from typing import Optional, Tuple +import os +import sys + +from typing import Any, Optional, Tuple import tcod import tcod.tileset import tcod.event -assert tcod.__version__ > "10.0.4", tcod.__version__ +assert tcod.__version__ > "10.1.1", tcod.__version__ + + +class LibtcodScope: + """When this context is exited, libtcod shuts down.""" + + def __enter__(self) -> None: + return None + + def __exit__(self, type: Any, value: Any, traceback: Any) -> None: + tcod.lib.TCOD_console_delete(tcod.ffi.NULL) + + +def init_sdl2( + width: int, + height: int, + title: Optional[str] = None, + window_flags: int = 0, + renderer_flags: int = 0, +) -> LibtcodScope: + """Setup libtcod to use a customized SDL2 display. + + `width` and `height` are the pixel resolution to use. + + `title` is the window title bar text, if None the title will be the name + of the running script. + + `window_flags` is a bit-field of SDL2 window flags such as + `tcod.lib.SDL_WINDOW_RESIZABLE`. See the SDL2 docs for more flags: + https://wiki.libsdl.org/SDL_WindowFlags + + `renderer_flags` is a bit-field of SDL2 renderer flags such as + `tcod.lib.SDL_RENDERER_PRESENTVSYNC`. See the SDL2 docs for more flags: + https://wiki.libsdl.org/SDL_RendererFlags + Target texture support will always be requested regardless of the given + flags. + + This function should be used with the `with` statement so that the display + will properly exit when your script exits. + """ + if title is None: + title = os.path.basename(sys.argv[0]) + error = tcod.lib.TCOD_sys_init_sdl2_renderer_( + width, height, title.encode("utf-8"), window_flags, renderer_flags + ) + if error < 0: + raise RuntimeError(tcod.ffi.string(tcod.lib.TCOD_get_error()).decode()) + return LibtcodScope() def get_renderer_size() -> Tuple[int, int]: @@ -129,9 +179,12 @@ def accumulate( def main() -> None: """An example for the use of this module.""" - with tcod.console_init_root( - 20, 4, renderer=tcod.RENDERER_SDL2, vsync=True - ) as console: + window_flags = ( + tcod.lib.SDL_WINDOW_RESIZABLE | tcod.lib.SDL_WINDOW_MAXIMIZED + ) + renderer_flags = tcod.lib.SDL_RENDERER_PRESENTVSYNC + with init_sdl2(640, 480, None, window_flags, renderer_flags): + console = tcod.console.Console(20, 4) TEXT = "Console with a fixed aspect ratio and integer scaling." console.print_box(0, 0, 0, 0, TEXT) while True: @@ -150,7 +203,7 @@ def main() -> None: elif event.type == "WINDOWRESIZED": # You can change to a console of a different size in # response to a WINDOWRESIZED event if you want. - ... + ... # See resizable_console.py if __name__ == "__main__": diff --git a/examples/experimental/resizable_console.py b/examples/experimental/resizable_console.py index 7e278b53..cee044b7 100755 --- a/examples/experimental/resizable_console.py +++ b/examples/experimental/resizable_console.py @@ -4,6 +4,8 @@ # published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ """An example showing the console being resized to fit the window.""" +from typing import Tuple + import tcod import tcod.event import tcod.tileset @@ -11,10 +13,22 @@ import custrender # Using the custom renderer engine. +def fit_console(width: int, height: int) -> Tuple[int, int]: + """Return a console resolution the fits the given pixel resolution.""" + # Use the current active tileset as a reference. + tileset = tcod.tileset.get_default() + return width // tileset.tile_width, height // tileset.tile_height + + def main() -> None: - with tcod.console_init_root( - 20, 4, renderer=tcod.RENDERER_SDL2, vsync=True - ) as console: + window_flags = ( + tcod.lib.SDL_WINDOW_RESIZABLE | tcod.lib.SDL_WINDOW_MAXIMIZED + ) + renderer_flags = tcod.lib.SDL_RENDERER_PRESENTVSYNC + with custrender.init_sdl2(640, 480, None, window_flags, renderer_flags): + console = tcod.console.Console( + *fit_console(*custrender.get_renderer_size()) + ) TEXT = "Resizable console with no stretching." while True: console.clear() @@ -36,12 +50,9 @@ def main() -> None: if event.type == "QUIT": raise SystemExit() elif event.type == "WINDOWRESIZED": - # Use the current active tileset as a reference. - tileset = tcod.tileset.get_default() # Replace `console` with a new one of the correct size. console = tcod.console.Console( - event.width // tileset.tile_width, - event.height // tileset.tile_height, + *fit_console(event.width, event.height) ) diff --git a/libtcod b/libtcod index 3d90f398..8cd69c07 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 3d90f398ce79815089a5c58953a17160d1f64277 +Subproject commit 8cd69c07c5e0ce91741ec51ad4b6d520bf0e13ce From f9b32f72dd9b5c76641c9d7a319ca2b99a831aac Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 6 Jun 2019 17:50:10 -0700 Subject: [PATCH 0173/1101] Hide deprecation warnings during MSVC compilation. --- build_libtcod.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 8d8f73fa..bf277b38 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -136,7 +136,7 @@ def unpack_sdl2(version): r".*-I(\S+)", subprocess.check_output( ["sdl2-config", "--cflags"], universal_newlines=True - ) + ), ) assert match SDL2_INCLUDE, = match.groups() @@ -144,8 +144,9 @@ def unpack_sdl2(version): if sys.platform == "win32": include_dirs.append(SDL2_INCLUDE) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = os.path.join(SDL2_BUNDLE_PATH, "lib/", - ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DIR = os.path.join( + SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE] + ) library_dirs.append(SDL2_LIB_DIR) SDL2_LIB_DEST = os.path.join("tcod", ARCH_MAPPING[BITSIZE]) if not os.path.exists(SDL2_LIB_DEST): @@ -308,7 +309,7 @@ def get_ast(): tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() -MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-"]} +MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]} MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} GCC_CFLAGS = { "DEBUG": ["-Og", "-g", "-fPIC"], @@ -336,7 +337,6 @@ def get_ast(): ffi = FFI() parse_sdl2.add_to_ffi(ffi, SDL2_INCLUDE) -#ffi.include(parse_sdl2.get_ffi(SDL2_INCLUDE)) ffi.cdef(get_cdef()) ffi.cdef(open(CFFI_EXTRA_CDEFS, "r").read()) ffi.set_source( From d97db14e01b87965fbf189c4c8cbec2d5f432a0a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 9 Jun 2019 10:00:01 -0700 Subject: [PATCH 0174/1101] Add pixel to tile conversions to `custrender.py`. --- examples/experimental/custrender.py | 47 +++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/examples/experimental/custrender.py b/examples/experimental/custrender.py index 4682ae53..44ed33da 100755 --- a/examples/experimental/custrender.py +++ b/examples/experimental/custrender.py @@ -16,6 +16,7 @@ import os import sys +import math from typing import Any, Optional, Tuple import tcod @@ -177,6 +178,38 @@ def accumulate( tcod.lib.TCOD_sys_accumulate_console_(console.console_c, viewport) +def pixel_to_subtile( + pixel: Tuple[float, float], + console: tcod.console.Console, + viewport: Tuple[int, int, int, int], +) -> Tuple[float, float]: + """Covert pixel coordinates to floating point tile coordinates. + + Parameters are the same as `pixel_to_tile`. + """ + return ( + (pixel[0] - viewport[0]) / viewport[2] * console.width, + (pixel[1] - viewport[1]) / viewport[3] * console.height, + ) + + +def pixel_to_tile( + pixel: Tuple[float, float], + console: tcod.console.Console, + viewport: Tuple[int, int, int, int], +) -> Tuple[int, int]: + """Covert pixel coordinates to integer tile coordinates. + + `pixel` are the pixel coordinates to use such as the ones returned by a + `event.type=="MOUSEMOTION"`'s `event.pixel` attribute. + + `console` and `viewport` should be the same arguments previously passed to + `accumulate`. They are used as references to convert from pixels to tiles. + """ + x, y = pixel_to_subtile(pixel, console, viewport) + return math.floor(x), math.floor(y) + + def main() -> None: """An example for the use of this module.""" window_flags = ( @@ -184,14 +217,15 @@ def main() -> None: ) renderer_flags = tcod.lib.SDL_RENDERER_PRESENTVSYNC with init_sdl2(640, 480, None, window_flags, renderer_flags): - console = tcod.console.Console(20, 4) + console = tcod.console.Console(20, 4, order="F") TEXT = "Console with a fixed aspect ratio and integer scaling." console.print_box(0, 0, 0, 0, TEXT) while True: # Clear background with white. clear((255, 255, 255)) # Draw the console to SDL's buffer. - accumulate(console, get_viewport(console, True, True)) + viewport = get_viewport(console, True, True) + accumulate(console, viewport) # If you want you can use the FFI to do additional drawing here: ... # Present the SDL2 renderer to the display. @@ -200,6 +234,15 @@ def main() -> None: for event in tcod.event.wait(): if event.type == "QUIT": raise SystemExit() + elif event.type == "MOUSEMOTION": + # Mouse pixel coordinates will need to be converted to + # tiles using the console and viewport as a reference. + x, y = pixel_to_tile(event.pixel, console, viewport) + console.tiles["bg"] = (0, 0, 0, 255) + console.tiles["fg"] = (255, 255, 255, 255) + if 0 <= x < console.width and 0 <= y < console.height: + console.tiles["bg"][x, y] = (255, 255, 255, 255) + console.tiles["fg"][x, y] = (0, 0, 0, 255) elif event.type == "WINDOWRESIZED": # You can change to a console of a different size in # response to a WINDOWRESIZED event if you want. From 528229cbf25e0323adbbf7447373b513a35048c3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Jun 2019 09:51:20 -0700 Subject: [PATCH 0175/1101] Change tcod.map.compute_fov parameters. --- CHANGELOG.rst | 3 +++ tcod/map.py | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 65710d9e..2205d82a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and + `y` parameters. This causes less confusion over how axes are aligned. 10.1.1 - 2019-06-02 ------------------- diff --git a/tcod/map.py b/tcod/map.py index f35111d5..705ea8a6 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -2,7 +2,7 @@ """ -from typing import Any +from typing import Any, Tuple import numpy as np @@ -146,8 +146,7 @@ def __getstate__(self) -> Any: def compute_fov( transparency: np.array, - x: int, - y: int, + pov: Tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, @@ -158,10 +157,11 @@ def compute_fov( considered transparent. The returned array will match the shape of this array. - `x` and `y` are the 1st and 2nd coordinates of the origin point. Areas - are visible when they can be seen from this point-of-view. + `pov` is the point-of-view origin point. Areas are visible if they can + be seen from this position. The axes of the `pov` should match the axes + of the `transparency` array. - `radius` is the maximum view distance from `x`/`y`. If this is zero then + `radius` is the maximum view distance from `pov`. If this is zero then the maximum distance is used. If `light_walls` is True then visible obstacles will be returned, otherwise @@ -182,8 +182,10 @@ def compute_fov( .. versionadded:: 9.3 - Example:: + .. versionchanged:: 11.0 + The parameters `x` and `y` have been changed to `pov`. + Example: >>> explored = np.zeros((3, 5), dtype=bool, order="F") >>> transparency = np.ones((3, 5), dtype=bool, order="F") >>> transparency[:2, 2] = False @@ -191,19 +193,25 @@ def compute_fov( array([[ True, True, False, True, True], [ True, True, False, True, True], [ True, True, True, True, True]]...) - >>> visible = tcod.map.compute_fov(transparency, 0, 0) + >>> visible = tcod.map.compute_fov(transparency, (0, 0)) >>> visible # Visible area. array([[ True, True, True, False, False], [ True, True, True, False, False], [ True, True, True, True, False]]...) >>> explored |= visible # Keep track of an explored area. """ + transparency = np.asarray(transparency) if len(transparency.shape) != 2: raise TypeError( "transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape ) + if isinstance(pov, int): + raise TypeError( + "The tcod.map.compute_fov function has changed. The `x` and `y`" + " parameters should now be given as a single tuple." + ) map_ = Map(transparency.shape[1], transparency.shape[0]) map_.transparent[...] = transparency - map_.compute_fov(x, y, radius, light_walls, algorithm) + map_.compute_fov(pov[1], pov[0], radius, light_walls, algorithm) return map_.fov From 1cbd5786c1877e8c9e76e992f5163130956848a8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Jun 2019 17:41:37 -0700 Subject: [PATCH 0176/1101] Documentation updates. --- docs/requirements.txt | 1 + tcod/console.py | 9 ++++++--- tcod/map.py | 7 ++++++- tcod/tileset.py | 4 +++- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..483a4e96 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme diff --git a/tcod/console.py b/tcod/console.py index fe80aff5..42b06dbc 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -788,6 +788,9 @@ def __enter__(self) -> "Console": This is useful for some Python IDE's like IDLE, where the window would not be closed on its own otherwise. + + .. seealso:: + :any:`tcod.console_init_root` """ if self.console_c != ffi.NULL: raise NotImplementedError("Only the root console has a context.") @@ -796,14 +799,14 @@ def __enter__(self) -> "Console": def __exit__(self, *args: Any) -> None: """Closes the graphical window on exit. - Some tcod functions may have undefined behaviour after this point. + Some tcod functions may have undefined behavior after this point. """ lib.TCOD_console_delete(self.console_c) def __bool__(self) -> bool: """Returns False if this is the root console. - This mimics libtcodpy behaviour. + This mimics libtcodpy behavior. """ return bool(self.console_c != ffi.NULL) @@ -940,7 +943,7 @@ def print_box( may change in future versions. `width` and `height` determine the bounds of the rectangle, the text - will automatically be broken to fit within these bounds. + will automatically be word-wrapped to fit within these bounds. `string` is a Unicode string which may include color control characters. diff --git a/tcod/map.py b/tcod/map.py index 705ea8a6..4f39735e 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -151,7 +151,7 @@ def compute_fov( light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, ) -> np.array: - """Return the visible area of a field-of-view computation. + """Return a boolean mask of the area covered by a field-of-view. `transparency` is a 2 dimensional array where all non-zero values are considered transparent. The returned array will match the shape of this @@ -199,6 +199,11 @@ def compute_fov( [ True, True, True, False, False], [ True, True, True, True, False]]...) >>> explored |= visible # Keep track of an explored area. + + .. seealso:: + :any:`numpy.nonzero` + :any:`numpy.choose` + :any:`numpy.select` """ transparency = np.asarray(transparency) if len(transparency.shape) != 2: diff --git a/tcod/tileset.py b/tcod/tileset.py index 1f6dbf45..a878e7c0 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -129,7 +129,9 @@ def load_truetype_font( """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) + cdata = lib.TCOD_load_truetype_font_( + path.encode(), tile_width, tile_height + ) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) return Tileset._claim(cdata) From ef47f02a13bb96dfab56e7568252b750178dd3e6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Jun 2019 17:49:55 -0700 Subject: [PATCH 0177/1101] Prepare 11.0.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2205d82a..cb725d5c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.0.0 - 2019-06-14 +------------------- Changed - `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and `y` parameters. This causes less confusion over how axes are aligned. From bde3920654e01c2cbd86878a59ffe095758e77a0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 19 Jun 2019 18:24:25 -0700 Subject: [PATCH 0178/1101] Better runtime checks for Window dependencies. This can now check the SDL2 version and Windows runtime before loading libtcod. --- CHANGELOG.rst | 2 ++ tcod/libtcod.py | 60 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cb725d5c..3f9ff1dd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - Better runtime checks for Windows dependencies. 11.0.0 - 2019-06-14 ------------------- diff --git a/tcod/libtcod.py b/tcod/libtcod.py index 299a686a..131ad89d 100644 --- a/tcod/libtcod.py +++ b/tcod/libtcod.py @@ -3,6 +3,7 @@ import sys import os +import cffi # type: ignore import platform from typing import Any # noqa: F401 @@ -10,6 +11,48 @@ __sdl_version__ = "" +ffi_check = cffi.FFI() +ffi_check.cdef( + """ +typedef struct SDL_version +{ + uint8_t major; + uint8_t minor; + uint8_t patch; +} SDL_version; + +void SDL_GetVersion(SDL_version * ver); +""" +) + + +def verify_dependencies() -> None: + """Try to make sure dependencies exist on this system.""" + if sys.platform == "win32": + lib_test = ffi_check.dlopen( # Make sure SDL2.dll is here. + os.path.join(__path__[0], get_architecture(), "SDL2.dll") + ) + version = ffi_check.new("struct SDL_version*") + lib_test.SDL_GetVersion(version) # Need to check this version. + version = version.major, version.minor, version.patch + if version < (2, 0, 5): + raise RuntimeError( + "Tried to load an old version of SDL %r" % (version,) + ) + try: + ffi_check.dlopen("vcruntime140.dll") # Make sure VC++ 2015 exists. + except OSError: + print( + "You will need to install 'vc_redist.{arch}.exe'" + " from Microsoft at:\n" + "https://support.microsoft.com/en-us/help/2977003/" + "the-latest-supported-visual-c-downloads\n".format( + arch=get_architecture() + ), + file=sys.stderr, + ) + raise + def get_architecture() -> str: """Return the Windows architecture, one of "x86" or "x64".""" @@ -66,20 +109,9 @@ def __str__(self) -> Any: # Allows an import without building the cffi module first. lib = ffi = _Mock() else: - try: - from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 - except ImportError as exc: - if "The specified module could not be found." in exc.args[0]: - print( - "You may need to install 'vc_redist.{arch}.exe'" - " from Microsoft at:\n" - "https://support.microsoft.com/en-us/help/2977003/" - "the-latest-supported-visual-c-downloads\n".format( - arch=get_architecture() - ), - file=sys.stderr, - ) - raise + verify_dependencies() + from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 + __sdl_version__ = get_sdl_version() __all__ = ["ffi", "lib"] From c6c4a2dffdd27bd37568cd3e031836f96acd8b48 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Jun 2019 00:24:24 -0700 Subject: [PATCH 0179/1101] Resolve NumPy type hint issues. `np.array` is likely a call in old versions of NumPy. --- CHANGELOG.rst | 3 +++ tcod/console.py | 10 +++++----- tcod/libtcodpy.py | 2 +- tcod/map.py | 10 +++++----- tcod/noise.py | 4 ++-- tcod/path.py | 2 +- tcod/path2.py | 8 ++++---- tcod/tileset.py | 4 ++-- 8 files changed, 23 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3f9ff1dd..b117006e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,9 @@ Unreleased Changed - Better runtime checks for Windows dependencies. +Fixed + - Resolved NumPy type hints which could cause issues. + 11.0.0 - 2019-06-14 ------------------- Changed diff --git a/tcod/console.py b/tcod/console.py index 42b06dbc..77b51c60 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -113,7 +113,7 @@ def __init__( width: int, height: int, order: str = "C", - buffer: Optional[np.array] = None, + buffer: Optional[np.ndarray] = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] self._order = tcod._internal.verify_order(order) @@ -206,7 +206,7 @@ def height(self) -> int: return lib.TCOD_console_get_height(self.console_c) # type: ignore @property - def bg(self) -> np.array: + def bg(self) -> np.ndarray: """A uint8 array with the shape (height, width, 3). You can change the consoles background colors by using this array. @@ -221,7 +221,7 @@ def bg(self) -> np.array: return bg @property - def fg(self) -> np.array: + def fg(self) -> np.ndarray: """A uint8 array with the shape (height, width, 3). You can change the consoles foreground colors by using this array. @@ -235,7 +235,7 @@ def fg(self) -> np.array: return fg @property - def ch(self) -> np.array: + def ch(self) -> np.ndarray: """An integer array with the shape (height, width). You can change the consoles character codes by using this array. @@ -246,7 +246,7 @@ def ch(self) -> np.array: return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] @property - def tiles(self) -> np.array: + def tiles(self) -> np.ndarray: """An array of this consoles tile data. This acts as a combination of the `ch`, `fg`, and `bg` attributes. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 47c15639..f5a88f03 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3156,7 +3156,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: def line_where( x1: int, y1: int, x2: int, y2: int, inclusive: bool = True -) -> Tuple[np.array, np.array]: +) -> Tuple[np.ndarray, np.ndarray]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. diff --git a/tcod/map.py b/tcod/map.py index 4f39735e..0cbbdea0 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -82,17 +82,17 @@ def __as_cdata(self) -> Any: ) @property - def transparent(self) -> np.array: + def transparent(self) -> np.ndarray: buffer = self.__buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property - def walkable(self) -> np.array: + def walkable(self) -> np.ndarray: buffer = self.__buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property - def fov(self) -> np.array: + def fov(self) -> np.ndarray: buffer = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer @@ -145,12 +145,12 @@ def __getstate__(self) -> Any: def compute_fov( - transparency: np.array, + transparency: np.ndarray, pov: Tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, -) -> np.array: +) -> np.ndarray: """Return a boolean mask of the area covered by a field-of-view. `transparency` is a 2 dimensional array where all non-zero values are diff --git a/tcod/noise.py b/tcod/noise.py index 2d86a94a..f53b9424 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -159,7 +159,7 @@ def get_point( """ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) - def sample_mgrid(self, mgrid: np.array) -> np.array: + def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray: """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -195,7 +195,7 @@ def sample_mgrid(self, mgrid: np.array) -> np.array: ) return out - def sample_ogrid(self, ogrid: np.array) -> np.array: + def sample_ogrid(self, ogrid: np.ndarray) -> np.ndarray: """Sample an open mesh-grid array and return the result. Args diff --git a/tcod/path.py b/tcod/path.py index 3085c46e..a6336556 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -156,7 +156,7 @@ class NodeCostArray(np.ndarray): # type: ignore np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), } - def __new__(cls, array: np.array) -> "NodeCostArray": + def __new__(cls, array: np.ndarray) -> "NodeCostArray": """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) return self # type: ignore diff --git a/tcod/path2.py b/tcod/path2.py index 033d5e82..581be819 100644 --- a/tcod/path2.py +++ b/tcod/path2.py @@ -3,24 +3,24 @@ import numpy as np -def get_2d_edges(cardinal: float, diagonal: float) -> np.array: +def get_2d_edges(cardinal: float, diagonal: float) -> np.ndarray: return ( np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) * cardinal + np.array([[1, 0, 1], [0, 0, 0], [1, 0, 1]]) * diagonal ) -def get_hex_edges(cost: float) -> np.array: +def get_hex_edges(cost: float) -> np.ndarray: return np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) * cost class EdgeRule: - def __init__(self, vector: Sequence[int], destination: np.array): + def __init__(self, vector: Sequence[int], destination: np.ndarray): self.vector = vector self.destination = destination -def new_rule(edges: np.array) -> Iterator[EdgeRule]: +def new_rule(edges: np.ndarray) -> Iterator[EdgeRule]: i_center = (edges.shape[0] - 1) // 2 j_center = (edges.shape[1] - 1) // 2 for i in range(edges.shape[0]): diff --git a/tcod/tileset.py b/tcod/tileset.py index a878e7c0..bd36ba68 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -52,7 +52,7 @@ def __contains__(self, codepoint: int) -> bool: == 0 ) - def get_tile(self, codepoint: int) -> np.array: + def get_tile(self, codepoint: int) -> np.ndarray: """Return a copy of a tile for the given codepoint. If the tile does not exist yet then a blank array will be returned. @@ -69,7 +69,7 @@ def get_tile(self, codepoint: int) -> np.array: ) return tile - def set_tile(self, codepoint: int, tile: np.array) -> None: + def set_tile(self, codepoint: int, tile: np.ndarray) -> None: """Upload a tile into this array. The tile can be in 32-bit color (height, width, rgba), or grey-scale From 1f6582555a0381e097c2c0b261e315e921a7c704 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Jun 2019 10:21:34 -0700 Subject: [PATCH 0180/1101] Ignore Python virtual environment folders. --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index 70a30b12..85bf65c5 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,15 @@ docs/_build/ # PyBuilder target/ +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + # Custom Release/ tcod/_*.c From 0740d0bb4f76d2743c0d89c80a80e7588d294d31 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Jun 2019 10:25:10 -0700 Subject: [PATCH 0181/1101] Prepare 11.0.1 release. --- CHANGELOG.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b117006e..6105a25f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,11 +8,16 @@ v2.0.0 Unreleased ------------------ + +11.0.1 - 2019-06-21 +------------------- Changed - - Better runtime checks for Windows dependencies. + - Better runtime checks for Windows dependencies should now give different + errors depending on if the issue is SDL2 or missing redistributables. Fixed - - Resolved NumPy type hints which could cause issues. + - Changed NumPy type hints from `np.array` to `np.ndarray` which should + resolve issues. 11.0.0 - 2019-06-14 ------------------- From 35cba3d35de1862379dd45f76d9bd4ec2c80b497 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Jun 2019 16:48:56 -0700 Subject: [PATCH 0182/1101] Remove OpenGL dependency from the build process. --- CHANGELOG.rst | 2 ++ build_libtcod.py | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6105a25f..c8f16ad2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - You no longer need OpenGL to build python-tcod. 11.0.1 - 2019-06-21 ------------------- diff --git a/build_libtcod.py b/build_libtcod.py index bf277b38..66e39c0d 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -107,15 +107,11 @@ def unpack_sdl2(version): sources += glob.glob("libtcod/src/vendor/zlib/*.c") if sys.platform == "win32": - libraries += ["User32", "OpenGL32"] + libraries += ["User32"] define_macros.append(("TCODLIB_API", "")) define_macros.append(("_CRT_SECURE_NO_WARNINGS", None)) -if "linux" in sys.platform: - libraries += ["GL"] - if sys.platform == "darwin": - extra_link_args += ["-framework", "OpenGL"] extra_link_args += ["-framework", "SDL2"] else: libraries += ["SDL2"] From e1f3d762cb98c1052cfe6c338d66947e23531c2c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Jun 2019 22:29:55 -0700 Subject: [PATCH 0183/1101] Prepare 11.0.2 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c8f16ad2..dbcb91b3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,13 +8,16 @@ v2.0.0 Unreleased ------------------ + +11.0.2 - 2019-06-21 +------------------- Changed - You no longer need OpenGL to build python-tcod. 11.0.1 - 2019-06-21 ------------------- Changed - - Better runtime checks for Windows dependencies should now give different + - Better runtime checks for Windows dependencies should now give distinct errors depending on if the issue is SDL2 or missing redistributables. Fixed From 7011e47ff2c7918a4516898324d920c22158b98a Mon Sep 17 00:00:00 2001 From: fuzzygwalchmei Date: Sat, 22 Jun 2019 19:18:07 +1000 Subject: [PATCH 0184/1101] Fixed typos in console.blit docstring destintaion to destination --- tcod/console.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 77b51c60..3e8b7664 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -696,9 +696,9 @@ def blit( """Blit from this console onto the ``dest`` console. Args: - dest (Console): The destintaion console to blit onto. - dest_x (int): Leftmost coordinate of the destintaion console. - dest_y (int): Topmost coordinate of the destintaion console. + dest (Console): The destination console to blit onto. + dest_x (int): Leftmost coordinate of the destination console. + dest_y (int): Topmost coordinate of the destination console. src_x (int): X coordinate from this console to blit, from the left. src_y (int): Y coordinate from this console to blit, from the top. width (int): The width of the region to blit. From 99f5391e8b3aea2ff2c4fd01552680ff1c692301 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 5 Jul 2019 11:58:29 -0700 Subject: [PATCH 0185/1101] Update libtcod. --- CHANGELOG.rst | 11 +++++++++++ libtcod | 2 +- tcod/_internal.py | 8 ++++++++ tcod/libtcodpy.py | 22 ++++++++++++---------- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dbcb91b3..c38f6089 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,17 @@ v2.0.0 Unreleased ------------------ +Added + - You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to + force specific options to be used. + Example: ``TCOD_RENDERER=sdl2 TCOD_VSYNC=1`` + +Changed + - `tcod.sys_set_renderer` now raises an exception if it fails. + +Fixed + - `tcod.console_map_ascii_code_to_font` functions will now work when called + before `tcod.console_init_root`. 11.0.2 - 2019-06-21 ------------------- diff --git a/libtcod b/libtcod index 8cd69c07..ebb9edd6 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 8cd69c07c5e0ce91741ec51ad4b6d520bf0e13ce +Subproject commit ebb9edd69e5a7efbc8d50b37d2bae5f11834e4f9 diff --git a/tcod/_internal.py b/tcod/_internal.py index 4e1c57a9..7cd1295f 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -2,6 +2,8 @@ from typing import Any, Callable, TypeVar, cast import warnings +from tcod.libtcod import lib, ffi + FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) @@ -49,3 +51,9 @@ def handle_order(shape: Any, order: str) -> Any: return shape else: return tuple(reversed(shape)) + + +def _check(error: int) -> None: + """Detect and convert a libtcod error code it into an exception.""" + if error < 0: + raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index f5a88f03..bc389076 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -37,7 +37,7 @@ KEY_RELEASED, ) -from tcod._internal import deprecate, pending_deprecate +from tcod._internal import deprecate, pending_deprecate, _check from tcod.tcod import _int, _unpack_char_p from tcod.tcod import _bytes, _unicode, _fmt @@ -954,10 +954,11 @@ def console_init_root( ) if not NEW_RENDERER: vsync = False - if lib.TCOD_console_init_root_( - w, h, _bytes(title), fullscreen, renderer, vsync - ): - raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + _check( + lib.TCOD_console_init_root_( + w, h, _bytes(title), fullscreen, renderer, vsync + ) + ) console = tcod.console.Console._get_root(order) console.clear() return console @@ -994,10 +995,11 @@ def console_set_custom_font( raise RuntimeError( "File not found:\n\t%s" % (os.path.realpath(fontFile),) ) - if lib.TCOD_console_set_custom_font( - _bytes(fontFile), flags, nb_char_horiz, nb_char_vertic - ): - raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + _check( + lib.TCOD_console_set_custom_font( + _bytes(fontFile), flags, nb_char_horiz, nb_char_vertic + ) + ) @deprecate("Check `con.width` instead.") @@ -4005,7 +4007,7 @@ def sys_set_renderer(renderer: int) -> None: .. deprecated:: 2.0 RENDERER_GLSL and RENDERER_OPENGL are not currently available. """ - lib.TCOD_sys_set_renderer(renderer) + _check(lib.TCOD_sys_set_renderer(renderer)) if tcod.console._root_console is not None: tcod.console.Console._get_root() From 2b91b77bd948a3dd80d940fda67e955f1412557c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 5 Jul 2019 12:51:25 -0700 Subject: [PATCH 0186/1101] Ignore PyPy jobs. Currently fails due to issues with PyPy and pytest. --- .travis.yml | 4 ++-- appveyor.yml | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 327d4db8..db39d8f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ matrix: env: PYPY3_VERSION=pypy3.5-v7.0.0 - os: osx language: generic - env: PYPY3_VERSION=pypy3.6-v7.1.0 + env: PYPY3_VERSION=pypy3.6-v7.1.1 allow_failures: - python: nightly - python: pypy3.5-7.0 @@ -68,7 +68,7 @@ install: - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' before_script: -- pip install --upgrade pytest pytest-cov pytest-faulthandler +- pip install --upgrade pytest pytest-cov script: - pytest -v after_success: diff --git a/appveyor.yml b/appveyor.yml index 8b4426b3..4dc86025 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,13 +21,14 @@ environment: platform: Any CPU - PYPY3: pypy3.5-v7.0.0 platform: Any CPU - - PYPY3: pypy3.6-v7.1.0 + - PYPY3: pypy3.6-v7.1.1 platform: Any CPU matrix: allow_failures: - DEPLOY_ONLY: true - - PYPY3: pypy3.6-v7.1.0 + - PYPY3: pypy3.5-v7.0.0 + - PYPY3: pypy3.6-v7.1.1 clone_depth: 50 init: @@ -46,7 +47,7 @@ install: - cmd: "python setup.py build sdist develop bdist_wheel" build: off before_test: -- cmd: "pip install pytest pytest-cov pytest-faulthandler" +- cmd: "pip install pytest pytest-cov" test_script: - ps: "pytest -v" From 7b4c4692f9aecf584614125e74d640bec8a188b8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 5 Jul 2019 16:41:31 -0700 Subject: [PATCH 0187/1101] Prepare 11.1.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c38f6089..76d1f1a8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.1.0 - 2019-07-05 +------------------- Added - You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to force specific options to be used. From dbea93b7824b31cabdd2c76045542392d69dc2a2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 29 Jul 2019 15:44:56 -0700 Subject: [PATCH 0188/1101] Add note that tcod.tileset needs an explicit import. Should prevent issues like #81 --- tcod/tileset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tcod/tileset.py b/tcod/tileset.py index bd36ba68..efbddc91 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -1,4 +1,7 @@ """Tileset and font related functions. + +Remember to add the line ``import tcod.tileset``, as importing this module is +not implied by ``import tcod``. """ import os From da385cccdeba5ab64770364f49ef6516d87445c2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 31 Jul 2019 20:18:51 -0700 Subject: [PATCH 0189/1101] Add cellular automata example. --- examples/cavegen.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 examples/cavegen.py diff --git a/examples/cavegen.py b/examples/cavegen.py new file mode 100644 index 00000000..1710cc85 --- /dev/null +++ b/examples/cavegen.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""A basic cellular automata cave generation example using SciPy. + +http://www.roguebasin.com/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels + +This will print the result to the console, so be sure to run this from the +command line. +""" +import scipy.signal # type: ignore +import numpy as np # type: ignore + + +def convolve(tiles: np.array, wall_rule: int = 5) -> np.array: + """Return the next step of the cave generation algorithm. + + `tiles` is the input array. (0: wall, 1: floor) + + If the 3x3 area around a tile (including itself) has `wall_rule` number of + walls then the tile will become a wall. + """ + # Use convolve2d, the 2nd input is a 3x3 ones array. + neighbors = scipy.signal.convolve2d( + ~tiles, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same" + ) + return neighbors < wall_rule # Apply the wall rule. + + +def show(tiles: np.array) -> None: + """Print out the tiles of an array.""" + for line in tiles: + print("".join("# "[int(cell)] for cell in line)) + + +if __name__ == "__main__": + WIDTH, HEIGHT = 60, 20 + INITIAL_CHANCE = 0.45 # Initial wall chance. + CONVOLVE_STEPS = 4 + # 0: wall, 1: floor + tiles = np.random.random((HEIGHT, WIDTH)) > INITIAL_CHANCE + for _ in range(CONVOLVE_STEPS): + tiles = convolve(tiles) + tiles[[0, -1], :] = 0 # Ensure surrounding wall. + tiles[:, [0, -1]] = 0 + show(tiles) From efe16930cda768d75e6561909f087ffb008c3419 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 31 Jul 2019 20:24:07 -0700 Subject: [PATCH 0190/1101] Update libtcod. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 76d1f1a8..ae203c27 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Changing the tiles of an active tileset will now work correctly. 11.1.0 - 2019-07-05 ------------------- diff --git a/libtcod b/libtcod index ebb9edd6..af0c6d0f 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ebb9edd69e5a7efbc8d50b37d2bae5f11834e4f9 +Subproject commit af0c6d0ffd46f874293e083810bba3ef0ca5924d From 675cb19e6933ad88c6e95b366815c30af982b169 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 1 Aug 2019 00:41:09 -0700 Subject: [PATCH 0191/1101] Add warnings for when a bad POV is given to FOV operations. --- CHANGELOG.rst | 4 ++++ tcod/map.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ae203c27..bbdbe981 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Deprecated + - Using an out-of-bounds index for field-of-view operations now raises a + warning, which will later become an error. + Fixed - Changing the tiles of an active tileset will now work correctly. diff --git a/tcod/map.py b/tcod/map.py index 0cbbdea0..f267fcfe 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,6 +3,7 @@ """ from typing import Any, Tuple +import warnings import numpy as np @@ -118,6 +119,15 @@ def compute_fov( If you already have transparency in a NumPy array then you could use :any:`tcod.map_compute_fov` instead. """ + if not (0 <= x < self.width and 0 <= y < self.height): + warnings.warn( + "Index (%r, %r) is outside of this maps shape (%r, %r)." + "\nThis will raise an error in future versions." + % (x, y, self.width, self.height), + RuntimeWarning, + stacklevel=2, + ) + lib.TCOD_map_compute_fov( self.map_c, x, y, radius, light_walls, algorithm ) @@ -158,8 +168,9 @@ def compute_fov( array. `pov` is the point-of-view origin point. Areas are visible if they can - be seen from this position. The axes of the `pov` should match the axes - of the `transparency` array. + be seen from this position. `pov` should be a 2D index matching the axes + of the `transparency` array, and must be within the bounds of the + `transparency` array. `radius` is the maximum view distance from `pov`. If this is zero then the maximum distance is used. @@ -216,7 +227,20 @@ def compute_fov( "The tcod.map.compute_fov function has changed. The `x` and `y`" " parameters should now be given as a single tuple." ) + if not ( + 0 <= pov[0] < transparency.shape[0] + and 0 <= pov[1] < transparency.shape[1] + ): + warnings.warn( + "Given pov index %r is outside the array of shape %r." + "\nThis will raise an error in future versions." + % (pov, transparency.shape), + RuntimeWarning, + stacklevel=2, + ) map_ = Map(transparency.shape[1], transparency.shape[0]) map_.transparent[...] = transparency - map_.compute_fov(pov[1], pov[0], radius, light_walls, algorithm) + lib.TCOD_map_compute_fov( + map_.map_c, pov[1], pov[0], radius, light_walls, algorithm + ) return map_.fov From 313cdcc69cd542993b047e1d5487de47eeecf0a8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 1 Aug 2019 09:35:15 -0700 Subject: [PATCH 0192/1101] Prepare 11.1.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbdbe981..87359fb3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.1.1 - 2019-08-01 +------------------- Deprecated - Using an out-of-bounds index for field-of-view operations now raises a warning, which will later become an error. From 74ab85860253a72d754eeffacc02e57c218deb1d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 2 Aug 2019 12:32:19 -0700 Subject: [PATCH 0193/1101] Fix parsing of SDL 2.0.10 headers. That version added an unexpected typedef which threw off the current parser. Fixes #82 --- CHANGELOG.rst | 2 ++ parse_sdl2.py | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 87359fb3..96971d66 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Can now parse SDL 2.0.10 headers. 11.1.1 - 2019-08-01 ------------------- diff --git a/parse_sdl2.py b/parse_sdl2.py index 4ec1ae5d..1f582d18 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -17,7 +17,7 @@ ) RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) RE_TYPEDEF = re.compile(r"^typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) -RE_ENUM = re.compile(r"^enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) +RE_ENUM = re.compile(r"^(?:typedef )?enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) RE_DECL = re.compile(r"^extern[^#\n]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) RE_ENDIAN = re.compile( r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL @@ -74,6 +74,10 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: # Replace various definitions with "..." since cffi is limited here. yield RE_DEFINE_TRUNCATE.sub(r"\1 ...", define) + for enum in RE_ENUM.findall(header): + yield RE_ENUM_TRUNCATE.sub(r"\1 ...", enum) + header = header.replace(enum, "") + for typedef in RE_TYPEDEF.findall(header): # Special case for SDL window flags enum. if "SDL_WINDOW_FULLSCREEN_DESKTOP" in typedef: @@ -98,9 +102,6 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) yield typedef - for enum in RE_ENUM.findall(header): - yield RE_ENUM_TRUNCATE.sub(r"\1 ...", enum) - for decl in RE_DECL.findall(header): if "SDL_RWops" in decl: continue # Ignore SDL_RWops functions. From d80b60f5de2a20c9b75fe9043d7be256368a0ec6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 2 Aug 2019 12:36:39 -0700 Subject: [PATCH 0194/1101] Bundle SDL 2.0.10 for Windows/MacOS. --- CHANGELOG.rst | 3 +++ build_libtcod.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 96971d66..bb3cafb6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - Now bundles SDL 2.0.10 for Windows/MacOS. + Fixed - Can now parse SDL 2.0.10 headers. diff --git a/build_libtcod.py b/build_libtcod.py index 66e39c0d..bf5a6172 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -25,7 +25,7 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.9") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.10") CFFI_HEADER = "tcod/cffi.h" CFFI_EXTRA_CDEFS = "tcod/cdef.h" From dffea9b7f465fa8490635b374dd470e4460ae5f8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 2 Aug 2019 12:59:50 -0700 Subject: [PATCH 0195/1101] Prepare 11.1.2 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bb3cafb6..01dcb997 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,11 +8,14 @@ v2.0.0 Unreleased ------------------ + +11.1.2 - 2019-08-02 +------------------- Changed - Now bundles SDL 2.0.10 for Windows/MacOS. Fixed - - Can now parse SDL 2.0.10 headers. + - Can now parse SDL 2.0.10 headers during installation without crashing. 11.1.1 - 2019-08-01 ------------------- From 219a985709379185c5fb26d6663c00822fbbfb52 Mon Sep 17 00:00:00 2001 From: pale-phosphorescence <54227629+pale-phosphorescence@users.noreply.github.com> Date: Sat, 17 Aug 2019 23:17:29 -0600 Subject: [PATCH 0196/1101] Check all SDL2 include locations. On at least my FreeBSD 12 install, the sdl2-config command returns a string containing multiple -I values. The existing regex pulls out the last of these and assumes it's the right one; on my machine, we should actually be looking under the first one. By iterating over all the -I values the command returns, we can improve our confidence of actually finding the SDL2 headers. --- build_libtcod.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index bf5a6172..478054b4 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -128,14 +128,19 @@ def unpack_sdl2(version): elif sys.platform == "darwin": SDL2_INCLUDE = os.path.join(SDL2_PARSE_PATH, "Versions/A/Headers") else: - match = re.match( - r".*-I(\S+)", + matches = re.findall( + r"-I(\S+)", subprocess.check_output( ["sdl2-config", "--cflags"], universal_newlines=True ), ) - assert match - SDL2_INCLUDE, = match.groups() + assert matches + + SDL2_INCLUDE = None + for match in matches: + if os.path.isfile(os.path.join(match, 'SDL_stdinc.h')): + SDL2_INCLUDE = match + assert SDL2_INCLUDE if sys.platform == "win32": include_dirs.append(SDL2_INCLUDE) From 9a8d6a2339901c7954533e26e8e759c1caa27c7a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 2 Aug 2019 20:07:39 -0700 Subject: [PATCH 0197/1101] Update libtcod. Getting fixes from upstream. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 01dcb997..d1f10164 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - Changing the tiles of an active tileset on OPENGL2 will no longer leave + temporary artifact tiles. 11.1.2 - 2019-08-02 ------------------- diff --git a/libtcod b/libtcod index af0c6d0f..d4aa1d1e 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit af0c6d0ffd46f874293e083810bba3ef0ca5924d +Subproject commit d4aa1d1e103fd6aa542c1f7436ed0757f25b7435 From 096f7b0740ee312c0128672e4b3688900f061993 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 21 Aug 2019 21:35:08 -0700 Subject: [PATCH 0198/1101] Begin writing SDL2 helper module. --- tcod/sdl.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tcod/sdl.py diff --git a/tcod/sdl.py b/tcod/sdl.py new file mode 100644 index 00000000..fa68442f --- /dev/null +++ b/tcod/sdl.py @@ -0,0 +1,132 @@ +"""SDL2 specific functionality. + +Add the line ``import tcod.sdl`` to include this module, as importing this +module is not implied by ``import tcod``. +""" +from typing import Any, Tuple + +import numpy as np + +from tcod.libtcod import lib, ffi + +__all__ = ("Window",) + + +class _TempSurface: + """Holds a temporary surface derived from a NumPy array.""" + + def __init__(self, pixels: np.array) -> None: + self._array = np.ascontiguousarray(pixels, dtype=np.uint8) + if len(self._array) != 3: + raise TypeError( + "NumPy shape must be 3D [y, x, ch] (got %r)" + % (self._array.shape,) + ) + if 3 <= self._array.shape[2] <= 4: + raise TypeError( + "NumPy array must have RGB or RGBA channels. (got %r)" + % (self._array.shape,) + ) + self.p: Any = ffi.gc( + lib.SDL_CreateRGBSurfaceFrom( + ffi.cast("void*", self._array.ctypes.data), + self._array.shape[1], # Width. + self._array.shape[0], # Height. + self._array.shape[2] * 8, # Bit depth. + self._array.stride[1], # Pitch. + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000 if self._array.shape[2] == 4 else 0, + ), + lib.SDL_FreeSurface, + ) + + +class Window: + """An SDL2 Window object.""" + + def __init__(self, sdl_window_p: Any) -> None: + if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): + raise TypeError( + "sdl_window_p must be %r type (was %r)." + % (ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)) + ) + if not sdl_window_p: + raise ValueError("sdl_window_p can not be a null pointer.") + self.p = sdl_window_p + + def __eq__(self, other: Any) -> bool: + return bool(self.p == other.p) + + def set_icon(self, image: np.array) -> None: + """Set the window icon from an image. + + `image` is a C memory order RGB or RGBA NumPy array. + """ + surface = _TempSurface(image) + lib.SDL_SetWindowIcon(self.p, surface.p) + + @property + def allow_screen_saver(self) -> bool: + """If True the operating system is allowed to display a screen saver. + + You can set this attribute to enable or disable the screen saver. + """ + return bool(lib.SDL_IsScreenSaverEnabled(self.p)) + + @allow_screen_saver.setter + def allow_screen_saver(self, value: bool) -> None: + if value: + lib.SDL_EnableScreenSaver(self.p) + else: + lib.SDL_DisableScreenSaver(self.p) + + @property + def position(self) -> Tuple[int, int]: + """Return the (x, y) position of the window. + + This attribute can be set the move the window. + The constants tcod.lib.SDL_WINDOWPOS_CENTERED or + tcod.lib.SDL_WINDOWPOS_UNDEFINED can be used. + """ + xy = ffi.new("int[2]") + lib.SDL_GetWindowPosition(self.p, xy, xy + 1) + return xy[0], xy[1] + + @position.setter + def position(self, xy: Tuple[int, int]) -> None: + x, y = xy + lib.SDL_SetWindowPosition(self.p, x, y) + + @property + def size(self) -> Tuple[int, int]: + """Return the pixel (width, height) of the window. + + This attribute can be set to change the size of the window but the + given size must be greater than (1, 1) or else an exception will be + raised. + """ + xy = ffi.new("int[2]") + lib.SDL_GetWindowSize(self.p, xy, xy + 1) + return xy[0], xy[1] + + @size.setter + def size(self, xy: Tuple[int, int]) -> None: + if any(i <= 0 for i in xy): + raise ValueError( + "Window size must be greater than zero, not %r" % (xy,) + ) + x, y = xy + lib.SDL_SetWindowSize(self.p, x, y) + + +def get_active_window() -> Window: + """Return the SDL2 window current managed by libtcod. + + Will raise an error if libtcod does not currently have a window. + """ + sdl_window = lib.TCOD_sys_get_window() + if not sdl_window: + raise RuntimeError("TCOD does not have an active window.") + return Window(sdl_window) From 2af95ebb54fdace0c9ea7d619ac24b81bdc974f9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 21 Aug 2019 21:49:20 -0700 Subject: [PATCH 0199/1101] Rearrange modules so that tcod and libtcod are not subpckages. Prevents the occasional `from tcod import libtcod` issue. --- CHANGELOG.rst | 1 + tcod/__init__.py | 2 +- tcod/_internal.py | 153 ++++++++++++++++++++++++++++++++- tcod/bsp.py | 2 +- tcod/cffi.h | 2 +- tcod/color.py | 2 +- tcod/console.py | 2 +- tcod/image.py | 4 +- tcod/libtcodpy.py | 12 +-- tcod/{libtcod.py => loader.py} | 0 tcod/map.py | 2 +- tcod/noise.py | 2 +- tcod/path.py | 2 +- tcod/random.py | 2 +- tcod/sdl.py | 2 +- tcod/tcod.py | 153 --------------------------------- tcod/tileset.py | 2 +- 17 files changed, 171 insertions(+), 174 deletions(-) rename tcod/{libtcod.py => loader.py} (100%) delete mode 100644 tcod/tcod.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d1f10164..98a2335b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased Fixed - Changing the tiles of an active tileset on OPENGL2 will no longer leave temporary artifact tiles. + - It's now harder to accidentally import tcod's internal modules. 11.1.2 - 2019-08-02 ------------------- diff --git a/tcod/__init__.py b/tcod/__init__.py index 15dd2c68..48fc7b6c 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,7 +15,7 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ -from tcod.libtcod import lib, ffi, __sdl_version__ # noqa: F4 +from tcod.loader import lib, ffi, __sdl_version__ # noqa: F4 from tcod.libtcodpy import * # noqa: F4 try: diff --git a/tcod/_internal.py b/tcod/_internal.py index 7cd1295f..dc85cf07 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,8 +1,10 @@ +"""This module internal helper functions used by the rest of the library. +""" import functools -from typing import Any, Callable, TypeVar, cast +from typing import Any, AnyStr, Callable, TypeVar, cast import warnings -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) @@ -57,3 +59,150 @@ def _check(error: int) -> None: """Detect and convert a libtcod error code it into an exception.""" if error < 0: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + + +def _unpack_char_p(char_p: Any) -> str: + if char_p == ffi.NULL: + return "" + return ffi.string(char_p).decode() # type: ignore + + +def _int(int_or_str: Any) -> int: + "return an integer where a single character string may be expected" + if isinstance(int_or_str, str): + return ord(int_or_str) + if isinstance(int_or_str, bytes): + return int_or_str[0] + return int(int_or_str) # check for __count__ + + +def _bytes(string: AnyStr) -> bytes: + if isinstance(string, str): + return string.encode("utf-8") + return string + + +def _unicode(string: AnyStr, stacklevel: int = 2) -> str: + if isinstance(string, bytes): + warnings.warn( + ( + "Passing byte strings as parameters to Unicode functions is " + "deprecated." + ), + DeprecationWarning, + stacklevel=stacklevel + 1, + ) + return string.decode("latin-1") + return string + + +def _fmt(string: str, stacklevel: int = 2) -> bytes: + if isinstance(string, bytes): + warnings.warn( + ( + "Passing byte strings as parameters to Unicode functions is " + "deprecated." + ), + DeprecationWarning, + stacklevel=stacklevel + 1, + ) + string = string.decode("latin-1") + return string.encode("utf-8").replace(b"%", b"%%") + + +class _PropagateException: + """ context manager designed to propagate exceptions outside of a cffi + callback context. normally cffi suppresses the exception + + when propagate is called this class will hold onto the error until the + control flow leaves the context, then the error will be raised + + with _PropagateException as propagate: + # give propagate as onerror parameter for ffi.def_extern + """ + + def __init__(self) -> None: + # (exception, exc_value, traceback) + self.exc_info = None # type: Any + + def propagate(self, *exc_info: Any) -> None: + """ set an exception to be raised once this context exits + + if multiple errors are caught, only keep the first exception raised + """ + if not self.exc_info: + self.exc_info = exc_info + + def __enter__(self) -> Callable[[Any], None]: + """ once in context, only the propagate call is needed to use this + class effectively + """ + return self.propagate + + def __exit__(self, type: Any, value: Any, traceback: Any) -> None: + """ if we're holding on to an exception, raise it now + + prefers our held exception over any current raising error + + self.exc_info is reset now in case of nested manager shenanigans + """ + if self.exc_info: + type, value, traceback = self.exc_info + self.exc_info = None + if type: + # Python 2/3 compatible throw + exception = type(value) + exception.__traceback__ = traceback + raise exception + + +class _CDataWrapper(object): + def __init__(self, *args: Any, **kargs: Any): + self.cdata = self._get_cdata_from_args(*args, **kargs) + if self.cdata is None: + self.cdata = ffi.NULL + super(_CDataWrapper, self).__init__() + + @staticmethod + def _get_cdata_from_args(*args: Any, **kargs: Any) -> Any: + if len(args) == 1 and isinstance(args[0], ffi.CData) and not kargs: + return args[0] + else: + return None + + def __hash__(self) -> int: + return hash(self.cdata) + + def __eq__(self, other: Any) -> Any: + try: + return self.cdata == other.cdata + except AttributeError: + return NotImplemented + + def __getattr__(self, attr: str) -> Any: + if "cdata" in self.__dict__: + return getattr(self.__dict__["cdata"], attr) + raise AttributeError(attr) + + def __setattr__(self, attr: str, value: Any) -> None: + if hasattr(self, "cdata") and hasattr(self.cdata, attr): + setattr(self.cdata, attr, value) + else: + super(_CDataWrapper, self).__setattr__(attr, value) + + +def _console(console: Any) -> Any: + """Return a cffi console.""" + try: + return console.console_c + except AttributeError: + warnings.warn( + ( + "Falsy console parameters are deprecated, " + "always use the root console instance returned by " + "console_init_root." + ), + DeprecationWarning, + stacklevel=3, + ) + return ffi.NULL diff --git a/tcod/bsp.py b/tcod/bsp.py index 69c9f4f3..caadc137 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -25,7 +25,7 @@ """ from typing import Any, Iterator, List, Optional, Tuple, Union # noqa: F401 -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi from tcod._internal import deprecate import tcod.random diff --git a/tcod/cffi.h b/tcod/cffi.h index da766a46..c53dc8ed 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -1,5 +1,5 @@ /* This header is the entry point for the cffi parser. - Anything included here will be accessible from tcod.libtcod.lib */ + Anything included here will be accessible from tcod.loader.lib */ #include "../libtcod/src/libtcod/libtcod.h" #include "../libtcod/src/libtcod/libtcod_int.h" #include "../libtcod/src/libtcod/wrappers.h" diff --git a/tcod/color.py b/tcod/color.py index da1a19f9..915109ee 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -5,7 +5,7 @@ import warnings from tcod._internal import deprecate -from tcod.libtcod import lib +from tcod.loader import lib class Color(List[int]): diff --git a/tcod/console.py b/tcod/console.py index 3e8b7664..9bf913f0 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -31,7 +31,7 @@ import numpy as np import tcod.constants -from tcod.libtcod import ffi, lib +from tcod.loader import ffi, lib import tcod._internal from tcod._internal import deprecate diff --git a/tcod/image.py b/tcod/image.py index 42363759..98b36db6 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -3,8 +3,8 @@ import numpy as np import tcod.console -from tcod.libtcod import ffi, lib -from tcod.tcod import _console +from tcod.loader import ffi, lib +from tcod._internal import _console class _ImageBufferArray(np.ndarray): # type: ignore diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index bc389076..a15bd24f 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -22,7 +22,7 @@ import numpy as np -from tcod.libtcod import ffi, lib +from tcod.loader import ffi, lib from tcod.constants import * # noqa: F4 from tcod.constants import ( @@ -39,11 +39,11 @@ from tcod._internal import deprecate, pending_deprecate, _check -from tcod.tcod import _int, _unpack_char_p -from tcod.tcod import _bytes, _unicode, _fmt -from tcod.tcod import _CDataWrapper -from tcod.tcod import _PropagateException -from tcod.tcod import _console +from tcod._internal import _int, _unpack_char_p +from tcod._internal import _bytes, _unicode, _fmt +from tcod._internal import _CDataWrapper +from tcod._internal import _PropagateException +from tcod._internal import _console import tcod.bsp from tcod.color import Color diff --git a/tcod/libtcod.py b/tcod/loader.py similarity index 100% rename from tcod/libtcod.py rename to tcod/loader.py diff --git a/tcod/map.py b/tcod/map.py index f267fcfe..2bbd6db8 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -7,7 +7,7 @@ import numpy as np -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi import tcod._internal import tcod.constants diff --git a/tcod/noise.py b/tcod/noise.py index f53b9424..88194752 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -38,7 +38,7 @@ import numpy as np from tcod._internal import deprecate -from tcod.libtcod import ffi, lib +from tcod.loader import ffi, lib import tcod.constants import tcod.random diff --git a/tcod/path.py b/tcod/path.py index a6336556..961cfdce 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -44,7 +44,7 @@ import numpy as np -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi import tcod.map # noqa: F401 diff --git a/tcod/random.py b/tcod/random.py index 1b3fef09..316ee6d6 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -8,7 +8,7 @@ import random from typing import Any, Hashable, Optional -from tcod.libtcod import ffi, lib +from tcod.loader import ffi, lib import tcod.constants MERSENNE_TWISTER = tcod.constants.RNG_MT diff --git a/tcod/sdl.py b/tcod/sdl.py index fa68442f..3d581822 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -7,7 +7,7 @@ import numpy as np -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi __all__ = ("Window",) diff --git a/tcod/tcod.py b/tcod/tcod.py deleted file mode 100644 index 3102b1ab..00000000 --- a/tcod/tcod.py +++ /dev/null @@ -1,153 +0,0 @@ -"""This module focuses on improvements to the Python libtcod API. -""" -from typing import Any, AnyStr, Callable -import warnings - -from tcod.libtcod import ffi - - -def _unpack_char_p(char_p: Any) -> str: - if char_p == ffi.NULL: - return "" - return ffi.string(char_p).decode() # type: ignore - - -def _int(int_or_str: Any) -> int: - "return an integer where a single character string may be expected" - if isinstance(int_or_str, str): - return ord(int_or_str) - if isinstance(int_or_str, bytes): - return int_or_str[0] - return int(int_or_str) # check for __count__ - - -def _bytes(string: AnyStr) -> bytes: - if isinstance(string, str): - return string.encode("utf-8") - return string - - -def _unicode(string: AnyStr, stacklevel: int = 2) -> str: - if isinstance(string, bytes): - warnings.warn( - ( - "Passing byte strings as parameters to Unicode functions is " - "deprecated." - ), - DeprecationWarning, - stacklevel=stacklevel + 1, - ) - return string.decode("latin-1") - return string - - -def _fmt(string: str, stacklevel: int = 2) -> bytes: - if isinstance(string, bytes): - warnings.warn( - ( - "Passing byte strings as parameters to Unicode functions is " - "deprecated." - ), - DeprecationWarning, - stacklevel=stacklevel + 1, - ) - string = string.decode("latin-1") - return string.encode("utf-8").replace(b"%", b"%%") - - -class _PropagateException: - """ context manager designed to propagate exceptions outside of a cffi - callback context. normally cffi suppresses the exception - - when propagate is called this class will hold onto the error until the - control flow leaves the context, then the error will be raised - - with _PropagateException as propagate: - # give propagate as onerror parameter for ffi.def_extern - """ - - def __init__(self) -> None: - # (exception, exc_value, traceback) - self.exc_info = None # type: Any - - def propagate(self, *exc_info: Any) -> None: - """ set an exception to be raised once this context exits - - if multiple errors are caught, only keep the first exception raised - """ - if not self.exc_info: - self.exc_info = exc_info - - def __enter__(self) -> Callable[[Any], None]: - """ once in context, only the propagate call is needed to use this - class effectively - """ - return self.propagate - - def __exit__(self, type: Any, value: Any, traceback: Any) -> None: - """ if we're holding on to an exception, raise it now - - prefers our held exception over any current raising error - - self.exc_info is reset now in case of nested manager shenanigans - """ - if self.exc_info: - type, value, traceback = self.exc_info - self.exc_info = None - if type: - # Python 2/3 compatible throw - exception = type(value) - exception.__traceback__ = traceback - raise exception - - -class _CDataWrapper(object): - def __init__(self, *args: Any, **kargs: Any): - self.cdata = self._get_cdata_from_args(*args, **kargs) - if self.cdata is None: - self.cdata = ffi.NULL - super(_CDataWrapper, self).__init__() - - @staticmethod - def _get_cdata_from_args(*args: Any, **kargs: Any) -> Any: - if len(args) == 1 and isinstance(args[0], ffi.CData) and not kargs: - return args[0] - else: - return None - - def __hash__(self) -> int: - return hash(self.cdata) - - def __eq__(self, other: Any) -> Any: - try: - return self.cdata == other.cdata - except AttributeError: - return NotImplemented - - def __getattr__(self, attr: str) -> Any: - if "cdata" in self.__dict__: - return getattr(self.__dict__["cdata"], attr) - raise AttributeError(attr) - - def __setattr__(self, attr: str, value: Any) -> None: - if hasattr(self, "cdata") and hasattr(self.cdata, attr): - setattr(self.cdata, attr, value) - else: - super(_CDataWrapper, self).__setattr__(attr, value) - - -def _console(console: Any) -> Any: - """Return a cffi console.""" - try: - return console.console_c - except AttributeError: - warnings.warn( - ( - "Falsy console parameters are deprecated, " - "always use the root console instance returned by " - "console_init_root." - ), - DeprecationWarning, - stacklevel=3, - ) - return ffi.NULL diff --git a/tcod/tileset.py b/tcod/tileset.py index efbddc91..51380df4 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -9,7 +9,7 @@ import numpy as np -from tcod.libtcod import lib, ffi +from tcod.loader import lib, ffi class Tileset: From 87b11638fc0aba10e2096b0f29a40f66c9f0021d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 21 Aug 2019 22:01:43 -0700 Subject: [PATCH 0200/1101] Clean up strict MyPy warnings. --- tcod/event.py | 98 +++++++++++++++++++++++++-------------------------- tcod/path.py | 2 +- tcod/sdl.py | 2 +- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 8fab3309..c8ba6530 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -17,8 +17,8 @@ """ from typing import Any, Dict, Mapping, NamedTuple, Optional, Iterator, Tuple -import tcod import tcod.event_constants +from tcod.loader import ffi, lib from tcod.event_constants import * # noqa: F4 from tcod.event_constants import KMOD_SHIFT, KMOD_CTRL, KMOD_ALT, KMOD_GUI @@ -61,24 +61,24 @@ def _describe_bitmask( def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: """Convert pixel coordinates to tile coordinates.""" - xy = tcod.ffi.new("double[2]", (x, y)) - tcod.lib.TCOD_sys_pixel_to_tile(xy, xy + 1) + xy = ffi.new("double[2]", (x, y)) + lib.TCOD_sys_pixel_to_tile(xy, xy + 1) return xy[0], xy[1] Point = NamedTuple("Point", [("x", float), ("y", float)]) # manually define names for SDL macros -BUTTON_LEFT = tcod.lib.SDL_BUTTON_LEFT -BUTTON_MIDDLE = tcod.lib.SDL_BUTTON_MIDDLE -BUTTON_RIGHT = tcod.lib.SDL_BUTTON_RIGHT -BUTTON_X1 = tcod.lib.SDL_BUTTON_X1 -BUTTON_X2 = tcod.lib.SDL_BUTTON_X2 -BUTTON_LMASK = tcod.lib.SDL_BUTTON_LMASK -BUTTON_MMASK = tcod.lib.SDL_BUTTON_MMASK -BUTTON_RMASK = tcod.lib.SDL_BUTTON_RMASK -BUTTON_X1MASK = tcod.lib.SDL_BUTTON_X1MASK -BUTTON_X2MASK = tcod.lib.SDL_BUTTON_X2MASK +BUTTON_LEFT = lib.SDL_BUTTON_LEFT +BUTTON_MIDDLE = lib.SDL_BUTTON_MIDDLE +BUTTON_RIGHT = lib.SDL_BUTTON_RIGHT +BUTTON_X1 = lib.SDL_BUTTON_X1 +BUTTON_X2 = lib.SDL_BUTTON_X2 +BUTTON_LMASK = lib.SDL_BUTTON_LMASK +BUTTON_MMASK = lib.SDL_BUTTON_MMASK +BUTTON_RMASK = lib.SDL_BUTTON_RMASK +BUTTON_X1MASK = lib.SDL_BUTTON_X1MASK +BUTTON_X2MASK = lib.SDL_BUTTON_X2MASK # reverse tables are used to get the tcod.event name from the value. _REVERSE_BUTTON_TABLE = { @@ -500,7 +500,7 @@ def __init__(self, text: str): @classmethod def from_sdl_event(cls, sdl_event: Any) -> "TextInput": - self = cls(tcod.ffi.string(sdl_event.text.text, 32).decode("utf8")) + self = cls(ffi.string(sdl_event.text.text, 32).decode("utf8")) self.sdl_event = sdl_event return self @@ -523,11 +523,11 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return Undefined.from_sdl_event(sdl_event) event_type = cls.__WINDOW_TYPES[sdl_event.window.event].upper() self = None # type: Any - if sdl_event.window.event == tcod.lib.SDL_WINDOWEVENT_MOVED: + if sdl_event.window.event == lib.SDL_WINDOWEVENT_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) elif sdl_event.window.event in ( - tcod.lib.SDL_WINDOWEVENT_RESIZED, - tcod.lib.SDL_WINDOWEVENT_SIZE_CHANGED, + lib.SDL_WINDOWEVENT_RESIZED, + lib.SDL_WINDOWEVENT_SIZE_CHANGED, ): self = WindowResized( event_type, sdl_event.window.data1, sdl_event.window.data2 @@ -541,22 +541,22 @@ def __repr__(self) -> str: return "tcod.event.%s(type=%r)" % (self.__class__.__name__, self.type) __WINDOW_TYPES = { - tcod.lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", - tcod.lib.SDL_WINDOWEVENT_HIDDEN: "WindowHidden", - tcod.lib.SDL_WINDOWEVENT_EXPOSED: "WindowExposed", - tcod.lib.SDL_WINDOWEVENT_MOVED: "WindowMoved", - tcod.lib.SDL_WINDOWEVENT_RESIZED: "WindowResized", - tcod.lib.SDL_WINDOWEVENT_SIZE_CHANGED: "WindowSizeChanged", - tcod.lib.SDL_WINDOWEVENT_MINIMIZED: "WindowMinimized", - tcod.lib.SDL_WINDOWEVENT_MAXIMIZED: "WindowMaximized", - tcod.lib.SDL_WINDOWEVENT_RESTORED: "WindowRestored", - tcod.lib.SDL_WINDOWEVENT_ENTER: "WindowEnter", - tcod.lib.SDL_WINDOWEVENT_LEAVE: "WindowLeave", - tcod.lib.SDL_WINDOWEVENT_FOCUS_GAINED: "WindowFocusGained", - tcod.lib.SDL_WINDOWEVENT_FOCUS_LOST: "WindowFocusLost", - tcod.lib.SDL_WINDOWEVENT_CLOSE: "WindowClose", - tcod.lib.SDL_WINDOWEVENT_TAKE_FOCUS: "WindowTakeFocus", - tcod.lib.SDL_WINDOWEVENT_HIT_TEST: "WindowHitTest", + lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", + lib.SDL_WINDOWEVENT_HIDDEN: "WindowHidden", + lib.SDL_WINDOWEVENT_EXPOSED: "WindowExposed", + lib.SDL_WINDOWEVENT_MOVED: "WindowMoved", + lib.SDL_WINDOWEVENT_RESIZED: "WindowResized", + lib.SDL_WINDOWEVENT_SIZE_CHANGED: "WindowSizeChanged", + lib.SDL_WINDOWEVENT_MINIMIZED: "WindowMinimized", + lib.SDL_WINDOWEVENT_MAXIMIZED: "WindowMaximized", + lib.SDL_WINDOWEVENT_RESTORED: "WindowRestored", + lib.SDL_WINDOWEVENT_ENTER: "WindowEnter", + lib.SDL_WINDOWEVENT_LEAVE: "WindowLeave", + lib.SDL_WINDOWEVENT_FOCUS_GAINED: "WindowFocusGained", + lib.SDL_WINDOWEVENT_FOCUS_LOST: "WindowFocusLost", + lib.SDL_WINDOWEVENT_CLOSE: "WindowClose", + lib.SDL_WINDOWEVENT_TAKE_FOCUS: "WindowTakeFocus", + lib.SDL_WINDOWEVENT_HIT_TEST: "WindowHitTest", } @@ -639,15 +639,15 @@ def __str__(self) -> str: _SDL_TO_CLASS_TABLE = { - tcod.lib.SDL_QUIT: Quit, - tcod.lib.SDL_KEYDOWN: KeyDown, - tcod.lib.SDL_KEYUP: KeyUp, - tcod.lib.SDL_MOUSEMOTION: MouseMotion, - tcod.lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, - tcod.lib.SDL_MOUSEBUTTONUP: MouseButtonUp, - tcod.lib.SDL_MOUSEWHEEL: MouseWheel, - tcod.lib.SDL_TEXTINPUT: TextInput, - tcod.lib.SDL_WINDOWEVENT: WindowEvent, + lib.SDL_QUIT: Quit, + lib.SDL_KEYDOWN: KeyDown, + lib.SDL_KEYUP: KeyUp, + lib.SDL_MOUSEMOTION: MouseMotion, + lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, + lib.SDL_MOUSEBUTTONUP: MouseButtonUp, + lib.SDL_MOUSEWHEEL: MouseWheel, + lib.SDL_TEXTINPUT: TextInput, + lib.SDL_WINDOWEVENT: WindowEvent, } # type: Dict[int, Any] @@ -672,8 +672,8 @@ def get() -> Iterator[Any]: else: print(event) """ - sdl_event = tcod.ffi.new("SDL_Event*") - while tcod.lib.SDL_PollEvent(sdl_event): + sdl_event = ffi.new("SDL_Event*") + while lib.SDL_PollEvent(sdl_event): if sdl_event.type in _SDL_TO_CLASS_TABLE: yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) else: @@ -704,9 +704,9 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: print(event) """ if timeout is not None: - tcod.lib.SDL_WaitEventTimeout(tcod.ffi.NULL, int(timeout * 1000)) + lib.SDL_WaitEventTimeout(ffi.NULL, int(timeout * 1000)) else: - tcod.lib.SDL_WaitEvent(tcod.ffi.NULL) + lib.SDL_WaitEvent(ffi.NULL) return get() @@ -841,13 +841,13 @@ def get_mouse_state() -> MouseState: .. addedversion:: 9.3 """ - xy = tcod.ffi.new("int[2]") - buttons = tcod.lib.SDL_GetMouseState(xy, xy + 1) + xy = ffi.new("int[2]") + buttons = lib.SDL_GetMouseState(xy, xy + 1) x, y = _pixel_to_tile(*xy) return MouseState((xy[0], xy[1]), (int(x), int(y)), buttons) -@tcod.ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: return 0 diff --git a/tcod/path.py b/tcod/path.py index 961cfdce..9bfc178a 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -192,7 +192,7 @@ class _PathFinder(object): def __init__(self, cost: Any, diagonal: float = 1.41): self.cost = cost self.diagonal = diagonal - self._path_c = None + self._path_c = None # type: Any self._callback = self._userdata = None if hasattr(self.cost, "map_c"): diff --git a/tcod/sdl.py b/tcod/sdl.py index 3d581822..14de2fa5 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -27,7 +27,7 @@ def __init__(self, pixels: np.array) -> None: "NumPy array must have RGB or RGBA channels. (got %r)" % (self._array.shape,) ) - self.p: Any = ffi.gc( + self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( ffi.cast("void*", self._array.ctypes.data), self._array.shape[1], # Width. From 6833479308b116f380083e14fe91dd5ee07cb8e6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 24 Aug 2019 02:39:41 -0700 Subject: [PATCH 0201/1101] Add new Dijkstra path functions. --- CHANGELOG.rst | 5 ++ libtcod | 2 +- tcod/_internal.py | 3 +- tcod/path.cpp | 203 +++++++++++++++++++++++++++++++++++++++++++++- tcod/path.h | 14 ++++ tcod/path.py | 156 ++++++++++++++++++++++++++++++++++- 6 files changed, 379 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 98a2335b..827c2f42 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Added + - `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state. + - `tcod.path.hillclimb2d`: Returns a path from a distance array. + - `tcod.path.maxarray`: Creates arrays filled with maximum finite values. + Fixed - Changing the tiles of an active tileset on OPENGL2 will no longer leave temporary artifact tiles. diff --git a/libtcod b/libtcod index d4aa1d1e..c342f614 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit d4aa1d1e103fd6aa542c1f7436ed0757f25b7435 +Subproject commit c342f61472682b1ca1de838ac52525528d9e1707 diff --git a/tcod/_internal.py b/tcod/_internal.py index dc85cf07..a0e9f1eb 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -55,10 +55,11 @@ def handle_order(shape: Any, order: str) -> Any: return tuple(reversed(shape)) -def _check(error: int) -> None: +def _check(error: int) -> int: """Detect and convert a libtcod error code it into an exception.""" if error < 0: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + return error def _unpack_char_p(char_p: Any) -> str: diff --git a/tcod/path.cpp b/tcod/path.cpp index 78e21a24..4f6ea071 100644 --- a/tcod/path.cpp +++ b/tcod/path.cpp @@ -1,8 +1,14 @@ #include "path.h" -#include "../libtcod/src/libtcod/pathfinding/generic.h" +#include +#include #include +#include "../libtcod/src/libtcod/engine/error.h" +#include "../libtcod/src/libtcod/pathfinding/generic.h" +#include "../libtcod/src/libtcod/pathfinding/dijkstra.h" +#include "../libtcod/src/libtcod/pathfinding/hill-climb.h" +#include "../libtcod/src/libtcod/utility/matrix.h" static char* PickArrayValue(const struct PathCostArray *map, int i, int j) { return map->array + map->strides[0] * i + map->strides[1] * j; @@ -47,3 +53,198 @@ float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map){ return GetPathCost(map, x2, y2); } + +template +tcod::MatrixView to_matrix(struct NArray4* array) +{ + return tcod::MatrixView( + reinterpret_cast(array->data), + {array->shape[0], array->shape[1]}, + {array->strides[0], array->strides[1]} + ); +} +template +const tcod::MatrixView to_matrix(const struct NArray4* array) +{ + return tcod::MatrixView( + reinterpret_cast(array->data), + {array->shape[0], array->shape[1]}, + {array->strides[0], array->strides[1]} + ); +} + +template +int dijkstra2d_3( + DistType& dist, + const CostType& cost, + int cardinal, + int diagonal) +{ + try { + tcod::pathfinding::dijkstra2d(dist, cost, cardinal, diagonal); + } catch (const std::exception& e) { + return tcod::set_error(e); + } + return 0; +} +template +int dijkstra2d_2( + DistType& dist, + const struct NArray4* cost, + int cardinal, + int diagonal) +{ + switch (cost->type) { + case np_int8: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_int16: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_int32: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_int64: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_uint8: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_uint16: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_uint32: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + case np_uint64: + return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); + default: + return tcod::set_error("Expected distance map to be int type."); + } +} +int dijkstra2d( + struct NArray4* dist, + const struct NArray4* cost, + int cardinal, + int diagonal) +{ + switch (dist->type) { + case np_int8: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_int16: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_int32: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_int64: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_uint8: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_uint16: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_uint32: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + case np_uint64: { + auto dist_ = to_matrix(dist); + return dijkstra2d_2(dist_, cost, cardinal, diagonal); + } + default: + return tcod::set_error("Expected cost map to be int type."); + } +} + +static const std::array, 4> CARDINAL_{{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}}; +static const std::array, 4> DIAGONAL_{{{-1, -1}, {1, -1}, {-1, 1}, {1, 1}}}; + +template +class PlainGraph { + public: + PlainGraph(const IndexType& size, bool cardinal, bool diagonal) + : size_{size}, cardinal_{cardinal}, diagonal_{diagonal} + {} + template + void with_edges(const F& edge_func, const index_type& index) const + { + if (cardinal_) { + for (const auto& edge : CARDINAL_) { + index_type node{index[0] + edge[0], index[1] + edge[1]}; + if (!in_range(node)) { continue; } + edge_func(node, 0); + } + } + if (diagonal_) { + for (const auto& edge : DIAGONAL_) { + index_type node{index[0] + edge[0], index[1] + edge[1]}; + if (!in_range(node)) { continue; } + edge_func(node, 0); + } + } + } + private: + IndexType size_; + bool cardinal_; + bool diagonal_; + bool in_range(const IndexType& index) const noexcept{ + return (0 <= index[0] && index[0] < size_[0] && + 0 <= index[1] && index[1] < size_[1]); + } +}; + +template +int hillclimb2d_2( + const DistType& dist, + int x, + int y, + bool cardinal, + bool diagonal, + int* out) +{ + PlainGraph graph{ + dist.get_shape(), cardinal, diagonal + }; + auto path = tcod::pathfinding::simple_hillclimb(dist, graph, {x, y}); + if (out) { + for (const auto& index : path) { + out[0] = static_cast(index.at(0)); + out[1] = static_cast(index.at(1)); + out += 2; + } + } + return static_cast(path.size()); +} +int hillclimb2d( + const struct NArray4* dist, + int x, + int y, + bool cardinal, + bool diagonal, + int* out) +{ + switch (dist->type) { + case np_int8: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_int16: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_int32: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_int64: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_uint8: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_uint16: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_uint32: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + case np_uint64: + return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); + default: + return tcod::set_error("Expected cost map to be int type."); + } + +} diff --git a/tcod/path.h b/tcod/path.h index 626c2969..3d5a700c 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -66,6 +66,20 @@ float PathCostArrayInt16(int x1, int y1, int x2, int y2, float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map); +int dijkstra2d( + struct NArray4* dist, + const struct NArray4* cost, + int cardinal, + int diagonal); + +int hillclimb2d( + const struct NArray4* dist, + int x, + int y, + bool cardinal, + bool diagonal, + int* out); + #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index 9bfc178a..97dcfcd0 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -40,11 +40,12 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ -from typing import Any, Callable, List, Tuple, Union # noqa: F401 +from typing import Any, Callable, List, Optional, Tuple, Union # noqa: F401 import numpy as np from tcod.loader import lib, ffi +from tcod._internal import _check import tcod.map # noqa: F401 @@ -306,3 +307,156 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y): path.append((pointer_x[0], pointer_y[0])) return path + +_INT_TYPES = { + np.int8: lib.np_int8, + np.int16: lib.np_int16, + np.int32: lib.np_int32, + np.int64: lib.np_int64, + np.uint8: lib.np_uint8, + np.uint16: lib.np_uint16, + np.uint32: lib.np_uint32, + np.uint64: lib.np_uint64, +} + +def maxarray(shape: Tuple[int, ...], dtype: Any = int, order: str = "C") -> np.array: + """Return a new array filled with the maximum finite value for `dtype`. + + `shape` is of the new array. Same as other NumPy array initializers. + + `dtype` should be a single NumPy integer type. + + `order` can be "C" or "F". + + This works the same as + ``np.full(shape, np.iinfo(dtype).max, dtype, order)``. + + This kind of array is an ideal starting point for distance maps. Just set + any point to a lower value such as 0 and then pass this array to a + function such as :any:`dijkstra2d`. + """ + return np.full(shape, np.iinfo(dtype).max, dtype, order) + +def _export(array: np.array) -> Any: + """Convert a NumPy array into a ctype object.""" + return ffi.new( + "struct NArray4*", + ( + _INT_TYPES[array.dtype.type], + ffi.cast("void*", array.ctypes.data), + array.shape, + array.strides, + ), + ) + +def dijkstra2d( + distance: np.array, + cost: np.array, + cardinal: Optional[int], + diagonal: Optional[int], +) -> None: + """Return the computed distance of all nodes on a 2D Dijkstra grid. + + `distance` is an input/output array of node distances. Is this often an + array filled with maximum finite values and 1 or more points with a low + value such as 0. Distance will flow from these low values to adjacent + nodes based the cost to reach those nodes. This array is modified + in-place. + + `cost` is an array of node costs. Any node with a cost less than or equal + to 0 is considered blocked off. Positive values are the distance needed to + reach that node. + + `cardinal` and `diagonal` are the cost multipliers for edges in those + directions. A value of None or 0 will disable those directions. Typical + values could be: ``1, None``, ``1, 1``, ``2, 3``, etc. + + `out` is the output array and can be the same as `distance`. + + Example: + + >>> import numpy as np + >>> import tcod.path + >>> cost = np.ones((3, 3), dtype=np.uint8) + >>> cost[:2, 1] = 0 + >>> cost + array([[1, 0, 1], + [1, 0, 1], + [1, 1, 1]], dtype=uint8) + >>> dist = tcod.path.maxarray((3, 3), dtype=np.int32) + >>> dist[0, 0] = 0 + >>> dist + array([[ 0, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647]]...) + >>> tcod.path.dijkstra2d(dist, cost, 2, 3) + >>> dist + array([[ 0, 2147483647, 10], + [ 2, 2147483647, 8], + [ 4, 5, 7]]...) + >>> path = tcod.path.hillclimb2d(dist, (2, 2), True, True) + >>> path + array([[2, 2], + [2, 1], + [1, 0], + [0, 0]], dtype=int32) + >>> path = list(path[::-1]) + >>> while path: + ... print(path.pop(0)) + [0 0] + [1 0] + [2 1] + [2 2] + + .. versionadded:: 11.2 + """ + dist = distance + cost = np.asarray(cost) + if dist.shape != cost.shape: + raise TypeError("distance and cost must have the same shape %r != %r" % (dist.shape, cost.shape)) + if cardinal is None: + cardinal = 0 + if diagonal is None: + diagonal = 0 + _check(lib.dijkstra2d(_export(dist), _export(cost), cardinal, diagonal)) + +def hillclimb2d( + distance: np.array, + start: Tuple[int, int], + cardinal: bool, + diagonal: bool, +) -> np.array: + """Return a path on a grid from `start` to the lowest point. + + `distance` should be a fully computed distance array. This kind of array + is returned by :any:`dijkstra2d`. + + `start` is a 2-item tuple with starting coordinates. The axes if these + coordinates should match the axis of the `distance` array. An out-of-bounds + `start` index will raise an IndexError. + + At each step nodes adjacent toe current will be checked for a value lower + than the current one. Which directions are checked is decided by the + boolean values `cardinal` and `diagonal`. This process is repeated until + all adjacent nodes are equal to or larger than the last point on the path. + + The returned array is a 2D NumPy array with the shape: (length, axis). + This array always includes both the starting and ending point and will + always have at least one item. + + Typical uses of the returned array will be to either convert it into a list + which can be popped from, or transpose it and convert it into a tuple which + can be used to index other arrays using NumPy's advanced indexing rules. + + .. versionadded:: 11.2 + """ + x, y = start + dist = np.asarray(distance) + if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): + raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) + c_dist = _export(dist) + length = _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, ffi.NULL)) + path = np.ndarray((length, 2), dtype=np.intc) + c_path = ffi.cast("int*", path.ctypes.data) + _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, c_path)) + return path From e97517114c544f49a4c64ae5c31ae855c852af71 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 24 Aug 2019 23:40:50 -0700 Subject: [PATCH 0202/1101] Prepare 11.2.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 827c2f42..36cc8ebc 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.2.0 - 2019-08-24 +------------------- Added - `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state. - `tcod.path.hillclimb2d`: Returns a path from a distance array. From e3c9f51bee09ddcd3c76280df11cd018d33dadf9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 25 Aug 2019 18:17:46 -0700 Subject: [PATCH 0203/1101] Fix regression in PyInstaller distributions. This did not need to check a strict location. A broad search for SDL2 would work fine. --- CHANGELOG.rst | 2 ++ tcod/loader.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 36cc8ebc..28d8f7e5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed a regression preventing PyInstaller distributions from loading SDL. 11.2.0 - 2019-08-24 ------------------- diff --git a/tcod/loader.py b/tcod/loader.py index 131ad89d..e49d27dc 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -29,9 +29,7 @@ def verify_dependencies() -> None: """Try to make sure dependencies exist on this system.""" if sys.platform == "win32": - lib_test = ffi_check.dlopen( # Make sure SDL2.dll is here. - os.path.join(__path__[0], get_architecture(), "SDL2.dll") - ) + lib_test = ffi_check.dlopen("SDL2") # Make sure SDL2.dll is here. version = ffi_check.new("struct SDL_version*") lib_test.SDL_GetVersion(version) # Need to check this version. version = version.major, version.minor, version.patch From d0d8b8feb290fb6a166ed4624ed5146e8cfc22ec Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 25 Aug 2019 18:20:06 -0700 Subject: [PATCH 0204/1101] Reformat tcod.path module. --- tcod/path.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 97dcfcd0..2aaac423 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -308,6 +308,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: path.append((pointer_x[0], pointer_y[0])) return path + _INT_TYPES = { np.int8: lib.np_int8, np.int16: lib.np_int16, @@ -319,7 +320,10 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: np.uint64: lib.np_uint64, } -def maxarray(shape: Tuple[int, ...], dtype: Any = int, order: str = "C") -> np.array: + +def maxarray( + shape: Tuple[int, ...], dtype: Any = int, order: str = "C" +) -> np.array: """Return a new array filled with the maximum finite value for `dtype`. `shape` is of the new array. Same as other NumPy array initializers. @@ -337,6 +341,7 @@ def maxarray(shape: Tuple[int, ...], dtype: Any = int, order: str = "C") -> np.a """ return np.full(shape, np.iinfo(dtype).max, dtype, order) + def _export(array: np.array) -> Any: """Convert a NumPy array into a ctype object.""" return ffi.new( @@ -349,6 +354,7 @@ def _export(array: np.array) -> Any: ), ) + def dijkstra2d( distance: np.array, cost: np.array, @@ -413,18 +419,19 @@ def dijkstra2d( dist = distance cost = np.asarray(cost) if dist.shape != cost.shape: - raise TypeError("distance and cost must have the same shape %r != %r" % (dist.shape, cost.shape)) + raise TypeError( + "distance and cost must have the same shape %r != %r" + % (dist.shape, cost.shape) + ) if cardinal is None: cardinal = 0 if diagonal is None: diagonal = 0 _check(lib.dijkstra2d(_export(dist), _export(cost), cardinal, diagonal)) + def hillclimb2d( - distance: np.array, - start: Tuple[int, int], - cardinal: bool, - diagonal: bool, + distance: np.array, start: Tuple[int, int], cardinal: bool, diagonal: bool ) -> np.array: """Return a path on a grid from `start` to the lowest point. @@ -453,9 +460,13 @@ def hillclimb2d( x, y = start dist = np.asarray(distance) if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): - raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) + raise IndexError( + "Starting point %r not in shape %r" % (start, dist.shape) + ) c_dist = _export(dist) - length = _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, ffi.NULL)) + length = _check( + lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, ffi.NULL) + ) path = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.cast("int*", path.ctypes.data) _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, c_path)) From 32fcc42d5107c5b212978ea88df1a8f6f4743061 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 25 Aug 2019 18:20:48 -0700 Subject: [PATCH 0205/1101] Prepare 11.2.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 28d8f7e5..b3221f55 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.2.1 - 2019-08-25 +------------------- Fixed - Fixed a regression preventing PyInstaller distributions from loading SDL. From eec39844adc71c8018c7b427f56397d682e9a80f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 25 Aug 2019 20:20:00 -0700 Subject: [PATCH 0206/1101] Fix dlopen call. --- tcod/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/loader.py b/tcod/loader.py index e49d27dc..c7dbe86c 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -29,7 +29,7 @@ def verify_dependencies() -> None: """Try to make sure dependencies exist on this system.""" if sys.platform == "win32": - lib_test = ffi_check.dlopen("SDL2") # Make sure SDL2.dll is here. + lib_test = ffi_check.dlopen("SDL2.dll") # Make sure SDL2.dll is here. version = ffi_check.new("struct SDL_version*") lib_test.SDL_GetVersion(version) # Need to check this version. version = version.major, version.minor, version.patch From a39ecd91df76f4fa6e3218a17599faafe700d53a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 25 Aug 2019 20:28:10 -0700 Subject: [PATCH 0207/1101] Prepare 11.2.2 release. --- CHANGELOG.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b3221f55..af3a226f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,10 +9,13 @@ v2.0.0 Unreleased ------------------ -11.2.1 - 2019-08-25 +11.2.2 - 2019-08-25 ------------------- Fixed - - Fixed a regression preventing PyInstaller distributions from loading SDL. + - Fixed a regression preventing PyInstaller distributions from loading SDL2. + +11.2.1 - 2019-08-25 +------------------- 11.2.0 - 2019-08-24 ------------------- From f36b4d2260af320703211b6a3f7acdfb130dbb55 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 28 Aug 2019 03:11:48 -0700 Subject: [PATCH 0208/1101] Secure PyPI account. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4dc86025..3fea39b8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: TWINE_USERNAME: - secure: 6EhNSnUl0yOO26EeQ5WG1pQx8v/vp99/u24NRpZvF0k= + secure: sUo+lYht329nQC5JCxEB8w== TWINE_PASSWORD: - secure: PwPzzV8DPqw/+5M66FJQ5tbiF7rlOIK+bQIl8dFdF34= + secure: mjSxOmR8POpeVSL9mwOnH2XQAn9Trj5wn6dbmCIwgTSr2qjfm/y1UoQ2XkYV9QpATp1xLUKalIDc62bORmIMKe6GkhDayUymLC6RM8KRZqM93aPoNXst9c5YfdUVbJuubk1QsJ/wE8KZrr6jrcXM3H7rfiq9Z8NtEud1XDD/d8MKbR9LkL4Y9ACI12jfayQ+uwaTOMhs9/1XlT92FEPGqy0Qlr/zXLd7TixOkAB/cyqYJdD3B6+fyvqxFxPVrA+iWhldv60ERQmlXW/j7WbPCw== CODACY_PROJECT_TOKEN: secure: xprpiCGL823NKrs/K2Cps1UVBEmpezXReLxcfLyU1M43ZBBOK91xvjdIJamYKi8D DEPLOY_ONLY: false From be7a736aea1fc65857ba0acb5ed9d8c1389ef671 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 1 Sep 2019 16:12:56 -0700 Subject: [PATCH 0209/1101] Guess better 64 bit types in fake typedefs. --- dependencies/fake_libc_include/_fake_typedefs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/fake_libc_include/_fake_typedefs.h b/dependencies/fake_libc_include/_fake_typedefs.h index 06f6dc22..9b39f9d5 100644 --- a/dependencies/fake_libc_include/_fake_typedefs.h +++ b/dependencies/fake_libc_include/_fake_typedefs.h @@ -118,8 +118,8 @@ typedef signed short int16_t; typedef unsigned short uint16_t; typedef signed int int32_t; typedef unsigned int uint32_t; -typedef signed long int64_t; -typedef unsigned long uint64_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; /* C99 minimum-width integer types */ From 572ff2aada7f2bbccdd533a8bb8f0076d728af39 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 5 Sep 2019 17:20:37 -0700 Subject: [PATCH 0210/1101] Add Console.tiles2 array attribute. Needed to simplify array assignment and remove the need to port put_char_ex. --- CHANGELOG.rst | 3 +++ tcod/console.py | 28 +++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index af3a226f..d6b9ea81 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Added + - New attribute `Console.tiles2` is similar to `Console.tiles` but without an + alpha channel. 11.2.2 - 2019-08-25 ------------------- diff --git a/tcod/console.py b/tcod/console.py index 9bf913f0..9c80baea 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -102,9 +102,10 @@ class Console: # A structured arrays type with the added "fg_rgb" and "bg_rgb" fields. _DTYPE_RGB = np.dtype( { - "names": ["ch", "fg", "bg", "fg_rgb", "bg_rgb"], - "formats": [np.int32, "(4,)u1", "(4,)u1", "(3,)u1", "(3,)u1"], - "offsets": [0, 4, 8, 4, 8], + "names": ["ch", "fg", "bg"], + "formats": [np.int32, "(3,)u1", "(3,)u1"], + "offsets": [0, 4, 8], + "itemsize": 12, } ) @@ -247,13 +248,13 @@ def ch(self) -> np.ndarray: @property def tiles(self) -> np.ndarray: - """An array of this consoles tile data. + """An array of this consoles raw tile data. This acts as a combination of the `ch`, `fg`, and `bg` attributes. Colors include an alpha channel but how alpha works is currently undefined. - Example:: + Example: >>> con = tcod.console.Console(10, 2, order="F") >>> con.tiles[0, 0] = ( ... ord("X"), @@ -267,6 +268,23 @@ def tiles(self) -> np.ndarray: """ return self._tiles.T if self._order == "F" else self._tiles + @property + def tiles2(self) -> np.ndarray: + """An array of this consoles tile data without the alpha channel. + + Example: + >>> con = tcod.console.Console(10, 2, order="F") + >>> con.tiles2[0, 0] = ord("@"), tcod.yellow, tcod.black + >>> con.tiles2[0, 0] + (64, [255, 255, 0], [0, 0, 0]) + >>> con.tiles2["bg"] = tcod.blue + >>> con.tiles2[0, 0] + (64, [255, 255, 0], [ 0, 0, 255]) + + .. versionadded:: 11.3 + """ + return self.tiles.view(self._DTYPE_RGB) + @property def default_bg(self) -> Tuple[int, int, int]: """Tuple[int, int, int]: The default background color.""" From 3843ee6dc78be03ec2db5f0040f3b7f5c2a8efdf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 6 Sep 2019 17:29:52 -0700 Subject: [PATCH 0211/1101] Prepare 11.3.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d6b9ea81..87c9b8bc 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.3.0 - 2019-09-06 +------------------- Added - New attribute `Console.tiles2` is similar to `Console.tiles` but without an alpha channel. From f2dfe5160e9760d00f6404314fa954025099f8db Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 18 Sep 2019 23:22:21 -0700 Subject: [PATCH 0212/1101] Add reference to SDL's keycode tables in the docs. Resolves #86 --- tcod/event.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index c8ba6530..aaf9d034 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1,6 +1,11 @@ """ -An alternative, more direct implementation of event handling based on using -cffi calls to SDL functions. The current code is partially incomplete. +A light-weight implementation of event handling built on calls to SDL. + +Many event constants are derived directly from SDL. +For example: ``tcod.event.K_UP`` and ``tcod.event.SCANCODE_A`` refer to +SDL's ``SDLK_UP`` and ``SDL_SCANCODE_A`` respectfully. +`See this table for all of SDL's keyboard constants. +`_ Printing any event will tell you its attributes in a human readable format. An events type attribute if omitted is just the classes name with all letters From 1143dd0ee161a906f403d53ade827a06e8ebdee8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 19 Sep 2019 04:35:47 -0700 Subject: [PATCH 0213/1101] Add Image.__array_interface__ method. This allows this object to be used seamlessly with NumPy. --- CHANGELOG.rst | 2 ++ tcod/image.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 87c9b8bc..8f9f6cbd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added +- Added __array_interface__ to the Image class. 11.3.0 - 2019-09-06 ------------------- diff --git a/tcod/image.py b/tcod/image.py index 98b36db6..d3a089d7 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,4 +1,4 @@ -from typing import Any, Tuple +from typing import Any, Dict, Tuple import numpy as np @@ -292,3 +292,56 @@ def save_as(self, filename: str) -> None: filename (Text): File path to same this Image. """ lib.TCOD_image_save(self.image_c, filename.encode("utf-8")) + + @property + def __array_interface__(self) -> Dict[str, Any]: + """Return an interface for this images pixel buffer. + + Use :any:`numpy.asarray` to get the read-write array of this Image. + + This will often return an RGB array, but could also return an RGBA + array or fail silently. Future versions might change what type of + array is returned. + + You can use ``dtype=numpy.uint8`` to ensure that errors are not ignored + by NumPy. + + .. versionadded:: 11.4 + """ + strides = None + if self.image_c.mipmaps: # Libtcod RGB array. + depth = 3 + data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf)) + elif self.image_c.sys_img: # SDL Surface. + data = int(ffi.cast("size_t", self.image_c.sys_img.pixels)) + format = self.image_c.sys_img.format.format + if format == lib.SDL_PIXELFORMAT_RGB24: + depth = 3 + elif format == lib.SDL_PIXELFORMAT_RGBA32: + depth = 4 + else: + raise TypeError( + "Can't interface with format: %s" + % (_get_format_name(format),) + ) + strides = (self.image_c.sys_img.pitch, depth, 1) + else: + raise TypeError("Image has no initialized data.") + return { + "shape": (self.height, self.width, depth), + "typestr": "|u1", + "data": (data, False), + "strides": strides, + "version": 3, + } + + +def _get_format_name(format: int) -> str: + """Return the SDL_PIXELFORMAT_X name for this format, if possible.""" + for attr in dir(lib): + if not attr.startswith("SDL_PIXELFORMAT"): + continue + if not getattr(lib, attr) == format: + continue + return attr + return str(format) From ce1e308b9fe29a79f8e1029e7c2cb7e82c1a1ed3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Sep 2019 13:48:31 -0700 Subject: [PATCH 0214/1101] Add Console.draw_semigraphics method. This also adds helper functions to pass array-like objects as libtcod images. --- CHANGELOG.rst | 2 ++ examples/samples_tcod.py | 2 +- tcod/_internal.py | 43 ++++++++++++++++++++++++++++++++++++++++ tcod/console.py | 16 +++++++++++++++ tcod/image.py | 9 +++++++++ tests/test_console.py | 7 +++++++ 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f9f6cbd..e2faa477 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ Unreleased ------------------ Added - Added __array_interface__ to the Image class. +- Added `Console.draw_semigraphics` as a replacement for blit_2x functions. + `draw_semigraphics` can handle array-like objects. 11.3.0 - 2019-09-06 ------------------- diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 92264b21..23973314 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -393,7 +393,7 @@ def on_draw(self): recth = 13 if self.implementation == tcod.noise.SIMPLE: recth = 10 - self.img.blit_2x(sample_console, 0, 0) + sample_console.draw_semigraphics(self.img) sample_console.draw_rect( 2, 2, diff --git a/tcod/_internal.py b/tcod/_internal.py index a0e9f1eb..483be1a3 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -4,6 +4,8 @@ from typing import Any, AnyStr, Callable, TypeVar, cast import warnings +import numpy as np + from tcod.loader import lib, ffi FuncType = Callable[..., Any] @@ -207,3 +209,44 @@ def _console(console: Any) -> Any: stacklevel=3, ) return ffi.NULL + + +class TempImage(object): + """An Image-like container for NumPy arrays.""" + + def __init__(self, array: Any): + self._array = np.ascontiguousarray(array, dtype=np.uint8) + height, width, depth = self._array.shape + if depth != 3: + raise TypeError( + "Array must have RGB channels. Shape is: %r" + % (self._array.shape,) + ) + self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) + self._mipmaps = ffi.new( + "struct TCOD_mipmap_*", + { + "width": width, + "height": height, + "fwidth": width, + "fheight": height, + "buf": self._buffer, + "dirty": True, + }, + ) + self.image_c = ffi.new( + "TCOD_Image*", + { + "sys_img": ffi.NULL, + "nb_mipmaps": 1, + "mipmaps": self._mipmaps, + "has_key_color": False, + }, + ) + + +def _asimage(image: Any) -> TempImage: + """Convert this input into an Image-like object.""" + if hasattr(image, "image_c"): + return image # type: ignore + return TempImage(image) diff --git a/tcod/console.py b/tcod/console.py index 9c80baea..a2556dec 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1101,6 +1101,22 @@ def draw_rect( bg_blend, ) + def draw_semigraphics(self, pixels: Any, x: int = 0, y: int = 0) -> None: + """Draw a block of 2x2 semi-graphics into this console. + + `pixels` is an Image or an array-like object. It will be down-sampled + into 2x2 blocks when drawn. Array-like objects must be in the shape of + `(height, width, RGB)` and should have a `dtype` of `numpy.uint8`. + + `x` and `y` is the upper-left tile position to start drawing. + + .. versionadded:: 11.4 + """ + image = tcod._internal._asimage(pixels) + lib.TCOD_image_blit_2x( + image.image_c, self.console_c, x, y, 0, 0, -1, -1 + ) + def get_height_rect(width: int, string: str) -> int: """Return the number of lines which would be printed from these parameters. diff --git a/tcod/image.py b/tcod/image.py index d3a089d7..dcb81c68 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -56,6 +56,15 @@ def _from_cdata(cls, cdata: Any) -> "Image": self.width, self.height = self._get_size() return self + @classmethod + def _from_array(cls, array: Any) -> "Image": + array = np.asarray(array) + height, width, depth = array.shape + image = cls(width, height) + image_array = np.asarray(image) + image_array[:] = array[:, :, :3] + return image + def clear(self, color: Tuple[int, int, int]) -> None: """Fill this entire Image with color. diff --git a/tests/test_console.py b/tests/test_console.py index 7b3af473..dd299ff4 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -121,3 +121,10 @@ def test_console_clear(): console.clear(fg=(7, 8, 9), bg=(10, 11, 12)) assert console.fg[0, 0].tolist() == [7, 8, 9] assert console.bg[0, 0].tolist() == [10, 11, 12] + + +def test_console_semigraphics(): + console = tcod.console.Console(1, 1) + console.draw_semigraphics( + [[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]], + ) From 7dd11e4c65f68aa119e3a81255e07779a59474ea Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Sep 2019 14:03:49 -0700 Subject: [PATCH 0215/1101] Add Image.from_array class method. --- CHANGELOG.rst | 1 + tcod/image.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2faa477..81efed1f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Added - Added __array_interface__ to the Image class. - Added `Console.draw_semigraphics` as a replacement for blit_2x functions. `draw_semigraphics` can handle array-like objects. +- `Image.from_array` class method creates an Image from an array-like object. 11.3.0 - 2019-09-06 ------------------- diff --git a/tcod/image.py b/tcod/image.py index dcb81c68..df16476f 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -57,12 +57,22 @@ def _from_cdata(cls, cdata: Any) -> "Image": return self @classmethod - def _from_array(cls, array: Any) -> "Image": + def from_array(cls, array: Any) -> "Image": + """Create a new Image from a copy of an array-like object. + + Example: + >>> import numpy as np + >>> import tcod.image + >>> array = np.zeros((5, 5, 3), dtype=np.uint8) + >>> image = tcod.image.Image.from_array(array) + + .. versionadded:: 11.4 + """ array = np.asarray(array) height, width, depth = array.shape image = cls(width, height) image_array = np.asarray(image) - image_array[:] = array[:, :, :3] + image_array[...] = array return image def clear(self, color: Tuple[int, int, int]) -> None: From 1664320b448879868d9c0bee944b02b493e49c65 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Sep 2019 16:19:50 -0700 Subject: [PATCH 0216/1101] Added tcod.image.load function. --- CHANGELOG.rst | 9 +++++---- tcod/image.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81efed1f..acfa44af 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,10 +9,11 @@ v2.0.0 Unreleased ------------------ Added -- Added __array_interface__ to the Image class. -- Added `Console.draw_semigraphics` as a replacement for blit_2x functions. - `draw_semigraphics` can handle array-like objects. -- `Image.from_array` class method creates an Image from an array-like object. + - Added `__array_interface__` to the Image class. + - Added `Console.draw_semigraphics` as a replacement for blit_2x functions. + `draw_semigraphics` can handle array-like objects. + - `Image.from_array` class method creates an Image from an array-like object. + - `tcod.image.load` loads a PNG file as an RGBA array. 11.3.0 - 2019-09-06 ------------------- diff --git a/tcod/image.py b/tcod/image.py index df16476f..5fd07ea7 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -364,3 +364,28 @@ def _get_format_name(format: int) -> str: continue return attr return str(format) + + +def load(filename: str) -> np.ndarray: + """Load a PNG file as an RGBA array. + + `filename` is the name of the file to load. + + The returned array is in the shape: `(height, width, RGBA)`. + + .. versionadded:: 11.4 + """ + image = Image._from_cdata( + ffi.gc(lib.TCOD_image_load(filename.encode()), lib.TCOD_image_delete) + ) + array = np.asarray(image, dtype=np.uint8) + height, width, depth = array.shape + if depth == 3: + array = np.concatenate( + ( + array, + np.full((height, width, 1), fill_value=255, dtype=np.uint8), + ), + axis=2, + ) + return array From 22de3a4498e371cd3c432c900a1445918b57e77f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Sep 2019 16:30:26 -0700 Subject: [PATCH 0217/1101] Change Console.tiles to Console.buffer. --- CHANGELOG.rst | 6 ++++++ tcod/console.py | 23 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index acfa44af..8b254c12 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,12 @@ Added - `Image.from_array` class method creates an Image from an array-like object. - `tcod.image.load` loads a PNG file as an RGBA array. +Changed + - `Console.tiles` is now named `Console.buffer`. + +Deprecated + - `Console.tiles` behavior will be changed to be like `Console.tiles2`. + 11.3.0 - 2019-09-06 ------------------- Added diff --git a/tcod/console.py b/tcod/console.py index a2556dec..7dd393c4 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -246,25 +246,40 @@ def ch(self) -> np.ndarray: """ return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] - @property + @property # type: ignore + @deprecate( + "The `tiles` attribute has been renamed to `buffer`. " + "This attribute will later be changed to be the same as tiles2." + ) def tiles(self) -> np.ndarray: """An array of this consoles raw tile data. + .. versionadded:: 10.0 + + .. deprecated:: 11.4 + This has been renamed to :any:`Console.buffer`. + """ + return self._tiles.T if self._order == "F" else self._tiles + + @property + def buffer(self) -> np.ndarray: + """An array of this consoles raw tile data. + This acts as a combination of the `ch`, `fg`, and `bg` attributes. Colors include an alpha channel but how alpha works is currently undefined. Example: >>> con = tcod.console.Console(10, 2, order="F") - >>> con.tiles[0, 0] = ( + >>> con.buffer[0, 0] = ( ... ord("X"), ... (*tcod.white, 255), ... (*tcod.black, 255), ... ) - >>> con.tiles[0, 0] + >>> con.buffer[0, 0] (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) - .. versionadded:: 10.0 + .. versionadded:: 11.4 """ return self._tiles.T if self._order == "F" else self._tiles From 64f012e8414182abbb379b4754278165edf728e0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Sep 2019 16:42:11 -0700 Subject: [PATCH 0218/1101] Prepare 11.4.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b254c12..88a1c8c5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.4.0 - 2019-09-20 +------------------- Added - Added `__array_interface__` to the Image class. - Added `Console.draw_semigraphics` as a replacement for blit_2x functions. From 412720e522250c7d310effda84f5e24db692748f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 26 Sep 2019 22:24:48 -0700 Subject: [PATCH 0219/1101] Change Console methods to use buffer instead of tiles. Resolves deprecation warnings with Console.tiles. --- tcod/console.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 7dd393c4..19c50a27 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -298,7 +298,7 @@ def tiles2(self) -> np.ndarray: .. versionadded:: 11.3 """ - return self.tiles.view(self._DTYPE_RGB) + return self.buffer.view(self._DTYPE_RGB) @property def default_bg(self) -> Tuple[int, int, int]: @@ -885,7 +885,7 @@ def __repr__(self) -> str: return ( "tcod.console.Console(width=%i, height=%i, " "order=%r,buffer=\n%r)" - % (self.width, self.height, self._order, self.tiles) + % (self.width, self.height, self._order, self.buffer) ) def __str__(self) -> str: From 8c467e2824073d1b86eb91a6b5e47bf358b1fcfa Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 15 Oct 2019 11:18:47 -0700 Subject: [PATCH 0220/1101] Add Python 3.8 jobs to AppVeyor. --- appveyor.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 3fea39b8..8b6b010b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,10 @@ environment: platform: x64 - PYTHON: C:\Python37\python.exe platform: Any CPU + - WEB_PYTHON: "https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe" + platform: x64 + - WEB_PYTHON: "https://www.python.org/ftp/python/3.8.0/python-3.8.0-webinstall.exe" + platform: Any CPU - PYPY3: pypy3.5-v7.0.0 platform: Any CPU - PYPY3: pypy3.6-v7.1.1 From e6da9fc35d1874d644d31f33df45cfedc60b6684 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 15 Oct 2019 11:53:52 -0700 Subject: [PATCH 0221/1101] Add PyPy v7.2.0 and fix PyPy venv. --- .appveyor/install_python.ps1 | 5 +++-- .travis.yml | 4 ++++ appveyor.yml | 6 +----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index a8260765..ba661f35 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -1,4 +1,7 @@ +$env:ACTIVATE_VENV='venv\Scripts\activate.bat' + if ($env:PYPY -or $env:PYPY3) { + $env:ACTIVATE_VENV='venv\bin\activate.bat' if($env:PYPY3){ $env:PYPY_EXE='pypy3.exe' $env:PYPY=$env:PYPY3 @@ -23,6 +26,4 @@ if ($env:WEB_PYTHON) { & $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=16" & $env:PYTHON -m virtualenv venv -$env:ACTIVATE_VENV='venv\Scripts\activate.bat' - if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } diff --git a/.travis.yml b/.travis.yml index db39d8f4..996f2183 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,9 +31,13 @@ matrix: - os: osx language: generic env: PYPY3_VERSION=pypy3.6-v7.1.1 + - os: osx + language: generic + env: PYPY3_VERSION=pypy3.6-v7.2.0 allow_failures: - python: nightly - python: pypy3.5-7.0 + - env: PYPY3_VERSION=pypy3.6-v7.2.0 fast_finish: true diff --git a/appveyor.yml b/appveyor.yml index 8b6b010b..ca10c421 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,16 +23,12 @@ environment: platform: x64 - WEB_PYTHON: "https://www.python.org/ftp/python/3.8.0/python-3.8.0-webinstall.exe" platform: Any CPU - - PYPY3: pypy3.5-v7.0.0 - platform: Any CPU - - PYPY3: pypy3.6-v7.1.1 + - PYPY3: pypy3.6-v7.2.0 platform: Any CPU matrix: allow_failures: - DEPLOY_ONLY: true - - PYPY3: pypy3.5-v7.0.0 - - PYPY3: pypy3.6-v7.1.1 clone_depth: 50 init: From 3011a11a7a455dd23132d18a2d010a17895f8a44 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 15 Oct 2019 14:38:45 -0700 Subject: [PATCH 0222/1101] Prepare 11.4.1 release. --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 88a1c8c5..8b5a31de 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +11.4.1 - 2019-10-15 +------------------- +Added + - Uploaded Python 3.8 wheels to PyPI. + 11.4.0 - 2019-09-20 ------------------- Added From fec0cbc5c5dd2cee448a8dae1d4e1c7072a1d88d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 17 Oct 2019 01:12:34 -0700 Subject: [PATCH 0223/1101] Fix MyPy warnings. --- tcod/console.py | 16 ++++++++++++---- tcod/libtcodpy.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 19c50a27..7102a95a 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -351,11 +351,11 @@ def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: stacklevel=3, ) - def clear( # type: ignore + def clear( self, ch: int = ord(" "), - fg: Tuple[int, int, int] = ..., - bg: Tuple[int, int, int] = ..., + fg: Tuple[int, int, int] = ..., # type: ignore + bg: Tuple[int, int, int] = ..., # type: ignore ) -> None: """Reset all values in this console to a single value. @@ -761,7 +761,15 @@ def blit( dest, # type: ignore dest_x, dest_y, - ) = (dest, dest_x, dest_y, src_x, src_y, width, height) + ) = ( + dest, # type: ignore + dest_x, + dest_y, + src_x, + src_y, # type: ignore + width, + height, + ) warnings.warn( "Parameter names have been moved around, see documentation.", DeprecationWarning, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a15bd24f..d7c98db2 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -993,7 +993,7 @@ def console_set_custom_font( """ if not os.path.exists(fontFile): raise RuntimeError( - "File not found:\n\t%s" % (os.path.realpath(fontFile),) + "File not found:\n\t%s" % (str(os.path.realpath(fontFile)),) ) _check( lib.TCOD_console_set_custom_font( From 59cc73c024aa605142867cbdfa450fccc07c3422 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 17 Oct 2019 16:13:30 -0700 Subject: [PATCH 0224/1101] Add Python 3.8 classifier. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 44725ad1..bfed6358 100755 --- a/setup.py +++ b/setup.py @@ -146,6 +146,7 @@ def get_long_description(): "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", From e851a21e5dd3d43a8fcf1c5b9b8b245a8ec0e7b6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 17 Oct 2019 17:19:13 -0700 Subject: [PATCH 0225/1101] Enforce strict MyPy settings. --- setup.cfg | 16 ++++++++++++++++ tcod/console.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 30f09380..cf232986 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,5 +7,21 @@ addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl --capture=sys [flake8] ignore = E203 W503 +[mypy] +python_version = 3.5 +warn_unused_configs = True +disallow_subclassing_any = True +disallow_any_generics = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_return_any = True +implicit_reexport = False + [mypy-numpy] ignore_missing_imports = True diff --git a/tcod/console.py b/tcod/console.py index 7102a95a..5362d6c4 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -754,11 +754,11 @@ def blit( # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): ( - src_x, # type: ignore + src_x, src_y, width, height, - dest, # type: ignore + dest, dest_x, dest_y, ) = ( From 691bbfbd4d731687e8958fcf781fd51e028a8482 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 17 Oct 2019 17:24:51 -0700 Subject: [PATCH 0226/1101] Define __all__ to include libtcodpy names and constants. This is for static type checkers. --- tcod/__init__.py | 753 ++++++++++++++++++++++++++++++++++++++++++++++ tcod/event.py | 503 ++++++++++++++++++++++++++++++- tcod/libtcodpy.py | 752 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2006 insertions(+), 2 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 48fc7b6c..08624a79 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -22,3 +22,756 @@ from tcod.version import __version__ except ImportError: # Gets imported without version.py by ReadTheDocs __version__ = "" + +__all__ = [ # noqa: F405 + "__version__", + # --- From libtcodpy.py --- + "Color", + "Bsp", + "NB_FOV_ALGORITHMS", + "NOISE_DEFAULT_HURST", + "NOISE_DEFAULT_LACUNARITY", + "ConsoleBuffer", + "Dice", + "Key", + "Mouse", + "FOV_PERMISSIVE", + "BKGND_ALPHA", + "BKGND_ADDALPHA", + "bsp_new_with_size", + "bsp_split_once", + "bsp_split_recursive", + "bsp_resize", + "bsp_left", + "bsp_right", + "bsp_father", + "bsp_is_leaf", + "bsp_contains", + "bsp_find_node", + "bsp_traverse_pre_order", + "bsp_traverse_in_order", + "bsp_traverse_post_order", + "bsp_traverse_level_order", + "bsp_traverse_inverted_level_order", + "bsp_remove_sons", + "bsp_delete", + "color_lerp", + "color_set_hsv", + "color_get_hsv", + "color_scale_HSV", + "color_gen_map", + "console_init_root", + "console_set_custom_font", + "console_get_width", + "console_get_height", + "console_map_ascii_code_to_font", + "console_map_ascii_codes_to_font", + "console_map_string_to_font", + "console_is_fullscreen", + "console_set_fullscreen", + "console_is_window_closed", + "console_has_mouse_focus", + "console_is_active", + "console_set_window_title", + "console_credits", + "console_credits_reset", + "console_credits_render", + "console_flush", + "console_set_default_background", + "console_set_default_foreground", + "console_clear", + "console_put_char", + "console_put_char_ex", + "console_set_char_background", + "console_set_char_foreground", + "console_set_char", + "console_set_background_flag", + "console_get_background_flag", + "console_set_alignment", + "console_get_alignment", + "console_print", + "console_print_ex", + "console_print_rect", + "console_print_rect_ex", + "console_get_height_rect", + "console_rect", + "console_hline", + "console_vline", + "console_print_frame", + "console_set_color_control", + "console_get_default_background", + "console_get_default_foreground", + "console_get_char_background", + "console_get_char_foreground", + "console_get_char", + "console_set_fade", + "console_get_fade", + "console_get_fading_color", + "console_wait_for_keypress", + "console_check_for_keypress", + "console_is_key_pressed", + "console_new", + "console_from_file", + "console_blit", + "console_set_key_color", + "console_delete", + "console_fill_foreground", + "console_fill_background", + "console_fill_char", + "console_load_asc", + "console_save_asc", + "console_load_apf", + "console_save_apf", + "console_load_xp", + "console_save_xp", + "console_from_xp", + "console_list_load_xp", + "console_list_save_xp", + "path_new_using_map", + "path_new_using_function", + "path_compute", + "path_get_origin", + "path_get_destination", + "path_size", + "path_reverse", + "path_get", + "path_is_empty", + "path_walk", + "path_delete", + "dijkstra_new", + "dijkstra_new_using_function", + "dijkstra_compute", + "dijkstra_path_set", + "dijkstra_get_distance", + "dijkstra_size", + "dijkstra_reverse", + "dijkstra_get", + "dijkstra_is_empty", + "dijkstra_path_walk", + "dijkstra_delete", + "heightmap_new", + "heightmap_set_value", + "heightmap_add", + "heightmap_scale", + "heightmap_clear", + "heightmap_clamp", + "heightmap_copy", + "heightmap_normalize", + "heightmap_lerp_hm", + "heightmap_add_hm", + "heightmap_multiply_hm", + "heightmap_add_hill", + "heightmap_dig_hill", + "heightmap_rain_erosion", + "heightmap_kernel_transform", + "heightmap_add_voronoi", + "heightmap_add_fbm", + "heightmap_scale_fbm", + "heightmap_dig_bezier", + "heightmap_get_value", + "heightmap_get_interpolated_value", + "heightmap_get_slope", + "heightmap_get_normal", + "heightmap_count_cells", + "heightmap_has_land_on_border", + "heightmap_get_minmax", + "heightmap_delete", + "image_new", + "image_clear", + "image_invert", + "image_hflip", + "image_rotate90", + "image_vflip", + "image_scale", + "image_set_key_color", + "image_get_alpha", + "image_is_pixel_transparent", + "image_load", + "image_from_console", + "image_refresh_console", + "image_get_size", + "image_get_pixel", + "image_get_mipmap_pixel", + "image_put_pixel", + "image_blit", + "image_blit_rect", + "image_blit_2x", + "image_save", + "image_delete", + "line_init", + "line_step", + "line", + "line_iter", + "line_where", + "map_new", + "map_copy", + "map_set_properties", + "map_clear", + "map_compute_fov", + "map_is_in_fov", + "map_is_transparent", + "map_is_walkable", + "map_delete", + "map_get_width", + "map_get_height", + "mouse_show_cursor", + "mouse_is_cursor_visible", + "mouse_move", + "mouse_get_status", + "namegen_parse", + "namegen_generate", + "namegen_generate_custom", + "namegen_get_sets", + "namegen_destroy", + "noise_new", + "noise_set_type", + "noise_get", + "noise_get_fbm", + "noise_get_turbulence", + "noise_delete", + "parser_new", + "parser_new_struct", + "parser_run", + "parser_delete", + "parser_get_bool_property", + "parser_get_int_property", + "parser_get_char_property", + "parser_get_float_property", + "parser_get_string_property", + "parser_get_color_property", + "parser_get_dice_property", + "parser_get_list_property", + "random_get_instance", + "random_new", + "random_new_from_seed", + "random_set_distribution", + "random_get_int", + "random_get_float", + "random_get_double", + "random_get_int_mean", + "random_get_float_mean", + "random_get_double_mean", + "random_save", + "random_restore", + "random_delete", + "struct_add_flag", + "struct_add_property", + "struct_add_value_list", + "struct_add_list_property", + "struct_add_structure", + "struct_get_name", + "struct_is_mandatory", + "struct_get_type", + "sys_set_fps", + "sys_get_fps", + "sys_get_last_frame_length", + "sys_sleep_milli", + "sys_elapsed_milli", + "sys_elapsed_seconds", + "sys_set_renderer", + "sys_get_renderer", + "sys_save_screenshot", + "sys_force_fullscreen_resolution", + "sys_get_current_resolution", + "sys_get_char_size", + "sys_update_char", + "sys_register_SDL_renderer", + "sys_check_for_event", + "sys_wait_for_event", + "sys_clipboard_set", + "sys_clipboard_get", + # --- From constants.py --- + "FOV_BASIC", + "FOV_DIAMOND", + "FOV_PERMISSIVE_0", + "FOV_PERMISSIVE_1", + "FOV_PERMISSIVE_2", + "FOV_PERMISSIVE_3", + "FOV_PERMISSIVE_4", + "FOV_PERMISSIVE_5", + "FOV_PERMISSIVE_6", + "FOV_PERMISSIVE_7", + "FOV_PERMISSIVE_8", + "FOV_RESTRICTIVE", + "FOV_SHADOW", + "KEY_0", + "KEY_1", + "KEY_2", + "KEY_3", + "KEY_4", + "KEY_5", + "KEY_6", + "KEY_7", + "KEY_8", + "KEY_9", + "KEY_ALT", + "KEY_APPS", + "KEY_BACKSPACE", + "KEY_CAPSLOCK", + "KEY_CHAR", + "KEY_CONTROL", + "KEY_DELETE", + "KEY_DOWN", + "KEY_END", + "KEY_ENTER", + "KEY_ESCAPE", + "KEY_F1", + "KEY_F10", + "KEY_F11", + "KEY_F12", + "KEY_F2", + "KEY_F3", + "KEY_F4", + "KEY_F5", + "KEY_F6", + "KEY_F7", + "KEY_F8", + "KEY_F9", + "KEY_HOME", + "KEY_INSERT", + "KEY_KP0", + "KEY_KP1", + "KEY_KP2", + "KEY_KP3", + "KEY_KP4", + "KEY_KP5", + "KEY_KP6", + "KEY_KP7", + "KEY_KP8", + "KEY_KP9", + "KEY_KPADD", + "KEY_KPDEC", + "KEY_KPDIV", + "KEY_KPENTER", + "KEY_KPMUL", + "KEY_KPSUB", + "KEY_LEFT", + "KEY_LWIN", + "KEY_NONE", + "KEY_NUMLOCK", + "KEY_PAGEDOWN", + "KEY_PAGEUP", + "KEY_PAUSE", + "KEY_PRINTSCREEN", + "KEY_RIGHT", + "KEY_RWIN", + "KEY_SCROLLLOCK", + "KEY_SHIFT", + "KEY_SPACE", + "KEY_TAB", + "KEY_TEXT", + "KEY_UP", + "BKGND_ADD", + "BKGND_ADDA", + "BKGND_ALPH", + "BKGND_BURN", + "BKGND_COLOR_BURN", + "BKGND_COLOR_DODGE", + "BKGND_DARKEN", + "BKGND_DEFAULT", + "BKGND_LIGHTEN", + "BKGND_MULTIPLY", + "BKGND_NONE", + "BKGND_OVERLAY", + "BKGND_SCREEN", + "BKGND_SET", + "CENTER", + "CHAR_ARROW2_E", + "CHAR_ARROW2_N", + "CHAR_ARROW2_S", + "CHAR_ARROW2_W", + "CHAR_ARROW_E", + "CHAR_ARROW_N", + "CHAR_ARROW_S", + "CHAR_ARROW_W", + "CHAR_BLOCK1", + "CHAR_BLOCK2", + "CHAR_BLOCK3", + "CHAR_BULLET", + "CHAR_BULLET_INV", + "CHAR_BULLET_SQUARE", + "CHAR_CENT", + "CHAR_CHECKBOX_SET", + "CHAR_CHECKBOX_UNSET", + "CHAR_CLUB", + "CHAR_COPYRIGHT", + "CHAR_CROSS", + "CHAR_CURRENCY", + "CHAR_DARROW_H", + "CHAR_DARROW_V", + "CHAR_DCROSS", + "CHAR_DHLINE", + "CHAR_DIAMOND", + "CHAR_DIVISION", + "CHAR_DNE", + "CHAR_DNW", + "CHAR_DSE", + "CHAR_DSW", + "CHAR_DTEEE", + "CHAR_DTEEN", + "CHAR_DTEES", + "CHAR_DTEEW", + "CHAR_DVLINE", + "CHAR_EXCLAM_DOUBLE", + "CHAR_FEMALE", + "CHAR_FUNCTION", + "CHAR_GRADE", + "CHAR_HALF", + "CHAR_HEART", + "CHAR_HLINE", + "CHAR_LIGHT", + "CHAR_MALE", + "CHAR_MULTIPLICATION", + "CHAR_NE", + "CHAR_NOTE", + "CHAR_NOTE_DOUBLE", + "CHAR_NW", + "CHAR_ONE_QUARTER", + "CHAR_PILCROW", + "CHAR_POUND", + "CHAR_POW1", + "CHAR_POW2", + "CHAR_POW3", + "CHAR_RADIO_SET", + "CHAR_RADIO_UNSET", + "CHAR_RESERVED", + "CHAR_SE", + "CHAR_SECTION", + "CHAR_SMILIE", + "CHAR_SMILIE_INV", + "CHAR_SPADE", + "CHAR_SUBP_DIAG", + "CHAR_SUBP_E", + "CHAR_SUBP_N", + "CHAR_SUBP_NE", + "CHAR_SUBP_NW", + "CHAR_SUBP_SE", + "CHAR_SUBP_SW", + "CHAR_SW", + "CHAR_TEEE", + "CHAR_TEEN", + "CHAR_TEES", + "CHAR_TEEW", + "CHAR_THREE_QUARTERS", + "CHAR_UMLAUT", + "CHAR_VLINE", + "CHAR_YEN", + "COLCTRL_1", + "COLCTRL_2", + "COLCTRL_3", + "COLCTRL_4", + "COLCTRL_5", + "COLCTRL_BACK_RGB", + "COLCTRL_FORE_RGB", + "COLCTRL_NUMBER", + "COLCTRL_STOP", + "COLOR_AMBER", + "COLOR_AZURE", + "COLOR_BLUE", + "COLOR_CHARTREUSE", + "COLOR_CRIMSON", + "COLOR_CYAN", + "COLOR_DARK", + "COLOR_DARKER", + "COLOR_DARKEST", + "COLOR_DESATURATED", + "COLOR_FLAME", + "COLOR_FUCHSIA", + "COLOR_GREEN", + "COLOR_HAN", + "COLOR_LEVELS", + "COLOR_LIGHT", + "COLOR_LIGHTER", + "COLOR_LIGHTEST", + "COLOR_LIME", + "COLOR_MAGENTA", + "COLOR_NB", + "COLOR_NORMAL", + "COLOR_ORANGE", + "COLOR_PINK", + "COLOR_PURPLE", + "COLOR_RED", + "COLOR_SEA", + "COLOR_SKY", + "COLOR_TURQUOISE", + "COLOR_VIOLET", + "COLOR_YELLOW", + "DISTRIBUTION_GAUSSIAN", + "DISTRIBUTION_GAUSSIAN_INVERSE", + "DISTRIBUTION_GAUSSIAN_RANGE", + "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE", + "DISTRIBUTION_LINEAR", + "EVENT_ANY", + "EVENT_FINGER", + "EVENT_FINGER_MOVE", + "EVENT_FINGER_PRESS", + "EVENT_FINGER_RELEASE", + "EVENT_KEY", + "EVENT_KEY_PRESS", + "EVENT_KEY_RELEASE", + "EVENT_MOUSE", + "EVENT_MOUSE_MOVE", + "EVENT_MOUSE_PRESS", + "EVENT_MOUSE_RELEASE", + "EVENT_NONE", + "FONT_LAYOUT_ASCII_INCOL", + "FONT_LAYOUT_ASCII_INROW", + "FONT_LAYOUT_CP437", + "FONT_LAYOUT_TCOD", + "FONT_TYPE_GRAYSCALE", + "FONT_TYPE_GREYSCALE", + "KEY_PRESSED", + "KEY_RELEASED", + "LEFT", + "NB_RENDERERS", + "NOISE_DEFAULT", + "NOISE_PERLIN", + "NOISE_SIMPLEX", + "NOISE_WAVELET", + "RENDERER_GLSL", + "RENDERER_OPENGL", + "RENDERER_OPENGL2", + "RENDERER_SDL", + "RENDERER_SDL2", + "RIGHT", + "RNG_CMWC", + "RNG_MT", + "TYPE_BOOL", + "TYPE_CHAR", + "TYPE_COLOR", + "TYPE_CUSTOM00", + "TYPE_CUSTOM01", + "TYPE_CUSTOM02", + "TYPE_CUSTOM03", + "TYPE_CUSTOM04", + "TYPE_CUSTOM05", + "TYPE_CUSTOM06", + "TYPE_CUSTOM07", + "TYPE_CUSTOM08", + "TYPE_CUSTOM09", + "TYPE_CUSTOM10", + "TYPE_CUSTOM11", + "TYPE_CUSTOM12", + "TYPE_CUSTOM13", + "TYPE_CUSTOM14", + "TYPE_CUSTOM15", + "TYPE_DICE", + "TYPE_FLOAT", + "TYPE_INT", + "TYPE_LIST", + "TYPE_NONE", + "TYPE_STRING", + "TYPE_VALUELIST00", + "TYPE_VALUELIST01", + "TYPE_VALUELIST02", + "TYPE_VALUELIST03", + "TYPE_VALUELIST04", + "TYPE_VALUELIST05", + "TYPE_VALUELIST06", + "TYPE_VALUELIST07", + "TYPE_VALUELIST08", + "TYPE_VALUELIST09", + "TYPE_VALUELIST10", + "TYPE_VALUELIST11", + "TYPE_VALUELIST12", + "TYPE_VALUELIST13", + "TYPE_VALUELIST14", + "TYPE_VALUELIST15", + "amber", + "azure", + "black", + "blue", + "brass", + "celadon", + "chartreuse", + "copper", + "crimson", + "cyan", + "dark_amber", + "dark_azure", + "dark_blue", + "dark_chartreuse", + "dark_crimson", + "dark_cyan", + "dark_flame", + "dark_fuchsia", + "dark_gray", + "dark_green", + "dark_grey", + "dark_han", + "dark_lime", + "dark_magenta", + "dark_orange", + "dark_pink", + "dark_purple", + "dark_red", + "dark_sea", + "dark_sepia", + "dark_sky", + "dark_turquoise", + "dark_violet", + "dark_yellow", + "darker_amber", + "darker_azure", + "darker_blue", + "darker_chartreuse", + "darker_crimson", + "darker_cyan", + "darker_flame", + "darker_fuchsia", + "darker_gray", + "darker_green", + "darker_grey", + "darker_han", + "darker_lime", + "darker_magenta", + "darker_orange", + "darker_pink", + "darker_purple", + "darker_red", + "darker_sea", + "darker_sepia", + "darker_sky", + "darker_turquoise", + "darker_violet", + "darker_yellow", + "darkest_amber", + "darkest_azure", + "darkest_blue", + "darkest_chartreuse", + "darkest_crimson", + "darkest_cyan", + "darkest_flame", + "darkest_fuchsia", + "darkest_gray", + "darkest_green", + "darkest_grey", + "darkest_han", + "darkest_lime", + "darkest_magenta", + "darkest_orange", + "darkest_pink", + "darkest_purple", + "darkest_red", + "darkest_sea", + "darkest_sepia", + "darkest_sky", + "darkest_turquoise", + "darkest_violet", + "darkest_yellow", + "desaturated_amber", + "desaturated_azure", + "desaturated_blue", + "desaturated_chartreuse", + "desaturated_crimson", + "desaturated_cyan", + "desaturated_flame", + "desaturated_fuchsia", + "desaturated_green", + "desaturated_han", + "desaturated_lime", + "desaturated_magenta", + "desaturated_orange", + "desaturated_pink", + "desaturated_purple", + "desaturated_red", + "desaturated_sea", + "desaturated_sky", + "desaturated_turquoise", + "desaturated_violet", + "desaturated_yellow", + "flame", + "fuchsia", + "gold", + "gray", + "green", + "grey", + "han", + "light_amber", + "light_azure", + "light_blue", + "light_chartreuse", + "light_crimson", + "light_cyan", + "light_flame", + "light_fuchsia", + "light_gray", + "light_green", + "light_grey", + "light_han", + "light_lime", + "light_magenta", + "light_orange", + "light_pink", + "light_purple", + "light_red", + "light_sea", + "light_sepia", + "light_sky", + "light_turquoise", + "light_violet", + "light_yellow", + "lighter_amber", + "lighter_azure", + "lighter_blue", + "lighter_chartreuse", + "lighter_crimson", + "lighter_cyan", + "lighter_flame", + "lighter_fuchsia", + "lighter_gray", + "lighter_green", + "lighter_grey", + "lighter_han", + "lighter_lime", + "lighter_magenta", + "lighter_orange", + "lighter_pink", + "lighter_purple", + "lighter_red", + "lighter_sea", + "lighter_sepia", + "lighter_sky", + "lighter_turquoise", + "lighter_violet", + "lighter_yellow", + "lightest_amber", + "lightest_azure", + "lightest_blue", + "lightest_chartreuse", + "lightest_crimson", + "lightest_cyan", + "lightest_flame", + "lightest_fuchsia", + "lightest_gray", + "lightest_green", + "lightest_grey", + "lightest_han", + "lightest_lime", + "lightest_magenta", + "lightest_orange", + "lightest_pink", + "lightest_purple", + "lightest_red", + "lightest_sea", + "lightest_sepia", + "lightest_sky", + "lightest_turquoise", + "lightest_violet", + "lightest_yellow", + "lime", + "magenta", + "orange", + "peach", + "pink", + "purple", + "red", + "sea", + "sepia", + "silver", + "sky", + "turquoise", + "violet", + "white", + "yellow", +] diff --git a/tcod/event.py b/tcod/event.py index aaf9d034..f21b144b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -857,7 +857,7 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: return 0 -__all__ = [ +__all__ = [ # noqa: F405 "Point", "BUTTON_LEFT", "BUTTON_MIDDLE", @@ -887,4 +887,503 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: "get", "wait", "EventDispatch", -] + tcod.event_constants.__all__ + # --- From event_constants.py --- + "SCANCODE_UNKNOWN", + "SCANCODE_A", + "SCANCODE_B", + "SCANCODE_C", + "SCANCODE_D", + "SCANCODE_E", + "SCANCODE_F", + "SCANCODE_G", + "SCANCODE_H", + "SCANCODE_I", + "SCANCODE_J", + "SCANCODE_K", + "SCANCODE_L", + "SCANCODE_M", + "SCANCODE_N", + "SCANCODE_O", + "SCANCODE_P", + "SCANCODE_Q", + "SCANCODE_R", + "SCANCODE_S", + "SCANCODE_T", + "SCANCODE_U", + "SCANCODE_V", + "SCANCODE_W", + "SCANCODE_X", + "SCANCODE_Y", + "SCANCODE_Z", + "SCANCODE_1", + "SCANCODE_2", + "SCANCODE_3", + "SCANCODE_4", + "SCANCODE_5", + "SCANCODE_6", + "SCANCODE_7", + "SCANCODE_8", + "SCANCODE_9", + "SCANCODE_0", + "SCANCODE_RETURN", + "SCANCODE_ESCAPE", + "SCANCODE_BACKSPACE", + "SCANCODE_TAB", + "SCANCODE_SPACE", + "SCANCODE_MINUS", + "SCANCODE_EQUALS", + "SCANCODE_LEFTBRACKET", + "SCANCODE_RIGHTBRACKET", + "SCANCODE_BACKSLASH", + "SCANCODE_NONUSHASH", + "SCANCODE_SEMICOLON", + "SCANCODE_APOSTROPHE", + "SCANCODE_GRAVE", + "SCANCODE_COMMA", + "SCANCODE_PERIOD", + "SCANCODE_SLASH", + "SCANCODE_CAPSLOCK", + "SCANCODE_F1", + "SCANCODE_F2", + "SCANCODE_F3", + "SCANCODE_F4", + "SCANCODE_F5", + "SCANCODE_F6", + "SCANCODE_F7", + "SCANCODE_F8", + "SCANCODE_F9", + "SCANCODE_F10", + "SCANCODE_F11", + "SCANCODE_F12", + "SCANCODE_PRINTSCREEN", + "SCANCODE_SCROLLLOCK", + "SCANCODE_PAUSE", + "SCANCODE_INSERT", + "SCANCODE_HOME", + "SCANCODE_PAGEUP", + "SCANCODE_DELETE", + "SCANCODE_END", + "SCANCODE_PAGEDOWN", + "SCANCODE_RIGHT", + "SCANCODE_LEFT", + "SCANCODE_DOWN", + "SCANCODE_UP", + "SCANCODE_NUMLOCKCLEAR", + "SCANCODE_KP_DIVIDE", + "SCANCODE_KP_MULTIPLY", + "SCANCODE_KP_MINUS", + "SCANCODE_KP_PLUS", + "SCANCODE_KP_ENTER", + "SCANCODE_KP_1", + "SCANCODE_KP_2", + "SCANCODE_KP_3", + "SCANCODE_KP_4", + "SCANCODE_KP_5", + "SCANCODE_KP_6", + "SCANCODE_KP_7", + "SCANCODE_KP_8", + "SCANCODE_KP_9", + "SCANCODE_KP_0", + "SCANCODE_KP_PERIOD", + "SCANCODE_NONUSBACKSLASH", + "SCANCODE_APPLICATION", + "SCANCODE_POWER", + "SCANCODE_KP_EQUALS", + "SCANCODE_F13", + "SCANCODE_F14", + "SCANCODE_F15", + "SCANCODE_F16", + "SCANCODE_F17", + "SCANCODE_F18", + "SCANCODE_F19", + "SCANCODE_F20", + "SCANCODE_F21", + "SCANCODE_F22", + "SCANCODE_F23", + "SCANCODE_F24", + "SCANCODE_EXECUTE", + "SCANCODE_HELP", + "SCANCODE_MENU", + "SCANCODE_SELECT", + "SCANCODE_STOP", + "SCANCODE_AGAIN", + "SCANCODE_UNDO", + "SCANCODE_CUT", + "SCANCODE_COPY", + "SCANCODE_PASTE", + "SCANCODE_FIND", + "SCANCODE_MUTE", + "SCANCODE_VOLUMEUP", + "SCANCODE_VOLUMEDOWN", + "SCANCODE_KP_COMMA", + "SCANCODE_KP_EQUALSAS400", + "SCANCODE_INTERNATIONAL1", + "SCANCODE_INTERNATIONAL2", + "SCANCODE_INTERNATIONAL3", + "SCANCODE_INTERNATIONAL4", + "SCANCODE_INTERNATIONAL5", + "SCANCODE_INTERNATIONAL6", + "SCANCODE_INTERNATIONAL7", + "SCANCODE_INTERNATIONAL8", + "SCANCODE_INTERNATIONAL9", + "SCANCODE_LANG1", + "SCANCODE_LANG2", + "SCANCODE_LANG3", + "SCANCODE_LANG4", + "SCANCODE_LANG5", + "SCANCODE_LANG6", + "SCANCODE_LANG7", + "SCANCODE_LANG8", + "SCANCODE_LANG9", + "SCANCODE_ALTERASE", + "SCANCODE_SYSREQ", + "SCANCODE_CANCEL", + "SCANCODE_CLEAR", + "SCANCODE_PRIOR", + "SCANCODE_RETURN2", + "SCANCODE_SEPARATOR", + "SCANCODE_OUT", + "SCANCODE_OPER", + "SCANCODE_CLEARAGAIN", + "SCANCODE_CRSEL", + "SCANCODE_EXSEL", + "SCANCODE_KP_00", + "SCANCODE_KP_000", + "SCANCODE_THOUSANDSSEPARATOR", + "SCANCODE_DECIMALSEPARATOR", + "SCANCODE_CURRENCYUNIT", + "SCANCODE_CURRENCYSUBUNIT", + "SCANCODE_KP_LEFTPAREN", + "SCANCODE_KP_RIGHTPAREN", + "SCANCODE_KP_LEFTBRACE", + "SCANCODE_KP_RIGHTBRACE", + "SCANCODE_KP_TAB", + "SCANCODE_KP_BACKSPACE", + "SCANCODE_KP_A", + "SCANCODE_KP_B", + "SCANCODE_KP_C", + "SCANCODE_KP_D", + "SCANCODE_KP_E", + "SCANCODE_KP_F", + "SCANCODE_KP_XOR", + "SCANCODE_KP_POWER", + "SCANCODE_KP_PERCENT", + "SCANCODE_KP_LESS", + "SCANCODE_KP_GREATER", + "SCANCODE_KP_AMPERSAND", + "SCANCODE_KP_DBLAMPERSAND", + "SCANCODE_KP_VERTICALBAR", + "SCANCODE_KP_DBLVERTICALBAR", + "SCANCODE_KP_COLON", + "SCANCODE_KP_HASH", + "SCANCODE_KP_SPACE", + "SCANCODE_KP_AT", + "SCANCODE_KP_EXCLAM", + "SCANCODE_KP_MEMSTORE", + "SCANCODE_KP_MEMRECALL", + "SCANCODE_KP_MEMCLEAR", + "SCANCODE_KP_MEMADD", + "SCANCODE_KP_MEMSUBTRACT", + "SCANCODE_KP_MEMMULTIPLY", + "SCANCODE_KP_MEMDIVIDE", + "SCANCODE_KP_PLUSMINUS", + "SCANCODE_KP_CLEAR", + "SCANCODE_KP_CLEARENTRY", + "SCANCODE_KP_BINARY", + "SCANCODE_KP_OCTAL", + "SCANCODE_KP_DECIMAL", + "SCANCODE_KP_HEXADECIMAL", + "SCANCODE_LCTRL", + "SCANCODE_LSHIFT", + "SCANCODE_LALT", + "SCANCODE_LGUI", + "SCANCODE_RCTRL", + "SCANCODE_RSHIFT", + "SCANCODE_RALT", + "SCANCODE_RGUI", + "SCANCODE_MODE", + "SCANCODE_AUDIONEXT", + "SCANCODE_AUDIOPREV", + "SCANCODE_AUDIOSTOP", + "SCANCODE_AUDIOPLAY", + "SCANCODE_AUDIOMUTE", + "SCANCODE_MEDIASELECT", + "SCANCODE_WWW", + "SCANCODE_MAIL", + "SCANCODE_CALCULATOR", + "SCANCODE_COMPUTER", + "SCANCODE_AC_SEARCH", + "SCANCODE_AC_HOME", + "SCANCODE_AC_BACK", + "SCANCODE_AC_FORWARD", + "SCANCODE_AC_STOP", + "SCANCODE_AC_REFRESH", + "SCANCODE_AC_BOOKMARKS", + "SCANCODE_BRIGHTNESSDOWN", + "SCANCODE_BRIGHTNESSUP", + "SCANCODE_DISPLAYSWITCH", + "SCANCODE_KBDILLUMTOGGLE", + "SCANCODE_KBDILLUMDOWN", + "SCANCODE_KBDILLUMUP", + "SCANCODE_EJECT", + "SCANCODE_SLEEP", + "SCANCODE_APP1", + "SCANCODE_APP2", + "K_UNKNOWN", + "K_BACKSPACE", + "K_TAB", + "K_RETURN", + "K_ESCAPE", + "K_SPACE", + "K_EXCLAIM", + "K_QUOTEDBL", + "K_HASH", + "K_DOLLAR", + "K_PERCENT", + "K_AMPERSAND", + "K_QUOTE", + "K_LEFTPAREN", + "K_RIGHTPAREN", + "K_ASTERISK", + "K_PLUS", + "K_COMMA", + "K_MINUS", + "K_PERIOD", + "K_SLASH", + "K_0", + "K_1", + "K_2", + "K_3", + "K_4", + "K_5", + "K_6", + "K_7", + "K_8", + "K_9", + "K_COLON", + "K_SEMICOLON", + "K_LESS", + "K_EQUALS", + "K_GREATER", + "K_QUESTION", + "K_AT", + "K_LEFTBRACKET", + "K_BACKSLASH", + "K_RIGHTBRACKET", + "K_CARET", + "K_UNDERSCORE", + "K_BACKQUOTE", + "K_a", + "K_b", + "K_c", + "K_d", + "K_e", + "K_f", + "K_g", + "K_h", + "K_i", + "K_j", + "K_k", + "K_l", + "K_m", + "K_n", + "K_o", + "K_p", + "K_q", + "K_r", + "K_s", + "K_t", + "K_u", + "K_v", + "K_w", + "K_x", + "K_y", + "K_z", + "K_DELETE", + "K_SCANCODE_MASK", + "K_CAPSLOCK", + "K_F1", + "K_F2", + "K_F3", + "K_F4", + "K_F5", + "K_F6", + "K_F7", + "K_F8", + "K_F9", + "K_F10", + "K_F11", + "K_F12", + "K_PRINTSCREEN", + "K_SCROLLLOCK", + "K_PAUSE", + "K_INSERT", + "K_HOME", + "K_PAGEUP", + "K_END", + "K_PAGEDOWN", + "K_RIGHT", + "K_LEFT", + "K_DOWN", + "K_UP", + "K_NUMLOCKCLEAR", + "K_KP_DIVIDE", + "K_KP_MULTIPLY", + "K_KP_MINUS", + "K_KP_PLUS", + "K_KP_ENTER", + "K_KP_1", + "K_KP_2", + "K_KP_3", + "K_KP_4", + "K_KP_5", + "K_KP_6", + "K_KP_7", + "K_KP_8", + "K_KP_9", + "K_KP_0", + "K_KP_PERIOD", + "K_APPLICATION", + "K_POWER", + "K_KP_EQUALS", + "K_F13", + "K_F14", + "K_F15", + "K_F16", + "K_F17", + "K_F18", + "K_F19", + "K_F20", + "K_F21", + "K_F22", + "K_F23", + "K_F24", + "K_EXECUTE", + "K_HELP", + "K_MENU", + "K_SELECT", + "K_STOP", + "K_AGAIN", + "K_UNDO", + "K_CUT", + "K_COPY", + "K_PASTE", + "K_FIND", + "K_MUTE", + "K_VOLUMEUP", + "K_VOLUMEDOWN", + "K_KP_COMMA", + "K_KP_EQUALSAS400", + "K_ALTERASE", + "K_SYSREQ", + "K_CANCEL", + "K_CLEAR", + "K_PRIOR", + "K_RETURN2", + "K_SEPARATOR", + "K_OUT", + "K_OPER", + "K_CLEARAGAIN", + "K_CRSEL", + "K_EXSEL", + "K_KP_00", + "K_KP_000", + "K_THOUSANDSSEPARATOR", + "K_DECIMALSEPARATOR", + "K_CURRENCYUNIT", + "K_CURRENCYSUBUNIT", + "K_KP_LEFTPAREN", + "K_KP_RIGHTPAREN", + "K_KP_LEFTBRACE", + "K_KP_RIGHTBRACE", + "K_KP_TAB", + "K_KP_BACKSPACE", + "K_KP_A", + "K_KP_B", + "K_KP_C", + "K_KP_D", + "K_KP_E", + "K_KP_F", + "K_KP_XOR", + "K_KP_POWER", + "K_KP_PERCENT", + "K_KP_LESS", + "K_KP_GREATER", + "K_KP_AMPERSAND", + "K_KP_DBLAMPERSAND", + "K_KP_VERTICALBAR", + "K_KP_DBLVERTICALBAR", + "K_KP_COLON", + "K_KP_HASH", + "K_KP_SPACE", + "K_KP_AT", + "K_KP_EXCLAM", + "K_KP_MEMSTORE", + "K_KP_MEMRECALL", + "K_KP_MEMCLEAR", + "K_KP_MEMADD", + "K_KP_MEMSUBTRACT", + "K_KP_MEMMULTIPLY", + "K_KP_MEMDIVIDE", + "K_KP_PLUSMINUS", + "K_KP_CLEAR", + "K_KP_CLEARENTRY", + "K_KP_BINARY", + "K_KP_OCTAL", + "K_KP_DECIMAL", + "K_KP_HEXADECIMAL", + "K_LCTRL", + "K_LSHIFT", + "K_LALT", + "K_LGUI", + "K_RCTRL", + "K_RSHIFT", + "K_RALT", + "K_RGUI", + "K_MODE", + "K_AUDIONEXT", + "K_AUDIOPREV", + "K_AUDIOSTOP", + "K_AUDIOPLAY", + "K_AUDIOMUTE", + "K_MEDIASELECT", + "K_WWW", + "K_MAIL", + "K_CALCULATOR", + "K_COMPUTER", + "K_AC_SEARCH", + "K_AC_HOME", + "K_AC_BACK", + "K_AC_FORWARD", + "K_AC_STOP", + "K_AC_REFRESH", + "K_AC_BOOKMARKS", + "K_BRIGHTNESSDOWN", + "K_BRIGHTNESSUP", + "K_DISPLAYSWITCH", + "K_KBDILLUMTOGGLE", + "K_KBDILLUMDOWN", + "K_KBDILLUMUP", + "K_EJECT", + "K_SLEEP", + "KMOD_NONE", + "KMOD_LSHIFT", + "KMOD_RSHIFT", + "KMOD_SHIFT", + "KMOD_LCTRL", + "KMOD_RCTRL", + "KMOD_CTRL", + "KMOD_LALT", + "KMOD_RALT", + "KMOD_ALT", + "KMOD_LGUI", + "KMOD_RGUI", + "KMOD_GUI", + "KMOD_NUM", + "KMOD_CAPS", + "KMOD_MODE", + "KMOD_RESERVED", + "MOUSEWHEEL_NORMAL", + "MOUSEWHEEL_FLIPPED", + "MOUSEWHEEL", +] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d7c98db2..df1fc196 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4218,3 +4218,755 @@ def _atexit_verify() -> None: stacklevel=2, ) lib.TCOD_console_delete(ffi.NULL) + + +__all__ = [ + "Color", + "Bsp", + "NB_FOV_ALGORITHMS", + "NOISE_DEFAULT_HURST", + "NOISE_DEFAULT_LACUNARITY", + "ConsoleBuffer", + "Dice", + "Key", + "Mouse", + "FOV_PERMISSIVE", + "BKGND_ALPHA", + "BKGND_ADDALPHA", + "bsp_new_with_size", + "bsp_split_once", + "bsp_split_recursive", + "bsp_resize", + "bsp_left", + "bsp_right", + "bsp_father", + "bsp_is_leaf", + "bsp_contains", + "bsp_find_node", + "bsp_traverse_pre_order", + "bsp_traverse_in_order", + "bsp_traverse_post_order", + "bsp_traverse_level_order", + "bsp_traverse_inverted_level_order", + "bsp_remove_sons", + "bsp_delete", + "color_lerp", + "color_set_hsv", + "color_get_hsv", + "color_scale_HSV", + "color_gen_map", + "console_init_root", + "console_set_custom_font", + "console_get_width", + "console_get_height", + "console_map_ascii_code_to_font", + "console_map_ascii_codes_to_font", + "console_map_string_to_font", + "console_is_fullscreen", + "console_set_fullscreen", + "console_is_window_closed", + "console_has_mouse_focus", + "console_is_active", + "console_set_window_title", + "console_credits", + "console_credits_reset", + "console_credits_render", + "console_flush", + "console_set_default_background", + "console_set_default_foreground", + "console_clear", + "console_put_char", + "console_put_char_ex", + "console_set_char_background", + "console_set_char_foreground", + "console_set_char", + "console_set_background_flag", + "console_get_background_flag", + "console_set_alignment", + "console_get_alignment", + "console_print", + "console_print_ex", + "console_print_rect", + "console_print_rect_ex", + "console_get_height_rect", + "console_rect", + "console_hline", + "console_vline", + "console_print_frame", + "console_set_color_control", + "console_get_default_background", + "console_get_default_foreground", + "console_get_char_background", + "console_get_char_foreground", + "console_get_char", + "console_set_fade", + "console_get_fade", + "console_get_fading_color", + "console_wait_for_keypress", + "console_check_for_keypress", + "console_is_key_pressed", + "console_new", + "console_from_file", + "console_blit", + "console_set_key_color", + "console_delete", + "console_fill_foreground", + "console_fill_background", + "console_fill_char", + "console_load_asc", + "console_save_asc", + "console_load_apf", + "console_save_apf", + "console_load_xp", + "console_save_xp", + "console_from_xp", + "console_list_load_xp", + "console_list_save_xp", + "path_new_using_map", + "path_new_using_function", + "path_compute", + "path_get_origin", + "path_get_destination", + "path_size", + "path_reverse", + "path_get", + "path_is_empty", + "path_walk", + "path_delete", + "dijkstra_new", + "dijkstra_new_using_function", + "dijkstra_compute", + "dijkstra_path_set", + "dijkstra_get_distance", + "dijkstra_size", + "dijkstra_reverse", + "dijkstra_get", + "dijkstra_is_empty", + "dijkstra_path_walk", + "dijkstra_delete", + "heightmap_new", + "heightmap_set_value", + "heightmap_add", + "heightmap_scale", + "heightmap_clear", + "heightmap_clamp", + "heightmap_copy", + "heightmap_normalize", + "heightmap_lerp_hm", + "heightmap_add_hm", + "heightmap_multiply_hm", + "heightmap_add_hill", + "heightmap_dig_hill", + "heightmap_rain_erosion", + "heightmap_kernel_transform", + "heightmap_add_voronoi", + "heightmap_add_fbm", + "heightmap_scale_fbm", + "heightmap_dig_bezier", + "heightmap_get_value", + "heightmap_get_interpolated_value", + "heightmap_get_slope", + "heightmap_get_normal", + "heightmap_count_cells", + "heightmap_has_land_on_border", + "heightmap_get_minmax", + "heightmap_delete", + "image_new", + "image_clear", + "image_invert", + "image_hflip", + "image_rotate90", + "image_vflip", + "image_scale", + "image_set_key_color", + "image_get_alpha", + "image_is_pixel_transparent", + "image_load", + "image_from_console", + "image_refresh_console", + "image_get_size", + "image_get_pixel", + "image_get_mipmap_pixel", + "image_put_pixel", + "image_blit", + "image_blit_rect", + "image_blit_2x", + "image_save", + "image_delete", + "line_init", + "line_step", + "line", + "line_iter", + "line_where", + "map_new", + "map_copy", + "map_set_properties", + "map_clear", + "map_compute_fov", + "map_is_in_fov", + "map_is_transparent", + "map_is_walkable", + "map_delete", + "map_get_width", + "map_get_height", + "mouse_show_cursor", + "mouse_is_cursor_visible", + "mouse_move", + "mouse_get_status", + "namegen_parse", + "namegen_generate", + "namegen_generate_custom", + "namegen_get_sets", + "namegen_destroy", + "noise_new", + "noise_set_type", + "noise_get", + "noise_get_fbm", + "noise_get_turbulence", + "noise_delete", + "parser_new", + "parser_new_struct", + "parser_run", + "parser_delete", + "parser_get_bool_property", + "parser_get_int_property", + "parser_get_char_property", + "parser_get_float_property", + "parser_get_string_property", + "parser_get_color_property", + "parser_get_dice_property", + "parser_get_list_property", + "random_get_instance", + "random_new", + "random_new_from_seed", + "random_set_distribution", + "random_get_int", + "random_get_float", + "random_get_double", + "random_get_int_mean", + "random_get_float_mean", + "random_get_double_mean", + "random_save", + "random_restore", + "random_delete", + "struct_add_flag", + "struct_add_property", + "struct_add_value_list", + "struct_add_list_property", + "struct_add_structure", + "struct_get_name", + "struct_is_mandatory", + "struct_get_type", + "sys_set_fps", + "sys_get_fps", + "sys_get_last_frame_length", + "sys_sleep_milli", + "sys_elapsed_milli", + "sys_elapsed_seconds", + "sys_set_renderer", + "sys_get_renderer", + "sys_save_screenshot", + "sys_force_fullscreen_resolution", + "sys_get_current_resolution", + "sys_get_char_size", + "sys_update_char", + "sys_register_SDL_renderer", + "sys_check_for_event", + "sys_wait_for_event", + "sys_clipboard_set", + "sys_clipboard_get", + # --- From constants.py --- + "FOV_BASIC", + "FOV_DIAMOND", + "FOV_PERMISSIVE_0", + "FOV_PERMISSIVE_1", + "FOV_PERMISSIVE_2", + "FOV_PERMISSIVE_3", + "FOV_PERMISSIVE_4", + "FOV_PERMISSIVE_5", + "FOV_PERMISSIVE_6", + "FOV_PERMISSIVE_7", + "FOV_PERMISSIVE_8", + "FOV_RESTRICTIVE", + "FOV_SHADOW", + "KEY_0", + "KEY_1", + "KEY_2", + "KEY_3", + "KEY_4", + "KEY_5", + "KEY_6", + "KEY_7", + "KEY_8", + "KEY_9", + "KEY_ALT", + "KEY_APPS", + "KEY_BACKSPACE", + "KEY_CAPSLOCK", + "KEY_CHAR", + "KEY_CONTROL", + "KEY_DELETE", + "KEY_DOWN", + "KEY_END", + "KEY_ENTER", + "KEY_ESCAPE", + "KEY_F1", + "KEY_F10", + "KEY_F11", + "KEY_F12", + "KEY_F2", + "KEY_F3", + "KEY_F4", + "KEY_F5", + "KEY_F6", + "KEY_F7", + "KEY_F8", + "KEY_F9", + "KEY_HOME", + "KEY_INSERT", + "KEY_KP0", + "KEY_KP1", + "KEY_KP2", + "KEY_KP3", + "KEY_KP4", + "KEY_KP5", + "KEY_KP6", + "KEY_KP7", + "KEY_KP8", + "KEY_KP9", + "KEY_KPADD", + "KEY_KPDEC", + "KEY_KPDIV", + "KEY_KPENTER", + "KEY_KPMUL", + "KEY_KPSUB", + "KEY_LEFT", + "KEY_LWIN", + "KEY_NONE", + "KEY_NUMLOCK", + "KEY_PAGEDOWN", + "KEY_PAGEUP", + "KEY_PAUSE", + "KEY_PRINTSCREEN", + "KEY_RIGHT", + "KEY_RWIN", + "KEY_SCROLLLOCK", + "KEY_SHIFT", + "KEY_SPACE", + "KEY_TAB", + "KEY_TEXT", + "KEY_UP", + "BKGND_ADD", + "BKGND_ADDA", + "BKGND_ALPH", + "BKGND_BURN", + "BKGND_COLOR_BURN", + "BKGND_COLOR_DODGE", + "BKGND_DARKEN", + "BKGND_DEFAULT", + "BKGND_LIGHTEN", + "BKGND_MULTIPLY", + "BKGND_NONE", + "BKGND_OVERLAY", + "BKGND_SCREEN", + "BKGND_SET", + "CENTER", + "CHAR_ARROW2_E", + "CHAR_ARROW2_N", + "CHAR_ARROW2_S", + "CHAR_ARROW2_W", + "CHAR_ARROW_E", + "CHAR_ARROW_N", + "CHAR_ARROW_S", + "CHAR_ARROW_W", + "CHAR_BLOCK1", + "CHAR_BLOCK2", + "CHAR_BLOCK3", + "CHAR_BULLET", + "CHAR_BULLET_INV", + "CHAR_BULLET_SQUARE", + "CHAR_CENT", + "CHAR_CHECKBOX_SET", + "CHAR_CHECKBOX_UNSET", + "CHAR_CLUB", + "CHAR_COPYRIGHT", + "CHAR_CROSS", + "CHAR_CURRENCY", + "CHAR_DARROW_H", + "CHAR_DARROW_V", + "CHAR_DCROSS", + "CHAR_DHLINE", + "CHAR_DIAMOND", + "CHAR_DIVISION", + "CHAR_DNE", + "CHAR_DNW", + "CHAR_DSE", + "CHAR_DSW", + "CHAR_DTEEE", + "CHAR_DTEEN", + "CHAR_DTEES", + "CHAR_DTEEW", + "CHAR_DVLINE", + "CHAR_EXCLAM_DOUBLE", + "CHAR_FEMALE", + "CHAR_FUNCTION", + "CHAR_GRADE", + "CHAR_HALF", + "CHAR_HEART", + "CHAR_HLINE", + "CHAR_LIGHT", + "CHAR_MALE", + "CHAR_MULTIPLICATION", + "CHAR_NE", + "CHAR_NOTE", + "CHAR_NOTE_DOUBLE", + "CHAR_NW", + "CHAR_ONE_QUARTER", + "CHAR_PILCROW", + "CHAR_POUND", + "CHAR_POW1", + "CHAR_POW2", + "CHAR_POW3", + "CHAR_RADIO_SET", + "CHAR_RADIO_UNSET", + "CHAR_RESERVED", + "CHAR_SE", + "CHAR_SECTION", + "CHAR_SMILIE", + "CHAR_SMILIE_INV", + "CHAR_SPADE", + "CHAR_SUBP_DIAG", + "CHAR_SUBP_E", + "CHAR_SUBP_N", + "CHAR_SUBP_NE", + "CHAR_SUBP_NW", + "CHAR_SUBP_SE", + "CHAR_SUBP_SW", + "CHAR_SW", + "CHAR_TEEE", + "CHAR_TEEN", + "CHAR_TEES", + "CHAR_TEEW", + "CHAR_THREE_QUARTERS", + "CHAR_UMLAUT", + "CHAR_VLINE", + "CHAR_YEN", + "COLCTRL_1", + "COLCTRL_2", + "COLCTRL_3", + "COLCTRL_4", + "COLCTRL_5", + "COLCTRL_BACK_RGB", + "COLCTRL_FORE_RGB", + "COLCTRL_NUMBER", + "COLCTRL_STOP", + "COLOR_AMBER", + "COLOR_AZURE", + "COLOR_BLUE", + "COLOR_CHARTREUSE", + "COLOR_CRIMSON", + "COLOR_CYAN", + "COLOR_DARK", + "COLOR_DARKER", + "COLOR_DARKEST", + "COLOR_DESATURATED", + "COLOR_FLAME", + "COLOR_FUCHSIA", + "COLOR_GREEN", + "COLOR_HAN", + "COLOR_LEVELS", + "COLOR_LIGHT", + "COLOR_LIGHTER", + "COLOR_LIGHTEST", + "COLOR_LIME", + "COLOR_MAGENTA", + "COLOR_NB", + "COLOR_NORMAL", + "COLOR_ORANGE", + "COLOR_PINK", + "COLOR_PURPLE", + "COLOR_RED", + "COLOR_SEA", + "COLOR_SKY", + "COLOR_TURQUOISE", + "COLOR_VIOLET", + "COLOR_YELLOW", + "DISTRIBUTION_GAUSSIAN", + "DISTRIBUTION_GAUSSIAN_INVERSE", + "DISTRIBUTION_GAUSSIAN_RANGE", + "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE", + "DISTRIBUTION_LINEAR", + "EVENT_ANY", + "EVENT_FINGER", + "EVENT_FINGER_MOVE", + "EVENT_FINGER_PRESS", + "EVENT_FINGER_RELEASE", + "EVENT_KEY", + "EVENT_KEY_PRESS", + "EVENT_KEY_RELEASE", + "EVENT_MOUSE", + "EVENT_MOUSE_MOVE", + "EVENT_MOUSE_PRESS", + "EVENT_MOUSE_RELEASE", + "EVENT_NONE", + "FONT_LAYOUT_ASCII_INCOL", + "FONT_LAYOUT_ASCII_INROW", + "FONT_LAYOUT_CP437", + "FONT_LAYOUT_TCOD", + "FONT_TYPE_GRAYSCALE", + "FONT_TYPE_GREYSCALE", + "KEY_PRESSED", + "KEY_RELEASED", + "LEFT", + "NB_RENDERERS", + "NOISE_DEFAULT", + "NOISE_PERLIN", + "NOISE_SIMPLEX", + "NOISE_WAVELET", + "RENDERER_GLSL", + "RENDERER_OPENGL", + "RENDERER_OPENGL2", + "RENDERER_SDL", + "RENDERER_SDL2", + "RIGHT", + "RNG_CMWC", + "RNG_MT", + "TYPE_BOOL", + "TYPE_CHAR", + "TYPE_COLOR", + "TYPE_CUSTOM00", + "TYPE_CUSTOM01", + "TYPE_CUSTOM02", + "TYPE_CUSTOM03", + "TYPE_CUSTOM04", + "TYPE_CUSTOM05", + "TYPE_CUSTOM06", + "TYPE_CUSTOM07", + "TYPE_CUSTOM08", + "TYPE_CUSTOM09", + "TYPE_CUSTOM10", + "TYPE_CUSTOM11", + "TYPE_CUSTOM12", + "TYPE_CUSTOM13", + "TYPE_CUSTOM14", + "TYPE_CUSTOM15", + "TYPE_DICE", + "TYPE_FLOAT", + "TYPE_INT", + "TYPE_LIST", + "TYPE_NONE", + "TYPE_STRING", + "TYPE_VALUELIST00", + "TYPE_VALUELIST01", + "TYPE_VALUELIST02", + "TYPE_VALUELIST03", + "TYPE_VALUELIST04", + "TYPE_VALUELIST05", + "TYPE_VALUELIST06", + "TYPE_VALUELIST07", + "TYPE_VALUELIST08", + "TYPE_VALUELIST09", + "TYPE_VALUELIST10", + "TYPE_VALUELIST11", + "TYPE_VALUELIST12", + "TYPE_VALUELIST13", + "TYPE_VALUELIST14", + "TYPE_VALUELIST15", + "amber", + "azure", + "black", + "blue", + "brass", + "celadon", + "chartreuse", + "copper", + "crimson", + "cyan", + "dark_amber", + "dark_azure", + "dark_blue", + "dark_chartreuse", + "dark_crimson", + "dark_cyan", + "dark_flame", + "dark_fuchsia", + "dark_gray", + "dark_green", + "dark_grey", + "dark_han", + "dark_lime", + "dark_magenta", + "dark_orange", + "dark_pink", + "dark_purple", + "dark_red", + "dark_sea", + "dark_sepia", + "dark_sky", + "dark_turquoise", + "dark_violet", + "dark_yellow", + "darker_amber", + "darker_azure", + "darker_blue", + "darker_chartreuse", + "darker_crimson", + "darker_cyan", + "darker_flame", + "darker_fuchsia", + "darker_gray", + "darker_green", + "darker_grey", + "darker_han", + "darker_lime", + "darker_magenta", + "darker_orange", + "darker_pink", + "darker_purple", + "darker_red", + "darker_sea", + "darker_sepia", + "darker_sky", + "darker_turquoise", + "darker_violet", + "darker_yellow", + "darkest_amber", + "darkest_azure", + "darkest_blue", + "darkest_chartreuse", + "darkest_crimson", + "darkest_cyan", + "darkest_flame", + "darkest_fuchsia", + "darkest_gray", + "darkest_green", + "darkest_grey", + "darkest_han", + "darkest_lime", + "darkest_magenta", + "darkest_orange", + "darkest_pink", + "darkest_purple", + "darkest_red", + "darkest_sea", + "darkest_sepia", + "darkest_sky", + "darkest_turquoise", + "darkest_violet", + "darkest_yellow", + "desaturated_amber", + "desaturated_azure", + "desaturated_blue", + "desaturated_chartreuse", + "desaturated_crimson", + "desaturated_cyan", + "desaturated_flame", + "desaturated_fuchsia", + "desaturated_green", + "desaturated_han", + "desaturated_lime", + "desaturated_magenta", + "desaturated_orange", + "desaturated_pink", + "desaturated_purple", + "desaturated_red", + "desaturated_sea", + "desaturated_sky", + "desaturated_turquoise", + "desaturated_violet", + "desaturated_yellow", + "flame", + "fuchsia", + "gold", + "gray", + "green", + "grey", + "han", + "light_amber", + "light_azure", + "light_blue", + "light_chartreuse", + "light_crimson", + "light_cyan", + "light_flame", + "light_fuchsia", + "light_gray", + "light_green", + "light_grey", + "light_han", + "light_lime", + "light_magenta", + "light_orange", + "light_pink", + "light_purple", + "light_red", + "light_sea", + "light_sepia", + "light_sky", + "light_turquoise", + "light_violet", + "light_yellow", + "lighter_amber", + "lighter_azure", + "lighter_blue", + "lighter_chartreuse", + "lighter_crimson", + "lighter_cyan", + "lighter_flame", + "lighter_fuchsia", + "lighter_gray", + "lighter_green", + "lighter_grey", + "lighter_han", + "lighter_lime", + "lighter_magenta", + "lighter_orange", + "lighter_pink", + "lighter_purple", + "lighter_red", + "lighter_sea", + "lighter_sepia", + "lighter_sky", + "lighter_turquoise", + "lighter_violet", + "lighter_yellow", + "lightest_amber", + "lightest_azure", + "lightest_blue", + "lightest_chartreuse", + "lightest_crimson", + "lightest_cyan", + "lightest_flame", + "lightest_fuchsia", + "lightest_gray", + "lightest_green", + "lightest_grey", + "lightest_han", + "lightest_lime", + "lightest_magenta", + "lightest_orange", + "lightest_pink", + "lightest_purple", + "lightest_red", + "lightest_sea", + "lightest_sepia", + "lightest_sky", + "lightest_turquoise", + "lightest_violet", + "lightest_yellow", + "lime", + "magenta", + "orange", + "peach", + "pink", + "purple", + "red", + "sea", + "sepia", + "silver", + "sky", + "turquoise", + "violet", + "white", + "yellow", +] From 480916f460508e6aa865573c5b962b352ee54208 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 18 Oct 2019 19:38:46 -0700 Subject: [PATCH 0227/1101] Minor update of Console.tiles2 docs. --- tcod/console.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tcod/console.py b/tcod/console.py index 5362d6c4..51fe60b5 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -287,6 +287,9 @@ def buffer(self) -> np.ndarray: def tiles2(self) -> np.ndarray: """An array of this consoles tile data without the alpha channel. + The dtype of this array is effectively: + ``[("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")]`` + Example: >>> con = tcod.console.Console(10, 2, order="F") >>> con.tiles2[0, 0] = ord("@"), tcod.yellow, tcod.black From 0e254143ad1c4184888b555ea70b22511b1bca03 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 19 Oct 2019 00:37:57 -0700 Subject: [PATCH 0228/1101] Simplify corner color interpolation in samples. --- examples/samples_tcod.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 23973314..397baa85 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -86,10 +86,6 @@ def __init__(self): ) # corner indexes self.corners = np.array([0, 1, 2, 3]) - # sample screen interpolation mesh-grid - self.interp_x, self.interp_y = np.mgrid[ - 0 : 1 : SAMPLE_SCREEN_WIDTH * 1j, 0 : 1 : SAMPLE_SCREEN_HEIGHT * 1j - ] def on_enter(self): tcod.sys_set_fps(0) @@ -116,14 +112,9 @@ def slide_corner_colors(self): def interpolate_corner_colors(self): # interpolate corner colors across the sample console - for i in range(3): # for each color channel - left = ( - self.colors[2, i] - self.colors[0, i] - ) * self.interp_y + self.colors[0, i] - right = ( - self.colors[3, i] - self.colors[1, i] - ) * self.interp_y + self.colors[1, i] - sample_console.bg[:, :, i] = (right - left) * self.interp_x + left + left = np.linspace(self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT) + right = np.linspace(self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT) + sample_console.bg[:] = np.linspace(left, right, SAMPLE_SCREEN_WIDTH) def darken_background_characters(self): # darken background characters From 889ffa812a04d965cf4e281e73aa8bd34d57d490 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 Nov 2019 22:34:49 -0800 Subject: [PATCH 0229/1101] Update libtcod. --- CHANGELOG.rst | 6 ++++++ libtcod | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b5a31de..f2a871b9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Changed + - Quarter block elements are now rendered using Unicode instead of a custom + encoding. + +Fixed + - `OPENGL` and `GLSL` renderers were not properly clearing space characters. 11.4.1 - 2019-10-15 ------------------- diff --git a/libtcod b/libtcod index c342f614..4763b45d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit c342f61472682b1ca1de838ac52525528d9e1707 +Subproject commit 4763b45d997a397137772159262321a180e19773 From c21fb3caec4b70cc2c604bf11ca37fe7b1556ccc Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 Nov 2019 22:35:46 -0800 Subject: [PATCH 0230/1101] Prepare 11.5.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2a871b9..ded68f1e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.5.0 - 2019-11-22 +------------------- Changed - Quarter block elements are now rendered using Unicode instead of a custom encoding. From 14fa5b268fd23bc4d2b8f7d126f9d37614759e5b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 Nov 2019 23:34:55 -0800 Subject: [PATCH 0231/1101] CI: Fix Python 3.8 AppVeyor jobs. --- appveyor.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ca10c421..57f3eff9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,10 +19,9 @@ environment: platform: x64 - PYTHON: C:\Python37\python.exe platform: Any CPU - - WEB_PYTHON: "https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64-webinstall.exe" + - PYTHON: C:\Python38-x64\python.exe platform: x64 - - WEB_PYTHON: "https://www.python.org/ftp/python/3.8.0/python-3.8.0-webinstall.exe" - platform: Any CPU + - PYTHON: C:\Python38\python.exe - PYPY3: pypy3.6-v7.2.0 platform: Any CPU From fd32727e5ea057d2b5f249d62110b838aba6a8cd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 23 Nov 2019 00:42:49 -0800 Subject: [PATCH 0232/1101] Prepare 11.5.1 release. --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ded68f1e..f0163395 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +11.5.1 - 2019-11-23 +------------------- +Fixed + - Python 3.8 wheels failed to deploy. + 11.5.0 - 2019-11-22 ------------------- Changed From 7da1b1a3b941f6b6d6be759c2a5206fb8d2f8852 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 23 Nov 2019 01:09:35 -0800 Subject: [PATCH 0233/1101] CI: Add missing platform option. --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 57f3eff9..204e6166 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,6 +22,7 @@ environment: - PYTHON: C:\Python38-x64\python.exe platform: x64 - PYTHON: C:\Python38\python.exe + platform: Any CPU - PYPY3: pypy3.6-v7.2.0 platform: Any CPU From 7a149a06da2c154bffe0cf06c0af8b680328d004 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 27 Nov 2019 01:15:05 -0800 Subject: [PATCH 0234/1101] Remove bad documentation. This parameter did not actually exist. --- tcod/path.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 2aaac423..e0ee53d8 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -377,8 +377,6 @@ def dijkstra2d( directions. A value of None or 0 will disable those directions. Typical values could be: ``1, None``, ``1, 1``, ``2, 3``, etc. - `out` is the output array and can be the same as `distance`. - Example: >>> import numpy as np From 6fb12e96c4d6d3928cebc2ebec12d6f1179093a5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 27 Nov 2019 17:13:00 -0800 Subject: [PATCH 0235/1101] Clean up doctest example. Use tolist to make the result look cleaner. --- tcod/path.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index e0ee53d8..8681acc3 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -404,13 +404,13 @@ def dijkstra2d( [2, 1], [1, 0], [0, 0]], dtype=int32) - >>> path = list(path[::-1]) + >>> path = path[::-1].tolist() >>> while path: ... print(path.pop(0)) - [0 0] - [1 0] - [2 1] - [2 2] + [0, 0] + [1, 0] + [2, 1] + [2, 2] .. versionadded:: 11.2 """ From 8f5f382f9340f163c497da1000bf7a4050d26a31 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 27 Nov 2019 17:13:54 -0800 Subject: [PATCH 0236/1101] Delete unused image code. This class is no longer needed now that Image supports np.asarray. --- tcod/image.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tcod/image.py b/tcod/image.py index 5fd07ea7..4728c73f 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -7,31 +7,6 @@ from tcod._internal import _console -class _ImageBufferArray(np.ndarray): # type: ignore - def __new__(cls, image: Any) -> "_ImageBufferArray": - size = image.height * image.width - self = np.frombuffer( - ffi.buffer(lib.TCOD_image_get_colors()[size]), np.uint8 - ) - self = self.reshape((image.height, image.width, 3)).view(cls) - self._image_c = image.cdata - return self # type: ignore - - def __array_finalize__(self, obj: Any) -> None: - if obj is None: - return - self._image_c = getattr(obj, "_image_c", None) - - def __repr__(self) -> str: - return repr(self.view(np.ndarray)) - - def __setitem__(self, index: Any, value: Any) -> None: - """Must invalidate mipmaps on any write.""" - np.ndarray.__setitem__(self, index, value) - if self._image_c is not None: - lib.TCOD_image_invalidate_mipmaps(self._image_c) - - class Image(object): """ Args: From 179df94de50aa4663fdb79b6941b0f87ec6f4933 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 5 Dec 2019 18:06:59 -0800 Subject: [PATCH 0237/1101] Update libtcod to add alpha blending support. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- tcod/console.py | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f0163395..bbe8270c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - Console blit operations now perform per-cell alpha transparency. 11.5.1 - 2019-11-23 ------------------- diff --git a/libtcod b/libtcod index 4763b45d..35738de7 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 4763b45d997a397137772159262321a180e19773 +Subproject commit 35738de7c9c1928f67b03992f6e3188c12d16813 diff --git a/tcod/console.py b/tcod/console.py index 51fe60b5..1ba2af95 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -753,6 +753,11 @@ def blit( Previously they were: `(x, y, width, height, dest, dest_x, dest_y, *)` + + .. versionchanged:: 11.6 + Now supports per-cell alpha transparency. + + Use :any:`Console.buffer` to set tile alpha before blit. """ # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): From 9da9a45c58a6f42ebb57834408483c08d2bff96d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 5 Dec 2019 18:39:09 -0800 Subject: [PATCH 0238/1101] Prepare 11.6.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbe8270c..489d0ec9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.6.0 - 2019-12-05 +------------------- Changed - Console blit operations now perform per-cell alpha transparency. From f731767dc5aa4029e4adc65a41e2f619375a2eeb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 12 Dec 2019 19:05:55 -0800 Subject: [PATCH 0239/1101] Switch back to collecting libtcod sources dynamically. --- build_libtcod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 478054b4..ac24b320 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -99,8 +99,8 @@ def unpack_sdl2(version): sources += walk_sources("tcod/") sources += walk_sources("tdl/") -sources += ["libtcod/src/libtcod_c.c"] -sources += ["libtcod/src/libtcod.cpp"] +sources += walk_sources("libtcod/src/libtcod/") +sources += ["libtcod/src/vendor/stb.c"] sources += ["libtcod/src/vendor/glad.c"] sources += ["libtcod/src/vendor/lodepng.cpp"] sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] From 9190084fecdc564160b9eddb3bcbbd86003ea293 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 13 Dec 2019 17:35:33 -0800 Subject: [PATCH 0240/1101] Handle warnings in sample scripts. --- examples/samples_libtcodpy.py | 11 ++++++++--- examples/samples_tcod.py | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 49fa1d2d..2a50d3f9 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -6,18 +6,23 @@ # from __future__ import division -import math +import sys import os +import math +import warnings + import tcod as libtcod -# Import Psyco if available -try: +try: # Import Psyco if available import psyco psyco.full() except ImportError: pass +if not sys.warnoptions: + warnings.simplefilter("ignore") # Prevent flood of deprecation warnings. + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 397baa85..6fca7525 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -5,18 +5,22 @@ # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ - +import sys import os import copy import math import random import time +import warnings import numpy as np import tcod import tcod.event +if not sys.warnoptions: + warnings.simplefilter("default") # Show all warnings. + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 From 506f6c2a4f023b52cc964d20a2638644dc16fe7a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 30 Dec 2019 19:04:03 -0800 Subject: [PATCH 0241/1101] Add get_mouse_state to __all__. Not having this set up prevented the function from showing up in the documentation. --- tcod/event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tcod/event.py b/tcod/event.py index f21b144b..37655a61 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -886,6 +886,7 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: "Undefined", "get", "wait", + "get_mouse_state", "EventDispatch", # --- From event_constants.py --- "SCANCODE_UNKNOWN", From f4139f6016d73b0091f9c7603e9be9761060b181 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 30 Dec 2019 20:53:49 -0800 Subject: [PATCH 0242/1101] Remove Coveralls scripts. Fixes recently broken CI jobs. --- .travis.yml | 3 +-- README.rst | 5 +---- appveyor.yml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 996f2183..56a303b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,8 +79,7 @@ after_success: - if [[ -n "$TRAVIS_TAG" ]]; then pip install --upgrade twine; fi - if [[ -n "$TRAVIS_TAG" && -z "$PYPY3_VERSION" ]]; then pip install --upgrade pyOpenSSL; fi - if [[ -n "$TRAVIS_TAG" && "$TRAVIS_OS_NAME" == "osx" ]]; then twine upload --skip-existing dist/*; fi -- pip install codacy-coverage python-coveralls codecov +- pip install codacy-coverage codecov - codecov -- coveralls - coverage xml - if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml || true; fi diff --git a/README.rst b/README.rst index 8149d726..9a511d25 100755 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ ======== |VersionsBadge| |ImplementationBadge| |LicenseBadge| -|PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy| +|PyPI| |RTD| |Appveyor| |Travis| |Codecov| |Codacy| |Requires| |Pyup| @@ -93,9 +93,6 @@ python-tcod is distributed under the `Simplified 2-clause FreeBSD license .. |Travis| image:: https://travis-ci.org/libtcod/python-tcod.svg?branch=master :target: https://travis-ci.org/libtcod/python-tcod -.. |Coveralls| image:: https://coveralls.io/repos/github/HexDecimal/python-tdl/badge.svg?branch=master - :target: https://coveralls.io/github/HexDecimal/python-tdl?branch=master - .. |Codecov| image:: https://codecov.io/gh/libtcod/python-tcod/branch/master/graph/badge.svg :target: https://codecov.io/gh/libtcod/python-tcod diff --git a/appveyor.yml b/appveyor.yml index 204e6166..653fbf65 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,7 +52,7 @@ test_script: - ps: "pytest -v" on_success: -- pip install codacy-coverage python-coveralls +- pip install codacy-coverage - coverage xml - if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml || cd . From 0b2a31216a1fb1b09aca6514d91455dbbdfc734b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 30 Dec 2019 21:34:53 -0800 Subject: [PATCH 0243/1101] Update PyPy jobs. Add 7.3.0 and remove an older failing version. --- .travis.yml | 6 +++--- appveyor.yml | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 56a303b7..1cb03fec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,15 +25,15 @@ matrix: - os: osx language: generic env: MB_PYTHON_VERSION=3.7.1 - - os: osx - language: generic - env: PYPY3_VERSION=pypy3.5-v7.0.0 - os: osx language: generic env: PYPY3_VERSION=pypy3.6-v7.1.1 - os: osx language: generic env: PYPY3_VERSION=pypy3.6-v7.2.0 + - os: osx + language: generic + env: PYPY3_VERSION=pypy3.6-v7.3.0 allow_failures: - python: nightly - python: pypy3.5-7.0 diff --git a/appveyor.yml b/appveyor.yml index 653fbf65..cea1e5a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,8 @@ environment: platform: Any CPU - PYPY3: pypy3.6-v7.2.0 platform: Any CPU + - PYPY3: pypy3.6-v7.3.0 + platform: Any CPU matrix: allow_failures: From 887c9658a3c99e050c6ca2162b91b7530011dd53 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 13:43:08 -0800 Subject: [PATCH 0244/1101] Update libtcod to 1.16.0-alpha.1 --- CHANGELOG.rst | 12 +++++++ build_libtcod.py | 2 +- libtcod | 2 +- tcod/cffi.h | 2 +- tcod/console.py | 20 ++++++------ tcod/path.cpp | 2 +- tcod/tcod.cpp | 84 ++---------------------------------------------- tcod/tcod.h | 57 -------------------------------- 8 files changed, 28 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 489d0ec9..c9b3730e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,18 @@ v2.0.0 Unreleased ------------------ +Changed + - Using libtcod 1.16.0-alpha.1; + - Console blit operations now perform per-cell alpha transparency. + - When a renderer fails to load it will now fallback to a different one. + The order is: OPENGL2 -> OPENGL -> SDL2. + +Deprecated + - The use of `libtcod.cfg` or `terminal.png` is deprecated. + - `tcod.sys_update_char` now works with the newer renderers. + - Fixed buffer overflow in name generator. + - `tcod.image_from_console` now works with the newer renderers. + - New renderers now auto-load fonts from `libtcod.cfg` or `terminal.png`. 11.6.0 - 2019-12-05 ------------------- diff --git a/build_libtcod.py b/build_libtcod.py index ac24b320..88df5e2b 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -102,7 +102,7 @@ def unpack_sdl2(version): sources += walk_sources("libtcod/src/libtcod/") sources += ["libtcod/src/vendor/stb.c"] sources += ["libtcod/src/vendor/glad.c"] -sources += ["libtcod/src/vendor/lodepng.cpp"] +sources += ["libtcod/src/vendor/lodepng.c"] sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] sources += glob.glob("libtcod/src/vendor/zlib/*.c") diff --git a/libtcod b/libtcod index 35738de7..2d54a7f5 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 35738de7c9c1928f67b03992f6e3188c12d16813 +Subproject commit 2d54a7f5bbb82726dc04895c4ebb68585c61cfee diff --git a/tcod/cffi.h b/tcod/cffi.h index c53dc8ed..98db5972 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -3,7 +3,7 @@ #include "../libtcod/src/libtcod/libtcod.h" #include "../libtcod/src/libtcod/libtcod_int.h" #include "../libtcod/src/libtcod/wrappers.h" -#include "../libtcod/src/libtcod/tileset/truetype.h" +#include "../libtcod/src/libtcod/tileset_truetype.h" #include "noise.h" #include "path.h" diff --git a/tcod/console.py b/tcod/console.py index 1ba2af95..694e37ed 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -579,8 +579,8 @@ def get_height_rect( """ string_ = string.encode("utf-8") return int( - lib.get_height_rect( - self.console_c, x, y, width, height, string_, len(string_) + lib.TCOD_console_get_height_rect_n( + self.console_c, x, y, width, height, len(string_), string_ ) ) @@ -960,12 +960,12 @@ def print( """ x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes - lib.console_print( + lib.TCOD_console_printn( self.console_c, x, y, - string_, len(string_), + string_, (fg,) if fg is not None else ffi.NULL, (bg,) if bg is not None else ffi.NULL, bg_blend, @@ -1016,14 +1016,14 @@ def print_box( x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes return int( - lib.print_rect( + lib.TCOD_console_printn_rect( self.console_c, x, y, width, height, - string_, len(string_), + string_, (fg,) if fg is not None else ffi.NULL, (bg,) if bg is not None else ffi.NULL, bg_blend, @@ -1070,14 +1070,14 @@ def draw_frame( """ x, y = self._pythonic_index(x, y) title_ = title.encode("utf-8") # type: bytes - lib.print_frame( + lib.TCOD_console_printn_frame( self.console_c, x, y, width, height, - title_, len(title_), + title_, (fg,) if fg is not None else ffi.NULL, (bg,) if bg is not None else ffi.NULL, bg_blend, @@ -1120,7 +1120,7 @@ def draw_rect( `fg` and `bg` now default to `None` instead of white-on-black. """ x, y = self._pythonic_index(x, y) - lib.draw_rect( + lib.TCOD_console_draw_rect_rgb( self.console_c, x, y, @@ -1159,4 +1159,4 @@ def get_height_rect(width: int, string: str) -> int: .. versionadded:: 9.2 """ string_ = string.encode("utf-8") # type: bytes - return int(lib.get_height_rect2(width, string_, len(string_))) + return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) diff --git a/tcod/path.cpp b/tcod/path.cpp index 4f6ea071..47bb38f7 100644 --- a/tcod/path.cpp +++ b/tcod/path.cpp @@ -4,7 +4,7 @@ #include #include -#include "../libtcod/src/libtcod/engine/error.h" +#include "../libtcod/src/libtcod/error.h" #include "../libtcod/src/libtcod/pathfinding/generic.h" #include "../libtcod/src/libtcod/pathfinding/dijkstra.h" #include "../libtcod/src/libtcod/pathfinding/hill-climb.h" diff --git a/tcod/tcod.cpp b/tcod/tcod.cpp index b2be7a09..5eb2d93d 100644 --- a/tcod/tcod.cpp +++ b/tcod/tcod.cpp @@ -4,8 +4,8 @@ #include "tcod.h" #include "../libtcod/src/libtcod/bresenham.h" -#include "../libtcod/src/libtcod/console/drawing.h" -#include "../libtcod/src/libtcod/console/printing.h" +#include "../libtcod/src/libtcod/console_drawing.h" +#include "../libtcod/src/libtcod/console_printing.h" /** * Write a Bresenham line to the `x_out` and `y_out` arrays. * @@ -23,83 +23,3 @@ int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out) { while (!TCOD_line_step_mt(++x_out, ++y_out, &bresenham)) {} return 0; } - -void draw_rect( - TCOD_Console* console, - int x, - int y, - int width, - int height, - int ch, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag) -{ - tcod::console::draw_rect(console, x, y, width, height, ch, fg, bg, flag); -} -void console_print( - TCOD_Console* console, - int x, - int y, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - TCOD_alignment_t alignment) -{ - tcod::console::print(console, x, y, std::string(str, str_n), - fg, bg, flag, alignment); -} -int print_rect( - TCOD_Console* console, - int x, - int y, - int width, - int height, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - TCOD_alignment_t alignment) -{ - return tcod::console::print_rect(console, x, y, width, height, - std::string(str, str_n), fg, bg, - flag, alignment); -} -int get_height_rect( - TCOD_Console* console, - int x, - int y, - int width, - int height, - const char* str, - int str_n) -{ - return tcod::console::get_height_rect(console, x, y, width, height, - std::string(str, str_n)); -} -int get_height_rect2( - int width, - const char* str, - int str_n) -{ - return tcod::console::get_height_rect(width, std::string(str, str_n)); -} -void print_frame( - TCOD_Console* console, - int x, - int y, - int width, - int height, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - bool empty) -{ - tcod::console::print_frame(console, x, y, width, height, - std::string(str, str_n), fg, bg, flag, empty); -} diff --git a/tcod/tcod.h b/tcod/tcod.h index 4946a8c9..062642c1 100644 --- a/tcod/tcod.h +++ b/tcod/tcod.h @@ -9,63 +9,6 @@ extern "C" { #endif int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out); - -void draw_rect( - TCOD_Console* console, - int x, - int y, - int width, - int height, - int ch, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag); -void console_print( - TCOD_Console* console, - int x, - int y, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - TCOD_alignment_t alignment); -int print_rect( - TCOD_Console *con, - int x, - int y, - int width, - int height, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - TCOD_alignment_t alignment); -int get_height_rect( - TCOD_Console *con, - int x, - int y, - int width, - int height, - const char* str, - int str_n); -int get_height_rect2( - int width, - const char* str, - int str_n); -void print_frame( - TCOD_Console *con, - int x, - int y, - int width, - int height, - const char* str, - int str_n, - const TCOD_color_t* fg, - const TCOD_color_t* bg, - TCOD_bkgnd_flag_t flag, - bool empty); #ifdef __cplusplus } // extern "C" #endif From 7c118ebee2721f9135e3c7235c200f4343858f06 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 13:49:57 -0800 Subject: [PATCH 0245/1101] Remove experimental rendering examples. These were not as useful as I would have hoped, but future versions of libtcod might make it easier to have more control over rendering. --- examples/experimental/custrender.py | 253 --------------------- examples/experimental/resizable_console.py | 60 ----- 2 files changed, 313 deletions(-) delete mode 100755 examples/experimental/custrender.py delete mode 100755 examples/experimental/resizable_console.py diff --git a/examples/experimental/custrender.py b/examples/experimental/custrender.py deleted file mode 100755 index 44ed33da..00000000 --- a/examples/experimental/custrender.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -# To the extent possible under law, the libtcod maintainers have waived all -# copyright and related or neighboring rights for this script. This work is -# published from: United States. -# https://creativecommons.org/publicdomain/zero/1.0/ -"""A custom rendering engine for python-tcod. - -This module bypasses tcod.console_flush to manually adjust where the console -will be rendered. It uses the FFI to call libtcod and SDL2 functions directly. - -It can be extend to allow arbitrary rendering on top of the SDL renderer. - -It is also designed to be copied into your own project and imported as a -module. -""" -import os -import sys - -import math -from typing import Any, Optional, Tuple - -import tcod -import tcod.tileset -import tcod.event - -assert tcod.__version__ > "10.1.1", tcod.__version__ - - -class LibtcodScope: - """When this context is exited, libtcod shuts down.""" - - def __enter__(self) -> None: - return None - - def __exit__(self, type: Any, value: Any, traceback: Any) -> None: - tcod.lib.TCOD_console_delete(tcod.ffi.NULL) - - -def init_sdl2( - width: int, - height: int, - title: Optional[str] = None, - window_flags: int = 0, - renderer_flags: int = 0, -) -> LibtcodScope: - """Setup libtcod to use a customized SDL2 display. - - `width` and `height` are the pixel resolution to use. - - `title` is the window title bar text, if None the title will be the name - of the running script. - - `window_flags` is a bit-field of SDL2 window flags such as - `tcod.lib.SDL_WINDOW_RESIZABLE`. See the SDL2 docs for more flags: - https://wiki.libsdl.org/SDL_WindowFlags - - `renderer_flags` is a bit-field of SDL2 renderer flags such as - `tcod.lib.SDL_RENDERER_PRESENTVSYNC`. See the SDL2 docs for more flags: - https://wiki.libsdl.org/SDL_RendererFlags - Target texture support will always be requested regardless of the given - flags. - - This function should be used with the `with` statement so that the display - will properly exit when your script exits. - """ - if title is None: - title = os.path.basename(sys.argv[0]) - error = tcod.lib.TCOD_sys_init_sdl2_renderer_( - width, height, title.encode("utf-8"), window_flags, renderer_flags - ) - if error < 0: - raise RuntimeError(tcod.ffi.string(tcod.lib.TCOD_get_error()).decode()) - return LibtcodScope() - - -def get_renderer_size() -> Tuple[int, int]: - """Return the renderer output size as a (width, height) tuple.""" - sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() - assert sdl_renderer - renderer_size = tcod.ffi.new("int[2]") - tcod.lib.SDL_GetRendererOutputSize( - sdl_renderer, renderer_size, renderer_size + 1 - ) - return renderer_size[0], renderer_size[1] - - -def get_viewport( - console: tcod.console.Console, - correct_aspect: bool = False, - integer_scale: bool = False, -) -> Tuple[int, int, int, int]: - """Return a viewport which follows the given constants. - - `console` is a Console object, it is used as reference for what the correct - aspect should be. The default tileset from `tcod.tileset` is also used as - a reference for the current font size. - - If `correct_aspect` is True then the viewport will be letter-boxed to fit - the screen instead of stretched. - - If `integer_scale` is True then the viewport to be scaled in integer - proportions, this is ignored when the screen is too small. - """ - assert tcod.sys_get_renderer() == tcod.RENDERER_SDL2 - sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() - assert sdl_renderer - tileset = tcod.tileset.get_default() - aspect = ( - console.width * tileset.tile_width, - console.height * tileset.tile_height, - ) - renderer_size = get_renderer_size() - scale = renderer_size[0] / aspect[0], renderer_size[1] / aspect[1] - if correct_aspect: - scale = min(scale), min(scale) - if integer_scale: - scale = ( - int(scale[0]) if scale[0] >= 1 else scale[0], - int(scale[1]) if scale[1] >= 1 else scale[1], - ) - view_size = aspect[0] * scale[0], aspect[1] * scale[1] - view_offset = ( - (renderer_size[0] - view_size[0]) // 2, - (renderer_size[1] - view_size[1]) // 2, - ) - return tuple(int(x) for x in (*view_offset, *view_size)) # type: ignore - # https://github.com/python/mypy/issues/224 - - -def clear(color: Tuple[int, int, int]) -> None: - """Clear the SDL renderer held by libtcod with a clear color.""" - sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() - assert sdl_renderer - tcod.lib.SDL_SetRenderDrawColor(sdl_renderer, *color, 255) - tcod.lib.SDL_RenderClear(sdl_renderer) - - -def present() -> None: - """Present the SDL renderer held by libtcod to the screen.""" - sdl_renderer = tcod.lib.TCOD_sys_get_sdl_renderer() - assert sdl_renderer - tcod.lib.SDL_RenderPresent(sdl_renderer) - - -def accumulate( - console: tcod.console.Console, - viewport: Optional[Tuple[int, int, int, int]] = None, -) -> None: - """Render a console to SDL's renderer. - - `console` is the console to renderer. Background alpha is supported and - well defined. Foregound alpha is also supported, but not as well-defined. - The `default tileset` will be used for graphics. - - `viewport` is where to draw the console on the screen. If it is None then - the console will be stretched over the full screen. You can use - `get_viewport` to make a viewport with specific constraints. - - You will need to call `present` yourself to show the rendered console, if - the viewport does not cover the full screen then you'll need to call - `clear` beforehand to clear the pixels outside of the viewport. - - This function can be called multiple times, but the current implementation - is optimized to handle only one console. Keep this in mind when rendering - multiple different consoles. - - This function depends on a provisional function of the libtcod API. You - may want to pin your exact version of python-tcod to prevent a break. - """ - assert tcod.sys_get_renderer() in ( - tcod.RENDERER_SDL2, - tcod.RENDERER_OPENGL2, - ) - if viewport is None: - viewport = tcod.ffi.NULL - else: - viewport = tcod.ffi.new("struct SDL_Rect*", viewport) - tcod.lib.TCOD_sys_accumulate_console_(console.console_c, viewport) - - -def pixel_to_subtile( - pixel: Tuple[float, float], - console: tcod.console.Console, - viewport: Tuple[int, int, int, int], -) -> Tuple[float, float]: - """Covert pixel coordinates to floating point tile coordinates. - - Parameters are the same as `pixel_to_tile`. - """ - return ( - (pixel[0] - viewport[0]) / viewport[2] * console.width, - (pixel[1] - viewport[1]) / viewport[3] * console.height, - ) - - -def pixel_to_tile( - pixel: Tuple[float, float], - console: tcod.console.Console, - viewport: Tuple[int, int, int, int], -) -> Tuple[int, int]: - """Covert pixel coordinates to integer tile coordinates. - - `pixel` are the pixel coordinates to use such as the ones returned by a - `event.type=="MOUSEMOTION"`'s `event.pixel` attribute. - - `console` and `viewport` should be the same arguments previously passed to - `accumulate`. They are used as references to convert from pixels to tiles. - """ - x, y = pixel_to_subtile(pixel, console, viewport) - return math.floor(x), math.floor(y) - - -def main() -> None: - """An example for the use of this module.""" - window_flags = ( - tcod.lib.SDL_WINDOW_RESIZABLE | tcod.lib.SDL_WINDOW_MAXIMIZED - ) - renderer_flags = tcod.lib.SDL_RENDERER_PRESENTVSYNC - with init_sdl2(640, 480, None, window_flags, renderer_flags): - console = tcod.console.Console(20, 4, order="F") - TEXT = "Console with a fixed aspect ratio and integer scaling." - console.print_box(0, 0, 0, 0, TEXT) - while True: - # Clear background with white. - clear((255, 255, 255)) - # Draw the console to SDL's buffer. - viewport = get_viewport(console, True, True) - accumulate(console, viewport) - # If you want you can use the FFI to do additional drawing here: - ... - # Present the SDL2 renderer to the display. - present() - - for event in tcod.event.wait(): - if event.type == "QUIT": - raise SystemExit() - elif event.type == "MOUSEMOTION": - # Mouse pixel coordinates will need to be converted to - # tiles using the console and viewport as a reference. - x, y = pixel_to_tile(event.pixel, console, viewport) - console.tiles["bg"] = (0, 0, 0, 255) - console.tiles["fg"] = (255, 255, 255, 255) - if 0 <= x < console.width and 0 <= y < console.height: - console.tiles["bg"][x, y] = (255, 255, 255, 255) - console.tiles["fg"][x, y] = (0, 0, 0, 255) - elif event.type == "WINDOWRESIZED": - # You can change to a console of a different size in - # response to a WINDOWRESIZED event if you want. - ... # See resizable_console.py - - -if __name__ == "__main__": - main() diff --git a/examples/experimental/resizable_console.py b/examples/experimental/resizable_console.py deleted file mode 100755 index cee044b7..00000000 --- a/examples/experimental/resizable_console.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# To the extent possible under law, the libtcod maintainers have waived all -# copyright and related or neighboring rights for this script. This work is -# published from: United States. -# https://creativecommons.org/publicdomain/zero/1.0/ -"""An example showing the console being resized to fit the window.""" -from typing import Tuple - -import tcod -import tcod.event -import tcod.tileset - -import custrender # Using the custom renderer engine. - - -def fit_console(width: int, height: int) -> Tuple[int, int]: - """Return a console resolution the fits the given pixel resolution.""" - # Use the current active tileset as a reference. - tileset = tcod.tileset.get_default() - return width // tileset.tile_width, height // tileset.tile_height - - -def main() -> None: - window_flags = ( - tcod.lib.SDL_WINDOW_RESIZABLE | tcod.lib.SDL_WINDOW_MAXIMIZED - ) - renderer_flags = tcod.lib.SDL_RENDERER_PRESENTVSYNC - with custrender.init_sdl2(640, 480, None, window_flags, renderer_flags): - console = tcod.console.Console( - *fit_console(*custrender.get_renderer_size()) - ) - TEXT = "Resizable console with no stretching." - while True: - console.clear() - - # Draw the checkerboard pattern. - console.tiles["bg"][::2, ::2] = (32, 32, 32, 255) - console.tiles["bg"][1::2, 1::2] = (32, 32, 32, 255) - - console.print_box(0, 0, 0, 0, TEXT) - - # These functions are explained in `custrender.py`. - custrender.clear((0, 0, 0)) - custrender.accumulate( - console, custrender.get_viewport(console, True, True) - ) - custrender.present() - - for event in tcod.event.wait(): - if event.type == "QUIT": - raise SystemExit() - elif event.type == "WINDOWRESIZED": - # Replace `console` with a new one of the correct size. - console = tcod.console.Console( - *fit_console(event.width, event.height) - ) - - -if __name__ == "__main__": - main() From c8cf0e731dca156f1741ca939cf38656f0553de4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 14:10:06 -0800 Subject: [PATCH 0246/1101] Fix tcod.mouse_move. --- libtcod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtcod b/libtcod index 2d54a7f5..7d925eeb 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 2d54a7f5bbb82726dc04895c4ebb68585c61cfee +Subproject commit 7d925eeb9f5726cc5d25b3e5ec09cbdebae17ac4 From 0d043bb415fde4c7c09c0e3db3d0b4e711bcb7aa Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 14:14:12 -0800 Subject: [PATCH 0247/1101] Don't build for older PyPy versions. Only the latest version is working and currently I don't want to bother with the older ones. --- .travis.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cb03fec..7bfe2cbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,6 @@ matrix: dist: xenial sudo: required env: CC=gcc-8 CXX=g++-8 - - os: linux - python: pypy3.5-7.0 - dist: xenial - sudo: required - env: CC=gcc-8 CXX=g++-8 - os: linux python: nightly dist: xenial @@ -25,19 +20,12 @@ matrix: - os: osx language: generic env: MB_PYTHON_VERSION=3.7.1 - - os: osx - language: generic - env: PYPY3_VERSION=pypy3.6-v7.1.1 - - os: osx - language: generic - env: PYPY3_VERSION=pypy3.6-v7.2.0 - os: osx language: generic env: PYPY3_VERSION=pypy3.6-v7.3.0 allow_failures: - python: nightly - python: pypy3.5-7.0 - - env: PYPY3_VERSION=pypy3.6-v7.2.0 fast_finish: true From 5c8edd4f2ddb473916a9d560267733b15edd0ae4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 14:34:21 -0800 Subject: [PATCH 0248/1101] Refactor console_init_root function. Needed an update now that the renderers have been improved. --- CHANGELOG.rst | 4 +++- tcod/_internal.py | 11 +++++++++++ tcod/console.py | 4 +++- tcod/libtcodpy.py | 31 ++++++++----------------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c9b3730e..77ccd375 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,9 +10,11 @@ Unreleased ------------------ Changed - Using libtcod 1.16.0-alpha.1; - - Console blit operations now perform per-cell alpha transparency. - When a renderer fails to load it will now fallback to a different one. The order is: OPENGL2 -> OPENGL -> SDL2. + - The default renderer is now SDL2. + - The SDL and OPENGL renderers are no longer deprecated, but they now point to + slightly different backward compatible implementations. Deprecated - The use of `libtcod.cfg` or `terminal.png` is deprecated. diff --git a/tcod/_internal.py b/tcod/_internal.py index 483be1a3..27f6a106 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -64,6 +64,17 @@ def _check(error: int) -> int: return error +def _check_warn(error: int, stacklevel: int = 2) -> int: + """Like _check, but raises a warning on positive error codes.""" + if _check(error) > 0: + warnings.warn( + ffi.string(lib.TCOD_get_error()).decode(), + RuntimeWarning, + stacklevel=stacklevel + 1, + ) + return error + + def _unpack_char_p(char_p: Any) -> str: if char_p == ffi.NULL: return "" diff --git a/tcod/console.py b/tcod/console.py index 694e37ed..52f31f9f 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1159,4 +1159,6 @@ def get_height_rect(width: int, string: str) -> int: .. versionadded:: 9.2 """ string_ = string.encode("utf-8") # type: bytes - return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) + return int( + lib.TCOD_console_get_height_rect_wn(width, len(string_), string_) + ) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index df1fc196..0816f761 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -37,7 +37,7 @@ KEY_RELEASED, ) -from tcod._internal import deprecate, pending_deprecate, _check +from tcod._internal import deprecate, pending_deprecate, _check, _check_warn from tcod._internal import _int, _unpack_char_p from tcod._internal import _bytes, _unicode, _fmt @@ -887,9 +887,9 @@ def console_init_root( Options are: * `tcod.RENDERER_SDL`: - A deprecated software/SDL2 renderer. + Forces the SDL2 renderer into software mode. * `tcod.RENDERER_OPENGL`: - A deprecated SDL2/OpenGL1 renderer. + An OpenGL 1 implementation. * `tcod.RENDERER_GLSL`: A deprecated SDL2/OpenGL2 renderer. * `tcod.RENDERER_SDL2`: @@ -924,27 +924,14 @@ def console_init_root( # Use the scripts filename as the title. title = os.path.basename(sys.argv[0]) if renderer is None: + renderer = tcod.constants.RENDERER_SDL2 + elif renderer == tcod.constants.RENDERER_GLSL: warnings.warn( - "A renderer should be given, see the online documentation.", + "The GLSL renderer is deprecated.", DeprecationWarning, stacklevel=2, ) - renderer = tcod.constants.RENDERER_SDL - elif renderer in ( - tcod.constants.RENDERER_SDL, - tcod.constants.RENDERER_OPENGL, - tcod.constants.RENDERER_GLSL, - ): - warnings.warn( - "The SDL, OPENGL, and GLSL renderers are deprecated.", - DeprecationWarning, - stacklevel=2, - ) - NEW_RENDERER = renderer in ( - tcod.constants.RENDERER_SDL2, - tcod.constants.RENDERER_OPENGL2, - ) - if vsync is None and NEW_RENDERER: + if vsync is None: vsync = False warnings.warn( "vsync defaults to False, but the default will change to True in " @@ -952,9 +939,7 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) - if not NEW_RENDERER: - vsync = False - _check( + _check_warn( lib.TCOD_console_init_root_( w, h, _bytes(title), fullscreen, renderer, vsync ) From 323cee730d5b699254d2998ee8a512a6640a7e79 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 14:44:27 -0800 Subject: [PATCH 0249/1101] Clean up code using static tools. --- tcod/console.py | 10 +--------- tcod/libtcodpy.py | 6 +++--- tcod/path.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 52f31f9f..ecb52367 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -761,15 +761,7 @@ def blit( """ # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): - ( - src_x, - src_y, - width, - height, - dest, - dest_x, - dest_y, - ) = ( + (src_x, src_y, width, height, dest, dest_x, dest_y,) = ( dest, # type: ignore dest_x, dest_y, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 0816f761..bdddbdcc 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1948,7 +1948,7 @@ def console_from_xp(filename: str) -> tcod.console.Console: def console_list_load_xp( - filename: str + filename: str, ) -> Optional[List[tcod.console.Console]]: """Return a list of consoles from a REXPaint `.xp` file.""" tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) @@ -2215,7 +2215,7 @@ def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool: @pending_deprecate() def dijkstra_path_walk( - p: tcod.path.Dijkstra + p: tcod.path.Dijkstra, ) -> Union[Tuple[int, int], Tuple[None, None]]: x = ffi.new("int *") y = ffi.new("int *") @@ -4205,7 +4205,7 @@ def _atexit_verify() -> None: lib.TCOD_console_delete(ffi.NULL) -__all__ = [ +__all__ = [ # noqa: F405 "Color", "Bsp", "NB_FOV_ALGORITHMS", diff --git a/tcod/path.py b/tcod/path.py index 8681acc3..94cad1d9 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -81,7 +81,7 @@ def _pycall_path_dest_only( def _get_pathcost_func( - name: str + name: str, ) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" return ffi.cast( # type: ignore @@ -211,9 +211,11 @@ def __init__(self, cost: Any, diagonal: float = 1.41): ) self.cost = NodeCostArray(self.cost) - self._callback, self._userdata, self.shape = ( - self.cost.get_tcod_path_ffi() - ) + ( + self._callback, + self._userdata, + self.shape, + ) = self.cost.get_tcod_path_ffi() self._path_c = ffi.gc( self._path_new_using_function( self.cost.shape[0], @@ -437,8 +439,8 @@ def hillclimb2d( is returned by :any:`dijkstra2d`. `start` is a 2-item tuple with starting coordinates. The axes if these - coordinates should match the axis of the `distance` array. An out-of-bounds - `start` index will raise an IndexError. + coordinates should match the axis of the `distance` array. + An out-of-bounds `start` index will raise an IndexError. At each step nodes adjacent toe current will be checked for a value lower than the current one. Which directions are checked is decided by the From 3bb05749d48bd7225467b6c8ce530360f15a508a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 15:39:56 -0800 Subject: [PATCH 0250/1101] Fix AppVeyor virtualenv. The current version of virutalenv puts PyPy script files at a consistent location. --- .appveyor/install_python.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index ba661f35..1f699fa9 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -1,7 +1,6 @@ $env:ACTIVATE_VENV='venv\Scripts\activate.bat' if ($env:PYPY -or $env:PYPY3) { - $env:ACTIVATE_VENV='venv\bin\activate.bat' if($env:PYPY3){ $env:PYPY_EXE='pypy3.exe' $env:PYPY=$env:PYPY3 @@ -23,7 +22,7 @@ if ($env:WEB_PYTHON) { $env:PYTHON = 'C:\UserPython\python.exe' } & $env:PYTHON -m pip install --disable-pip-version-check --upgrade pip -& $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=16" +& $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=20" & $env:PYTHON -m virtualenv venv if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } From 36a56133e89158b059e14c0b256cbaace96d7a6c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 14 Feb 2020 16:20:47 -0800 Subject: [PATCH 0251/1101] Prepare 11.7.0 release. --- CHANGELOG.rst | 9 +++++++-- libtcod | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 77ccd375..02b8809a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,11 @@ v2.0.0 Unreleased ------------------ + +11.7.0 - 2020-02-14 +------------------- Changed - - Using libtcod 1.16.0-alpha.1; + - Using `libtcod 1.16.0-alpha.2`. - When a renderer fails to load it will now fallback to a different one. The order is: OPENGL2 -> OPENGL -> SDL2. - The default renderer is now SDL2. @@ -17,7 +20,9 @@ Changed slightly different backward compatible implementations. Deprecated - - The use of `libtcod.cfg` or `terminal.png` is deprecated. + - The use of `libtcod.cfg` and `terminal.png` is deprecated. + +Fixed - `tcod.sys_update_char` now works with the newer renderers. - Fixed buffer overflow in name generator. - `tcod.image_from_console` now works with the newer renderers. diff --git a/libtcod b/libtcod index 7d925eeb..01ddb1fd 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 7d925eeb9f5726cc5d25b3e5ec09cbdebae17ac4 +Subproject commit 01ddb1fdf445f32c077de65da5d84ffcaee59376 From 2145cb7d1f357aaf9269cc5aef87aa1c50dcdb1f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 00:02:25 -0800 Subject: [PATCH 0252/1101] Update libtcod to get regression fixes. --- CHANGELOG.rst | 4 ++++ libtcod | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 02b8809a..b1c6c84d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed regression in `Console.draw_frame`. + - The wavelet noise generator now excludes -1.0f and 1.0f as return values. + - Fixed console fading color regression. 11.7.0 - 2020-02-14 ------------------- diff --git a/libtcod b/libtcod index 01ddb1fd..84e60b29 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 01ddb1fdf445f32c077de65da5d84ffcaee59376 +Subproject commit 84e60b299f8e2d98a2fb3f47c58555641d8d0c71 From 28681d3a1d0b9d0a48b323561e60e130b9459465 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 00:16:33 -0800 Subject: [PATCH 0253/1101] Prepare 11.7.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1c6c84d..3d04c220 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.7.1 - 2020-02-16 +------------------- Fixed - Fixed regression in `Console.draw_frame`. - The wavelet noise generator now excludes -1.0f and 1.0f as return values. From 0865de74338932664a8c2e95b7e8a5d5323a7eb6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 01:13:40 -0800 Subject: [PATCH 0254/1101] Add sleep after failed setup script. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7bfe2cbe..3eedd719 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ before_install: - pip install --requirement requirements.txt install: - if [[ -z "$TRAVIS_TAG" ]]; then export BUILD_FLAGS="-g"; fi -- python setup.py build $BUILD_FLAGS develop bdist_wheel --py-limited-api=cp35 +- python setup.py build $BUILD_FLAGS develop bdist_wheel --py-limited-api=cp35 || (sleep 5; exit 1) - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m pip install delocate; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' From 98b0d19aa4a6cc6f80df9b8580f5d7bd7476fa08 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 01:31:04 -0800 Subject: [PATCH 0255/1101] Add C99 standard flag to cpp command. --- build_libtcod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build_libtcod.py b/build_libtcod.py index 88df5e2b..c4c68711 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -284,6 +284,7 @@ def get_ast(): filename=CFFI_HEADER, use_cpp=True, cpp_args=[ + r"-std=c99", r"-Idependencies/fake_libc_include", r"-DDECLSPEC=", r"-DSDLCALL=", From 87fbfed213ea789489b7632cfdd3fe045fbd4bfa Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 01:52:13 -0800 Subject: [PATCH 0256/1101] Use "gcc -E" to parse files. --- build_libtcod.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_libtcod.py b/build_libtcod.py index c4c68711..7bde17d2 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -283,7 +283,9 @@ def get_ast(): ast = parse_file( filename=CFFI_HEADER, use_cpp=True, + cpp_path="gcc", cpp_args=[ + r"-E", r"-std=c99", r"-Idependencies/fake_libc_include", r"-DDECLSPEC=", From 23a71ebc62d71dc626e718ce93aab04266de0eba Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 11:31:21 -0800 Subject: [PATCH 0257/1101] Set length when initializing console struct. Fixes function which were using it, such as TCOD_console_clear. --- CHANGELOG.rst | 2 ++ tcod/console.py | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d04c220..638d0441 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed regression in `tcod.console_clear`. 11.7.1 - 2020-02-16 ------------------- diff --git a/tcod/console.py b/tcod/console.py index ecb52367..545fe446 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -137,6 +137,7 @@ def __init__( { "w": width, "h": height, + "length": width * height, "tiles": ffi.cast( "struct TCOD_ConsoleTile*", self._tiles.ctypes.data ), From 21959dd4f549259d360c957b005bc99809f87be9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 16 Feb 2020 11:34:26 -0800 Subject: [PATCH 0258/1101] Prepare 11.7.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 638d0441..de81cfa3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.7.2 - 2020-02-16 +------------------- Fixed - Fixed regression in `tcod.console_clear`. From 5fc32a5db561c324113fb2573d8412b33f89f83b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 17 Feb 2020 01:41:51 -0800 Subject: [PATCH 0259/1101] Always compile to ABI3. --- build_libtcod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build_libtcod.py b/build_libtcod.py index 7bde17d2..2f465402 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -353,6 +353,7 @@ def get_ast(): extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, define_macros=define_macros, + py_limited_api=35, ) CONSTANT_MODULE_HEADER = '''""" From c764938bc61f2df36ce5ef9049490b3272c178d0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 18 Feb 2020 01:50:36 -0800 Subject: [PATCH 0260/1101] Compile without libtcod C++ sources. --- build_libtcod.py | 13 +++++++------ libtcod | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 2f465402..76916d84 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -33,10 +33,12 @@ BITSIZE, LINKAGE = platform.architecture() -def walk_sources(directory): +def walk_sources(directory, cpp): for path, dirs, files in os.walk(directory): for source in files: - if source.endswith(".c") or source.endswith(".cpp"): + if source.endswith(".c"): + yield os.path.join(path, source) + elif cpp and source.endswith(".cpp"): yield os.path.join(path, source) @@ -97,9 +99,8 @@ def unpack_sdl2(version): library_dirs = [] define_macros = [] -sources += walk_sources("tcod/") -sources += walk_sources("tdl/") -sources += walk_sources("libtcod/src/libtcod/") +sources += walk_sources("tcod/", cpp=True) +sources += walk_sources("libtcod/src/libtcod/", cpp=False) sources += ["libtcod/src/vendor/stb.c"] sources += ["libtcod/src/vendor/glad.c"] sources += ["libtcod/src/vendor/lodepng.c"] @@ -297,7 +298,7 @@ def get_ast(): r"-DDOXYGEN_SHOULD_IGNORE_THIS", r"-DMAC_OS_X_VERSION_MIN_REQUIRED=1060", r"-D__attribute__(x)=", - r"-D_PSTDINT_H_INCLUDED", + r"-DLIBTCOD_SDL2_EVENT_H_", # Skip libtocd/sdl2/event.h ] + extra_parse_args, ) diff --git a/libtcod b/libtcod index 84e60b29..aeb5975a 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 84e60b299f8e2d98a2fb3f47c58555641d8d0c71 +Subproject commit aeb5975a0241fa8e0130629416cbd02633555243 From 8e6fd7a8a1d07720d89ebb52ef1ca845a5c94309 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Feb 2020 01:55:37 -0800 Subject: [PATCH 0261/1101] Setup new functions from libtcod. Can now adjust viewport settings in console_flush. Can now recommend a console size at runtime. Images no longer have an SDL surface. Make sure Black targets Python 3.5. --- CHANGELOG.rst | 11 +++++++++ libtcod | 2 +- pyproject.toml | 1 + tcod/_internal.py | 1 - tcod/console.py | 29 ++++++++++++++++++++++- tcod/image.py | 13 ----------- tcod/libtcodpy.py | 58 +++++++++++++++++++++++++++++++++++++++++++--- tests/conftest.py | 4 ++-- tests/test_tcod.py | 4 ++++ 9 files changed, 102 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de81cfa3..509803f0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,17 @@ v2.0.0 Unreleased ------------------ +Added + - Added `tcod.console.recommended_size` for when you want to change your main + console size at runtime. + +Changed + - Added parameters to `tcod.console_flush`, you can now manually provide a + console and adjust how it is presented. + +Fixed + - Fixed keyboard state and mouse state functions losing state when events were + flushed. 11.7.2 - 2020-02-16 ------------------- diff --git a/libtcod b/libtcod index aeb5975a..f8a6131f 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit aeb5975a0241fa8e0130629416cbd02633555243 +Subproject commit f8a6131f08d1ffe4cc402a563b015c11736f0f13 diff --git a/pyproject.toml b/pyproject.toml index 21235e52..515b1dde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,3 +9,4 @@ requires = [ [tool.black] line-length = 79 py36 = false +target-version = ["py35"] diff --git a/tcod/_internal.py b/tcod/_internal.py index 27f6a106..d730f517 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -248,7 +248,6 @@ def __init__(self, array: Any): self.image_c = ffi.new( "TCOD_Image*", { - "sys_img": ffi.NULL, "nb_mipmaps": 1, "mipmaps": self._mipmaps, "has_key_color": False, diff --git a/tcod/console.py b/tcod/console.py index 545fe446..0f7a21bd 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -137,7 +137,7 @@ def __init__( { "w": width, "h": height, - "length": width * height, + "elements": width * height, "tiles": ffi.cast( "struct TCOD_ConsoleTile*", self._tiles.ctypes.data ), @@ -1155,3 +1155,30 @@ def get_height_rect(width: int, string: str) -> int: return int( lib.TCOD_console_get_height_rect_wn(width, len(string_), string_) ) + + +def recommended_size() -> Tuple[int, int]: + """Return the recommended size of a console for the current active window. + + The return value from this function can be passed to :any:`Console`. + + This function will raise RuntimeError if libtcod has not been initialized. + + .. versionadded:: 11.8 + + .. seealso:: + :any:`tcod.console_init_root` + :any:`tcod.console_flush` + """ + if not lib.TCOD_ctx.engine: + raise RuntimeError("The libtcod engine was not initialized first.") + window = lib.TCOD_sys_get_sdl_window() + renderer = lib.TCOD_sys_get_sdl_renderer() + with ffi.new("int[2]") as xy: + if renderer: + lib.SDL_GetRendererOutputSize(renderer, xy, xy + 1) + else: # Assume OpenGL if a renderer does not exist. + lib.SDL_GL_GetDrawableSize(window, xy, xy + 1) + w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width) + h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height) + return w, h diff --git a/tcod/image.py b/tcod/image.py index 4728c73f..d55ff417 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -306,19 +306,6 @@ def __array_interface__(self) -> Dict[str, Any]: if self.image_c.mipmaps: # Libtcod RGB array. depth = 3 data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf)) - elif self.image_c.sys_img: # SDL Surface. - data = int(ffi.cast("size_t", self.image_c.sys_img.pixels)) - format = self.image_c.sys_img.format.format - if format == lib.SDL_PIXELFORMAT_RGB24: - depth = 3 - elif format == lib.SDL_PIXELFORMAT_RGBA32: - depth = 4 - else: - raise TypeError( - "Can't interface with format: %s" - % (_get_format_name(format),) - ) - strides = (self.image_c.sys_img.pitch, depth, 1) else: raise TypeError("Image has no initialized data.") return { diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index bdddbdcc..bb463778 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1144,9 +1144,61 @@ def console_credits_render(x: int, y: int, alpha: bool) -> bool: return bool(lib.TCOD_console_credits_render(x, y, alpha)) -def console_flush() -> None: - """Update the display to represent the root consoles current state.""" - lib.TCOD_console_flush() +def console_flush( + console: Optional[tcod.console.Console] = None, + *, + keep_aspect: bool = False, + integer_scaling: bool = False, + snap_to_integer: bool = True, + clear_color: Tuple[int, int, int, int] = (0, 0, 0, 255), + align: Tuple[float, float] = (0.5, 0.5) +) -> None: + """Update the display to represent the root consoles current state. + + `console` is the console you want to present. If not given the root + console will be used. + + If `keep_aspect` is True when the console aspect will be preserved with + a letterbox. Otherwise the console will be stretched to fill the screen. + + If `integer_scaling` is True then the console will be scaled in integer + increments. This will have no effect if the console must be shrunk. + + If `snap_to_integer` is True then if the console has a close enough fit + to the screen then a small border will be added instead of stretching + the console to fix the remaining area. You can use + :any:`tcod.console.recommended_size` to create a console which would be + close enough to not need any scaling. + + `clear_color` is the color used to clear the screen before the console + is presented, this will normally affect the border/letterbox color. + + `align` determines where the console will be placed when letter-boxing + exists. Values of 0 will put the console at the upper-left corner. + Values of 0.5 will center the console. + + .. versionchanged:: 11.8 + The parameters `console`, `keep_aspect`, `integer_scaling`, + `snap_to_integer`, `clear_color`, and `align` were added. + + .. seealso:: + :any:`tcod.console_init_root` + :any:`tcod.console.recommended_size` + """ + options = { + "keep_aspect": keep_aspect, + "integer_scaling": integer_scaling, + "snap_to_integer": snap_to_integer, + "clear_color": clear_color, + "align_x": align[0], + "align_y": align[1], + } + if console is None: + console_p = ffi.NULL + else: + console_p = console.console_c + with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts: + _check(lib.TCOD_console_flush_ex(console_p, viewport_opts)) # drawing on a console diff --git a/tests/conftest.py b/tests/conftest.py index 22deade3..f752c831 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,8 +22,8 @@ def session_console(request): RENDERER = getattr(tcod, 'RENDERER_' + request.param) tcod.console_set_custom_font(FONT_FILE) - with tcod.console_init_root(WIDTH, HEIGHT, - TITLE, FULLSCREEN, RENDERER) as con: + with tcod.console_init_root( + WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: yield con @pytest.fixture(scope="function") diff --git a/tests/test_tcod.py b/tests/test_tcod.py index c5690ce2..36bc8836 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -231,3 +231,7 @@ def test_cffi_structs(): # Make sure cffi structures are the correct size. tcod.ffi.new("SDL_Event*") tcod.ffi.new("SDL_AudioCVT*") + + +def test_recommended_size(console): + tcod.console.recommended_size() From 8451afae25e911578f746ea6d50b146c0657e7d8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Feb 2020 12:52:01 -0800 Subject: [PATCH 0262/1101] Add tiles_rgb and shuffle console attributes. This is an attempy to make the attributes a little more consistent. "tiles2" was never a good name for that attribute. --- CHANGELOG.rst | 9 +++++--- tcod/console.py | 55 ++++++++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 509803f0..0d1f6d40 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,11 +11,17 @@ Unreleased Added - Added `tcod.console.recommended_size` for when you want to change your main console size at runtime. + - Added `Console.tiles_rgb` as a replacement for `Console.tiles2`. Changed - Added parameters to `tcod.console_flush`, you can now manually provide a console and adjust how it is presented. +Deprecated + - `Console.tiles2` is deprecated in favour of `Console.tiles_rgb`. + - `Console.buffer` is now deprecated in favour of `Console.tiles`, instead of + the other way around. + Fixed - Fixed keyboard state and mouse state functions losing state when events were flushed. @@ -87,9 +93,6 @@ Added Changed - `Console.tiles` is now named `Console.buffer`. -Deprecated - - `Console.tiles` behavior will be changed to be like `Console.tiles2`. - 11.3.0 - 2019-09-06 ------------------- Added diff --git a/tcod/console.py b/tcod/console.py index 0f7a21bd..eed8ed29 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -255,55 +255,68 @@ def ch(self) -> np.ndarray: def tiles(self) -> np.ndarray: """An array of this consoles raw tile data. - .. versionadded:: 10.0 - - .. deprecated:: 11.4 - This has been renamed to :any:`Console.buffer`. - """ - return self._tiles.T if self._order == "F" else self._tiles - - @property - def buffer(self) -> np.ndarray: - """An array of this consoles raw tile data. - This acts as a combination of the `ch`, `fg`, and `bg` attributes. Colors include an alpha channel but how alpha works is currently undefined. Example: - >>> con = tcod.console.Console(10, 2, order="F") - >>> con.buffer[0, 0] = ( + >>> con = tcod.console.Console(10, 2) + >>> con.tiles[0, 0] = ( ... ord("X"), ... (*tcod.white, 255), ... (*tcod.black, 255), ... ) - >>> con.buffer[0, 0] + >>> con.tiles[0, 0] (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) + .. versionadded:: 10.0 + """ + return self._tiles.T if self._order == "F" else self._tiles + + @property + @deprecate("This attribute has been renamed to `tiles`.") + def buffer(self) -> np.ndarray: + """An array of this consoles raw tile data. + .. versionadded:: 11.4 + + .. depreacated:: 11.8 + Use :any:`Console.tiles` instead. """ return self._tiles.T if self._order == "F" else self._tiles @property - def tiles2(self) -> np.ndarray: + def tiles_rgb(self) -> np.ndarray: """An array of this consoles tile data without the alpha channel. The dtype of this array is effectively: ``[("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")]`` Example: - >>> con = tcod.console.Console(10, 2, order="F") - >>> con.tiles2[0, 0] = ord("@"), tcod.yellow, tcod.black - >>> con.tiles2[0, 0] + >>> con = tcod.console.Console(10, 2) + >>> con.tiles_rgb[0, 0] = ord("@"), tcod.yellow, tcod.black + >>> con.tiles_rgb[0, 0] (64, [255, 255, 0], [0, 0, 0]) - >>> con.tiles2["bg"] = tcod.blue - >>> con.tiles2[0, 0] + >>> con.tiles_rgb["bg"] = tcod.blue + >>> con.tiles_rgb[0, 0] (64, [255, 255, 0], [ 0, 0, 255]) - .. versionadded:: 11.3 + .. versionadded:: 11.8 """ return self.buffer.view(self._DTYPE_RGB) + @property + @deprecate("This attribute has been renamed to `tiles_rgb`.") + def tiles2(self) -> np.ndarray: + """This name is deprecated in favour of :any:`tiles_rgb`. + + .. versionadded:: 11.3 + + .. depreacated:: 11.8 + Use :any:`Console.tiles_rgb` instead. + """ + return self.tiles_rgb + @property def default_bg(self) -> Tuple[int, int, int]: """Tuple[int, int, int]: The default background color.""" From a072fd0439d6b1d482e58b1a2bdcb5402b569d51 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Feb 2020 12:58:39 -0800 Subject: [PATCH 0263/1101] Don't flush during TDL tests. These tests are not important and the flush is not needed. --- tests/test_tdl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_tdl.py b/tests/test_tdl.py index b838659c..ae782e93 100755 --- a/tests/test_tdl.py +++ b/tests/test_tdl.py @@ -51,8 +51,7 @@ def randomize_console(self): def flush(self): 'Pump events and refresh screen so show progress' - #tdl.event.get() # no longer needed - tdl.flush() + pass # There are no important side-effects from flushing. def get_random_character(self): "returns a tuple with a random character and colors (ch, fg, bg)" From 3598e39ce6945d6a73352a79c1ab8ffd4e963b7b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Feb 2020 13:43:07 -0800 Subject: [PATCH 0264/1101] Update libtcod. Use _console in console_flush so that the root console is a deprecated parameter. --- CHANGELOG.rst | 1 + libtcod | 2 +- tcod/libtcodpy.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d1f6d40..31e7b7ff 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added - Added `Console.tiles_rgb` as a replacement for `Console.tiles2`. Changed + - Using `libtcod 1.16.0-alpha.3`. - Added parameters to `tcod.console_flush`, you can now manually provide a console and adjust how it is presented. diff --git a/libtcod b/libtcod index f8a6131f..5e00406d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit f8a6131f08d1ffe4cc402a563b015c11736f0f13 +Subproject commit 5e00406da3921dbb338af46f1a5ece5f528a48b2 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index bb463778..aa634d0e 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1196,7 +1196,7 @@ def console_flush( if console is None: console_p = ffi.NULL else: - console_p = console.console_c + console_p = _console(console) with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts: _check(lib.TCOD_console_flush_ex(console_p, viewport_opts)) From 615dee4a96b917d979f9c6b8921f4f5775cea97b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 21 Feb 2020 16:03:26 -0800 Subject: [PATCH 0265/1101] Prepare 11.8.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31e7b7ff..738cc21d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.8.0 - 2020-02-21 +------------------- Added - Added `tcod.console.recommended_size` for when you want to change your main console size at runtime. From 5fed47536d771b1ed6e72dabe0a3974008276238 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 03:45:40 -0800 Subject: [PATCH 0266/1101] Update libtcod to fix mouse tile coordinates. --- CHANGELOG.rst | 5 +++++ libtcod | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 738cc21d..7043b3fd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Changed + - Using `libtcod 1.16.0-alpha.4`. + +Fixed + - Mouse tile coordinates are now correct on any resized window. 11.8.0 - 2020-02-21 ------------------- diff --git a/libtcod b/libtcod index 5e00406d..9c0eebba 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 5e00406da3921dbb338af46f1a5ece5f528a48b2 +Subproject commit 9c0eebbaee8ab0398af23b55b0d4426c56f80f7e From 50775fad0ccc9d02e5cf5dd42b91a00de0995fb1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 03:48:50 -0800 Subject: [PATCH 0267/1101] Prepare 11.8.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7043b3fd..6c4b5353 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.8.1 - 2020-02-22 +------------------- Changed - Using `libtcod 1.16.0-alpha.4`. From 6a42d847cd5b5dfdd658b1fc17e0d009348024e5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 11:19:53 -0800 Subject: [PATCH 0268/1101] Prevent KeyError when representing unusual keyboard symbol constants. SDL did not always return keys with a constant name. --- CHANGELOG.rst | 2 ++ tcod/event.py | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6c4b5353..4c1878a4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Prevent KeyError when representing unusual keyboard symbol constants. 11.8.1 - 2020-02-22 ------------------- diff --git a/tcod/event.py b/tcod/event.py index 37655a61..908d3918 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -234,11 +234,25 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: self.sdl_event = sdl_event return self + def _scancode_constant(self, table: Dict[int, str]) -> str: + """Return the constant name for this scan-code from a table.""" + try: + return table[self.scancode] + except KeyError: + return str(self.scancode) + + def _sym_constant(self, table: Dict[int, str]) -> str: + """Return the constant name for this symbol from a table.""" + try: + return table[self.sym] + except KeyError: + return str(self.sym) + def __repr__(self) -> str: return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( self.__class__.__name__, - _REVERSE_SCANCODE_TABLE_PREFIX[self.scancode], - _REVERSE_SYM_TABLE_PREFIX[self.sym], + self._scancode_constant(_REVERSE_SCANCODE_TABLE_PREFIX), + self._sym_constant(_REVERSE_SYM_TABLE_PREFIX), _describe_bitmask(self.mod, _REVERSE_MOD_TABLE_PREFIX), ", repeat=True" if self.repeat else "", ) @@ -246,8 +260,10 @@ def __repr__(self) -> str: def __str__(self) -> str: return "<%s, scancode=%s, sym=%s, mod=%s, repeat=%r>" % ( super().__str__().strip("<>"), - tcod.event_constants._REVERSE_SCANCODE_TABLE[self.scancode], - tcod.event_constants._REVERSE_SYM_TABLE[self.sym], + self._scancode_constant( + tcod.event_constants._REVERSE_SCANCODE_TABLE + ), + self._sym_constant(tcod.event_constants._REVERSE_SYM_TABLE), _describe_bitmask(self.mod, _REVERSE_MOD_TABLE), self.repeat, ) From 5374fdc9a007588e331c14aff26293d647c61ac1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 12:50:55 -0800 Subject: [PATCH 0269/1101] Prepare 11.8.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c1878a4..f105c1b0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.8.2 - 2020-02-22 +------------------- Fixed - Prevent KeyError when representing unusual keyboard symbol constants. From 5be3bed62b86e0d8cf13d85efc83ed29d93902c8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 16:11:12 -0800 Subject: [PATCH 0270/1101] Add the Tileset.render method. SDL2 parser had to be updated to include more symbols. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- parse_sdl2.py | 7 +++---- tcod/tileset.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f105c1b0..d488a878 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Added + - New method `Tileset.render` renders an RGBA NumPy array from a tileset and + a console. 11.8.2 - 2020-02-22 ------------------- diff --git a/libtcod b/libtcod index 9c0eebba..0818f4b1 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 9c0eebbaee8ab0398af23b55b0d4426c56f80f7e +Subproject commit 0818f4b183f4431ce99b3ec46a74103e426563b6 diff --git a/parse_sdl2.py b/parse_sdl2.py index 1f582d18..ebcb772f 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -18,7 +18,7 @@ RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) RE_TYPEDEF = re.compile(r"^typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) RE_ENUM = re.compile(r"^(?:typedef )?enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) -RE_DECL = re.compile(r"^extern[^#\n]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) +RE_DECL = re.compile(r"^extern[^#(]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) RE_ENDIAN = re.compile( r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL ) @@ -89,7 +89,7 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: typedef = typedef.replace("SDL_TEXTEDITINGEVENT_TEXT_SIZE", "...") typedef = typedef.replace("SDL_AUDIOCVT_MAX_FILTERS + 1", "...") - typedef = typedef.replace("SDLCALL ", "") + typedef = typedef.replace("SDLCALL", " ") typedef = typedef.replace("SDL_AUDIOCVT_PACKED ", "") if NEEDS_PACK4 and "typedef struct SDL_AudioCVT" in typedef: @@ -109,7 +109,7 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: continue decl = re.sub(r"SDL_PRINTF_VARARG_FUNC\(\w*\)", "", decl) decl = decl.replace("extern DECLSPEC ", "") - decl = decl.replace("SDLCALL ", "") + decl = decl.replace("SDLCALL", " ") yield decl.replace("SDL_PRINTF_FORMAT_STRING ", "") @@ -129,7 +129,6 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: "SDL_hints.h", "SDL_joystick.h", "SDL_haptic.h", - "SDL_gamecontroller.h", "SDL_power.h", "SDL_log.h", "SDL_messagebox.h", diff --git a/tcod/tileset.py b/tcod/tileset.py index 51380df4..03c7f37e 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -10,6 +10,8 @@ import numpy as np from tcod.loader import lib, ffi +import tcod.console +from tcod._internal import _check, _console class Tileset: @@ -99,6 +101,46 @@ def set_tile(self, codepoint: int, tile: np.ndarray) -> None: ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data), ) + def render(self, console: tcod.console.Console) -> np.ndarray: + """Render an RGBA array, using console with this tileset. + + `console` is the Console object to render, this can not be the root + console. + + The output array will be a np.uint8 array with the shape of: + ``(con_height * tile_height, con_width * tile_width, 4)``. + + .. versionadded:: 11.9 + """ + if not console: + raise ValueError("'console' must not be the root console.") + width = console.width * self.tile_width + height = console.height * self.tile_height + out = np.empty((height, width, 4), np.uint8) + out[:] = 9 + surface_p = ffi.gc( + lib.SDL_CreateRGBSurfaceWithFormatFrom( + ffi.cast("void*", out.ctypes.data), + width, + height, + 32, + out.strides[0], + lib.SDL_PIXELFORMAT_RGBA32, + ), + lib.SDL_FreeSurface, + ) + with surface_p: + with ffi.new("SDL_Surface**", surface_p) as surface_p_p: + _check( + lib.TCOD_tileset_render_to_surface( + self._tileset_p, + _console(console), + ffi.NULL, + surface_p_p, + ) + ) + return out + def get_default() -> Tileset: """Return a reference to the default Tileset. From 9d0d519705d2f3b92b84386a62445fcd481418e9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 16:17:03 -0800 Subject: [PATCH 0271/1101] Remove deprecation from Console.tiles. --- tcod/console.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index eed8ed29..b9f0cea5 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -247,11 +247,7 @@ def ch(self) -> np.ndarray: """ return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] - @property # type: ignore - @deprecate( - "The `tiles` attribute has been renamed to `buffer`. " - "This attribute will later be changed to be the same as tiles2." - ) + @property def tiles(self) -> np.ndarray: """An array of this consoles raw tile data. From 87d3034c31a414cf58f7f6adc79eff1c7a099455 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 22 Feb 2020 17:06:32 -0800 Subject: [PATCH 0272/1101] Prepare 11.9.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d488a878..9bc7f641 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.9.0 - 2020-02-22 +------------------- Added - New method `Tileset.render` renders an RGBA NumPy array from a tileset and a console. From 512639e42979227e4680e94e48c83f67a7448624 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 24 Feb 2020 14:31:43 -0800 Subject: [PATCH 0273/1101] Remove bad deprecation warning in Console.tiles_rgb. --- tcod/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index b9f0cea5..bc14b277 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -299,7 +299,7 @@ def tiles_rgb(self) -> np.ndarray: .. versionadded:: 11.8 """ - return self.buffer.view(self._DTYPE_RGB) + return self.tiles.view(self._DTYPE_RGB) @property @deprecate("This attribute has been renamed to `tiles_rgb`.") From 3745b14ae41d948d53ca978132fc7a382cfaebb1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Feb 2020 16:25:06 -0800 Subject: [PATCH 0274/1101] Update libtcod. --- CHANGELOG.rst | 4 ++++ libtcod | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9bc7f641..e47f281c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Changed + - Using `libtcod 1.16.0-alpha.5`. + - Mouse tile coordinates are now always zero before the first call to + `tcod.console_flush`. 11.9.0 - 2020-02-22 ------------------- diff --git a/libtcod b/libtcod index 0818f4b1..1c368ba5 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 0818f4b183f4431ce99b3ec46a74103e426563b6 +Subproject commit 1c368ba5c0116b41c4bc4f559f33d264ca0dc8a1 From 5c0ddf410b7e8d20b09322ae3c0d2d103db58abc Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 28 Feb 2020 16:25:32 -0800 Subject: [PATCH 0275/1101] Prepare 11.9.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e47f281c..b56459c2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.9.1 - 2020-02-28 +------------------- Changed - Using `libtcod 1.16.0-alpha.5`. - Mouse tile coordinates are now always zero before the first call to From 4b2c9aa1c8a4b9a00661354e6ba9c2e87dfd95bd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 3 Mar 2020 17:10:27 -0800 Subject: [PATCH 0276/1101] Update copyright year. --- LICENSE.txt | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 7665b7fb..b407f8da 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2009-2019, Kyle Stewart and the python-tcod contributors. +Copyright (c) 2009-2020, Kyle Stewart and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index 96f2c81c..70a3fa37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,7 +56,7 @@ # General information about the project. project = u'python-tcod' -copyright = u'2009-2018, Kyle Stewart' +copyright = u'2009-2020, Kyle Stewart' author = u'Kyle Stewart' # The version info for the project you're documenting, acts as replacement for From e2219211a01e78e1a749826ccb31023f01e0b2f5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 15 Mar 2020 11:36:22 -0700 Subject: [PATCH 0277/1101] Add error message for when the libtcod submodule is missing. --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index bfed6358..82dd2cd7 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import sys +import os.path from setuptools import setup @@ -109,6 +110,11 @@ def get_long_description(): print(error) sys.exit(1) +if not os.path.exists("libtcod/src"): + print("Libtcod submodule is uninitialized.") + print("Did you forget to run 'git submodule update --init'?") + sys.exit(1) + needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) pytest_runner = ["pytest-runner"] if needs_pytest else [] From 74b0119682641f48f9f9418177ca9de2c6051adf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 17 Mar 2020 16:59:40 -0700 Subject: [PATCH 0278/1101] Fix segfault with tcod.tileset.get_default. Tileset was deleting the global tileset object when it went out of scope. The upstream has been updated so that getting a reference of the global tileset counts as a reference increment. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b56459c2..aaf46234 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes + out of scope. 11.9.1 - 2020-02-28 ------------------- diff --git a/libtcod b/libtcod index 1c368ba5..63c7f313 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 1c368ba5c0116b41c4bc4f559f33d264ca0dc8a1 +Subproject commit 63c7f313803612819fd2a6789e14a23643cb8159 From bd530ca8325f24bf77c753792781f998802f0d0e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 17 Mar 2020 18:16:09 -0700 Subject: [PATCH 0279/1101] Prepare 11.9.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aaf46234..eaaa770f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.9.2 - 2020-03-17 +------------------- Fixed - Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes out of scope. From 52a383d0b9718f7d583a84c96ca545c4d319b063 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Mar 2020 15:24:55 -0700 Subject: [PATCH 0280/1101] Add PyCharm installation instructions. There have been lots of help posts released to this, this addition will make it easier to resolve those posts. --- docs/installation.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index e50c18d3..a7a33159 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -56,6 +56,35 @@ in a user environment:: python3 -m pip install --user tcod +PyCharm +------- +PyCharm will often run your project in a virtual environment, hiding any modules +you installed system-wide. You must install python-tcod inside of the virtual +environment in order for it to be importable in your projects scripts. + +By default the bottom bar of PyCharm will have a tab labeled `terminal`. +Open this tab and you should see a prompt with ``(venv)`` on it. +This means your commands will run in the virtual environment of your project. + +With a new project and virtual environment you should upgrade pip before +installing python-tcod. You can do this by running the command:: + + python -m pip install --upgrade pip + +If this for some reason failed, you may fall back on using `easy_install`:: + + easy_install --upgrade pip + +After pip is upgraded you can install tcod with the following command:: + + pip install tcod + +You can now use ``import tcod``. + +If you are working with multiple people or computers then it's recommend to pin +the tcod version in a `requirements.txt file `_. +PyCharm will automatically update the virtual environment from these files. + Upgrading python-tcod --------------------- python-tcod is updated often, you can re-run pip with the ``--upgrade`` flag From d00a13ca13521c0f2634a1b4e3f191c15e14b410 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 20 Mar 2020 15:42:36 -0700 Subject: [PATCH 0281/1101] Fix documentation directive typos. --- tcod/console.py | 4 ++-- tcod/event.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index bc14b277..631206d9 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -276,7 +276,7 @@ def buffer(self) -> np.ndarray: .. versionadded:: 11.4 - .. depreacated:: 11.8 + .. deprecated:: 11.8 Use :any:`Console.tiles` instead. """ return self._tiles.T if self._order == "F" else self._tiles @@ -308,7 +308,7 @@ def tiles2(self) -> np.ndarray: .. versionadded:: 11.3 - .. depreacated:: 11.8 + .. deprecated:: 11.8 Use :any:`Console.tiles_rgb` instead. """ return self.tiles_rgb diff --git a/tcod/event.py b/tcod/event.py index 908d3918..22a37fbd 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -293,7 +293,7 @@ class MouseState(Event): * tcod.event.BUTTON_X1MASK * tcod.event.BUTTON_X2MASK - .. addedversion:: 9.3 + .. versionadded:: 9.3 """ def __init__( @@ -860,7 +860,7 @@ def ev_windowhittest(self, event: WindowEvent) -> None: def get_mouse_state() -> MouseState: """Return the current state of the mouse. - .. addedversion:: 9.3 + .. versionadded:: 9.3 """ xy = ffi.new("int[2]") buttons = lib.SDL_GetMouseState(xy, xy + 1) From cdb3f34a329730ea863f7aa21c41b22b5c73e13f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 21 Mar 2020 12:36:58 -0700 Subject: [PATCH 0282/1101] Disable and deprecate the `snap_to_integer` feature. This had some issues, notably it can cause weird mouse coordinates unexpectedly. It is now forced off. --- CHANGELOG.rst | 4 ++++ tcod/libtcodpy.py | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eaaa770f..585fff78 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Deprecated + - The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated + since it can cause minor scaling issues which don't exist when using + `integer_scaling` instead. 11.9.2 - 2020-03-17 ------------------- diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index aa634d0e..adb40bbe 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1149,7 +1149,7 @@ def console_flush( *, keep_aspect: bool = False, integer_scaling: bool = False, - snap_to_integer: bool = True, + snap_to_integer: Optional[bool] = None, clear_color: Tuple[int, int, int, int] = (0, 0, 0, 255), align: Tuple[float, float] = (0.5, 0.5) ) -> None: @@ -1163,12 +1163,8 @@ def console_flush( If `integer_scaling` is True then the console will be scaled in integer increments. This will have no effect if the console must be shrunk. - - If `snap_to_integer` is True then if the console has a close enough fit - to the screen then a small border will be added instead of stretching - the console to fix the remaining area. You can use - :any:`tcod.console.recommended_size` to create a console which would be - close enough to not need any scaling. + You can use :any:`tcod.console.recommended_size` to create a console which + will fit the window without needing to be scaled. `clear_color` is the color used to clear the screen before the console is presented, this will normally affect the border/letterbox color. @@ -1177,6 +1173,9 @@ def console_flush( exists. Values of 0 will put the console at the upper-left corner. Values of 0.5 will center the console. + `snap_to_integer` is deprecated and setting it will have no effect. + It will be removed in a later version. + .. versionchanged:: 11.8 The parameters `console`, `keep_aspect`, `integer_scaling`, `snap_to_integer`, `clear_color`, and `align` were added. @@ -1185,10 +1184,16 @@ def console_flush( :any:`tcod.console_init_root` :any:`tcod.console.recommended_size` """ + if snap_to_integer is not None: + warnings.warn( + "The snap_to_integer parameter is deprecated and will be removed.", + DeprecationWarning, + stacklevel=2, + ) options = { "keep_aspect": keep_aspect, "integer_scaling": integer_scaling, - "snap_to_integer": snap_to_integer, + "snap_to_integer": False, "clear_color": clear_color, "align_x": align[0], "align_y": align[1], From 37093318a12e190828420d629819132f7387b9f3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 21 Mar 2020 12:52:52 -0700 Subject: [PATCH 0283/1101] Resolve MyPy type warnings. --- tcod/console.py | 4 ++-- tcod/event.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 631206d9..ca25c39c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -269,7 +269,7 @@ def tiles(self) -> np.ndarray: """ return self._tiles.T if self._order == "F" else self._tiles - @property + @property # type: ignore @deprecate("This attribute has been renamed to `tiles`.") def buffer(self) -> np.ndarray: """An array of this consoles raw tile data. @@ -301,7 +301,7 @@ def tiles_rgb(self) -> np.ndarray: """ return self.tiles.view(self._DTYPE_RGB) - @property + @property # type: ignore @deprecate("This attribute has been renamed to `tiles_rgb`.") def tiles2(self) -> np.ndarray: """This name is deprecated in favour of :any:`tiles_rgb`. diff --git a/tcod/event.py b/tcod/event.py index 22a37fbd..cb0ecfaa 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -234,14 +234,14 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: self.sdl_event = sdl_event return self - def _scancode_constant(self, table: Dict[int, str]) -> str: + def _scancode_constant(self, table: Mapping[int, str]) -> str: """Return the constant name for this scan-code from a table.""" try: return table[self.scancode] except KeyError: return str(self.scancode) - def _sym_constant(self, table: Dict[int, str]) -> str: + def _sym_constant(self, table: Mapping[int, str]) -> str: """Return the constant name for this symbol from a table.""" try: return table[self.sym] From 3b02627e8751aa61ca875e6e7168c8c1f3c7869c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 25 Mar 2020 11:57:55 -0700 Subject: [PATCH 0284/1101] Add BDF support. --- CHANGELOG.rst | 4 ++++ libtcod | 2 +- tcod/tileset.py | 27 +++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 585fff78..5396967d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Added + - Added `tcod.tileset.load_bdf`, you can now load BDF fonts. + - `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable. + Deprecated - The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated since it can cause minor scaling issues which don't exist when using diff --git a/libtcod b/libtcod index 63c7f313..6cede1a7 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 63c7f313803612819fd2a6789e14a23643cb8159 +Subproject commit 6cede1a789b7451fd56da8c3f841e27696fb9c60 diff --git a/tcod/tileset.py b/tcod/tileset.py index 03c7f37e..0ffaa3be 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -145,7 +145,7 @@ def render(self, console: tcod.console.Console) -> np.ndarray: def get_default() -> Tileset: """Return a reference to the default Tileset. - This function is provisional. The API may change. + .. versionadded:: 11.10 """ return Tileset._claim(lib.TCOD_get_default_tileset()) @@ -155,9 +155,7 @@ def set_default(tileset: Tileset) -> None: The display will use this new tileset immediately. - This function only affects the `SDL2` and `OPENGL2` renderers. - - This function is provisional. The API may change. + .. versionadded:: 11.10 """ lib.TCOD_set_default_tileset(tileset._tileset_p) @@ -204,3 +202,24 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) if lib.TCOD_tileset_load_truetype_(path.encode(), tile_width, tile_height): raise RuntimeError(ffi.string(lib.TCOD_get_error())) + + +def load_bdf(path: str) -> Tileset: + """Return a new Tileset from a `.bdf` file. + + For the best results the font should be monospace, cell-based, and + single-width. As an example, a good set of fonts would be the + `Unicode fonts and tools for X11 `_ + package. + + Pass the returned Tileset to :any:`tcod.tileset.set_default` and it will + take effect when `tcod.console_init_root` is called. + + .. versionadded:: 11.10 + """ + if not os.path.exists(path): + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) + cdata = lib.TCOD_load_bdf(path.encode()) + if not cdata: + raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + return Tileset._claim(cdata) From bd9755bf6f33b9420e99d9f7a2c73646c1d06209 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 25 Mar 2020 12:10:01 -0700 Subject: [PATCH 0285/1101] Remove outdated font files. Now that python-tcod supports BDF these image tilesets are no longer efficient. --- fonts/X11/10x20.png | Bin 86340 -> 0 bytes fonts/X11/4x6.png | Bin 6177 -> 0 bytes fonts/X11/5x7.png | Bin 12275 -> 0 bytes fonts/X11/5x8.png | Bin 10796 -> 0 bytes fonts/X11/6x10.png | Bin 15080 -> 0 bytes fonts/X11/6x12.png | Bin 40656 -> 0 bytes fonts/X11/6x13.png | Bin 39730 -> 0 bytes fonts/X11/6x13B.png | Bin 17240 -> 0 bytes fonts/X11/6x13O.png | Bin 12683 -> 0 bytes fonts/X11/6x9.png | Bin 12129 -> 0 bytes fonts/X11/7x13.png | Bin 35920 -> 0 bytes fonts/X11/7x13B.png | Bin 16001 -> 0 bytes fonts/X11/7x13O.png | Bin 15158 -> 0 bytes fonts/X11/7x14.png | Bin 30561 -> 0 bytes fonts/X11/7x14B.png | Bin 17035 -> 0 bytes fonts/X11/8x13.png | Bin 43701 -> 0 bytes fonts/X11/8x13B.png | Bin 19912 -> 0 bytes fonts/X11/8x13O.png | Bin 21852 -> 0 bytes fonts/X11/9x15.png | Bin 64044 -> 0 bytes fonts/X11/9x15B.png | Bin 31763 -> 0 bytes fonts/X11/9x18.png | Bin 64411 -> 0 bytes fonts/X11/9x18B.png | Bin 21415 -> 0 bytes fonts/X11/AUTHORS.txt | 42 ---- fonts/X11/LICENCES.txt | 42 ---- fonts/X11/README-TDL.txt | 10 - fonts/X11/README-X11.txt | 369 ------------------------------- fonts/X11/README.md | 14 ++ fonts/X11/TARGET1.txt | 23 -- fonts/X11/TARGET2.txt | 27 --- fonts/X11/TARGET3.txt | 30 --- fonts/X11/bdf/bdf2png.py | 184 --------------- fonts/X11/clR6x12.png | Bin 15062 -> 0 bytes fonts/X11/helvR12_14x15.png | Bin 40498 -> 0 bytes fonts/X11/{bdf => }/ucs-fonts.7z | Bin 34 files changed, 14 insertions(+), 727 deletions(-) delete mode 100755 fonts/X11/10x20.png delete mode 100755 fonts/X11/4x6.png delete mode 100755 fonts/X11/5x7.png delete mode 100755 fonts/X11/5x8.png delete mode 100755 fonts/X11/6x10.png delete mode 100755 fonts/X11/6x12.png delete mode 100755 fonts/X11/6x13.png delete mode 100755 fonts/X11/6x13B.png delete mode 100755 fonts/X11/6x13O.png delete mode 100755 fonts/X11/6x9.png delete mode 100755 fonts/X11/7x13.png delete mode 100755 fonts/X11/7x13B.png delete mode 100755 fonts/X11/7x13O.png delete mode 100755 fonts/X11/7x14.png delete mode 100755 fonts/X11/7x14B.png delete mode 100755 fonts/X11/8x13.png delete mode 100755 fonts/X11/8x13B.png delete mode 100755 fonts/X11/8x13O.png delete mode 100755 fonts/X11/9x15.png delete mode 100755 fonts/X11/9x15B.png delete mode 100755 fonts/X11/9x18.png delete mode 100755 fonts/X11/9x18B.png delete mode 100755 fonts/X11/AUTHORS.txt delete mode 100755 fonts/X11/LICENCES.txt delete mode 100755 fonts/X11/README-TDL.txt delete mode 100755 fonts/X11/README-X11.txt create mode 100644 fonts/X11/README.md delete mode 100755 fonts/X11/TARGET1.txt delete mode 100755 fonts/X11/TARGET2.txt delete mode 100755 fonts/X11/TARGET3.txt delete mode 100755 fonts/X11/bdf/bdf2png.py delete mode 100755 fonts/X11/clR6x12.png delete mode 100755 fonts/X11/helvR12_14x15.png rename fonts/X11/{bdf => }/ucs-fonts.7z (100%) mode change 100755 => 100644 diff --git a/fonts/X11/10x20.png b/fonts/X11/10x20.png deleted file mode 100755 index 6e6227513bb9b63f8490f435bfe7a213aaede224..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86340 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

WcP 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 N?lZ%uRhqB<`Y)^iY=Hm( diff --git a/fonts/X11/5x8.png b/fonts/X11/5x8.png deleted file mode 100755 index e31c12f0ea05a045e75791c9d7c42aadc0af57b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10796 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 diff --git a/fonts/X11/6x10.png b/fonts/X11/6x10.png deleted file mode 100755 index e6aaa0e9ab02c5edf033618f6da4c3b63d372158..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15080 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 ChGY2v diff --git a/fonts/X11/6x12.png b/fonts/X11/6x12.png deleted file mode 100755 index 317574baa848e5bf3a38341330d2831e14179536..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40656 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 diff --git a/fonts/X11/7x13.png b/fonts/X11/7x13.png deleted file mode 100755 index 86c0668bb26504d34d346234dd6fa80044b69289..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35920 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 DFGK_B diff --git a/fonts/X11/7x13B.png b/fonts/X11/7x13B.png deleted file mode 100755 index b152ce478469281b5410aaccf84771b51ea1cb86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16001 zcmeI1_g7O}*Y8mdARuBvK@bpqklvJD&jA(bO0Nk;dat2_f>bF=2_=9W1ccCAs0m0D zLKB1lA)$sII)sn}F6VjPd&j-+fAEg$`elu|#@=hL`PplYJ=b@}zt&Y}Vqj;WqM~Bb zc=cSLit0)y6_q5_waWo262*6iic0yF#`C`n10i?}ITy^rLRKSV`8u#9^;z7L_0@YmaQ7`)GNRj>2(vK+<=M6>BbV(dqrO5|%l zo^;jViIz12JaNcpQ(IS7s2gFMXxMb- zl=ynn?jvvS&653qSw(4PS~(GWNxgzKGQE}y@U6V(S2k5`w0c;5XFK{Nz{otnV-p24 z{9!()9iYpfL0xG64KPfQEKF+_EYISo(#1MT4C6w}vlE8DICrY?C;5X758u41Wfv~y zUK2hRAP3)ut_YOKPQr;-!?BII!*-TH za07HC#NY3QA%4F>aMGc{xp|8$>e$hgdw2)l@>m5po({Jb=y$etp3h7yC?;AsXh-Gb zj)&4@>ud7F{5ql5k0Wu5ZVe^bYuH*r5iqLVN&hDSq%s)CoVg z-3gi4jj4ynXc>2l#(aD-8Sn&D;@^?u?DaOvbLbhN+LrwRrDe2ZpU&0-(K~h8&&a6UH&{)!*zW&dvWB9-lj-G z(Z9E0NO7*@=egfY?*HY;mi^J}=AuWgd|kBPI71{h&2RWQ$UFR|lE z>_>s#MNv^?FT~4SC(&SWBq-ijXl8CbZnBnrji!9aEe{jkd#Bno)|GiNACE-E!&K%h zLn)*E`tyepdAXw!PD;|s55y?1uW;$bAr?azNPmX$-|FAcHf-IX3+6B0g69}oX+9X} z6`z~6Ov@Ay=!J)9x*x#*BvW+Jdt?PxG0nX(yMo)5_ zbsU!*9StrrO03*4=1>>F zEVFgX+_l~S;wU!{qlC6%4fGL~^iM_zjb}!lT-Og1e6w6xKFkh!6xCnE2O~*1S!7otU3671GCKBkXgkPmqEq%?THPs4}$4?NifWgrE^GZ=-&)=)2_%7^$}~J>=g0QSd|K8n3aJU)L|muXJkH+k2*%iu3MejFyt& z1{HcAjlN0hD5W-IW|K3a`7N^$W08Uz#o>xGcPN$N#iI(j5V=~VuF>0B0wLC$+R}Q) zBi=odG`Ci%FxD-K7bgwm$|<=IkITfy!|M*Sm19K*>-~a4&;7Wv?Rbk)eWuMtWAzP` z^cn{nEY~twJW7={{nNJvBcFvvdo>8y#IIJmjGJv7F;Z`8KY)0@7J-Ty4x!fM#Fp7* z&8viKESroAjE0RSqZnlj#4v>B+CP;X^TW<+fVPe`6&2eh2Y+ntceUqK`C~cKJF>r5 zmfFxoJ$`ZS#+Sx<47gEEnV);hdEako^`T0SJbT5nwABd=c44ff5{|JXAB7IuY z4iy#0Rj&;%^J8qVC9g}ETEAmf911dq+3`yO1euT;K&S>Nt9x>@?Rj}6m#zh;7qZbz z<7u?fvoSse+4LBJ3Rj$$2z1h@-(C>Oa_xVzE6|N?EomWM>?fR|BhNkh-Q%ApXHTJ4sSyApHJ#mnM&~ zx>=kJsKRU@(A;_6?pbZMb#WIHQiXP~fo18yDU9^JWc6;>q$#PSWZBNZ#TPsA^xmM! zy)!wK9mzM75IFY$sZcRX2oh7W`xn= z%uB1&SV;GvmvsfYu6uum&l&C;04LjK0iS)=Ne=YlJ)KSNaQ^r6_gl`(X8L!B;I%LCL2izu zrVvS_EheV%F;uF*{j+HJYZS&m9Q@2XV;xWxYjT|jDD;cTD$h+upwzIKu**6G2E@rF-hg%rF`4rtHj@c3O|zY38gP5sA>i^@i-@1{cy`dXQ^w z(_;^}`G$GBi79D2+|2XrD{JiH5#o4+1YF?NKUMF$bu)a)9vpI>T=q*eYEu$i|uy~LO{o+ zY{Q@H1Du32nyTHN(*yhMh1c4cfYtp=M;ZBx4e}qxk}^!%+BeEdOwYA1ek^vd*nW%f z1KGKqukc0RTsosuYOC3qx)SV>N#WJ{G;CJ>q<2~;m(f`?E5HpN^fbQ-oRmfA*;;m+ z_%weZt15u7ZrgRNr-Vn%4RsG3wCx&M zh{^wH0DKF*ZE3!cPABp8j-9{D<>nP?nx9oP3)fN?p0vZi*u#E-z+0K=P^jFfa1sA- zgD|7!k9pdK@M%HhJloc@)96~m^WFTwA=@k9|gJe=NhCTQUM7IN6JR|)WHQloE zctWtYro7@YHc=0G#+kkKQEgyHSPrmv-iY0Bc<;VO*ZfX@dws6+OSLHgn`S`HZ7 z(Iyc(&k=$)eMQ;FkNiALqddbBS_klIhs=3h^MES|T;7bt*K}GjNW=^IMNay=yU=&b z?wX$RE(Waor_Zov)anvTr={j&+esoDV9COI5FRDNl{Rk_88IeCpeBL0+C^tjbQPH) zOA-4OH8?$h=|5Mr%cj`vzr!g5YCSZmX#RL^VHz_3ko06XMD&RMS97D*UF&OGnMV}n z`iM-VqH%M0(5q8u3eAPpanP8Ymru)+qhDYWqSHQSEUKE3D36lM0s&bMIz$bq4{5aE z9sOC| zQSX2kcFfB?P{TIuuQv_IeFqASEyxtHh)#gZ)BTuv<|~(hauJT1<1H`B8pOo91cS3S z)tL?AoWsGo+L$5SD0vpT3AS6)1AnnNG}B$84tHsqi4h_RR4WeRSl3`)C2Xo6CHgiz zKY9nhG2=r}Bt*6P7sCf|R_6h`ip!!Y+~ERlCf^pl&u`W4sVwh%uJ>*3#?$=67yi11 z=kOThP*onnsaT^S)gp~kcVdp(QqkWJ8%L?A`}ngjsr2CY8Oymfy>GP3(gbR-j{pNm zPv1HwJAD3|%tV*}pPZ_Iys-{9Ke zuNv*`)sF%(b*Jrh#5X$|W#sm{1AY+TSdt(3-E1M{QgX4 zwA32k2^jK~C0A@&_|Dd@S&zfJGx4%I!*KdGOou>#kkG)CN>PVFIL5;n^xWm?&xl3q zcI5ZXiGUUk8aAkkgp|Q-k?i#nk*!x4$f&FmfutPWa`Iq|Gfp7GR=2i+_xRn+BR?nwSD-SRc4KXbqZ@HC_1D^;5Zk@w>P_1X&rHSjE0F z)p8z;8b2y|9cG&Mcs8@4EFNP{3mg+->`UV$2231Ol5-O)bAAMGl%%dwOBUB*h9MYD zFwMa`rHf|r`cvB@OtZ~-OpCjZQiM!Z=nIcMotDLhfR@h%L75Ha-I8k$+XON)@}TeV zjel0CD7pu_xJm9M774wT}`f!TcP^M$*2+^0N>- z{z}U$Zko8!qm+)8(%nL7#s@M0e}Ij!TvA(UgEvJs{mcqEJYVDf?BeprTkvEy-O+AinzZgfYHsS# zuc{6Ud^Orsd<#{@1h1b=KhE14K=PT8Zmp=;vxNpN`qb>2R%Fel<%}I8L%%oW*2+d>p3~hb&WJGZ${R!RW!(qj@ zWAaZNIhTe1IwQSzrjGKp7CtU4cpb~m)ed+d3bB}fa<9(dqMR4a`IP$*SMu`O1~!Xi z7PW0$gi8%Ici54o3qXAKc_<{~IOF_qErL_Q+Q6SSKX8oio}_FyZex>%VgFIswepDeSBT9d zg>6iOg;vM29}~xKfvmRXdDIaqZ)=~9=+9-JkfsO=^Le9m%vr0(Ho@3MKFirM>bS5T zS2viQ5`df?JD=7akKo7)dS68^()Drt1K>M7C2WsHH7McRAC}+O>xwot#3XX<^Cru; z_8f8|GJH9wG;n#RzJ`tri3IO2ukC9tYKF4Udcj@f=AZMzY@l z;mx7Z`UAggHL*hjdSCeJH`RRJSjWw2M}QW;75QY=cI;JB6$|o{F zpd=qW2Wk{zVqBQyB4LQ!7*vf`3Xcv7><;xMq-m8hD5E;z!5Nx&)MSbaeBH#(q=Hku z&;SFh{pN_2)clq#PYnJVk%AdXLyG9E|D<8&d<0rWmxpp)PlIXkbue@*QuF6`2 z)pW{G1s6~E0{S@=RMM^Z3{`UT!HM|CGf08INBl+O%PL|@<92p>rv(0v?^$8`CM{C% zIe>Ybh<;luFPTG`$Y=7oCBrdYDPAy>6P9{tBJE<=G8wz2X9t42z=AAcrMdjhM1oId z<(=IdC%^7KKNnx2_9K3u{PJOd(Nm?PiDkjSPA7y0p7(m57^koml|3r9&2Ff+vj5^sdvpef0{~iBkWz`{OMr86IX_wVDO4 z9M!dAG}uAzScuxb1lvxa#LIU_i^((aMY%`S>7>NLo6k`X zDjb9LGXb~gEa%6MuQg?y1J^- zw${`VJSEeoyxp}Wb5`aNC>&BL@^vS9a#75oqTF&W51_2Q5&F)M(T&R{VjS_H(7V~txbjZ9YJ_Xk$lLlHJiQm4$T4X8@LjZLW1`p;HP*UIl+3jtC$%yI={buy2e&O zy*yq8pZgho1H^#MCW;;3HComod-U{nP;_(z=Y1*Pp-r2qtSRHsfH}{OZC$Oie<27> zs`xbnCsi@sD*(vV%Y@_KV*Nj<%71Ubt;C3lNimq^O7BUNn@1#2t*Y?P*jf-(5t@pZ7P2dGSRcq{dAu1d}R^sYJJ^V@I#im zT2`O!HSaUYRWr4ZJem83dbw*AboL_+=>ULjzH!1A;hS2S2M4pvt_KL<~0g)~5IP}~=0F!3lybdFIG&JQ{d!khn=TYv})|zq>f+$qb z;`z$gpi37%p(x_M>4KdTFZBl~H*5@rS5jT(NpfrJJjywqa27!vc&dJCt#-#t19uou zXvk{8uo%cCx41gs5)tF#@~p`B;eTiHKf|8mE#{bjw&*uY941GA#E1g=ak+Msb)>P9 zCf|nxn|z4sHJ;Tab6P?MFk!KbMt1F>HtfXlI!JZyJ|v^WisE^niD%U&L10CVHzsxI z2$%b!aedCEg$lgwRwtL|L%7%|7e5l@^%?s zHiW6&m-H>Utke`^^t$CH8uEiC%gjBN7b^SF&RVpn>=Y=ZlD50C=cej)fxeAy=Tz9E z;!U!Q80&$=UT6yyy0L%9Dc<{dQSlyBDNXew?)CM;>#Cq{B`m6B)H_ zvnA~qZzPeg_h45v3X@%BklZY~v66)Fr>2cz;a71jInOjip?3^4!(@ z3#mf0(9Cf@w8J#mM3v`jSz(@-E;b|@J1%t%kzF4ohHF}P0O()aVn1bYzEr|VPn-k4 z7g~;8Mydj!Y(9SC&l#mxZXYqjS)4U!OZ2-9)zk7ki{}w(!Wc!iF&0aQn}P*LQT?i5 zXg!-MJ5B6C?|SC`ziiVO42(W$G7pk zO8V~&1cvxf_Tv40wh00z=a!YWIm+8bmHwuZ^Gj#uyX!w#m+zQWoHsdkPHX2pmJd@W zT;4Q0XiV`NybkkoCr-z#Prc6-u9bvW7Kpck#n}|yT5qwfby?;fm}c`vr>}OU2SO5r z`%92+_V0E@5#0wJ$oUbvJOiC?-JJh3&iMb-RYn3d@q2SVj^N;=IwnMJ67cAiT?76;(zV^e#z7*%)l^g>!0akx&Y z;XEM-Ews{>u-G=kREuw_*c60KIG;M}tn*LaOEIf z?59jWRwQ$p(yyU6BQR>F#sA^1XL5(h4qeGjl`00^e6LbLR9Y-d>_h5OVJ(bEFLzFE zsqK#aUVo|CtcsJm(WscUlRoTfk(%?y{iI7N0anC4^Ig%2zBp{bD$a3dZ@%?MU}H+v zyZTtcr8!?My*8Ek02)Fcc8P(8joX>DWEilof%i5@A@u!F7C+tBXv&YbL0O{oF=|1Y z>bU&&ufzN4hLL)Ni2`)vGHdZ8z=WP?UfH9}I!zwI7NKtn2aVGdi?*{uAC;O)w-CF~ zbRoZ$nf+=6U9C*tn%T&^!Tryfq<8fl63VZ`zm<_;aV>w zwss5@Xd@@s2~NO`LIb~TGy=|(?nvz|u`DR`psWU*ROcVTw*w-thSqLgSwK}5n2;(< zo7yagV6+#T_%4v=yur8yz|A6`$(>f>Ll&KP4zKd*lewY|rPLm)4JBt%4k)8TxdVqL z;&#dYMDmAL)36A5PQA|g>4IS}KAaw@aQ}oh$|`vDq)~yi_ay|^)k2X2Z)SJtX$b1- z>Gs^TJ6H9|f#g=yUR(d`<;6fm0Gk^k8Nd*(Px(-XTT8Dk_kolsJd`;E)hl1hdLw!WVcy-0`3UuK@ zCfa073w(1^;)iPk#`kgN1lV1q)n|*SH5b9j_}asb2N<}C2VTeV{fwSBN;k;5zg?lD zLuoqyov)z%*iew9Bj5WO?nlye6Z^F?GxJea&7r<6%ZeE%`rZiwy`}B$0qcwm;ux8% z5V2A^tJzWdQP};8_#QbRck{E3r7psqn#5h zKJl=c{53t( zV3!jW9$_oCmSoUl*w@vGhm7(EAZ5d5O8|sp4iERFBM+v{M9l455uz!?%h~7MccdX) z(HpksM+>J_Z4nX5)78Oqz;OLDsQ@wC9c&&*hS*}o~km?gthXPQapWoL59Kz@A?s52P^td6vMBi%>)GF^v zqc%;5WQVjRm)gMgXKZtL2HTs)x}Uv^`;?YD55>B2>H;h7#Nkc`%8)awH?*B>4bbDH4Vp2@$t{$4+ zFpVC2@lvcYjBeQ98(XKh^V07M6t9&?`7s5kSfil@{gDr~A{2AEu)o}29Unk>;nN8w zoky$nlf?|;(KR>LOM^AbtH%wlJ5N#q8&CKC@)d;^geC{-4!kFDm>u|(PvXW zA#gBXDu+9Tvj0;xpo*LUznHqf zhGZeOlgZ1~b`(i&vgsleFx*Nc&)caOo@gnz(!vREkV)JUBrk5ONU^;I=pQPhrK}4$ zm!7t;z$+!dU~7uJaywNEuA6Z$PtUyXU`_MVtcI!0<1a_HQ?!)RjfvEqCe0@=zHfpm zGr5n6_qfAv`D)YK=mI0p^T!z@zEXjKO6&<*>`k6=VC=o>XJFVBjshccee@<3EVvp8HULeQgya9 zdLzX?+A8;p`8_U&VMUAPHYhZvcXMxt&Y|11aPYsv!y=^mf*wh*5>C(>wPmiG*di>D z+NAgHTET>uXnVhyWTCfG@Nns@%J*0Zb=_qlrfxWR)ZjD3GSZoq};tqMb#Pd zbp={n;87i^-zw2v+C~-vbJw+tQ0f-jp{t~t4kuS5H=r`7pZ+1K*n>RjPz zPma2il7 zY6pq-l#pQ;E${H=qkXw{+&)KZ*%@dkVmuH#>YM%1BdDDUZ6{mY6^oNohK0PnGFqx#J%1s2G%cM-$B!7r=V>oK`l zJ`s=@rBg-gzcipg@9?R`pmOdvL)^bqdI&kd_&3y-^TKr*Bvw;j22YMk{@a^aS_$x~ zkkOtLn1f-@d3*s_P57JpuXA>-U-0L-b~zDT3Zn<(LSa&SJY{DVvp*b8H^Xz-Pvayn zbe&zo6;3PjT&4CT4y!Y(G7>Y{^qtVZO7;JV09dpsb$Km}t1f@fkQ)0n6+7#`)G`j_ z?@Axv4dYn^=Z#|r2Mw~w2W7reXOE}aVdG}w8J3)INk#7iOmr0Jm$tn7R(^sii z6?g6keYvW*d`Ii`B^?t{?!)*}#Lf5Yfn)ud1y|eUX##)n0pS1rBmK626ZlQwH-X;- zeiQgj;5UKa1b!3vP2e|y-voXW_)Xw9f!_pv6Zrq0fZnYL1?mT2)z`MHnfmA5yv1Z7 zetvi5L&!(L`BAiQ7xhy4&ASV|&Z=Ven7HzH;M*p7*E$jye=_)!r(Rl diff --git a/fonts/X11/7x13O.png b/fonts/X11/7x13O.png deleted file mode 100755 index d9508de83c1202388208c71a46ea87d266388cb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15158 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 UILa%@{+o-DzL{R-edm||0aMCGGynhq diff --git a/fonts/X11/7x14.png b/fonts/X11/7x14.png deleted file mode 100755 index dd0a2a390d3ffdfa0d882fd65a12a4d1792cafe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30561 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 diff --git a/fonts/X11/7x14B.png b/fonts/X11/7x14B.png deleted file mode 100755 index 8ad21b3cfcc44dec690ec44deed6a4d0ccd7e69a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17035 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 DrWdl0 diff --git a/fonts/X11/8x13B.png b/fonts/X11/8x13B.png deleted file mode 100755 index af13a50d61592e659d5ff2436f7426387e6490c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19912 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 DT9>(d diff --git a/fonts/X11/9x15.png b/fonts/X11/9x15.png deleted file mode 100755 index 73a082faa2dc0957fc129fb8d6d38be3591fa244..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64044 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 z zz7BsErRs%|WUGcURX%>z8hMBZ?6bjpGgNTI^5fhqJLv`rQtd+kU1e6&A}w#&3ZZAi z=@<|V13nMQ`TJ6}6H7rf=2CyhCI2Z&D(Q2P`WjfeJ8IH7;kSmR-2k6WS%;Ve59hkP zml<#}WKeX-oOm1I=dn*RlOG;P!lpj|Qyn9>I}#DZ_uN?HEO~W#uY@|bz2e5<(+d=k zIL8_Z$&lcif3L7UQ9#enkagkcbDsAcOj2c-N8192NBbwyQo$NHe7vVe*&>n?+ZD9h zvM$#Uy`|8oiJxW0DPBW}{qY#TO$j}F&~OdMb4U6X_YUWIXixWR>^B!QdzjV8p6;&o z&FT)lHPkvc!XYcj=C5h=-W!8F@+Z>`Mx0$^%smu_UGo^n*{r<^NXuJdBr=ngw$O0y zTGMpTR44CvI|LzVL*>eix-A}X+xGb^Q%cQEj>|Trpl=lIc{$@%e@bc4od>xm`!N1i zpBQ$mGV|fjT16wNZS?qA^RfpF83VAXJv5;p)ep7P`scLp%VMaxS(3K!i_M98h33@0 zzxEqH3g$8ABD_|Tc)>opB1{fQ#rf;hwYuD>3PZPYtr5<6G&t0`Ywr%T3r2W-L$iL? zRZ10DNY_;A-PcK{c*Oi^+no(PEqIFaa`k&t$nY3+=J>$bF&6?CpIjdw`!IoHMNYj+ z1{9cpi@Rx?G#`dp0>4)5HVn|f6dsg}1f$v5WFt!=__8-c@x1(|DmqUmDvfB9=IE4K z#9R`s$ypnv=KE?Rx4ol-Bz`(DWOKsHRJ@XkrzwW?)F(#dXV?3?%OypPwD{UvKj<7h zkO|PNWfzwb;4^u(+pYgpJExn`^L`|95UcdkT>=qp;fZ#U_RL?(U@{9z1<~Y@y z+04j`zb)h3oEsc}I*ET@q4)FoUGGOfBY424LS#w*qUSW)TrI=l#0_>u`L94uY@nv; zpt6~}LFC~!YWCmW2LM87+DpfrY6ONP?i#7j2&^hiDAe_qOoqKF>CK9uiPz2ft zh=9AO@yC$N19e1$+1RBta`TOU5uH%%MVj#M+@#!1sODWwAR+4A(5v~;*%H|pNC$Lp z0z<~$girNNpr}9QP*Jl8Gfh_Fo>RKqy$GZv>xolAq`QaJR(K_HvB>lCk*NOGMqt4R zt62qqZT!CNYmrivlNv;B?SWjtojF%oEy%_o?|iS39tymij2tg`+m`Sp{eXD=;vH0P z56Y~ebe?(?b_Cg*zIrKd0K6xxn9uB04&?TJP0)qB+gxP7|j69dd2uRn;H^2B*Wo>x=@i-ExPQ1SH;S;4H8$bEwx&|_EHX58t) zxtVi}kHBx60*y=Y$ydflJtPh!W-E7}+6a$ESDb>b+{yhqjA+_u!J?VNonJLnFg3XnlFh%0_9iajQ_PqPy&*8Bzh+WRf?PbCY@LdB|36OZ58Y!f?gH_4HE zLR8X<^Pbi&GGvAN_3ljwRA1~h>cJWgWyYduJY6(evv=Rq8-6!Qq z{NF3_|F8cCHgMtozgU4pM<4!|nu$4*S(Aa{AEwV`uBfx(zGx~b{bd48#;p+dQ_xJ% zDzYjfW4A7S@Bix%69oVh4|2)y1vvZ_9`3A8--o6qYb9oX8aCrm>u(e()(&I`?zWav z`1x=t?hxe1>0_pG=(|O1@}z#XFwj@IAn}cXy|JCrrDVKMPwG)u9`>pJyYI_oF0K4` z=@(%arQeHF|H_uaKV8K-?-Gz&MMrv{`C+%}MQ;`PkgS~9$>9Z*$u_k0cwB6ric3^O z({FhBj{noFvfN{;gJ#&IObe0bOBoQ2F{#|J0CO7mBy_V$_EvxWFG1l3O8BWbxq=ll zcJ1)O6k19nVs+?*!aru_p&BaF`e(ty9ru6Od#j+hx-Ja!OM(OoL4r$ghhU907J@YH zE+M!F_a=eh4vo9hxVwbl+DPN>(6~Dc|I}Q~_0(KU&AB{vPHkU%)!OfRHa`a)MONW; zd^r_mbR^d$1Tw+~k+D)r%@R@O&2?{OWdbGAj$WvIi_^%}k!CEojmoc~t(M?6QeG7w zW3re)hvPbsM=@D(2C+lyI>Xd15SzpYatsT|LbOv)VVF4t=XGNYMd{9oUqU~_oITTb z0f14uz+>q!N$q$8xCHB_PDJr!xX7+8xM)c}>#tvI0mqDBm{riiQWTcr1Dy&`SX~Xu zLn6Q5yapBSR}iRmO5>z75`2>nx8^MlX5>XBG~Bj$uVLUlQ2=q8$&B~Xbn-<%SXDeV ze4iy&EcRirJXQszQv4{1HtVe0;TX#^ER~kr*d0Pp(nD5frc#u%)v2>({+ArSGd_^$ z)^pA2IwvGMLrUJL|2_B)z=dYpN$F?+Rk4fB$8?jQ4YKZ2n#^H1qh)$`K`Xp-59=xR zBLibws4xxhIo2_mL}Uh9=VeHB6FJA2S)C;lFn%!GAVqR+-V0(kRw@;uQ-UXUCQ;fW zOMdD%mKlxtM}6`vxkew)5t2Q@D~J0W z3L;+*9JUy?iVb|M-z6C@&{207M?bjSerj9M3W+M@DBXt8%GtLPcaH^x{`zoGSu+8s zvw}Bwavq1id-Ot#6kqeX+Vh1U=1@$1pl(2s4J$E3*uk+Sok>K>v3 zLCpnN%pRWj%sAKj&DEyo0z*d0-EuNCk*Iaxv@j)-^4+{>e%SbIbGbO>Es%g>;TLq+ z9((^Ex4zx)6>i?+B5~<}rVH#YN7U_t49TNl)gv{rldIaQhPMfnz_B+kKpwHb@>@!| z1c-Qpmf7KpQwMINyeel@;dp~Go8mPt0eAAhM0>N;*H;kdm+lkE9)W4CnHIXu1RNu3 zabh;j=2eFNLcUqWxxmB@Mf0fbeCt}7p+>dIv`RMP7i8Z>m262)pz_!CH>=~Tnpf>t z1EWFtc4&&LY0Aq4vZb?6razN|5OV&F%EQ41ORs&$Fpcq@Cj{-_35n~#LTI3d&^0d9 zG;!F17@w|Su<&}R2ORz3h_R)a?#D+*-pd+K+@mkEafC`5d)D?`HfJ=PKLWf=I4`*i znFo=GYk^oigMmbx|1c4j>IguvLSmz3OeaqM#0jeEtSE%fG2TfJa~kf+)2*0Y#IG1; zMjQ;b@)eTy)6-cVQl!AcQf`O+;_WO>PivqTF_Zbg2>FQQD!5$rT z92fQvMBQby_Zgo|ik-xMQctRpK#or2NZ{BwcYKF@%s&#k$m<3KewJKI!fMgBH$%xO zjSWexNmjGu9Hq&x$3!q;ok%#mzYT|>@>n82mRI;ixsnmv*vr{KI5@YL#w0V~Z0swJ zB#RSSiGe1>S=g~{58v-sg;F}9G**X5Cs3p!A)&G^@cx-( zaL_%u3z;H37lZa9XMt+;`k^*Ttwgo#1}Bl-ttrsqxv%ADuslHI69C1lpdG7%#WqMq z-Rj6ssZQro;$EjL!P_R==zFq@54WgbQGA4KD-svuDbJx~okw>rcjI{5J5Rlvrr7f= z`GcFOBqMX=*_n?!kKH%lB#h@DcC&1}O7Q&v)ke`-VG$JILf|y!KZC6T2aE@|uC;FH zL6L&^{k@qx35P_Gkpe%8SNS3-`vV(}tgqZ%0o~^c|2wS9-;H3>A9?M2zwc3sz*+yi zI%fX6GPl`lcFmGWyQV4LPj8-(Jj!YJ5$-S5SoHp9e3UG5)<3biv=F3@p#p0zr|?{T zcVhI>`2zVyH(4E8FvM|`-~R|#@YcOFc}IALp8pAy-g(}?c#Nrvm3>?|CE>!smqn$0oC6fab8T9{~bNFGX8 zy%1er>-8i#tZvVDzL!;4-L&8238=N25|1AY1t;U7#zc2MeoB}3(u&;Po^QKzIRJ`N* zGdaNnD&dC;{lk5AJnrV75A7$9>f`tR0+`9+w*)5&BiLv_R45DdQYj0W!+@;B(OLQocqPFYTN*O39n2T8uM&k1h#z$NBldgj=clAdJpwbc_94>EObI8w4C)B)Qz*;(5u!)L89Dq=3Om zFE}veY$i;h8}ZhzRutt-Bts#a&f+W$q*0%SIbL&S5ulFdHi?&K5m$a}-X|t1P7vEp zVL7pu;qDx*jA|n?S<=inPF#yyrf6djB3PMz5%nw+zBYs;e|#)? zI`y(UhOqW*shu@%Oq?}1@>}=par3Z75D$yh%>1OLM1{JpFJ{mAx^EBhGl9_jeV$G* zsuC}WalVW522~}bjotM z&vZ-z$X<-g6^a3WioS+El%il;=P*=;_d%kQ|0Ez$;}%Ung(NKBmx8TsA7wg9sQLyRLNcOQZgi_$K?o0+PzvWfw1~o=BnMSCEvvBF|DJq0gN#y3q zh3ADWASsCBc5slPR4xpE_KT}R1yavfnl1)P4)09LE5P|0#dL)2c90wnTeI$kZ`Agr z$^4@Pu`ND2Zw=B_9Y@-ha9xWpOBT$fk}l#GWFzZt@kB}%J*S@h+migPhcLHhf*B`O zp4@M%76}yj__f-{U*Pu;-;bRwIS=WwT~-b_e@v14JD9VLwn6MIw!6Wn8*RH?kv%zV zp{uNro*tZ&N}N9$CeWC80pl5<879O+7)OX;n0_8lPA{> zjn+G{_RuuXF{WkPnvBd39QPAT(YGRY*Z4%|aHZiuVr_Z<8=nxTlG=o0c;Ani!H50K z*7&$-8KU=vTPn_-IRiFTpGTL#NKUS4D*vp^z~2wf-fNkoI-a^muNW$pDz(^JuwTQx zRbCF@cjp*go%Q$*gC*uB^tT5s(QnDyv_GusM)2X<90dfKVcAk@Fu}N1V$iL zMUZMm=X7*)&ZFXdAAN7`^D)W^&&{EP5|(fS5}Ayuy>?dIcBX1qmSknY*qL%n2x@ebGw7X8dr0}1S!6NL$Y z2A!Xlu~wK0CZ_&f{9|GW-ArLpUb^36Eu=?zvcirc3jb@hJrAn+p|}4E*5Ui&7qU@Q zT+gSFR6_aS$LGOtVzqv!2{aN1*ywn7Bi3Uf@iO+EtzAOp!bzrvJeZFjq%InM^j&i7 zbhE3Y)IVj%UW;k>(8qubt7WK}^5Ra^pt*Fu`*s_N;VdChCT;##G&k3_sdlPC*!{r0cHe`9U z)>HFOWC}7bMg`>xSF@mn1!bWHh|Pfc9&}dTwP@j!OLe_^E~fFi++0PfYRHi1Kwn$7 zL*GS$?E{{Sh_3x463G;-zeh&Tq4)6Ik^b-L3vCpaQy4Y&YnW8|qu7nwjP$WudanNH z&X+Cwk7|T7u=J{Qho5YjFHz@gf(2g8v$_B5!0kJ~dQES~&Mn2!XF)8Owg&M0@H zbm{!53sscU)Tryy+j8prH<`@4S(!6}3tZiJIKm%c5)(k832vaqSoAdEfB*ee`dMZr zujPx<{e_Ec7Gr6^*3ZN!n90eG=-y*I8j}!!(Jh}_1}<$ADiUGFmLSe)zZ3_zO$FYo z7L=JCqZLmlVo7}XT98klzZ=x>hZ$GiG=TU!jw?>>u0L?-ANC$aT+zM5x);*eevHJA zqM{~EqbCOqR|Um+9!QzY(?mMl91(l8e>r>1Wk$T)8NPNi7t28BErP&0A2`*c+e&Xs zNJQi$9-{XhzG&R#Pbv zZrYs#`y|+Oj{Ks07dHRA~>g!{NVbBAwaG6xF}Y#7a4h5jl6{ zfjCpGA69wp{NI-^aH@0ylWHA)1+K(@%;}{5PA#;su;8t;^GMK$e!QS*Thwlt4WJVq zwRLQ1&eKK&)AEgy`_`@?XXP@qOfOdkGT)u)=Uz^!Q?VVM>hZ_}vJXKM2U8Sb8^r$2 z9)+7p2}4HKz9;?MWL6p$8mbMy``;0%^ms}j(ul*i1txy=Z*#se;PvZ23w`t813Ngd zJBzT$Ej6-=O-XLt*7n?go8kS!%D_q*$p!_=hV zf0@qJe^vrcXd1OdbTJ7fC~ffR)fc_xb(|(S_8NF$-=W3tJASq_mrc2KCX8gsQh?Ly zvr;0xk2`fgOvf!-ss?70k8)*jFj5-F^aLE1kz8K}*MFEk{iL=Ci0!xqTk7duyDJ(5 zq{iE2>MkbMORnL2lH_s%YQY(m=njMOSB{d_^|5hwqI(yjGV!i+poy$LqcwTH&7J+_HSgeVuwP zdc9}*(0_?!2#>qUX>q$!3{W6}*iem|S@>NaX#}>gM0e)co(e`W%^(He8+vQ*FngaN zl}vx;W4=<1htwafnK6$;QP{RpLU3LB&WG%PjoUg)vD%BKY#Vf(=9-N>)=o_SB15FY z!*LX{in6bkOZtnruH^-c5-d7C@E~AcE<0i6$cdKY9&+h7l9%0Q{Pq=-29VvzO=p&G zAX^b{KOhu4bXV`L&*|9^;j^_}y%Lx&LYwKZO*%~c-&+6R#OJ}vd1)c!wObpSXsnth zveMDCP-AbG&XA#p2~8=3r;6l?;tn)xnwFaBshcV4qJ}4TYng548OYnB#Hs8rJBr4? zN5f3I$A|Y*Jf1Q5LdLLgo_3!qT6W-azLj`?B$)zX+*^+EJVuKbj<5hJOr@ z3-9Bxb1Dx_2bA$`5{owE_Kp~05yHocpO5i$hdJ2~$!8M_rS9>(lf(EpQOPac4C!&O zG9TH{rb0$|%rNUaI!gs~B(4>iykWu{!5cbl3656k5HAbJFkJ*M-OGPw(~ii!_*g2= zqq-Ef?0gAI1|?^z-w=sYnyS1D#pCKF(noR)K_=!Vw)gjH7wM_JCTIE{<-2viVHaG;Oj8p*A``4QrDk>ThaLO#No@$YT?X74xs zf)lTzL-(jM<1CyHV|A5oqbF*fIdO!0)Xsf$JN{c+-E?l*Axt))h`Fo@L_B1u8EyRv z*Vio+<=b0#qjVQy5RM_G=K$B)OQa-+8@CVw^!{8bdbyg$br`k|;xS}-8*W( zygpD=<5hyLsFL23Aa6+7qsu41iJuw8md|qTV|bu77c^hzulRx?m5uu1E`;$n>x<{x zEW48x*(@dl8k(-l`U!6%Dp#{eWPNptNWb+y8hv6GHr{KyB~!vSZ-;tal(~}5-Ik2n zhb`C=dKc_;KH0t)D#__IB-$^=q$xPxd7b%$cIXd=+_>;kn%#Xpl{VM5m>)vy9u?2P zLPW8g><4~N4UCd^y{Oy1s`qlcxU`MR{;ov@5pq}&;Wa^(3k!x+j<04v*Pc_y`!N-G z6(rmz9l`E7)~H3of$`$o{s<{u6-1pe15VQ5H?)38f#X~&&;X?Q=ar) z!~iSMykR+G{L5Z%jYy*$U@mEfupuRD0Q<&_6H33jR3*Ywe`RbvbAhUms~(cg@5(cC z6+1D&HCP*?G=Qf-Mkx9D^~B)T=wgN~TJ%AQ9QHN$>0bBSmcDu3jVq6q2YaC$=$Ryw zISV^ndI9@&-@W~uZy(dpaX*@IeN0`SWbs28Lo&~pT;KZZXjRrXBA9<=ckq0CRjsqh zUS_=p7rPz`G{+k#xFM+H(G|0ZUkG#7q;pI<(8Dy$EW#3xG77g)WT*RC47y_$*I_yf zqM6k9`@lVmfo_UeY^&kcv2T6kP*z|#4TogwJ z*}bL*x&SWOv+h`9rNIJX(vhHJT@$f~V7N2k+ ze*zIR)ZG|*qZYo~&GVy`dU&rf`UAmLVS6KcJ+dgs`@B4&sy?A0*hE<$)pl|QPyRay zp_~OE%OSYoZBe&tBpH};dV%v84hv;EaDaBqH=T*AM2whx_46^WA4jx-;qT?-mZOow z{V3PH-aRFlBOh|;lp3hZbjIh2TsQM&ZL5@`5UZEm8=^S`Wideu5B})SPNf1*)?CCg zY-s8P$t*LNyFb=wejRV!QjIDF7olbiFR-f(@7q!c$)mT7w0BaZb{zsszc^;EsNaN| zhgSybDy~G4Nx!`oj{;G-N{eQC6A1K}H^2Y41pdGB z{GA$5!uYTABhw3CfQ&mMJuw#3myjAxoS+URtPS}5EhE!FjQaO+C!HH-&Sz4yU+)L| zVlq%P((K<4v=_?$8U3O;`wi1(z%bDMOcuyE{vqI*M`M5mD{e-4a4*ptIL~i&b&-JR z)h;eo%4K@_{(i&JZd;uh-}2pH{qF$_yFb*k)`RWf)E2mh(+zuH4pY4KYYxLWGjG_E zZbSFyW%^v+5P)5QY1|Tbyz1BA4FmP~7oK>u;tcfN4$FbAr9O|#0hGLzi`bk1@pyQ_ zU|tT3gMyIUL6}_ygo%N$_6ZMp!B|Bl&&f9O_jy@g%_Rvl^8&C=#8QGat4<9`5_<*J z{mApZm#W=s;(oFg#QK*{@r+f|J(?}Mt1-LaY#&?9jMvUg)wOM1Vts?(H2qLuwwPX% zp;)WJ0qg8ao{j29aSDY}_7Re|Si1Fo-k1h1xj z$`qk8R~%M!SKnR%GQu9KOz30%BdXd3E8KH@{6+p!Tz-tWkP^Qi(-MaVIzvjtz%@z!>Bk4=zHGfkyBD0;2a zWEfm!XO^7^djQK-HL&%=8g4cdx0SMJ27CS5&ftm+O$u+^j|+}Y2lV0QG4lY`_#%$o zwD-YR8wgrYFDdE%yva~uQc1y{f&Q_Utu`R)uMX{QB>J-D|m~#Z6Wr$SGYxlr{d>1sbXQx@;hS=1*k}2dFUNS!IoT`h_yxl zoXGj``^;$df#+j)9S}MRY{pHRkYuQO>Ot8R@_x5-X;G2djv=R~aV)sCLL+UkkE*7d zodUVSSL3kUc6xmMlT?l^igT-3PpLaYT<0o|P&uzog@)9`%}cbRW(hUil}x)x+dk^k z-VD!yfL6g|DxFh)l}P6OyAccH?r+RI@IuW`%5yNgOBOtdN({Ehl!>|>!^VejpF1mw zyUv$nbLsFYbK7krEOy0Y$mJGjzIU6e!dDUp{I=*oY`3IB4|kSOBwou=Le5p@AoSZ~ zx?JvI9e%%ZpV>;fG}c+qLp}Gm|F0OX|Gypow;u3AySx5lYIj5U{`{&|XIVzwlt({8 zr>wW$lAW&p%V95<7v%*zwc_K&`99}x`1E7fJq^<~L^Te0meSe}Q8iph_9(IXX_sSI zH=kq5U1@8JV=Gnm*1{wP2>Dfg4bD|o5Cb_|1^p7@Orxy8*~vGs}~m)=U(>eSeFsb z6ePPtY=i0bW(OEI?4Hn;w$15Y8*wj4Jroy-QrzTj=M_8c(L!w)Vm@4`)n*s0mK+|a zFz!wip(`uPWLXCLb8Y*_i98Z&PWNXPo-&HRy24sK&BSB|67Wq4>Fa4}Xe`m2gYeT6 zfBD*Bok!}i2-FrHV*m^Wrs4tbchFM2WTg&nzCxo4uk>cTLxmc%Y|@Gb;^^c)$)9WO zmF_eb={q;oRHNNNR?WDLCo*{wBlS7%a$+5D=fG3nb`rWD&HzG|)nWF7dpmY3y-8!% zwOFn5O1TLw1cvo@V;4Prv+fSElA^5iAJUvggtzB%n4}4-+{#$ecH{U|z*l8Mip@Un zJ-gS0jcU#eS8_k_6>B7WAE=fk0#4RD*Bx{o>rJ?gHd-iVD@OUK1a_6ivsO^Hmu5J_ zrzdxy@KGZprfKlG$3I%SeUog#?uDvB-}3|%QIQ=&y9IP{xdTp8BSUs0hy2Jkf}s2s zoO*kB|3Z$!C^YmmDDvygizLWMeI||cv-r0>JH85_$gSR_#oHy~=c}MW(BVB%hGIbc|VU9taW~3Hr}(x_R>I8jR~<3Sa=OMKbS z&lsDOJ5Tqx?(r-1Vgxqx{0rV5RX~LmPwtSHw`|BFg#LrvdCdZMVy~!jY1)S`t@)1$ zRT1YMTbj3PY)?gE5M5S0Jn)}wVuUCDvO+!0Yq1lVysObWmlJ>`@+g=Zjn7o zfA=*#pO-~b#pcq)Dgy67WV#}3YiobMk=Q-_+J96b)rm*adXi_}DW1Q`12lOY$kbJM zG^2itmDYg1igW>BX+IN?FJz|Wup=wXHwObYIAQi`gq*nd9zJ^Ks-L9Xkq$4zc&6&G=cd*cp!6TZHa#mn^8r@(>22D_EQe(oDJId9AL`q9J@cJ-S? zeZ-Bei$=!0r7N*Z-pPw*^zwz}#q_kKyAxY$Z()7E0d=iGqg|e)&tLPRks4A1*EANe zI#udU`Q0EXz?eNqwbRZS+vty0NVM~w|mpJLlqrvM$l{N+P{O-_BS@tcL>|Drh5J&Ubj0BMX;~$VZQp#rzY)d z6u=P!-;6^!kP@EF_O*ag$e&yEDZ1p1()=TRVd|xkY^zeY^v+yqQNz5-eL3nT5UORn zg0tw3qqvy_s9cMdTH?m!9J6!t+Qk!98f*g+O!jTN+uDxk+{AG#6#vs9qV6GH+1>qA zArdS4)b=b)Z;~xkhgvFXtT%io`ZYtc84n@OPSC|LT+8t%4$HG8>)AacLJpR+^{f}@ zbro}vd|$b9(T7x7L)9s|tKJPl0G`&uE*^D>n+@rrju}`sBCSO04tzDum4^LFePw>Y?Gq?_<{EFN*dx#c6CDkrS~~&rZQNSV1_;|Z~9yv9y8|5MUWqNtGqvu zb^!{Ckk(=<1o4oP*W#K+^w+Ce2prUC2B7UYS*t1F#oNnPB26Kf<}0j*BexH-5o zkuh!SKxJ0N{Zzc}jN{}75nN`a(vUJX)b?XNhsh^+T`GQj8y}1rKyJ)JMG9oxorva; zh@YzHl#2RNw0~KounvR(DmEuHZA3c=Opwuk@C3^ooPc6ci7^F#r)Y2ZYAMu!%RoNH zx5XI@po)>D&6r1~CGa0#ab-e4ZtB!p$apQFtK=9|ygAGN??&Qz(CchwOAMxGT{6!< zHy9MD*7D14k~{3N7C^Dfz&O4+Tf?3nnc62>0csv&jvPgJ{4zC&*44D)o==G+>nvcF zkjoAKjxm9st!u6wk{)?;aRfm8*1okOt^nL6Mj{p`Pj|xsR@3#-fI=Nt z;Xl^GCZvwBb7RIK)pR^0Jq@P-q0T<3PbWY~k3hSn2_df1edr>}UIR)sKgUhEqlr{it&p7Lq^HG|&y%tc@m?$JI*-o-m9$1gSu<5|L<)(a)j z@UPg5XAazTiaTe??tgB;=U8d(%rRSBS2#O`nu*Patw(10p^?8KaR&}PBiW4dL9ijRo?bkyt5`GfM&eG$&(^C%Kexz)w6 zx%lI`pO_=rE3{Y4PV`W7q-*)}R2sJ7Yz83r7KPWR0JF01@tdwgMxa3#C7b?zCaGbB zCyU0OJjB6cF4xB9k?1y&I;(-y8&`^2ipuP4RyK3^&siJ8RrwbO8xgt!$2>)c`9)_? z++}KcSXVOpkl>#dFPbZ-!fYF8UO%!t5{BtI;;4suN3;lc?K^zh zq`%c?esIR~Qk`5d(^Pu)DmxwKo56o&OBj3btiQ6PT}CLZPXA!70LxRTkrgNMgg$)_ zaiCYaQ4fx;x4iO<=ltL(`i?2+E#hhmtk+y9h_kbPC~yvSkST_nRe+}I8fMM^U?({q`8p zKNYG9H?*E8Bv;-OrTlEP1*27{fJb)vRc|;dP|>ms<9}k$2I}lr%sm2p)`iV3j-adF z0S~cB!pk@RY;hZH*C;n8+P~a-9<$-dx8S(4GMux5MtBS_Plf@VJuy?Qn8Ikp;RW-T z%9ZnT$$E5hCrPjMV3sycrMkMS<>J9;$?6=)Q&7flxB6wc|F4JG?eXxBSJ+JfFKkSS z;_jmb!S1R5am2clETv2OX60#oK~_)U+@B1P*ZR-s=IHcM7x7Yh-pJuh><0 z%S_ElhUAbH&jwM!4LqgtDIPR~C>(hs2prQMmG8*(-VSB&==c(BVFJ`Yk3|9V`1ytz znw1NxysM@Cqyb~JX4;0A?T-*^&&N2^BUMCa{dyGe-Lq81{b{I;>^CL$+~VVb&7q=% z2=(fyDR~VF=y?dw>XV<%7m)2Bp4mSsXq15lRcpS=Ry4q@P(MylP0F!O;lkF;M?RB2 zl(~H8)j8`&O@t|+Nag@THzPbNO}X>RjxRqCx`egi^cn|T>@Db1Aav5GJlf9e*dQ`yFV4Bv0khQU>O4>b^5$6opWQ@&cJ*L?vi|*H|(Olyoy*8GFFF(#NNm ziiN$OoAhMc5J<(9+}BBy_8Au{D%X5WuH_-085&_gVaHu8M;kp8g}UW=%pa^ZK_1}V zObBcJvK<%4^c(To>8$EWtjbtA^C8$*zwncWcYXT>GB-LQ1caJ~O~aE8wMTPEb-{;{ z-PGlB0PMlnLUVEB3pE$MTT4$`hNRL`B7j0sl?>5JKCE4OYYdfPX~PwM_C*$MOOokb zGm0+Bv=R4@G9w^%ZPe6LV%R|0=okjJfSVs)8{zfC-9$HAo!F|hx5n3Y?)9>Cs`tE@ zmib^JiAzGR4mA8dK3pt$z|P`Ha@_2czN=V+UGn?na;hqHV6(RUKyBg|qBmYF7HUdd zyI$aRy6(&bZX^Px+3v~F?-vvwV*0yRy#x2Z1^ETJ^YDLY`Qm|w{~jEoBvkPWDB5p9 zt|gW^d~Q%xe@sYw(X0&E@C#QkIcWMipKKNMLp2E3kTggcd&v1#6Qwgy{ieIVE6A(H zCsSYyN8KkMHP0Y>^T_k#gaB9bsVpunr&p@Y%s<)!d^u!DmMU8TBD`7-`Z5zFy=4`r z{^Wai+hZ}nt=^_ET(t6Rf6r%yTY$08VnGu+`+toB!Cc!>)7$g(a5%iueN?lTZ}PEi zQ9$Gf5aC_)DSe&OvtoLTS>3?V7R7y7v)X{|GkE2QtkTdw=ffACYJV^t;L_DVrP25? zXp@W~f$I#<&;JSCjJ!AyJn{3>%ZMwV=(=C==G2 z@vbAZcuBf7TQ;Z=abdrE+gf+Ra)r(2`M()!P?mu&RBveLdShZR7c16Nv@-UFbdtp92~3Hp<@^Vqz^09ood6Z+nzQat%Qr;+hJ@cdw>l!F8Kt zGkrjBA>U0|>PolnvtN7aQY;unVmbWfJ_io1q8S82lLqN4NW6S_6O4ZugZWFLV!D}} z+AN9JG%=p9X!i75T6U{(bKk+x97#I*67Ba!mq~lrW#IP)&8$dGS#GSb+sk4LFiW*G zGKp*vLF7#0G~PL|YdzBDDnXSi7~w4PtW-jEl>~&t+trri)j0*z=(J|`g~yb}tmd%V z8KVsck_vKI4jv1`bsjIsrwlaxN1tBDkEvkL9C04e16PurK$VwA={Q)ww}0gPtRxgL z)k#*QsB)PmW)Gc+J~Hh=ce$|?BT}qe_i;Wx!95Q1GFoLAr#Us8yID_56a5bzDdUVj zhBeN#{D`td3_CKfGcrr=u6@g81}Xxko}f@`=^YFH3it{lAK{ZQt*23t-TvM;vUcLJ z@JKd7!wQR$fBGg3q@{Cq$!GOw;}XEc!VrC-fB7l!&0wzG-Xz|yGNd!$Mq@r@_b28b zlqW&3V!(ff>nE%-ZLxkgN531I3|}gOM72M5?Ppl8H-M224Q@@bL}gP* zt|H6xol@Z<#bpM7DQmNj7S9s1zbTCK+KIBNVy<(2pZ}mL)!;X0!Oin*RdNNkzjSq$ ziTN6>2thMm8h&iHU-sgdoH9Lrv10(tJ{?_~!gWPw{19B@jEz?h4jZjsUa7sS({it* zjCL93NcZMd5cvX8+^v+??xpsIgx1`HfCj6USBKGH!1g{3fz4jl-}&+EU1`N1U||9y zK7>>wi*oV`Dwrj&{7Fl~?oV3R?pB6Ld;=xE1Ljk2ijK$H7Kgd|MFT-`R%plUTVW0* z)mapgxaT^I@W)4ul_D`O`Uf17>`nABKFb)@{QcB{zgd}q0u55E+(ylhNpT%j2+n!% zuS}SUdN!f_1wYFDw7|0Qu1*_N%)XsnzT>H-`n&sY8n@}t_K#Dvr?v#wO4)*`1Fkgw zKffAng2ii;XtZZqK5E~?{6f6%Yp35nIYas{VOO?xW7wT`fKqmRh& z63cuO`d4J;t3`8PRg2)6)dUu5K|lYlQPymH+8C(9Qz7aXL zSBPte*1@*dYjK`#vDsJZrUO$vETEd*raKJPG(K;4>R;p9HYeVbGmKAfFV#o`wI zUd5?@-;xae#`dW`?T*>HwntDXR1eH7t{1R7fmAbV9ZCL$aLdhrTyBhq@kEGQe{miX zL`Ru?A;#QDUwG9n{A)WeZU7#M6kcCAkH0mb6$)@yZR{J!<^ZU-dbW9h;xl?7aa-%m zB>Qi`xcHUDm~$8uQm&EZ}kK+Vm_Eisa_{DUG3Z}Qk6>E=Qf!WHaef$$}>xr z^|YKU)+=xe9^PoKp+N<|3eqf5*wBJ+x@AbKamE4(5z2ZwI&w~pIZ zE_8aLm2Gem&6K@qTt&^9TD=aLSw;tQeTsRh@SgiaqwB7@qkI9n3kdKtTnQ9hN^-Td zbqRTqra^pj&oF_aX$+}$YB@Q@mgc9|Z0T0$NTx?9gx0^_cp48;h3&_$2u)zsJdwLg zRk&Yd78%U-c}ryt2W77&B`GoUx<|U5p8p_uLOPM|I8%@&%7(8+p9&aQb-SE68gU%G zbBN2m@l#wCX2M`Y2+y?mYgyW`_OI2S_#vpmT{;O9X$Y>KUc=hGfl1H#~exPQ>e){gbr{D%up63vEyBFnjF?-oc-*dLHZ_cN7pLR-L5Z zS|qGL3^G;`l$M`9{_YJog0&8>vM6WSdW|x6zO&=5m8(^~tn118x9e(bV_)tEf1}3N zQ(bb^Y_FDRC}O$3op)=8?t-SWxhV1$96*1$JSrDH>cT)0TR=9rVpJHM?m1k`%ud^*2a!5TZG6S)#Tn>sF8V`bO zaw0ne`kpQODg4db!9SXO7Z~p8{RJI3=wiUDvM=_*wvb{$K^p(zSIP+0RaR&b>AjG? z0oW$*TnL;f?z`3u?zh~-@AW?RXY=Y8`FCT)Q)T3H704Ot*vdkXW?(gSD;{I2wW_|~ zvz5Op<;YN6bN5M(-fX?8b86AXv<)I$w6sz%S&|WFinE_YS~lPEt~DdG{*AR9(N?}` z9xK)MIZYa?4y>iPv|BxUa%d}q49?)FUPARu$o}pHfk6?TfPH~X=D`1lNG>mg_(4-8 z>5ItE;~-B-&r3<7N^I7inUbt1tc;T5WIKzB6+B7|$;&ju`aIhVmoHKoJ1lY!hVH(u zot1<7ThFh6<^}sSXy6eS3KjXlrYW77% zk(+0ZGC((@MsSrEZso3wCUanBP3UMNC@>?g^#NKk&daw~biefD=h3?(pa(B@MX0{6 zL?KB=St<L07I zW+gza$@dAKret~_)^hIsNX&!k%io}Pt?1_xTV71ah(uWrCFoVCOIprL)UhxAMmAw9 zde@nQUaNU)pS(&u{uaq|2#g@%ni^W+y-{yDNY-Zdjh7*JLv}5$?2P%lFXIqfBx7qL zVA_OL7tQ-inc`D}YnJTT9u4(`^oCE8Rw^3^TH!@jt&kQvJw+kpQ!rz=qnZ$J?;$Q=Gk>YuNpIV`t3R?FJY! z3FZ0|EAy|x71^Xu@TBq<15LK;a{ObQ<1c`sl-IPWy|`?h``mm}XKPLm_TdGiD~+VL zH|Rx@I&y~ANF@%uvullpT(iAb=KQ!>UKByhyjTW zm44SdNS?Qj5jxAX6hvVRGz!&f93I1R*r@M^+Qm&XgR9?gv2E0$TsVp;4xK>Gt2fFG z59SBdh5kC0gFH*FVriR!pNEtw-cO`)3YtMD8#ceI-b`SV+F3R)EnqxpRxJI~c265? zQ_$_HJysAl-r?`}PstMeJd7ub7=a&r`hlNs6ZR(v;2!$4H5XG;)BJh9%;)QY!{J1u zhh+MT+m(gaBn>cpJR^%8>K3;i*F{MZgVi0yzX`VcaAea{-^$o|5L?oIc`YSd&&kxe zm~6{5`=t0FI_(7W)yTQ0w0N!*?Rmj0{4y)D%0BolhhegL-Gq4+gN0_zV^8>-O6zED zLo=mm@vcLVMqxboiut+JR0!n->6l2D;J!$McGg~RB$m@Rw~VbN?0hB3IOs5hsg4;D{y)h?A~47kzIuWql5pRmxMx=3-;L4S1w^1=hEENONzI82tC&uU#mO zAMd;3ci*=!)sQgaNdc=V_nwRzK^$R-hR}LPha*qOf#T6M)?UM-+V%Bt=r3!1`#nF5 zt}|tavj&=cA*d1hRc`SFbHs9Ink0wdU5Ck>0mq ziZ7$?K)sK@_gSD8`cUtBA*73)Mnu5nUPR9Mb;#MEA^M@zaLnlRH`9J=j?}w2>xKNJ zsju_FT5$>(Jn6p$VUt38pFZD5uQw8jc8>}jd)nUT`D1}oBO_m7lQXg+)h{S19mdQ=)D3eowZPTS}VEFryH6G?CwOH5frLr2T#Pvj^zeSLt5p&VS$q(}NbdQiIHX^1VCuakJQr9_JCepiY?S2)L<`d-wLib?`e^?)*vm)CB zypbh!bfVj;s}5xwjylLZIbEguVih#E9SvqsvA3Xeiky-LZWGpvNx{45BRfCj755D` z61KDx+p3Rn$@vx`Pv!&#z@&<+g}Jzz(tFFJEtUEy%e~Jj(WX@{?*Rf6U5iyi4Px}c zM-5`!=uh^qp#-Tk7ZK?+9{e?9nn7^i%1rASYo8Jcx~0rAquR%U=hi?Ho4*Xq>}_1P z{ZMmN?9vCj@R@if0=#JAcgSbhU4-cHZ3vw1*$o-pX&C`mVh@^S=xG5xS#4ruelRo1 zR0>til52{)3nQZ7o=6Xl-FcuD*=%k6oHy=_Mn^}0YoxGzCo`C!E&Mt5ZDZOw=5SD~ zW2HV~;Bcum`nEPo|1oH4%@*I*8BS6o00Z$p%WGZ?;@)_RzeP>C2v1wFHlFz z-cy8KcYU3z0ZHb{*!_oGe}dN(u9PxgzM{ujEvYoo&KRib`$T{o{8+(_7;8FOH$bDb zdg|Ld(oc@j3~mcviM#&?dv6)k_7}x{)_}G^DW!OUQmlA!hXTcm1rJsnf)#ftP@s4T zTHFF8Skd4vMS}$Q;=vsPgopp^zS*69=6SO_&&*EG%gDVqpSd@4&-tD6J@{3MwD?$V z4rDGHV{b$PZ2?)EOc55N+*2S5w~krC?chPC&4jM+6!$6}acTP|k;?kL(-~bKo-;E3 zAf%60QDIANXxWlnK!0&w;q5yRvnfPU5H>oET5M53ab4aOb63me>`RkHX66rl0&BXj zsxH}=4J7t6dWV$qw4^f0u>q(ihU*fR!sPv|8W;TN#QR$U7P@y1dTNYkdpK)h(dYy~ zy=A@Jd*;MB4b+DJE$OzxtYmH-TH6?X}@{lZ#e5BmeH+%-68rwWA z?)_<7c%RwT`^e7s(na8%a+wyphxkTi^DGH+!FnGBCMcWQP&&iGo$ZFkpnYdb6XcVg zq;oYwmvrgv=!UwoW}P|n1fV(S!E}OQgG2G_(j*VUNqH8>HvZrUIlxpUty*FSBa(XB zTlZ7O7wBC-@g3w3y2|^#mDbYg!#;4UYjHNWuxKUNup9NDO>7&6Omu@MyAhM5o2*|a_Nq@iRkihjE6dCDK2b0HFkdq8*z3F9ciAS$sZ(4CAElgqaX4E&db z47OWH9QuuPd65vao{vW##wd-*9*3-tHRCYF_N+dewYlt!qVfAA{ZQt<@C~^fq)!az zx~^_AmlijZ$Z{=}wv+A-4WpD)7WKRXG{pmBR3H%}sFZ=$X8t_qRNJ1@u8Q3aDaN zYbEyJp`&%cgBF6i9a+F9e(;9NJSRd~(f70~BX`i=@PePzn ze86NPkyHPw9xI0KJtdZ2clH}wJgsr5FuxmzFV-As0RLjP{5>6{U#SR;Y{QHK@mnC5 zx=+LAVqHial+Ixo$|_AeyyG__$YGnh{@r085=!gp{r;@mbc{&eHgueOYCSBgrof$g!fSzEIZQ%Xe~x1@|1f zzi8w$uB7opEr!BqV?IN{wXu$|o>h=>=`5GQ+a-Y?Jp!dw%!v{U-zNhkTuM9?DSc8o zl@zX>6)!C0z?~ zj+@?>U#U_}d{$+tA$qa+#TQGPA&{5M^{uUm_(yGtP#3FR!^|*)*sX+dVVL#j;DXm1 zY=0rNQ;sv{42#c-rm(@1? zC0fh{@h&oa8I05Q>-HigE~A^q#vwT&)^!p|6UWws#-cs9D>CkgJ(NOvnz{u57ujY<)%+^|_`l>ObM z7S?g+6_m4_XeEwKBzfe{;0nuUqn!Dttz%GV;Q^sQ4Y^Sn&oe9qMSOc)sSu~D2p5=@ zLFc@$$b!k5r zf=I}>cX+c?G5hstEjQ9Wel^)H6ozfc8$9&$7MhhBMp5OSl+Rtqz1197+sw*zpDodM z6w#oxa6JR=MJhhG|CIba-`x~q)uHJNund?S%6jmMPCXmoxYmzB9;253&sj95);3J&pZlQ z&z5dX@hI+^GDJEk+Xu;RfnZA|A6fa=>X7lWPL85Ov+=gyvG=AatySZ3(ee7kpjN6M%!FW=$ljA4G)i*Mv00gex9}b_PA+=pSn?(^2{F{EW%yz5)jw(hOmnsY+Y*KO1A$)zS2;H~p&v&vujMk1;Wd~nNBtq!Z zM!t{{QQ6G~Mlcq7M~oA9vLzG+@Qo&bOJr)gQ8_+WGxXM`k}59zw@3DLuf zeYFSYn`h1R|7Pj$Tuc);$Q~u(J!RW~)Nz4wzMabV<{}m;Ph*j;_b%n+R&xB7ulouA zXZ~p-{h0uu>T_C?RZs7&G^gSZ9aFs2q1{gt-J`f$@5zbP#Up?BxCpKj`N6nqQCupW zlhRWobSX2VCH}K@**&c5RVg*7ey(ZVF330_we5 z$Xhj3?PF`F>?8`|`DFMg0k*ozgiOth7+QqHNswl?z1P{w^~f3b4EH z@g9aBTTTK^yqWMr1mN_q*y?~pO7MYuCE%FQdL_;0pl`<|E2(5T29=w9mYg+3TvL$7 zvt1eJi}uIf8qRxOv%h$}Bs?sEhlrqt``zk6x2jr`dkdukWKkHt8B}OM!!QIy&snd} zju!`q_ro!`g|9>b$&yB?%#gy#^}Ivz4bQ8;rr1WnKR~S5*Y7hg?jFUQokHhL8T$^5 z-CDoD@|o8Oh^zIz1ihg(hO$pAHM+UGNp(`V{%p5X!F{qb<;L?CuHThMRb87WK&Zth zYD%JfT%@eMF}gJcXWZc^c7IH=cB&oYMJav739Rzuf4u=%m|SMh$JO zN{>{BXF|GGEPFS^5LQMrs8rYO&gPob=o0prbjH#g z;Y@L1`*(9si;=Zh;~}dQfT^RI{kP<{X7P4a%={hMB8Or+qz4^j@0;j$UGrbLfz+oy z=*oD6xb*>fSJjcU9%+LS%el1d$2=4$8c^9O|%OJKLk|f zoSL21cddV;d3$T+T=0?dY~SdtpXRzK2Fx4{WKLdsc#FsJly9)1s&a#wNmo zCC2rrei0y0PJy4P6k80Ij##X=0jaLvFgL>!cO$chZPe5C#45bqSbY#+fWD!hO5<&2 z9?*L)$cE~UMZ2~Z?KZ`O4{$#KEu2*9INA0CW;3=?R!f6EXFhYKq-0tKXhsv2v*+K< z*qbfQD>Z?x%EyF?%s$3#Q)b_5?=mX=IuDIP4LldpX+mK{(xZM*eq&Mv2LcchCuQ+y zqAGHR!gUa&J`Ph_y4b=NI@j2wh0>pQY!)V zfFEnP(xn0+Bz;uG0NDI&w72-j1Fzyar3h_!BRTzgIbd1F3SC9M$tTi@+zDUy0fpsopjqya64z|E4{xP~3BLXsF4tO>^Ty41!Pr0&pzmBU=wcEdrl zs@6$JgJ}-z5=1s)+FDnkF>&?dOo`#AMFF1oN0giwseX30q#%)yp4(BOV^u(lJ}&Z1 zqlUbkH?98G&Z(%~)HXcE9a6|=A2>)Jo0PHm``l!XjAfz#O8v({0dcRzVRD93I`0SiL)4 zML$a>b~VbrH|$BgtW?T7En2uG8^R2^ypzlC@|yi{QMt(=M;%2asLHGSgqA3<%0Tsk z=md2QX0UnZHah$2%ukKf2wnnyyuL$(JG1=0s`5v(TDmn{%d@-s7^ZT2^8To+h_TpCv+M>}@31(;Y(j?^oO zEl0e$daNvrP6}iWF4*W>~EFnosS_#d;Zezf# zhvF=&9{!cSA+-NUDYZbQbSu+>yxF>RzzmiSbE{i_U$TLm8l0TXNG&2Ho8(D~228ks;6F%(4cf+isQcp$b(xjPQLvw8nHV z!)f>IL1j|efUF}n^gJFEEtRG=#P{(kx>(5iak_&{arS1vx^{|{#opxDVtDlp-ua}n zAOXGQH75jpmNDUfPzT)7Vf5K~wOjh*Rm~+c;1j;=(l0xh7^e!2@C?s0SjRjF1{+}f zg4_Z|yB_76u?r(>#qtYvuD@}ln_G<2oYsCBz1GecuPq*_6uxAheIr`PGD=_X%bF)c ze&tY~)LsAq>fqNrEhOlx>n^&6<~ve7rL&v67q))o?BCyJ<36)Y;AiV)UhoTSM z0u9fGN-YY`P+qFDa0uDlx-^@YBRQIpp$ct&28nQcsidJwkdM>S$e}l>ciO}uX@NlEm0g=k{RO939KiyMK zUyD>z+cALrzFp)+z58G^`_khuoypRcuxrZB?>$p&+1O&mW_z&~jn-d`tKtc`iJH?b z8^8MzDY=XXW{^#>GV5WKuT8iC&pLjf?y%ZAZ?yB?^TwQ8>Q4z?&a7TYJi~)6h_aSWf=4rWJw3E_=kK)fxMEfji*i%^<$m79Zs^JTx)%-;=Gj}7RpxyGYt<)r zgzlLwN=Hq6@?=A@q6AH|cn9log?`qq`e{iTNW9`NOG}e&h!aZ6UE}`BtY5;5xHWvW z^ArK+NB)f$(csUS)IU@|@;L&;v*MgOs zmk=p2CnEd_2tImp_5ZffGmhIg9jJIxVpM1!TB}K6kMw8{WfyXEktLQ6DZ73n*8+T2 z^7r~j!I{jw3q#=y^`nE_hTkp7gta=i))owL*LxdeHoDSiDIaxhZBLpvVRP`s+h<*M zPcT@~zwAf7*B=6KBUj*Gu00A*Fy( zIAks}#$A_*|8!W~sLYYz*O={DEgVD~@V%KYaq0jRp64f>&J6Kyj;ysEZ)zf=35UsF z$DAD2zYsG5ns@R7WQ^B-mxr!MM)K*~+EN=u-HoK7+;yXscGdo}WriuWxtnO=DLB88 z-?mc0uJlUZ87Ymfg5YI@?Ku|evvkb54&npg_P|EoAFlFC^huLva5hXnW*2(Ug(wc1 zn#3GgZLw|#+tfNJFT_4tR}XNeu|kCAj5!QGedD$@J%ZWBmn4lAui~oqxo;ZL94r2m zZ1g1MG%0W-Kmjm3Lmd)#EIky|%VUU%(jk95LEgA#&?~!-QR6sQn!v1cCCi%L< zYmt^sL!Ip?68b4qHiXH?s{P(n-QlW?ZrL zQG`~90h4Ke!P3?g5IZk{p+o%mn4hkU7nonujPme-+T76O(fs_}w{}=F$U*vju&jhi zI`Yq7@#t9NBwI>h?v2k^PN9EPRNjC&3fi6a0S3wH1ja8#5-|WswF2PHHeU;1!noMM zW#aadNzkKnSMj%7oqgDKw=3i1gcPQ}8?pM?_4;8}EJkIsRPXBS?LZ6OgXKas+^2u- z_J!E(Iy~J~9=FL=rCN%b%Y(4qCaz~;Dz0O*X4<3fz_5>wo|LN;FK+-Cp2#10COP_? zWir{~0pIUX`yS8na2LSd&;|j6@B2GCCrf)jBL&3DzhQ2?XCqp9{@fS?Ut|p9j4sH2 zmptX%P_i%a2~L6Whb9abv|CSq;;`p$3p;*1e(7*Rnzh2H+itE0a268^Y3 zxf9dC$jtzEHn?DYBX6C(j`68cU{?l~dw;z_X47wHC_q<`*^4BUwlEX(TnX3Xwjg7v zPp9?IM5}b(zGivoegbwH=5IPH921n2R!LA8xewMkLe8c5wYVF7c}hZYv$HsCiY}Nm zmM(q#%eHR73)cpR6D8tZ$`82K_VTIE11;a^;fvcD8S4R(|5|POSri>3J@&81#mudpv&B0h%_!_0E{Cb0LgEFg>RDB{+oR zWR%Ml49sc#J}E1cuW6I1cTsZV=j}3x=PQ;45P$G@TWKG>)Xq+OMc1 zcD&#Z=;UzKp$<%RpKeBvv5?k%Qu*!SOsRnV1X5HNrVu=olphIoHQuk}=EPHpx>vFx z3v0iS;LrGP^IV2mC({%~zi%cKY!um}kpcjuHXihdG z#LmnJ>wQI*jlC%g#Td^tm3U5Zx~ODq#1vn$2&*9xnRRD*Z}-t{FKCEt1Xv55!`q$e zx+!4eqf0hSj^i2qenY*=OZD@TSycv@WrMF7W>oB0g(aAdWJjb-yUm9Z?d|aa*6{N0 zcl?mM=P$_5k+FBmg2V{=fH75vTOsslWrOa5s)ap43Npf5`$t&!Z?XT213sw3&f5z3E}(i#t^55l z+P9~2$WcJl6&qrse%f%oVrk^1GNd77&ifrCRlw`xA#~QQbONldm{q%O*@xPFmYR?) zp&?e34bC5Z$ynj&$(ISN_m3MX6~^vO1KMmYgY$ilkI#7C8wQ}Hl7yeDEf|!P68d&R ze=#0w{-xW-ZN4;JU=!&q%vGA5z5Ki-T%uxs$Io!I)c?~p&CdT9*nm^Fr8fO4sEZjg z-vT;N(F$d^sZq$&ILW=4{W3PEw5c~!#6R&TZ)xKIzbKvD+ua#4T+-IbW@VLk#S%wA z-r$A}8Q`)zf@q%8&7JI1-((5#G=vuDsc~N2%#d%hc{&)UY zwL85^86dr@QnAzvMzB;v;L}v6isZFD-YUc>5ZUt;SA&&2=o4 zTpy0SEm}!Pi#QySOtPTUQCIbdF-(XS@yf;Lhu>fIY(pc*r%VJ2oHy|lcM8@n(o@w- z6ZfU6+#^CVvWXmZR8)4Bmhe%z`))waoGl~G*lF}6ziLrC1H{3$cHD(8zh3gXZ?eGs z(*EOl?uvl3!~nMoSf8+8c2EaD*B)(+c6vO&vXUD=a;f-zNvob9HV{K7epgzjnoRIu z`Jwo4XnewAz2i-I=+e-2&n6sCb?A9O+=|BuZHO*@jp$uCmGid7-co<^X(Z{Ob`6;{ z7|Hqdj|-*GUU00H#rqrpSZNQfP(_jOllF2D0{lP0n- zurgZqS1O|dOTl@yH-m=HYXt>+d^4l#_7`^**~?1eZd`T!q$D zSY-$#%P6X=vNTxPrUgci@DnNgGD~bfI#FnyAohOV`L_Uc@tTile5~($8w<7=3u;8p z=CPWd_eiQ-RdytQO=v}#@_{4k&6Ysauy4;eMbsKRVo9Voxrs4MpgYL zNYv3~f?1%7&n$(@MUx7vilymqf9EaH{6vA9@6U$KCo|puUd;}i7&af7WiMq5*s_P` zrA*5!+8lg~h-7-fRqSY0KZ0;*Jg+7~e7`7tELP)x@_H&s!9AV-@6mw7+zv;)nnZ2p z>4qNJjhik`P_pL19YlOz$`xU)0_qL7%mBn}vUOMR!$T9w0&YBp+o^LB%iNQs)=l6pwtY3q2) z%VVSWoG0(38u6(`LKH`|R>K8u>?O(PRx^4VU?Js#Y+QuHrY(!8R1%pHm;Mij>=Ca`^HlL_y~T* zI}201miH!=-W$CldeX&SrH}Y8^IyP5gsksgFhSvI_*y^xGV16p@q2jLcT@*_--Lq` zLPf|YZSZquiI)8F-O4fmsBY)3_+}l_ZVL&bi`YhM--Sn5+A2)ur%n`CHuV|#>ohx} z2jO{cR%V`@CfgKLVbncECl$2SyFFnm2 zzA@r$1mkbOc}r^P)R85EZc0{~=-AOz6P1?`xbWr~BDZ%u(je#@L4S zs`wvN|1P7;f`L>0WDGUp zZ7TG;`_KHerJ?$2Kc*QnyQCF0kC)fr+2YQ!YK2Xu)AP~&+j8O3N>BZBpgoDkEsCAjVHepI=ZQ0v^d81O`7)7VSUD?rkdhE?wx~-3E7lrL^2vPXb;AV8- zqe=`F{3B+Ey%{TY|L^YW+cOlom8pYeEsqxEi*mpHx9F4m)m?&wg)QFW$8r}j=K4fT z_a#pW*|hG67Pl_Z`T6o{Wbs}%%JP$8Kf*#MaQu~?3=DD}(kN64jZC+iBRFgt|p$q+i-Huak!IW06Y3}oF zdvq;!){kPLQ_4jSYC+1b*uhLZSYsJ^ zE+;b6Rfa61+9dm{cE-Fo@hlF-4-}yRbg*`=xW4z$IBI{(B6MOmvneSu1VO8M7}?7W z&DCsZykKn&WU8+=9{S?4WP~1O#+!KedBNs$2R#U1dRP;ex+<$d~p=WQ<5X0n7f zX?ue&TEHrAO`SiC@icre8PTcO0uzGu*8o`Da!<7=CpJI76#vocCi(o*HeUIv!s2< z+k{FcDknB<g5{+G{=-{JDvO<_|v}cP=zEzVB_8Z4?r(u;clF~$FIr1mF zax|U}rKz@RRjFBp_-7~z6&^Pqyg_>ze)}-|<+=c2Z55a>7#j&r9q>fI!P#oOC!=k#Ox{<9gAdGnnv{?;$GDXzmDLS) zTCk~;oh)g;sT78QleM$YIF5s}8158`RcLcBTf?*l>hUIU{|P(}+l!;V<@F>cp&$y zfMMZ6Gsg;hmRfvp?`$>(rUYiI-@Olf#Zoz{ud_Aaa0{>@l@a9*v;lm_5BaqU1acGNBmTAhcQn!aCAeM9->0-n!HhCtp?-s zO=xx)b5?Zsk-ymF=shDJ5%Fn$eXkBY5#uq@p+vRIHg+pap0G)wS|*`k53>!#9_oZ; zSZmSp?X!(Jjv1^mHRmcci}w`jS9ghdyCv$>`h$GTOp_Zna%3~YI&~?8J?$SMr|TsS zq3A7tS1odB_fR5OQ$=Ew=Yv&IZ{3K|nX zsG99c<{Rl^Cm9jZnV9U;)dUdwr;=E;DPbS8Un0)CSSO|lUTC~Bq+Py%>vt!BkKS7O z=5yS{w`u`9myQY~Il7_-5mZZfpxLW9lN3vRuroO|QRetkHpaQnP4JyqCckrux*^}C z`lvZxg1&5w+%}nNf+w;aB)~89+1LbIANo2LU2W>92aLm~O9Pr2_BJYOD9k~(!j!r< zUZqqW{*|Roey0{I8LnJYQnS-nmIq{-5QeN)FbmJ}GS@I{V_a*vFw)gH()dd0v^Agf zvCA!p(J%y@-krPT_O1A-SPjC=?(I_gbh&!ABP-Em@ssD@RoZ2Y1l7)~X8Ac3{O?=e z@RJ-LrC(nq?e{G?eMS6^rdHi?!f?^(L>f6P8}81pwQ=?}2Q#XF1iS=x#8FP%N_&1aTyvjbv__}D30aKK&Y}&DPri&2M)Q*1WrF^Ar{3n~$fVFi42)_KfrreHdm*AD{dw=`HlE%5Ey!34bDB*5 zSV8Ksfs)^kusCN#{D|c zQqN_ZU>`<0Y9&AR8x8f4HsU#bI)3ycV$IbTJ`{NhnIc^XFJ#sWf&Dt!8Q!xCcDT_1 zi5Rn#w)gau=5e6LNQ?xG)-@xc6PD1j<{Xi5@69K$eDAL@;C$M#q8|0F zJUbcZ_x1(WPdj|SC~f>9#x#(9Z(6O|^ZfHqU>_q;`?fR{(4dBQ-^~<|?2`DC4W^8u zX(+is!W+^LDv8zIj{TwHFKS*3mlEyy17D%emZBMpOfxO%bgpOIr14$dE@?;as}=}% ze$o;Iy@l+b*MSLHgl_utM-7@oNtG}Or@qv>9b#`)|wbkwYetUZL)~@@IicHJzascqrIplezLT$8CVnF4 z>>t@*6j<}uTHy*~lk4m7`dk`yBG(ntij=AQf?~||dKEY%5l*eF>U&D=)n85l{O&%< zuLEwDL!MRY=WsyyqaF%6Rw4v{dry3YdPAA+edNpDI!wzSlLzw5za}DBL(R^Yw7*+o zU!H8yl^*4*Fow36so@V3-1t>LS|54oaP&zZhnb;~{lVRXJIHITbEGVsV+eM|krqQa zXMQMm$qBJ;IC3TLwwiIf4-PzM|*r|+4gc9%fQ}x)lky+)#}8l#;Mgn+x0NH zJer3eFb0lS8~%+}9IvPKo>Gm<@r{!e8@QqX7vV*^ru=|vNWd@1Qo3-5w_v%McD%ZH;Ln`N*Pk^ zgi7NFl!ip!)#ttNydkElOqzEyOOq4aU7SJyqY7Z)=~%3Z5pLg~9KMRrpF zO@4_gHt+Byd%{%79iJt=i@TEbJ<(tF#)s1Ef0#ntyO0U$4)r)9%&L=0x2-biX{HRs za*B$9{Z^iexSE+@e=9ds&_oago&s=ph9s1@W}5eERv@uSQbEY_?FGQGHEhSLQItWK z=e-0fQlYf^x_KEGYik3-0ytMcZFoKtHvC?##`@|E<2XWG$!*TgL7KdMZXZe=tMhhY zGV!{#7>4|Pd)1l{ExDIAN@A!LoLiTZb7L!T>{tKI&OY{KEou^(QWjW!8Hf@syI0h-^N1gW4&9!869!(RzW!?9yVT*;~j~ z12UG$WU`<-$p}suYw&@S8O~&$n^0!Zp{ps7_=pN!nw5Y_ z*SYE)L!3pvcWbg4^Ta>v4&}Fe-`UI7fY6a{n=P9Rt)8qLntoP0IOR zm%wpJGG0?$1-K!*mquBU^K2}k!40toem%D|5g-F?oQCLENN1G=fKdWQjM&h>#Uw?x z=%QO0xMujN6||#J+`1tF}~M`7vftq4sP=@x>G(oJD7#7p*H8U-<|pe_-v)Q zPi5xP*L_HNV{Y-Z1SYwoytgubiYrZ7WOGqo;JRi>_4GcF_SWU`_b4-HYoDjw-|yUk z(Ri)#CPZh`3?-CG1b7gXN=~ZP-9R-J$no;tp&7w9HAG{(c+sD}xDr()R^6>)OQ^n= z;N?DANl(Wa!^J|G?Js0c2Aoz-Tdz(k_w)rAznkh&GDy{UVH@M$EQlA6hC#O67F$S1 zbz=Mbhh>|VBw3M#_^eo9*AnNmRVwBWYwaWFHqG%7!iDA%l;RvN@5N@vi!!>!R$JN0 zSi{j}b_w6~GT|J}c_>@A75;w22nkTbkYXZKlY}i+xF}MEd$w9FEIyx2A&Fg042#~P z9$tFwF0C#1^jM8GPNPrtXydt#jv=AHN5#$tZ1Q*7^rY^T!bGTSs?o(uh&`;)?23pY z+k7(odOM^6EN0%pR$xg3L-$wic0|Nvn76VAOc}PW28x>RXXP)I8BM*$;?Jy1uFH_0 z`?HU`U?Kg{et;vWcW1c1RFJ>bM`3NRD|rLo$sN zXPF_m6#G6{s;pHV}EZ8bA*^nzVdiZx0sJ!euWls%*4x{7x?=i(bHX4_Lb$3+$*L zG<#XWoNHcaTE2vcB(u#>_P^dc^sk-;YYeZuR`S;Hspihg*@c%fA~x49SfNJL6xV_2 zC72?rVcIqX&rC{7Ujr6O?zjLa}r7I5YzxScn6x+w=2 zXwAQBdZmZ|7ykr#)}ZjFtc3$~9y03yQ0xSj%o!?zCfwhR{GHZVGSuFQ;_uySi<#@U z_MscjPL~z1;PE8UH(-Xajj4RChRjh{X)=wH@M}ggPZe?2%yu?_u6ab@(J_pqfzIq9 zprSMx;moZyOHy$-o01BE@-;J#CCY`Kc|E=NHak6*^hYG^`h4stwd91?K*DJU3JDEQ z2a`!OckMn*yNlAd{Q%4H8B*q|s*z{!pmyOK(qx;-{hDrXLQtP|wH-Qdds6guJEVv> zVBjaidis!8vE>hGW@v=7rnHbt;tsLwSt%lYz(IbPd0qP*>qPd~wS6YL?KPFd1UEL96yRAzX~*cT~3msTA2 zo-jDcK;rq?2hB$19HH$=7rVB6ppu6`D1aThv-FjB`zsiz7M7D?t7%iif>M}T?SX1J zmwLHS5B@05^gau40^z5EW?m5L&&&)VGgaYwWLRZ^{JdMEbWikyv+WuHZsbvpq@?T? zU$%nb;;22t=#%*;+FXejw~_8hMrcw+gA6U4rARp|oTpx95%pjejz3zTP`kRYG+KghxwOQxp_a>I|;4LVoBcXf-8D zLaQ7_(>L>#A^s3nxpDG2#;GGUd`$~2uYJPWBt^cj%uw0)JAsidfLJ~5SOfO7d3=W7 zuU3_;G%gB*`{oAm^?W$_-847R(DNH3L8+g$_u>JYGCO`j;MR$8E|$dksEJl#2Aa?1 zX_e>v+&G567bX#YWEMLk7vMFdBErCD*y?0U9sC-RP7VBoPCEVXWMHT`2(o4`S>Id*kho_Y_&~H+pT-v6)(0E4 z+L2tnG@Fh*6g!P|eJy?WK2+g0lZ^8jr|_9cp!xs<`T|lkP?J{v^y)#{#p4Vf%eBNP z`>>%rsZG#H8xM%|3W2xOyO?)vIGt$SsvHN_8{Hc)?R}?zTkI%gQN`;K$srr*tJ~Vx z_;$@G*U)K2Y-%VG<|I(G&H|6j6BrXdXNWowPOsxHQFBTsy?3hAS2qW&+t|WGNY~$j zh;I#Un8AphGS1scQnTh2AQ(3i-+l+7%GVS*$%1lgx4V;gtze+cOSy^U@E#Ko4~Kai z)C=BP6I<5%IWoxYF7mMHnvKYm(Z0~TH)^W1xth=1h$5I|v< zj_KZx?Il6}k&8Xz-}d`8?ATafP9|9Edi{H>Z(hz>=6y||XnyKkX_&FXPS;Bt_HrC- z{_9g#N=-=#u=j6UKlU-)(uHgF-k};iNEi#ublsN!BK@?e=!QwCJ~+h^tNYY;mPH5`;5Hu_|x&RPW46Mk|XSlhUpXm94nEk3-HgaAdt>n6jZ$O1u{7Ry~z=F z2REaLjx=Q^{?ggU6;0FoP$niXPKDWCqNudOI~wx@Bp0{Kg;n^0e| z_08b^Fb=#2;+cBEN%y@Rv%)u#X^uA@4Z*E12@J{pI~VuX)*5CU)Cl6yhc$qLMO$U5 zNnSl%?wp>{@h;99r~2i1exliud3++dE=`L4fCf<_sWh;0f#`BU4GCaG7TS$}{5OSv z!YwxbpE7)?_ z>{^1xVyxPub>gYBAA19-F{vxV%S_5yk)%~O2Tr`t!I=1TaDDm%T_BBVUHKX`9R1@lpU(%VYwIB=G&hv%{aw0C&UT8T#l9f76MP3?-AZ@quNSRa&EFh*TY!CV8&ua|S5s^$ zpGT?yNTn_`6O*EGg0UkKEj6t39gd8X5}XwQK94Nqn^R8`C8JQ>&N~AIyviU7LS7ZW zr${*+ixEy`xkl{I-W=G%2%;CcHQ4Fjli7rz;&xG+ml+&(LIO zGKD>Y+l+Vk1N>kg6UxG-+rwG6zy#3dWz%7K@bLUP8K)9j7=`u(*y5JkV@ngXCdw;9 zf?ZXvh^1o>YCO>+^(VX8`cQ zWyYT))_~g*vO}Lf<;VvdbnC5m{=kz&`^2GXghDiy8Kf&!iqc2r0wdab}5={Zudu6>&e9 z;QvIZM)>sy*8l9A|6cz`;2(j11pX2DN8lfUe+2#!_($L$fqw-45%@>oAAx@a{t@^` z;2(ki4+&gyy@ObH49O8OrEbRoB7WEdqcn2$JU=^R*5Kr_>5k0+_Kra(yO@lDH%|I? zK+;9F*^IAq>et_oovJrYvy9ECCVNvX-JBXI$VV9{%@Rb2i&)b^r|Ou31n0Fmxc*yb zdt%fnd-c#CDBpgn{MA>-bqW#Zh`A|wW=FlvKTs0J_2y5ivq}3J>!p~2bF$&RUXodN zI~vqFQYUv_voWM@qO~-0of&w|-kOKWPoRTJ(EN|~&h(oN?%U%nRn;n5Dyl@EP8zeC zHR#h)HO8zpKBdMs=6S52no6k(Dne?AP-AOI4W+HA5<`hO5-mc7gbE3Q-2Cpk|G|BA zf9vj7=fz%UoxRstYkyzty+2F-c+xo4*^{BRdNZw}xs4~-r%trJw#9?>O}WJfy6$6a zk6YV6>X3?`b;ag$j)#KGNymc!wJ=b|Ej{KUZOV>1MQOl&}wdBcc z&iX8@>asX*c5)rK?^@>8uHBIWdU;wP=Rqs-bx?r~TD7E*vvHwGD%0Iutye2(Np++q zvI3FU$oz(EmNYv)3pV*iHlZy7QDA6d3l!Vjp3!#B{{!J{hr#TM6X?(%!`$zFVIVBz zyxB6HrFm*kvsyergwqS@3!3^=P$}}C{@?#UzW<9E_!2MU7vH)(Vh?zZRJ>vUvnUWD zB2qoa?0t==dEETfUF05jG#o6N+|S@CM#mJSqf*5ilS|df-AMJVmHH=+Cca4u1`4-z zNqBBE=SSs%*soR_HY!Ej$X|DL-2r(kH`9$ksE3dvi&n>yx!lWAFnVdEb#sad9b%sb z0dxwj(2(#|iBtttS%(0v)#vS&*!NHu6%PT`>R={hC`8~H-*U@l@K^ZG%M#GdH8lm&g;1H0rQgzE z&D6|V`h#rx^of{E+q_7yLIq#`*ef4ntj|0ab2!!%;{I;NAQUU%t@4S5_SELxDkjx( zF3OuIDHydaFt9||7NHfkH}XuE#=IxL$R|Dk85Y4XqCWvxcODrvZ`LJHOCnqSF9RSq z%O!Q}*nO3?Nln`76g5KhX2Ebm=frejS=~R-U{xB15G5&3svB;wxlM*(LH-k3D~^R$ zT&*v*bnCCt&4z__&7Z-S^XmtmDckZc7tg(RA&Al z-RNFI3TPxVVVRrJkK>*9OYu{1&uXV>jCyF~es#R`9r}(F2>h11ha`6}%vnVdSuSjN z=>}^18IO$5Q3YxJH|!l_onY=<7o;&M(f(fC$LV9SQPBy8Df&)Gt4^nEreJbpXeJ=K zl#4uV&a2ColgkHs+&(h^WuG5fNB8;gnq;7F;Qltv`C_E=>~6M|LadE8-?(WOmq)-{ z^DDuc!_VE19g3FcHZZBQt3{FybM(!Y=rH~s=_fJ!}EH(5tUNp=2}c&)ZsLg z>3-(OPmg+YS3N8E0Ctw)H_Gd_d%(~|Yf$T!Hl}VB)*Wg%3q|OM*G9Du&&35zM5L4T zM)d-ACQy-MA>!acO~eJ9=IDVI*6my>%q1#s4Wrr4+}v|h9R53UE3T;7ywW5e#3H`) zwDYTX4b4(t+CnRgdt~Q>G$4SE5y4Mxx^oTlQkHuH&JU?ZS?%L`bmGB#$3~aWlHk5H zLQ(GA^=XfDI9RY_YRXyrb6vfo(MEF$4>l4O=+z@(bWJN6X7&^&AUYmT63Pgtthqa; z6ZW-cwWtpR^kxfr#oBmJ&Y~!EK|@RdkRI%79xYw&yixCz$S@yZ6pT^%TF$>cFrs%K zvmM`868Hl@)X~$n02;jAI8z}K&B;4u?PI=F-a~0&)VWu`JJn-nrvyP!_X_Zu z_XqzBcx28hFEvdkYuULEObB5hp*m0f7fajarOejsQuo0?^F$!GtrGyW$We_Y{jjAZ~co z2TwKDEaG>f3Y)v;Df&l_y9?0x=?qnCiePWb1L~oJEoW?sW6xoXOAoT&V48yZKhJwr z-a&vMC@H%VfBGAbeH=ZsN{Zx4>C3mNS%^5F?p~db*5_bN2<%|( zhw!8L*(f`^_psAFAI``NAyTcvzrDWrzb_iQG8|;eri#9C74hL{OgcO2($SP`yd`YN zhAVq(bA!Fcc45ixi=PK=n3)p<{<|-k)9#FND?U$eiZ(HjJFo4i(8qURiWkTbS@k>X zpb&b!QS>zU2Ci*`K_unxF?OKV_J2# zC1;EH0`Mr8%sk=$)N;zR+2WFyBbneBcSTC{a^IA(b4GBJTASvguN+tPIvgPEO!B4r zky`FPV%}Q(n1~G+ZvpGLB1!AMwFZd_P(lQe9KM}@yA<>EdV3&)C~2p=pgD_(EJs+G zXI~+gl~tt&k3WFwI1s+9z^nlg_6YeHJ*5HW)mnlPwWlK0e9{H=Xx`6{U4*0l`h)ZL6L91kx&D!h2bY-0VMWi*QN? zY6S^n92w4m#s=*?SGL@+4!7N#uezniig?v)1j_z~$~{bXtXmhPZ{%A~GLWwUwAe*2zuu5BNxWN!f06MNg-|N5@}v`so3s zoz=1m`lq7(2$waq3^EaJtc>cwNO(^SVK}4owsFa?L_!0) zTtqRo0qP+Rb49F5_g`y$oz>dexZNrNXS|4)6Qu_k0-ANI#D7t#s*&jzsYP6AMjLl0 zO(KHKBiZXgPzKp4UibS-@#*afYA#Owd$cgP#~Dh#oeZO$SaA874VHSAkjF

Je?L zZd%WdgnB|kA7`ol000jo%!n#xYv2>hn(3haqw?+Fm2$49z&Xaj$0Q63nFkT|0hz)> zzosCOpAQQd2c^1@YHb~(*Shbc3teu&){^!Pqgb#V$_0JHBN9!KD7mdCbaURsG4f1{ zG=qA@Fwl?AdMdS(c~2E{HyKY*Hhpm{itLHtUdf#XZ*3W+^yyKA47Uz-5h{9l0Q;EJvz3+1pjo-yz6+eZMecqe#Vmi-w%1GDC?X^kuUI*}#^fv8;Z|yV{vBmId{)d3lo##**8tyIC)n zz`Lx;dwb{ZS$Lly{ZxqmODkRRQPb6_)aWF!OYcBcZ+)kgPHSRopzVvfAYR7l7QNHT zp(nC>XLzp?Nj?DV&@8g|;v3q+(sWlymMni@9jsO4h9L$LB#y&pP>sdiBbP(=llD`r z+HUF3YPf3U!9T9&-kb#;a+}8%_`CWcwOrnAr774DIVCs5obDbAL0Kz;>-sOOc+^1} zI_!#RzbInp`_`SmjMk>qm*^eK8mMvA3uc9X-r@knVcaN?27NU|Se%SC=`tH2Liqa< zz9xZP+fIdUK3IcTN*y@r9*=$$tFNxtrA&9kHtvFKDDnJ4TIAY=rj=QgRJtQle3c^1 zuNY<_HQMg{$&^{=xLc2r5M0Xb|3bjw`Ev=%L9|jBybW8O(dnBTC5M-e`F_K{FTzl3 z+r0+@EZ12P2AWgi6e1f!8I+G2U_qPwZt(wc=NJkz4c6s;tp-KUg4 zgHdrCaW!}i4;G{d#}&AH@ujp zi!u~UYwFH!5o=|s^C$PUU2yJ5lsX}we#=f%{zsX@;lDU=oG=w)^!YXqxP3}}91o+Z zHOy2mY7pm5um4F2e7&OS^kM(=-hhdS=!VIQ?XDBMiM> zIP&MZK=zw_*^YiU{-CIk%zMiwhFhqI%FF=fRix@2su!*LP~8L&HgiynXsixXqx>ON zC6;Il4UBelv_|W7Q+U_WAVHI!Qna{?k;I5^UXA15>>bJ2Ju>t;J7+n6>foh#6SDv4 z<-}gV;ZHEU8pc{PY`5= z$W@dXJDZ`ZTao_DzP^J7s2R@p_UD%z!mj>)x$#I~$8>wl-2Ya4EKM5iJ9O`;!iJON zf3XsWG+qCxoXmCqBp3qv>X4Ld6$1Ts6!0y^bv4Z}kdnwj=4RPZ!(m2$&{bRKyA*&?ak;u4g6aYGhWcAw@Hp01>0$;);miyE?}!h3cDLkvA5 z*%hs;w@7#2gYF+nEWBb1I}}Xle$x`IjPF!|cVTKMQ@@R;M29Q)_~J2tEi<-%tlj`q zCgH1Due4K6Fzcx|D?3kV)dKx}^GFrVXLPJYT|P#rv44;dz=}m3a$Hpjk2sCiH-0T- z==+?|aIfYwbwXd=jY)OW@TrV++5|Gv9PeRTWQlZ=V}@B{+y?b3Bqi`9-D;CyHDJ_0 zXJS##uQsaOvuCs{R;gG&oKVd)L0Td!it-$b?Q=sY1(4PEV@K@2n;%I$eL5P#8wnSs zhX7)WHPlr46ZVvfZo>`qTEs;KaRLKb+`nhLc8uiWE;ajtSryPx)wXqh&Jf^K_0$g< zTX07yEeiAQvl#wQBak*zC!`pExSW!N7&uJimz5Ec4#Kb3xE?R%3Y2SMy#}Gm_TBk= zS|wJp?0gb5^YBNqiHVl4If9%anZ`TBITSqjphsKJ1|5sbj(<7)gj7{frS)%#Ze(Dt KUw8MPfBhF}13e=E diff --git a/fonts/X11/9x15B.png b/fonts/X11/9x15B.png deleted file mode 100755 index 451ac4913cac4561aae95cdd36389a1d659e2566..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31763 zcmd>lWmFSh{I~H_R8m1u@`r_^yF)>xK{^IZK!gFKYotMlq*4+Ck?tNaO1irl1Bq?a zMhrG!Y&^f`|Nc48`{%hY?u+k@bM86!-t+y$H~OufI{h8?J6En;q1V)SWq9SvwHH^e zC|fMuDty@dwDrLqP7|T#=Atz1Q&wwLH={~ z?%kB1On)B&AeV=sjm}h)6v}=J?LksL6JZrv6UsWA1L$Eg91wG!fdC36%Y!SffOX^| zszaDm(-!9p?c9 zV{wOy+UhhQk7(b;zE3B>t`%3Fd?}DS+Nqec>HK14c>ZQiLsavLZ_TCxddf#ymjOb< z;{KWkktQi97+6@bCpUlTM(bHcv+;rs{YN-2PI0WH#ihe7L657hy9pTuc2*Jdf7tL8 zr5p#`G-vz_&gG=bG~CIU=nzxqW-7{)i%7JSsJ_1$)R3F}H=^*qyV~vzp6vzGSlif)1Wh2dT^x;S5 zLReHO#myUU^*)5~NTFiBvQI$O{k2uOn6cgi#$_#RPS&a$^3!sad$=sHQ`$MDr3omz z5^B}|aO$DVT7WuX54hZ(h-^NWOP9?8FdY&+CVjZUP7t~e&b1z&em0dNZKYR0n zKPQ5?KNFm)0(L7%AdL;F3_s2&%x`>ecMBaE%=Y7`E1+c~AG7XHYrgEHYa4jwaN+`H zc`mRX)5pEPBQ@+Ug@SE4C8erS3_m*A=txuS@je|3)8qDjhlT66({i!|6RXt5^mPZ z`!{!F0Szb%*K6m@W^&v8EaZj^Bh#P!is8xb_!Z}pw&D4xWb&VBKzp-L2#zJJsbzy@ z4W#E#Uee}sq|`PP5T^~4o=X!Y*a!gy|WIUxym&#*L1-V(@9O_yVsb+q(`9WnsnaLbV@IwdRNcM@q)XYU%iT+bv!IcMo|e8X0cT3~Jr z9FyST*xmS$09DTbFDE8sfw`Z#n}&l{KQ2$X&U_wWUM*d9^se+a;5)_+ULrZ_Kz0c& z^fQlFx{Sccm!!A(fj?tVIth{DG;-*5!N{jEoESd+4a=CF=4x0;MNXc^8r&D8jEKg5 zGtb+H=a^LN)mL1q-r~i`aScO4o6Nwbic9+B6Gf%xis>%OnRG5fWq*B!z*V8=n|3?` zk&+F2v(8o9E%wU_bKFXQ@aOIvUb+bfMjVIuJYb>%^S?Q130<$`S8TydmY-EDf?2`Q6pBC!Mi+A#zz{Yim_n5_YqqadJiR zJ$HW%VF!J|gM0%j?2b!^@Ex5c2h)OY(#O0!UVmkyezVROkG*LE^z{1Fat^3dEm|^9 zhBm!%juB&k74G#fw+KOwhH8qoOV5@n8RnOK;w!U>rD)4zkR3_2>XP(eJ2%U2!G*! zK?Si^RrDsv;h$f!1&Oq2h(by+I_ZP$*9)2c7YT?)GX$^qnTwQ-2=o3vrs*|z7lN}d zinHtoUgpn?@aR(<+Y+A?;1&H=MhUes1nQw=l5Go-Df4=)E!H!E-$(CQZ;JJn7<-z# zKi^t;Bjta7qz3mIRe6;jQ+(BWaxd-Z={SZuw{zR!)^uEHJ!O*nJP1_I|9w2AOgUyj zdpG>(Uy^8<5HtPORaE>yBX+25dKNbkN|E<|wA$eUl8}5RO5_H9Lym5=?{oNH6aaP6 zeftxi4VCZrXQ?qn+w32GWwGPbzf;2y5Q(K7R_{bcyEv)rE@M)nq8{UJkz!F!Pf_NM zv8nwsHKE`Z7KN=(+{O$;t%ND!pI)f#s>=tymni}$&f?OwqS zMu*Y*Xe9Yr`U0GXe(T{Va1W01I1nxSp3$-HH8Q!srm)@HJ5^fl0Dh1(>f#<^Cs#Hn zVY)~DZ?YKSZL?L%{6X+j32RP$(r1P@72AXU*otMYy9LCFuLaEsyuA5LQonZX?l?R^ z2AeqSZ{CfJ!T0toSKjFL;Dk=o{Z7c=H5w<2Elz|w55(O({@PkLO|fojDh4gThz%+! ze*}^kIg`y5((!Icq_G5&nsu$9rLJMiFnFCm-?;$lVM5zqgL3XIPi!BYC+KVY2mbHm z7h!YhB9~c{sN$hUxFN++{%bo~GxJZwbG1F6ws3VcyV+_SsRn@*=lR=#=da0z6CQN5 z8wrFdxcO_qPag$qIsq?VC71qVC15MURkc#y;4jN9buK`&kp2a0oF!js&H0W`>r|t2 z?j>*d0SMUX%dHx^x`Lm5`F!=IW$PzIvxDezkL=xocE*S0eFq-AXyky@p7&?)4)qz^pXSOqJj z=+JNd{kW>{xY9!Q)S}hjT_9;S6(=*_aPbS`DO!F>KhJtJTKGY4T4;@#7QNCnrW#Zv zZ7pO=T`Xu_@Abb0p;(^vqYW@+?aw?DsRDIP7fOd>1PtXx8tY9;#R4i~>XKVCOAkrzJQUdeN&dos7w=J7GYw&FbW*DkI5 zWOhJC-TvZdC38ftOqljl^gb=fLPKo)s|IhFaabYPe{EUJ{LOO`a~A840(N@s7U{+1 z2{~&2x=`c;hbw{A9%4x}bKp1WY|f4*X*DXYRf5SMana6a*+kD>mB%TI8VQ_d5b-sq zKk%xKD`)fho?u(gAvvRNQjA$r-1c`|kfj`Wriuf5;6kf*17?BK*|uxC@F{cF_Mre4 zyE?RsNFQ=ZJqke#iuEBS!St7%;&G27{G7aP*OdC_Y1=+LINHmL4UP<{5w9Jy$Y;_Q zN)GGI8YyYOvtqwTcx5-ySK~#p#L~_zDVwo&%F)RZi|G+!(`9DOs4hSDv0hExp?=z@ zM{BAQMvR-^Vt}mxrAtZbV;8>Q>1w+*vl;!2%G+84gp({~N5_zSkbw#+x{ML} zY}V&u-#2z&;fYzZ3NWKJFoSh&Q6&v|V_tgiFCh3f4-XR6<0qM7pVM-y(%RCQ#vjKw z{cjnaFX4G}=%^U}gGJxpkh@rt_Nu^9GTDx)ZGreDe0Mq(T#i`z_9H|_82_UMNN}v; zh=O@O>ZLTba5P!vw*1y?uuDsa&tKoLNq;>5d#((2mLz(!@|*dPRBPhz=4^IaaZLMj z?-txfrZD0Wyr8OIt!3g#n6?LkY5_3?5m3ET2t&9)2P}(V6423XUpdUaN8J1wMD_Q+ z+q6&(wv{JbocF7&Q>i|sI1=DifM8yk5XR=O=rA^H*~A~?JuEmb|a0VZlo<= zdiDh$JW?jKP;DDBjVOxt&H@j_dCwL`EqJGle~(N=6OTP3OI?sFO;v^EthY~iN%Qh` z_hZDXI81?>4#}c7M{$0_YqPRtTI_d}O!oM{oRy36#2ICU0eap(yA|#gQyK(tzf+^g zbE|>CIBjV?AQW?s+A+y`dyUxgBiB-E94fO(wJcC*mf0dqlh8=0v3|k9Cv*wBf-V^^F;=JWZOv~Hrx?vWG9qJe2t-vk5-4(PijxW+p zUft}rzgoL-(2g9>y|ErS`HhB`8@bbtM(hS5(eEzcOO}?@@;O>dU)Y$Km8>prBS9M^ z6ca;^QXLEYmDI|1&s{C5aCU?jI4=t??@f3WY~^p1V%Iy>1u~devsjp1L*7ib;M~Xx zkKqM=o7RtV%}YKMb)OG>sqru~|6;DHU$q?sGT0>#(h#0xi(ahTLKjy#e+1>sS{+8Q z84g^1y3KCvJ@yjfe)=tOEl*u7a`@Eid(!!MpN~y_B5BGGFXU@ZTJ(ABxim}}Yhv+Q ziY`*(!30N>_myF{z#pVro8^Vg$i|r5pV+Bpl1F)yIBX+=Yn4rNx+~^~(3h{hX>vBW zp*MTVbfX1|X3Ipa)A(`Oh_Dunap1E}N(IIMY=8-_TvD}J{){j1DI{ISCoP?QZg`qC zhX5T*a4dw~2;d{8>0z5x8Zf8R+Z;f$8ezjL`Bn==EP`q2?vg>CHRqhzRkqBhteL)mqy|nH!;^eVP@?k>FZY6V`3voL$uRUE5EbN zkPSRuKA(Xzw#6@P$v-w`w*00wPj;#)zsM58${GAll|9=N5P_{=GTk2Q7ag9O**tGefa0up+6*`lmang!8x>O-A# z{!n`LQ%>>u57XZ=#-7FcvZ+01G>RItUm$@)qeJOSr-@o5<40*cXs5tRdEb23PDz7& zMzHuOpqEKit1&ZcdJsa(zLC#qxbuNw`v}3thyLb*_+*ln8QHMTv^~meJ@LDRf7HQA z+*9@sMwYjSsyy-2S*|_O&e*Tyg9j6YCC)`t>q4%FRsY zPsStbk@t(10>B!JdnKwF4Lda5p(^)l$WJ0|7a~U&{KMErh4m-+b z(l~m|Opb>wcS^$B51M4L>mx;G_}CcKMCaL2P+R%CqI>5H&Q1G61y5R0+DmrlL(*;j z6CI5b0qQ#-OUV9|@>B;Uh|`m#h8Ok=n@mP0_*LyMZAs;~obH&p5FYSMN!^y3XF&&w zN%QCi38qxh<%bH8aqK~qKrRVLk5#o~sIgtq8MlGi1`ZPKWt(zN?0qK zW&z$d+6FLh9O^LsY`4=fwJ4~QcY%~zzu2HbK2FAc57P5!mlx5}&@6gp&7n(irTuy` zo*QoMP>37G$u-a9%ivpbpY8g^DV(RS9H=3zh3E#u5A!fvPWBBdH`|^*DB_yKq)!i# z-ja(Ofof`|yK0Z>C++HRo-%$!5FJ{~$b2;$X!Fz!w`q#`5Y=n>HFPar2zEp{gI&)H z`G}P1nY{zXPM>v7{In9%e=Z}{$cT3z_bc!wFRfWs9{oAU?nShaI_))18t?D0?y*#_ z=Knwu^XuJ$^ja*W=uHl)#>-ZK&I*)S$a?OnZTl}Sc?vjJJKrx*fsxj3v07+qywavsvVcDG8G~tu>PF7UW}oMx6BXnHMpIBj+Lny@s&0$n0jN{9&be;^Px< z{TZ015%62##$XP80lu`lLxjVfzL?mrYnD;;Gc`dgQlUkb6a-!&vS{^B3eU(4_=EtR zk%X}oS3mC^J<;Z{9ff1dDD>9{jk#_%pz2VG!pMbI+V#0#nX6&?y7hP~(>Hf0A1eSO zYoBWP;_d5N?*bwnOtAa%{+SCxvo|MP&0**iH^L{oMnJBgJj9H3H)?o#3de-tdZV@q zjIZ=uo9oAFK7bZ#)}2G#dKX;av!bTnR=R%%i+V36*FU}0eaCN}IV8E3uP0eX(9QU6 zdJ9k>tIr&`H0X$cKsNg(siFE?G!JoxW)@;|6p$&Sn9hE*vch|4&e_Cn>19_HmD6>2 z=QQ-Nu*mD%Xg`F)x=xF~eOz=&sgg-$sHGShaJgW@(7njqlz2x8U#XM7F@4i(DyzkE zysM)={!cn2z6QTOb9Pp{3Fs+I2mzbYXqm=@Hpis)Rt%=Khs9I_7cV0wr2B&xX5=fe zr+C#8Tra%N8tL5fPn4|vP{H5Pcx%>EFCNb`OAmwWz$RipF5Wj8Z(&lh5#&d*+|m9? zbJKdQNKKLS%C;OU39}g`6R-MBJDPv@ba;rjIUZzf}V;&LWi3I=r<~?*|;uCaY$CsHj0C=AFoi zNJ|No3hO=H>u*PCH9rmSjTh*p8$CbK@i)I+)8TdD%omTYF~t|2p3Vsh2ai`yg0A#yupWqoGE?E-WR%6Y?FvlNN zDr;nor@=tR<3VmW{REjFN5r^;Guw}0rSUP?cbMI8BJt%91&iDW6Ws5;!bXJuJ(k&hni7ar@|H0OK*7U;t9?|B$SEjPLxich%1!Ob4K zoCL4558ehcKAdK%TInJ%B4hbI#mOaxv4_*@l;RVg?=7^yt5HH7*5A3}dO*^xyTYu_ zZCUKE(rQ;Ct)AvGzeZ9l-HO{l4YzqUHR0O&jSmcXhV9XbW0aEXH3!NTqRU%e=SQ)C}{C#pbK7Ce`A)wyw7=x(lsFOUwEu-S8koCvJoeFav2y$1BV+u~N5CGpkZ&F4=`N-q{(l(U-4zqm3(4$2l(ik0wh8GcQ{W)*8t0)x68AaJY3aK z%s0_31lcIkP7wD~_(h3EfUUOf3BWdK>VF%?RtRoCf0CSt(E$20;uvdnRQ$b0Yg6OL z@08_yY0l|hOJ!KiX_C3Qlx`{}tmude#WeG5ba7(06GdAK1q9Xl)*I81CC{2d%+2UStT@V%|GL2MN|l-fR)9RuYtK2>K+lcJm&hwPIrNZHJM3OVGkn6kVpQ`>Z$ii zF3F4BbU&h7<(U7Rt6+#T6i63$nM`B*GUayRy+_rRwIb1Iqly4Lv&0?fT1dsa@Z6{7-^7l;oa z15kpcuEspBHw((+)dd+ns!Y*FZ`6nR8e1kz%H=EA;v!ndY=P|=a|Jw_Yrn(L88Qjn zUHth0=t|Vx?uFb@-uWrowUU2iMRl-0TatwH^^A~=avmS9sp`Z~z@60f>5t7r4`T84H7D#CV2%{=1R1xNimkQ}7@CuG;`Y_#F0JNHZk{00Rh zbg9bNp1Y_1b^4msK^kAf)hkzWcGy5`a}OV{nYr`r8}LcqOv718>wsrJuWV+nFjMXu zlGyeST{zxC@60K0N7U_1U{4!k0><{+VKBVr`Qua}gUu zB$TV~mQxl}a*Pgxyzl+*v)k(}fdXG1wB7wAdev$k{p zj?f{VLUxzKpg(#qbL@lDy+V{oUldB~^UiR^qy81ctKe912gGS-70tKa1VU-3-C?_j z(Elif?PFEY-!qDwnne^B|wXHL72XQw#j!hiKMv;T(L7M^H&5OCWvDI0#Yp=aS_ zu0<3fD6~eja7O>eX{BLnBSn(=xgAyJkE7UXgrk#+q8j;XXi>XAxeT#Q_nrD@Y^vJA2EEN0MIQqXUWO8@$h4@`utyw;Xi}!+0 zrfM7K7IYn}(i80CB(V+8%_(89Z(+-!rh}>fw8ZVHvOc9N8M$V|B3iDJ5yrB+`LDbi z$3y#8%Hv2DJ6C^;WzZI36}U?@82uf?-aqH*-jt@AHRr5Qxn1w^;RNc!%3IKj7WP&C z2LFqrSmOYKn4i;K^|X-?_L#fm35PnM`@7iTI3OS4Z~pnAs4_B?l~*jz8a#cLX1_Ty z+e48xie1QpS7s*sh1z2h&kgX4hTldlWKv*hG}j8I86oK80m@tflS|>~cE`+yadu_G zFxot4y`@3*tH_z@8|PIk^C2&r)|L%6?CBJEe*D~avXl;@OHWpo9)$6*ms?Jh#tA3w z{?!D^PrU18;*% z6|^DGVmu`qn>cD!3d>3AebUmaP;H8#3?A@M%Iwk8PKAx=!1=#_cff*u{BzW*uUImT zXC&#kdTR= zg=Lw&y<=4LiHDq7RWoaubXw<`9bWx-`6PEK?LBGs%a#O=FI8aqff_=o_4XmLOu4b( z@YAt;%q(AbT>JyF0))ojwa-Llu{GD7;7Mab){vD(nco_HpF728jp&v|6`*$8GB(wM zBth+&XS$}02s+j6o#9hbObZwJ53Iz$S2&TSn8^OPCCIuInQ*Ye&)K;Z;>`GH`t zi3cBxmq4=LxyF2t0)V-naV1yH`71Ci0SKL}VK8cEfO8-kX^jRR4xbDngL36>vS%;# z0LAuUS|UpCl}Ycmq>q||0kc$A)V3v1(PbIet&IK0R3ATjy@D#%Ti{qc-lXtVdGfOj zsLnxr%f3uG6MBocUgbdx*9@}rXjItk?<|zpOeySPm|Y*rY^kbZgP^}N?%?q1d;bnn z5a;f$Vg@{zhjn%Nh-Wz7$WpNN?FYBawK5fcIiHgA9Fs)pwFfajATst>jPD0h*Hrf( z$0vWdjvU%^KAe;feg(TOXB`ybrjv7?D5rNDEb=+30TV&}Ca2?hZY_%&0Zg};Rin=f zh6CE)41`IgrK0@f+qJ!~a&)c7yP*EGe{TQvetRpY|H$((UH-BBe;dbmKX`XBRky-Hq6Na zC+ssJIWpl#y2!vAVm7vMX;HpnbBQc=-*ER$gIfGjUfYy4GjW=N99Ngi+?nZ0QWYx< zZ7B05N9%!-Gz{}?*5Pbf)o_tN${5hC0^>vCrJur*m|DdKi%b4OPbuim*3p*0GZB3Y zNs9u2mYbVyDUFnTsC%=nqWc~~UrVZvC|bh!>xiqjdBlu{Co1o+{7=npf2^5jwnP|^ z`Z;VPcW&N~LDO?dz&fieO*%%cBTm%OcVFy-4ghpl*qal7dyY|L1!%*zK6hCVuw#IK zNIpS1ock!tjOP=oti$x2Xfm{0$ZO=P2J{{lCdPT!c1e810gt|k;a=zm*=;$cyW(&lQUD~l5A zdnq>7bo#!U3synkf2xm@o=MuPe@lDM6b1E;w_$runn8}P*vKR#VVA5~&%W!h;jj&a z&bbJlqXFwJbWhRa%47Z{igK;rJE@^%HEhJ1rv0r(7bMI|?N(Zmx1!S5=k&SBqOlPQ z1{-D9(bBQjj`HaC)4jV!2J>cDL?&^|xuJb^Q^5~WeuEB?4rkTUX{hsbi8eWs;GUn2 z;z}7`LaprFsaz2d@A()Cr#;LYl;_pi&X=}&H2up>!0-z@3y^5Rf+O_ z5jf(9tI|8@k|DfWum_U_?|^0aT%J!*Gk!O!eB~Zn+5Kmpqn3o2KRR3KS0B$_5B9Rk zMx94cUix_VJ*)x_LF)kHJN%EsSr~^~efHny~$^Ex;BE$q8Oq*yqK1{zy6?0 zGkblMGJ1iFHOV<1NSZ)rRXtbE3Nwqv9Bi z_KgPw_;lKYzdIUo$oEcLgk8qilrDX$%J(31rTS@u%!p+oWl;DMO!3@^E$ z4SwouA-pdXO9uBp!>6~~XZ@Xvh&nhZ{=2)H6>u6lVC4xi%c{AGg4G>numeMCT2OmN z^Kw|c({l7ws5{otvOUIL5yaK1xqhQL>adj0wJx7_N-*}n;Cb6V%3+h&n0eQXb9MNqzO%}};BH;7rHTVE z?62M@p3d~mqSMw@+G(HA=N1JFRaU zEKC2j$mxaR>+9$+|S+cZ^dr%WqZq&Iu=!p67Vai zS)tf33x3bOkKMEWNno>uu5(#H#oUg{Qk)T$e1jU|fAx)61dG}&BsGRN{HNj9r%%Fv zrrF)HB|i^mS<(;-XCV}UPxGHyr=BM++<(vib|@@gxHC~g$5Tenwb7U1466Je&Hw_p z{4G~1Hl;!0G&*JeaRO55Db$K*KebeKwXA{s6@q-MM$Q?A&TKn3Q*V;LgxD~$ ztW)$Tdk3~!fYFy}%BzM#8|k&G5_SSY{40Q+k`ZN8ztpdvjC-9%>N~%6=Xg&}P_ic+ zV$v_0SHCGh_^96^lR}mOg5imF=hU{vu<_fr2 zcw?kuPF_>4q|xf`nhT9AOGEAOs93(Xs?T8$!&(Ap7`lJSb(S31a~Gsjjwi=+e9$Yy zB~AAE;DS}pM#t6ulNT3)>`bldT0uoQ#@w%xpO1EN2GIq0U3hz;ee3ayLjlx~U!sA( zl=?~TLlQs4^^`*_QqK88{Of?_m+$JEpB8-y3oYc4D75lbIoX2Q=_P!n z3SesB@PQ|~`u<@yjK@j%Tp>66sN5Sk(hGHKLqTvF%XH}8lt{9580M_MG&4qbtov~b zyU%QnQiRgX{dg%WKXm=&#+pRz6cygqs$7On)><;6S$HB>b)T&lRl6TJNU1!E?O6J# zH#1wROd?0Gar(?$PYEsBL!9kXCpk3%z#2^1BPPWu+e zo#zmhJZrBJlBXpX!2;)VM2>u%5zL3Y7Z(X%+mlH33;MUtng>p6>rz{=mRuMDd_aXx zrpf%`k@(hb4l_D#xjqxfU3MAY{23DgZC!Eb2uam8V9O}-#&sh;z)GH&1s`_LoB~F% z-@o7v#Lg)M>x|UGI`#ixmQnd=UV4pR=H<`-=-~5+sr#v+LJ%X7;jX7Sr;=2GtY*$E zax#UstfV8_woPVT$c`kA( zUMio;@_(shQWNsIKk#jkqxdoHuwahdS!*9iDWc7^-R4d|AB-8tw``cfMkMU9Apq`# zE}QIrPlgK=`Gv?#L(Z4sOR{72cIwF5X!v2I9(Cj0+ON0!JK8p$8->|rX8Np+Rt#Q5oRc_X%I+i>axX9fSJ_M)@n@tSD}@9KflPX%K$rPlkWYV<|b z$?ik${_UXJ+>&N(@z~qL#sBJUe3A55TwF^UsuCRycT>@N+NTFFT3D^;addhimLp`} z9^~=Y^=S{L{oF%OyT+)Vpdk#uHQuKlE#F!{0S#7YR6LJwgkE1D<~meMttpKXKcC zG+6aSbNC@Yl8-msi&-GjxLw&YXNjKlJha-IbXiz^q!mSlsR4tAVKe$=nHSbbL;TY9 z6Vi9P1qB%hz=z;MP)Fmz4_HHMDJ3=nqt8f>KT7f?t6tc$y)PjPgfmeKsME{c`C@j_ zwXogHL-(BEJ7O4`fQ}8NYmU;kK^aWb!2*Ge_u!;Iil9GLlB|uy7bl+sOKaDjZ@anl z0iHiBc&HvyxqSj)piLsay*-4Aq0>GMKQM`f~G8ph7=tCjP7 zSQsqh5>=Vc*$?9Ck<%y&J^n$Ikg-_VFRAPAWb zJ0TXOOh9d}s^so{S-tI9;l#GC?1jbqG4DDb}|8Z2HS!s1x~&Xk`Aiio2{Zt>Kr;)0Ttti~etQOwBfS)9vXTqF*UENw+>2o{nNhm z<*8O9)Q~68aa}FuL_kUG=2D35fIs=q_pbeu07e!f#yPmopRRP_r02m_`s^x3CYXLx z9ePZ8!9%{HP56&c{Qs`~?T=ZtzQUcNx=fX277I^wYf*>@_)w8FNDWfX&2cX6ecjNW zU%2`#qMb&gCNhdiRR=PHNbH)1`?UY^Gh}B|?lu1P`ZmaVORh~KJzMZ~;Ok*oKmo3! zkOqO~ZmblJ_-Tg_s^{rN7NPTy88sj!QcB~v(VqOIP-l~+5ca-NW{?iF0$KosA`;&Y zXU&(F`6&e~T^p)t`8v%aDE&Q3Ff#6ew4?Bv>5*`LS%zChMKN$T&5q&5$kVezJ;L0G z_AooOj0ZvsNO}A?%H7k;4pRRiS0An`A1-9mg#KZuVN0&tRIW@`1$AAh>lU$KSOp6F za?9`gt1sI?Q$c=`o%17iK+yuW9pZ&J(E$%j7bc$9^sgLc_97Y1s^}M35lgL_ zP$;nYhVIF$24CfZ>)BH!Me#pgj7nW{seMKJFnhC7ENRfuNK3Ks2igQ{Y=Z|=GppSG zsVrs!hF34iu7jsey&1zUg%aF2CaG=cb1^C~T&UF5h0YY{a|!y#PQcE z`&qH{m{CeIvP`7!RBa%gc$Tep;UcIz1I(2>$bVG6 z!Hgx2=r^)rE6T+KD6ABrN(05_=!<=dJd4W3A|CY1N_hz?nUV}f=IzCLIcsM_yaW2g zrhi`Mzy)f})n7MIbXxfG#myHOF7MR~^&p!bN7ha}WpAw0-II}9d1D5BbuV+d(dry7 zhH4yc#tx{}c%SvPYT0dHU(#QWWM4J^!7}_3CbD3p13e>`ZPoJl^N9P$fZaB*F!_i! z_5X@GSnTBhs6cGfRwFdV>zdo=%J|Cm5VI4R6V+Rdz^|*I>W+71Sd1#sYqY~jWwuCk z)Ea|4E4G58f5&&eo)NhdDhBEY+OEVw8$ z4H4%$JeaR%c#Bt((P^jrE3R8SVcmB}6JqYN4zKN?WR>bevkTxK8O*5@*_c>Sc= zfn9l7c}yFeZF1lPIJ)%i8dXit{eEr-N4wsc8+o~?>MoEf_fSNT2=d3hSwH=_7BQ3` zfin%|1{Pd^Ib{4JL#oW}=4DH^1%}o3dYQGj|I0qNWcI#*JK#y|tO;V%EH3d$-B2kV8p~f4ce&h z)T5;YB<1r@>}^*<=MnP}{GaaD9pvnS-?_`WtNu`?#jupbDE75mngTkb4EiX=e#FR~ zm6;Y~OX4q&_1XzO!Z?e%ziz)HKq?=4+z=?#jH>lY+X@n@{B&B@?kP_b24naispJ2< zfBgS+;CK33y!p!iHss(ceU1&B+2ZYZQe3Qdb;JSKWx@Q{$*7*lgUhk|!~|_my60g^ z^}F7m9oBN+s|FXZwMlc)Z-tjodMY+C>erFi2jT(xNyvI4IA3CL;R0cZSsD$r0(wlOM+xc0V(4`RJXnn11OK zTrsIMKPsiHW{ri?vQsOr!bE4?DVv=&+Hw)Izi6fwMdp*X^9kt5k9q;FDqKGN=r35y z2|F0s`YpewVU}LL)Y=ya-fQCH3VK-+Ae=fDAT^{ ze`egx($~cD0~GJBn*|O!1Xt|yxmEJ$#>tAU{wvknbRc>r+RHM8b}gqx7VQd!czo_3 z_pQCkH9w52G}4K}Vf8pP4iE0Gpn@vJ3Km4l?Q!8)O$Oaha3EC-Qc)ndxz5eH@v=E} z$f{1?zw#>AK5FUnnDPq?tVeP`1@&Y9qOiNqFH!AGVEklI=()c_O>-Ok^5?Dzqion4 zvfTVfJPuVDsn)FDuC;D+Er^UBvgKkV;@ucd+Nm$uj{2sJwzKU^zV<&02yT{99Qx|1 z%#f26a1ddVu26lJFYVv$;C#sAS9%py3R%20g;zOesZ0hp{TQSbW@f7LT)+@XUX{Uku(;~wB5Rg}~{=eHx)xW81(D@Nr;40O{ zLDIb>9EPt7f-h1Thb-)0XQ;Z52Z?Zh8+~`6UjGZ*{{j^;m}&X;{KueFNGh7&Rd}S+ z*+25&|8#l(UyL94=hs`d*4yxS08+Fq8UP*78WF@$dD2Gyaczb0;0UdI@d7hK=dw+F zjfpL0|E|$D+^TN2*^%CV+<4ye#tZJ}_Cj+Kvj%f8wrm}la3PBA`kSNLD&!+4uJ{TqVYa#i^!+nB6 zs;*&W%NimvWRY-5V^rzuJ|;s@hqng1GO`QCfcPQ4#p}HFTQ34Yg+6 znpk5EPO42r*2J;q^m((*ke`wdJmB;RbJXt!hu}g=c`ZV6EG~C<@DdgOBiT-A#x1%K zx5c5=#^yy<*Rf3*TkgxDEy)wk_;eF$P7Is9`A>qF?x_~*f_JjcO{qQ_Mrlx4rHFjXY*eCX#VmVX zteKKEmaF=+Qv$(iK%YXpjeOG&t4lC?ve7H%zxG``E_XAc6kTf5O`0%*^a?-qX`kul}|D zJcHxMyp)r-I=nsg3VP>u4y#s{0Ty_Q%H{55`gz0*XJm;x*nVKnbDq1kFS=27t%0{O z4onDi7&w@$_(v>Ml?)#80k0|T{3~CWtnQ2!=ZCXhZ#0;6(~wi$&CU3)dbIH3TcTA@ zAuzyX;CYx2ncQamR?d@K(A5F4Oam!!pMna$H`$XER^->EWi0rCDQJ{yI|RU@Pkxk# z{s?Py834M(w5a`Dqn`ZEX+1eC0m^C>L)Ol2AtSj*t;g$acJrxoRNTH*7bN#Rs|ZRD zIqKXP?%=XC`i+7k)hZd0E#<28QrT zvKb+H`T;TWRjm?7Zl#TleM_yYl9|rd7+*e^2`<*Q>|VB~7irn$#jvm5%tbo)iAfvY z&P|o8pb++axVq+*_id03#zm}=>n+wM8DqsQj*jTP zzqNj<>^p%H8cRK_vs3c#k@he@l?}OT?=%L4Zm!ThO0|CLDichts|Yw8G4qeNol*R(##=t}NQDGoKEZaORE zup7E>%|{)~t4&orT}{otwf!wXBc^T?QE1P4$XM?d91rLU09aTdU zrw&JWFT0%WS$U`9zV)lx8SjCVN?aAI`^uq{eFN_29l>$tJH z_6JAoj-_F9znnJyO#-el|B`45)fPLFI~k1VvmLN|*3IH0GS)eNo^ACY zt2wEi?%}RuC01Px!A$-@C#P~V@HLw;`%w~Y$T^wU@q-a6+GtEx1Y|xttj23iSr+C7 zu}op48uF`}Xb$nW>Gp%Dy_&n+#gdg@Or`q(AvPAb0GrW|Y(4c4IYyAab^>|@1amx% z=MXHlQW3adjyc*Y4OtM?p@o)@_&2WmG8%2vzjeDZr|^l#edXJJ!}IazGhhsuce@54 z##gRDA!XfRWxm-iOkgX#KwQ&Hvf$SF@IWBnxX8&0u;&~VS50(d3WO+!R>m2IYz3&*QPs4)U{O6!EddU8J2#G)H-?PE zo_UcYLpzW%82ostdqLzDSVRVKjVCfcbW2&VVopg(r!q~ttbt%v2xybwEgP{(8jDVz zPnqh^SRXN<(tignJj1A8I{s%u^Kl-|DAe8+dvbC=`6$zMsEf75YB{@w6`cJSGrPM~ z@?=?0R*u8aeC^*Om1eK-<)j`3V5_R*Ec;47~js)luJ&sh+c+lFD8bm{j~z?hi|r^PJ}N) z2gG|@MRQUM6DxAQ#xyOSgkFyAj7QA0<7T(BbmSPmdslfqsP$ERx>>P0qydecXl3)D zv#9iIqhtx{osm0d=W;Ig5}!R^VcgcU&24?`sdadtmtxK1 z+UWF<7j<(iGgy36I#&=9jZFsKGbFbyyI~S7v&6#!oKL zU3_9zo>5J8-M&{*K#?LKMFd1dL@6Rodb6NNjfx@= zMUW2Cd$3TXt2Ai|HK9uHkobfEN(&t!6ln=PkN_c-%kz$Vp7Gu@#yif3`{jJ#+uCF8 zJ@?wbHTT$i&Gn!2Fn<&e9u@BdWwV`f^v(d^@8=QVld$4@D?9ht0r0c&9OpRku~2MR zF3HOzn2a7hw>e5xTj4j@J?=RpPm&aI@jrxX}7g5rd9qb(OMNT zeD(p!JLGnU$J~FeQZnj;Q3s^K#h@wjtQl^E!&{UstXX3N0USC%?e{2ciyqD5t1Z*5 zCcB;$Px;vw^6^#1=*W2`8K>(@dErtiGE=Lr98#qP?ttN19!w%scq~#etUfmOvnq)j zwm9Pp2W%1{QA4W*Wjkgolb@q$^&uR?Hx6PTYqcAX?(xM%cp zi#gE}Nw^-~q#;qz7A#0@om|H#9_CKng2;DFwUuJ5ZYFrnH(hKnTEX6)3ceC>Yejg& zBGZ5EyK>sy8^tb4VSgs)UTFvv^t?$0FiG!u+^B5oRQIhBA&ZmKcVdDW6ghVFF}L|M*Ugg*xTsr=IU+U)FctT&ZG%v8Ajfo z@y=keB=}Q1ELy57E71n0z> z7EkA&OrYh{Me8E>7Gyp?8UEG|&p$rO+?r`XUVb?jI+Hf;;BFCIQAAVPk#);z`)sIX zuf~FY@yIGYqvIm31=^B~)2j&LCG~_@(pyV4+KbAB)co1VaDSU?cn%2g7OCuhH_=%1 zsrGA6c!Ng!u{P0}27dL|KO3JOcuLXspoCT~VoT2&vT|%;NwBnO>UeR39yCY(rFD&5 z38dV7Rqv-Z(uS|zLt=+*+A`KTs8yqfvQlls?_E^Y%o15|O7M^qXn#g)eLBttc{b)}f_C|FZ@DMq!xWw;LfI-%{+%H7>z|I=dQ#y?Zc4_ZQ%Yu{h3 zfxqO$SCk({JzeW6H!76de#0F@Yyu*939b+g6`2T(Ht}(Wy+QO;uVb)%pdRaV7IXxp z9My_u8AF@0!StXJVWBX}zMfmG=RWo3m3Xn-owziT4-fU}S3H-Lv7dcgSN_(kmbt>F)qOZJl znksyq-s8PobR_o(#Sz#Ct{j~N;lcAx$ZUcq|q+7{3U`Fsz4%Vt(Yb~n;rzIql55K)I8=!udG5&WI_ z{NuW;bLdBhyN#I2BB8rb?8mG3NYY!`B2s9Wkz61!uz9iqZFacKN(`5SDUjuSGWi>{ z3FYa}Hlf&Ef0#+ZM&g@SLNFCJv0XO9#AH}2w`%08u)ajG2FWCA0}iY5-LC|Vr`klz zKAHL~T*qf<&iHUTZ5&kFVItSL`!(tI5t#RbpTI49~n;ZnYo%GzSQj#K}wZM zbIoFv$7w~hzNVm!VydLQBf=~zwLiSK`!wZOM7`ZY&vn~x3+v^(C4Y}k zeb|~WNvI(~#+7cnxw6XYx+>Te8dUzY=D=Mjjqns~z5jfE#10=(TN41^mbJ~WG(WTL z$Gpi;5R}8i2p_MvP}iok+FH&Zxaq?;snsNY5JCmAZ8Eu1voKHmEt@=D+k&vEGYOSj z@1oB9lDRN2E~Q_!*Y$m5hF$>1>*cJ519KHNgMe`X8RlVaNhB4O)auY6IzQvs}knd487IAO``dltV>N`Z{)31WqEqV zJkL297h9bhyU`X{ZiSccqD(e;_bv&3_Z1Bl>g*atyE@?N4-(wu{QxVRxx3h%=dESl zM$@3{QKN30qe23&e-b`ffJtc%qc?l1Mn%s1SJN6Bymyqy`$)E)B*WbiWNEjz8$LY0 z&h{Dt8yZa4fAspPe0bx*W(=3s*2cv-;!LF39x92A^LJqIuchp29!ac9)|8V3%iq27 zBi>sIY+|&|hF3cWjkN%qO2ivck{<`WyLvL}M~8@@+Q@<5P>|~!;T)o2tb$iGqrCFj z+NAMmbe)_}@s-Z-9EW`4*wJ{WbW=P?w>8F@KHVedl6l_FmJb<^3|)K0ZWkN|R4d^q#Ea+BqY2yuYr6@@lr`{ z+IoMzx>oIR&Apr*VHi1Zz4}659NXGCbW8G!k3^(y*0AKLeubM(@k~^xt!V$aN$ic1 z7MIk3%IonG>R?Wv{9v6Qf+zx9J;nN(@ z)mP;q{}wHqm)qJHR_&}a4$mSlOKhu(Z^&J|15duy`WL=QY+K7gq=05#95@zAB|*dK z84a}wnavSyVVscUcYE@-qNR}qHyMd_DlJDUxCJX0Hr?yR=E zPW{6{P>JIlvY2|-{6|G42b(Z)MOmJ}vwb&tLpL~~zFCtbG?l&6i1$~?>GSzvj8?~e zzXqAC7l$9FM;Jkh32F7!jmwIh{Q%!iC(wc^$a~b5p567sznf^N;r&M2I3O5kV*N9T zDhNz^628S&pU)riSkRq?HHAsbP0^pVB z%o$%goSLUB9!fzT0Kd&wzMZL1z1^RMG|qa2T_a;vgIRMAmJid7lM&6^lZu%LSu)SZ z<+Mi4Uz??kRy|5lZ$Xm>NpRC=GsIhOQQA+hi+1Q?~j;qp8O$dfAWVyttg)s{<(m*9vtRLxi`S|b)o6aPsVc4n}^K2(;#$oQt`r9)LbRV6=2(#S$@s5S3z@5OWbu|0&`#A5g zShVygdf*`S2f%kCgpIU}`=s^OBlP7LP~4pQlF8mVavf_hL<`dXqLGJw%Y=4EH&C6h zS$Y`WDGZ$vw&v*ue?i|cffJ>cpxaRXxYi_*WkK>OjqN4|td;7DV4*y_Fitu%2+r2&%rl0U|)(t2(=zmC!65A!$)<M2RXK##D!j-`XQV){S$0@LSA*YIy8xx*W@kYOo!@~wT5AFg z2N_wjN=3#HYW7HGtfuMNS+Y{|?hi#MWm}<~$|F~5&MJ@E$!_mgmkp~^4P9uW_sQK1 zSUn}<;Q}%Vv8UXya?^nXU&{(s?H=q8m`jL3!@Wx$T<_e+isJ-Z*q3WCp|Fun?_F9n zzY%#_A7WAMI`}Rj0nCdaNu1j*dIejEfn#*QPP*+kpys4;o_U#R^u~Jzm{Tj`__2LK z#{3rC>TYUJrdKSr;t?_+Tbus1?@eciAKS`D{O`~rZ90IZk3H2SglkWH`F(vve5B7H zaO1r*ZkQuH^3*0ZU(>W3AD$?M`rNHYkMm}%=0aJ5zGzP~`5Ff0M9~JBQKhs|_@va? zW5-_JG65>*l7~YULvtwEkt0!Co?2DM>t>?|KWI&{DCZiYgwDu}W8Hv- zQt@ITah&V#_aA6^=uObTu~WNOvIlVFA0m!QGsR4oH*hr}FPSXsKmR^BF4_3y|K07} z(gpqCkLdA0hPp`o%PHRB|B!!_7I4jr(&|PL^=+K0+wQAVJ`5`!%8M7&oEG*XH|4Oh z)AT~O(4Xl|$Ml(~0J-4<{Y*+Fm0gb$^E?wv;Sl4&p4rzN!fmKxls9)vEo)}7E-`kEloAzP^J?ik+!|A=BW4%cPqwv(Wa)RX<7Vhbw%7~uJ})&_YGlHZ{4pH)9Cj(CxJ^F?2hhN3MGe_ z=qG?7qRaeLYtp^{tS)+o-X&|7gh0b9V!S(HqrWZO#xY@pud_z~QUU)LB1Vet9zXq! zZ*$UkxrW0TZ<;y=C#MHKXxp@~e%IyK0(sVyaAI#o~L0{{US4klHJ9Cqk1QEFc}Xg@quxZS_h5TQc4 zozLm0L$kXukeI61t7+0G6XMBIaGZY8$$=ak>gH`EhH~=zVIiuZeNVln5OOL>uFafW zYxODO_M>Pg?X~fsXwK#Ld3!arySJR=4X^phM|M=~9#=QvtW6GH{kU2zI{JP2jyn2ZN*D#=`Inn$KlQ&FBQ~uEZ)T&A3Y%mEkJIgfav;tEXSzPSivg!j zIqA^+ifNo8x%92Gnp|jd&xT}SvQ`jsgj3)Stnu&irby)B_?zgBz@2E24$}}*Xtn6T zyB!_KI*Z~Z%>ha3adv~l?EHW4(&)%`1n2+D;IXX-aSsBpTA$5i=AxOCKWKgG5;ApW zEOH*o9t!6?7$q!1rWYC5il7}{zD#X$yNJl0t{nOn?}F$*ZhY(*NEukRZwv$3k!$Gh z(p(wt+VqygRSStifUJO&{+^8=70L&0ojNq9pd380{l2zJP>u)j#$+Nh%3CqVW8swQ zzghbqn=~(}QSG^sDK8L!jdBR!SQbDX3>-PN^8Sy5V@vkz@ZYVdo6INRhXv?z^!lrT-EQ4YxFP2CcDu1n@UP=q9J@~`f{=JwOMer;60`cdbrKhq7cI= zb25H+AK)gd7vwUa8_WpyNP=Uki??4VtitQcJ4oka(Fh}D+XN!DOf`EiWJeaOUdUye zfwT(}j)~X4#q2z7_M98B$Jf&kE7sqJCIo>XtN0zAU71*k!L}$}*$6IMgJD4>Xn(&n zJy$c+R_+U9;3?cp^=f7U+__?NqRw^9Wq-PZmdk%)Sz5V^%`($0+oze0*}a-5yoslF z!T-wLf9~zWL;HlWc(oS((ZWF_t^-XdD9ZdJnoR|;7``4q&?2J+9o#HH1Y_SqWa8@#jmv}3>s}IxQCIcFPQz=9(rm$DQqh0 zd@e1cGB%Fd`J%PV)5@!}rhplg^yo9!bJScZPaO&EOEe08=^d=r#W=Y)&;P6fEE7%t zznir0rWhVzS{2X@>*RodY&(DyQW^|e(wK>tmM8mA-5;)n9V!4!%fej5{AP8&XMGwy z!>7q*?HTw>+<|I1hADgOXh?M3l|F~u92Y*x6Zm11OW>Y-Dwr*oKS7D?zQcY@XOm{hF(zO8mVCd_dH(!-l*L&s ztaBNQ*i`(Z6!#fBO5#?rgWAlj9@u{ z@46drl%6RUDH>FH8fq?{S6YI*-hY{B45q9Jo=M0Raa?>?Mp!R}IB33!27xQMkcF=d z^5uz%66Y2a8LZ@@W|5P1G0(d?H(aGqw=L=^by(OHykt4c&MS|Uebb40|y5}`|Et4PC^nWhB0Q(eYtxw zzWS{^vv>MkUdL{0jFbjruVDc0cg=7Mf%QC|&a$UFL`u?J@1_o~*O2gNqLSZ`hAFd) zrHR9a>+8afF-uQs6@Hpy{KBs!y-)I;OT4Xi@qt(vp;tBaPF%T!xFrs8yDb(#a0gSY zRnUf2HQm%ovdyBGqbA_5Orv%r*SH^vu+B{)SUElpwf)H*Plfkgp2@-y1d4Y5XLUa3)?hk zT}Es!*Ey`)^aX;e_7?N={N}~2+#gIBR-|2QaY>ZuD!j1D7P*jS!p=4Z%vi(c$?v*|?x&h+zdD`ww4=TG7Qcq1->t?l;$`zk(vC zA4=uFCcF03w1{c@R(irm&*7}%e>iraTS8_>@-6ES)q@iesP`383wuP3b0*v$OLBUY zZ}mL3!`yOEwf*uNp^PhNl?Qb*HF8j7)>fQ$lrXbzos1d-3i=PA$o?)@3+}GO##oIP zW6!XsTXas7ouzurB5l7&LIZK-(JTwFnGuTnojRF`#SiL$5ZO;;T^?d^%kc1$)F(`!{d*w(RCe_iH%0uUG%znIRq$sK>(%O%h1OT+qm!rGu=J0j>LIDQIg9&mo3 zIAHohy+#q|6a57L9$g+%#hUI{H|MGXtuQOq{E$A3dn=M)0RXm#uQx zB}~D(qwGRsFQ9?==42g5y2MFy5ncN{>8oQOW2?n>b5%uMsd>ivE;m&C(rWkq+Oi>^ zDVG7sSFuMmyyzmK(%KMdCiWK7|9kcv|?#=l^-kJ}7M7-D{trz%U`bO7VDHFu6=c-FnO&`6CColTpnnTrzQ}Lf-rAp-^ zKc_~INiHRy>cf1EK~@`0Hpm74NI(B%u?;owVi~ zMI4a?+Xlk@R?CDq7Nee*ra6DN+0&JG(Oo0nQ^h=2-+WVu((owFYJ}WNlTmdi{^d|* zb+I%qin_J4@WL1;#EBIwcS_gyLR98@aX^w}vj<`~9(4Eu>V2xM4eSk}9DGnQ3gO2( z&D!ki|7M@=Vg>qTXB5s0%`_RV$*OAq({cJ5UNyo6t33i8yu4_;L$$uX1){5&f9r|}8_1D)FZmfL@a{rp0dQ0fKb0K+Z#2Z33IzH}t+2_RfCNXvX})WO&)*=4%685<pmNRyt8d zscx~Nu+;R6I?O&Aoo!RlZCPQX9{R`B8*1xQi&hPZuOiePmAWDCRwhamPV@}a#2lVS z_+)s$WPBh6(~5qRjqADWmuDz=yt!ECPs{IHI*Q4i>kFO<0Hy8w9lR4;mv9fRe!L++ zfot0x2{4^T%HuQmB`v!%OJ+9>wWfs4K+czF-6?7mj1W>|x8KWFIt-i^s16xV>N1Y7 zX`oAvFJT02CkoWAo|T7|JI1WDfu9VSc*~ zLbEF9DUgxu_k7kGh4Rj*uG;au4?YhgxUryOzVWzKeY9$jwbIqHx-WsGueRmjYe7+; z!%;bj-hR6Ox=~qL1B=lZqfBt``BRgRM;BuTVj^7+F@Jn>Io))H z*>t$w;DT$j&Bh*lxV1q#NEi1-wc;u(O0({GyYu}pSlrv9*_)eiW3m;f279F*mZEAy zuTvEt9ibhfS73c`Q2*Wr!Z}!1KGU*U&l@)!(U#AOl8ASczGh7jU)a)+rdI@)^}YmE zWHN{YYkh_?yX=Buv15L%|Q%QI<)n&I3$0pqr;bk4LO|$iTt02pM zj9O5%YD33{fAo-3UD4M_I~>4*5WdHuCBt-!5efdrr_uJJ_Hb7^$xE znXI*caX$XN0liw_`rL&d+KR}TwPI`^X^n~M3SZP|f$3EL-K_UjT$KVM&ePy#Qjm9% zl>9|TOL+qL8|*AAaYVCdHjo0gtxdI*4JTq9=@7y>0g~xcx;x#-yfb4ORXWAoWsF}g z8vPYtVkn+>CAOZ;d+a<%O8pXk76&|-jClk5P_HM$W*VnWn;+hGD3IpJ)u-aX4AOh zXp?SM|J<^Wk*KKUS-L`t&n?ki7>oKlPO}5=a58;51}Wm|Zo1}EME*1t!=KVFvRZs; zta16vLZKQt1{1JhI)WX)H@-@5-@Gx;JiivnSZTW0@c0amYTw(!)!)5e^zhCo5DMxT zxFCUO_$?!dti8DSR_Hd*VtN?Jsi;KeCTK3@{RecqsYvV}y73y;#3bHJcZ>`UB-T7Yp40A>fz_8u4C4I6uFJ&| zfQhekiih9*KZxvqUx@PzRjp=ZuoOCB;hM7`P7drg>MB-tsb8Bn%D0{QZ6@Ja?v4MaNuDbdao6wLD8*?0cg0rs^Xvt zMrGyTk2FS7nXpX=>?|?Wzg=L-i1|Gdr#wR6BqOL!Qs<(2gQ2yOOH=}Wbo}v?u--6~ z2Zw1nZW!Ew?|NLGw^`jcZUIxbcy^Pt6GG?vTMEIiL%S9#!0aggR5e!V2^!)K}HUtk-_*+ z+B)L0rWQne{A}PrmAX7EeUYGTW~j#h%&2Bys<7lG{aH#yakF$UKud(b&o>FiJh)~d(`cIgg@rJ4 zWNt#qrUd|G6%J}h`1~mJ>V0DkrQr<8&|Av?2qh_bQ0+d+4X82JZr^(9MT=%y;b?Mn zF7nMBTc|yZh=Js}g&T8vwf8;^kb8rnUtrEuQ@Tmgy5&V9dqojBaGW_xdH-xCykuLM z7qMIT&!@28-BkmOd+J~LFFgQC`dFB|P#QQx$}V40r`npj*D1GEj>rK6ZYpUs+P>hb zYLLfS45YmlW!Z~xJ1X4(wZ1p_juk_4?Zvs8To|hAm8tQ(YJQ1x!HjxeI_Kv{v6n@x zx>~Re$1x-_+hiIMqI>FxgKMr1ISb%=s+m7B3fN zD30~gVKue$(tI7739v%F1_8Lylm??_&+M&gS?*$9D7sAvjgPv7Eo*=wVlXG+ct%Us zjK0|XYhF8%o15LaCB|NhoZdx}5MTWhy18TJ58%x)PV+ZA(=|bH*e0?T=_ANn+7kux zxN+7r?@~^TK615F&xr#0r8c!fxXMKGH!!I7Oqj9fAkKh5{N3ajUZhNt>YGFb&%-lT z=bc;~G{hu;65%ru@vI|4eh-dV(@QrSK{q1zva zdrP`O4@5)Tl656#et*6RmD;w5Yxa7@3=VZ#IDn4@N^Uyq^n==u7X-CZ+Tg^|#WjwHGiCj`&2iAGSTt?NmY^)w6h> zw<|0I`?<)dr+C~7G+q@c3iD*C14OC=r)PZpY?1AeblzvPXzNQIC}+R1H?c-sIIwn3 z4cmcw1+k312-ikW2z!3oOu*)WUJKfPbl$I~+jl5~{7qsG6__+~MDDTE|7y)&3rF-> z7<(0U`;@mpMR9RQE>&}|U3x#9lRqz=`H_v=c=W6>NAwNF?n{K!D35<@u_j%_Tlpyh z_lnuzP3|!IrXv)*Mm(zS$WmrD4`r}=;B|{x+DQBW7{05;YWJ*1wYh+mwiS@4%S)VA zj%B9}P$spi0w*PYLS7_1CcIU}sX^%Kx@ibMosI!>be!G2-?&x#`QqFAcAU2{{sj(<74nsW^cX?zvO2GvLTee1|&Y5d&c9u}@+;m9)Zk0Y><~fw$Ku~@O_B=}`A@{1EI!Npo<^Hqn2wh7BJqtr0dd>e z3SETfH{UmQJ|A-q8Oh{6;w|0rza*0t%bt3M!e-4=>>hW;H#7K>s`MWMVmf|Y+kzS4 zWQQ4qQN97!d+iO!!3s*ntb5G}F|pKe>Bm>X_Ho3?gG3bsp? z-xEe&xG;!XB@$*@V&waD+Yhd3n~Ow}QlP({e8VO5HEcw7H=O&fG9MP}FsM6bBrzzq ztsvR11c`kQ;79KyrB*C!tw*6T$jnou=zmvNMp5;s&*bxJg)!Y^WlW9n$>8VCURs%& zT>EHTTlE~=&DjHd8G^nF|b8Sw_=J$JXh>_N#` z_IALF5cB0;=V5w<_Ayj%@P$u-aLN{PZHedA;K^J1AWHS&%^$xAEFtZC{7R(fvHIKV z^fQIatfqmz^6bxNkGHf!cGuUld{{E6Edt9hC3(+>DHeasN;=Yp%R&_{f1QhyohI4k z23-hSTL8kNDkLud&)Q3M#Ta8krx}=*w*N3iq6`~ArBHQC@raVAze}NtX7fH#`zK{v zLCx1v&p{HCc8;Mb9m9`YXMK(L%8mU*O}`4|4>%byFR>MFleVUR*Zd5A)hp#dr4*$Y zA=eI<3hOH%`8$gS9F}08zNIo@h{W%i3%jqOUvy<__@bU6W6h9&^{wvD-LW5k8V=38 z7x?*ir9W109a$cOEkC(+rZjEqQN@3=Gw@#qEryIcnV%(qj`cmZvCul{&`AEMTc-pT5x5Lg&0Q zOh$Dv9Dl;tX5F$HTMR4eswqo*Af0kqfx?!s8h`LdYfsUH4yxg3yhi>*w<;AWrYN)K zo)i7Y${bfbDIHy~vBqOtt&iKSU39}XttVX{ZPi^;X@w-`<_EE0iM@sV^1aTRV>!_L z$F9bWb<8e@rq(evm64V8tUA5QMO@RM*DTsUDe>rSN78HXo7s{1e`3RT0DGElA0* zcm2_ZXY&fT2jT51Px0SQB{|ykZ5H-;?W-!?U$P|=YVt#%W)dWs=-Is~bzH)eMye*H zOW*H)(-+kyq78!;OYM)petQojWKR3_J}+_e6kWvo$|eaF!cg+?29PgflmZos$uh7E zME`P0U&tfiHv9A2m4iwLz~?ZH0fETUegzCQ`_<`^aZQw>Rl62;GpeTD#OajmhE^#T zSKu6D8yiiU5uWbKd3Ewk&1}yfd2^14lYL)UpZigK!$TtR`my)C`~?=yU~GeB-Y|P| zwfv>-%}_CG;{a*kjjkN&2ldz}#LXEd@KjQ*kJ10Cg4ebc`9P~|fvZi(6GUr9U;1?#V9a2QTaCK?DZ@XfHEy!BHEpCO9`Cp#a5}d~Rp?=@GQ zCLdMwPh_8vKE(GdGQ+VgpY@Gp2*$S|FSRJ6mYTbnQjNGIT+i@E)sjg6Piw?DerttW zBX&Z17YRkxFJoQP>Ee&}B?FZ&nlpkPljb^XICUPR>gxPOb-aQLAJ%-6PajOpQw8l4 zrOr}5QlWq95=0RoQ+0Ct8aSj2tCEFB>`S|%j&TWU0I$<#h$Ys;1|||1iE-f zS)%o6%+nA*igv|dNhVy<>25ilRB|F;ciK-j#K zeXK<2-h#fXyg-5bN|IhCw5+t)Ge(pP`2=nPlFj8}AIP4$oy zQD7jDEUfP=axqj~F#!CIQ<(!Mq9Qhzw(VhMcEFhNSr+|~A+l`xnpqj7M23Kj?~}`? z8Kur3Q70DVnW*dc)D}md`V0+r!)Hf{i+gT z2LcT-Q(m_{NL!A6sH)w$rqEdLYEQ~Oe-dO> zYw{o#=-}|6Oq<~5dQbGxq(&j%foh*4i^M)x(#3#qT)czK8}|p3#NM#_4_+Qo|N@BjR`H@@^;$HT@QlDA4+N`7%;e%W>7`@Tn) zgK=d?+)7Df&bh76*N5)M&T5HwVBcs##?=YwbuXuE$;C*1Jqx(YdjWpkw=X1E_J6#y z-+?IajPdEM>s*y^(5!7Ws|Z4{>O#-tE<>_h@CVYJLoYAiNh!D`DkcGoV`nk6#RQdw z9r8PWwD?+e%SwR+e2QxE=S(tmSVq1iAJkPB{+Z{imS-4wGIY2Zy=IfjRPZkGfiKB^ z?I3ws^m(U+hqirsV0ihNX4G#@amGXd&%wbOT4M2oz3=7K-^*yphh0#!`}m>u!9nnR zJ{M}8ski*KZYg$G^J3mPn|f6sndF7-#3h9V3%np*o8Yvnwl^cn;}?))+y$1u;?e z#F>RH@c3H-YfG;#KaOsS-vB4ZpI>xbClJWnKIZ66=Jyxw*GKmI?FOEO_=nghp26Yjv<% zW6~0Jv&Zldr_|El5uSRzB-MK+Q&w^}+VT}$;&m|SUI$8Q&H{pdEXd7k|sNJAn z`~gQJao2S+Ht@`Cy8i1W;}=QPLzzgGJL$mg{yPYKJ2Oee%%w3^K>c3KTp zJ#H1}Ry$9eV)UusI|4og$%)CSz-#t^d#EAYm~3L1I%B5t0gZ``IYdAzGCRmve2=Tg}wt@_=h8Zoe~QXMS?aoG75e~h0Ncc#r5 zudSEz{a(4@7iP_i=I$(RhB_9?HCD*=#zz=e)Uj)z!eqL(u_W!9jF@oU!lLH(2{-+s^9QT`<2@Vj zb-Y1`q_un7`N;zV`HERRyK%%c=`)12PHJP6WtdBa9~3VA$o9c+dGc(?*I^fr2dhW! zYgoEFmQ#u`}eVXomPe^GelYnknE24;~J z*|x>=Bgn0%ye-B2P)qjhaKhZDv$owj3q*vY9rdx78f1)daEZAs^(~w^|3-D-w{&L7 zY>86T9EsW3M~N>-!^>|^pWMASlq=sVbIPb-3I`{}>z^h`bM2&J8KF^T%otLKbaqsb z-ddlAj|7#URAh?YqRbW9Q8$^~OH`84B?}$o=x6X57dFgHn~9qT<}cc<;x*inU-K&- z)1*_zHf7b(1|AAbc2ri13z8W8(0JV*7T-Jjv?G)47Syd%bAXuH)v=6fbRD)8vi0RJ z_2PC9UeGOEFDOI_xBtn`n?#>i&u&ApO7(+MPuVza-oZ;s-{N<_24EaXQdF7kF)jA- zX`f8nZ{(?BEMtLW_7RA|#!C zQ6fD$z zu$>xG1-y_eq>PVo!G8v^arfH(?=cfz>+F5bh}&Nx4;=OG~EInGl;R z4g=4FV?=F*_1uf@#+j5`i@yI)CU3CF_jgw6$ZGHLxK53xMK<-)Dy=C=UzwU5o>1{d zzuH;$HA!#cv|m3-a#_?9yP&B}zY|A8)?X>)hX{jOlt?K6uVeO+bLktK1RFaf5<0@q zzGBp}85Hy2Qf=Q3YfS@O;`0Dw$z27F91l0+N{nd3KX7zv52C8?+pJ+_HRClW$}eDP z#que}L-%z$=qgO({qjXiN*lg;J68ivgtVn=lu`(9M4e3{ZRb*Foe)8JdPqqW## zBFbTibTjRQB;&Y!pWMsu;g}`Cz z?NUc?=&8akw{E{HOxUetYNW?gf4PvK%|5G{R6o*Ro^q8v)#|3{-$fWSK$okA4nMlm zP`eiVW7su)k4Wu4Zenj~>r#eYYu>8dARbM=+?MBH(P%!Yvz397?u->jaOPdYQ>`mu zpA%`~rK?Tze#^&aesm+}`nrsUEsfUMg6$UX_V`p*t^b^VdM>Q^t^al;2SKFz9&~}N zN~U)4>D6gZ__`kBmEjPpy=Ru`=)+0dlbo1{U0G0S;;b2Qn;o0ctxgW@BLIH?F=hG< z&kw&%a>X3L)&S0hxx4@w_WpsKHn=BaptWMrlnZ3&J`Y4Z^be}?*Bit%{2k$hHfvM_ z6nch&_}3TC0dC;NLEXl(HB7gitKK<^swKxGzt~HOl#8(>t%84r)b66`b$X6EJv6_h zio^k9Dv9r9c!Mk#`XrPkRG=9JT-Su`zeDMvU~?|AaBs7?Jp6_CQ|gj$V}9VZd`Rc# zOlbYXebxGv^B2sCBB8*5F-r9L$o9C&|%hpSR3f4Tg&-2zQ;JFlRAP7iGoCPdJY1RMA6C?AzZ5rtikWIg{ zcbj$bWJ(BP9jCgdi%3_}Ye_|GbT~vDIms@lODI(=6g(WH*i{*(R;sto1Ra`n4$K%3 z`5<16@$v%45(GL0tarBTd4I|${B!Qs4aN>)%{8s=vwwZU4#u(HyAW|8=wfBu@QrG# z?T4Uvb(w#)D4t>;E>3%fuvY}V3yWgei)o&v`NTE%4f16gId+J)7*O?2CD?JfA_5zus$Xw8&i=zbkqfRCwBd@!QJe$!`?@UV2t9FgqwxzJ#m7EiX1AZA;m z#hlb8K$x_I`})%_zMt^vo~8jZ0(qsqW}L7q zrN{Vl#>t;D;GgD>u7LBCix&AAePPCldSXqLSsoeb6-mf$NaF2pfdhymIdFiCZt(fy zY}Ldqq}hB&rE|*AYo?O2EK|jfkd=Zg#d#fj5U*#vsd0urx2QolYj{m9N2a1J=*twX zw?UIG50qUakSV_4_HoC(dKSRV&}QwpSw~^}wMF)MU^z{MY<^|48)qasG8n?JVtMfJ zwPEao49MJV{BcoM?@YLg(O6Q7uS!hK|G@uBRmxMU7omg|bmqu%Eb|F#$qpU}Md zC+Kc_|0O$6itCBvDSx+450!Lg;|ExAMNdWY`Xgrk2@Q9iPtTz+p?MP&|!F8p^%~7=KH*nYsh{T;!>cV%r=~ zTxK4=bJiE+$m!VKXByN-35Gd}k<=>t1~4sGwCQjfB$~gC6~KYAJegyTa$u+B!0DgT zXBii0REfonB(2K5Ucj{cK^c;np+MX^;4v;*x$=s~?{kr@LlX{wQ%KT_!WJ64#ZhsY{Zp=Yj&7(m;Syp6_ zWso*YnNxlZ+uZSpU{b}(tV$=V@|4nz!;`iZrIg1imvDvMAw%XAk~421bdPTvE#WdA zDObcPj~c19E+zON3y~&F%)5JQJ|A2s=uNT~P)XPDZ5n3#xi?iB_SO0@cYiRk&N7m% z8hTvN7xQhjYUbj1)`om)i4PkpnbkmOgzCi8KE&7F2f0M%Ly9cdl zYam8fIemTMVC^8nTLPl+_3#wp2RM6Jm<0a?(`_<@-7kP&r|^T%E{L^a(iLJ#MEDH? z?k6?6t7N};g))w8?pL3K;m`kOuMB!^$0If7<5=sX-47m_HD3POqW?1x%djE{(ax;0 z)*o>CCuo(^`8-|DZN=e_E4d=5R<#(<@_#<$AVBK`ktWP3< zc16qt`JtCL)Z4ACk>%7mTP-rY}*x}?LVXKyv) z23=3jHVOrd)!JfAb1w3SbOLiByVdO)O3!0yFBDuk{&k){zuWd3qB9H7xBWuCs$8>8 z9o!m>U1>iv$F-@@i2glSDbm*bQ`_?11IuU$-9&R8dWmPPGV`?nVgqop`+Yem29Wo%>m$X?tOMow7zVz zO3-OZu}?D}>PcH%-A4_lF0nFX@B&P%59cXod7m^v$t&?UB^I@2459kz9_&F`A4kvK{^&$4ry=`>CQB~w z^lfmVwZ(pNplSYi=`6HI)3KJp`HC@Pt*H1b_hH)-RB3iY!HjxK5b`>Jc(?<7`!B2c z<1?=?Nz1;4U)Q$Q7B^90BA+6DM)hgqSGABgYnF~ksjJc*V8<`IVH(ULtlvHtEI0a0 z(3S95*3F9dC}=IWTx2krIzq08V{(&Wnv<0WG(|rV(j~EXV}3b4+?%jLZ=Wsse*3C~ z8sH)kIaX&%;ph4XTRRiV-Fe$+-9x5UG;$kCz|j3Y$vdCsy3PMvqhErP^wdL$xd3ig z9Djoqon2b+(q&w_WBDbII%8WT5JCjJ)+4kHG+Zt5HM^P9*{NAb8 zo;qH;FI2KrSb7aVKjew|bt~6>%GI#$7A`Q{xb`+53Wu&2@9!(~AKs%e&NVh6ED79r zs>4kC&U^#ASZZ5I>((Q9^x}vq7mSyTv}ZW&Xz)4;`bVQp2OG86Wef7c57a&i+JS6eNzUqSh?1kw_C9I84-2xRuOK*opRPP}Y~wCE3_U*CD8 z#}lsnv`^_Wu5#?B+q)t;EFHIR;3YW0Rb>?@+##KDIbX5BIx2YfZD8}r@1UD1eXB%V z94?naHZ2h-hC8rCCtX*ZW(ykqEQ&k!DAa5E-idU*+^Z5l=CYsoIXPXGr^~}4R-8Q~ zjO+IaO4U$C&$^rrtvS%D`Evg%s8fh=O)wu4yE5WM`!ZO93B)1}f3_8jap| zps6lG?73~u_5+(If86fyt&+4zuHAhF5$3xfj&pRBQ*%?E^}TT46AerJ=?Cf4kg$lY z98ku7At9}?&-{Yt=F;cFt53{l!E8pT3QSaOx>^s&S$-@&yMKsuR^tA8cmsYoKLnEM zU|lVjE8$Oumq0^gJ5ZfjXQ$&JStpj!@Yy4Ht%B8#nD5TVD*blXkkO-IIdWHlF8dwd z=MCvs&j6WxghFvQ(h{)-+0dbm(}QNE#+7G;Itaco`$vJojbV4j#$)_5PxzfxzRm01 z+f6werrhSDe503-uN>C6lh5WJ^~G4S`%53biOn8dzLYc?Cn!7Vgel!*)J^+&bD*Kz zD>RAu`=Zu8JV&9vR5SAHr=NV)ApgKxx5n=&2K3G7p+4oU#|PRvkDP1#tqGP!2d8Cj z9IDu52%{bVC5QL)mwc41(~d2;Jl8av63B%cc(^q$GfjPIqz^g&-Y^;iSr29mzS7gg z#&8#&jBCM6%ND+Fx}0$9{q`oXv` zhUOUEJRpkKC(h*I&%b19pE6eR&F-7LT#Z`BH0Skc*EEc2RX%7$D&Ocfp2_J80%_Jx zYsw?owL9SCtq5;Ik>~s94-X5yZYsg^^rWETSxAcSkP5v~<8UJk2JFs;)a{{3cypgU zBA`447EvTlSITb0*h%`DHJwUodKMAndqS5}*d!?fvZqEr^seFua#K5w`AeaXd|o*$ zv+jGC9bD5Vun}RE=Y(Y}@M39So@)M|<&i<#V#b=rocJHZ&bw#cPdZL|<;&!jsH{;B zTkn>of*!98+`MgI`D1D)?8+5`7}j}1IW{}HL!pM)fR+zBoiZaZi55piDNzj*n+IjG z&N8C&4+U#9HnDsU;p^E8i|+S`dvWW#nPd5xHCTASH)C@(pPvzZI-K9UkYQ}U;WqJgInM_vJUQFaxMoumV^k_1f5U?U%*J zZEDQvRNA&f)SnwNVdus(?ym|NRSeH&r9Nz6YQ`2h^mUNSHMUZgZPw4*jC&Mh1m^k_ z(zE7-5A(x8IA|b=Wvc)M!Q;#4^#;y0k>b=T6^?~#ywlOC(<5oVo%wj;z?zlO()L~7 z?`+Q(NjU5!wq;G2y;BA5uD2PLmbDAAHzg+9i(9!ocnGVJ1>eqw1OTZ*(N0p-^zWt4 zO39KC(-MBQwZBtEYYGrisP~jru%Aj5jz@9J9;(wSciVE|oN}}R{%q*RGFJHY9{@g2 znGA|lF~+oG9}K4CqX;nrPQkp#?jqL(6$Q;fnef=pIM(&Hq24au4FPs?6#H@e#%T)X zE9OZ{A6r|*L#rucHPQ15xtC*M?z zyd(=HSB00VFPjsj>KFk9fDd>ZJTEWN-V!?rL^Sqri*6IEL5nd#?N}wamgAjt&DM@* z5e8W8Rv(qq-2Iadp{5uQ_R$lj^s%tb@L6)%0IKJ=E8r2IX#tP->1GwIrmAHsx`tGw zNLeY)WS?`NniyDN>|UaD-?$5Y&?>G8bCvK}{|n6z_vx0*Sd64~w=Fh_cLZ)0;JEca zB8yi#_mI(eb$m(qd9t%un(U+lGDhZJ=l>h|IqkoKT#hy<0Axln`Z=C=DJE1hy>+bN(IP_hvX= zdA>qtDvhsun$BsQmsFPDk@7(Z_rIJBj?a8csKMrn_`M$K7}+IjJ{lKtXx!DjRTt!| zc7v2&KVsqHUUkiu`%Z766ueLSe$BfXPAAs%THsYhc9-UhPI5GIJ`K?61PrZ-FlQp# z!F8S1`Cw~}^)=iVE)gA|i2N!1e@@byf6#5f)ok`3NA!(nq+f?S7@vdt<(mR{^(sPe z>W?HWBj?|(O))giw>J>6)eNY%^;QC8t}wi6ZFW+=2Z(=Z`t)Tme7hT@-CG~tCY0xYeKR?0pE!LJ!1>57*Xy*rJ@r-XQ>5Q||{`7u)&XaqadPkPEeKu=TRb=;W zlJ-X<$jp|XDDf6pvD7;Eb$Ll-jrL)Ea8aS)tLAVi^D!Ovs%dr9*9z(ps&0M3PA+G< zwDvA`iNz@g%Yu%*xD7k6{E4bd z3n?X0%jDUp&w-Uwd}A@hH0Pf%q0E^#E!6{=O3moAg}~FBzL#pf?7bCApL(H1n%)*4 zp}upDkAV}AWG{o`yzj5?xLE$jO&99*clV%|db)M{Rk&@@L@r3s8#0TJEQ-_6nyl%z zF-m6n+L5Zy4jnL>FP9xL{Fc?}0ucjAxNG1w2UDz|-CD#Sx3#DEF`eIag#5b9 zJLPgzBXK*M4?Ompax#A43bviQD~JEWGCnh8pGv@_rRgV|TD=X7kr~{jrV0P=p$_|M zz#P0JNC`!MLPcoQVrz%BjZMGYVVpT~fl+Om;*jyM@8y_mLW~(;E=&MIeTI#UXFqiBlZ7H;Vcd_!3bIY1$ zkUXGeJGdz^numWQj&MxjPO+$7UFChsA~(i8#v`v{35wnt(~Iq?v!=pR=Oe^hvkmn>ORXoIEBsY~0GoTo3=@cSR? z^IV97JvKA$qdL2wyN}u2n=a8!R4lkgkvI48?EqE2bzt(Jk(%JV@nzJ&Gi-aY5mvpUhB$giP0Y7% zZfss%vIyp=zG4~#`s+F-LRj0>N(5Qz?zyzHxN&$3HDBK-OVUbN5~NTy7@Lk^+B`B| z&JWwH26hcDM9*nm>8y>mICtEg7;#*Loty^gd5gNBMczHR%A1p)|7Suur4o4~7m3&^ z3oDv@bCJQ}D?J1J#4=hdXdn&cWl(cfFzOc@*Y4_2Gl4UeLsn1xt#H4*7y)~8&?^=F z#?m1f?8K622?a#$jFJK?;U8%x%A~?-SJ&WkH|x}J!bp)^;DYY+{~&l5H_UXRK*s+-J4$tX-o(9Ru4dev`7l3br~9b;U-uxeyl|wqWGR+= z;1~V-6xSZ)6qB2&n!bT+#yeng2N%W+bBWz%!mCnU*+p~k1eQ(2e2|Li3#%ts{uHtA zT9T#$V{4%<%s;L>=%Y!s?i5&{LtL86R+}&c8V}7@elytMi^NL_!~u0iH1H295SZ=e zq1~bERcNVjfY$4C>K%FTsU8kte93kX^Q^bz_1YZBQ=^wmq5=K1f+53PMK4u*Ab;Kd zn`ppx9nCw6{nJRh*ffLqm!^u5)=OIX;t=Jq$RDQYGopkGpIJs^g5Mqz0k}i62oj&a zcct>46|Rk3ZG$6++YSmq>~qSHYA`|fE-9!+`qEQSU`Eii zX<^*s+C>9qvi(q|nc!G z?B>z#Tc)jaj7%SHe@A!4Ha*o7zK|+@`;W5!AO9bL|N991A)J0`W~7)VjaSAl*`k&r zL49GT8kY5gflE20!uMSUaUO~iuW;y}<@=I+1`iTqO^BaWB3L` zHush;AL3;1`{46&B`#)1qX<#~YWgB7eyQYMBbKDHTV?4zl_2_3dU4U1f7B7W*f;dY zhw;=hEc7H3*w$l=5n=1tRY!>*<(6Uj$N(9Rzm^_p|J2|@tl}pYTABdI9yiID9MKpI zH`PTb27~e|W+3EH<`i5{K94?NTbexUQX!~n2(FD{VQ`aLCLNhUX6HC2Q0_2smD^^k z`&U}vNdGgWo+?uz^+?ID)#4Pf=%AhIM(HFw`|2D}kmv;3+sx~7`D?#p&2uzs{TzXT z!#9HScZ58qrd?<|;JBQ_dtm9W z+7Yf}WQgiUoLVxKtJOXuOmK*Qk4f8Kd*AjSJ2&&d6m`;Fg|5{P9|2>pBiS~?5ED<` zIP&?Q**x&wMCS=?voDDrEHP-S2ZhujWD?<$i}8B1qD(lNk;T(xzylkgie06`X4lY+ zhILjym*lD4AZC%zuSEHnIaP)zOcgH{Ppf-Jm5K7o@l+^l)*EH*27y_*#O3$+21(|j zYE^foGW2`pNVK4;T~GY^bNLH>RPguW@0i0C2}+n%KTbvCS>a!J?xTgo_2T8L4HEIn z3P^5Dr*d+4yR`PqdPwW{Cx?@L`=Z%F%YxGLf}~BlhD8rN?5N&d07uahCEK_H5?A?! zLMs~C=^EN2s**CzX_d-X*>D8(%Uv@QgE=8mG|)79D#8%o!kSrgFx5%X#PTSWj4s-A zni$y`av05G1T8a=&Ms#4V-;CN3oniCjZ3EtQoA{FPxCXTXQ}1=+wq=X9q2@yP=Fy1 zm=t{_uE*pjkn=^|(R7GZ>8d2?N)*ZYevw~a0xLR{%j%m=t4a;qB|j`0`UD8|9bq$8 zy8I+r6r-(qZ73guXqnfq(LdUeK`ZT1OH zV9UbA=TsM^n#RCiQ1N@=o2Osrsa1*$lamOv8c_T!A-VFUUGP>*=F2$?rfaY>|1pTL zXZCQLrMpab6>Y}V1iC&F^0q!vzB>Jszb*!^`B1YdPh7Hx z?AmrST`gm;WmO`f&on+R?DJ?!%!Pq;%0Tr9@jkY3JS^qQ8oDxZgSI5&UtYNg@Cx1WTK$M+A%Xf0@Vs1F zT_0trAXIzykIPk3_X{avl8sVF#WeMB!{0BzKJyLfNkLu_6z)gcn#?%~yIsh%zvO2G z<^B(Q@A(gB7qt!Niiqe*^d8ZBZ-YUiM33Hy-ibC0Gh8Bw9=)6Bz4sm@I-`poozcxG zgW+}E&-4BX_m}tgo}c$#>-_C~oO|ta@3oGjuEPCYL{;QB(=Kpd68-j|rBEif5&`KR z!Tje1(~-OmmyhYA@yxqfwsbpcCHB2$Bg-kgstH+B^M%a!9{o4Kn8{B zYC~31?Ok9w@CTs>@3aEJ+5%nchkZpzk$#TpyLK97&v(I>LkZ_|bW6K4fMaeKCm|!r zrPy=#{f2^DQ?WQpmTl21;zUP*el;Ahf2qsQ_fc9uTrYRu01J?~4O`q$B}&F=~}?$3<9{dW77nP!GEG#i|F@x1#ho*xu8^2&!8jDwYe z3RpgG|D$%S?BRTS;$^e)j$y)ojlI`0 z(@HRz89`2Sly+?VX8C2M#Fu0g>&3eGtKp;kt39{<*=IUA8yE%c5p&(g<7y&H`BChn z+1FXhtDRX}O4f5<4~rLv4304Js*$ftY_+hkcbAe&ih#%Ju5x{(_cIhUo7t z9Fz%MFtJ**1b_PlzTmgAZC-au%h1rR0I{^WMkDsQe_gj_Zm>@3D;mkc*FHUKW|sNb zm2C=XGP0l&d$CpH@}0JFPj%IStWiQh=+Nko4<)V{s$MjpO}V?lXWAT**>fy3C)fPO zVM(}8Uws8f%9zi0gly@YV`TJRu3wsM2wY=kV9}61`lh^&k)-eo2Zzo#Jq!nzu(coK zN|AKw*NgG281I=x)U`BDI85LtaROZH3FaFKz9ehgIl$(Ny(&MG+1PhMMuVkLQj?mY_8xs%psAlvcdOuVcp)2z}SvH)dz(O zi1~O())CKH^u4KdpibnsZ}+1d3^68Vt8Aa|UQ~9+>y}CEZaze9fw8wC%!V@UeP-I} zaj8$S7nOb`?ct!<;39+0L&cxXwUs4ycPPswlsUW}-2`zI3QW8`-O$!}T`Z*YqvdMI zNKKXCX4;KbfWD_t?-Sclq-$ZK4`A)*9c$!C-rSn7S()YG!^%1zS~luu^As8{(&%7O zQeP~tAWPo!_R*jtU5gDe&-(Ac?T_*sp$Kyoj@CfYxWC$-!Q{awg`56AOpg&+ZwGLF zez`KaGkjTLIm+i9Vhrv;oYvWvzBlr5gl%Sm)Sh=`MmiMTt|)Eg-M8QquTc6EbKpQU03FTNN^9PX z>dbnHWdpw2BEh9z*)Ix)0rJv{oZGF*es0;;NeV}RfV6!4{fF|7#`3_dtx7tdAL_0B zF0nZQJi6Vw;a_;0QdE+dDj>MGdOG-8(!{^h3{YdKU@u_h>) zcF!za{yQ1v{L!quUwyP*QKa${n@NQ6w@eyZRXbkdp1$84@oDTd4B=ueDdRy(81igw zrsM;jxY=*a+WvM6Gwi-gwDol(p~&X7i<@{Uj`c+Gygx}uNkZ5mRV_`r0v`2Z`nE`r?_;T_Re}cAC}MO zc>Oyj6y7V4$X>3g2l6fD;|{Hjo-n`pA$30$da2!5VS7wfLKs4*L}XQ1FsBQrbEWvm zY#2pIE)Z1`l_X8Er9~g8zQG7~x+bq?`^!t``2n)eQPpVhP?b?N#E}`ROcvM*P-x#Ih%K1Me!+v7Pr+vxnn$iUU#mklEW7=9$}uq<+=b zTbl@xAxZ|ccTvBHXoM>`gVH@|fJI_fP-1%D2=HDwb=>>DP8+S-d4ll{v#@3N8cA~9 znhfJt5qJLpt^ZUj#YTPFBE_2LymM0Xgtq4Ds-tr?W&FB6hg#p*ignvnLSL>;cDF}H zoj$u8`B)MjSKw72Xs)@tv*JrwY9ulMKkq!3N*)s0holG_2quy)yUO%k4NrUCe)9&Z zQvVNH{tbHJuER2|s?%3mXXz~k59kvO8frNN{A=Qg_ZE$Uv{roV8xJ$`PSO8*ToK#$ z@fFX_;Acng1#e!x@xQag2`!&%EGZp!UVWvbTi32bwGH+&wUA5)x~F#GsOYr!ySMtIPUL+U2cV=c;l3(J9s zP_}lH^2z|KHLb)L5$njs#DYs#agwJ%=jiZiZdjFmdTXZr+@~n7Id2t_RflwT1vtXq z_gYo%N9uDlM--9FUunUDq%S(gSJ8-$wwb+Rde-!M$a9mG(4Mg&M@W|;z;xU=xkB|O z*k>%EIYTfmbwk#YE^7|iwm~l=gsPI7WX$x^;3wR8~Q z1$b+_bv3jC$eiuIO>;o?KL9+A&)cl7Y*ovu`;V z6a%PyF2d;Ma`Gwhm@_9>oI@{pi{9uvk#iTvW{fkAd@fBEE{R4L*7VxV)~@eepws{l zp`t{)vEKp%t&Bn)_%QgiywNH}$JVM#v037}%qKoEMl+@|Alg#eY0^%&;ys&(!`R=2 z^hNl9%2-0B>f-PzG@xw!2v79lWYLLEj>Kjt;nrScY$Vs3ARl4>xgs8)ZsS%$g0(O! zOrIFc^ggW5Ed9%m1!K~+uAl?AF0 zo2FV@toS`@PDnA+QxbTIV>}|jcZfBgQlxl8f|C|0=7EFd0Fq2m)@KJq|RAfGHyJy$a06jAq z#>Y~yDl+L*#!91?F?`_w9h&oyTA+k=-OoHN-Z?V_Pz0v?8_U^!t{{0(CVYnFfu@#; zxc(Zuu)eKinmVzfJm!pBdLE^h)^1%8t;G@(g%>Cek?W6Vjg z_d1SW{BRvN8F?>&EB#C79Ae!1KVS{Sg(1DsPwSbM6U@N|an;A#7RBLFri+e1-#xPY zF7GYg{l;&Wm@z)_1w+>DjkFA}(aXs@md>9tLl*8bTSF`s{uXq6fV}f%&zZ%JT?hKg z8DjA!IEMS=DLKtA7NP4jicC%=h(>r4{zbjyU16Udg{X_C_I)WK_5Q)(r8ZJ4714cp zyg5&6d9>t(I#A+Kd+QiDL`c|}wIGr=M;uBPLJk#fb_ zonfrQo7THi81Ts}L-VZ&L?r*6VnLyOv<}~Nu6kh)nT@UvxJ!QK%g}evr?F<&(|28| z;>4yTmXq{Jx~xled#Q&E(knrI%|~uPr5bqxb&xwoC!#fH{)a2HjZ4P{Dm+2AOZS~Q zspl@zD@{ydFwpja0*Ny8SLLgdeLEoHJajKk$}*dbtGv58(Rht%^~c$Ae26NOSd8(# zPH7CBl7cR!=hh1AIoo!ppYCc}k9w$}v|g-AwHj1gU(V*7uTYnmT>qgCWAcE#oHJp{ zfR~&Pz7xxIWZSi7U0d~FJt};yq_^$!7fkStG{vA#$%> z2S!3DegimgNY01!^UwBjvnlT%=#4ZSr|G#g9bF`0?pM6&69#-NPta!XvfMGR(m}L* zwa38DY`wG@h-q)-E&(^szz;;AX-@p!@gig4xdVrs55nefR>_WOgd1)JWYk3cZ7F%G ztX%i&!MNra3^@w&0pLemE;+}4Zx_t$tWIq0_2MuaKA%6;#Xt-JQ}_R8*Y>0%QkMpH z*@E8L9bbX!^TB`RUIG*c0BtkT8P+mU{MpzRN-ydw-6+yx@^R+OhfEhdKg6s|PvXvB zR8i2jR`y;Lw&|%a+XkhYreZo{fBdR06kb)as0s@IJ&%uPv$#Z? z#lIlXcE|Too$WMg1~GPEvzBxleo98>o|~zRh`|gWEmIrruTReg(Dq%gVoH~RUwG$y z;1YyaW=CO5M>CQ;E#;!YOO;1%s-?G|`i^XnW#$RS4Z=2@#lA`%6rv0?2*vWDHiOBo z{Gl?{X()0ew(!%{#Quk2IR;w6F#e4huM_`^#hCG(X+9pcksB6|;>?yk?c4iJ4atq> zTG>e4p_%A{QxT9*f?JMqNn$B9X&y#(D-~z*|`G zF?-faHd_}^CD9c}~c+A1~la3*ZDBJ8X$ zGj7q4NwgX^LyloS8B+DtRD#L!u@qW4@{}5Km8n)fAVB+z9>0vUGEO`jM|gv!Dp8u! z{m$S4m_stR7g7rZM7qq&y!sC0(OAr~0ghDa(+QU75_Y~em-b595_2ClouAH-I{EAUZe>>%{}vQbD!BZswdKns zORB$VjHL99=^UuQN5dXoD}f#_^7YpT9SQ6QAL=DXr(c#@6lN9=50xzFsa?qMR0D?u zBxXJ@M0x}@wfqFi@qX#FMco;#>M&dA6|=1q69&l~(C%F7S2_0Egff3d;pTX8=U=5; z0HPYzz{vhls3SSNBAMV^ZRnS=YKmmlKr{l;2+$6|zEO z_HgDF0|oN#WoQu{?;0Zo{Y%(E@v8lwLhJ=oH&|f2kwbEPr)eLe^cXUK;9A>0-dm_9 zj_KC?iD7C~HschIvs_fBbPeHbw38h7me?43o_yThG3kzanom!klZqq8(Y`@oIX{GP z|5RTf@y$G-edwo%y~B4iWaTE-tB;+!1b10kcq-bwkP}JDQ`f^z zu5N!Dxc}@l^s2G!J1R0ltj1D$!`)NyMuS%e!X&m)d(PG`WgPoKP?I;vO868UJNbJ1 z@XOQA7zVPp=wC!{XW#mVi8sF~G}MTl)B#^4K7u#K3y*BHYCqb; z0PH4XnJ(vSY1Mt&Gx-N^MBbW zE?y(Z=hH2U8wlC>HCRd8sd{c?AVXJFWo;UW`o9zUuLZo_{55X(zg7u$zUb)eEW&)? zQsKR%zb%!$Xy^eVQ6+HH+wfr4vtd@mmhh1S@VwE%NvR|44;f!0*!)V|2dr2-LlltM zh0$Hq4etc%+rZz?I(PEE@9{1z&sQzr68a8|>E?JKpW%%XwDy`$4$j>BI9qwIEQ$5b zU)LR>Pjb4WZbt2lZC_298{JbqG_hmuMZy*4;mzSgiQL$!C4HanVl0OaP+z?9FhU&^ zNeLD#XAaS--3H#{SL|~SNZT)onK~QH>qw`tg#V*$v1JjzJyXM}+DJw$kLTXwzYAM| zDK!=pC{Q-PcA0H~hFSV@Q!p`jJ%w{XAGW?q*ol$}ytZ;Vq@kcJOGpnh5@p{kH=zm+}ejfWK9_qMohTY@Mtoq*s& z_COT~iJgdIGd_~eNlWM(z=9f3UqwPOZaK!9Es6vv{}s3RP?xn#0o^bhuW29|0tuv) z5-FR-Wz_%HO03@*_rOxW*%CX|LvqcgJI7fUO{-$9DIgToM~`4m0W)t(W3dN(Y*USt zGPaGh!84Zo*`Hvp2WsIa zD>z_^aOsIr{sNkeQHI$N{8SD5s_C^XFE=y2*`=Hfso)B?0dZLujb@fC_~G?uks_1k z4~^f^X$XY>IhhLy0pZhn!VERlKsap)Wzui3GJ6_+{cBXix+f4eu}Gk!a;fyRs_2)* z7x%=Z=66nW&`sef@gzbr?rHj8A9Mh!i-B^zQJSV?Ip`DpyskILAUFY~9-1_N}c4jB1 z5-2g8v3#&%9K8?r$cCV{+boL~Sdj?Eiga{iD4An?*w+whfFzZ7irPqP4%Ae2^4-$U zZJl8z8$N|o>q`BMlXQ8k$N2uUJ8b6hWR5iJ)*X1!UpTt4*E#w7-YTkc_1kByOJd8L zs%W#ra+*sf5yjv)nt9G2kwyl7#DH4eqgf zBfh5@cSw-sP!uT*QTHFNguKS<9fGb_aYCsk6QJRpeYziT?T- z46fPhIZ$+JDRgt)gi4gCDrzF-;WF`sK6HFOIPa+Xg4&{CJIGh-)WoE?lTA_~^PjrY zSh}9jjL>nXEGKzj$j4QC%89vG{el1wcW+1(qf0_WgtOE8?z|si{4D+k2c+RcTNOUu z#7V9{zird_EaTOrnx0TvII>yOyhQ&YL+FH>*aW)&=>TQM;V+7=9Ugq<0(bgD5$qgF zt{@6%Tw5~6?w`dtA1SW_!El|}4+FN>S+{$;89f8zWSSwqJ-~k~XyoYJ8Z=ZkpKWJs z>K~NQJJc*^PF&Vxy;o$YUK5E*NnRqR5oH|xozzzoceK}c&^D_6yqvXFfHXdzCfpL$I*FA%4pV&)Pj4}^ zqfGVOn)ZZ)Tt+y~(KU=NU;baa*Xfu~T$DEi>E#1*xU|(8d6>uIpG=E~^2EKG206N% zMh#=N?_zZGCIcv08k|oS0nx;(=mnQ{-W8+yo!`G@Rd;|4AYf*BObyZ6B%m+)*8kw^ z`!qE=PNbJo@)&>>Jk+aHNdt$t1j9x4rgcmHBGA`$Y)<@qIg(DpeeyK_?KlHtJ&G|5 zTZ{LQsEZcb?&r35O}53YT6&zV$T`2|({ZvL?GhnJJ9xpk%xJgK;C{JprJ#Gt>8H!8 z=ybw0{a)~y&JB4$U&V3E;p~dn0wF92mP+ct6*3lZq~2i>Jo-}9;)vS1Q$0uQEP2Cr zb@|@O0uw)+)4g&-Jf9zC%Z)VIV0Lv^#f$~So^CMuqf5Xx7q686=nM(LcwIW1&L?R= zd0u>9b~aqtq*H9dyk7eBmdSS7NSHFuoK+f@Nv$dsZY<4|M7Y|ocniTU$JPuGO~%!t z!~vWM@04CzBVMqYun1^%O+DqRq=Q61Hpz~}|DrP~K$eC$cUM!sZ;i2 zcsFzB?y$1CB~BNf%as;X7@l8XbI4Q9tOvW3E;_&z_oO-G3GruY$>8kcc zqgXZy=wXkj7sGjdfTY-wjfXEZ6iG67FpI`M2#(t&!GD)Y?#LEj8mpH!D#TnY7)e8@ z{A-m?t&d1f*32$s5KA4z8JF**MKBL#IDl>nGxq{7lI$H2al;Z}HP^7OoHR{l0)8v| zVMRZA_H`ak#`T_mVg!6oz~lohR68?1eNzhurqkDie2=Z#+_dY^`N_B7z8r>+Nk%0Z z;75dC_w_{Q#>HCwru@2=EC8?_2pJM-@knoCV2@EMDs<%))0U zL#j5o3X>~C(!vu)JSzhEcklYgg3G%R(v1s$@Eek&;~Mp5bYn!!x%QLus?-d}NhH>Q zX-3TCi&O~@zZh|LP{ed@S0k7l1>^ZFR_@2T#fo?0VE$PbTOR>oW4nEUp=9 zQIS8qVO%mVi{+T=ZF84uF{bg*P5+`}vZ?OB2?75uju%nn=&;Zn4_0=JJMSsZ=#v6u#Lps$AByeaOjR$F8C|BqAxgrukKNmM(-tE~2xtRb6zlU$+u;>%~5WRcB@(+?E{D{b*>$OB+d-X`5t5@A(Y0SwuL^jN)usW1#k^H8z zcgfee{Or7&w1Fc;ha^;LwzdkvuMS&v(^$KtzIVO6c79 zf^Nfs^YyLt+rmTPv0w0ScPa%n0>57P5zk;J^oX6-693MJr!I!Q>XZ;Xo>a3iBRBhK z$1ps>zF!LzEw!*Ja5NF*`{wl4cj(JAHluH`J_Gij8d&bQ?erZ5?-<0LqOggAawTU) zZc9y9J&dEbv&GMdtkEWJ*VLvPYqdrLNdR56bW_NJa!-S$iTWx+?~aAS8D{%3v}jt=ZdROH@L%eo~|j4V8cTx zcep?IPB~Wf4Q=>G9_71jNFcFJN!kH4HvKX^s!=FS*SB$S@bh&LAr$n*wMgji>O>to z!t>eEi)P04{TC@++MeNffPu*-3C|?L@T7L8QuBJ~B|lF+^XxeskmB+!!!FK#GB|Qb znhMQCj>C|6KGRgaD8t#VaBn{q#3)`cP5KTINMAI<7g2I9aS6>ixLS zak*$kK;993lSqhHotR0OWs_bdPfSQ3?r}0D9&_%|VL55r2jM4}D3gMPORL{7&pDPT z{9EFecV7i<7`W@wUV_e@PE<%Rn1G;9OqYw{a4ymI)Fs^FPLZvjIy-VJHkMwEx7>d; z1nVQ)x+$7Ip4&Ys(1`Ld2@*Smgv@ULJiwFguuvt^PZfKLspGx2y(Hc1SN^j-J2s(#0$sCt|F<*zSK|C(gY~YZnqbd zLsL6JHotZf7Qh?Egj?xwTr3cdv~e3zI?(Fdj(@HQET!co%}1H|Adxwj^eboroB_^Z zF21;K4fUmbg}};}oGU+6Be@Hfq&PSRh$Na@ZtR@tB?f@0_@06sGuYWHyyG$h${#e8 z<$YymH_E#~-aGjgY5?RuSUDDI8c%I8(9xY8Z|INg+4UPa2~>z{KdV%EJ0M_<0D?X_ zC2~M1t-a*k54axFP6Y3_nEUxIwZu$QHjR8vU-^1}ze1O1QMgI#KJV6R3l+vwS{b2c5<}LY6>9SQYZd+r$bVS; z%OEFx2i?Sab4PWeDUOTH-bDc zGT8Cf#FfhPww&$>R>)Zi`@kvLJva@kM^&CY;%1vXe92ohj{rs4|DS{ZmFJ%OeER86 zKKbE?VJ$Mp?GO3+W+u#(zcT^yilL&6D&&GYjqI`QfxP`exa09IbglXuy{|r2E2}H@ zKhtb$$$f?7tWVij{7&JoPwIf>cw|CHL%k(gSQOq`lvu8LWjG=LsN0v)c*+pVozFC= z&&IW$jECnPI|Fiyr6Y7&dH=}5NaAAP#4Ypj;spE|^~~nbzQ9a^b_(i!6Jo=|=aWw` z*-$0Gvi#0pXPix7jPYJOhOPV=%fe6Sab8ydjQ9IJipXP{+i@Y|QSv}`&jG}Sl9jyf zku{^C>v^+fEdryz`D{B$;W{Du+%-~qt0i&=FgM2pV$sVDDEso%^?SB8SxLRN6hq}` zQFB-gxqVi`5do*RkDe#pS`w*Q$Z)b~({p2(ARbDz-)NAxFWOO*B9_SvoHJbNTzPTC zu}!){y@-SDPazQ(yYj7ny~ zpxPi$%-6%eHfwAs)D~jRV@bDQlq+s6HC=$D&T;%G1ko~1Dm48t&dGYpai5TtTN2?G^L9yIbZ}13ijf9Z6O9u`9vVSw0NJ<5`m^MIEnC zQ(dL3ki*?-n)Iec<&Yhcvao8f7EoqXm^I)OG0GMeU|X2K1`lE*T#AwsPK(eNac~Hs zLpF-a>#jukW@lH|J*Sg8FOks-zOnPwE2vFh?3@4<#5e&YJM%yZY?YF$rn5^Bb~A%g z8)Xhv0nVhXtLWTrVi|=Jpwm-+qMr!;Gc4pE6J=r4jo;sVfQroFH(ii)r zNLVx#@PorhU`Ko9!Tm745>EI(fy z6f5+y+AFwYAnc70K~! zoU|05^sI&^J+N?U-~-g{XWO(or2GS8o&HyrhyUN`|K~>lLVcbu0kt?@i{eWi<%42L zrmrTRN~&`9*&i+{+Y)ppjw`knm?u);pKPOpD7oMC9cUF@K=RP8r-pau8Uy@RKY*x#i<*wwcsdBYV3ek^=CWfYK^wd$kG%83kJG{I2NPIE7-I{vy0jBa%L)lyK%NZ;*4sY7B7DeNLB|M%pt^M?)YrZiZf%e`tvh6 z-YYV9rg?#C4sJx0us98a zC~0xGw|%=&Ly!@_w(NA5Q2#gi76Wr#XNGr8S2m)#@you=;c@Pi?8;kCFlQYKux$OAXap3fz!C;xCjieKpaO46PWa`z^p-r}OzC0zdJsr+H02|~rg$STcw7V$n zb`qKAx7i(?kMiNqJ-=0XweD?Br=uPCI=h9Vc7{KSiJGNNDYo?=qnDlWv$qO%^eSHW z7a|L^Fwa}XN@=zIVEU8zN((#6o!k;J7hXebA0+fzwc947X4}hE=hl5HD&Fl%xN-KrV3$4; zGmWkb>;%GhjqSn5(~6a?a0A3q*3!Yb!wj1 zK{N^dO%p)Py$%bdz+r)!`ULtL8y(z)9OS~!OIb;#KCXt}!vj4t z5s_3{BDyFJ==ei!vcJsXL}|nQr_b~#*iB7cbQ;V#mu#%AqchT^wdkt`lQ~6TvcJrj zW?-2Y8|)-ILG4ZChcFIaL0wfV$){50FRf6<*pATY&cGeJR?+8Qc$}+al6gppUNgiSOZWnMLgNL$W2*?ZEj6MhPzV{sG=0ypd z{lYo7od<6IEG&MP$ZwojC2Yu-It^36eu$_&ta$J*klp0Ibn2*F{Q)7`NjHGH4fz(D zdLC6HUhM{nX%kIx0P_N^#BXMjI^i7-9O+9RD&ig-=d1+Y4vsgP>as83F^NVW5cGSS zBPB}p=E(*76NEV;Tz9r0s}-{E1;S^9Nw5)4`BP*8dfwwzfH0>BJ1KMb0%#=RbVrdT zGhU)8;6C_{43>Q}(mp zu#8)(Z{%5~W3cuHwZv_Kpj+2cHz`>OTmF0TTUMiQr7u5xep(hC4E2U+S9+`z%WO~I zBi!uvH`@hh?2hvRh9t7j*VZ#{d)ofcn;oA}2@B2L>KE=$8-qUIX^3SqKNjYZ#8q zpgxWa+8UTl0o`(a&~0w@YngyG!79h047I>1C^Nw(UEan$eypCEi^RPuhjJ$)1L%X0 zMp;wOs?vI38_50o@VE2%UJ?s|dp<8fgaPAzy{xgvWy{fh30|J8mq6J;v@<~N#5UXtX|pQC zMczQn0?aw)cefga>RZRf7^?W$G%dc9h2=<^t5H1x2Im^!b1yfsUd?;8(YUL=qu7`5 z0KIPG{d-hFuL2fYDfwh2t%%>;P#@f!`k;7hIbxCedBl3aIV7MdR7{g&`Q_;`Iy``5 zWLYVUxY^b9?IPUlpMLI+Hu)47?`rAS+WAqXA+PdwI_LLa9FT|V9vby)rDwUyosH`z zl!v+-Z$dUR?AD^TH(Zs|bo(e7&em)QgfzXBfSZyx*N6FrGz)wM+GD7a4PlykbFXoP z+ESUar)5&=t2vqrboh~8g?@VetK}LFZpZA@l+Sx+8JdZ?;U2YLyw!fD$3+7rgUl+N zNc;qk z6?gh*ZOtfs;pfag@+4d=(T;GEZ8b}KlP#%Ilu*;ylq>7cC{TRHEOkNxzE()1Ykv}_ z$ucTUyms<|@|zbg5|#Cn#I$1K=_jTu<;zRmm_vwZn$&ePcH$hBgpg$FIjVB38cm=; z2+bKjD2i)FgMaRV%g5;$+-Je?VkMh!y17$Rpke4< zx&0STe0HjA9MiSC;#(AMXc4eE)+vD7MdcmwYkU8+y&P~lmU$?^N@7(Obia?-RR#MW+TybaW1=DrS4VvHMiSXw8(39f~qr$ps z0(yl&-JGj4%Zp8T!9x75`UWjuma{VbG-BC&)8y(?y>Q5PH$5w>MuU3}B@Ygzh34s4 z#yKdMTRR9+PAk2BUk*N~HT(|k42(5%-Tr|2?bq1LED;_<_N|@`{_?;a!%R`G3sUGk ziI4MAHZn@kHOMT$%mbN?x|42-LCf*>KhqZ)*DXv9UjI2;DmWh8L=?|P>(s>>cr>WWk{7FK^1$ zrkP^P)o%ZVj0mVo5AG!E9^)A_=1M;0+Z-HYIxv(8s~%Lk>aAL{3|2b2t(N#3&W_hG zIZhLM;A{BT!~Ih@!v_DZpN;V)`xPk~ww_LNkK2`Tv``*f?0~PEO3Ua9Il#)k!g~r8 zAB){k2%o_T-+9rI#q(%r0 zRBVXpcqA;2*pn}tBz?v{Y7JoJ^_z{6L%A{LHBw0;!Ehk|Ji0dH(|f{5WP;O@NW{gz zQdfw90qV#N6)EJI+;Jhv+YskFdi1l14;T}K4}9ZT0L&@~0yn^a@Wx9SOVWSem57qB z&XXhx+BOHA^vA0Uwr+QuE|onBRsj>5rM zP1*J#b8qhRwk_oMN2hFMwV=TokF>dXVL%@5&;WohcJn|P|7RSFIN|Bmpe(Yj4JM?Y zX5Cx~13U5)egM!ST4W3sf6WZp>EJ2!E1&czib@>7LkYbv4nP=C!jA=7b3cUtN|2Uh$hK{Uv+mS-Sk)p2w0O z3uQiDd{4<{K+=&)8(=>E%CEpQ=wzC<>2UoS_zp8=ZVQNChpp4m#fC5vm_NmS`tRCc zvC%Rk{A6RA`}jM~-{x~8a^FEOqb~fe%Ny(=2uD)fwPr2Y#b}vq>B|l~?>75cqxH9o zA5uyV+o$?3XA4v%T`JfPcB!I%{a9y`ro?o_ioK8P%X;t6tz0#z#adOs8K{fnoSJ0J zP5AL%NagMd#k zU&W1brB|FZ9jyBNRlj1pL^u#?y=$N1{ifIx`H!IOyY8QJJ~jy&A5zd6oLwy^)(Ib^ zxv_3`Ml*@URaj#vwJ>S!snpvSm*j{D{ON;liVQWX;6u>IkS9;xK(qFDi-)kB za|AKEtY+1P$Rx%;I!~dz<3D+MkMooNx}x=i`93g~K2l5?p%2ya+t2r&xaJ+u=F28j z&E&$Ztej6;0vEH8XF=L@r7}*{JI8!__0-9ENFz_%@`cUbt*qxEIZ@|(oeoBTwz_dR zO;(|d*v*11%O9!HgQH~aj6YaBV|PWC+L5bYUGPrL#O8fBd5omTq8lX2iaiE-L~d%w z9)D1ejhjQyx54kVp4*H=?$6>l??M6}S=QwKI|zDs6J)QB ziMeaxq1!_P0E|*%R@+ghf}HW%Aj6n9UF%FsZI>-c=}p(^0!fs;;Xp z-Jm?A<&>c<`RoPFV{{7-ZP*+?XIu75C>+ETh|<_b3661uQ{CHQBZ_SA69mnjGl_s7 zEY`$`V12~X(Gi}Hsqnsvt{2w$`NuZVo=Y_ktbs44H&_A-#TX>Sk{-b1G+ro|0R9cZlVgRt?Cw?9*l zn~n|;CE1_7t`j=E+@{p1t(L;NuWiT|)$V_Paaz54zcshAd=Q+I%2wL9#4r>D_^M!e z&*^^0((a#t`6e-O_V%lo!*}h1{fxn%us}R7moY=TTu(ILBLV8EbsgqOXVyT4EMMA= z2N9}VwXJWw(@!UO+21S;dfQU_$v2qEN90@Q_bKM{Lykk3ETQn^Y|OJ>oSTeG`~)a< zPu*PT;MBwY`{ij^CH)>Of4L~g&Bhsi_up)Ip>lDaqz+xAX0&t8%p6Igj;r0f@67Uc z^*juSoWr}=C&+*FPnUpVTcgj*OULB#tfiZ}9&#%xbp3x&gO?*>p^*{;MiGB?7L(s*Wnmr7;RnaciHS9cD*-LNfiWgrOpd?x-fca zIGjmsz^!D9&L1Ub>qjO5&KZ=}wxHpu3gu*ZW zo4da!JyJn`vEqEu{LB)_5GT8jQgwu&XQj8o3A%q>cnLjzwXz&=-(2)K;Q*=HpUY56 z#ZU}52#WtQb2?dT3K-%1QMXuIBn}vb%Mp9WAJX0@0NQn)Lu+dnxd>Fh3k$?5b12gj zJxiuvXOjySrBsCR%hKo%Yo#=T$8P=NJjzRww4P`8l*p{;EFtV3-fqF3U@!L2d@Jtz zQPt7OQ{E*{MCgQ5*i@>b!|M~~q+F@tSfCbFP;Qfv4xGlq08tp< z0(O`xF)8#3Z`Ro1=D?Bus4{dZ%V8^G+8UulN1-zz7ATW1of-uCIDeQ};ek_24u4S_ zJQ*%n4s+X^IZ(o=j6dpIAC^7b@U1-`vBb0#)jQNq73*dRI$ppjFh+Trdp6#vH7eIX zerxDZg#D|vca=(pb8>VsGbVT{D|#dA6{%RGxBdzk(8fYDZHMArd9+*68cb0Yz8Lgw zok2Mq3wcpLv`;(>q}GyADT5m#f;nwV`G7!hc34s^$5mOK^Oqy+VJ0Kb1nmO}=#(a# zPq>TBX3t=Z8}QWo#nN@@;cgw{-c|B;qSILCDEl=AO5OKN<}7G4?!xk4W2`6FqmDpZ9xxYK2FF{7^IG?lWf4#4Jy8Tyn!Gzk|(R7jMPHaa%2>e2ISfyN= z&9%wcoY(qT9?Jih4OvW9l90K*Hg>k5uC7juG8qg$;2tgwMgS1qz1_L8_H2%P&nwzq zLKTt`C!9|oMcTUMgFAX2ZtIsNaPRSgkUZ~#&>V+PqvknVRDHhQ6f2f-1T{H#4u{DZ zk?y{@r6Biy{P>7#&zOvh{x%{y@aP$${QqI< ztfQjpyS}fYaw!6eN=T`6H%N>03^{a5!%z}K40Qnt(lvApHNXHv=TIWu-QC>`G1SZb z+|T>2_ph_gTIZj0*8c6i&yLUcehYn%MSq~XFxo44Va~&K2+{UAVP%_$k{2}qCN~wF zR^NPjhrMW8?f$giiQV7a>Q|Yj{5TQ|JSS=Em)!|WI+wQ)GEn$RTcuT|>3jpDO$hSl z3oiC>Z}>i8u?YO4#;hvPglf8KT?@h6Ar}#-gwwJFjg|CIXcfj|ihO5!>uBnSoL)+WoEl0K^d}3r07P0v!5r=N5lJt1p173oV z6&$w~;_U3RDCQPjOCdrdGDrVw2E*TM#g)I>7Q)6e1)|jV+tzY4J#TGRDz1Lh#g<=8 z+h_Pt{0g&y3a-0m{nFfr(T2*2o7neH^q$Qq583{ldHbA>sr^e)v=h5;Ia3UNQ6bte z@ufO6sX@&@SA*R+t;xG^smfzsYw=8H?=Dfx+OzXhyEeIW;+IMQy6rf6{N%JDQw00v z?<51UiQYIB9RggW5%nQ)|9i30?gtj>8xIhsl7f%6peHq+C4u^}oFhHYQ_l|aU8g%x4_m;f6)o zYBiVQmw0VEvHHRb|5iR!Ysbq1G$gyL{2&89*)slYIQ-i|!&dJr?r(Hpy0i#xmQUrN zAM1L;A(?{*w^}ioR~nJXrySmch=OLX!5hm>-q^qc;nCO^Ls+EoCk@VumrvLjTz493 zp?5h7gfG>MBTJ^CFf*Bwx2bnq%zB_AY5y8itw<+Dm#BFoJcU8wdVp}`#PQYU4=J#74xJP@=%@9u8TAh6$vJ|PEN){_g?|(9%CPds2n}@XVr%U=hDF0~V z97)D|eBEiyi{XKim-(iX_hvtLkVh6nDUgcw!Q=B(Mwb9ovk11u)MtIOXC&}qLb#Cn z)?#0U4($BLiz0IwcOjn?g%ibZ#I)-Tj+k(OdNXDZpBYQpulInVFf0n8v^v$UkTzSX zJEPBe!Q7X57bbnkQ^pLIpAu<$7is)_49?s)9ZEM1OJ4j^U+~MPs1+Kr{^$I>fkDRs z=wft6&2lz-Nv2i{AA{qDc(F85dE60*n*pvWEWh2WEHvMJ+>2_~rvFcL{+HT>lWiC7 zDhp(@W#`8k|CXe~qdqH8^2XLHjDSs&ii2*G$MD2B5Uh$((>%*L^aa!vgnh5TM?IOu zbb$SgYaHm3_=izPQDplBv2oJ%xHekntEjL$t-iR5&ieQ>8k=uB`sztnm-zp->xpI= zgmcBe7_*Vk&F#>Ot4cPWSeo!JL(^eiTSo!0n^Qt`-YkPTArPAUrQSbmE~EgDyGe(C z?W!ZwZKb`o7*iXTqskVCXDzw$_G(fV++q(YW;1L#b;EcoEe7^)lFA`O)=a?wf+ZZYKtD*K027G8mHRMm5)D~A5TAFIh_Xj$D<2p17?oIYB_q(HSjgu zD$ben=yYrh!B4=pe21g9*=EJpt^JN>V)!Bo8el4PG@|R?XeZz(j&Dp;FGg(u@)Mq4 zqCQ%S%Q&&A(`*1Jh-8GP#_AFqHs*p4+}~c<=T%?N3dNks;hI6)tnoHlJM?-i0asI% z8bK_!?+T_r8t#J9cf4uk*#&+(!N|W=iuw)c6jb(S=@c+cEoW1rV9N0R<44ygtOFsX zE%c%*(?_`noTkyW--&aTx=v^nDhlhZKY=-4j_^r;llA_V{Ik1^#(dpbqFm?p=fdKb zEQjNeFZcU9gJarUym`9`@M5Gzm|OSq{(@O+8O?x|6HM|x`3EI?k7dJQ_rq#haLA@0mAv>&TmCg>=F0fst(`tQe{LgYr zmirpt!x}s7*Ee#5o(=m!Ucz-oA(*WEVHtlui6lA>p=Qxb>#}KJ9BTF$8P=pg)h?x5VC~V>mWqFUs=uC`I%~*~C%!{7= z$a-aBQ%U7oyoahkemPdH;UenQ{6w$?_WD*$A#wRe%}qR+y*iJ@^!5Dl4!G>3w&RCr z4e?h7*6#b}li>(g*zS*M8B*}D;V$kn{dlZg^Lmd2TJv=%Q+5dwu9a~-!KE~7I4^6f ztyhOGU*#tq|BNuhVqFJ+aVePY(0>3J>A+)nxAppj%+tBIH-UZ}K^Jq#xzD znFlr-50Eeh8#pW`=~MiZj@&QvA{Em&)dpKI0+uIC5l%ktTmoWVG4J#$O_JOzwGU+Y zLEl_#hQ6O_r}ha}DX7)>ScwIx{vuYeQBm@aDIzjZEW~K>w6hywyG!emuGf=umEC(d zcKpKf1C&3A6XfL&bYGC5o5{S?d$@bO^KeJXIu;MMXglt@x|RUM7h+jBltwd0W*btg zBDz&IOu-TCS@j(2Ds(qd@|6k(=+tcG56nkL(K)%Y0GbgMvKdRpTB$m6^OrGWi+V+6 z&%AP1R#<|X)4-o@D`%_=QN*zkNjT5835N}0;1{Haz2qMGh}FQV9G-Vf`iptbDAldHpQhi(<%3Uj@s13=HiX3%lj`x_s3#SS zB@{SM;GnL9(s<2jE>|@FYk?1Llcx#7v@Xn(!cJVrXJ4NT*3u04BhbD@NO5Ah6 zW(tk&bbDc>H`5d|=_S1S>D)I-$iAyGi@ddC<+{(~t#F=ObMHD)NKGT zF$u4FOBSclU)r{TQrk;t<9ta^AM%DaJRwz^-OYQ>a8gR|WuE+k*Eb^o>TTJD!XVlv zr?@Y;s3)^Xz1{PS)$UVFj){sZ^sjo^uMx3fiO*@r|GpChMMG7|+~luBq;@)V_Md`| zY?^TbU$_KszPUb+*s&C5lt`lVxSA25jP; z6lb;&QS>pc_|JV&52}Q^t9;sK{OedE|U#h(@YXdO9Riz016&;DfCF z=b~sRfvR)9OAeXLjF}$1=+%2Qw$L%6N9(q2h-b5`QIL14vBya6#27 zjc3L2(C7UJ4t)mXzWAfK^2`;V!Wea0f!^Of^Ow?!%f3HqZGt)_WmXvf!A0Bt!Gv49VZ#j z@ddabO9(hfWSa3EeXr@7jfNFpVuiHE`cD1(ifj2Q_tB*BW^Ugu0%L^Z#Ow(fWh2iExULsuhZmd1|+j!;CU$KP@Iu{_-6L?9- z`GsB&o5rhNE=9|!1+AJjWaagV+zyB3Oa#CBO2ho-O@dR82j@g>jEt{&;g}W)!1oj6 zh1%j+vNM$;A-hvk(z;u;e+2-{$H#>@(4Zoh_2CuhmUS=-RUkL{L^rswMI2}!5WB!E zE`>4_%u7Xhk@~d@p$0=n3G#W(M1l{lhwNBNBKJ`VD!Wx+q3e-u)b_C^pMXKs z`Se~OHuS#j=YWoMVUKS^rvNaAI`_x1;*JO+-_m=%I_a^kjSA8d$Qpw=bd4>I@m^0Ae=@6)uIb1iy(b)bW{?@1qcIIeH&4A!f* z_P_Bb$M&1)JI(A%=^1)Eje?K_Y=hqwDd;WvjCp5SGM|*hNPM6WiotBv=<(q+m_02b zikYDT)zipl6?V8-0gMNT427J$nOx(SZK@H}fu;K?9T%;4&$>I;I@`8XNgSZ*F{fO{ zyAj&uLKbq9wtBsOlC+^YtWK{8B}1}5=1~m}+sgMURJ}5$|1C$*EsU$f$)nrkAyt!K@^DeBIISLbt!-C8H(NxL7HHrZE7Amz^M%l=rds7PL?dhU&5 z;`!>qCVj1Dc`z29#y$7eMQEle)#)YkqQQsHQWpzC33zdw=Rv}LB?d|NwFd$2vAoy> z+!1lQ2jJcc=5*qsMAB%(neJM1K?7gy)-fUZj?|cae(^sC>d~XcNKeCKyh`!%pGV!w zEBCKre__|3`1<-!5O#}aNr*>HUYMb~=o;XoeN-0W3^fampJnR|I*B!AcblxoVhbzB&C+V=JPppIL;LE_a^q; z^Wyc8?6H3YP(wzoaFb7f)b88T8B;Ry)uL5vhG$8G^oepYW-;m)+Y!vR-Py?oFyUI$ zsz@wE8k{R+?mI^Q9OHZ2)?yKDPqA>qXx$p^wD7%xC$?JL*Om#~`Dv<0)$J~la5_wb z5)FP1&%7Mvta`;~?yVEUywsCfbCL2ZCbew@McXN1%2VX;SJmIdEm0c4oku7{Is4% zrs*Y8*u&x>Q}z6{>fePU^0+A*HDoeMaz1?gk18QA@Jbe%QH3@m&wYZ0^m`=}73W`ec)2xCzhV6$b$fi(+)u?=giAS#XqcsK+JD&oS$TUQPvU%Q z@l3buQzs&t7vQ;SKjEj(z-!gHAmy2y7FO2z&f(+0GKY0@*Xg2(sws1yjOaeynF8s5 zE%4DLk4Qaljkf;H=vGyWxXM|R0K{u?woy=dLh#_}6^`85_{ zk@(RfcU5Dn1`6k8PWfVrh&YY2R-V}sv9NLjZA?6iEQ_*mbYYywAXoT!pORBy@SM1B zjss?Ypv)d+8+4URs5FAbWEGnrUeW?aGxfavKV*vd<$j&cbq5EgzgP{ehTdbwkMWeM-iQ5$Cb zhGSwN9~XOPTlGD6-1L$QfxHKlx-z>6v)hkyg}#4fe9&^!Nw)b_$slC$_UiXe>naYF zO{foM=vH%j%iympts?;?i@Eo1B*EfhQ96A5>AFs*($F(hj@xuE8?HnpTHgRl3Nd|C zX!Ez~+S+!a@nPI$Ny~ZH+jv$V?fKtC{qDWl4a@#z@)a1>nc0ToPE^OQT)T5g9Gr%CO#QBh`zw;7dIsS)OC zERF|v(NmIZ*wW>|Kv;m^a8%we`4NqZ1+Vefb{Ls%iTPQez9Lnl!-u`Mx&9EA+zH@& z)$7{i$jMhgfgS6J2~pn8V^x!o{jW}dS>hC*+P?0M=$JFN>i7>_)yim7xdM@5lBfwa zZIoTMT60eQ$%d&&TTZrKjwMoUf#UCpRa{=7PbqxfsVF&f`aN=Fz@Hz}KeC`?GoGu} zc<9}J`QEeHIHOx0+G^w;HY}^n%%Ncvjb2;WpDH@J>1NS0;GvY_TuC%W2K5*GH4su% ztM8)29#C}Y5lzhIL6kuI-UD9c(Yld?&XXFTa zRfi#Hx66etjV=#Tvb~1PGKeu`&e4{VQFT>qEU@%;;}$IvWo?RPYc`F z;8AuaCCljPdX~GlMj^Lv^;I&I^@Hy^V2>Xy83h`*A1@f0|AFH^oAC>ZW16i$cJA8< zF*uZU+p6kFZo6@3q{es-Aq}yBbnd+0-@^gUFTW^WWGEfk z6?Gv|*mW0NVV|AYIqXldjymz2Jo{K8^S)+S9l(h4@rI!vryr6XCijhtqBJTU7B^j{ z-o-bZ)j!RC8d@x0CAHVHVDG=+Bv_SpksWNP@yUn;Ei6v4ij4WiF}}IW+`TUo-tmY> zsOgJ;I6aHdI+krJL7_W74_12U5pQtt2-FgI)|Gz+YjTWlsi7kwk5 z?CoWuWZ>cfUX?GGJ!4Y)loCng(baPS+>IF*0}+aLh-{bI7Kkx*_Rm3ba)oSns0|W{ z%jlTKm*iYz$JD*}@U4(WsOZE^a2a>{MgBS1Ixpv$BhQjqkj1d31_u zbbNQ68Z8+_RUV)`qVc>OX$s^`8iYmZ5WqKU+ZJgF&wJuZ>5}n<5?DIu_8$hsnK{fw zH*f!D(X|lcc`%%-@^rq0Iskoywv3}-H1btZAqEe=4h! zVKz&xt%hZd9hzL8gxt?Hy@99=-DTf+gwxtu8e zHA36?vEA!cFU<`;2ci6HQM};8Hy}YAn-%yP_8|ePi13Z_t=o&M=TEg`^8@@8uDm<5 zZ2x!hWypV4*#$ftKODcQh^l`1!%c9B;{*|Z84+3W4B-b!bQf?g|E4j?ocH3O$b}7L z$Yo@z4>NdHfVTqHxu57s(IbJF*mj>jl~pB1M5Pu()QtAN#A&Ys+Itu7GsV4MCfkSChXAMfFJMvTN3kimenVIba&>k}i3d zODg7L(7(@Xe>Uk}1CMid%ZF1W3T}&Z^My&X|%@fp(ph zIF!=$c<#F^m{=K7_Fdk3bG>;rFkw-Dw-NZA>LXx`C|B+cgmk_(7kSiGZXTW$Go;po$j^4Tv+TUiwW^qD?Zzk% z^2)Pw%-hKeP1ieANsu2Ra|oo=Kc_CxX2*q5<$?IZSahzRrmXi2RHIYLdj$L0`N76k zY>{DhOH$)PMjOwoXqdW@l=l0iL_708PBG{48~Y^;oRwJ}EP{FACM?$up z_aZvkTkTY}rk%DZy*laIG0b4TEyrW(PrRqQJqb$qaT3h;pSN|EvCnyaTCeZ@Vv{bq zD-d^UUCZ#rOJ-gRr_Ot73=07&>E6#VPLo#wIjXW}$7i`t7lscD5?pv%bH6FZN(~kj z{E!H~<{22)Nl;qt*`3L24UevxP}>E$xX`J1SngyI1XaAW3la<;gG$OX?OvCg)A8?7 z*}LoxkCVm)D%rDE#LP9hOfGm`=?mWodnS}y77EaJda%6`jec2(Lb+K?xIWVVsR|E<-SiyuSSTfxcC(*{j>*E*Ll;NC|}lRdlP)_5vh+8 z*?D209%UC=U-~Y#LHGOQ!g5Nx-DA_u*9N$6SOOD6R~N-(CdEpcXc~TzKEXBKAc`-T zr{+yx@d-MYI-@q;JgA2a=w<)xw#_;HvrTbiNL4o*q*Q~k7L=YZ4LQH{#=jA1E^B>$ zNur~qo=bArJ_G$S4gGif5S!Eq&xUikBs*V0(lwg)bI9q;u^=YcWkqF3Jw(g4yG(X^_)YREtMH+Lv;#moiOQ* z#cb$bn7X4~@3gC5S7XawC5L6R$I)h3-JLVtnkSb4y^|W8tDPqJ>(uj!3MvnQ${G`c&VC>*u;tyDz05wkij zYEjj17H296IUAc#0v{v~%i%00_K(YV0_ThweP7$yet(HAw!dtC=*gSjIyHJ!YDb@)^xV&AzY0PfTibT_tU5z(adsqTf%3D7QHlP6Xp)Az@h zS9`nchzehHvLAe6~&k{!2;=bF;XH$aM z2+`&Z@0k!euS9$G7h!u07)xbu2IK0d;yQkEOzWM8nI%q^ZoTJDgBPiPU(rQkW3NR# z`SIWTUb@M4Mo{j<)f6RL3tn?Js85p;zJ`bzZ)1pc+HsOLwNZEY9jSx)ohaSE9UBB4 z*><6Voh6cNg=cErvjGz+}NvnL@Gc=Ng!{Nx#8zW())KgckNzGvz&*oN5?v%W2P^t zC{wx+w@#NTK1*Cp*Ir%M>dREa_FiO-78X{s3B-OW%>9D`&!_wk{&#_XN$57{2Sn23 zK+aS&=Y%OfEAH4{35Y&I4(k#0C<4lLSkcHz^ zp~(YoblSH5xHOd}yH14|TJqaB;hvs$|CFIm*oghYF|7R+HDvH@H=77lz{^oouXQKZ zC~V#HZYn<9GFJ|q&_TcZR<2BpsOE;W5uH4c*w8T-*H=KQVG;N9gez?_|L;I1`n`2G z;Rm8Qr~6muxcp^LkN|bbR$Hlmz{3zcpHh_onZsvTp0llg-gU8fN2_dWo|4h(3(wdM zu`9fjgBWkaB}6leBcx-vF1<9mB0r1ih=aF60kuh_EA79%&*atclJX)P-KG76?h)_O zcoMMDgRR1q_^Wij?x!nOD@!?+95Rw@BwdLvKg`Y;r&d794$GuJhpFisD_Z66DKK1j zA;#Z_j$~cxG;DN4lW~E3)<@GZE|4WVy@PUY4hp3(Y{zphlE9(+sa28xtwMPT?f)ZW z4$QRExj{niDPm%DF;|ok4l;K1iEPZbT5|~Brv^C9?Z@-_Dj(&CiK9nCFQzS{~vT~pLs z`!==&eplq$(g0qwln3u`#Ml!%>bnl4i=1 zSAFZ|@q)eUZst-BOoOoi)kDZ#|CvApqNQxcN{PcEu#xC~7Iw!{8hcnreV^jaoF;#j zLr#4!h=#b#JM4?oChq;22%n7AdWJQ6)^!6?u0MPgsnuh7x`h-iIIF_#;UiR~KkEkD zcK>-<`l0s@Kr%KIAa_8$zR=%3Za;lK%#oHm=SvrxOi;Vnd!}3{k?GW+wOvABtb>?2 zy#)fwzuPt&wWP%PDErMMN9`mE3I2#X^v%NCVUPhd2b{c?nlF#7*IE40BIN`=pgq-| z#<0gy=QpZtTMq7HOY*-nTX-gl`d3+c+}1yE@M!PDBiob(X;GSQBan8taZ>KqZrc(u zTO{0r_Ypkhz9Fm2=URf$*2cyE+hcun|CIIVElq<5=EM(jw%BF4CvxN3DQN*y49$}4 zjIh~QLXJIoFFgH8^DQ@G`UMDi*qqdg44Vv`xj+f8n{pS3nY89>a|_XiM{tIEcNT7s zbzrXb?N?b#hwQ2{vw=daL~zdRJ*?V=35-v#aRX$RwnY9)Jf%x5II@T%Y$X&0-=lzs z6kG4E;$_a38iMKNn1X$`i*shpPL07%jhp8)5p#G%4>AFwIC2;NE?D-dVc^-M%thh! zjm<88agjS};UZXtss+EEq-+v~irOfb6dR+ zTh0qPH~@B6;bYIwYspXwWtx9o1;MK7}7(u*on(C-ok)~aY6ox)dW<2CHrR2r{}8$Vr2 zs|Dri#OjG-+-Ph{bZq1d)BkZ}4$7u=z0xAH$r(_Z;WgbYI--W8I;p*(DsR_!b$SnR z+q?`paqNhlF$V79v+4=xvwKten6kU|G0t+*HFqAVVKkxt^kYG+dr#9mgfD$(?(XpM z*|!&RF^qqxq{4sO+1d@h;6vw(d524D7f0~AZE;Rx#~wE;b{V?d$Q+R9SR|}z`j)d0g>NM%TAV~2Gh_Smgs1`tLgztg$3UKc6qHJDf71k+~f zM)=&4kb6Aa&~1(|fe)b;}QD!q!3y1I}i(Op`gg0z3 zpJmDz_NMQV9Fr*@mI(K}b#&i0^q=1pl`-r;PC*J3dkFnG?kD(-YZn{B_+5uuzPv^Q%S=c`o16Z6$F*B1CxeH96RABd-C~n;`$B~ zx-Zo6k(~N?z%zWD)K!m&8JUKh>Ii#J4 zL|HW-kq*)b9ld+bak7zDnpXAsvcI7YcSZHSr1kZ44!??f$-vdPFs^Q^b4Qk!xdn>SN0ah{A?$`4{L_8R^3dihcES2YEhoEMdYf7%lkz z`=rI;`7`Xi992Z>g?tjcvPCy zo+M|~Boe@){dFdu<49CE&c>-f(SCz>0(v@lt2Yv&?%Z$6YD5>19#m%gr9`0YnZrk! zbi;BxTbunHq5y8|#ZUk-w1&1xA*7|yT2XcIq0>Jxth9cTC5v1;Ya%nt&k!(Z6~(q0^%$L_VBZ=Rx6iNW z4&|Bgr+52j*r#~p)4PPzlNv~Rq?Bu0S|?hJ`Su@?U$=&P%bD$)3%$#S0T16j<7;!g z@nx*~;=vsX^q@{jM9mqTF7o8hhx>_0?A9f!!u|oE6)d9TRu@L@VbfD5zFPnps21Zd zrmJcz54(RhlE>(pz%WQDMwul1y$AQ=Yu(+X55(v`U~qX{aOgVsrg7Ce0!z(&Cg^2P zI3|=jfiwKf`=fwo#-X;&KO0{Wc;5nKvJyPSq6$urk-mRSZ(a%D>h|T{v)_>|88WXP zob&x{`~w?dU>PzYIBRsGpso|v)x!c4C`h_bBa9kv+<4!EwPu)yYvY=s7lyHQGq|-N zeYK23+j_B8Yh~l*PgaXessQ8xX^w?}F&t|{GEG^vkV4ZB9Zvg{{cOjzC}m|bfhG5R z$9k3pSrzAzT(=;y?!8YvhjSYu7{xeGR#MqiPQD8}R1C} zMDEoeWA2B)<%8vSUpe0zj`j*tuXhVh-fhY8O-$j*U>5r}`~9Aj--UU8y#KJZfTMU3 z?N)FHJq=SfIq0_CRJG$=dSHx8WSk`3e$Asmtf~@>OP_sxBfNeh#IRo>W{IKh2EwWP z#~AvXCflBO{=sD#3M#^RhDpv#U>9U}GPD(MIpyxvzf`ns`|2lV7IDn9`&GQpLwIqw zCcWq0nPGVE*X>JxH*>F-)B?Ev{|@f^-BA1{*uuH0K$UH&+}kGk`V$`bEeSjv(Fcn= zB4Qj0dc$V#%dO#OpZZUe+SnlbP08QGNuQ^kLQx>UYSkYWWgBct=vL8f7q|u2rh>7d zE3aSYTp^{j8)Hju*^}jP2Tcw2)8mOi61#UAl)9aB^qhdsgNgPC%S%9kk_P4T#t=Im zhqfF_RKq;9M85~4Ud+n>Lz%P6MSUIL7)!UEfX;_pl#;9ec764D^bOQCZ|$8vm|iT+ z7NiB9WF-pXRrd^O-uWpvPLz}MCH}IRyMr)vMOtmE)Dp{B}*$q|IQW$tKg-FU98ZEOrL@x?)A~VsU#emna26Y}OuYOw5&|YX8hGU~@Ow zMKAGiMXJ6_6m8gQK&)yWzZ>Y?w0+7fJyb$F&1MVKiy8n&LW-VMeD|$~f6@8gSS~PH zd8dlM6ZmXj&uDUR9iZ}(Be499+U#G%CK*8>C85Wd1pUbix>wcrL%Ir}#=Y#k!V&|RMXB3bcPO&aA9 z%7hFy;jzLLjU?n0=*P%|^k0_HY+J3C`9ZR$iaG~jw1|?G9=`B7x0#v9Z$Vy#U?qXp z8OjN?86-5Ww8~Ocq_dE%MmA!FF`gc|De^^q8>U=#9c$~Bi1FH?vZh_IIMfl?dK119|S`dywns1aG8?US}UB9${F z`G9yV&K{uc6C59tG1I zbH?0Dr#V2US5{885Bb^#eb6oo>-WMqA{Fwvi*$s>G-m}eWD?SF%^SB@7osScx51rQ z@lQDyFFTP+O-pq0fzIww?EKfNfdYI$r=6Nhr8Ni)9JxIJJKqFZ2?Qp`e~|?zRX;Ig zVLsD0V2CZ7HL~Bg+E3oGqGV{F+coOC|nK9zXiC=%>2xcI>zhk#*lvccR8*dEyM1&-))#y) z;6fXy5;N61gU3PW@a4oM>k5qM)97-^Y35>UU!Ib!PFtqXUZUQ8!w}{+QkJB2I(#9n zQ~9Ru>>1YC=eHdO;h$$-ffnJi)WZU2M$TtXM;${$)?6CSIzrD7HC?I02e2ZO@xNE7 zQH%6E;d@x}abS&jJP3vGIZ1IXPCZdOl8opWRY}S`!{)RWjww}-ih8X6*B|b>p5Xp)uwUx0RV8C zJ-jqQUUA!?e583ey4Tr6b4L99X@y22lsez?P6xiVUXAmesbi@^q}>td9r39sXRqVe zgLcStydMg&-N~&Cr*BI*n14wEy~S+govb$)bwesrbYA zEqsA^t5U~6qa{_{meSzl2&J5Loej6!H0Tt?zCQF7Q|U)|X6Pg~)% zoxJ3A{&{95%Wg=2@1Qrku#-6FU1AWi&DN1%E`P@r+sca3htK-F5UM?q&_A2-9Mr#3 zcR_{X(Rnuo*>qD7QxvUdO+LAKjfp{zT_VnR?z%|dHfX;i(!-kJzqQGK7yn?4_NfW} zg~jOnXkPh3TsoWE(f8Pw%2b6|$ZVx_!qS-Z^4jWIPbN(z=(lUL8o-?x|F=f^zopXu|MI6KfcnwEl*pfZqs?8BF7d#8YRmhI=A*^? z>0`TRI}RF`E%As1HC#_SB)G&HKp#4wFK2n&xIT*%RJ9Wc6>%}cXEWo zhv;0SW0%(*I)z8DdIeIlzE`(zJ7fWdPDH(enkAm%Zc+@zza3#(*CDJFY@^F*$Pw`Z z$z(}n`kK070h`C&!r_G)UrodNp@yFNredye4qCmD#k)D9<2j=i-gIQu4;QjUHQ`oO zp<}!>z1uN4`Nq#q_m~YlVo7b)o;!NvRzFbI0q*R}<|tmg2if*N6%Fd`bcE-A-U&sO z%ZaM2xp2nB{~BXQH(Q{aGZlJXecz;0k0q%@_%k)fPZnbv-4e%lT7ehZ z7=g@q9ea;VLy(_s6xJ8`MW7wP91ep$iM#Mfpvs@4yIprf;%Tygf6!VZE%nGW$5uwy zY&a}02mG+Btz)^=JOg*D9%*Ip822gX-I;u|&mW(+z2KEx5?|709xb z5c&obN5y4YIX2dQt4U}$cWHRC-BjElS6ToFpFO+>wv&!E3EGWo@G*vv3-vUg1BTe0 z8(*7q0J2fcWA!wz>lR$_ZVQvnKNZbcAkPKZRK{?SLe@!q7`;ZWQzA4796nA{5Y$j! zkTLgs6k9zjakjvQ*8b2D*kic)( zaHO@I#EFwofKDq4u*#6^QOGf&i!rN8i5MnS-lTUMjY9zpHcQE^3ee&(W(Fq?eQHqt z<0x22^3UcD+-BE3XkssOC8ECkT(Xf=sJ=_JgIiv3{8yklxOx2B5CHS~@WgOj-;x~V z+DE%o6rhnO1O&vyJ)y*+4q-lUt*g$NLCkm$q{;fk9UrqZ$#2Ox+Q|y-(^C60Q!)`z zSzXRH^`d&TcY7!p8WjI=Q=rS_RY}DSZD*PxP!-~`Vdc+@uy3vKNvhg;zSEpFX0XRy zG=)^26qm=C55EmkKh_`|CoB9t(Gh z7>wnOdzOl#VEN^>0*sfisy>k5;lnJZ~cmK|p@u5B+vErL#VYW^QSJ=ft zOYX(l(`eYguv_C(1|i-CvD)0)b>IK`=Jkbv9tB0|g`@>J=Ci$Otl+`cBJLe;(T6AC zO*(eAP=Z+tpI)A@SZqhi7OJNyZ9(8`&qkXJdQqZqERT}qS&WPAm>;RpB#>D>t34X0NAVVW}0Vd|3T}B z^SAZv&)p)hERAA`XV{2<22SM73rZDky|np9CH5Tzzc{b`raKEV%UOd;vrHM<9fWJg zf2`MjB204b^{MwT(0X_29wON1O5aahcsL#}YG-@k#h_2}TvBBO^4Z7ce18ys*qG$=L4L>K$qvTRpg@c8Y1gUi z#mRl$^t@g@Gg|}I9Wl)VduP6}0gzvh+@wN>%yp~~Kc8d`&0 zr{vW1oEeza662?LmakH8eCWn|C0D;h#g0>(=kQ2fdeB#E2Dp?uoHd))kx$80X|9Ui!2%U``8y(_prLZmV zHJ?Vu@RPJcxH8%FoOX&KYdgH%DhW+@O{5S^(;DNN7%8;S9&c)K$zS17*C+!ISij4o z=-!j84c{5NXiWynz?t)0ZjNp-1?j+f%lFLP^(BTJFDk!nDA$V z2fltrhIa;k#p|87|A?sWu?_0m@XLrw@&+hlknccJJ-1clr&~Pb9)eaqdAYEjW z*fQ+FW&ND!5r9)>&9GtauH#3&n^0TwD-ag;`wqQYJ$JcI>#ZZ%k|e4xxu~9@I2U&Q z=e_fii!|^XQPh6dO@k0`pOBKcEJT`Ib9>qACD?)$K@Rnft*F8JlE zp4YrRy3Hj&FBL0rC(i2>rl$^TC&k_}uJC^GxPKB`>yhe5QVn=$+FWF>0{?ccgn)l& z8)}2FGKqPYzEJwn>hx9_LDtpOV{T%jy>L9XbTwoP)-ViI9H|i!`OsLieX}_WJ0liv zBMNym@Xp-FIx){P+SLCtk^}%~xXdmkqBRLbt=H^R%;u8GX1Mr(VVRQADqxQc`pSFT zTz&Dnp%pm4jPCOE{)cv7|IC$CT63>V>4^u>+J^K^#YO?$XGz(^@0ATVM^4xn(Q6Cs zD5+y<+NJJ|z@2aBpIM^n#Vp&bRg-`R*j5&!iTe{4jlPQ6RpGwbTqAn-&F?SdJS3Fd zV@pM>L{;b1*MnD-!3!y+{)tWRw9OsEmK|5t{d|IR8$=1SZAk3O;d8!a%Pu6%JuZz!_SG3a3B6G-g#iulc$f@(5&GUd(?urCfqXv*4q1 z=WI75aUSDg9=w@9D4X$EjgHdytQ?5YL0awEeIuOrR7S_ew(G3FztqMfg#Xp13cFFp z^zlS9-|uoHWW+M0|h? zpR)<4t!YfeQ+Oy|Ec-66%-JDMeZS&KmldM6E0ypDU8(?~i6^P?C&Shz`6NnJm^tB? zxEg`Op>VupJ-zwiD5nokLUYT}+oi_lRr}1BPTI(1pC_|(6(|!4uB8gq6Y6$-xdBkm z8S^Ie@T=Ks1B{zB@OSlXxz1-5Af=& z%f3!O;9JVodF?X#sdQTl(J@oMH`O$J4>dj>zEa+39I6r@NRjilW?Sh2ZB5HT?84TM z(6^9sR|YW|uEkbDf-!a(yV&%_!KSwIx+?0l?QbMg55Qz^Jgo5{X{viEo#H3?&q`@_ zajnPOB7Eft=%>DxMfjUVTqk+5PnBu5pXa>)PP2n66-m;=i1(V!he-9Zjc{oRFomht z9QM9w!h;gA?sE7&5(gXX(&3k_|Iseq72CBfEY+-;PAS#wBzw#`$Z{}gz7iUWMMtYD zN$oDk87C7;~mI2@SP)_Z}qwZ9_hcY(;yVT+jS=P57z1imoM-Wewi_{<6j9q#k5 zg?=Fdu#D>WM9-tRHYn$UALubZ5R`gr4gSq^Y%jw%3cf#K9DiLDf9P#`*K%@&B4g>G0gUJ8MmP7U8Z<(*Fc#y)D8N zCJ=%9nc3RAZ;XE%VVs6Md`+Fkg%Un2=5COykd;*c26iQT=wS;Pf0mPW8~8^--u?0m zyx}7k_20GqpBMfYt*^b^N2_rr850Pvq+cg)=MyWg5lR2p?5#EPz2BIfYLH`YYxIyshhE4S=k z(L^b_>98|A`XUHgV%Xi_!r@%svEu+uiIkou4ZKB8-FT@( zH=%)J6pE63v671$H%t{vG?Ksws-~V@S|t4p#$>^G789mk4Gv#5g%A5(ozL3X7MjRe zm?fPyDoAGi_3B{>D26)Wz*L0kf`yM4tC<=|o)y*)K9DSuX8R<(5X73e>0i4ge*%?I zJ>ll_o|$$=f~pfieDLRO`-@cnf1HM{h(K&6fNs;=kIcjrZko&|W?dHSFfU8eM4h2? zZn*D+Bre21D4<1Lrc-N~=p4=XF2qKOK_}bQ*9#=6fyKZB5kuqQJB6ykHYu3Wo03I- z4i)R}bEDRu6V^>ypJwc38AWP$V>SoLxkV`s^THb3ADK@wtGvB-;&7PK+?#W|tw8RnD4mP{Nv@1z83KPZfv5CwG@7V;OM)pc ztAa+C%?*~jcr>GS$avFa#I^|3+Z9W@Ed@$*!g8s5+(>VX37g z#bvF~^qw?4vc}!nJh|I{ZUHt23&r+UeS!9(gMa5AKOFu4Lq*u`@@W|L)^>TJ(vsDD z$ak`nZl1eDk3xIa+`eAgO^`rb3eK$ulN^deU%@G7B}(ovmz!Gau;v->T;$9^hSK>_SMW#xU!If>s)bY`lh z<9S?fZ10$|^SGuqPQFbb_Cxv0^kdWCr>O2Pu+JbdgBB4L4+3pe{aCi@+keHNjFPdP zPG1GZ2OCaZ5EE^ene`}(f3+SeucKaz&vGGMe8CCbQ6t@zTW|D>TVQPFzV{ALR8c!49~NJPGX;BSyylin#)EfjPt zLY{TI^iAEdUaz5Y=+q7#`}2MtKadUH+&&{DBsCg%=cj*@5YBR&<`Zob-V2W7wT8m$ zw$0 zJJ&sJu4w);IUS;0g^}{)x&Lw;py-!b0AqnZ$Odd7U4UnJdNcyr=Ym9J$&t0LHU;L< zhE;lr9W4Gu#86q0)KGW}uX?%{tHLSuh6z6f-4Xxe<1S4>xb%Y@H2tPCb0|>O#jbzU zxdc4yOX&w6bf*PxS=$<_$l6k_$wY%8<`4bkI+m2BrMn(JKrxj6KtNv#Cx zu8CdG%!yYXFC5bZw#xIZIaRO-&eCPLRmGEZb4kGA4xjDzZxv?wopmE3vGEB6A^|_N zcr<}pDH#zHBQ8(T2QAnN^y?ox`fq~wFG+qXbr}5pm%x(LuI^Pc+A?$($wj+mX>4}N zIqo|NzlBbq@pSMb#I|LSq?rG8jyYDY3xPNXcau`}y+A%Prz|~jj}#wEs`xUiCFqw! z@_q%QcKW)1AXC!I%j~+0Azf!}9poWh15Y*elQup#f2oc#Zx7G_cbWcZ+|$*aQJ$CY zlfsp==%}ilJGBIwSAn~n)mq;I;&wk$Ut|cH7gEH~^AP6M_*uY^Y zruH?<*dIUYHZRA8y%cnOB-n5}cpopb=ga9g`6OVz@WN8focwPcnRi+mJG3o};Xey% zn=*aJOKdEPt_H1!E9P+pWZKvAIOlK(xk+^pc+Qryxz{8R*@cP5sin&0F@ieT6wl{2 zR1HwZ4OVoO59s!(rR#Nj5J5%w^>510$911;ImI?u#K{~~r8z-?URjf)5YF(oCB}`9 z9Ei|p*3IvZd^3|>`Z>QEqZ1W_x0jY*U!NE`ALJ=2&ti0ybn6Fl+;~s<)kvw|lh4;3 zJ#Q1!cbW%_iH=lvSixy=chavl=d|Ht;W;&IX8R8mDKSBHFM?dx_sF8JQu*_FXm*5VoK;kGEqTs!Izr^2 zF=A14SG+FhhBdN2L8D6zHkF0M?RinuD7{4zOr&)ajfu*ovmJmW8$sdAGs4gCPwZrC z?h4{`=JhiOLKuSBjE43K8qYCE>RMG);d{KdFzafz#ZDDy(QCZZW)Rj3M^sao^} zuNzes4QF>w&alO{?&ypM z&12CfMKH=i;<5iO-97*Shvdh|59qjtV6RB?q&*Yd%V+O)R!Ujr8DYI6*}SO+=u@f4 zrso?!Nm>FBAFEph7XePXO?RxWCdLdWkNC#nA$5pj?3^%f{*7gG#c`r&SgbpNV#r&E zcHZVp4*kx0N58^4*$n-Ss@=mfID2zL5}S4r*HA*OoFA+1f9WuKqbV=AzD<60zE`H3 z1IaOl8yBVp- z9~OFYx+qXaEk|VH?8hT68kepo=`@@0qE31Vg2nw$j)W`0m+tg#e-f02z>x!eS0fD8 z+i+boDa%=W8h84=U&kv6LLLI8t$F+`1jz-}m% zUQU{Yz>olST^_6L{`J76J!*}dFB;u3HSzB8Y~R_A<%2?hm2SKK@o&a<$x|EP@(c|3 z{eX|P$rdj5e^xb5>3YN$zxL}Y+oNTRInkk~O}M|*to!IdJ;eCW*~zh}nuba%5L>vp zuMKEsjF~)tdJ(D7I8eUgaNOzQgM!}3OU@TN-vi92lefHYxthFPwwVqK^vQUzckWfEY zdki5VxuQ%$qDDgT=K@K^Xg-pJ1nTts@gp7Y%q`S*Lvb++iFWEx!-bLcnOXv^6iBc{ zY;eY};rX@0JK8^0NJv^t)bTjkddnh(YRj2T0uv-0(7}K<`3QEO^J*X828VNpcxSAy z(QpUTjeYSW)Py$exP(~t(xrBWJyCBsD}!3ppX{^+Gq<(UalTB4P?}R@q;@g;%BoL( zt7ktpM-{1oZ53iIh2{JiORV~QA`eUNNb4>p^xny5Xh+9?Vt&Yl8f=pn9;?Fbm)48T zjq!wf8$H58B8_Jcb

X!$6*Abxug$MGHUZD#Pe0gIb1g;yfU?plU(AKvjbmX&jU zR4L)UdM;Rl9AQ6_6U%d|kv!bPdY3W@N}NHD6+~z*;-1im}J1U6e(^>o8^2)uJd^X!~&&VUJHuZh({bx7LHEn_E z4N78RdN&UrFTAtMTK4TqCg`n2d_Ft1&)jVg!mJCd#U8#MA)P`0 zeAHk@N8=8l?^>!xIc4q*c?*ZO&+M%>gbsUFS;+i5UHA<~ETcrPqS(~TaR$oI=h zo5VQ#B)TN5u`vP2{Y+NAo8&Q*d}rODeH%^jayKJoZ@Fygp_Pjnae+OqNq$y7I?G^t zAkkSU1j_-1^8#OQT(7~{2x3USFzi9mSZUj=kG%?zYWK7L!t?@zZv(lRsT?FAD>&IP2cuR7a0=FLxLDbS?O%4}Ibw1O8-2k)z_>ZW%)UZ_w2 zI8c^;9GK{+6=U$-=i>X}F8-5HMQ$voz!Aong#!-UO03yf@zv$LIM!mX_;o z5i(8!)a;!W*E|D_-`u$B z<@QpA+0cx^z327LV(x{Z-^x`ZL*dpq0-}-Xj7cRNP#aR<$<^oR-;}AB7 zu=W}{k}8bK|NE5?NzU*Q9F zBhngKvB{#Or=+eDQ7I+1O7+wSQC@3z8+zNP=NVF33l&ocs}=EDW~R)?U=i?np{RNG z$78Tr^wO7>KO^^VXVQa~H#96| zto0#^qFPcBnXY7rA8Qi^A581q9EL01@5_84_SFBT2WLMq*vT^M@+{uGfW{;W!g4lNMtz1xESr>!p%f&K1M6I7VY3uB` zcU!=UwPA->g1&fDV)s>X}_y_R@^vk%tLDn zz1sOzY5xsoPzyN6;gMY$k?k3CW&PH2j@Gd=`CyFVO_aEct+1QKR7l;uvV#D|#1KF^ zW2i9u=B)X_GQg`TaNKuGXdxm1aY#@hzv9x}8v=7--Jsw;sa$v|rJ^@!IhOK0dCm0H zq0jdSJIMv14ESW}yr-EFc+06mcu1nG!at*9@-~MEvmY^J z>4vdOg#a3gLRvCSRl33HBEd^o$eel|H*|OZzF$l8MSu;%Q3%NY46!aLqmg=7{@^2D zfVtU1G0wOhm)cXS7PjEeqVMg+Gt{JzFjqGI7b-hdTGoaos@Trc!cHKAwcINM^(o`z zKEYrRQe6Yusd%&0_|W%cT{JizJ^Iz2C)hu;U`k}70=&hyM-iM9F&q=9MkVT=o>iEf zjy9WPn5+ImYGVIFrYaA^6)6kUF#J9xwOg;?XwDIkD7L@@<_>M-owp|{V$U|WwN|$A zpf#^X_pOVrJ!gsP%z-xFRmN*^f^T_d6ES)ECB-)Qb;vgLy6g*S(Z*!E+`4RmjErwNlt%o;+muiA&HEmk4yEqtkLx$`6^q}z zm}pjV!GuI=K#-HqOmUUlAzBhq16vDok&$A*=lGXo3!U`|bFBqVs{o}Cm z?S0Bx)4Yx)u)zIai(cOX zlMjGXl~xG>0&RV#6mC9QNynSc8>Mr#P@E~%s>#$&d%fG2^@R%S*+DV4V#ic|jD66y zGYT%e7>Y=7Gc(#xwh%VilB}FJoTyQ3MJmW+o13~Yx}uJ+d=iW?=W4_QLV>7uApJMy zS5-}$GKp`;0-B9h)1)fOycMI?b-ruiu!1+6dXFre--%=>3vZ^R0 zg8R-s>aSPxGwphw3yo7+#mo*!=zbh)-c4im8aS)>gr6q%#YIOo`rwBduLDdG5``|fQI?b_ zcKogIu%l{qT=8rZ%7|zRjJ##P4Bl`T6QdSm>*hav*2H-=O=XKCD%ni>`B+zZa&@a& zH8;lW(7d&mx1B@4Ivvv0YQLYjxf9nFFD~)QUE@YA+pKNMWILfa(Hib5h)vXD$;~D3 z{-TRC`3m{v_}s0sYEchI97`gtLR6ftBu)hLV-{p?#vwFIV#kz^GDinTMBUWY=U!yn z2I|jMDni1!iGuf6m{rEe%ksuUNW-Gm`tn?li(>f1w?plnDbugh72>wZiK>3!W?Y0NnkKI4t`}OYPZPb&i6Rt^{lxq6wAn=G(BZbD@|=>;=yzh?`tZ z^olzw7|&jbiG0#lmxeUN1}7GYx(Oj#N)6KsBV86AQ*xUfhHNlmm}iy|zl?wMk|P8c zyAorxm>USL^1bsHe|`5=4-h{ivVZd$sLVcm=*E>J-?c>J=(tU81^H6Q`DG~i74M&?XBeri7+%{pnzhrN6OZ}Lt)qYjJ zq(L8*17q5Qt#Y?y){etH0l5#_JR}`HW#GIx-JbuLSw( zRAf@Hj1|IL*c816%%i@7{4B@LH+qdtQ!!1Da+?O!D6Sr_fRhRJQfFzKSB}FRR{MAY z3Od31U-9hKyp;qWP?&{ncK*!uEH9|Fuhd+CyVul*rbc(#bS=M*k z61~-p-psbMrrzKOtzWuo9IR}P=zW|^5&f&W9G^EHfB+cbAFeixQR4vJi&L|8(StRS zvs%f!?GIX$8g^7oez;i?V<3tbP-vuoAFL@cAN6^o3(@~$3y z2-)1q0qd!<8y=4t?+N$ip&ShT%T z5r?Jr$nn5oL<)cN!`WnKqBtc^z1A#Krs$cS_74bKvL~<-NwXfXki1$>7~p}@oK2g zFJ*WEi~55@_TuydEp>|RFVP|auJ*~NUJD`xD`;YtlHFXV$fBuwZ_teAqI5yW17f~s zFYYgY0jrm8HwWa|8fqDjE1T5NqPF0n=?-vs=k$AF0C%u4S?B#ep8bsw&ql~>ob@Rw z2OGgM+^19N z2{BU4VTA5oFRJGY5VHDEJE1JEa2tpR7LcS%?~k%7=;g#w%V+D2`X3qedLXv1!2|-n z$xyHLd^q()1kpkt-N>3FXYEJBy#xJrb`L%3o8Tw6*<~yt5^{Yn#(eh_< z+ehwxmQ5u{Io=R&g3DGnRq~Yo+gw9TR^o<6{seibO@0SMSfJjL)kjg$eR8MgKI4P8 zTq{_6z4f$=Cf$&v>2cK33Z3VC4nyB-Qg+r%?(NW&ua!2L;D$~T0zvj?O)dM?82$wYXuKB|p)y7}K>nlYB10=(N9M zJpZfIy~myPu5Gg(B0(pspw-8<`r?wJ)~Nxszc3iQUW$+Ej9Z~2e7lcKAULFE>tlsF z{xQ0dwYx4Y;M8}AiA`?Z&;Ijbb8*21Rx}Am{dwfKd_2#(Tnq4BdU3cL?7RGJPPb$I zYbL$fOMXLr{=2%yA(+Aa#cU4p!QTS~<`;eW0hmmpM;lce$Q>;g#AEolK;`q zJHf2(i?!_aXL$_sa{lRNcWFC55tT`b{NNkcuM{#fbZoTXZ52d+Q?XywH~+goZ{rE} zrS$||%{Xz{$0SI;L9Mo@W6BDud;iwi8p49j!NyV2$)4Bh>5CVD;MBEU33poA2OMus zN#`__HsZJue1Ad04~-Zh_^8mOwOetxN-z->(U}{Wog#Q2E;tGNTNItYMyzGWkZr)h z5sVVpxbc=3$-Jc<>nH2#s}&~{b-mn$%~Wh9ff0Cv!#*IsRr{ukK-a+h$I&H&m?fD+ zh(x(1i)2E9t9wW1dN_Ayyows2L$=)1471le`#C&2SC2WgFIarE>*q{Eo;sF%E)NiA zU1WYg2BZFG(a@iRxKtf7{%enL+6vWpr2q#^{CUJeKgIji7#j2wlB^OxQZ-$i|KbV5 z7hbK&R+5zu3#NOiOQk0*^ubI$=Jx`;)pjnL>)F0PDWsNFA-)YhSyc5uW~UsSnxAy# zXCXOx@^ED93;jUa)!|xs0^DNE`o?F`+O=zPaUJO-r%v)_U#qx-o4!SqthZNlrq_rM zl_LaTN9Ts;$&n{Byoe=}PkuFcb%ISG=Nr69F-D%Tz1hc8_{oOE@sNI?{Em9E;EKl1 zX&F+1{&1i%Z>+9Ove}}#k6Q}RIpi8A8YgNtn7&lCYVi6+lYz9&>?(Y}*p%?{g3kNJ zcya&P(uAL_jdXpF|2Z0efk{5nDAVbYaBk1)9SKY-)DrYGNvnE$k^;m|?mGuipScdO zzEf30&GpO)5o!+>j?i01{m^g}zg$$4PU~x-b9dkdJ0#LWtl@TN$k;>Ov{g%!qz9Zm z^};3&c6-T#&7CI!#QyTHGj3-Au_($_&y%yb265*tvQGZ$hFY(vxXws!)VGEV6T4ii zlA@(_({j_Z7?~1`32`Xx&jR6btN2=WFMf z8uhw0J@x2}?RY3Gb(vp_H#30GqNN4|1&!$S`0Tpo&WFyW6DC&G3`;&h>(iU1w3BA) zdeb7@M3^Im7-v_~7C=5uHJt`U*2xc5JzU`9i9^Zv^a3ZXTeh=PN4Mfs4`5@v&b5Aw z;)0XLdir7r?_Y+~`^Ol#kaV?QiRjC_r|p6!pVPoKk{R4FhR#lz%Q`TQ*#Vb)7E|gT zKf$eS`)W71Uvb?pN8CwCn&yXSf~+fSOTBmJ7M9i;n@+_8d^*^F`>lp1$k6-1ZT~XvC!5>j zO4z%|VNPRELF#ZoDj$x=(C*1Zhek!UPh0GQrP&A}3w^E?)}Cz3H!^-S3LOQ7de_U` zHb=`ARX)45>**yw!pfsYb}mZ3s96+W(1-+1_5=v|nOGgA{ta#`Nm ziRnmG`)4h6_u=>#sribTtXDcMi@a(l1uyB(6Z9X9pmdU96VJ2~Y`V*sH= z;}=f)ENkc4&c#})7G299^Vxbckd7c<^@*2jO=GRv40gUGoF$3os$E_kI-(q`Il<&g?zy_)>g5iVEaEff1M+9?Lih z%&FT}aP+TbbzQDPF0e^xY!~CY!M&n)0lqh)((-q$?&Sy&Rfzp%f7Mh9ISufcIP;V* zJQC<8#v%%-(4y?BGd-U4&_^m049Rlv>5hw#T=FO@!HEOQd_!qPJPRQJZ>QMW@l9ky zGt45Le{I70rI2`{+?_sOVHt|a1r`NHkB0Yvg>%E~w{D^(MtV** zs2b^epE4rOK*W;PNr`K1y|KVqat8F3Lj99zC|c1ZxT?~2N25xoD|IeH>Emqev|hF* zXu(?vW6hndKai$p`UDfQ8s%Y1;-~;@_#i)Ju%k3w`Qb{#iI3#_N%xSz$>HFoU!J7R z#;S2RP=($4u2icdv40zG@uaeImQlHu<$+`D$Hq;Ttc&o)K^9uJg(mujQjWlTOAZ;g z!eAz_gW+n_Z9S5Qu{q)O1^%n_#F7`&Lk+JGFMf8eQ z^tR>wa|@1jC-~W=ZQe7%ZzH2#^xcLT~bVVgd29c`vsRmKHY8WExY~a8|Qjv1+e-9XX?pI~{l~9+74zsEy`Cgr$L5s{$-?)R zgh85zXXhmZ{TI0|DqK6WAHKmAFmbyKZWqr)#( zxp^|rB|lo;{+_7-;a!7s=Ih4QL-0TM6qd)Z;-6gL#y#T40fN{Y9wV^jci$vq>6xEW z>40&ZtowdzQ{5S6yjo+!UypQS%vplsuk&{>Ti)keZy5fDBMdQCq+|S9TOsUU}W@ zNZjp3<=83en}5a*JucayNh!k+yI*sMB1xmhxPucTxmake?C2wl6u=o@8%~z&das36`N9)dlZ$~bGx;wrsEB@E zq%Xxn7*r|RccZw2Qz7AUyuq?sopf!#Ur+!NB|7OKY(UTe|5)+9PS)*`_wLu!ms=s# zZ4azdmzETe^OH)Led7x9NL8Wn>y8Sg%RDXTwWqY3TbAhCK#%slltz}4_jOlxY-*1>pmr@4jM z%|u>jiYsdK?#ofmOg%{Gx)9*g>H&sr#9_4- z(kgwPwc{_(6&%*QF(l&N+9I6hB-Ht5VUn(W zDUA4C(GLu!3$+%{qZ=Fc?b`byTa~~3td`fZ+1u%zC53saT(IrH*yGCSRDDgW9kCdV zLn>4+6cJq1zx;75xAsXF)I+UjZKq%1$&cPcNkd825eF@^wwx7R^=kWKgi6m@N>M*5 zh*e`RUwxl(Rqk8*1(=hXzCMDQ8&rmsO-P@KjPR|-6st0?Qq>xVK9!FtgUy6{VSd)f zX|Qo*!yg=3$$Rt(*M&y$vu!u8U|7x`B%~D<8U0i6XqbXQs$!`Yk z6!|d!q%O3c1iSLdAwBif>q%qcMgFj}H^q}Mw}(%2(h8IR_&%MG2^u(GWanwTx=W7o zv98?Vv&y2^-7Q{sCX1SK1sK>5JKajDNc+cw4`WNzMr6T*-O2_H$W9MdAfb ztOL$Cn%rDq#sz|{ooyZS2ows2Coyq@=g}E z5DU=geD8xi{RC+ktsAC2Vp3TcsZChmW_XU#@yO3^IbMF-$`z}M3gsSH=BoI1bwK}v zlfI?uzJ9-i0vOQZEn&syuLYs`E)=$;S^Ws;>u&8pyvtb8hAnurkLK%h10R<{T3*|? z-d+_M@f$EE+bvm;KYS4MOURjtto;cV*v&Z00L5_UxwkDut(C4Ky7uzvfEB+hj<=MwZ#Lwb zIku4ZLSzvf7sFhTi+LGNWjUuo+rH5<)x*2)Fi#&R&UJd~j4YLt3B|8b1ZAq+$T#$) z9o$1SpNHxQ?d?16xwNJs)a;|{$lD|&-&f?z6X3OXvg=k;T<-1OZsGdqT`)xbSyt(O zYs?SA^jufxdUs{5#mmE=_Jj9;z!@_NZdku|l+^BX%PB}W_@zg5ZD1S!l=6j2YADwK zMT9ZTrM^AI|V$RZZ4^PkuRVm1=Xpj1ov{bzy$cH6@q(02b?A?FWG9DN&} zjZTJT%3J0F)x;PM=PRHc+R;PA;dY_|nqC+#iG{myoU65|~ zxp-hUvn`H5WxjJ5Vg&j8CxoQim&_25W@FAKe$o6@7(j=0w$emz&m>;q4&F6-jsdj^ z1O9wL*}b~d-QirZw;pAxa3Rg)x>gTwt(ar=@#{>%yL`^Dme>ARCT27XqzQ?)k~BB_ zmDP&7|E@*t<_5+r?;k%Ze`46MPba4p?|tX0dX~A2qfu=n{_M zMzLVyyM6}X|7^m$Ka?Un`mLfuHlGeE#HT{p9@|I1$tz!PY~yT$Y)Kn#`ZRa=aAf)J zm{zbXoC662!@W5=cFlY&YzB9)C_3+`Z{+a#kEM?dpFLNlN_O*w#2)ub)a3D(Bh#jL z?6qQrLDQjKd985H%|qjhr0gd1V?++QlEkKDO%Fl;ls;=6+F_I}IcY0{0c1kX3CWb$ zTVlz9l*ckl1y|m*7nA3+OPiiOd(G`+ zCBzvU^WU${ywnwQ$ghyRdj{)=>54ojK3Fc`u=@Sqz2!5jL8RH0Jih|@v+dBQAH%akL+Z}Ev2V&I^san`%>MIcGa|`QzbtKBIpubH zo5l^%X;@4B8)SfH0PoRWOviYuBBN4ve9QZi1?#`_F8^N2dr}fSSzDg^&WN(MPNr-H zr?fQNdmByrhEDlsPve`67aw`OzZb$RN_ZM00RxekY`#Uzl?0UQ4(g<;gH!R?-H5{NGAVjvC=RtTSXr}pQK^9xiLpH zSp>rNw-&PNOgMMUvzz!SjXmKWDpVAa4t*fxP-`*#Ynz|H8|ZKZiWBz~3;=HMbNDNL zV0n|uOp%urP)(kGKl|Igam}~rxJhMOqRY=zm74&tW;Sx50&Zvb`4iEdS_kxJ!`j{# z(hkFb>hj58(m$`Y#PQ1u_7}8g7L4sI{qiCq3BG+c=u4Td%9rzQA z%a6z3h9@#dYTH5UqNyg8{XcHTMC=5rzAE+osJ`wiVn8-<=#@{Ca8jR4MLz5h+;M95 zHHbCh-?{$d@l^wuA{g_A7B&y~SLp7zE({#))l2?PU?`6ex^ z%CoDx@ocg@;R~oj0qYYa#op%wxG*@eL<~xfN@{IIMzrozT_LIXO#9eg-$sq| z@85K#P=d11tgC7djEh7mQHk!j%suoV=s(KiShQE^zEDxd46C6|NGwKcr1zZbO5J%(eQY}A{ z?fJ}zx`X>l_e@m=Ag64dn(z!nglZ**OFe==~=nNFhbn9nKl6P3F=s+6`6* zm~0p+WYN||gI!MkI4^C{v=pDk0z}RYPM`frJ{JX*Z;@S5qjtCAq*J#0EJ%1q0wFV* zxqdaCWxZF4>)p6A+me>#zsZX}`7_%n^nEoLar-A`APMJhdqwp282SO% zy9L1j->WycXz<57SBMW`Tmk7U>oBFJW4ABlz8MwaoTMFMSJoYy@)`bpflC6H z1TG0&61XIAN#K&eC4oxa7o~jz$Jl80+$3X3H;9lPSCOJUq~q0Nf;bE z31)IMG!L1>DU+7`RM&21Yyb0E%>TKe%O@@gToSk>a7o~jz$Jl80+$3X30xAmB=G;2 zz;FGtP14jyRg)cU&|BS9wVkFzy2_is%~Q2o{E~y94=Mvp65a_sKfOhVJ0W}V@$9_= om%6X(Rae&B@7%XYemRi6a}T)!61dm(x7yF2Xgw}hdG-EZ0C&S=M*si- diff --git a/fonts/X11/AUTHORS.txt b/fonts/X11/AUTHORS.txt deleted file mode 100755 index 71316c53..00000000 --- a/fonts/X11/AUTHORS.txt +++ /dev/null @@ -1,42 +0,0 @@ -The identity of the designer(s) of the original ASCII repertoire and -the later Latin-1 extension of the misc-fixed BDF fonts appears to -have been lost in history. (It is likely that many of these 7-bit -ASCII fonts were created in the early or mid 1980s as part of MIT's -Project Athena, or at its industrial partner, DEC.) - -In 1997, Markus Kuhn at the University of Cambridge Computer -Laboratory initiated and headed a project to extend the misc-fixed BDF -fonts to as large a subset of Unicode/ISO 10646 as is feasible for -each of the available font sizes, as part of a wider effort to -encourage users of POSIX systems to migrate from ISO 8859 to UTF-8. - -Robert Brady and Birger Langkjer - contributed thousands of glyphs and made -very substantial contributions and improvements on almost all fonts. -Constantine Stathopoulos contributed all the -Greek characters. Markus Kuhn did -most 6x13 glyphs and the italic fonts and provided many more glyphs, -coordination, and quality assurance for the other fonts. Mark Leisher - contributed to 6x13 Armenian, Georgian, the -first version of Latin Extended Block A and some Cyrillic. Serge V. -Vakulenko donated the original Cyrillic glyphs -from his 6x13 ISO 8859-5 font. Nozomi Ytow -contributed 6x13 halfwidth Katakana. Henning Brunzel - contributed glyphs to 10x20.bdf. Theppitak -Karoonboonyanan contributed Thai for 7x13, -7x13B, 7x13O, 7x14, 7x14B, 8x13, 8x13B, 8x13O, 9x15, 9x15B, and 10x20. -Karl Koehler contributed Arabic to 9x15, -9x15B, and 10x20 and Roozbeh Pournader and -Behdad Esfahbod revised and extended Arabic in 10x20. Raphael Finkel - revised Hebrew/Yiddish in 10x20. Jungshik Shin - prepared 18x18ko.bdf. Won-kyu Park - prepared the Hangul glyphs used in 12x13ja. -Janne V. Kujala contributed 4x6. Daniel Yacob - revised some Ethiopic glyphs. Ted Zlatanov - did some 7x14. Mikael Öhman -worked on 6x12. - -The fonts are still maintained by Markus Kuhn and the original -distribution can be found at: - - http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html diff --git a/fonts/X11/LICENCES.txt b/fonts/X11/LICENCES.txt deleted file mode 100755 index 0ddf13db..00000000 --- a/fonts/X11/LICENCES.txt +++ /dev/null @@ -1,42 +0,0 @@ -All the fonts here are public domain with the exceptions of clR6x12.png and helvR12_14x15.png - -== clR6x12.png == - Copyright 1989 Dale Schumacher, dal@syntel.mn.org - 399 Beacon Ave. - St. Paul, MN 55104-3527 - - Permission to use, copy, modify, and distribute this software and - its documentation for any purpose and without fee is hereby - granted, provided that the above copyright notice appear in all - copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - Dale Schumacher not be used in advertising or publicity pertaining to - distribution of the software without specific, written prior - permission. Dale Schumacher makes no representations about the - suitability of this software for any purpose. It is provided "as - is" without express or implied warranty. - - - Modified by Robert Brady, - - -== helvR12_14x15.png == - Copyright 1984-1989, 1994 Adobe Systems Incorporated. - Copyright 1988, 1994 Digital Equipment Corporation. - - Adobe is a trademark of Adobe Systems Incorporated which may be - registered in certain jurisdictions. - Permission to use these trademarks is hereby granted only in - association with the images described in this file. - - Permission to use, copy, modify, distribute and sell this software - and its documentation for any purpose and without fee is hereby - granted, provided that the above copyright notices appear in all - copies and that both those copyright notices and this permission - notice appear in supporting documentation, and that the names of - Adobe Systems and Digital Equipment Corporation not be used in - advertising or publicity pertaining to distribution of the software - without specific, written prior permission. Adobe Systems and - Digital Equipment Corporation make no representations about the - suitability of this software for any purpose. It is provided "as - is" without express or implied warranty. diff --git a/fonts/X11/README-TDL.txt b/fonts/X11/README-TDL.txt deleted file mode 100755 index 692a4399..00000000 --- a/fonts/X11/README-TDL.txt +++ /dev/null @@ -1,10 +0,0 @@ -To use these unicode fonts with python-tdl you should call tdl.setFont with only the filename leaving all other parameters at the defaults. Remember to do this before the call to tdl.init. If using libtcod then note that each tileset has 64 columns and 1024 rows. - -unifont_16x16.png is the recommended font with the best unicode support. - -Unicode support varies across the rest of the fonts. This is detailed in README-X11.txt with some fonts meeting better support targets than others. - -After unifont the fonts with the best unicode support are: - 6x13.png 8x13.png 9x15.png 9x18.png 10x20.png - -Also note that libtcod will be put under stress when using an extra large tileset such as 10x20.png; When decompressed the 10x20.png image will take around 40MB of video memory. Modern graphic cards can handle this can get better compatibility by using the SDL rasterizer instead of OPENGL. diff --git a/fonts/X11/README-X11.txt b/fonts/X11/README-X11.txt deleted file mode 100755 index d4f407ef..00000000 --- a/fonts/X11/README-X11.txt +++ /dev/null @@ -1,369 +0,0 @@ - -Unicode versions of the X11 "misc-fixed-*" fonts ------------------------------------------------- - -Markus Kuhn -- 2008-04-21 - - -This package contains the X Window System bitmap fonts - - -Misc-Fixed-*-*-*--*-*-*-*-C-*-ISO10646-1 - -These are Unicode (ISO 10646-1) extensions of the classic ISO 8859-1 -X11 terminal fonts that are widely used with many X11 applications -such as xterm, emacs, etc. - -COVERAGE --------- - -None of these fonts covers Unicode completely. Complete coverage -simply would not make much sense here. Unicode 5.1 contains over -100000 characters, and the large majority of them are -Chinese/Japanese/Korean Han ideographs (~70000) and Korean Hangul -Syllables (~11000) that cannot adequately be displayed in the small -pixel sizes of the fixed fonts. Similarly, Arabic characters are -difficult to fit nicely together with European characters into the -fixed character cells and X11 lacks the ligature substitution -mechanisms required for using Indic scripts. - -Therefore these fonts primarily attempt to cover Unicode subsets that -fit together with European scripts. This includes the Latin, Greek, -Cyrillic, Armenian, Georgian, and Hebrew scripts, plus a lot of -linguistic, technical and mathematical symbols. Some of the fixed -fonts now also cover Arabic, Thai, Ethiopian, halfwidth Katakana, and -some other non-European scripts. - -We have defined 3 different target character repertoires (ISO 10646-1 -subsets) that the various fonts were checked against for minimal -guaranteed coverage: - - TARGET1 617 characters - Covers all characters of ISO 8859 part 1-5,7-10,13-16, - CEN MES-1, ISO 6937, Microsoft CP1251/CP1252, DEC VT100 - graphics symbols, and the replacement and default - character. It is intended for small bold, italic, and - proportional fonts, for which adding block graphics - characters would make little sense. This repertoire - covers the following ISO 10646-1:2000 collections - completely: 1-3, 8, 12. - - TARGET2 886 characters - Adds to TARGET1 the characters of the Adobe/Microsoft - Windows Glyph List 4 (WGL4), plus a selected set of - mathematical characters (covering most of ISO 31-11 - high-school level math symbols) and some combining - characters. It is intended to be covered by all normal - "fixed" fonts and covers all European IBM, Microsoft, and - Macintosh character sets. This repertoire covers the - following ISO 10646-1:2000 (including Amd 1:2002) - collections completely: 1-3, 8, 12, 33, 45. - - TARGET3 3282 characters - - Adds to TARGET2 all characters of all European scripts - (Latin, Greek, Cyrillic, Armenian, Georgian), all - phonetic alphabet symbols, many mathematical symbols - (including all those available in LaTeX), all typographic - punctuation, all box-drawing characters, control code - pictures, graphical shapes and some more that you would - expect in a very comprehensive Unicode 4.0 font for - European users. It is intended for some of the more - useful and more widely used normal "fixed" fonts. This - repertoire is, with two exceptions, a superset of all - graphical characters in CEN MES-3A and covers the - following ISO 10646-1:2000 (including Amd 1:2002) - collections completely: 1-12, 27, 30-31, 32 (only - graphical characters), 33-42, 44-47, 63, 65, 70 (only - graphical characters). - - [The two MES-3A characters deliberately omitted are the - angle bracket characters U+2329 and U+232A. ISO and CEN - appears to have included these into collection 40 and - MES-3A by accident, because there they are the only - characters in the Unicode EastAsianWidth "wide" class.] - -CURRENT STATUS: - - 6x13.bdf 8x13.bdf 9x15.bdf 9x18.bdf 10x20.bdf: - - Complete (TARGET3 reached and checked) - - 5x7.bdf 5x8.bdf 6x9.bdf 6x10.bdf 6x12.bdf 7x13.bdf 7x14.bdf clR6x12.bdf: - - Complete (TARGET2 reached and checked) - - 6x13B.bdf 7x13B.bdf 7x14B.bdf 8x13B.bdf 9x15B.bdf 9x18B.bdf: - - Complete (TARGET1 reached and checked) - - 6x13O.bdf 7x13O.bdf 8x13O.bdf - - Complete (TARGET1 minus Hebrew and block graphics) - -[None of the above fonts contains any character that has in Unicode -the East Asian Width Property "W" or "F" assigned. This way, the -desired combination of "half-width" and "full-width" glyphs can be -achieved easily. Most font mechanisms display a character that is not -covered in a font by using a glyph from another font that appears -later in a priority list, which can be arranged to be a "full-width" -font.] - -The supplement package - - http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts-asian.tar.gz - -contains the following additional square fonts with Han characters for -East Asian users: - - 12x13ja.bdf: - - Covers TARGET2, JIS X 0208, Hangul, and a few more. This font is - primarily intended to provide Japanese full-width Hiragana, - Katakana, and Kanji for applications that take the remaining - ("halfwidth") characters from 6x13.bdf. The Greek lowercase - characters in it are still a bit ugly and will need some work. - - 18x18ja.bdf: - - Covers all JIS X 0208, JIS X 0212, GB 2312-80, KS X 1001:1992, - ISO 8859-1,2,3,4,5,7,9,10,15, CP437, CP850 and CP1252 characters, - plus a few more, where priority was given to Japanese han style - variants. This font should have everything needed to cover the - full ISO-2022-JP-2 (RFC 1554) repertoire. This font is primarily - intended to provide Japanese full-width Hiragana, Katakana, and - Kanji for applications that take the remaining ("halfwidth") - characters from 9x18.bdf. - - 18x18ko.bdf: - - Covers the same repertoire as 18x18ja plus full coverage of all - Hangul syllables and priority was given to Hanja glyphs in the - unified CJK area as they are used for writing Korean. - -The 9x18 and 6x12 fonts are recommended for use with overstriking -combining characters. - -Bug reports, suggestions for improvement, and especially contributed -extensions are very welcome! - -INSTALLATION ------------- - -You install the fonts under Unix roughly like this (details depending -on your system of course): - -System-wide installation (root access required): - - cd submission/ - make - su - mv -b *.pcf.gz /usr/lib/X11/fonts/misc/ - cd /usr/lib/X11/fonts/misc/ - mkfontdir - xset fp rehash - -Alternative: Installation in your private user directory: - - cd submission/ - make - mkdir -p ~/local/lib/X11/fonts/ - mv *.pcf.gz ~/local/lib/X11/fonts/ - cd ~/local/lib/X11/fonts/ - mkfontdir - xset +fp ~/local/lib/X11/fonts (put this last line also in ~/.xinitrc) - -Now you can have a look at say the 6x13 font with the command - - xfd -fn '-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1' - -If you want to have short names for the Unicode fonts, you can also -append the fonts.alias file to that in the directory where you install -the fonts, call "mkfontdir" and "xset fp rehash" again, and then you -can also write - - xfd -fn 6x13U - -Note: If you use an old version of xfontsel, you might notice that it -treats every font that contains characters >0x00ff as a Japanese JIS -font and therefore selects inappropriate sample characters for display -of ISO 10646-1 fonts. An updated xfontsel version with this bug fixed -comes with XFree86 4.0 / X11R6.8 or newer. - -If you use the Exceed X server on Microsoft Windows, then you will -have to convert the BDF files into Microsoft FON files using the -"Compile Fonts" function of Exceed xconfig. See the file exceed.txt -for more information. - -There is one significant efficiency problem that X11R6 has with the -sparsely populated ISO10646-1 fonts. X11 transmits and allocates 12 -bytes with the XFontStruct data structure for the difference between -the lowest and the highest code value found in a font, no matter -whether the code positions in between are used for characters or not. -Even a tiny font that contains only two glyphs at positions 0x0000 and -0xfffd causes 12 bytes * 65534 codes = 786 kbytes to be requested and -stored by the client. Since all the ISO10646-1 BDF files provided in -this package contain characters in the U+00xx (ASCII) and U+ffxx -(ligatures, etc.) range, all of them would result in 786 kbyte large -XCharStruct arrays in the per_char array of the corresponding -XFontStruct (even for CharCell fonts!) when loaded by an X client. -Until this problem is fixed by extending the X11 font protocol and -implementation, non-CJK ISO10646-1 fonts that lack the (anyway not -very interesting) characters above U+31FF seem to be the best -compromise. The bdftruncate.pl program in this package can be used to -deactivate any glyphs above a threshold code value in BDF files. This -way, we get relatively memory-economic ISO10646-1 fonts that cause -"only" 150 kbyte large XCharStruct arrays to be allocated. The -deactivated glyphs are still present in the BDF files, but with an -encoding value of -1 that causes them to be ignored. - -The ISO10646-1 fonts can not only be used directly by Unicode aware -software, they can also be used to create any 8-bit font. The -ucs2any.pl Perl script converts a ISO10646-1 BDF font into a BDF font -file with some different encoding. For instance the command - - perl ucs2any.pl 6x13.bdf MAPPINGS/8859-7.TXT ISO8859-7 - -will generate the file 6x13-ISO8859-7.bdf according to the 8859-7.TXT -Latin/Greek mapping table, which available from -. [The shell script -./map_fonts automatically generates a subdirectory derived-fonts/ with -many *.bdf and *.pcf.gz 8-bit versions of all the --misc-fixed-*-iso10646-1 fonts.] - -When you do a "make" in the submission/ subdirectory as suggested in -the installation instructions above, this will generate exactly the -set of fonts that have been submitted to the XFree86 project for -inclusion into XFree86 4.0. These consists of all the ISO10646-1 fonts -processed with "bdftruncate.pl U+3200" plus a selected set of derived -8-bit fonts generated with ucs2any.pl. - -Every font comes with a *.repertoire-utf8 file that lists all the -characters in this font. - - -CONTRIBUTING ------------- - -If you want to help me in extending or improving the fonts, or if you -want to start your own ISO 10646-1 font project, you will have to edit -BDF font files. This is most comfortably done with the gbdfed font -editor (version 1.3 or higher), which is available from - - http://crl.nmsu.edu/~mleisher/gbdfed.html - -Once you are familiar with gbdfed, you will notice that it is no -problem to design up to 100 nice characters per hour (even more if -only placing accents is involved). - -Information about other X11 font tools and Unicode fonts for X11 in -general can be found on - - http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html - -The latest version of this package is available from - - http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz - -If you want to contribute, then get the very latest version of this -package, check which glyphs are still missing or inappropriate for -your needs, and send me whatever you had the time to add and fix. Just -email me the extended BDF-files back, or even better, send me a patch -file of what you changed. The best way of preparing a patch file is - - ./touch_id newfile.bdf - diff -d -u -F STARTCHAR oldfile.bdf newfile.bdf >file.diff - -which ensures that the patch file preserves information about which -exact version you worked on and what character each "hunk" changes. - -I will try to update this packet on a daily basis. By sending me -extensions to these fonts, you agree that the resulting improved font -files will remain in the public domain for everyone's free use. Always -make sure to load the very latest version of the package immediately -before your start, and send me your results as soon as you are done, -in order to avoid revision overlaps with other contributors. - -Please try to be careful with the glyphs you generate: - - - Always look first at existing similar characters in order to - preserve a consistent look and feel for the entire font and - within the font family. For block graphics characters and geometric - symbols, take care of correct alignment. - - - Read issues.txt, which contains some design hints for certain - characters. - - - All characters of CharCell (C) fonts must strictly fit into - the pixel matrix and absolutely no out-of-box ink is allowed. - - - The character cells will be displayed directly next to each other, - without any additional pixels in between. Therefore, always make - sure that at least the rightmost pixel column remains white, as - otherwise letters will stick together, except of course for - characters -- like Arabic or block graphics -- that are supposed to - stick together. - - - Place accents as low as possible on the Latin characters. - - - Try to keep the shape of accents consistent among each other and - with the combining characters in the U+03xx range. - - - Use gbdfed only to edit the BDF file directly and do not import - the font that you want to edit from the X server. Use gbdfed 1.3 - or higher. - - - The glyph names should be the Adobe names for Unicode characters - defined at - - http://www.adobe.com/devnet/opentype/archives/glyph.html - - which gbdfed can set automatically. To make the Edit/Rename Glyphs/ - Adobe Names function work, you have to download the file - - http://www.adobe.com/devnet/opentype/archives/glyphlist.txt - - and configure its location either in Edit/Preferences/Editing Options/ - Adobe Glyph List, or as "adobe_name_file" in "~/.gbdfed". - - - Be careful to not change the FONTBOUNDINGBOX box accidentally in - a patch. - -You should have a copy of the ISO 10646 standard - - ISO/IEC 10646:2003, Information technology -- Universal - Multiple-Octet Coded Character Set (UCS), - International Organization for Standardization, Geneva, 2003. - http://standards.iso.org/ittf/PubliclyAvailableStandards/ - -and/or the Unicode 5.0 book: - - The Unicode Consortium: The Unicode Standard, Version 5.0, - Reading, MA, Addison-Wesley, 2006, - ISBN 9780321480910. - http://www.amazon.com/exec/obidos/ASIN/0321480910/mgk25 - -All these fonts are from time to time resubmitted to the X.Org -project, XFree86 (they have been in there since XFree86 4.0), and to -other X server developers for inclusion into their normal X11 -distributions. - -Starting with XFree86 4.0, xterm has included UTF-8 support. This -version is also available from - - http://dickey.his.com/xterm/xterm.html - -Please make the developer of your favourite software aware of the -UTF-8 definition in RFC 2279 and of the existence of this font -collection. For more information on how to use UTF-8, please check out - - http://www.cl.cam.ac.uk/~mgk25/unicode.html - ftp://ftp.ilog.fr/pub/Users/haible/utf8/Unicode-HOWTO.html - -where you will also find information on joining the -linux-utf8@nl.linux.org mailing list. - -A number of UTF-8 example text files can be found in the examples/ -subdirectory or on - - http://www.cl.cam.ac.uk/~mgk25/ucs/examples/ - diff --git a/fonts/X11/README.md b/fonts/X11/README.md new file mode 100644 index 00000000..621dac8d --- /dev/null +++ b/fonts/X11/README.md @@ -0,0 +1,14 @@ +To use these BDF Unicode fonts with python-tcod you should use the +`tcod.tileset` module: + +```python +import tcod +import tcod.tileset + +# Load the BDF file as the active tileset. +tcod.tileset.set_default(tcod.tileset.load_bdf("file_to_load.bdf")) + +# Start python-tcod normally. +with tcod.console_init_root(...) as root_console: + ... +``` diff --git a/fonts/X11/TARGET1.txt b/fonts/X11/TARGET1.txt deleted file mode 100755 index da42e224..00000000 --- a/fonts/X11/TARGET1.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Target repertoire for bold/italic fixed character sets -# (CP1252, MES-1, ISO 8859-1,2,3,4,5,7,8,9,10,14,15, etc.) - -# Plane 00 -# Rows Positions (Cells) - - 00 20-7E A0-FF - 01 00-7F 8F 92 - 02 18-1B 59 C6-C7 D8-DD - 03 74-75 7A 7E 84-8A 8C 8E-A1 A3-CE - 04 01-0C 0E-4F 51-5C 5E-5F 90-91 - 05 D0-EA - 1E 02-03 0A-0B 1E-1F 40-41 56-57 60-61 6A-6B 80-85 F2-F3 - 20 10-22 26 30 39-3A AC AF - 21 16 22 26 5B-5E 90-93 - 22 60 64-65 - 23 BA-BD - 24 09-0D 24 - 25 00 02 0C 10 14 18 1C 24 2C 34 3C 92 C6 - 26 6A - FF FD - -# Number of characters in above table: 617 diff --git a/fonts/X11/TARGET2.txt b/fonts/X11/TARGET2.txt deleted file mode 100755 index 6c1de726..00000000 --- a/fonts/X11/TARGET2.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Target repertoire for normal fixed character sets -# (TARGET1, WGL4, some math, some combining) - -# Plane 00 -# Rows Positions (Cells) - - 00 20-7E A0-FF - 01 00-7F 8F 92 FA-FF - 02 18-1B 59 C6-C7 C9 D8-DD - 03 00-11 74-75 7A 7E 84-8A 8C 8E-A1 A3-CE D1 D5-D6 F1 - 04 01-0C 0E-4F 51-5C 5E-5F 90-91 - 05 D0-EA - 1E 02-03 0A-0B 1E-1F 40-41 56-57 60-61 6A-6B 80-85 F2-F3 - 20 10-22 26 30 32-34 39-3A 3C 3E 44 70-71 74-8E A3-A4 A7 AC AF D0-D7 - 21 02 05 13 15-16 1A 1D 22 24 26 2E 5B-5E 90-95 A4-A8 D0-D5 - 22 00-09 0B-0C 0F 11-13 15 18-1A 1D-1F 21 24-2B 2E 3C 43 45 48-49 - 22 59 5F-62 64-65 6A-6B 82-8B 95 97 A4-A7 C2-C3 C5 - 23 00 02 08-0B 10 20-21 BA-BD - 24 09-0D 24 - 25 00 02 0C 10 14 18 1C 24 2C 34 3C 4C-73 80-A1 AA-AC B2-B3 BA BC - 25 C4 C6 CA-CB CF D8-D9 E6 - 26 3A-3C 40 42 60 63 65-66 6A-6B - 27 E8-E9 - FB 01-02 - FF FD - -# Number of characters in above table: 886 diff --git a/fonts/X11/TARGET3.txt b/fonts/X11/TARGET3.txt deleted file mode 100755 index 92e74f26..00000000 --- a/fonts/X11/TARGET3.txt +++ /dev/null @@ -1,30 +0,0 @@ -# Target repertoire for comprehensive fixed character sets - -# Plane 00 -# Rows Positions (Cells) - - 00 20-7E A0-FF - 01 00-FF - 02 00-FF - 03 00-77 7A-7E 84-8A 8C 8E-A1 A3-FF - 04 00-FF - 05 00-23 31-56 59-5F 61-87 89-8A B0-C7 D0-EA F0-F4 - 10 D0-FC - 1E 00-FF - 1F 00-15 18-1D 20-45 48-4D 50-57 59 5B 5D 5F-7D 80-B4 B6-C4 C6-D3 - 1F D6-DB DD-EF F2-F4 F6-FE - 20 00-0A 10-27 2F-64 70-71 74-8E 90-94 A0-B5 D0-F0 - 21 00-4F 53-88 90-FF - 22 00-FF - 23 00-28 2B-E7 - 24 00-26 40-4A - 25 00-FF - 26 00-9D A0-BC C0-C3 - 27 E6-EB F5-FF - 2A 00-06 1D 3F - 30 3F - FB 00-06 13-17 1D-36 38-3C 3E 40-41 43-44 46-4F - FE 20-26 - FF 61-9F FD - -# Number of characters in above table: 3480 diff --git a/fonts/X11/bdf/bdf2png.py b/fonts/X11/bdf/bdf2png.py deleted file mode 100755 index 2d1902bb..00000000 --- a/fonts/X11/bdf/bdf2png.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/python -""" - This script converts bdf files into png Unicode tilesets for use with - programs such as libtcod or python-tdl. - - Requires scipy, numpy, and PIL. Run from the command line. -""" - -from __future__ import division - -import sys -import os - -import re -import math -import itertools -import glob -import argparse -import multiprocessing - -import scipy.ndimage -import scipy.misc -try: - scipy.misc.imsave -except AttributeError: - raise SystemExit('Must have python PIL installed') -import numpy - -class Glyph: - - def __init__(self, data, bbox): - "Make a new glyph with the data between STARTCHAR and ENDCHAR" - if verbose: - print(data) - # get character index - self.encoding = int(re.search('ENCODING ([0-9-]+)', data).groups()[0]) - if self.encoding < 0: - # I ran into a -1 encoding once, not sure what to do with it - self.encoding += 65536 # just put it at the end I guess - - # get local bbox - match = re.search('\nBBX ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)', data) - if match: - gbbox = [int(i) for i in match.groups()] - else: - gbbox = bbox - self.font_bbox = bbox - self.bbox = gbbox - self.width, self.height = self.bbox[:2] - - # get bitmap - match = re.search('\nBITMAP *\n([0-9A-F\n]*)', data, re.IGNORECASE) - self.bitmap = numpy.empty([self.height, self.width], bool) - if self.height == self.width == 0: - return - for y,hexcode in enumerate(match.groups()[0].split('\n')): - for x, bit in self.parseBits(hexcode, self.width): - self.bitmap[y,x] = bit - - self.sizeAdjust() - - def sizeAdjust(self): - """If the glyph is bigger than the font (because the user set it smaller) - this should be able to shorten the size""" - font_width, font_height = self.font_bbox[:2] - self.width = min(self.width, font_width) - self.height = min(self.height, font_height) - self.bbox[:2] = self.width, self.height - - self.crop() - - def crop(self): - self.bitmap = self.bitmap[-self.height:, :self.width] - - def zoom(self): - h, w = self.bitmap.shape - zoom = [self.height / h, self.width / w] - self.bitmap = scipy.ndimage.zoom(self.bitmap, zoom, output=float) - - def blit(self, image, x, y): - """blit to the image array""" - # adjust the position with the local bbox - x += self.font_bbox[2] - self.bbox[2] - y += self.font_bbox[3] - self.bbox[3] - x += self.font_bbox[0] - self.bbox[0] - y += self.font_bbox[1] - self.bbox[1] - image[y:y+self.height, x:x+self.width] = self.bitmap * 255 - - def parseBits(self, hexcode, width): - """enumerate over bits in a line of data""" - bitarray = [] - for byte in hexcode[::-1]: - bits = int(byte, 16) - for x in range(4): - bitarray.append(bool((2 ** x) & bits)) - bitarray = bitarray[::-1] - return enumerate(bitarray[:width]) - -def glyphThreadInit(verbose_): - # pass verbose to threads - global verbose - verbose = verbose_ - -def glyphThread(args): - # split args to Glyph - return Glyph(*args) - -def convert(filename): - print('Converting %s...' % filename) - bdf = open(filename, 'r').read() - - # name the output file - outfile = os.path.basename(filename) - if '.' in outfile: - outfile = outfile.rsplit('.', 1)[0] + '.png' - - # print out comments - for comment in re.findall('\nCOMMENT (.*)', bdf): - print(comment) - # and copyright - match = re.search('\n(COPYRIGHT ".*")', bdf) - if match: - print(match.groups()[0]) - - # get bounding box - match = re.search('\nFONTBOUNDINGBOX ([0-9-]+) ([0-9-]+) ([0-9-]+) ([0-9-]+)', bdf) - bbox = [int(i) for i in match.groups()] - if args.font_size: - bbox = args.font_size + bbox[2:] - fontWidth, fontHeight, fontOffsetX, fontOffsetY = bbox - print('Font size: %ix%i' % (fontWidth, fontHeight)) - print('Font offset: %i,%i' % (fontOffsetX, fontOffsetY)) - - # generate glyphs - pool = multiprocessing.Pool(args.threads, glyphThreadInit, (verbose,)) - glyphData = re.findall('\nSTARTCHAR [^\n]*\n(.*?)\nENDCHAR', bdf, re.DOTALL) - glyphTotal = len(glyphData) - print('Found %i glyphs' % glyphTotal) - sys.stdout.write('please wait...') - glyphs = pool.map(glyphThread, zip(glyphData, [bbox] * glyphTotal)) - - print 'done!' - - # start rendering to an array - imgColumns = args.columns - imgRows = 65536 // imgColumns - print('Generating a %ix%i tileset' % (imgColumns, imgRows)) - imgWidth = imgColumns * fontWidth - imgHeight = imgRows * fontHeight - image = numpy.zeros([imgHeight, imgWidth], 'u1') - for glyph in glyphs: - y, x = divmod(glyph.encoding, imgColumns) - x, y = x * fontWidth, y * fontHeight - glyph.blit(image, x, y) - - # save as png - - #rgba = numpy.empty([imgHeight, imgWidth, 4]) - #rgba[...,...,0] = image - #rgba[...,...,1] = image - #rgba[...,...,2] = image - #rgba[...,...,:3] = 255 - #rgba[...,...,3] = image - #scipy.misc.imsave(outfile, rgba) - - scipy.misc.imsave(outfile, image) - print('Saved as %s' % outfile) - -parser = argparse.ArgumentParser(description='Convert *.bdf fonts to *.png tilesets') -parser.add_argument('-v', action='store_true', help='Print debug infromation.') -parser.add_argument('-c', '--columns', nargs='?', type=int, default=64, help='Number of characters per row.') -parser.add_argument('-t', '--threads', nargs='?', type=int, default=None, help='Number of threads to run. Auto-detects by default.') -parser.add_argument('-s', '--font-size', nargs=2, metavar=('width', 'height'), type=int, default=None, help='Scale to this font size.') -parser.add_argument('file', nargs='+', help='*.bdf files to convert') - -verbose = False - -if __name__ == '__main__': - args = parser.parse_args() - print(args) - verbose = args.v - for globs in (glob.iglob(arg) for arg in args.file): - for filename in globs: - convert(filename) diff --git a/fonts/X11/clR6x12.png b/fonts/X11/clR6x12.png deleted file mode 100755 index 8ef19c5f493651b67844b94ef072882e7f79cb05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15062 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¬V;YqA+LSB1 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 z z5c%|8O$)<>J74?eIBu_cgs753`yi;0<|ISoU@B%;lVR`V7q95TX1Holf*q;h{a@Wq zo?0gaP6(V3I3aLC;Do>lffE8J1WpM2w+U>A2KvSQx_9Z!-lffE8J1peOwlm-F_T)uK>Tb+ib@=J}9NedwFH~KsfxA q&7%&=r6Em`zpp347*3rE`E|O+9XCbY-NXOW!e{EhC#8?9-~JCx_(~K2 diff --git a/fonts/X11/bdf/ucs-fonts.7z b/fonts/X11/ucs-fonts.7z old mode 100755 new mode 100644 similarity index 100% rename from fonts/X11/bdf/ucs-fonts.7z rename to fonts/X11/ucs-fonts.7z From 996e5af679661e3e861d3e9b0949c417423851b8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 26 Mar 2020 16:05:36 -0700 Subject: [PATCH 0286/1101] Prepare 11.10.0 release. --- CHANGELOG.rst | 6 ++++++ libtcod | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5396967d..e5c21b48 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,10 +8,16 @@ v2.0.0 Unreleased ------------------ + +11.10.0 - 2020-03-26 +-------------------- Added - Added `tcod.tileset.load_bdf`, you can now load BDF fonts. - `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable. +Changed + - Using `libtcod 1.16.0-alpha.6`. + Deprecated - The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated since it can cause minor scaling issues which don't exist when using diff --git a/libtcod b/libtcod index 6cede1a7..08918748 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 6cede1a789b7451fd56da8c3f841e27696fb9c60 +Subproject commit 08918748a5c8c0b594fe59ec595d7947938fc823 From 7116bc34554ffd667c6e98780695fa18498489da Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 26 Mar 2020 18:41:19 -0700 Subject: [PATCH 0287/1101] Set Py_LIMITED_API for Windows. This will now properly link Python3.dll instead of Python3x.dll. The output has been checked. --- build_libtcod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 76916d84..3d7c2f48 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -97,7 +97,7 @@ def unpack_sdl2(version): libraries = [] library_dirs = [] -define_macros = [] +define_macros = [("Py_LIMITED_API", 0x03050000)] sources += walk_sources("tcod/", cpp=True) sources += walk_sources("libtcod/src/libtcod/", cpp=False) @@ -354,7 +354,7 @@ def get_ast(): extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, define_macros=define_macros, - py_limited_api=35, + py_limited_api=True, ) CONSTANT_MODULE_HEADER = '''""" From 2c33edabd883a80d0e3d4a279f447ea5ede2cd5d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 26 Mar 2020 18:55:28 -0700 Subject: [PATCH 0288/1101] Add Windows ABI3 wheels to AppVeyor. --- appveyor.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index cea1e5a7..a8681b6f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,11 +6,18 @@ environment: CODACY_PROJECT_TOKEN: secure: xprpiCGL823NKrs/K2Cps1UVBEmpezXReLxcfLyU1M43ZBBOK91xvjdIJamYKi8D DEPLOY_ONLY: false + EXTRA_SETUP_ARGS: "" matrix: - PYTHON: C:\Python35-x64\python.exe platform: x64 - PYTHON: C:\Python35\python.exe platform: Any CPU + - PYTHON: C:\Python35-x64\python.exe + platform: x64 + EXTRA_SETUP_ARGS: "--py-limited-api cp35" + - PYTHON: C:\Python35\python.exe + platform: Any CPU + EXTRA_SETUP_ARGS: "--py-limited-api cp35" - PYTHON: C:\Python36-x64\python.exe platform: x64 - PYTHON: C:\Python36\python.exe @@ -46,7 +53,7 @@ install: - cmd: "pip --version" - cmd: "pip install --upgrade setuptools" - cmd: "pip install --requirement requirements.txt" -- cmd: "python setup.py build sdist develop bdist_wheel" +- cmd: "python -O setup.py build sdist develop bdist_wheel %EXTRA_SETUP_ARGS%" build: off before_test: - cmd: "pip install pytest pytest-cov" From 6488ad09338eefe206241de9c032118b8b4f783e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 30 Mar 2020 20:25:22 -0700 Subject: [PATCH 0289/1101] Refactor libtcod parser to remove GCC dependency. This version uses a class to sort headers then uses regular expressions to process the header code. GCC's preprocessor is no longer needed. Resolves #90 --- CHANGELOG.rst | 2 + CONTRIBUTING.md | 4 - build_libtcod.py | 284 ++++++++++-------- .../fake_libc_include/AvailabilityMacros.h | 2 - .../fake_libc_include/TargetConditionals.h | 2 - dependencies/fake_libc_include/_ansi.h | 2 - .../fake_libc_include/_fake_defines.h | 43 --- .../fake_libc_include/_fake_typedefs.h | 159 ---------- dependencies/fake_libc_include/_syslist.h | 2 - dependencies/fake_libc_include/alloca.h | 2 - dependencies/fake_libc_include/ar.h | 2 - dependencies/fake_libc_include/argz.h | 2 - dependencies/fake_libc_include/arpa/inet.h | 2 - .../fake_libc_include/asm-generic/int-ll64.h | 2 - dependencies/fake_libc_include/assert.h | 2 - dependencies/fake_libc_include/avx2intrin.h | 2 - .../fake_libc_include/avx5124fmapsintrin.h | 2 - .../fake_libc_include/avx5124vnniwintrin.h | 2 - .../fake_libc_include/avx512bitalgintrin.h | 2 - .../fake_libc_include/avx512bwintrin.h | 2 - .../fake_libc_include/avx512cdintrin.h | 2 - .../fake_libc_include/avx512dqintrin.h | 2 - .../fake_libc_include/avx512erintrin.h | 2 - .../fake_libc_include/avx512fintrin.h | 2 - .../fake_libc_include/avx512ifmaintrin.h | 2 - .../fake_libc_include/avx512ifmavlintrin.h | 2 - .../fake_libc_include/avx512pfintrin.h | 2 - .../fake_libc_include/avx512vbmi2intrin.h | 2 - .../fake_libc_include/avx512vbmi2vlintrin.h | 2 - .../fake_libc_include/avx512vbmiintrin.h | 2 - .../fake_libc_include/avx512vbmivlintrin.h | 2 - .../fake_libc_include/avx512vlbwintrin.h | 2 - .../fake_libc_include/avx512vldqintrin.h | 2 - .../fake_libc_include/avx512vlintrin.h | 2 - .../fake_libc_include/avx512vnniintrin.h | 2 - .../fake_libc_include/avx512vnnivlintrin.h | 2 - .../fake_libc_include/avx512vpopcntdqintrin.h | 2 - .../avx512vpopcntdqvlintrin.h | 2 - dependencies/fake_libc_include/avxintrin.h | 2 - dependencies/fake_libc_include/byteswap.h | 2 - dependencies/fake_libc_include/complex.h | 2 - dependencies/fake_libc_include/ctype.h | 2 - dependencies/fake_libc_include/dirent.h | 2 - dependencies/fake_libc_include/dlfcn.h | 2 - dependencies/fake_libc_include/emmintrin.h | 2 - dependencies/fake_libc_include/endian.h | 2 - dependencies/fake_libc_include/envz.h | 2 - dependencies/fake_libc_include/errno.h | 2 - dependencies/fake_libc_include/fastmath.h | 2 - dependencies/fake_libc_include/fcntl.h | 2 - dependencies/fake_libc_include/features.h | 2 - dependencies/fake_libc_include/fenv.h | 2 - dependencies/fake_libc_include/float.h | 2 - dependencies/fake_libc_include/getopt.h | 2 - dependencies/fake_libc_include/grp.h | 2 - dependencies/fake_libc_include/iconv.h | 2 - dependencies/fake_libc_include/ieeefp.h | 2 - dependencies/fake_libc_include/immintrin.h | 2 - dependencies/fake_libc_include/inttypes.h | 2 - dependencies/fake_libc_include/iso646.h | 2 - dependencies/fake_libc_include/langinfo.h | 2 - dependencies/fake_libc_include/libgen.h | 2 - dependencies/fake_libc_include/libintl.h | 2 - dependencies/fake_libc_include/limits.h | 2 - dependencies/fake_libc_include/linux/socket.h | 2 - .../fake_libc_include/linux/version.h | 2 - dependencies/fake_libc_include/locale.h | 2 - dependencies/fake_libc_include/malloc.h | 2 - dependencies/fake_libc_include/math.h | 2 - dependencies/fake_libc_include/mm_malloc.h | 2 - dependencies/fake_libc_include/mmintrin.h | 2 - dependencies/fake_libc_include/netdb.h | 2 - dependencies/fake_libc_include/netinet/in.h | 2 - dependencies/fake_libc_include/netinet/tcp.h | 2 - dependencies/fake_libc_include/newlib.h | 2 - dependencies/fake_libc_include/openssl/err.h | 2 - dependencies/fake_libc_include/openssl/evp.h | 2 - dependencies/fake_libc_include/openssl/hmac.h | 2 - dependencies/fake_libc_include/openssl/ssl.h | 2 - .../fake_libc_include/openssl/x509v3.h | 2 - dependencies/fake_libc_include/paths.h | 2 - dependencies/fake_libc_include/pmmintrin.h | 2 - dependencies/fake_libc_include/process.h | 2 - dependencies/fake_libc_include/pthread.h | 2 - dependencies/fake_libc_include/pwd.h | 2 - dependencies/fake_libc_include/reent.h | 2 - dependencies/fake_libc_include/regdef.h | 2 - dependencies/fake_libc_include/regex.h | 2 - dependencies/fake_libc_include/sched.h | 2 - dependencies/fake_libc_include/search.h | 2 - dependencies/fake_libc_include/semaphore.h | 2 - dependencies/fake_libc_include/setjmp.h | 2 - dependencies/fake_libc_include/shaintrin.h | 2 - dependencies/fake_libc_include/signal.h | 2 - dependencies/fake_libc_include/smmintrin.h | 2 - dependencies/fake_libc_include/stdarg.h | 2 - dependencies/fake_libc_include/stdbool.h | 2 - dependencies/fake_libc_include/stddef.h | 2 - dependencies/fake_libc_include/stdint.h | 2 - dependencies/fake_libc_include/stdio.h | 2 - dependencies/fake_libc_include/stdlib.h | 2 - dependencies/fake_libc_include/string.h | 2 - dependencies/fake_libc_include/strings.h | 1 - dependencies/fake_libc_include/sys/ioctl.h | 2 - dependencies/fake_libc_include/sys/mman.h | 2 - dependencies/fake_libc_include/sys/poll.h | 2 - dependencies/fake_libc_include/sys/resource.h | 2 - dependencies/fake_libc_include/sys/select.h | 2 - dependencies/fake_libc_include/sys/socket.h | 2 - dependencies/fake_libc_include/sys/stat.h | 2 - dependencies/fake_libc_include/sys/sysctl.h | 2 - dependencies/fake_libc_include/sys/time.h | 2 - dependencies/fake_libc_include/sys/types.h | 2 - dependencies/fake_libc_include/sys/uio.h | 2 - dependencies/fake_libc_include/sys/un.h | 2 - dependencies/fake_libc_include/sys/utsname.h | 2 - dependencies/fake_libc_include/sys/wait.h | 2 - dependencies/fake_libc_include/syslog.h | 2 - dependencies/fake_libc_include/tar.h | 2 - dependencies/fake_libc_include/termios.h | 2 - dependencies/fake_libc_include/tgmath.h | 2 - dependencies/fake_libc_include/time.h | 2 - dependencies/fake_libc_include/tmmintrin.h | 2 - dependencies/fake_libc_include/unctrl.h | 2 - dependencies/fake_libc_include/unistd.h | 2 - dependencies/fake_libc_include/utime.h | 2 - dependencies/fake_libc_include/utmp.h | 2 - dependencies/fake_libc_include/wchar.h | 2 - dependencies/fake_libc_include/wctype.h | 2 - dependencies/fake_libc_include/wmmintrin.h | 2 - dependencies/fake_libc_include/x86intrin.h | 2 - dependencies/fake_libc_include/xmmintrin.h | 2 - dependencies/fake_libc_include/zlib.h | 2 - tcod/cdef.h | 2 +- tcod/random.h | 1 + 135 files changed, 161 insertions(+), 589 deletions(-) delete mode 100644 dependencies/fake_libc_include/AvailabilityMacros.h delete mode 100644 dependencies/fake_libc_include/TargetConditionals.h delete mode 100644 dependencies/fake_libc_include/_ansi.h delete mode 100644 dependencies/fake_libc_include/_fake_defines.h delete mode 100644 dependencies/fake_libc_include/_fake_typedefs.h delete mode 100644 dependencies/fake_libc_include/_syslist.h delete mode 100644 dependencies/fake_libc_include/alloca.h delete mode 100644 dependencies/fake_libc_include/ar.h delete mode 100644 dependencies/fake_libc_include/argz.h delete mode 100644 dependencies/fake_libc_include/arpa/inet.h delete mode 100644 dependencies/fake_libc_include/asm-generic/int-ll64.h delete mode 100644 dependencies/fake_libc_include/assert.h delete mode 100644 dependencies/fake_libc_include/avx2intrin.h delete mode 100644 dependencies/fake_libc_include/avx5124fmapsintrin.h delete mode 100644 dependencies/fake_libc_include/avx5124vnniwintrin.h delete mode 100644 dependencies/fake_libc_include/avx512bitalgintrin.h delete mode 100644 dependencies/fake_libc_include/avx512bwintrin.h delete mode 100644 dependencies/fake_libc_include/avx512cdintrin.h delete mode 100644 dependencies/fake_libc_include/avx512dqintrin.h delete mode 100644 dependencies/fake_libc_include/avx512erintrin.h delete mode 100644 dependencies/fake_libc_include/avx512fintrin.h delete mode 100644 dependencies/fake_libc_include/avx512ifmaintrin.h delete mode 100644 dependencies/fake_libc_include/avx512ifmavlintrin.h delete mode 100644 dependencies/fake_libc_include/avx512pfintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vbmi2intrin.h delete mode 100644 dependencies/fake_libc_include/avx512vbmi2vlintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vbmiintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vbmivlintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vlbwintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vldqintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vlintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vnniintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vnnivlintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vpopcntdqintrin.h delete mode 100644 dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h delete mode 100644 dependencies/fake_libc_include/avxintrin.h delete mode 100644 dependencies/fake_libc_include/byteswap.h delete mode 100644 dependencies/fake_libc_include/complex.h delete mode 100644 dependencies/fake_libc_include/ctype.h delete mode 100644 dependencies/fake_libc_include/dirent.h delete mode 100644 dependencies/fake_libc_include/dlfcn.h delete mode 100644 dependencies/fake_libc_include/emmintrin.h delete mode 100644 dependencies/fake_libc_include/endian.h delete mode 100644 dependencies/fake_libc_include/envz.h delete mode 100644 dependencies/fake_libc_include/errno.h delete mode 100644 dependencies/fake_libc_include/fastmath.h delete mode 100644 dependencies/fake_libc_include/fcntl.h delete mode 100644 dependencies/fake_libc_include/features.h delete mode 100644 dependencies/fake_libc_include/fenv.h delete mode 100644 dependencies/fake_libc_include/float.h delete mode 100644 dependencies/fake_libc_include/getopt.h delete mode 100644 dependencies/fake_libc_include/grp.h delete mode 100644 dependencies/fake_libc_include/iconv.h delete mode 100644 dependencies/fake_libc_include/ieeefp.h delete mode 100644 dependencies/fake_libc_include/immintrin.h delete mode 100644 dependencies/fake_libc_include/inttypes.h delete mode 100644 dependencies/fake_libc_include/iso646.h delete mode 100644 dependencies/fake_libc_include/langinfo.h delete mode 100644 dependencies/fake_libc_include/libgen.h delete mode 100644 dependencies/fake_libc_include/libintl.h delete mode 100644 dependencies/fake_libc_include/limits.h delete mode 100644 dependencies/fake_libc_include/linux/socket.h delete mode 100644 dependencies/fake_libc_include/linux/version.h delete mode 100644 dependencies/fake_libc_include/locale.h delete mode 100644 dependencies/fake_libc_include/malloc.h delete mode 100644 dependencies/fake_libc_include/math.h delete mode 100644 dependencies/fake_libc_include/mm_malloc.h delete mode 100644 dependencies/fake_libc_include/mmintrin.h delete mode 100644 dependencies/fake_libc_include/netdb.h delete mode 100644 dependencies/fake_libc_include/netinet/in.h delete mode 100644 dependencies/fake_libc_include/netinet/tcp.h delete mode 100644 dependencies/fake_libc_include/newlib.h delete mode 100644 dependencies/fake_libc_include/openssl/err.h delete mode 100644 dependencies/fake_libc_include/openssl/evp.h delete mode 100644 dependencies/fake_libc_include/openssl/hmac.h delete mode 100644 dependencies/fake_libc_include/openssl/ssl.h delete mode 100644 dependencies/fake_libc_include/openssl/x509v3.h delete mode 100644 dependencies/fake_libc_include/paths.h delete mode 100644 dependencies/fake_libc_include/pmmintrin.h delete mode 100644 dependencies/fake_libc_include/process.h delete mode 100644 dependencies/fake_libc_include/pthread.h delete mode 100644 dependencies/fake_libc_include/pwd.h delete mode 100644 dependencies/fake_libc_include/reent.h delete mode 100644 dependencies/fake_libc_include/regdef.h delete mode 100644 dependencies/fake_libc_include/regex.h delete mode 100644 dependencies/fake_libc_include/sched.h delete mode 100644 dependencies/fake_libc_include/search.h delete mode 100644 dependencies/fake_libc_include/semaphore.h delete mode 100644 dependencies/fake_libc_include/setjmp.h delete mode 100644 dependencies/fake_libc_include/shaintrin.h delete mode 100644 dependencies/fake_libc_include/signal.h delete mode 100644 dependencies/fake_libc_include/smmintrin.h delete mode 100644 dependencies/fake_libc_include/stdarg.h delete mode 100644 dependencies/fake_libc_include/stdbool.h delete mode 100644 dependencies/fake_libc_include/stddef.h delete mode 100644 dependencies/fake_libc_include/stdint.h delete mode 100644 dependencies/fake_libc_include/stdio.h delete mode 100644 dependencies/fake_libc_include/stdlib.h delete mode 100644 dependencies/fake_libc_include/string.h delete mode 100644 dependencies/fake_libc_include/strings.h delete mode 100644 dependencies/fake_libc_include/sys/ioctl.h delete mode 100644 dependencies/fake_libc_include/sys/mman.h delete mode 100644 dependencies/fake_libc_include/sys/poll.h delete mode 100644 dependencies/fake_libc_include/sys/resource.h delete mode 100644 dependencies/fake_libc_include/sys/select.h delete mode 100644 dependencies/fake_libc_include/sys/socket.h delete mode 100644 dependencies/fake_libc_include/sys/stat.h delete mode 100644 dependencies/fake_libc_include/sys/sysctl.h delete mode 100644 dependencies/fake_libc_include/sys/time.h delete mode 100644 dependencies/fake_libc_include/sys/types.h delete mode 100644 dependencies/fake_libc_include/sys/uio.h delete mode 100644 dependencies/fake_libc_include/sys/un.h delete mode 100644 dependencies/fake_libc_include/sys/utsname.h delete mode 100644 dependencies/fake_libc_include/sys/wait.h delete mode 100644 dependencies/fake_libc_include/syslog.h delete mode 100644 dependencies/fake_libc_include/tar.h delete mode 100644 dependencies/fake_libc_include/termios.h delete mode 100644 dependencies/fake_libc_include/tgmath.h delete mode 100644 dependencies/fake_libc_include/time.h delete mode 100644 dependencies/fake_libc_include/tmmintrin.h delete mode 100644 dependencies/fake_libc_include/unctrl.h delete mode 100644 dependencies/fake_libc_include/unistd.h delete mode 100644 dependencies/fake_libc_include/utime.h delete mode 100644 dependencies/fake_libc_include/utmp.h delete mode 100644 dependencies/fake_libc_include/wchar.h delete mode 100644 dependencies/fake_libc_include/wctype.h delete mode 100644 dependencies/fake_libc_include/wmmintrin.h delete mode 100644 dependencies/fake_libc_include/x86intrin.h delete mode 100644 dependencies/fake_libc_include/xmmintrin.h delete mode 100644 dependencies/fake_libc_include/zlib.h diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e5c21b48..cf5f691c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - GCC is no longer needed to compile the library on Windows. 11.10.0 - 2020-03-26 -------------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd2a4fec..a65636eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,10 +16,6 @@ use with pycparser. - Install [Microsoft Visual Studio](https://www.visualstudio.com/vs/community/) - When asked, choose to install the Python development tools. -- Install [MinGW](http://www.mingw.org/) or [MSYS2](https://www.msys2.org/). - - The MinGW installer is [here](https://sourceforge.net/projects/mingw/files/latest/download). - - Add the binary folder (default MinGW folder is `C:\MinGW\bin`) to your user - environment PATH variable. - Open a command prompt in the cloned git directory. - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` diff --git a/build_libtcod.py b/build_libtcod.py index 3d7c2f48..d80f81da 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -5,10 +5,9 @@ import glob import re -from typing import List, Tuple, Any, Dict, Iterator +from typing import List, Tuple, Any, Dict, Iterable, Iterator, Set -from cffi import FFI -from pycparser import c_parser, c_ast, parse_file, c_generator +from cffi import FFI # type: ignore import shutil import subprocess @@ -18,7 +17,7 @@ import parse_sdl2 try: - from urllib import urlretrieve + from urllib import urlretrieve # type: ignore except ImportError: from urllib.request import urlretrieve @@ -27,13 +26,118 @@ # The SDL2 version to include in binary distributions. SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.10") -CFFI_HEADER = "tcod/cffi.h" -CFFI_EXTRA_CDEFS = "tcod/cdef.h" +HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") +HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") BITSIZE, LINKAGE = platform.architecture() +# Regular expressions to parse the headers for cffi. +RE_COMMENT = re.compile(r"\s*/\*.*?\*/|\s*//*?$", re.DOTALL | re.MULTILINE) +RE_CPLUSPLUS = re.compile( + r"#ifdef __cplusplus.*?#endif.*?$", re.DOTALL | re.MULTILINE +) +RE_PREPROCESSOR = re.compile( + r"(?!#define\s+\w+\s+\d+$)#.*?(? None: + self.path = path = os.path.normpath(path) + directory = os.path.dirname(path) + depends = set() + with open(self.path, "r") as f: + header = f.read() + header = RE_COMMENT.sub("", header) + header = RE_CPLUSPLUS.sub("", header) + for dependancy in RE_INCLUDE.findall(header): + depends.add(os.path.normpath(os.path.join(directory, dependancy))) + header = RE_PREPROCESSOR.sub("", header) + header = RE_TAGS.sub("", header) + header = RE_VAFUNC.sub("", header) + header = RE_INLINE.sub(r"\1;", header) + self.header = header.strip() + self.depends = frozenset(depends) + self.all_headers[self.path] = self + + def parsed_depends(self) -> Iterator["ParsedHeader"]: + """Return dependencies excluding ones that were not loaded.""" + for dep in self.depends: + try: + yield self.all_headers[dep] + except KeyError: + pass + + def __str__(self) -> str: + return "Parsed harder at '%s'\n Depends on: %s" % ( + self.path, + "\n\t".join(self.depends), + ) + + def __repr__(self) -> str: + return "ParsedHeader(%s)" % (self.path,) + + +def walk_includes(directory: str) -> Iterator[ParsedHeader]: + """Parse all the include files in a directory and subdirectories.""" + for path, dirs, files in os.walk(directory): + for file in files: + if file in HEADER_PARSE_EXCLUDES: + continue + if file.endswith(".h"): + yield ParsedHeader(os.path.join(path, file)) + + +def resolve_dependencies( + includes: Iterable[ParsedHeader], +) -> List[ParsedHeader]: + """Sort headers by their correct include order.""" + unresolved = set(includes) + resolved = set() # type: Set[ParsedHeader] + result = [] + while unresolved: + for item in unresolved: + if frozenset(item.parsed_depends()).issubset(resolved): + resolved.add(item) + result.append(item) + if not unresolved & resolved: + raise RuntimeError( + "Could not resolve header load order.\n" + "Possible cyclic dependency with the unresolved headers:\n%s" + % (unresolved,) + ) + unresolved -= resolved + return result + + +def parse_includes() -> List[ParsedHeader]: + """Collect all parsed header files and return them. + + Reads HEADER_PARSE_PATHS and HEADER_PARSE_EXCLUDES.""" + includes = [] # type: List[ParsedHeader] + for dirpath in HEADER_PARSE_PATHS: + includes.extend(walk_includes(dirpath)) + return resolve_dependencies(includes) + + +def walk_sources(directory: str, cpp: bool) -> Iterator[str]: for path, dirs, files in os.walk(directory): for source in files: if source.endswith(".c"): @@ -42,7 +146,7 @@ def walk_sources(directory, cpp): yield os.path.join(path, source) -def find_sources(directory): +def find_sources(directory: str) -> List[str]: return [ os.path.join(directory, source) for source in os.listdir(directory) @@ -50,7 +154,7 @@ def find_sources(directory): ] -def get_sdl2_file(version): +def get_sdl2_file(version: str) -> str: if sys.platform == "win32": sdl2_file = "SDL2-devel-%s-VC.zip" % (version,) else: @@ -60,11 +164,12 @@ def get_sdl2_file(version): sdl2_remote_file = "https://www.libsdl.org/release/%s" % sdl2_file if not os.path.exists(sdl2_local_file): print("Downloading %s" % sdl2_remote_file) + os.makedirs("dependencies/", exist_ok=True) urlretrieve(sdl2_remote_file, sdl2_local_file) return sdl2_local_file -def unpack_sdl2(version): +def unpack_sdl2(version: str) -> str: sdl2_path = "dependencies/SDL2-%s" % (version,) if sys.platform == "darwin": sdl2_dir = sdl2_path @@ -87,17 +192,19 @@ def unpack_sdl2(version): return sdl2_path +includes = parse_includes() + module_name = "tcod._libtcod" include_dirs = [".", "libtcod/src/vendor/", "libtcod/src/vendor/zlib/"] extra_parse_args = [] extra_compile_args = [] extra_link_args = [] -sources = [] +sources = [] # type: List[str] libraries = [] library_dirs = [] -define_macros = [("Py_LIMITED_API", 0x03050000)] +define_macros = [("Py_LIMITED_API", 0x03050000)] # type: List[Tuple[str, Any]] sources += walk_sources("tcod/", cpp=True) sources += walk_sources("libtcod/src/libtcod/", cpp=False) @@ -139,7 +246,7 @@ def unpack_sdl2(version): SDL2_INCLUDE = None for match in matches: - if os.path.isfile(os.path.join(match, 'SDL_stdinc.h')): + if os.path.isfile(os.path.join(match, "SDL_stdinc.h")): SDL2_INCLUDE = match assert SDL2_INCLUDE @@ -156,7 +263,7 @@ def unpack_sdl2(version): shutil.copy(os.path.join(SDL2_LIB_DIR, "SDL2.dll"), SDL2_LIB_DEST) -def fix_header(filepath): +def fix_header(filepath: str) -> None: """Removes leading whitespace from a MacOS header file. This whitespace is causing issues with directives on some platforms. @@ -197,115 +304,6 @@ def fix_header(filepath): .split() ) - -class CustomPostParser(c_ast.NodeVisitor): - def __init__(self): - self.ast = None - self.typedefs = [] - self.removeable_typedefs = [] - self.funcdefs = [] - - def parse(self, ast): - self.ast = ast - self.visit(ast) - for node in self.funcdefs: - ast.ext.remove(node) - for node in self.removeable_typedefs: - ast.ext.remove(node) - return ast - - def visit_Typedef(self, node): - if node.name in ["wchar_t", "size_t"]: - # remove fake typedef placeholders - self.removeable_typedefs.append(node) - else: - self.generic_visit(node) - if node.name in self.typedefs: - print("warning: %s redefined" % node.name) - self.removeable_typedefs.append(node) - self.typedefs.append(node.name) - - def visit_EnumeratorList(self, node): - """Replace enumerator expressions with '...' stubs.""" - for type, enum in node.children(): - if enum.value is None: - pass - elif isinstance(enum.value, (c_ast.BinaryOp, c_ast.UnaryOp)): - enum.value = c_ast.Constant("int", "...") - elif hasattr(enum.value, "type"): - enum.value = c_ast.Constant(enum.value.type, "...") - - def visit_ArrayDecl(self, node): - if not node.dim: - return - if isinstance(node.dim, (c_ast.BinaryOp, c_ast.UnaryOp)): - node.dim = c_ast.Constant("int", "...") - - def visit_Decl(self, node): - if node.name is None: - self.generic_visit(node) - elif ( - node.name - and "vsprint" in node.name - or node.name - in ["SDL_vsscanf", "SDL_vsnprintf", "SDL_LogMessageV", "alloca"] - ): - # exclude va_list related functions - self.ast.ext.remove(node) - elif node.name in ["screen"]: - # exclude outdated 'extern SDL_Surface* screen;' line - self.ast.ext.remove(node) - else: - self.generic_visit(node) - - def visit_FuncDef(self, node): - """Exclude function definitions. Should be declarations only.""" - self.funcdefs.append(node) - - -def get_cdef(): - generator = c_generator.CGenerator() - cdef = generator.visit(get_ast()) - cdef = re.sub( - pattern=r"typedef int (ptrdiff_t);", - repl=r"typedef int... \1;", - string=cdef, - ) - return cdef - - -def get_ast(): - global extra_parse_args - if "win32" in sys.platform: - extra_parse_args += [r"-I%s/include" % SDL2_PARSE_PATH] - if "darwin" in sys.platform: - extra_parse_args += [r"-I%s/Headers" % SDL2_PARSE_PATH] - - ast = parse_file( - filename=CFFI_HEADER, - use_cpp=True, - cpp_path="gcc", - cpp_args=[ - r"-E", - r"-std=c99", - r"-Idependencies/fake_libc_include", - r"-DDECLSPEC=", - r"-DSDLCALL=", - r"-DTCODLIB_API=", - r"-DSDL_FORCE_INLINE=", - r"-U__GNUC__", - r"-D_SDL_thread_h", - r"-DDOXYGEN_SHOULD_IGNORE_THIS", - r"-DMAC_OS_X_VERSION_MIN_REQUIRED=1060", - r"-D__attribute__(x)=", - r"-DLIBTCOD_SDL2_EVENT_H_", # Skip libtocd/sdl2/event.h - ] - + extra_parse_args, - ) - ast = CustomPostParser().parse(ast) - return ast - - # Can force the use of OpenMP with this variable. try: USE_OPENMP = eval(os.environ.get("USE_OPENMP", "None").title()) @@ -342,8 +340,15 @@ def get_ast(): ffi = FFI() parse_sdl2.add_to_ffi(ffi, SDL2_INCLUDE) -ffi.cdef(get_cdef()) -ffi.cdef(open(CFFI_EXTRA_CDEFS, "r").read()) +for include in includes: + try: + ffi.cdef(include.header) + except Exception: + # Print the source, for debugging. + print("Error with: %s" % include.path) + for i, line in enumerate(include.header.split("\n"), 1): + print("%03i %s" % (i, line)) + raise ffi.set_source( module_name, "#include \n#include ", @@ -379,7 +384,7 @@ def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Any]]: `prefix` is used to filter out which names to copy. """ - from tcod._libtcod import lib + from tcod._libtcod import lib # type: ignore if prefix.startswith("SDL_"): name_starts_at = 4 @@ -411,7 +416,24 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: return names, lookup -def write_library_constants(): +EXCLUDE_CONSTANTS = [ + "TCOD_MAJOR_VERSION", + "TCOD_MINOR_VERSION", + "TCOD_PATCHLEVEL", + "TCOD_PATHFINDER_MAX_DIMENSIONS", + "TCOD_KEY_TEXT_SIZE", + "TCOD_NOISE_MAX_DIMENSIONS", + "TCOD_NOISE_MAX_OCTAVES", +] + +EXCLUDE_CONSTANT_PREFIXES = [ + "TCOD_E_", + "TCOD_HEAP_", + "TCOD_LEX_", +] + + +def write_library_constants() -> None: """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi import tcod.color @@ -420,6 +442,14 @@ def write_library_constants(): all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): + if name.endswith("_"): + continue + if name in EXCLUDE_CONSTANTS: + continue + if any( + name.startswith(prefix) for prefix in EXCLUDE_CONSTANT_PREFIXES + ): + continue value = getattr(lib, name) if name[:5] == "TCOD_": if name.isupper(): # const names diff --git a/dependencies/fake_libc_include/AvailabilityMacros.h b/dependencies/fake_libc_include/AvailabilityMacros.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/AvailabilityMacros.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/TargetConditionals.h b/dependencies/fake_libc_include/TargetConditionals.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/TargetConditionals.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/_ansi.h b/dependencies/fake_libc_include/_ansi.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/_ansi.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/_fake_defines.h b/dependencies/fake_libc_include/_fake_defines.h deleted file mode 100644 index 41c5d0f9..00000000 --- a/dependencies/fake_libc_include/_fake_defines.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef _FAKE_DEFINES_H -#define _FAKE_DEFINES_H - -#ifndef NULL -#define NULL 0 -#endif -#define BUFSIZ 1024 -#define FOPEN_MAX 20 -#define FILENAME_MAX 1024 - -#ifndef SEEK_SET -#define SEEK_SET 0 /* set file offset to offset */ -#endif -#ifndef SEEK_CUR -#define SEEK_CUR 1 /* set file offset to current plus offset */ -#endif -#ifndef SEEK_END -#define SEEK_END 2 /* set file offset to EOF plus offset */ -#endif - -#define __LITTLE_ENDIAN 1234 -#define LITTLE_ENDIAN __LITTLE_ENDIAN -#define __BIG_ENDIAN 4321 -#define BIG_ENDIAN __BIG_ENDIAN -#define __BYTE_ORDER __LITTLE_ENDIAN -#define BYTE_ORDER __BYTE_ORDER - -#define EXIT_FAILURE 1 -#define EXIT_SUCCESS 0 - -#define UCHAR_MAX 255 -#define USHRT_MAX 65535 -#define UINT_MAX 4294967295U -#define RAND_MAX 32767 -#define INT_MAX 32767 - -/* va_arg macros and type*/ -typedef int va_list; -#define va_start(_ap, _type) __builtin_va_start((_ap)) -#define va_arg(_ap, _type) __builtin_va_arg((_ap)) -#define va_end(_list) - -#endif diff --git a/dependencies/fake_libc_include/_fake_typedefs.h b/dependencies/fake_libc_include/_fake_typedefs.h deleted file mode 100644 index 9b39f9d5..00000000 --- a/dependencies/fake_libc_include/_fake_typedefs.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef _FAKE_TYPEDEFS_H -#define _FAKE_TYPEDEFS_H - -typedef int size_t; -typedef int __builtin_va_list; -typedef int __gnuc_va_list; -/* -typedef int __int8_t; -typedef int __uint8_t; -typedef int __int16_t; -typedef int __uint16_t; -typedef int __int_least16_t; -typedef int __uint_least16_t; -typedef int __int32_t; -typedef int __uint32_t; -typedef int __int64_t; -typedef int __uint64_t; -*/ -typedef int __int_least32_t; -typedef int __uint_least32_t; -typedef int __s8; -typedef int __u8; -typedef int __s16; -typedef int __u16; -typedef int __s32; -typedef int __u32; -typedef int __s64; -typedef int __u64; -typedef int _LOCK_T; -typedef int _LOCK_RECURSIVE_T; -typedef int _off_t; -typedef int __dev_t; -typedef int __uid_t; -typedef int __gid_t; -typedef int _off64_t; -typedef int _fpos_t; -typedef int _ssize_t; -typedef int wint_t; -typedef int _mbstate_t; -typedef int _flock_t; -typedef int _iconv_t; -typedef int __ULong; -typedef int __FILE; -typedef int ptrdiff_t; -typedef int wchar_t; -typedef int __off_t; -typedef int __pid_t; -typedef int __loff_t; -typedef int u_char; -typedef int u_short; -typedef int u_int; -typedef int u_long; -typedef int ushort; -typedef int uint; -typedef int clock_t; -typedef int time_t; -typedef int daddr_t; -typedef int caddr_t; -typedef int ino_t; -typedef int off_t; -typedef int dev_t; -typedef int uid_t; -typedef int gid_t; -typedef int pid_t; -typedef int key_t; -typedef int ssize_t; -typedef int mode_t; -typedef int nlink_t; -typedef int fd_mask; -typedef int _types_fd_set; -typedef int clockid_t; -typedef int timer_t; -typedef int useconds_t; -typedef int suseconds_t; -typedef int FILE; -typedef int fpos_t; -typedef int cookie_read_function_t; -typedef int cookie_write_function_t; -typedef int cookie_seek_function_t; -typedef int cookie_close_function_t; -typedef int cookie_io_functions_t; -typedef int div_t; -typedef int ldiv_t; -typedef int lldiv_t; -typedef int sigset_t; -typedef int __sigset_t; -typedef int _sig_func_ptr; -typedef int sig_atomic_t; -typedef int __tzrule_type; -typedef int __tzinfo_type; -typedef int mbstate_t; -typedef int sem_t; -typedef int pthread_t; -typedef int pthread_attr_t; -typedef int pthread_mutex_t; -typedef int pthread_mutexattr_t; -typedef int pthread_cond_t; -typedef int pthread_condattr_t; -typedef int pthread_key_t; -typedef int pthread_once_t; -typedef int pthread_rwlock_t; -typedef int pthread_rwlockattr_t; -typedef int pthread_spinlock_t; -typedef int pthread_barrier_t; -typedef int pthread_barrierattr_t; -typedef int jmp_buf; -typedef int rlim_t; -typedef int sa_family_t; -typedef int sigjmp_buf; -typedef int stack_t; -typedef int siginfo_t; -typedef int z_stream; - -/* C99 exact-width integer types */ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed int int32_t; -typedef unsigned int uint32_t; -typedef signed long long int64_t; -typedef unsigned long long uint64_t; - - -/* C99 minimum-width integer types */ -typedef int int_least8_t; -typedef int uint_least8_t; -typedef int int_least16_t; -typedef int uint_least16_t; -typedef int int_least32_t; -typedef int uint_least32_t; -typedef int int_least64_t; -typedef int uint_least64_t; - -/* C99 fastest minimum-width integer types */ -typedef int int_fast8_t; -typedef int uint_fast8_t; -typedef int int_fast16_t; -typedef int uint_fast16_t; -typedef int int_fast32_t; -typedef int uint_fast32_t; -typedef int int_fast64_t; -typedef int uint_fast64_t; - -/* C99 integer types capable of holding object pointers */ -typedef int intptr_t; -/* -typedef int uintptr_t; -*/ - -/* C99 greatest-width integer types */ -typedef long long intmax_t; -typedef unsigned long long uintmax_t; - -/* -typedef int va_list; -*/ -#define bool _Bool -#endif diff --git a/dependencies/fake_libc_include/_syslist.h b/dependencies/fake_libc_include/_syslist.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/_syslist.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/alloca.h b/dependencies/fake_libc_include/alloca.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/alloca.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/ar.h b/dependencies/fake_libc_include/ar.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/ar.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/argz.h b/dependencies/fake_libc_include/argz.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/argz.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/arpa/inet.h b/dependencies/fake_libc_include/arpa/inet.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/arpa/inet.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/asm-generic/int-ll64.h b/dependencies/fake_libc_include/asm-generic/int-ll64.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/asm-generic/int-ll64.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/assert.h b/dependencies/fake_libc_include/assert.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/assert.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx2intrin.h b/dependencies/fake_libc_include/avx2intrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx2intrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx5124fmapsintrin.h b/dependencies/fake_libc_include/avx5124fmapsintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx5124fmapsintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx5124vnniwintrin.h b/dependencies/fake_libc_include/avx5124vnniwintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx5124vnniwintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512bitalgintrin.h b/dependencies/fake_libc_include/avx512bitalgintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512bitalgintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512bwintrin.h b/dependencies/fake_libc_include/avx512bwintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512bwintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512cdintrin.h b/dependencies/fake_libc_include/avx512cdintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512cdintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512dqintrin.h b/dependencies/fake_libc_include/avx512dqintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512dqintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512erintrin.h b/dependencies/fake_libc_include/avx512erintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512erintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512fintrin.h b/dependencies/fake_libc_include/avx512fintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512fintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512ifmaintrin.h b/dependencies/fake_libc_include/avx512ifmaintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512ifmaintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512ifmavlintrin.h b/dependencies/fake_libc_include/avx512ifmavlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512ifmavlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512pfintrin.h b/dependencies/fake_libc_include/avx512pfintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512pfintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vbmi2intrin.h b/dependencies/fake_libc_include/avx512vbmi2intrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vbmi2intrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vbmi2vlintrin.h b/dependencies/fake_libc_include/avx512vbmi2vlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vbmi2vlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vbmiintrin.h b/dependencies/fake_libc_include/avx512vbmiintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vbmiintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vbmivlintrin.h b/dependencies/fake_libc_include/avx512vbmivlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vbmivlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vlbwintrin.h b/dependencies/fake_libc_include/avx512vlbwintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vlbwintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vldqintrin.h b/dependencies/fake_libc_include/avx512vldqintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vldqintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vlintrin.h b/dependencies/fake_libc_include/avx512vlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vnniintrin.h b/dependencies/fake_libc_include/avx512vnniintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vnniintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vnnivlintrin.h b/dependencies/fake_libc_include/avx512vnnivlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vnnivlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vpopcntdqintrin.h b/dependencies/fake_libc_include/avx512vpopcntdqintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vpopcntdqintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h b/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avx512vpopcntdqvlintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/avxintrin.h b/dependencies/fake_libc_include/avxintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/avxintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/byteswap.h b/dependencies/fake_libc_include/byteswap.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/byteswap.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/complex.h b/dependencies/fake_libc_include/complex.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/complex.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/ctype.h b/dependencies/fake_libc_include/ctype.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/ctype.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/dirent.h b/dependencies/fake_libc_include/dirent.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/dirent.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/dlfcn.h b/dependencies/fake_libc_include/dlfcn.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/dlfcn.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/emmintrin.h b/dependencies/fake_libc_include/emmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/emmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/endian.h b/dependencies/fake_libc_include/endian.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/endian.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/envz.h b/dependencies/fake_libc_include/envz.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/envz.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/errno.h b/dependencies/fake_libc_include/errno.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/errno.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/fastmath.h b/dependencies/fake_libc_include/fastmath.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/fastmath.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/fcntl.h b/dependencies/fake_libc_include/fcntl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/fcntl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/features.h b/dependencies/fake_libc_include/features.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/features.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/fenv.h b/dependencies/fake_libc_include/fenv.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/fenv.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/float.h b/dependencies/fake_libc_include/float.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/float.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/getopt.h b/dependencies/fake_libc_include/getopt.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/getopt.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/grp.h b/dependencies/fake_libc_include/grp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/grp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/iconv.h b/dependencies/fake_libc_include/iconv.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/iconv.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/ieeefp.h b/dependencies/fake_libc_include/ieeefp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/ieeefp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/immintrin.h b/dependencies/fake_libc_include/immintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/immintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/inttypes.h b/dependencies/fake_libc_include/inttypes.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/inttypes.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/iso646.h b/dependencies/fake_libc_include/iso646.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/iso646.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/langinfo.h b/dependencies/fake_libc_include/langinfo.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/langinfo.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/libgen.h b/dependencies/fake_libc_include/libgen.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/libgen.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/libintl.h b/dependencies/fake_libc_include/libintl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/libintl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/limits.h b/dependencies/fake_libc_include/limits.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/limits.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/linux/socket.h b/dependencies/fake_libc_include/linux/socket.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/linux/socket.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/linux/version.h b/dependencies/fake_libc_include/linux/version.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/linux/version.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/locale.h b/dependencies/fake_libc_include/locale.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/locale.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/malloc.h b/dependencies/fake_libc_include/malloc.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/malloc.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/math.h b/dependencies/fake_libc_include/math.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/math.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/mm_malloc.h b/dependencies/fake_libc_include/mm_malloc.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/mm_malloc.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/mmintrin.h b/dependencies/fake_libc_include/mmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/mmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/netdb.h b/dependencies/fake_libc_include/netdb.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/netdb.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/netinet/in.h b/dependencies/fake_libc_include/netinet/in.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/netinet/in.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/netinet/tcp.h b/dependencies/fake_libc_include/netinet/tcp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/netinet/tcp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/newlib.h b/dependencies/fake_libc_include/newlib.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/newlib.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/openssl/err.h b/dependencies/fake_libc_include/openssl/err.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/openssl/err.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/openssl/evp.h b/dependencies/fake_libc_include/openssl/evp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/openssl/evp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/openssl/hmac.h b/dependencies/fake_libc_include/openssl/hmac.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/openssl/hmac.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/openssl/ssl.h b/dependencies/fake_libc_include/openssl/ssl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/openssl/ssl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/openssl/x509v3.h b/dependencies/fake_libc_include/openssl/x509v3.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/openssl/x509v3.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/paths.h b/dependencies/fake_libc_include/paths.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/paths.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/pmmintrin.h b/dependencies/fake_libc_include/pmmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/pmmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/process.h b/dependencies/fake_libc_include/process.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/process.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/pthread.h b/dependencies/fake_libc_include/pthread.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/pthread.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/pwd.h b/dependencies/fake_libc_include/pwd.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/pwd.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/reent.h b/dependencies/fake_libc_include/reent.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/reent.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/regdef.h b/dependencies/fake_libc_include/regdef.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/regdef.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/regex.h b/dependencies/fake_libc_include/regex.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/regex.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sched.h b/dependencies/fake_libc_include/sched.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sched.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/search.h b/dependencies/fake_libc_include/search.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/search.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/semaphore.h b/dependencies/fake_libc_include/semaphore.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/semaphore.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/setjmp.h b/dependencies/fake_libc_include/setjmp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/setjmp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/shaintrin.h b/dependencies/fake_libc_include/shaintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/shaintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/signal.h b/dependencies/fake_libc_include/signal.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/signal.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/smmintrin.h b/dependencies/fake_libc_include/smmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/smmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stdarg.h b/dependencies/fake_libc_include/stdarg.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stdarg.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stdbool.h b/dependencies/fake_libc_include/stdbool.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stdbool.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stddef.h b/dependencies/fake_libc_include/stddef.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stddef.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stdint.h b/dependencies/fake_libc_include/stdint.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stdint.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stdio.h b/dependencies/fake_libc_include/stdio.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stdio.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/stdlib.h b/dependencies/fake_libc_include/stdlib.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/stdlib.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/string.h b/dependencies/fake_libc_include/string.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/string.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/strings.h b/dependencies/fake_libc_include/strings.h deleted file mode 100644 index 616b4714..00000000 --- a/dependencies/fake_libc_include/strings.h +++ /dev/null @@ -1 +0,0 @@ -#include "string.h" \ No newline at end of file diff --git a/dependencies/fake_libc_include/sys/ioctl.h b/dependencies/fake_libc_include/sys/ioctl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/ioctl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/mman.h b/dependencies/fake_libc_include/sys/mman.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/mman.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/poll.h b/dependencies/fake_libc_include/sys/poll.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/poll.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/resource.h b/dependencies/fake_libc_include/sys/resource.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/resource.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/select.h b/dependencies/fake_libc_include/sys/select.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/select.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/socket.h b/dependencies/fake_libc_include/sys/socket.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/socket.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/stat.h b/dependencies/fake_libc_include/sys/stat.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/stat.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/sysctl.h b/dependencies/fake_libc_include/sys/sysctl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/sysctl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/time.h b/dependencies/fake_libc_include/sys/time.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/time.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/types.h b/dependencies/fake_libc_include/sys/types.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/types.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/uio.h b/dependencies/fake_libc_include/sys/uio.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/uio.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/un.h b/dependencies/fake_libc_include/sys/un.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/un.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/utsname.h b/dependencies/fake_libc_include/sys/utsname.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/utsname.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/sys/wait.h b/dependencies/fake_libc_include/sys/wait.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/sys/wait.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/syslog.h b/dependencies/fake_libc_include/syslog.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/syslog.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/tar.h b/dependencies/fake_libc_include/tar.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/tar.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/termios.h b/dependencies/fake_libc_include/termios.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/termios.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/tgmath.h b/dependencies/fake_libc_include/tgmath.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/tgmath.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/time.h b/dependencies/fake_libc_include/time.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/time.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/tmmintrin.h b/dependencies/fake_libc_include/tmmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/tmmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/unctrl.h b/dependencies/fake_libc_include/unctrl.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/unctrl.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/unistd.h b/dependencies/fake_libc_include/unistd.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/unistd.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/utime.h b/dependencies/fake_libc_include/utime.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/utime.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/utmp.h b/dependencies/fake_libc_include/utmp.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/utmp.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/wchar.h b/dependencies/fake_libc_include/wchar.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/wchar.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/wctype.h b/dependencies/fake_libc_include/wctype.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/wctype.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/wmmintrin.h b/dependencies/fake_libc_include/wmmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/wmmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/x86intrin.h b/dependencies/fake_libc_include/x86intrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/x86intrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/xmmintrin.h b/dependencies/fake_libc_include/xmmintrin.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/xmmintrin.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/dependencies/fake_libc_include/zlib.h b/dependencies/fake_libc_include/zlib.h deleted file mode 100644 index f952c1d6..00000000 --- a/dependencies/fake_libc_include/zlib.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "_fake_defines.h" -#include "_fake_typedefs.h" diff --git a/tcod/cdef.h b/tcod/cdef.h index 08c4bd93..f2a369c3 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -1,6 +1,6 @@ /* Python specific cdefs which are loaded directly into cffi. */ +#include "../libtcod/src/libtcod/libtcod.h" extern "Python" { - bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char *name); bool _pycall_parser_new_flag(const char *name); bool _pycall_parser_new_property( diff --git a/tcod/random.h b/tcod/random.h index 360073ca..0219b44d 100644 --- a/tcod/random.h +++ b/tcod/random.h @@ -1,6 +1,7 @@ #ifndef PYTHON_TCOD_RANDOM_H_ #define PYTHON_TCOD_RANDOM_H_ +#include "../libtcod/src/libtcod/mersenne.h" /* define libtcod random functions */ int TCOD_random_get_i(TCOD_random_t mersenne, int min, int max); From 9b0a94e1384c1bccf9c79c7c05015b8725a75457 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 31 Mar 2020 12:25:46 -0700 Subject: [PATCH 0290/1101] Update installation guide on Python 2 and libtcodpy. --- docs/installation.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a7a33159..3feae7e7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -99,11 +99,11 @@ collaborate with developers across multiple operating systems, or to distribute to those platforms. New API features are only available on `python-tcod`. -You can recognise a libtcodpy program because it includes this file structure:: +You can recognize a libtcodpy program because it includes this file structure:: - libtcodpy/ - libtcod.dll - SDL2.dll + libtcodpy/ (or libtcodpy.py) + libtcod.dll (or libtcod-mingw.dll) + SDL2.dll (or SDL.dll) First make sure your libtcodpy project works in Python 3. libtcodpy already supports both 2 and 3 so you don't need to worry about updating it, @@ -111,12 +111,16 @@ but you will need to worry about bit-size. If you're using a 32-bit version of Python 2 then you'll need to upgrade to a 32-bit version of Python 3 until libtcodpy can be completely removed. +For Python 3 you'll want the latest version of `tcod`, for Python 2 you'll need +to install ``tcod==6.0.7`` instead, see the Python 2.7 instructions below. + Once you've installed python-tcod you can safely delete the ``libtcodpy/`` -folder and all DLL files of a libtcodpy program, python-tcod will -seamlessly take the place of libtcodpy's API. +folder, the ``libtcodpy.py`` script, and all the DLL files of a libtcodpy +program, python-tcod will seamlessly and immediately take the place of +libtcodpy's API. -From then on anyone can follow the instructions to install python-tcod and your -project will work for them regardless of their platform or bit-size. +From then on anyone can follow the instructions in this guide to install +python-tcod and your project will work for them regardless of their platform. Distributing ------------ @@ -128,9 +132,9 @@ Python 2.7 While it's not recommended, you can still install `python-tcod` on `Python 2.7`. -`Keep in mind the Python 2's end-of-life is the year 2020. You should not be +`Keep in mind that Python 2's end-of-life has already passed. You should not be starting any new projects in Python 2! -`_ +`_ Follow the instructions for your platform normally. When it comes to install with pip, tell it to get python-tcod version 6:: From cf337217a2ccaccf874d3a8a2d6a9157dc43c9ec Mon Sep 17 00:00:00 2001 From: Chapman Siu Date: Thu, 2 Apr 2020 05:42:40 +1100 Subject: [PATCH 0291/1101] Fix small typo --- tdl/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdl/map.py b/tdl/map.py index d03c2002..e8344071 100644 --- a/tdl/map.py +++ b/tdl/map.py @@ -167,7 +167,7 @@ class AStar(tcod.path.AStar): .. deprecated:: 3.2 See :any:`tcod.path`. - Before crating this instance you should make one of two types of + Before creating this instance you should make one of two types of callbacks: - A function that returns the cost to move to (x, y) From f4f056653d8959c8ee15ad5a8f8d0fa53b278353 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 1 Apr 2020 11:55:41 -0700 Subject: [PATCH 0292/1101] Document what determines the result of the recommended_size function. --- tcod/console.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index ca25c39c..77052a43 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1169,7 +1169,8 @@ def get_height_rect(width: int, string: str) -> int: def recommended_size() -> Tuple[int, int]: """Return the recommended size of a console for the current active window. - The return value from this function can be passed to :any:`Console`. + The return is determined from the active tileset size and active window + size. This result should be used create an :any:`Console` instance. This function will raise RuntimeError if libtcod has not been initialized. From 1cea388cd0ebae52242cd9b5fdb8ca73f022433e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 17:44:57 -0700 Subject: [PATCH 0293/1101] Update libtcod to get fixes. --- CHANGELOG.rst | 4 ++++ libtcod | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf5f691c..4e91724b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ Unreleased ------------------ Changed - GCC is no longer needed to compile the library on Windows. + - Using `libtcod 1.16.0-alpha.7`. + +Fixed + - Changing the active tileset will now properly show it on the next render. 11.10.0 - 2020-03-26 -------------------- diff --git a/libtcod b/libtcod index 08918748..a733e695 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 08918748a5c8c0b594fe59ec595d7947938fc823 +Subproject commit a733e695cb347ff6690d0bee74a67735928a123e From 008c1cd0ebe99773cd893aef210bd79936bfd2ac Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 17:55:14 -0700 Subject: [PATCH 0294/1101] Add the Console.close method. This makes the root console as a context manager more consistent with other Python objects. The root console can be closed like you'd close a file or other managed object. --- CHANGELOG.rst | 4 ++++ tcod/console.py | 16 +++++++++++++++- tcod/libtcodpy.py | 9 ++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e91724b..dd84a979 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Added + - Added `Console.close` as a more obvious way to close the active window of a + root console. + Changed - GCC is no longer needed to compile the library on Windows. - Using `libtcod 1.16.0-alpha.7`. diff --git a/tcod/console.py b/tcod/console.py index 77052a43..b4c7c986 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -847,12 +847,26 @@ def __enter__(self) -> "Console": raise NotImplementedError("Only the root console has a context.") return self + def close(self) -> None: + """Close the active window managed by libtcod. + + This must only be called on the root console, which is returned from + :any:`tcod.console_init_root`. + + .. versionadded:: 11.11.0 + """ + if self.console_c != ffi.NULL: + raise NotImplementedError( + "Only the root console can be used to close libtcod's window." + ) + lib.TCOD_console_delete(self.console_c) + def __exit__(self, *args: Any) -> None: """Closes the graphical window on exit. Some tcod functions may have undefined behavior after this point. """ - lib.TCOD_console_delete(self.console_c) + self.close() def __bool__(self) -> bool: """Returns False if this is the root console. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index adb40bbe..10e28557 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1828,8 +1828,9 @@ def console_delete(con: tcod.console.Console) -> None: if con == ffi.NULL: lib.TCOD_console_delete(con) warnings.warn( - "Instead of this call you should use a with statement to ensure" - " the root console closes, for example:" + "Instead of this call you should use Console.close," + " or use a with statement to ensure the root console closes," + " for example:" "\n with tcod.console_init_root(...) as root_console:" "\n ...", DeprecationWarning, @@ -4255,7 +4256,9 @@ def _atexit_verify() -> None: warnings.warn( "The libtcod root console was implicitly deleted.\n" "Make sure the 'with' statement is used with the root console to" - " ensure that it closes properly.", + " ensure that it closes properly.\n" + "Alternatively, call the root console's close method as the" + " program exits.", ResourceWarning, stacklevel=2, ) From 6d8093ad344a8c72138f0efbab718ba25950b56b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 18:50:40 -0700 Subject: [PATCH 0295/1101] Accept an RGB tuple as a clear_color in tcod.console_flush. --- CHANGELOG.rst | 1 + tcod/libtcodpy.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd84a979..11613ed9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ Added Changed - GCC is no longer needed to compile the library on Windows. - Using `libtcod 1.16.0-alpha.7`. + - `tcod.console_flush` will now accept an RGB tuple as a `clear_color`. Fixed - Changing the active tileset will now properly show it on the next render. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 10e28557..c2bc9c26 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1150,7 +1150,11 @@ def console_flush( keep_aspect: bool = False, integer_scaling: bool = False, snap_to_integer: Optional[bool] = None, - clear_color: Tuple[int, int, int, int] = (0, 0, 0, 255), + clear_color: Union[Tuple[int, int, int], Tuple[int, int, int, int]] = ( + 0, + 0, + 0, + ), align: Tuple[float, float] = (0.5, 0.5) ) -> None: """Update the display to represent the root consoles current state. @@ -1166,8 +1170,8 @@ def console_flush( You can use :any:`tcod.console.recommended_size` to create a console which will fit the window without needing to be scaled. - `clear_color` is the color used to clear the screen before the console - is presented, this will normally affect the border/letterbox color. + `clear_color` is an RGB or RGBA tuple used to clear the screen before the + console is presented, this will normally affect the border/letterbox color. `align` determines where the console will be placed when letter-boxing exists. Values of 0 will put the console at the upper-left corner. @@ -1180,6 +1184,9 @@ def console_flush( The parameters `console`, `keep_aspect`, `integer_scaling`, `snap_to_integer`, `clear_color`, and `align` were added. + .. versionchanged:: 11.11 + `clear_color` can now be an RGB tuple. + .. seealso:: :any:`tcod.console_init_root` :any:`tcod.console.recommended_size` @@ -1190,6 +1197,8 @@ def console_flush( DeprecationWarning, stacklevel=2, ) + if len(clear_color) == 3: + clear_color = clear_color[0], clear_color[1], clear_color[2], 255 options = { "keep_aspect": keep_aspect, "integer_scaling": integer_scaling, From f10b2e8c68ffaeef319ea21576847d6184d9df72 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 20:01:45 -0700 Subject: [PATCH 0296/1101] Prepare 11.11.0 release. --- CHANGELOG.rst | 3 +++ tcod/console.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 11613ed9..6b36e255 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.11.0 - 2020-04-02 +-------------------- Added - Added `Console.close` as a more obvious way to close the active window of a root console. diff --git a/tcod/console.py b/tcod/console.py index b4c7c986..f5410427 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -853,7 +853,7 @@ def close(self) -> None: This must only be called on the root console, which is returned from :any:`tcod.console_init_root`. - .. versionadded:: 11.11.0 + .. versionadded:: 11.11 """ if self.console_c != ffi.NULL: raise NotImplementedError( From fcbe829fc13b2a2c7277f58a379e8bfe8f4fa4ff Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 23:23:49 -0700 Subject: [PATCH 0297/1101] Add root console context handling info to the documentation. --- tcod/libtcodpy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index c2bc9c26..25238391 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -909,6 +909,17 @@ def console_init_root( This option only works with the SDL2 or OPENGL2 renderers, any other renderer will always have `vsync` disabled. + The returned object is the root console. You don't need to use this object + but you should at least close it when you're done with the libtcod window. + You can do this by calling :any:`Console.close` or by using this function + in a context, like in the following example: + + .. code-block:: python + + with tcod.console_init_root(80, 50, vsync=True) as root_console: + ... # Put your game loop here. + ... # Window closes at the end of the above block. + .. versionchanged:: 4.3 Added `order` parameter. `title` parameter is now optional. From 4c3d043f5ea7f107a1c7853e476882305eb1cab5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 2 Apr 2020 23:47:22 -0700 Subject: [PATCH 0298/1101] Refactor setting up virtualenv on AppVeyor. Should remove the warning message and download the latest pip/setuptools. --- .appveyor/install_python.ps1 | 5 ++--- appveyor.yml | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index 1f699fa9..7d35e900 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -21,8 +21,7 @@ if ($env:WEB_PYTHON) { Start-Process $PYTHON_INSTALLER -Wait -ArgumentList "/quiet InstallAllUsers=1 TargetDir=C:\UserPython Include_doc=0 Include_launcher=0 Include_test=0 Shortcuts=0" $env:PYTHON = 'C:\UserPython\python.exe' } -& $env:PYTHON -m pip install --disable-pip-version-check --upgrade pip -& $env:PYTHON -m pip install --no-warn-script-location "virtualenv>=20" -& $env:PYTHON -m virtualenv venv +& $env:PYTHON -m pip install --disable-pip-version-check --no-warn-script-location "virtualenv>=20" +& $env:PYTHON -m virtualenv --download venv if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } diff --git a/appveyor.yml b/appveyor.yml index a8681b6f..b8606fb5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,7 +51,6 @@ install: - cmd: "set PATH=%PATH%;C:\\MinGW\\bin" - cmd: "python --version" - cmd: "pip --version" -- cmd: "pip install --upgrade setuptools" - cmd: "pip install --requirement requirements.txt" - cmd: "python -O setup.py build sdist develop bdist_wheel %EXTRA_SETUP_ARGS%" build: off From 3e27e2e5435b51483bae47e0d4838f2619fea41b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 3 Apr 2020 02:03:17 -0700 Subject: [PATCH 0299/1101] Update libtcod. --- CHANGELOG.rst | 5 +++++ libtcod | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6b36e255..c5455391 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Changed + - Using `libtcod 1.16.0-alpha.8`. + +Fixed + - Changing the active tileset now redraws tiles correctly on the next frame. 11.11.0 - 2020-04-02 -------------------- diff --git a/libtcod b/libtcod index a733e695..fd8ad596 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit a733e695cb347ff6690d0bee74a67735928a123e +Subproject commit fd8ad596e8a81eafeb11767729f141c6dabce069 From f35a0c720eb6e5eb4dac13eaeae4740e95bc8096 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 3 Apr 2020 02:04:24 -0700 Subject: [PATCH 0300/1101] Prepare 11.11.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5455391..808e268f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.11.1 - 2020-04-03 +-------------------- Changed - Using `libtcod 1.16.0-alpha.8`. From 4f96139569ad2d3ed93bd6ddf6699e65091998e7 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 17 Apr 2020 00:30:02 -0700 Subject: [PATCH 0301/1101] Add support for PyPy 7.3.1. --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index b8606fb5..282b3a98 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,6 +34,8 @@ environment: platform: Any CPU - PYPY3: pypy3.6-v7.3.0 platform: Any CPU + - PYPY3: pypy3.6-v7.3.1 + platform: Any CPU matrix: allow_failures: From 767765a6a8499628f89553a1dd56db1b7b49db18 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 22 Apr 2020 08:03:02 -0700 Subject: [PATCH 0302/1101] Upload source distributions in TravisCI jobs. Just in case the AppVeyor jobs don't fire again, you can still build from source on Windows. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3eedd719..b9c96dee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ before_install: - pip install --requirement requirements.txt install: - if [[ -z "$TRAVIS_TAG" ]]; then export BUILD_FLAGS="-g"; fi -- python setup.py build $BUILD_FLAGS develop bdist_wheel --py-limited-api=cp35 || (sleep 5; exit 1) +- python setup.py build $BUILD_FLAGS sdist develop bdist_wheel --py-limited-api=cp35 || (sleep 5; exit 1) - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m pip install delocate; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' From 88a2eb0bb997204082624758afdfbbf21b3e992c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 22 Apr 2020 10:50:58 -0700 Subject: [PATCH 0303/1101] Prepare 11.11.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 808e268f..be1b03f8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ v2.0.0 Unreleased ------------------ +11.11.2 - 2020-04-22 +-------------------- + 11.11.1 - 2020-04-03 -------------------- Changed From 06ce0fbc29deb7072fefec466872a6d551109ddf Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 24 Apr 2020 07:12:04 -0700 Subject: [PATCH 0304/1101] Update libtcod. --- CHANGELOG.rst | 8 ++++++++ libtcod | 2 +- tcod/libtcodpy.py | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index be1b03f8..57909ce4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,14 @@ v2.0.0 Unreleased ------------------ +Changed + - Using `libtcod 1.16.0-alpha.9`. + +Fixed + - `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant. + `libtcod#48 `_ + - The `TCOD_VSYNC` environment variable was being ignored. + - Fixed characters being dropped when color codes were used. 11.11.2 - 2020-04-22 -------------------- diff --git a/libtcod b/libtcod index fd8ad596..dc0257d9 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit fd8ad596e8a81eafeb11767729f141c6dabce069 +Subproject commit dc0257d97a96670847eb272901d2ef49b712411e diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 25238391..8bc94095 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1213,7 +1213,6 @@ def console_flush( options = { "keep_aspect": keep_aspect, "integer_scaling": integer_scaling, - "snap_to_integer": False, "clear_color": clear_color, "align_x": align[0], "align_y": align[1], From 9ba91852b3009b655eb5d3fb48858f3f11fc7b7c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 24 Apr 2020 09:15:38 -0700 Subject: [PATCH 0305/1101] Prepare 11.11.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 57909ce4..a2b5cbf5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.11.3 - 2020-04-24 +-------------------- Changed - Using `libtcod 1.16.0-alpha.9`. From 11c191e2bfde3f8d6edf2c04c0f562564e01e1dc Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Apr 2020 00:00:59 -0700 Subject: [PATCH 0306/1101] Remove libtcod data and font files. These files will be fetched from the submodule rather than stored in this repo. --- examples/data/cfg/sample.cfg | 43 ----------- examples/data/fonts/consolas10x10_gs_tc.png | Bin 8651 -> 0 bytes examples/data/img/circle.png | Bin 322 -> 0 bytes examples/data/img/skull.png | Bin 1953 -> 0 bytes examples/data/namegen/README.txt | 70 ------------------ examples/data/namegen/jice_celtic.cfg | 15 ---- examples/data/namegen/jice_fantasy.cfg | 17 ----- examples/data/namegen/jice_mesopotamian.cfg | 17 ----- examples/data/namegen/jice_norse.cfg | 17 ----- examples/data/namegen/jice_region.cfg | 7 -- examples/data/namegen/jice_town.cfg | 6 -- examples/data/namegen/mingos_demon.cfg | 16 ---- examples/data/namegen/mingos_dwarf.cfg | 29 -------- examples/data/namegen/mingos_norse.cfg | 19 ----- examples/data/namegen/mingos_standard.cfg | 17 ----- examples/data/namegen/mingos_town.cfg | 9 --- examples/samples_libtcodpy.py | 21 ++++-- examples/samples_tcod.py | 17 +++-- fonts/README.md | 2 + fonts/libtcod/arial10x10.png | Bin 14870 -> 0 bytes fonts/libtcod/arial12x12.png | Bin 16364 -> 0 bytes fonts/libtcod/arial8x8.png | Bin 11559 -> 0 bytes fonts/libtcod/caeldera8x8_gs_tc.png | Bin 6882 -> 0 bytes fonts/libtcod/celtic_garamond_10x10_gs_tc.png | Bin 8558 -> 0 bytes fonts/libtcod/consolas10x10_gs_tc.png | Bin 8651 -> 0 bytes fonts/libtcod/consolas12x12_gs_tc.png | Bin 8204 -> 0 bytes fonts/libtcod/consolas8x8_gs_tc.png | Bin 5936 -> 0 bytes fonts/libtcod/consolas_unicode_10x10.png | Bin 24558 -> 0 bytes fonts/libtcod/consolas_unicode_12x12.png | Bin 30673 -> 0 bytes fonts/libtcod/consolas_unicode_16x16.png | Bin 42003 -> 0 bytes fonts/libtcod/consolas_unicode_8x8.png | Bin 50472 -> 0 bytes fonts/libtcod/courier10x10_aa_tc.png | Bin 11760 -> 0 bytes fonts/libtcod/courier12x12_aa_tc.png | Bin 11935 -> 0 bytes fonts/libtcod/courier8x8_aa_tc.png | Bin 9126 -> 0 bytes fonts/libtcod/dundalk12x12_gs_tc.png | Bin 11591 -> 0 bytes fonts/libtcod/lucida10x10_gs_tc.png | Bin 8876 -> 0 bytes fonts/libtcod/lucida12x12_gs_tc.png | Bin 8195 -> 0 bytes fonts/libtcod/lucida8x8_gs_tc.png | Bin 7712 -> 0 bytes fonts/libtcod/prestige10x10_gs_tc.png | Bin 8090 -> 0 bytes fonts/libtcod/prestige12x12_gs_tc.png | Bin 7794 -> 0 bytes fonts/libtcod/prestige8x8_gs_tc.png | Bin 5516 -> 0 bytes fonts/libtcod/terminal8x8_aa_as.png | Bin 3762 -> 0 bytes fonts/libtcod/terminal8x8_gs_as.png | Bin 3503 -> 0 bytes 43 files changed, 28 insertions(+), 294 deletions(-) delete mode 100644 examples/data/cfg/sample.cfg delete mode 100644 examples/data/fonts/consolas10x10_gs_tc.png delete mode 100644 examples/data/img/circle.png delete mode 100644 examples/data/img/skull.png delete mode 100644 examples/data/namegen/README.txt delete mode 100644 examples/data/namegen/jice_celtic.cfg delete mode 100644 examples/data/namegen/jice_fantasy.cfg delete mode 100644 examples/data/namegen/jice_mesopotamian.cfg delete mode 100644 examples/data/namegen/jice_norse.cfg delete mode 100644 examples/data/namegen/jice_region.cfg delete mode 100644 examples/data/namegen/jice_town.cfg delete mode 100644 examples/data/namegen/mingos_demon.cfg delete mode 100644 examples/data/namegen/mingos_dwarf.cfg delete mode 100644 examples/data/namegen/mingos_norse.cfg delete mode 100644 examples/data/namegen/mingos_standard.cfg delete mode 100644 examples/data/namegen/mingos_town.cfg create mode 100644 fonts/README.md delete mode 100755 fonts/libtcod/arial10x10.png delete mode 100755 fonts/libtcod/arial12x12.png delete mode 100755 fonts/libtcod/arial8x8.png delete mode 100755 fonts/libtcod/caeldera8x8_gs_tc.png delete mode 100755 fonts/libtcod/celtic_garamond_10x10_gs_tc.png delete mode 100755 fonts/libtcod/consolas10x10_gs_tc.png delete mode 100755 fonts/libtcod/consolas12x12_gs_tc.png delete mode 100755 fonts/libtcod/consolas8x8_gs_tc.png delete mode 100755 fonts/libtcod/consolas_unicode_10x10.png delete mode 100755 fonts/libtcod/consolas_unicode_12x12.png delete mode 100755 fonts/libtcod/consolas_unicode_16x16.png delete mode 100755 fonts/libtcod/consolas_unicode_8x8.png delete mode 100755 fonts/libtcod/courier10x10_aa_tc.png delete mode 100755 fonts/libtcod/courier12x12_aa_tc.png delete mode 100755 fonts/libtcod/courier8x8_aa_tc.png delete mode 100755 fonts/libtcod/dundalk12x12_gs_tc.png delete mode 100755 fonts/libtcod/lucida10x10_gs_tc.png delete mode 100755 fonts/libtcod/lucida12x12_gs_tc.png delete mode 100755 fonts/libtcod/lucida8x8_gs_tc.png delete mode 100755 fonts/libtcod/prestige10x10_gs_tc.png delete mode 100755 fonts/libtcod/prestige12x12_gs_tc.png delete mode 100755 fonts/libtcod/prestige8x8_gs_tc.png delete mode 100755 fonts/libtcod/terminal8x8_aa_as.png delete mode 100755 fonts/libtcod/terminal8x8_gs_as.png diff --git a/examples/data/cfg/sample.cfg b/examples/data/cfg/sample.cfg deleted file mode 100644 index 4da7a5bb..00000000 --- a/examples/data/cfg/sample.cfg +++ /dev/null @@ -1,43 +0,0 @@ -myStruct "struct_name" { - // - bool_field=true - char_field='Z' - int_field=24 - float_field=3.14 - string_field="hello" - color_field="255,128,128" - dice_field="0.5x3d5+2" - - // dynamically declared fields - bool bool_field2=false - char char_field2='@' - int int_field2=4 - float float_field2=4.3 - string string_field2="world" - color color_field2=#FF22CC - dice dice_field2="3d20" - - bool_list=[true, false, true] - char_list=['a', 'b', 'z'] - integer_list=[13,2,-3] - float_list=[5.0,2.0,-3.5] - string_list=["item one","time_two"] - color_list=["42,42,142","128,64,128"] -// dice_list=["3d4","2d4+2","0.75x4d6-1"] - - // dynamically declared list fields - bool[] bool_list2=[true, false, true] - char[] char_list2=['a', 'b', 'z'] - int[] integer_list2=[13,2,-3] - float[] float_list2=[5.0,2.0,-3.5] - string[] string_list2=["item one","time_two"] - color[] color_list2=["42,42,142","128,64,128"] - -} -// a completely dynamic structure (not declared at compile time) -struct dynStruct { - int someVar=4 - struct subStruct { - float anotherVar=4.3 - } -} diff --git a/examples/data/fonts/consolas10x10_gs_tc.png b/examples/data/fonts/consolas10x10_gs_tc.png deleted file mode 100644 index 4b9b913f28dbce3fa4154eb91898de8fffe9cb75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8651 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 HT7>)$K!AdZ diff --git a/examples/data/img/circle.png b/examples/data/img/circle.png deleted file mode 100644 index 1c1404f3c06b1243853d99330a5f07fed99ba78a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^{2jKx9jP7LeL$-D$|I14-?iy0XB zj({-ZRBb+KpdfpRr>`sf4NiVRL(z-ta&7~Ku6VjQhG?9hdR{T?u!BhJ!)p1C6DLlc zI(6dFs~b+L)`^@ayrwbqsVcnG%X-~(^0)S~lb%i%t=02p%>2C9-1yr0H@}VsT4h^m zzf9O&D&TxG|5BQt^ve`s!)1;x3EMB#8Ew7bde=N>{jaUd_rK(Lqwl$H>Ezg(OFyrh zZCAMEd`-t%lMB%|k4Dw^#@@AWGHzd-S9Ufd-`@Rm|IOY8wcyzBRhPH?3Vp^E#W=qp z_%NdY!=5j^-BzDhZdlD`moV4z^32kiQAr6uj&Bdy+x__K5$WvuX|EV#7_8+_yi7Z> R;|$P$44$rjF6*2UngH4Uhd=-T diff --git a/examples/data/img/skull.png b/examples/data/img/skull.png deleted file mode 100644 index db0014e804f44d6e335e7ce7cabe44afdcb85654..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1953 zcmV;S2VVGzP)Px#32;bRa{vGi!~g&e!~vBn4jTXf00(qQO+^RT2@er51$&Afng`;nB2+JcmM zGc+I;Xfzs|%@zuU0)ZemH@CXFn#18l1~{Egl}fd+uwXPAaU4fcGzQ6L69|IE#loh*tKgHi^YNq0F%kAsHhkj83_i1 zPef0O%YJr=_K(wY4=lIXS8g{vEhnZYUJua=9Q3!wn4$Fbo6#9jdCTl9Q9c z;^N{12Mz%L2Y{)msoS<~`;SRMki+2s7K?@I{6A?s3OzkNCX2$i>+*}kzU%q@fG&JP#ctj!*2!g+6BnW~ah|ixtKYsk!U@&Mj8nIX`5{Wb#4WqBG zud=d|iVFk+YPI_L^XGcKp21)g78cgk)$QB2ucV}e#bQM=A_y`*KEArT+R@P=5C~*4 znZ;r;nM`?kc@l{PMbW{*L7=6j1w~PNm{#mC1- z>ReJ%l3Xqy9v*IPZeCnmRIAlju3Ta0^?IF7XEvK95{XKs8W`8ym@0Z8f`WkXti3+X0yp;%FN8v>-E2LV`j5?v(aX=4G#}zWo5m6 z`*wJE7{_q{f}nzeg43r@&(6-ueO-<9&)6~1FQmHaBGWdLcZ*MQ9y?VXAySux#wzj;y{QC9lOG``CC0ni5rlzK! zx$ND$_v6Qpl*8}$8;!>K`T62!uI z4u|8?rAr$M+tt-YvBbo}!a`WWGBPse=H|jKuh)xVSom>mZLPh%T_6x_Tmpx~xpCu$ z-EKz^Wb@1|FE5Y0u}_~qgR5#si~<_ zskF4T6oQ~|kU$_{GMPAz0|3D1^CSPJwzl@{*|R#GPN`H*O-+rCjz&{cTMLaw)9G{$ zha)#PS11&QUAJ%F&d$yb1Ofm6q^GB6W@d(6H*ek)3WeXkeN!rxb#-;q)6-N~G$tk{ z-oJl;>((u!(HK<|pU;ON2!bF~w?)EWFjQApXJ%%8`0!!p&Ydw%4FJG}3l|g$g<7rV za5$$Z z4jw$%+}u1mI@;FO#$+-D0s#!eX=!OVj>pHxQxoLHix)PVEh{U_YPII%bIa=F|mU%mu`L7&fu zqUh4nlFep2a^#3wtriM}$B!Qu2n0T#kHOd=1f!_wbh<<$sjsg`5JV!86crW8WU}Su z<>uyQkH?dqo}QJJg`()mlP9yYv+Z_!eSLjTPtTb%XFw3VeEG7$VAv4)s111c?%g7h zsHCLi(4j*bjV2fj#>dCITrL=f&1SRP?M4v9>-ExTw4$P-nwpwx*RCZbBoG8KIXS6R zDwmg+w_M$Ga&jI#cu-YU1pvVG^t99I47+~(_@P#-VHiGo^e7C&`T6-&+xY$d{{DW2 zLNPu*9t;Mzlz9e&QC3zqHa50dQUZa%%F2q{?T*^4pFMlFXV0Er?d_2ei^Zd(qwDMI zf4h4qmCD4##DBI|2!+DV&d!yU6-uoFfq>iXj!Xr^FpWl2SXj6f!nhF7m%-Me>BtJRVu8T&KtZx_RqloURn&*SmLV)4w( njK|}lCWh1Lbi3WZ`0Dx#f9&|2caQ1_00000NkvXXu0mjf;)|jh diff --git a/examples/data/namegen/README.txt b/examples/data/namegen/README.txt deleted file mode 100644 index b814c879..00000000 --- a/examples/data/namegen/README.txt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Collections of syllables and other data used for name generation. - * - * All the strings must be enclosed between quotation marks, with no semicolon - * at the end. - * - * SYLLABLE SETS: - * Please use only latin characters, apostrophes and dashes for syllables. All - * other characters will be treated as separators between syllables, eg. "ish" - * and "is'h" are syllables, but "|sh" and "ish!" aren't (the erroneous - * syllables will be read as "sh" and "ish", respectively). If you wish to use - * a special character, please write it after a slash, eg. a semicolon will need - * to be written as "/;" in order to be correctly parsed. Beware: a slash at the - * end of a string will not trigger an error whatsoever, but the final syllable - * will not be added to the list at all. Spaces are a special case: they can be - * triggered either with the above method, or with a single underscore: "\ " and - * "_" are both valid and will produce a space. - * - * PHONEME SETS: - * Phoneme sets should be single characters or digraphs. Please use lowercase - * characters only. "ch" and "tz" are valid consonants, but "Ch" or "trz" are - * not. They will be rejected upon generating phoneme lists. - * - * RULES: - * These denote how a word is generated. A rule is a string consisting of - * normal characters [a-z,A-Z,',-], special characters preceded by a slash (see - * the notes concerning syllables), underscores to denote spaces and wildcards. - * Wildcards are preceded by a dollar sign. Here's the full list: - * "$P" - a random Pre syllable - * "$s" - a random Start syllable - * "$m" - a random Middle syllable - * "$e" - a random End syllable - * "$p" - a random Post syllable - * "$v" - a random vocal - * "$c" - a random consonant - * "$?" - a random phoneme - * So, if we hav the following data: - * syllablesStart = "Ivan" - * syllablesEnd = "Terrible" - * rules = "$s_the_$e" - * the generator will output "Ivan the Terrible". - * The wildcards may also include an integer number. This number marks the per - * cent chance of actually appearing the related wildcard has. The number is - * placed after the asterisk, but before the corresponding character. For - * instance, "*50m" means "50% chance of adding a Middle syllable". - * If multiple rules are specified, they should be separated by characters that - * are not special character or wildcard indicators. A comma is a legible - * separator. - * A rule may be preceded by a special wildcard consisting of a per cent sign - * "%" and an integer number. This means the per cent chance of picking this - * rule should the RNG encounter it. For instance, if two rules are specified, - * each will have 50% chance of being chosen. However, if one of them is - * preceded by the "%50" sign, it will actually have a 100/2*50% = 25% chance of - * being selected (100/2 is the initial chance any single rule from a set of two - * will be picked, for five rules this would be 100/5, etc.). - * The rules are a mandatory field. Also, any field thai it references are to be - * included as well, lest it produce errors or, in the best of cases, generate - * an empty syllable as output. - * - * Don't get paranoid about controlling whether the syllables are repeated. The - * program will ignore repeated entries anyway. This applies to phonemes too. - * - * Please make sure you have enough syllables specified to ensure variety in the - * generated names. A string with 512 characters should be sufficient in most - * cases. Anything below that is a risk of making the names predictable. - * - * I hope this little tool is both fun and useful for you. Take care! - * - * -Mingos - */ diff --git a/examples/data/namegen/jice_celtic.cfg b/examples/data/namegen/jice_celtic.cfg deleted file mode 100644 index 744d25b9..00000000 --- a/examples/data/namegen/jice_celtic.cfg +++ /dev/null @@ -1,15 +0,0 @@ -//Celtic names from Jice's "The Cave" -name "Celtic male" { - syllablesStart = "Aen, Agno, All, Ba, Beo, Brig, Ci, Cre, Dan, Del, Ela, Eo, En, Er, Et, In, Io, Morr, Nem, Nu, Og, Or, Ta" - syllablesMiddle = "a, ar, ba, bo, ch, d, ig" - syllablesEnd = "aid, ain, an, and, th, ed, eth, gus, lam, lor, man, od, t, thach" - rules = "$s$m$e, $s$e" -} - -name "Celtic female" { - syllablesStart = "Aen, Agno, All, Ba, Beo, Brig, Ci, Cre, Dan, Del, Ela, Eo, En, Er, Et, In, Io, Morr, Nem, Nu, Og, Or, Ta" - syllablesMiddle = "a, ar, ba, bo, ch, d, ig" - syllablesEnd = "ai, an, da, id, iu, ma, me, na, ne, tha" - rules = "$s$m$e, $s$e" -} - diff --git a/examples/data/namegen/jice_fantasy.cfg b/examples/data/namegen/jice_fantasy.cfg deleted file mode 100644 index ce3e95d7..00000000 --- a/examples/data/namegen/jice_fantasy.cfg +++ /dev/null @@ -1,17 +0,0 @@ -//Fantasy names from Jice's "The Cave" -name "Fantasy male" { - syllablesStart = "Aer, An, Ar, Ban, Bar, Ber, Beth, Bett, Cut, Dan, Dar, Dell, Der, Edr, Er, Eth, Ett, Fin, Ian, Iarr, Ill, Jed, Kan, Kar, Ker, Kurr, Kyr, Man, Mar, Mer, Mir, Tsal, Tser, Tsir, Van, Var, Yur, Yyr" - syllablesMiddle = "al, an, ar, el, en, ess, ian, onn, or" - syllablesEnd = "ai, an, ar, ath, en, eo, ian, is, u, or" - illegal = "orar, arrar" - rules = "$s$m$e, $s$e" -} - -name "Fantasy female" { - syllablesStart = "Aer, An, Ar, Ban, Bar, Ber, Beth, Bett, Cut, Dan, Dar, Dell, Der, Edr, Er, Eth, Ett, Fin, Ian, Iarr, Ill, Jed, Kan, Kar, Ker, Kurr, Kyr, Man, Mar, Mer, Mir, Tsal, Tser, Tsir, Van, Var, Yur, Yyr" - syllablesMiddle = "al, an, ar, el, en, ess, ian, onn, or" - syllablesEnd = "a, ae, aelle, ai, ea, i, ia, u, wen, wyn" - illegal = "arrar" - rules = "$s$m$e, $s$e" -} - diff --git a/examples/data/namegen/jice_mesopotamian.cfg b/examples/data/namegen/jice_mesopotamian.cfg deleted file mode 100644 index 8ecab25b..00000000 --- a/examples/data/namegen/jice_mesopotamian.cfg +++ /dev/null @@ -1,17 +0,0 @@ -//Mesopotamian names from Jice's "The Cave" -name "Mesopotamian male" { - syllablesStart = "A, Ann, Ash, E', En, Er, Gil, In, Ir, Ish, Mar, Ni, Nin, Re, Ti, Ur" - syllablesMiddle = "am, an, du, esh, gam, gir, ka, ki, li, un, ur, ta" - syllablesEnd = "aki, al, ar, at, du, eph, esh, il, im, ki, nu, uk, ur, uz" - illegal = "aa, e'e" - rules = "$s$m$e, $s$e" -} - -name "Mesopotamian female" { - syllablesStart = "A, Ann, Ash, E', En, Er, Gil, In, Ir, Ish, Mar, Ni, Nin, Re, Ti, Ur" - syllablesMiddle = "am, an, du, esh, gam, gir, ka, ki, li, un, ur, ta" - syllablesEnd = "ag, il, la, na, sag, su, ta" - illegal = "aa, e'e" - rules = "$s$m$e, $s$e" -} - diff --git a/examples/data/namegen/jice_norse.cfg b/examples/data/namegen/jice_norse.cfg deleted file mode 100644 index 01bd584a..00000000 --- a/examples/data/namegen/jice_norse.cfg +++ /dev/null @@ -1,17 +0,0 @@ -//Norse names from Jice's "The Cave" -name "Norse male" { - syllablesStart = "Al, Ae, As, Bi, Fen, Ha, Hag, Ho, Hu, Iv, Jot, Ma, Mio, Mu, Nid, Ors, Ra, Sta, Svar, Tys, Vae, Van, Vol, Y, Ygg" - syllablesMiddle = "an, ar, ba, da, dra, gar, na, tal" - syllablesEnd = "ad, ald, agr, ar, ard, eyr, far, frost, heim, hogg, in, mir, nar, nir, or, osk, rir, sil, sir, ttir, urd" - illegal = "yor, yar, yad, yin" - rules = "$s$m$e, $s$e" -} - -name "Norse female" { - syllablesStart = "Al, Ae, As, Bi, Fen, Ha, Hag, Ho, Hu, Iv, Jot, Ma, Mio, Mu, Nid, Ors, Ra, Sta, Svar, Tys, Vae, Van, Vol, Y, Ygg" - syllablesMiddle = "an, ar, ba, da, dra, gar, na, tal" - syllablesEnd = "a, la, li, va" - illegal = "raa, ya, aea, aea" - rules = "$s$m$e, $s$e" -} - diff --git a/examples/data/namegen/jice_region.cfg b/examples/data/namegen/jice_region.cfg deleted file mode 100644 index 73f5508a..00000000 --- a/examples/data/namegen/jice_region.cfg +++ /dev/null @@ -1,7 +0,0 @@ -//Region names from Jice's "The Cave" -name "region" { - syllablesStart = "Act, Afr, Ag, Agr, Alb, Am, An, Angl, Ant, As, Asys, Asis, At, Atl, Brund, Cath, Cor, Dan, Eb, Eg, Er, Esc, Esp, Est, Eth, Eur, Flor, It, Lyr, Mal, Mir, Myr, Nor, Pel, Rom, Seg, Sib, Sylv, Terr, Tir, Tr, Tyr, Xan" - syllablesMiddle = "ad, ag, al, an, and, ant, anth, ar, ard, as, at, atr, eg, en, ent, ern, et, ian, in, itr, on, op, ov, ur, ymn, yr" - syllablesEnd = "a, aia, ana, as, ea, ene, eos, esia, ia, iad, ias, is, ium, ius, on, ona, or, ova, um, us, ya" - rules = "$s$m$e, $s$e" -} diff --git a/examples/data/namegen/jice_town.cfg b/examples/data/namegen/jice_town.cfg deleted file mode 100644 index 5a79a0ee..00000000 --- a/examples/data/namegen/jice_town.cfg +++ /dev/null @@ -1,6 +0,0 @@ -//Town names from Jice's "The Cave" -name "town" { - syllablesStart = "Ael, Ash, Barrow, Bel, Black, Clear, Cold, Crystal, Deep, Edge, Falcon, Fair, Fall, Glass, Gold, Ice, Iron, Mill, Moon, Mor, Ray, Red, Rock, Rose, Shadow, Silver, Spell, Spring, Stone, Strong, Summer, Swyn, Wester, Winter" - syllablesEnd = "ash, burn, barrow, bridge, castle, cliff, coast, crest, dale, dell, dor, fall, field, ford, fort, gate, haven, hill, hold, hollow, iron, lake, marsh, mill, mist, mount, moor, pond, shade, shore, summer, town, wick" - rules = "$s$e" -} diff --git a/examples/data/namegen/mingos_demon.cfg b/examples/data/namegen/mingos_demon.cfg deleted file mode 100644 index e2919ba2..00000000 --- a/examples/data/namegen/mingos_demon.cfg +++ /dev/null @@ -1,16 +0,0 @@ -//Demon names -name "demon male" { - phonemesVocals = "a, e, i, o, u" - syllablesStart = "Aam, Ab, Ad, Ahr, Alas, Al-A'w, All, Al-M, Ap, As, Ast, Az, Bal, Bal S, Bag, Balb, Ban, Bansh, Baph, Barb, Bath, Bazt, Be'L, Beel, Beelz, Bel, Belph, Ber, Bh, Bifr, Biul, Bush, Caac, Cagn, Caim, Chalk, Char, Chem, Coal, Dag, Dant, Decer, Demog, Dev, Dj, Dragh, Elig, Emp, Errt, Etr, Ett, Eur, Euryn, Gorg, Graph, Grig, Haag, Halph, Haur, Hoeth, Ifr, Inc, Ibl, Ith, Kabh, Kas, Kokb', Kray, Lab, Lam, Lech, Leg, Lil, Lioth, Lix, Luc, Mal, Malph, Mamm, March, Mast, Math, Meph, Merm, Mol, Murm, Naam, Naph, Nek, Neph, Neq, Nix, Noud, Onom, Onos, Orc, Orob, Oul, Paim, Phen, Pont, Proc, Rah, Rak, Raksh, Ram, Rang, Raum, Raz, Rimm, Rub, Rus, Sabn, Salps, Sam, Sat, Sc, Scarm, Seer, Sem, Set, Shait, Shax, Shed, Shez, Sidr, Sitr, Sth, Succ, Surg, Tann, Tart, Tch, Teer, Thamm, Thub, Tlal, Tsab, Val, Vap, Vass, Vep, Verr, Vin, Vol, Vual, Xaph, Xiph, Xitr, Zaeb, Zim, Ziz, Zaln" - syllablesMiddle = "b'ae, ba, be, chi, dra, du, ga, ghi, go, lia, ma, mba, mu, n'e, na, nti, nzu, phe, pho, r'e, rba, rgo, ssa, thi, tryu, ttu, tzi, v-e, vna, xra, ya" - syllablesEnd = "b'ael, bel, bub, bur, bus, ces, chus, dai, ddon, des, dhaka, el, fer, flas, gion, gon, gor, klet, kor, ksha, kuth, laas, lech, les, lion, lith, loch, lsu, mael, math, mejes, meus, mon, moth, mmut, mosh, nai, nar, neus, nex, nias, nnin, nomos, phas, r'el, raal, rept, res, rgon, riax, rith, rius, rous, rus, ruth, sias, stor, swath, tath, than, the, thra, tryus, tura, vart, ztuk" - rules = "$s$v$35m$10m$e" -} - -name "demon female" { - phonemesVocals = "a, e, i, o, u" - syllablesStart = "Aam, Ab, Ad, Ahr, Alas, Al-A'w, All, Al-M, Ap, As, Ast, Az, Bal, Bal S, Bag, Balb, Ban, Bansh, Baph, Barb, Bath, Bazt, Be'L, Beel, Beelz, Bel, Belph, Ber, Bh, Bifr, Biul, Bush, Caac, Cagn, Caim, Chalk, Char, Chem, Coal, Dag, Dant, Decer, Demog, Dev, Dj, Dragh, Elig, Emp, Errt, Etr, Ett, Eur, Euryn, Gorg, Graph, Grig, Haag, Halph, Haur, Hoeth, Ifr, Inc, Ibl, Ith, Kabh, Kas, Kokb', Kray, Lab, Lam, Lech, Leg, Lil, Lioth, Lix, Luc, Mal, Malph, Mamm, March, Mast, Math, Meph, Merm, Mol, Murm, Naam, Naph, Nek, Neph, Neq, Nix, Noud, Onom, Onos, Orc, Orob, Oul, Paim, Phen, Pont, Proc, Rah, Rak, Raksh, Ram, Rang, Raum, Raz, Rimm, Rub, Rus, Sabn, Salps, Sam, Sat, Sc, Scarm, Seer, Sem, Set, Shait, Shax, Shed, Shez, Sidr, Sitr, Sth, Succ, Surg, Tann, Tart, Tch, Teer, Thamm, Thub, Tlal, Tsab, Val, Vap, Vass, Vep, Verr, Vin, Vol, Vual, Xaph, Xiph, Xitr, Zaeb, Zim, Ziz, Zaln" - syllablesMiddle = "b'ae, ba, be, chi, dra, du, ga, ghi, go, lia, ma, mba, mu, n'e, na, nti, nzu, phe, pho, r'e, rba, rgo, ssa, thi, tryu, ttu, tzi, v-e, vna, xra, ya" - syllablesEnd = "b'a, bel, bua, bure, buth, cess, chia, dai, ddea, dea, dhaka, el, fea, fla, gia, goa, gora, klath, kore, ksha, kua, laal, lexa, less, lia, lith, loth, lsa, mara, math, maja, mea, moa, moth, mmuth, mosh, na, nai, neuth, nex, nia, nnine, nomoa, pha, r'el, raala, repte, reshe, rgona, riaxe, rith, rish, rothe, rushe, ruth, sia, stora, swath, tath, thann, the, thra, trya, tura, varte, ztura" - rules = "$s$v$35m$10m$e" -} diff --git a/examples/data/namegen/mingos_dwarf.cfg b/examples/data/namegen/mingos_dwarf.cfg deleted file mode 100644 index 300a0b00..00000000 --- a/examples/data/namegen/mingos_dwarf.cfg +++ /dev/null @@ -1,29 +0,0 @@ -//dwarf names -name "dwarf male" { - syllablesStart = "A, An, Ba, Bi, Bo, Bom, Da, Dar, De, Do, Du, Due, Duer, Dwa, Fa, Fal, Fi, Fre, Fun, Ga, Gar, Gim, Glo, Go, Gom, Gro, Gwar, Ib, Jor, Ka, Ki, Kil, Lo, Mar, Na, Nal, O, Ras, Ren, Ro, Ta, Tar, Tel, Thi, Tho, Thon, Thra, Tor, Von, We, Wer, Yen, Yur" - syllablesEnd = "bil, bin, bur, char, den, dir, dur, fri, fur, in, li, lin, mil, mur, ni, nur, ran, ri, ril, rimm, rin, thur, tri, ulf, un, ur, vi, vil, vim, vin, vri" - rules = "$s$e" - illegal = "rur, ueu" -} - -name "dwarf female" { - syllablesStart = "A, An, Ba, Bi, Bo, Bom, Da, Dar, De, Do, Du, Due, Duer, Dwa, Fa, Fal, Fi, Fre, Fun, Ga, Gar, Gim, Glo, Go, Gom, Gro, Gwar, Ib, Jor, Ka, Ki, Kil, Lo, Mar, Na, Nal, O, Ras, Ren, Ro, Ta, Tar, Tel, Thi, Tho, Thon, Thra, Tor, Von, We, Wer, Yen, Yur" - syllablesEnd = "al, ali, ba, bida, bra, da, deth, di, fra, gret, hild, iess, kala, la, laani, li, lona, ma, mae, mala, na, nuda, ra, ta, tala, tu, tuna, vada, vara, ya" - rules = "$s$e" - illegal = "dueal, frefra, grogret" -} - -//surnames have semantic information. Here, they're separated into three -//somewhat coherent sets and either is chosen -name "dwarf surname" { - //1st set - smith & Mr.Muscle surnames - syllablesPre = "Boulder, Bronze, Coal, Copper, Gem, Granite, Hammer, Iron, Marble, Metal, Rock, Steel, Stone, Thunder" - syllablesPost = "bender, breaker, carver, club, crusher, cutter, digger, fist, foot, forger, heart, smasher, smith" - //2nd set - warrior surnames - syllablesStart = "Bear, Boar, Dragon, Giant, Goblin, Elf, Ettin, Foe, Kobold, Ogre, Orc,Spider, Troll, Wolf" - syllablesEnd = "bane, basher, _Battler, _Beheader, boxer, _Butcher, choker, cleaver, crusher, cutter, doom, eater, _Executioner, _Fighter, _Garrotter, grapple, _Gutter, hammer, killer, mauler, masher, ripper, slasher, slayer, slicer, smasher, _Strangler, striker, _Wrestler" - //3rd set - heroic and general - phonemesVocals = "Black, Blood, Bronze, Fire, Firm, Grey, Hard, Ice, Iron, Moon, Oak, Onyx, Red, Steel, Stone, Strong, Thunder, White" - phonemesConsonants = "axe, beard, blade, brand, cheek, fist, foot, hair, hammer, hand, head, heart, pick, shield, spear, spike, sword" - rules = "$P$p, $s$e, $v$c" -} \ No newline at end of file diff --git a/examples/data/namegen/mingos_norse.cfg b/examples/data/namegen/mingos_norse.cfg deleted file mode 100644 index f1d9f7c9..00000000 --- a/examples/data/namegen/mingos_norse.cfg +++ /dev/null @@ -1,19 +0,0 @@ -//Norse names. Most of them are syllables extracted from names that -//actually appear in Norse written texts. Norse names consist of two parts, -//which is easy to reflect in a generator such as this one. -name "Mingos Norse male" { - //these are ready-made names - syllablesPre = "Aunn, Bjoern, Bjolfr, Bjorr, Boltr, Byulfr, Erik, Erpr, Eykr, Feitr, Fotr, Froekn, Gaukr, Gauss, Gils, Gimp, Griss, Gyi, Haegwin, Haengr, Hakon, Hand, Harekr, Hattr, Haukr, Helf, Hjalli, Hjaerne, Hjarrandi, Hnaki, Hneitr, Hrafn, Jarl, Karl, Kar-Toki, Kaun, Kilfisr, Kiuli, Knut, Knutr, Krakr, Leifr, Lokki, Manni, Mar, Moegr, Naemr, Nagli, Nef-Bjoern, Njall, Oelfun, Oenn, Oern, Rafn, Roki, Skjalf, Skog, Spjall, Sveinn, Tannr, Trani, Trjonn, Utryggr, Vagn, Varg, Ve-Finnr, Voettr, Vragi, Vrai" - //and these are syllables to stick together - syllablesStart = "Ab, Adal, Adi, Alf, An, And, Ans, Arn, Arm, Ask, Au, Audh, Ba, Bae, Bag, Bal, Bar, Bas, Bein, Berg, Bern, Bjad, Bjarn, Bjart, Boan, Boed, Boerk, Bogg, Bor, Bot, Bram, Bran, Bratt, Brei, Bro, Brunn, Bukk, Dag, Djur, Dor, Duf, Dun, Ed, Ei, Ein, Ekk, Ey, Fa, Fad, Fal, Far, Fast, Fen, Finn, Fjall, Fjoel, Flae, Fol, Folk, Foest, Frey, Frid, Frost, Ful, Fuld, Gaes, Geir, Gag, Gal, Gam, Gar, Gaut, Geir, Ginn, Gis, Gjaf, Gjal, God, Gnaudi, Gny, Gret, Grim, Grom, Grum, Gud, Gull, Gunn, Gutt, Gyll, Gyr, Ha, Haf, Hag, Hagn, Half, Hall, Ham, Har, Haur, Hedin, Hef, Heg, Heil, Hein, Hel, Hildi, Hjall, Hjalm, Hjoer, Hlif, Hloed, Hoeg, Hoegg, Hoer, Hoes, Hol, Holm, Hord, Horn, Hrad, Hrafn, Hring, Hroeng, Hross, Hug, Hul, Hum, Hus, Hvit, Hyr, Igul, Illu, In, Ingi, Is, Ja, Jar, Jarn, Jat, Jo, Joefur, Kjoet, Kol, Kon, Lamb, Lids, Lik, Ljot, Lyd, Nadd, Nef, Odal, Odd, Oeg, Oel, Oen, Oeng, Oes, Rad, Rafn, Ragn, Rask, Reid, Reyr, Roegn, Rok, Run, Sae, Sig, Skae, Skjald, Skjoeld, Skol, Slag, Snae, Soel, Soend, Spjall, Stafn, Stark, Stein, Stig, Stod, Stygg, Styr, Sunn, Svein, Svart, Svarta, Tid, Tindr, Tjoer, Trygg, Tyr, Thyr, Ud, Ulf, Yngv, Vae, Val, Varg, Ve, Ved, Vest, Vetr, Vid, Vig, Vik" - syllablesEnd = "adr, afr, all, andi, arfr, arr, astr, autr, bi, beinn, bert, bjoern, bodi, bodr, bori, brandr, burinn, burt, daenni, dan, di, din, diarfr, dinn, dr, dridr, dur, eifr, eirr, fast, fasti, fastr, fidr, fill, fing, fingr, finnr, fli, fri, frimr, fuss, gall, geir, geirr, gils, gir, gisl, glir, glumr, grimr, gripr, gur, guri, haldr, hegn, hjalmr, hjoefr, hjofr, hoefdi, hoess, hofdi, horir, horr, hoess, hvatr, ilir, ill, ingr, inn, jadr, jarn, jarr, jartan, jartr, joern, jofr, karl, kell, ketill, kirr, kuldr, kull, kundr, kunnr, laugr, lan, leifr, leikr, li, lidi, lidr, lingr, madr, maer, mann, marr, mingr, modr, mr, mund, mundr, nall, narr, nefr, nir, niutr, olfr, ormr, phorr, pli, r, raeifr, radr, rik, rikr, ring, rinn, rir, roedr, rudr, rukr, si, sir, skegg, skeggi, sjall, steinn, styrr, sur, tir, tyr, utr, ulf, ulfr, undr, ungr, urd, urdr, valdr, vandi, vandill, veinr, ver, vett, vi, vidr, vifr, vind, vindr, vi, vini, vir, visl" - rules = "$s$e, %10$P" - illegal = "bjarnbj, gp, vv, aea, aee, aeo, drd" -} - -name "Mingos Norse female" { - syllablesStart = "A, Aer, Aerin, Aes, Aet, Afri, Agaer, Ager, Al, Alf, Alm, Arinn, Arn, As, Au, Aud, Bau, Be, Beg, Berg, Bir, Bjol, Bod, Bol, Bor, Borg, Bot, Bri, Brun, Bryn, Bus, Dag, Dis, Dom, Dor, Dot, Dri, Dyr, Ed, Ei, Em, Emb, Engil, Er, Es, Ev, Ey, Fal, Fast, Fin, Fjol, Fjor, Fjot, Folk, Frey, Frid, Frost, Gaut, Geir, Ger, Gil, Ginn, Gis, Gjaf, Gre, Grim, Gud, Gy, Gyd, Haf, Hall, Haur, Hedin, Heil, Heim, Hel, Her, Hidin, Hil, Hildi, Hjalm, Hjor, Hlad, Hlif, Holm, Hrim, Hrod, Hun, Igul, In, Ingi, Ingil, Is, Jar, Jo, Jofur, Jor, Jut, Ljuf, Lofn, Mal, Malm, Mar, Mat, Matt, Mund, Nid, Odd, Ol, Olm, Ot, Rad, Ragn, Rand, Rann, Regin, Run, Sal, Sae, Sig, Skjald, Sol, Stein, Svan, Svein, Tid, Ulf, Vet, Val, Ve, Vig, Vil, Yng" - syllablesEnd = "bjorg, borg, da, dis, disa, disla, dora, eida, erna, fasta, finna, frida, frosta, fura, ga, gard, gauta, geid, gerda, gida, gret, grid, grima, gudr, gunn, gunna, heidr, hilda, ja, la, laug, lina, linn, ma, maer, rida, run, ta, trid, trida, truda, unn, ve, velda, vi, vilda, vina" - illegal = "ii, iei, edeid, tg, ee, vev" - rules = "$s$e" -} diff --git a/examples/data/namegen/mingos_standard.cfg b/examples/data/namegen/mingos_standard.cfg deleted file mode 100644 index f7e71854..00000000 --- a/examples/data/namegen/mingos_standard.cfg +++ /dev/null @@ -1,17 +0,0 @@ -//Names based on syllables from J.R.R. Tolkien's and David Eddings' novels. -name "male" { - phonemesVocals = "a, e, i, o, u, y" - phonemesConsonants = "b, c, ch, ck, cz, d, dh, f, g, gh, h, j, k, kh, l, m, n, p, ph, q, r, rh, s, sh, t, th, ts, tz, v, w, x, z, zh" - syllablesStart = "Aer, Al, Am, An, Ar, Arm, Arth, B, Bal, Bar, Be, Bel, Ber, Bok, Bor, Bran, Breg, Bren, Brod, Cam, Chal, Cham, Ch, Cuth, Dag, Daim, Dair, Del, Dr, Dur, Duv, Ear, Elen, Er, Erel, Erem, Fal, Ful, Gal, G, Get, Gil, Gor, Grin, Gun, H, Hal, Han, Har, Hath, Hett, Hur, Iss, Khel, K, Kor, Lel, Lor, M, Mal, Man, Mard, N, Ol, Radh, Rag, Relg, Rh, Run, Sam, Tarr, T, Tor, Tul, Tur, Ul, Ulf, Unr, Ur, Urth, Yar, Z, Zan, Zer" - syllablesMiddle = "de, do, dra, du, duna, ga, go, hara, kaltho, la, latha, le, ma, nari, ra, re, rego, ro, rodda, romi, rui, sa, to, ya, zila" - syllablesEnd = "bar, bers, blek, chak, chik, dan, dar, das, dig, dil, din, dir, dor, dur, fang, fast, gar, gas, gen, gorn, grim, gund, had, hek, hell, hir, hor, kan, kath, khad, kor, lach, lar, ldil, ldir, leg, len, lin, mas, mnir, ndil, ndur, neg, nik, ntir, rab, rach, rain, rak, ran, rand, rath, rek, rig, rim, rin, rion, sin, sta, stir, sus, tar, thad, thel, tir, von, vor, yon, zor" - rules = "$s$v$35m$10m$e" -} - -name "female" { - phonemesVocals = "a, e, i, o, u, y" - syllablesStart = "Ad, Aer, Ar, Bel, Bet, Beth, Ce'N, Cyr, Eilin, El, Em, Emel, G, Gl, Glor, Is, Isl, Iv, Lay, Lis, May, Ner, Pol, Por, Sal, Sil, Vel, Vor, X, Xan, Xer, Yv, Zub" - syllablesMiddle = "bre, da, dhe, ga, lda, le, lra, mi, ra, ri, ria, re, se, ya" - syllablesEnd = "ba, beth, da, kira, laith, lle, ma, mina, mira, na, nn, nne, nor, ra, rin, ssra, ta, th, tha, thra, tira, tta, vea, vena, we, wen, wyn" - rules = "$s$v$35m$10m$e" -} diff --git a/examples/data/namegen/mingos_town.cfg b/examples/data/namegen/mingos_town.cfg deleted file mode 100644 index cd093e21..00000000 --- a/examples/data/namegen/mingos_town.cfg +++ /dev/null @@ -1,9 +0,0 @@ -//Town names. The town name construction is based on real British town names, -//although many starting syllables are made up. -name "Mingos town" { - syllablesPre = "East, Fort, Great, High, Lower, Middle, Mount, New, North, Old, Royal, Saint, South, Upper, West" - syllablesStart = "Ales, Apple, Ash, Bald, Bay, Bed, Bell, Birdling, Black, Blue, Bow, Bran, Brass, Bright, Brown, Bruns, Bulls, Camp, Cherry, Clark, Clarks, Clay, Clear, Copper, Corn, Cross, Crystal, Dark, Deep, Deer, Drac, Eagle, Earth, Elk, Elles, Elm, Ester, Ewes, Fair, Falcon, Ferry, Fire, Fleet, Fox, Gold, Grand, Green, Grey, Guild, Hammer, Hart, Hawks, Hay, Haze, Hazel, Hemlock, Ice, Iron, Kent, Kings, Knox, Layne, Lint, Lor, Mable, Maple, Marble, Mare, Marsh, Mist, Mor, Mud, Nor, Oak, Orms, Ox, Oxen, Pear, Pine, Pitts, Port, Purple, Red, Rich, Roch, Rock, Rose, Ross, Rye, Salis, Salt, Shadow, Silver, Skeg, Smith, Snow, Sows, Spring, Spruce, Staff, Star, Steel, Still, Stock, Stone, Strong, Summer, Swan, Swine, Sword, Yellow, Val, Wart, Water, Well, Wheat, White, Wild, Winter, Wolf, Wool, Wor" - syllablesEnd = "bank, borne, borough, brook, burg, burgh, bury, castle, cester, cliff, crest, croft, dale, dam, dorf, edge, field, ford, gate, grad, hall, ham, hollow, holm, hurst, keep, kirk, land, ley, lyn, mere, mill, minster, mont, moor, mouth, ness, pool, river, shire, shore, side, stead, stoke, ston, thorpe, ton, town, vale, ville, way, wich, wick, wood, worth" - syllablesPost = "Annex, Barrens, Barrow, Corner, Cove, Crossing, Dell, Dales, Estates, Forest, Furnace, Grove, Haven, Heath, Hill, Junction, Landing, Meadow, Park, Plain, Point, Reserve, Retreat, Ridge, Springs, View, Village, Wells, Woods" - rules = "$15P_$s$e_$15p" -} diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 2a50d3f9..33b27d5b 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -23,11 +23,18 @@ if not sys.warnoptions: warnings.simplefilter("ignore") # Prevent flood of deprecation warnings. + +def get_data(path: str) -> str: + """Return the path to a resource in the libtcod data directory,""" + SCRIPT_DIR = os.path.dirname(__file__) + DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") + return os.path.join(DATA_DIR, path) + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -font = os.path.join(b'data', b'fonts', b'consolas10x10_gs_tc.png') +font = get_data("fonts/consolas10x10_gs_tc.png") libtcod.console_set_custom_font(font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) libtcod.console_init_root(80, 50, b'libtcod python sample', False) sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) @@ -66,7 +73,7 @@ # default listener print ('***** Default listener *****') - libtcod.parser_run(parser, os.path.join(b'data', b'cfg', b'sample.cfg')) + libtcod.parser_run(parser, get_data('cfg/sample.cfg')) print ('bool_field : ', \ libtcod.parser_get_bool_property(parser, b'myStruct.bool_field')) print ('char_field : ', \ @@ -129,7 +136,7 @@ def end_struct(self, struct, name): def error(self,msg): print ('error : ', msg) return True - libtcod.parser_run(parser, os.path.join(b'data',b'cfg',b'sample.cfg'), MyListener()) + libtcod.parser_run(parser, get_data('cfg/sample.cfg'), MyListener()) ############################################# # end of parser unit test ############################################# @@ -1157,9 +1164,9 @@ def render_bsp(first, key, mouse): def render_image(first, key, mouse): global img,img_circle,img_blue,img_green if img is None: - img = libtcod.image_load(os.path.join(b'data',b'img',b'skull.png')) + img = libtcod.image_load(get_data('img/skull.png')) libtcod.image_set_key_color(img,libtcod.black) - img_circle = libtcod.image_load(os.path.join(b'data',b'img',b'circle.png')) + img_circle = libtcod.image_load(get_data(('img/circle.png'))) if first: libtcod.sys_set_fps(30) libtcod.console_set_default_background(sample_console, libtcod.black) @@ -1269,9 +1276,9 @@ def render_name(first, key, mouse): global ng_sets if ng_nbsets == 0: # parse all *.cfg files in data/namegen - for file in os.listdir(b'data/namegen') : + for file in os.listdir(get_data('namegen')) : if file.find(b'.cfg') > 0 : - libtcod.namegen_parse(os.path.join(b'data',b'namegen',file)) + libtcod.namegen_parse(get_data(os.path.join('namegen',file))) # get the sets list ng_sets=libtcod.namegen_get_sets() print (ng_sets) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 6fca7525..35357509 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -21,11 +21,18 @@ if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. + +def get_data(path: str) -> str: + """Return the path to a resource in the libtcod data directory,""" + SCRIPT_DIR = os.path.dirname(__file__) + DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") + return os.path.join(DATA_DIR, path) + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -FONT = "data/fonts/consolas10x10_gs_tc.png" +FONT = get_data("fonts/consolas10x10_gs_tc.png") root_console = None sample_console = tcod.console.Console( @@ -1108,9 +1115,9 @@ class ImageSample(Sample): def __init__(self): self.name = "Image toolkit" - self.img = tcod.image_load("data/img/skull.png") + self.img = tcod.image_load(get_data("img/skull.png")) self.img.set_key_color(tcod.black) - self.circle = tcod.image_load("data/img/circle.png") + self.circle = tcod.image_load(get_data("img/circle.png")) def on_enter(self): tcod.sys_set_fps(0) @@ -1243,9 +1250,9 @@ def on_enter(self): def on_draw(self): if self.nbsets == 0: # parse all *.cfg files in data/namegen - for file in os.listdir("data/namegen"): + for file in os.listdir(get_data("namegen")): if file.find(".cfg") > 0: - tcod.namegen_parse(os.path.join("data/namegen", file)) + tcod.namegen_parse(get_data(os.path.join("namegen", file))) # get the sets list self.sets = tcod.namegen_get_sets() print(self.sets) diff --git a/fonts/README.md b/fonts/README.md new file mode 100644 index 00000000..94500021 --- /dev/null +++ b/fonts/README.md @@ -0,0 +1,2 @@ +The regular libtcod fonts are found at: +https://github.com/libtcod/libtcod/tree/master/data/fonts diff --git a/fonts/libtcod/arial10x10.png b/fonts/libtcod/arial10x10.png deleted file mode 100755 index 64691f1bbd47ca4f7346f8d502efccfb8a325ece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14870 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 i%m0rr=&oN{Bjy3Cx-_~ooA8ktL{VNr{gP diff --git a/fonts/libtcod/arial8x8.png b/fonts/libtcod/arial8x8.png deleted file mode 100755 index b05f533ea72e9ba4a73176dba1391b7f8e9ce90b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11559 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 Or1V5h?xT!Z=>Gs2E+Ah3 diff --git a/fonts/libtcod/celtic_garamond_10x10_gs_tc.png b/fonts/libtcod/celtic_garamond_10x10_gs_tc.png deleted file mode 100755 index 7a0bc4a0f4f808d11e82cbfa5a3e11f93d917ee2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8558 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

XL}klIw2>vdp)pK--3uO%wFs^-ToVX zDI%lCCLNDpN?#wh&NsWe^|I_DELInxIaIy? zvq_!>vZLbY=CTf=;@Qb$Wnqg4#C<`@w(3=%SO&ks43d#DEtAabz@X}(t1cZsziq4d z&DHPU-78J4sp^$YU6NJbHZgu@s#F2J4Zd4yrT;@&sy=_j^WFuHzAGGlX|BAnd?G`R zd~by0t!B#Tnu(PY<2M5T5aCK^t4xl|TX#8J$P#uFw|k2UH%lq2{8^GjM+uEo$;k41 zqa$+$hlDu306olID!fd*8@93jYFC&WnISKpOL8P0DzNz3k1jPg8P>4ge-fyd(>jeCBA3?4klc;|Ua>7>m}3^U*(0g{Bj|4WYJpN955caLif z?y>{+F;3TuI+2l&w=AYwUk-B>N5k#-@w}knwY35bMt9}T$D3Y`{QFRKTNEGFII4Do zCCTcN^hV!p8KXxjB3}lyyzkvRZP`)6x7kiLxx#4qi)l;i68y$3cv`XmwnM`tl$9Y*Pt{Ex3| z-fW2wD2|#$F9uxpH~3@2tr5}kJO>dH16tg)VBR29siuQ^wOAnk9?r_VsuBgFP8oN&wM`jzZ@90NumtW|d(yR7!R_6W0`c;jJ&%O4?YIFRQhF&?ZE?Q1Ji>8<4 zs-ipEPePl`nQeAmbe*Fr%x!7Gs-!*VwXFl8&eZ zJ^b+G<6_Jl0y4xS*SD_F>7<_cEefHb2VT);r^duDPkgOp_b#cHiN7;Iu ziUbzls^h=kOgd?7?8+T(cq>aBl(3iJBagK0`Rk!(#&U=RKb{`*(HZV=oxrRH+$i)t z&esFcM|&wNkltXXs2@g8xOSlTA91-94_!hSPD-Yl&7O0N}^f5jFS2}4iSXVg3D<1v}+{m!zieJzq#c5-CyA8TZw!vsdMVkzA!@|RTJ8xbFnM$ zrV`U0)28`N^|A1Qr~qVZ2!H5^)wK8(q2wqXX1k%I*DOTO>%F;gzb(ZDJ9U@~B)$lIWKxUYkC6QGEt(N?&T8F_?s#{Ni{XVU?Pu zc2uoLRLP$4zP&~xQ_MZ(PPI8H!sFGILS;4QYtFK>Y?xJkl7CQ8wcF+RSwH-71mQoZ zr*&7lUB1Z3G_wE#q;h_h_2f2@{4?@#Vn_prs7e`j6=sLD5I>~JlMs8iFDyS{!*Bn` zgZn?8%^LTUwbu+CNF+fU_7w^i>4R7o03yuGzI$gO;k7SpX%4$B2`^|MY-mY|Q;eCa zh>y;Ymb0D7mhZ8E`b~PovDv_82!Vr)u$BE^V=12>)4C{~k1g`!*HUyfW6br>6;L^3 zQIJrf|8S~-Hs&qebRQsg#t2Flh7POsK{^#`CrScpN8^`}JJOD5AavEr IRjkAQA1Uv0bpQYW diff --git a/fonts/libtcod/consolas_unicode_16x16.png b/fonts/libtcod/consolas_unicode_16x16.png deleted file mode 100755 index f6ee8aba5d1934d7f5cecfcfa944f1a6c6316f48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42003 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

70ZlZ0!*7v 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

1Xv>s*SNRk}0uhJGRJsgCwXNkN9z) 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 diff --git a/fonts/libtcod/consolas_unicode_8x8.png b/fonts/libtcod/consolas_unicode_8x8.png deleted file mode 100755 index 6716f3dd51b7c9c1b3115a5f5451993d5e314b5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50472 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


{F4Go7QBPpM(ub0GSs9_3mdyw>b1YF$D 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 diff --git a/fonts/libtcod/courier10x10_aa_tc.png b/fonts/libtcod/courier10x10_aa_tc.png deleted file mode 100755 index 50498e3ecbc1070c426fd89d4e71e3b15ec0bf4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11760 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 diff --git a/fonts/libtcod/courier12x12_aa_tc.png b/fonts/libtcod/courier12x12_aa_tc.png deleted file mode 100755 index a1c4555d9cf0751f8b24b8650ed77744a8efd772..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11935 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 diff --git a/fonts/libtcod/courier8x8_aa_tc.png b/fonts/libtcod/courier8x8_aa_tc.png deleted file mode 100755 index 675a743d2f6d1c9176c5c1284ccff80b3b58ae32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9126 zcmb_h1y|h8*AA|WE?V5(p-|k4yHmWyOVQ%)RoUVha$z{&F}vi-Z|N1 za&}K|N}gt?V5rp2xc_$r zdv!cy3U!Gne`q`%6D10kd^qKgMLe}`Y-wruH@|bk;bLO^qw&;O&?8Y7kaj5ZVuJF+ zBZnS0z4C46+HdzpUmE5`_A9Qk>c^3~kkON6xz%|6(Mu)Bus4DR`vx|5*u(-*X&eA7 zOc4>^+QsRw|takP8o(41YA+22@!A6MDbBX}~}0F3k%D`0$+)4<tQdOXr4-nUASHQJJ+S zOjnkLdJZQRmmSM1T(a*i4Nin47kx_{tXyq@<`mA<`>8ML)Q2pk89cJY)QNwVKH1hG z34CXvVE!GlleN>k!@cunhw}0RYL=OpW5J!q7K8ebjXcpV)-KO3qIoKpxK@6;`fAA! zO>Ev+eTm8+jq25EVI{acU(*FfgYe}P3M=!HrirZxtZuEV4_sQYSChz;Qzmq#h7V;9 zQV!s*-OwSSctL~VmJCE0gh+&zL~exDsftBJ(@{i&njCLAk5l*4Fw^X{beS5Nyz%0P zP?O}7=#t(rv1^u=6_u@(p=n)d4Qs}h8fo}cz1QN_*e*qxNiNqa%Thhoc%xxiDp(R$ zW?5pdDO%vG_Z)$!*Q?m8B3Yo8TWh@CK0tx?k*MCCh0+#M9{M$aw~wxNbkWbgVnd1(=s?*}Eq7Lacy~h9y@j-cd19}FP=?MT|v)ih_!Rqa&kvqjPDz8N!_NT>BaO8S@$IO(y#8`Yeqljm_qAbvyb+dS{L8 zUqbW+^a^#g^bPCZS7uex&b3vx6=N5-C?_hHXU|#o*8FY!(^%R>RI|H)xLnm}*XUzv zX+~h#)L+$mme`T#)9Ul>Sr`ctwun(qs!!_c{L=~gPW#Mnj8TA=G zrzOkPXMAVYXJIQjBRn~b*?Qd06TQDBH`I0~MbUDSIBeG|d{rb>0xTQ$so6iW$Lmzi z*EAKnmED@26Nvm1amv{i*%o@c#37qxO0Rvkamc!)ANA{}SI^f;pxB=l z#u6?fKs?|B?BkuMH|4Jw(Ra({|LPV`#iMgu#PP&q11ke}yDKTJexJluQr_;Tj>V5z zV}z*s9?VT3neLfJ_4=aIhlGXbgo?&EgzFMda;EUQO|D2fuS-2hB}=VkSaPZeRI|$S zGKrM2*$6GYZRKed9bmK28=2M3(v|zHKqA1#RK@q@bLaZg=~dWP))v~f7eOlfZ$k%< zA7ee&A;tKmY&b0MlEoCmmHEG4h+{F8;78M5;=0kspnCL>_E=J}Px4KMFGw0Nkttuw zGAVf`oW!^DdeHQ97qaZO1k;zswJSmtS2Nya);GZh>vrWD5F$LY_vSG z+_P{Ut}qX&d+GlY)x=Jgg!IR@KCS{mj}^61qUb91szsW-Mx+q8-Y3@M~`LHZ9D|#P;JBtq2|a&Cc9ac z@>89ZD&Gz|PbJ#`qwb-=?gkj!V>B()-4LJSmHAUc6CHds zW%iU&hUK=PM#+}^Uqa38=EZ-;Mg>JR3pB4aA2j*SrdAtF=kLBup1tvE|HpGOyZoX3 z_F3dl=du=zmeQ=ntoftdWBsjYRYck0UGk%yVWqO2>Ts448|aN=S$YP8!LnlqV+9vV2j#8%46s77G?dG4DxQtHQK9U6KR^GRx$cnLI*z9`aY3f2@}d1E&)$FUO;^lCpe!+3r_N zmh&3D_CJoT+hE$Cw_>?$PD#H;?F+UGju~7wyIj^S@8aq->!eq|GqyW&ITCtFoK_iZ z%WO-pYB8{FIrHpj@Upl)e3SilZ1uXs<1hZ}+-$ah@M_1Er~PgJ?aey=s;?9M^Vo3P zq8t5LWxGnd@wM2g_@d-$*sSP>mz9O{6+fFUx7@4A6`5Pakf8CG_?uWvNrc$6*x^t} z=y5i`h=9PBsMGV|Q?caur*XP$f|tCP;VJ@rcHg_%(k)G$TW2)?UQ}{8wTHV*)XiOA8CK4qkzQV($UOj!} zW?csLCcLG%k~jd=#&|!fC4%=9A7#~*Ks8DW0RBM$aQ_NkL0$Tl0|1Up0N`CJ01((G z827&i0K#rLDRE8rr4vIBP3Yj9y#=BvK#ha9vK6BMRaJz~r(h@v7s5l}wds?M#&)2M zppn0hh17}y({WrqKt_&Iu!!829QM#C=ZN_1{Ek;_4GHNFM#0tHjV2RL z&n+o0&qz%j$r99&kqP@_|LMOLtkL;!R8GWgp_s{E>vp~de>GF8p?5VqJ4-6+D|B;6 zMZ*)ks9%Hf-(%YmtTrCPY}K7`lojQa1Wr93x_gn}{q#MTj%LfpJ+eLyiMrfj_4>?~%|_0P}Va*CISr}gXcKcjLUMuEe#jyC@O z)m~u=4lyXJe!T@m+5WQ)JkLU%Ia)oAvRAh6_rF%-Yr^p04=6Yp|NhHbmcn6XW)_3X zV}~V%4}(8n>NqaW@int>+}`_EcElttEiHSv)*+bNjdF|Y_}5WN(89uet<{HFfL4G- z2)+2+VcAji#MeZ1!+Lz$-q;?EfDN9A zK0l^*c!N_2izRV&brt#g08JEN7DSuah+^;Tvb}0r_kF|nIkmQybL{+dTiEyMPBIMh z8!<0xjvOL{cxGh^otbpsxZRzzoKND3`aVpKg@MIn*62=WT~%cT6&2&*s#z%kdn!3^@w8=pIBd)8f;Mz;CNIuH8s`MsV=2O({|&bvamt0X+2+ETnZq~QpI(Rjh#OG zjpydZ>%*P5XX_bU=BTZ_t|tqP{~CwNUuOxjb zUDV)};;!1DLtRhnaBnXZH&G*@I9>VX`Wjq@oGbk+4?YJxatprx%Pk4jUnT}hR&(&_BgABhKUckt?#*CN4~r<28M zu}aR9-(}i$`;*YU7rnHa(SC=4fkEoEVVm36GCUZ_(9kgQR$9GfxE5+(?e^f8m3C~} zNU92KO%%)VEa^TNI7EPWW_G@%xp_l7r>u-Aw_EF1IS2^gx=Z0VHa7YrTG0eba^(h$ z)8@?locTTj&GZr9(JYCUeQ4Bz@_T0zGO_Ui-CnaH^@PslV5KU?A;2?T$mwHIG?G4Rs-ILm8az z7bX@Oe;7t8^y*WMpn`&el9G}d8q@tz1l#Z9J3C)MfY+|j73Uc(QOiF$J+&N3?fx$M z>33WNEx@^?tKm6F5whsGKR(X1Gz~J}%yNW6@uk&sT z4FZ8I^0H?P_Vk!d708iyWfW9YL_eXNqe%R4xY`@7PK~Xqa?)3=4mmepZzA9_=>j5y z6caCf+;B>3YHKM^@Of&-azt}3gyc0e;NajC6cpwx!7qpeT`Qh;<>ZtOmz&s+S)HAo z_q4^DzN~^UwZo3dytKTWHh&R#huPhpOjlXo@(a_`^6HeuL4XI*`spW09R2C?g-h3V z!Jz%GXV<6mZAIr-kOKS>`%eDl-63oOA-b(D)(W=6AW9ds)b3O(fkAeFG^zM-{_Ay& ziJq7Brl%k6GDRl4ioNnS|3igM7h_j(F;Hk;SXNdBh7?XBP8w>B%b~hO!pdMvMPAIS z&(rTo>XEhed>PE<_`k^w-XO1lF<0A4fa2E@eaXPYbaZ^|%ncL+r2iaMRVTV7k+!$D z>g(%KV&>-Nu&MPL4?u!*eQ8OQBj>SFwPijWe#7UYtEabEsF33F)n&ad*>XJl`GhfQ zniSL{am7N0ZLW4-tUC{UCVP6Q*hDbS>U#!)lJOiz_pj1k^#lZ{UlM}=0elQnq3ijo zUuEUxdb|;GGbe?5x|#=di_&C!?d|OtD%ze8%TNN>hLshA`$Uf6-tE6LnzO{j!~mqd zqXUzKU(CgAD_sjo)nno!c6#0YyANx|Pq|QCXoq$Tk8dWMB(hoJfaK@34$j!0MSauq z`NN5gk}xA9BP4VD3)MgEt*lH$juP|e6(j8I?DX~ZkLRnyNRfXuH|q`z+t`c#F=}=& zeTY8n?(QBT^4Gde-4&MyNn$f0&CqxN-9MUK0gXMxxhxb)h^WTw-%;2?z!{o4g$nK5c2Ipf=Z-G#_N5j4H- zcRyr47ZS{`;SF{I7fLU^ooxM8q{rFosP$lj_#?)v#bd`~gNIrk!~Oi=%iE{4Y$a}P?!EU(PrLWf4zWzwb_E1v%>SCD z&k|-^dr+YnYlhISAKW;cnjVNBASK{LNKwT@@$eRxmL$}Ylauf7?*;zXzz%pjVnpRP zE4S^Zb3L~AXu+1NGK-5vMus8yt{$_N3D`-|#;zcZKgc~*@6u=LW3P--U>1FdVADkc ztl{94rokc1Y6+G2BO@ctY8SxJnwsL`h2i zsHev1iBL3Or-i;xchIJkir>-nXZ_G9MpeGdBrGgUM~C?C`_up01{y)!odqc@zQ10( zoAmkTPJ5G_c3c5+^eIKsz_BGqJsB744n4kS$j-3w*u{kd5vtfU&kC-82l)#@gp{Ih zye#~xs;{pv$B&#Ga%r_@d-|1<<}OspKApdZCOS?KXVR$GjSA=kwHQ0W6Ygl0EJ>P{&30muK1n zM05t0^MLz#*tMHEY)34SST?p{HqrLzjx6)|YdQ)|vDA_g}_pp)*yF#;{ z5qP>(c2C#p-yl-_86NMgClvAKAAx2YV__d)1x+iUaWIlHxh#*toP3=0g(&e38z|9$mbta z^qTYy0a`VvQ7B0}v;KnVteS8|FxKn2yx9ncy_sex2B)hp6qQ)4JU2TJL;QKU^IJ(% z=m4Tv(5jc~_LJYs4SnQEU^ti;GEz^3Y@b;O2nZI$z6h>P)C0uPgq%GK3xpH7?RnH( z2rLm`1cihkuaf zeo|GQ^oprKzT;@70PX}J3PsD%78j>v7sbQ|m1a~nwEMWPKZ&xwe;08yg#9EJA;B>i3_OMwOaLprObq z+Xn~kcfU8ueeX;!cmAY!vnyrrK~_O66dUo)L+#1zbg7{^6P&smrrYbAmp(NJ1O9}` zSf&8^*Yl?O`lyI+_h)VAsd$FkM(?dIdQ-#3#>NyWg~%|!;p;8xfwrMbkmQ(S<8+%e z2TNr_dXh>})8Jh?RY4%Gj~zV`^RLqCd(37#oh8>{$UbDm^~V#Dubr$U{r zuL=6u!{f-wsUx$Yg#|@@^nFc1u5&~ipmRgQAQStCLcF1+sd>Q>A1+GAhZyUEa`^D@ zFfcISe#ZnZ=X9|OIp1f(?9xFwOkMNkMteA(yT`uuW-7T180F?1iadp^EpIypFCSw^}wpLTsr4P8T(o|KAIi2VDTLG>i7&24LI8>?2Q+KAzdJU0MTf-lHm z3}8=^d{0VJbE0w@`Oi8FvhlQ5Js8q{VKr#3Dfn5?Sf#C^5-YpaIQ@-iZ+2`9L!Lsy z+}B{?eqZ^M`u1$?=S42DpyQW$OtN>#RvbPLF71J!i}7Z=|M{~qSN9v}2(7F>^Kbf6 z@v-6iZO|$t1H=Z`od;W6Q!7(K&U=bHfw07y^P1I09qEezy-L;-=<8r0svPwK&U-%_ z8^GwBURxuZ=<4bM{^s8axT6oL<-dm^CK2?lsAxye8QdSuK(YdQzk)`o()btI6a*dJ zq&*7+3CnP9)rzX6R()N1jyPotZMp*c*V}&sMeotjq8zseIPpf#)7LAPo5Byg9S}c4 z`aF)VOEX~J{{79Vkj!FjYu|R@gUyvYA)H93RAygtE3{37O(ed9HtHf4WIk*Zv4W3H zw0XLH>NXln6snaD8%-jB!$(3@Wzd2%kqed(93cKkd4Ww}QGyiUX(n>OteM79d$0D#=~dDV^zQ5ILqdbegd9{EpddU@<>|8QFaE8!Y8)BgC8dW1OaM#%P;tuHGpYiDOCnD3y&4ZX3j zGT{f`gW;m>yz`g?TbfD>Pm7v5A2b1kZ#r;nd1-0;7k=wXOCwa1MCL_*i{2ke)6mlD zzlPen%$HQt!xbDwPyPXQ*~o~Zyr)2%`KM2W#Dt(B7WKY!xGeZHIy!owZDCRL2Z}2+ zK;7s5{I|pNW@=`J({xB2+-YSA($9yc>EFgY3j3`jf|-#j!sRtH0mO(+(R!M-g)+Z=2gBWl6pX=RdezY0$#O$JUrz zvl0A=(xZfE6_=RdEP5iMyxwMCT_6T4>@qQn9G{g)N``F&@)CoXuDhliiw$>nT?U| zQb|GOc>R~?@x%GZB6k=DxtTWXlC31Gjh!9DN>yJQ)k+M0;$O!}#v}jjpK1DL87^K{ zyoub#s>TC3Y-~DSE~dJgk{XaLCICzbnzN#;Vo7DWbd`E!UX|gmbzw1McZ!ukm;e|KUoIEE+{9_nK*vF{?>mp&@TN9JQW6t!SM(&AiJGyCN=s4R z&YYZ_TwHcZ_`<$11r@`;lhq&sO$KOBwWcZ*?+rmzJFTauCrSj$#lzg`2P>@ACO`5{ z@z4)aHS4MA>9E8lrKPo=`q(if!mwGg|HZ>=5Zao^n%C{AxzEr4s4as4bl0P-qZQ=X zC>SVoPX}Pc@3)UFE-woT2pBfI2dK(gcz|Y=G7_IslrbC4EGjBiQthMP>$BU=rs>z; zU1%#(@I2uW-yD|@`7A~VGxZD&vm8FT-BG?=$zfRd{5zTjQ3DloW(CZ<@ZaH}Uz@Ko z9v=IXJZ&qbHghnUUucKz-JUNhttPnQ3J}91Akxxz;4HV+)?$wq@F?9LEMgOGfil6~ z`Vg7^yk-EVl`*%$P))6S1g=$y&u;0Hm6iLy6Fm>Auk!V5*}})Va9C@Ix;?ORFt$v* zybVPIbUnn@Ha73PcQ_JTK>JwnGdi^oEVh;v6={Q>d}bzChL(wyHH4sfURp^B((wTm z5G%?8OS+d$z=adV!GoR-bSjFsQ6~&d^tBLe-M8>LzE=kH4;s_zj@cl-gSp(rb#T-f zEsnSxZcT+TPxg{`7xD`6V;xOtGc-3hPo!4?ODlZ5ytH}udG2FU$g%7)J}AcN69+Hy zx_dt)!(fRopzc`^m}}#ZhzL2bB&6@D?=Hok+YJ^$G7Hvu5JvIIlACZWAS2Ycb zai{lc>sNn7aDVT7VXsyRgFzzv>t?+-mKD`_00W7$rc}asI{dm;Ue(FQz>mC|F6t-3 zgOZt<2|y|;E7cNmH*&$vLkK~TlEN1Y+Go$Er=xuzk>}v)B!3C&t3E*q2UvI>E2LAf z9_APV#za)Cf`Uq$;}zoH8n8YCR&Z2ug!`L%$1;WCtN>#hs!k%9CNpG8(AC?{RXE-{ z4F0-DC|_M#B7V3*N$~@VXjfhuH5ZJfj5+=&!H7&V;*+YZtY(;|Be{4SwoIV2yuOw6 z(x<@U&@u5A$yqR=dXN1?pP={&h8)H>L@u4A2e*q!hAsB;7_p_(=dsSWA6-VzgXj$1 z|5%eyOzwq5PxLa(=I+~e|FCb@R)U!|d6H+)(9>%SSbgbD+hi5BKz-{NQElPPKtn^v z!^6iB_96Hv{RkxGpT2jRz`T3kWeUh%B>WDbXZe5X{HVYC1p`PW4ZnpXqkw;I0Dzpd LvQ(9XN#OqhJ}X_7 diff --git a/fonts/libtcod/dundalk12x12_gs_tc.png b/fonts/libtcod/dundalk12x12_gs_tc.png deleted file mode 100755 index d73097943f3a33c3c05cac9005f29997f8feffad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11591 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 diff --git a/fonts/libtcod/lucida10x10_gs_tc.png b/fonts/libtcod/lucida10x10_gs_tc.png deleted file mode 100755 index 4e68dce8f2680467dac43d4920ac664de1a10eb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8876 zcmcgx1y>wRvtEKbAvl2`i!bi(?hYZiyL*tuU4u(-ch}(V5ZppYa0yu~=r`~E5x39T z?b)3(U0qK-Rn=7!t)?Q2iAI730)a5)<)kz~AUHALS`Fn5@O`K$xCl(BE^_+rAQ1Yy z|2}Y_>|7!c2u;;iQc_LL#@WN!-NxC4LS9mm!o|(m%GSXW1oGL))3nyoJSGsn+qsrd zj0*mu=&XT{LZKlM8;qAs%SefeqYy<|xQ?$r@K#zHfpoke3O*qrI1XPOf)RtZhWv}N zC?T{sDrWL=&%fAyrRVNo=B0I2HbU&2f7aw5@Mq2NB#za^I1Yi0re~A^s~K>e8oPI$od2q90u3#k<6!v z5{w5D^GlE@1BuDN1?OebYJv(7Kqgb>X8RyDW{?S8(7_TYIPX5o9}Z;jg%TgGAQ?pQ z&N500WFrWwnbV1p2I;YZ@T?Sv`9W*UAU1hjD>+a@JE(sa2fYb|f&pSvj|yi1A^3w# z#;K`&Kw()RJej|`LRU<6IERb?QdvzxonU@xr~wMIE3&RG3pxF)JRuVvkLerJY%!KW z-z;JdC>Qn**fa=Kkc(1p4DhmCY$Qs?^m?_a;x6ow@XOeb{% zUaSX=`*oik{sjs>-QE27_7e={Foen?-2WJyzE&!IKbL|UX0~;ZDErio{`y3=L^UjL z)~Uycx37-tnjn=O2jeKEik3)W7@=8wwfMOUd4w0);plnv z|GWc%{&qVLe5Xf&54H*2oAZ9X6n&O0paBJ2$)~u2Kt@tv7Olx<(J>SdNU8wJ*dR`N zJwOEMM|wYixHW+OWWp0B0UjQdz>z?+2!8Ko!cY?;K_4;GK*ngo`dy5St6$wFBGm

hnIiOg3lH|} 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

C4L#@5Bukpc- zn*vobV_;)iZ$?&Qk7>EjTgIK#pN;Vqb1Dww?}`d$4w{pGHF3q_47mqBTNUomdF4)< zX~&U2*fQXEv2lW&R>w(?(X~cNs<2J$q2A9p!bzl613(j~U++@)#8N1EzXv`-l0XOUIV*6eZOtPOKCi+99k>_CG+${xnmA*z`*c1N3v)Jo3nif zeTlQF!6H=^G*{jp>z>Z=m$SvJf9P#a{R%So+D7=F0l&f6dRe1Z=&uwl@9J76ez)wk zL}IBvyLFV>37;GlrIv&2w9P_5GNS99Sn(nRZ#Mtxg) zWmm7(ebnAUQam>Er<5?pb>KDo0mJm+6)x#&o?Xk#pW2GMy$UofRuYHDNQHjH$$_VT zVBl8?o9+AVEwRma5uOxO=Y^WA{eJBH6oLYH3Z~>DyEwT@j^UpgcTwUER2cRge@A#= zChXyB-rT0OZ^ff+#0n?Ot_4U6D#Q|Jz+SZW4aQaTcnpwrs3?D{M{`X;w;UHm7I`|157F|Z#KXWB3d6ArO4v4E}14TRrs$_vhQVTh@)lBcnzY)KY;Pu6T1 zkrV8%UnR#FYrMjvWskW0w1_nujy;>e7$cj%bOrf}@N_fC=u$zE9@E3)_aNXRZbTZ-gZS-;Eg zANW3A)^pZ9uIs(-Sd^9u2_YjP0)Ze=Q&rSKAh6(1gzVKT@Y7#LdJ}$OdFZIfBR&i< zU;cwih>@A?vdnVitLui8kofm?aJ zL9TT8R31kU8l|0-9;T$o?@YbIp-93JyU1-NFIrD&p&jO=I45J{)9N>*Cg1$A_H1Y; z;qLXFRAFJ;wM;)j;XPJjVM25Bg>T;sncddDq!Q5yc)Ge8HhJILeKk?~P`6}U)L~MV zFkboOV8+E|zPZ6~&xSERdeq6$vAs4TA|fS)t{I+jvqUc~Bjfo%;P2_$L>^5J+Rw|o z{5NjE6WlPBmB=W03Oc$NNmI8}NkI`dYHDJll_{yOuU~7~g_mTetDCP=qQ|dqYkNJRF9P%D&!3-V2yqSXg-Xx8CG~B1Z?sabPQo7qh!N zf)?rir&gdNBhwaiav+x{%P!%ML2GMk8yeF8LNLbjB=(PvjkW#zd4ss?1?#<^i;D=h z29I?uSy|b~kC%^jmq$)(jc%n3^w7XVIR>S%rZ48X#XgHK1S+g}(Z+F86NYSJp-fHV zC)Z~iJO$KwQT~LzTLL}~&eu&(+PH!dG%C7q1f7+YMIT*I zR0NyDaHyxQuI}UWsiozfGh&gKK3cnE++pg&V75%Qdcg}8sW*lp{K-c8`ncv{T15^b zj68`Aj^C?mY==9aqzIU%Ne4-yLohAv-HbFeQ3I2`y~+hT6eo1-92|L#jniFS`3q~e z`1rmzdfj-%b`*I2n)^PI_;Jm5`&vfw!T9ydEuwT;Xe~D=z1Xh^`JqgoJ+q zN3>;daBy;Ra?WE(DJgtndX%0X4po4kpJ{{pYST{Zz?Ux~!ooLh-Ey+C3-b|W)Kpbf z4G3tG&y|hy{fF6H?n{W(^f*1*RY_po42pX5hBj4045rJe@&fU!w6wISsHm+iL_wkJ zom!&1hld<@3^BuP&yBBP*!a^kGlv+AreEJNsKx9s3dZWUi0#x+?4H#H@lQ z%O2^Pm=OPsw9+jaOHN7({L_ISA0OYXf^((g6{m{pZ{ffrSH^hn-_xS1Rn^s!KHJRE zmiznrSXfx-hrtKmZ7C=yu4`qq?D%o4-O6#2@sVqma~&XDsFFG-9!zZ zd=zi7I?CAC!P;6WV<#vuP=a{~)>U3XVI;M>wbkq-U4GlFkG{DTGF@Rh8 z4h2B;3ri6+KR?|YzBoT@qq6OW&0kttI^J7fjw}IG3;Ks?^x6t7 z`&3o6xxHvXke`ksa~fyT^hqM_x2GO1cIeZqHeEMdzO8OxVku{!z%&y!wxH;_=8q z-{4o^hhk|*b*Tu;&>Dh$tM#Ew#mSh@SJMZBaifw4Rwu{D-@c{))_RwfY$}f96|c1P z`E;!VR1*3yz}NR(K|xeh6gLl#R=OC8djIR!uV-gXpy>?^4mL*eg9~Vq)yqptQiW|_ zTw^eTo6%YLRfdlLNeHjp<7D&`w)qlj&--j;Wd)jw=7s@pvbtXBihIMAu$-o*^x^gV z0&H$gUTVT9`K0tsPf7GClelx>+5S{__)~tAlarIFsp+Ff4AcL~UJZe}mo+gkpfIMQ zq}2c9cAoYJJ$}EtX6ovMY0^mK%YoJH?=>+SDr)NL_Gq;4o_DZ*<#$Juh8>2G_QAnH zpoh1yXPtwCBwdvq5%{GO_TzHh7RW-a%tq(A#+NV2(XUV?6T)8C=?1ANFW4Y4PBS>9R!9D=8^$n2M#}b6u2mof{j& z4!Fk8PdMFr=gys$mX?=c63j26qHygg%-Yp6&<+SbfLcSw2>z>g2V+O z#+Zk<4)w#<_9(-0F@l<*e8a73jKp8QLz9-2l=%7i37WS?l9JQLDN&n+=DNLM)^gyO zer5d0*$ItqZymTKpPa>Tj&AGEb$A+5Tf%0=Kg&DM`VQ4b6d89qGRSr9)r)Y8&2 zG<=(@NheWe(x7vb*2l|hVtjmY_ElkiY3U7i_V&(BtG_G#g)*Y{Mmig|*qN8uZyeH|Sg*8rT08SL!rB-Pm zS7NiX&E@68wTia3wgPrq4h2l=Ej}mqydIE;)Hz56Jn* z6R%Cre>rjy8Y^0CY}pwZ%t^14la+4b#sDC|a%&fj4W@|_dF9P~|4wo53_umCP}uF- zPRbhu*SuF!O3Ic#P4M0a+}fWHE6XMgtafAE8=C9t zXuFVvQLnQvFEX(Stfeg>oMJ?kzf0Wt`_)$dc?0P-2I3M)iAJ;D38{RhC7Wf;)PD;Y z|2HWqObiI~aR?TM8(LeW8bi*_- zA5QDc*g2J(&Gh%lEO0K&{$ATUakL8%Z<^=AVHS5wq}56NC&a3efT^qYs5>? zN!{Ay@?!QsGUcT<(9?UW$WfpZo0V=;We9J$c$F=UmD#uUf^G2wAy48XbMx%9w9o5% z0JH=VwjCQ}^@-J>#^iG=!5HV{AU=un@$nJN$|n?e374H5?x^HTRTT zZ+P>3X5n1fF{IXrWRQ}$i$^O1l(k%asD7yd7DuKQ{w?BZIbP~cG~>pjfR9<~1(eDR zQoeuKzZG455#J>O`bkV1rf6w-E!t8*KmZ01$>gZ3qk{;5-h>4Tmm^9e(kscy0qt`_ z^CIu!Gh(ESl=bda1>ts51iqE;T{AZ~AxCt)a{l30p(m)>5j%zF*1RinMn-A#H8t|N zD0cF1GcyTH;xCSJ)C+D?+hO|I6 z(xRiO{}kX>Nl}rSmKIF;E#taH(lTFPUsbNx;P=kK@ZwTeej=u;AjFSX-otE;{pgD6 zI&+pHo+{4(|S_T3r~Y-~lR ziPs-IHI9Wc_+ZlTIy3XknG``7Z3(Fb6Zq?IRgWQxf%k}*cXo6%Y4&x4MfLZW%()wU z`mM7wZ=oxmmX>y7q#VpNX-wA-S69nANl8f_jkie4P8>`|J1B|YzkXF-^jsW`WuHe9 zhLz|RwH|xA@2zQ;8#Gz<5K@iV+S*o_w`KWel!K7u=2qgO%x$vrl4N1d0~%LW_&$RE z2n^Q}cs$zIhtK;JU@T(25`?!UTKuVYzVcj40NtF@%>2Bx_2;DTHMWpmynoNH+iA_1 zRzyvRn?7jP=*jnal`x6}RPIWDqHd8^ZC#zRXx<%8P zzqnVho)Vu7F?J*gJ?=&ECiV9AZf`r2zoTrKo0{^uI6nhD0r6G1k24xwA98UHfYd?X zK4_Se(006*4z}~g!w)$aP!n(kFB4hNdxNoxjKsNqy$FK1HnmVhI|CpXEM9Fb&H87< zdeG>Eq)Q z;p)$>jP1V#LOV7#7CdVFQZ9)sT$ElBrLSF-s?jwjPY;M(g>g9AQO_3l0xh9M$lj)F7~o79Gfq7 z3u^dbJnJ!2y_{Mz(N#jiC0B{|&~xnejt(rv2FNDEu{VA277GI>ik?S$_FNMY5doHx zmNs)458)tXlm4>4C-ZNE)5^H7uW!riBc!cF@hXZ%W5d}p3nL?8lHOR)o;~BDBoK-| z5{!pWTh?y7clYkoYhep40x>@>7>Ij~PpR80p4p8R?j8qPeN$AxMg%s64X<}i@GfJw zPfot+Th8s!#1aISg93sU)3eS)@v6LFel@VVxw%qD9gmL}iTT z<>&tiQ49`s5C#AX#X^HNHG6w|!otEXbB$4qnNmL684ydrL@*d;5j6QP>R834 zA3v%tBRx9vI+q3MC;yQXcR`*QugrL`zmMB5%ObnA>DdUcw4ygKHDzI701VSWivc#! z(9%lYxdW{?G&V*h5eQ=ha)4A;ow!T-;?(0?8+|khi{$v|=;+XpfT$=3AreuS4ojs{U9z+H9q?P68e1{14#QQ|HMW-W*}qU2K{QRc^Lmn4nrfw~Nzj|Tk` z9)6`>{&{bX*vBOzu22mgs#rx3N-x)s0$XL-k?%Yy+E@`>h)k`DvNEpsW)cMLtL|~n zo>?TRju$|f-+uH;mCJPr;u7rKb~#74@%x23C83(kY;0tlSOEb6U1D;3SV|7H(-3@+ zk&)@efsnD#)~47#QO#+9lK?cj820r&N$1=8`qRF6Cj8DEND6Wm6wr>2#8v-v6?!e4W9?l=wM?pdHcSn%Y9)TJ;K0Zc0 z19P3%YUx@1Wq3GbcL~>-sH&)F{V4?}?Jc8~^>uvC=Czd-uItyaKUrvK5dUQL1Jr~^ zWd|tlA8$A9yb&}PNbI)=($Uo=!5t6}SBN2nFv7Go@H=$~nE!6Ulu$2XX6tX2mat^) zf4h*s&JGt;cCM&c1_Vf9OfPNy*9i%Wc0}GuEzo{$F4oz~_(Ss`FUW75X6itQh`bke z-S_tPPTO$)P*Jh7v2m4{*rd*pVLFsBDtvf7PB9ON*5A)>Vrr_(xTmKF!pGaG0OGbU zzm1MwvTrWxDqS2qB);1VZ7!dul!FJb$Qc+O>FB&pOH)7nM=sgaUP(kkLIQLd#VVBK z7M3RBxq-rbhuj%*4zI1rp6>3&*76)W2!!sZ2;6gDxjZ1)Wztm43LpXSeoHT~s;>>! zYHbY!B`|1)N%f~sMJV3IME6P|HtE2(Sy`=6<5X?K>l+)si|tsDI}=7Nt*_gD<^<(u^Zxy1cQ~0D!d);4hjc`bKe_(< z%E?aK&hOt!GAT!-oy=WGs59C)Y;5cYnUXXtEG&$S&w@iN!JorH$&z~m%Bj4#_#5PA zm&+9pz$Oz+Jd>A`vk1cH_wV2J^>r{kIGOSC@&d&H!F}2tkHTF^eFFnL9Gv=whT7WN z%{gk!8l2=nRb3qQ$-o$P>mm8P2cHG0GrY+nU8dW~zklt|NjZn17BAUX7Ef74C105P ztG$@h^g9&q*Oy386?o<~aKa&4@RxvqfGr3^k*uDqUQj=*Q9SVz$})jb^k;88(@U1I zv9SaeDeoVhQJ^#yIaXqOtiog~?Z$YLU~BUXN_Xa4jNx2HQAp2v7@*=G#;)8TJtN~T zKn441MrLMye!klRPIXlk0T~$-4#LG7W2OE;<$9$wz3eaj?l(z9O) z=?E2w6d*VT+;($wgQJi7DAtYD{{{z@GHJxb#9)FO)dELPIF2Y8hQ#p)GDYl0!Rxts zd+X_qJGclSNjYcsG1CYz9R3PVn6MK2L9PT!Up(AvMAAQpS?6V2D7<>}2Jq}mpj4Ji z^(DJpI1T~j|NNty?F#5aJe4)omq!wR)Ar)-4#72eV1eMe0fJj_cXt*Ki(Am(?!nz%0))j0zCiE*0dCyk-RJue zU!5~uJ#%_`s;jQLdb(oNRpoIoDKP;60FI)9oF)K(AO-KMqa(uGLoLyHxWaH#(DwuY zu!#P5Apo*-$pHXNHG5fEb#*&eFIP`HS2sFESy?(a4_6y|Cu;z}cP&rLR$J?cRQzu9 zT1F{4HE_10eV=V4(vBRB{G*-$lLQ9m`}HA%uh@40t7f zR6-9S21xlQ$dm)5J|TqUWwK}i3XuV3P)mzlfI1hzjP>im0w5&sJ}UqLVECP$1feh) zKu2U9EeEg@1=P%Zi3I`lcmc#VN+ZI66)pg;qOOesprIWwFpZDZ1VG0I@M=Uyyayl$ z0L;c28GQlaSpecsm%3sX9Ci4I>~K$IHHmdH34Q@8WEl@T6J+g-2_;S8o6F4X7xob9HrpZ*S%Eh>W56 zgl^EQ?U3n!?u*kyu-Nn6&7Zy~G6&(P$xa@o6?6pU~S*g>NFb34}CGxY+) zh@wTO9y{@_2BCX`Tz32wUl~J;Ov?M8%=2$nJ6oJjND^BDfCdK%gn(qOAI6qQw@R$| zdR_xNcL2a;x9i|s|gILdIg5feuBSSLyGMH8&?>x-j*M!QjMNTzPvzzh!mZAm@XxK%j zy5VvU>2%noOroMhh-W+a$=-fLK_)su&YNY z8@|*ab;sOT&NPCN2_c{X3_)bD44pZRw1S?pp6ZIi54v|uK!P<4Aq=E&jUiTs)E`PM zEL{W-Bjgr*AENXWI2o4+6Nz~61EOU|gqR3pWcgS-lModfOSR_-=ReK6XfhsgR^|W| zC>c|KtywuXp$UKIq~jQm*vs1+-s9h++M~ZT#LTmhaxcBt+F{e2aQIBIkG~JxN43u6 zlh!H8*4+3}q)i}@XdqKt)TUXl5%q%z=$$P*6-KJ4R90J@wm@k|W_M>_|JSRFU?Yu2 zHDgwH9(pAIH{&nTH5eNwk|b;_+LnzXha8REmI6#(pQ&6%eK?A@6!)ZKC7Rh31;vizUzS~Hm?Pj{)=X$AP9I{npTm{E&5}&`{c~4ai zRPOv>ZRA*$o{_Fstx&B|Eq~H0Sa({HSKbTi)_;QFN{@YJHDZ;wO`Jx_JJed$+Ni*+ zAXO0;xvAr>ax9}N>XrldyHuQI=J%O|z}ptxI`ET>zC>27#KHys?DoqUh1yxt{|xBR83W_&R?<}Zn$Xs)mGU~(XhXQ zx?b1j+!kbRYe8n){TWs?m+{-o4LWH}1yU z{_{6^In6mkr$5%~&jiox&!S)jlfZ)a`TG2xv%}-Ee>C>zB(Vz8cpbNDg4JZzAhxZC zj69Y+$-1@64ee#%syp*@GKpIWkAhu^T`{3ezD<48Nuh!E@ph=%uxq|)#Fp$=#y6`s z|F=0nMF=y3Em90b8e)j}bnhQX-=82UvTl9bw0bHXU)U*4BApmo8@fMOOK&&+FR7OP z?l5yYdD@FLsGYnWtla>z zOrCLLH@~9kq3ehW(n@YZPLXseM!Z>NMqgxe`?Wsyp{T-168y)_QPO=9kT>zKPFC>)VrP;10G zV%ylA7uBjybu;RMds+Nd93duy6QP5x2#zOMI+*(rK_{@~Q)4wf9{sIW^T)_MDhZO9 z*gE2MvJ8^1D{L^|eX1zeiT*6u_m;})kxr`_n|Yg~gH5h{L)`SLjDrkZ7o63#7M!gw zEvGlPpmm;%DYo_Qur}Gwl76v{9_xx<(^I07+NIjp+K<|TXY(7a=F9gkb7xcmJ-5Jr zi|d9xcP|pZ`qp(2bW|2?7OkHYpPKI^>td>o?$e)~jcZk%-A~7m=8;~|7;)Q{JAW@y z&x>noCN6QD$m5RD_PyqvgSl`7adf`rj_xqJ$@%Ra-C=Cq(cp-z`NRj*Y@;pE`cT_a z^Ek+4UuJ*G(aL@@v@q;ao0OPaI^OhumS3mE%LR(*`+dIB{$;;*6uOXVdpQ%ImzEb4 z%>4i}TQ6=4IJBJJa=>#v@51-mo(H|f9g6mdP8(fzcwIKF?-S{E=w{c8m^vSO9gDrD zE~t%l=XPh;bs9N#p85B-2H4ykQRNFwZ(R5KU68&lE#?c0Z}eXIyWai0yV)Y$2=-uo znTB?+f?3aMd(?VNucc0$s?u<(wg$IH=kh3t&g3`;)QYw;_zj*OHi_KPCykrYcZ7-1SE8CmhL*tFsY{gS5MQZ9BGE(N% zvn4;*I{beV*;ZOb8USca2z=5=h2PUzDrl+z0RGGXKu8z>@bCuLM*x5~F92|C1^|d; z0sv&LDW*R^0RY6UigME0zH9%Cjh(cAlJ$2x7n!!X46=Lytw`mtRjx5)-xdYe5C%#S zHIMwD&&(w8o0GB_)GnfgIxrNoUB}{;Gf^&u$XLX}(ihbmiq_LVBeeE95-AYH_M$rv zUpDr>MU`C9o=@iX2DBTliS$R=1RPG^=KTT0N7@CFWlB0 zvdgQgBF&=>Sc>N-CO$6!RThBQ!o(l~z@;{dz_# zKFciEH#FopPGIA)=Wjm_=U@ee%g`GygP^j=g%MJ8Av2+Ns6_SHb14zU0xFiKv)*~} z#Lo>aJcKa=M8mwCPG59%3T7&**q`y|^sKGRq9tXA<#x$2BUaL+?{wwDOL>ZMpb5&! z61UJ(Vtn`r=Ju)Zq@=$4mtgLQ;WN(9Kba9#7XEub>rGA87dl8Ms?yM#vbKr>a8_La z+Gmr-$fcB-$P7oCH(1K}hkNFYywd~DN)*($wWW)ywYIj-vYa71W}^l$DHThxD#V2i z()G1`ISrgtOvb6uiL{N(!y@7#(0~6tP0+KQWr8tHcn~@y5i4j{&k!hL@ zqgCBDPE$umU7&N}{KD6a6qJIb9A@XJtE02*E!Q)aMbgyG_;TalJ7}h^qa!FPs;KyD z{u(Hn3hP*$3nI7?*?tZcVO+xm4Y4r(7kyh2 zURkKnWWB~}_Q%l3W5Ceb(OEd@uNgjLGI}?+I1Qdt0q-E}b)d_9k)(-5O4X-<%RZZB zO@NRu7wGI$37s+W`ObibIyzK_taO^50zn}BLy871uE+@gF~xu(X8zS21R+nuy&}no zbf$d9pL&-;K}6KhjH04E%h`G#D8Zl~gG%X3%{B}pLGma;LPR~nrFWGRSBeOE3GTNf zB5Oi}yE0idUmb09UD@fzh%z!Q*6`*(gl!KqzW(AA_bl3(xW6+#zQ#io;V1?F^reQ3 zk`+cC+_C@R;A(qXgxXxeQERck!;Q;}itpJno<81MumL>=_k=)~+f=I4A}OwNK2_wPajR8uCv$aGEH+<0*<+m5n70}1HF?&Zc$FTqB|A#0$BT?1N+XJW0^$~n!Z@i{a@ALODCREy{SMt@t`rQ2>X&># zo;?1cSlO!TWoD7bfc!7sl-|kb662%FJ95dqfU=J+wg;Ha0}*IocX#(5`jwWX=W>FB zj{hmJ&kjiW?GttU&0)dUV66mC0=W{6vAz;s;$PFWTyu@{d4KEKbcYs?%<{^bV5|*3 zV)e}_7e)-ODo;G?TZ62gq$v$|I|Y$!Vy3(vxVfBYs60woDcQ5+NY@WWCdd=8a`|niJ!i!4{)FMl zd|gE~%>VuSca2^n10!P?i>e;c;Y`8Z)5{&qa|Vqzu)%(TB?H>(f8zks*!=qT!l7Fi zpO{ESOx*K)3B5X;(IwtrP?Ho7y#Eg_j`h8_FLzr!dJSe~W`!Lc_c^Y85Wc&At3=qS zyBGI^VQ62UPKB~xsiRjhK-br03|5v}?rwDU6~!sa34cW)$HQ-?#w~VpOSOiyLDzF` zAA4Ljy88Mgpw(lEv{9{nUPqcr^SyVQ0e2hVySux$*Qaxs15NP#_Ve@e>+3570>PlI z5%xToERgIkE!`V{AX(*CRaaX!>bbg}w>mC&bauQjuS@&+Jq!#C>_aoKFfk8i3f`bR z{cN$9+flT2hV6CL)mV0!bTo;&+S+Wfe0+TKTU+No-NxL@D=RDTDK4+B>P>nLbaWPH zXYsL7g`C&4H8nN$^vL6Wj*g98U0toOt=*okSDZZd_+91uA6Lf4#`=m$I4;$|V6f!m zH0`uEc)3kV2MP*AL{t}5QNmzI|H_V!j)eb)G1RaJF+>!a}Uw`*>6#+6#`CZw3G1~K4I&hE6)4o-CR%GXD)XHeyRB;#g8&WqhfnyQ(rN@02_1VtV zm4}B%9-X|c1r$e$aKmld_pZLIsAv#4RbI}bmzN~Y$XHTY8593eu~l|}JHzqrY@=Ae z^z_`@+uP?cU@PcOI&o;5_dhp~20XSZ3)oS^AP^`=$W_992e}5*3r>{f6~^r!ah4MFK=;i5&i%L1?AdtYHF(7UQkFVl@&fWUevG@QiW>X z7NwdBzPNQokn#=`Tx{XymUIoavx~m|QBhIR-VVOl`H4lw-Q$0AT&Y#z(tEQ6w-@%T zZe;X4O63}#kdU61*8ATOR)t!@W|K7(p0rRXwBEFTddmCd@dlo%dYtePI$B!rU>^H) zD=8^CIygu??4`mfaQq4p!|;zoXB;UP1KBe{Cy*F3Ej=BP%-PB59TimoJR)y!csrF<4enPlxDi%n3nLI#`Eb?!7pn*>Tjq5? z&q$;%=8ldWj;P}blGDNMEe7R1w&t5QqZ}nn0ZhPYd3&6y8!b2>O+Iv{1C#m6wMpQBq5_o+# z`g7lYx}g|N;!jE=ZEY_OJb21pI|>U6!xKtjTVG!v&P?D9Z!+;Xc<15aA>EPrpWz0a zP9eE`{QPjez1`-#?qpEZ+|=|}L&WD_X1Tq6lMHJ$^UgH=TSSU2;j8z@HnYJ{6f9Es zDjU1I4b20YFtM->r}IR`#GaQ;zrKI-IkSfD`@7GsON-HfH|;8>ss4x|<8ZLEuXTB! zFeZw5T>qVWeDt5g5c~$;W6r09ww&zk-8+pUA|l}K?}Gyg4sAHg(|&z+Z(nyNM~&Wx zbKy7?%E8I$)oJARxd)DG@TQ;ba3l_%_%*)Q7H|rVqx}5*m#4b|5pRbxSSL^4{f=d# zxJ@>T9TX0KV`F1Do49=nYMzGJn*8D?#is$SAu`D&wVSBgODPB>^M)Z~7S-dn!LAez z_ctTuk$yqHF@0Kgd^nDVk>Lz+ow-l+&c<2oVY+tJrIP)%{w9x9)PF)pPra(`Vct}R z&$L5BeUKRNz@L&aJ^n)XeT3G^)t@dyhLIoru2=6FbU&dUt@{L34+c=Zq$ zE6ABH;DwG_H02wyA~%2N9~!3|#aaHdwGe+00ev~M*^-V92}&44?qG@>4|?L5W39rF+qQd`tw!Mi3DpZ*=#+G~@7Uw@wOUPKgw! zLS8W*@h>p7Li&03OGt|y6MRUG$l1?70~%5ov2qMXpTi83KnrM$oY`1Ryz{{H0+7BftW%V%o92%iuOzN%#Xh95f z(l^sss(@3&A_5AG%H=GQY(W>#qY+KMWz28a=z=J14qeTKmh0_IaZU$LCh5~h@lG)D zY$A_S#_LO|0|l>bdZTnE2D9k=;mEX6+rfELCBn{CL$5A+`VZdbtibYcxQzS`nK_ho z3KAS_HB-3|j055tFT3NW_pLEAr68@GXlh??t556G%P}qjrw%q+1lKuZ5kVXVHH$dk z>%s>~-?Ywd5_q=KGqIJ&Z>kfp=xv%yr(vhsV*-2&yx3t>3e4LA%L=C${N;Q@l|44B zqF^8)YKKCvBVmkmjUVSs5#D2`eA5UxBaKR&MvaM3I+Jiaz2*tQ8Rx*D-=5Z3sj6(M z7&Yz-jr#>jXGW5ZXBM7^RD@pt5mOzq>SV<~&e>18b>V8@tdm*HpDbQ2ZP#RyMRY7+ zHVBQfg(8c*bPG^UQaZ3m@zg8FUCrL-hixbiu!Do$Vj0E4fh>9T^c4QdPy71t}l}_BP89W%Gh!(du$Kp8J5xvMIftfe=lVlL*(#cT-t6o(kuzG~t42UAo5tRUynrsb8h z$`v!sM#Ipv=b`30OWpZog}#PWR7VGvq6W~hKSg3 z2bTk5SAxI}LpJE+IV9D7DLOKLPUo-17n#2@d~~zswWS+9wJ&8Ppum(&0gYs3$@Pv` iBoqFB7A?#-DnMEuce%z0KN`H)2~Y&7%GJr3h5jGZ9!?|x diff --git a/fonts/libtcod/prestige12x12_gs_tc.png b/fonts/libtcod/prestige12x12_gs_tc.png deleted file mode 100755 index a4b99ccd0a5edf2e7705363bb1bfe54063aa1c84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7794 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 diff --git a/fonts/libtcod/prestige8x8_gs_tc.png b/fonts/libtcod/prestige8x8_gs_tc.png deleted file mode 100755 index 179450496694a5c66965dcae290c135a54c06e88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5516 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 From fd121501c4069e63c90d3f0907b0426905ca4ce8 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Apr 2020 10:03:18 -0700 Subject: [PATCH 0307/1101] Update libtcod. --- CHANGELOG.rst | 6 +++++- libtcod | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a2b5cbf5..5ff7d54c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Changed + - Using `libtcod 1.16.0-alpha.10`. + +Fixed + - Fixed characters being dropped when color codes were used. 11.11.3 - 2020-04-24 -------------------- @@ -18,7 +23,6 @@ Fixed - `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant. `libtcod#48 `_ - The `TCOD_VSYNC` environment variable was being ignored. - - Fixed characters being dropped when color codes were used. 11.11.2 - 2020-04-22 -------------------- diff --git a/libtcod b/libtcod index dc0257d9..1ce60c09 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit dc0257d97a96670847eb272901d2ef49b712411e +Subproject commit 1ce60c0919b71d3dd7fc28df4bfef7094e0e6911 From 0f97b500c34627c65f4a9ae693a2b650fb0c39c3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Apr 2020 11:52:56 -0700 Subject: [PATCH 0308/1101] Remove non-ABI3 Py3.5 jobs from AppVeyor. --- appveyor.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 282b3a98..1b994e55 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,10 +8,6 @@ environment: DEPLOY_ONLY: false EXTRA_SETUP_ARGS: "" matrix: - - PYTHON: C:\Python35-x64\python.exe - platform: x64 - - PYTHON: C:\Python35\python.exe - platform: Any CPU - PYTHON: C:\Python35-x64\python.exe platform: x64 EXTRA_SETUP_ARGS: "--py-limited-api cp35" From 201e014fb328fb69b6577c88bbdd8cc9e844187e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Apr 2020 11:54:22 -0700 Subject: [PATCH 0309/1101] Prepare 11.11.4 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5ff7d54c..f08f3cd5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.11.4 - 2020-04-26 +-------------------- Changed - Using `libtcod 1.16.0-alpha.10`. From 6208d2d19fc7544a8eac88b63d77be131aac6088 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 27 Apr 2020 02:11:46 -0700 Subject: [PATCH 0310/1101] Update EventDispatch to pass return values. This also has a major update to the example for this class. --- CHANGELOG.rst | 3 + tcod/event.py | 194 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 156 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f08f3cd5..dda8bd2a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - `EventDispatch.dispatch` can now return the values returned by the `ev_*` + methods. The class is now generic to support type checking these values. 11.11.4 - 2020-04-26 -------------------- diff --git a/tcod/event.py b/tcod/event.py index cb0ecfaa..2f08bb2a 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -20,7 +20,19 @@ .. versionadded:: 8.4 """ -from typing import Any, Dict, Mapping, NamedTuple, Optional, Iterator, Tuple +import warnings +from typing import ( + Any, + Callable, + Dict, + Generic, + Mapping, + NamedTuple, + Optional, + Iterator, + Tuple, + TypeVar, +) import tcod.event_constants from tcod.loader import ffi, lib @@ -28,6 +40,9 @@ from tcod.event_constants import KMOD_SHIFT, KMOD_CTRL, KMOD_ALT, KMOD_GUI +T = TypeVar("T") + + class _ConstantsWithPrefix(Mapping[int, str]): def __init__(self, constants: Mapping[int, str]): self.constants = constants @@ -731,47 +746,141 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: return get() -class EventDispatch: - """This class dispatches events to methods depending on the events type +class EventDispatch(Generic[T]): + '''This class dispatches events to methods depending on the events type attribute. To use this class, make a sub-class and override the relevant `ev_*` methods. Then send events to the dispatch method. + .. versionchanged:: 11.12 + This is now a generic class. The type hists at the return value of + :any:`dispatch` and the `ev_*` methods. + Example:: import tcod import tcod.event - class State(tcod.event.EventDispatch): - def ev_quit(self, event): - raise SystemExit() - - def ev_keydown(self, event): + MOVE_KEYS = { # key_symbol: (x, y) + # Arrow keys. + tcod.event.K_LEFT: (-1, 0), + tcod.event.K_RIGHT: (1, 0), + tcod.event.K_UP: (0, -1), + tcod.event.K_DOWN: (0, 1), + tcod.event.K_HOME: (-1, -1), + tcod.event.K_END: (-1, 1), + tcod.event.K_PAGEUP: (1, -1), + tcod.event.K_PAGEDOWN: (1, 1), + tcod.event.K_PERIOD: (0, 0), + # Numpad keys. + tcod.event.K_KP_1: (-1, 1), + tcod.event.K_KP_2: (0, 1), + tcod.event.K_KP_3: (1, 1), + tcod.event.K_KP_4: (-1, 0), + tcod.event.K_KP_5: (0, 0), + tcod.event.K_KP_6: (1, 0), + tcod.event.K_KP_7: (-1, -1), + tcod.event.K_KP_8: (0, -1), + tcod.event.K_KP_9: (1, -1), + tcod.event.K_CLEAR: (0, 0), # Numpad `clear` key. + # Vi Keys. + tcod.event.K_h: (-1, 0), + tcod.event.K_j: (0, 1), + tcod.event.K_k: (0, -1), + tcod.event.K_l: (1, 0), + tcod.event.K_y: (-1, -1), + tcod.event.K_u: (1, -1), + tcod.event.K_b: (-1, 1), + tcod.event.K_n: (1, 1), + } + + COMMAND_KEYS = { # key_symbol: command_name + tcod.event.K_ESCAPE: "escape", + } + + + class State(tcod.event.EventDispatch[None]): + """A state-based superclass that converts `events` into `commands`. + + The configuration used to convert events to commands are hard-coded + in this example, but could be modified to be user controlled. + + Subclasses will override the `cmd_*` methods with their own + functionality. There could be a subclass for every individual state + of your game. + """ + + def ev_quit(self, event: tcod.event.Quit) -> None: + """The window close button was clicked or Alt+F$ was pressed.""" print(event) + self.cmd_quit() - def ev_mousebuttondown(self, event): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: + """A key was pressed."" + print(event) + if event.sym in MOVE_KEYS: + # Send movement keys to the cmd_move method with parameters. + self.cmd_move(*MOVE_KEYS[event.sym]) + elif event.sym in COMMAND_KEYS: + # Send command keys to their respective cmd_X method. + func = getattr(self, "cmd_" + COMMAND_KEYS[event.sym]) + func() + + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: + """The window was clicked."" print(event) - def ev_mousemotion(self, event): + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: + """The mouse has moved within the window.""" print(event) + def cmd_move(self, x: int, y: int) -> None: + """Intent to move: `x` and `y` is the direction, both may be 0.""" + print("Command move: " + str((x, y))) + + def cmd_escape(self) -> None: + """Intent to exit this state.""" + print("Command escape.") + self.cmd_quit() + + def cmd_quit(self) -> None: + """Intent to exit the game."" + print("Command quit.") + raise SystemExit() + + root_console = tcod.console_init_root(80, 60) state = State() while True: - for event in tcod.event.wait() + tcod.console_flush() + for event in tcod.event.wait(): state.dispatch(event) - """ + ''' # noqa: E501 - def dispatch(self, event: Any) -> None: + def dispatch(self, event: Any) -> Optional[T]: """Send an event to an `ev_*` method. - `*` will be the events type converted to lower-case. + `*` will be the `event.type` attribute converted to lower-case. - If `event.type` is an empty string or None then it will be ignored. + Values returned by `ev_*` calls will be returned by this function. + This value always defaults to None for any non-overridden method. + + .. versionchanged:: 11.12 + Now returns the return value of `ev_*` methods. + `event.type` values of None are deprecated. """ - if event.type: - getattr(self, "ev_%s" % (event.type.lower(),))(event) + if event.type is None: + warnings.warn( + "`event.type` attribute should not be None.", + DeprecationWarning, + stacklevel=2, + ) + return None + func = getattr( + self, "ev_%s" % (event.type.lower(),) + ) # type: Callable[[Any], Optional[T]] + return func(event) def event_get(self) -> None: for event in get(): @@ -781,79 +890,82 @@ def event_wait(self, timeout: Optional[float]) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: Quit) -> None: + def ev_quit(self, event: Quit) -> T: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: KeyDown) -> None: + def ev_keydown(self, event: KeyDown) -> T: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: KeyUp) -> None: + def ev_keyup(self, event: KeyUp) -> T: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: MouseMotion) -> None: + def ev_mousemotion(self, event: MouseMotion) -> T: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: MouseButtonDown) -> None: + def ev_mousebuttondown(self, event: MouseButtonDown) -> T: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: MouseButtonUp) -> None: + def ev_mousebuttonup(self, event: MouseButtonUp) -> T: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: MouseWheel) -> None: + def ev_mousewheel(self, event: MouseWheel) -> T: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: TextInput) -> None: + def ev_textinput(self, event: TextInput) -> T: """Called to handle Unicode input.""" - def ev_windowshown(self, event: WindowEvent) -> None: + def ev_windowshown(self, event: WindowEvent) -> T: """Called when the window is shown.""" - def ev_windowhidden(self, event: WindowEvent) -> None: + def ev_windowhidden(self, event: WindowEvent) -> T: """Called when the window is hidden.""" - def ev_windowexposed(self, event: WindowEvent) -> None: + def ev_windowexposed(self, event: WindowEvent) -> T: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`tcod.console_flush` is necessary. """ - def ev_windowmoved(self, event: WindowMoved) -> None: + def ev_windowmoved(self, event: WindowMoved) -> T: """Called when the window is moved.""" - def ev_windowresized(self, event: WindowResized) -> None: + def ev_windowresized(self, event: WindowResized) -> T: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: WindowResized) -> None: + def ev_windowsizechanged(self, event: WindowResized) -> T: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: WindowEvent) -> None: + def ev_windowminimized(self, event: WindowEvent) -> T: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: WindowEvent) -> None: + def ev_windowmaximized(self, event: WindowEvent) -> T: """Called when the window is maximized.""" - def ev_windowrestored(self, event: WindowEvent) -> None: + def ev_windowrestored(self, event: WindowEvent) -> T: """Called when the window is restored.""" - def ev_windowenter(self, event: WindowEvent) -> None: + def ev_windowenter(self, event: WindowEvent) -> T: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: WindowEvent) -> None: + def ev_windowleave(self, event: WindowEvent) -> T: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: WindowEvent) -> None: + def ev_windowfocusgained(self, event: WindowEvent) -> T: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: WindowEvent) -> None: + def ev_windowfocuslost(self, event: WindowEvent) -> T: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: WindowEvent) -> None: + def ev_windowclose(self, event: WindowEvent) -> T: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: WindowEvent) -> None: + def ev_windowtakefocus(self, event: WindowEvent) -> T: + pass + + def ev_windowhittest(self, event: WindowEvent) -> T: pass - def ev_windowhittest(self, event: WindowEvent) -> None: + def ev_(self, event: Any) -> T: pass From 89a4e382d3ce7a9200faa7d5b3af84bb3e9a0af9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 27 Apr 2020 10:39:52 -0700 Subject: [PATCH 0311/1101] Add context module. Mouse coordinates are now int types. Import modules on the top package so that you don't need to manually import them. --- CHANGELOG.rst | 6 + docs/index.rst | 1 + docs/tcod/context.rst | 5 + tcod/__init__.py | 28 ++++ tcod/_internal.py | 9 +- tcod/context.py | 349 ++++++++++++++++++++++++++++++++++++++++++ tcod/event.py | 4 +- tcod/libtcodpy.py | 2 +- 8 files changed, 400 insertions(+), 4 deletions(-) create mode 100644 docs/tcod/context.rst create mode 100644 tcod/context.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dda8bd2a..83d2e9bd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,9 +8,15 @@ v2.0.0 Unreleased ------------------ +Added + - Added `tcod.context` module. You now have more options for making libtcod + controlled contexts. + Changed - `EventDispatch.dispatch` can now return the values returned by the `ev_*` methods. The class is now generic to support type checking these values. + - Event mouse coordinates are now strictly int types. + - Submodules are now implicitly imported. 11.11.4 - 2020-04-26 -------------------- diff --git a/docs/index.rst b/docs/index.rst index 5741959a..ebeeeef1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,6 +29,7 @@ Contents: tcod/bsp tcod/console + tcod/context tcod/event tcod/image tcod/map diff --git a/docs/tcod/context.rst b/docs/tcod/context.rst new file mode 100644 index 00000000..fbd3b2f5 --- /dev/null +++ b/docs/tcod/context.rst @@ -0,0 +1,5 @@ +tcod.context +============ + +.. automodule:: tcod.context + :members: diff --git a/tcod/__init__.py b/tcod/__init__.py index 08624a79..6a059121 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -18,6 +18,21 @@ from tcod.loader import lib, ffi, __sdl_version__ # noqa: F4 from tcod.libtcodpy import * # noqa: F4 +from tcod import ( + bsp, + color, + console, + context, + event, + image, + map, + noise, + path, + random, + tileset, +) +from tcod.console import Console # noqa: F401 + try: from tcod.version import __version__ except ImportError: # Gets imported without version.py by ReadTheDocs @@ -25,6 +40,19 @@ __all__ = [ # noqa: F405 "__version__", + "bsp", + "color", + "console", + "context", + "event", + "tileset", + "image", + "map", + "noise", + "path", + "random", + "tileset", + "Console", # --- From libtcodpy.py --- "Color", "Bsp", diff --git a/tcod/_internal.py b/tcod/_internal.py index d730f517..d58c5d42 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,7 +1,7 @@ """This module internal helper functions used by the rest of the library. """ import functools -from typing import Any, AnyStr, Callable, TypeVar, cast +from typing import Any, AnyStr, Callable, NoReturn, TypeVar, cast import warnings import numpy as np @@ -57,10 +57,15 @@ def handle_order(shape: Any, order: str) -> Any: return tuple(reversed(shape)) +def _raise_tcod_error() -> NoReturn: + """Raise an error from libtcod, this function assumes an error exists.""" + raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode("utf-8")) + + def _check(error: int) -> int: """Detect and convert a libtcod error code it into an exception.""" if error < 0: - raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) + _raise_tcod_error() return error diff --git a/tcod/context.py b/tcod/context.py new file mode 100644 index 00000000..cdb3aa40 --- /dev/null +++ b/tcod/context.py @@ -0,0 +1,349 @@ +"""This module is used to create and handle libtcod contexts. + +.. versionadded:: 11.12 +""" +import sys +import os + +from typing import Any, Optional, Tuple, Union + +import tcod +from tcod._internal import _check, _check_warn +from tcod.loader import ffi, lib +import tcod.event +import tcod.tileset + + +__all__ = ( + "Context", + "new_window", + "new_terminal", + "SDL_WINDOW_FULLSCREEN", + "SDL_WINDOW_FULLSCREEN_DESKTOP", + "SDL_WINDOW_HIDDEN", + "SDL_WINDOW_BORDERLESS", + "SDL_WINDOW_RESIZABLE", + "SDL_WINDOW_MINIMIZED", + "SDL_WINDOW_MAXIMIZED", + "SDL_WINDOW_INPUT_GRABBED", + "SDL_WINDOW_ALLOW_HIGHDPI", + "RENDERER_OPENGL", + "RENDERER_OPENGL2", + "RENDERER_SDL", + "RENDERER_SDL2", +) + +SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN +"""Exclusive fullscreen mode. + +It's generally not recommended to use this flag unless you know what you're +doing. +`SDL_WINDOW_FULLSCREEN_DESKTOP` should be used instead whenever possible. +""" +SDL_WINDOW_FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP +"""A borderless fullscreen window at the desktop resolution.""" +SDL_WINDOW_HIDDEN = lib.SDL_WINDOW_HIDDEN +"""Window is hidden.""" +SDL_WINDOW_BORDERLESS = lib.SDL_WINDOW_BORDERLESS +"""Window has no decorative border.""" +SDL_WINDOW_RESIZABLE = lib.SDL_WINDOW_RESIZABLE +"""Window can be resized.""" +SDL_WINDOW_MINIMIZED = lib.SDL_WINDOW_MINIMIZED +"""Window is minimized.""" +SDL_WINDOW_MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED +"""Window is maximized.""" +SDL_WINDOW_INPUT_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED +"""Window has grabbed the input.""" +SDL_WINDOW_ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI +"""High DPI mode, see the SDL documentation.""" + +RENDERER_OPENGL = lib.TCOD_RENDERER_OPENGL +"""A renderer for older versions of OpenGL. + +Should support OpenGL 1 and GLES 1 +""" +RENDERER_OPENGL2 = lib.TCOD_RENDERER_OPENGL2 +"""An SDL2/OPENGL2 renderer. Usually faster than regular SDL2. + +Recommended if you need a high performance renderer. + +Should support OpenGL 2.0 and GLES 2.0. +""" +RENDERER_SDL = lib.TCOD_RENDERER_SDL +"""Same as RENDERER_SDL2, but forces SDL2 into software mode.""" +RENDERER_SDL2 = lib.TCOD_RENDERER_SDL2 +"""The main SDL2 renderer. + +Rendering is decided by SDL2 and can be changed by using an SDL2 hint: +https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER +""" + + +def _handle_tileset(tileset: Optional[tcod.tileset.Tileset]) -> Any: + """Get the TCOD_Tileset pointer from a Tileset or return a NULL pointer.""" + return tileset._tileset_p if tileset else ffi.NULL + + +def _handle_title(title: Optional[str]) -> str: + """Return title, or if title is None then return a decent default title.""" + if title is None: + title = os.path.basename(sys.argv[0]) + return title + + +class Context: + """Context manager for libtcod context objects. + + You should use :any:`tcod.context.new_terminal` or + :any:`tcod.context.new_window` to create a new context. + """ + + def __init__(self, context_p: Any): + """Creates a context from a cffi pointer.""" + self._context_p = context_p + + @classmethod + def _claim(cls, context_p: Any) -> "Context": + return cls(ffi.gc(context_p, lib.TCOD_context_delete)) + + def __enter__(self) -> "Context": + """This context can be used as a context manager.""" + return self + + def close(self) -> None: + """Delete the context, closing any windows opened by this context. + + This instance is invalid after this call.""" + if hasattr(self, "_context_p"): + ffi.release(self._context_p) + del self._context_p + + def __exit__(self, *args: Any) -> None: + """The libtcod context is closed as this context manager exits.""" + self.close() + + def present( + self, + console: tcod.console.Console, + *, + keep_aspect: bool = False, + integer_scaling: bool = False, + clear_color: Tuple[int, int, int] = (0, 0, 0), + align: Tuple[float, float] = (0.5, 0.5) + ) -> None: + """Present a console to this context's display. + + `console` is the console you want to present. + + If `keep_aspect` is True then the console aspect will be preserved with + a letterbox. Otherwise the console will be stretched to fill the + screen. + + If `integer_scaling` is True then the console will be scaled in integer + increments. This will have no effect if the console must be shrunk. + You can use :any:`tcod.console.recommended_size` to create a console + which will fit the window without needing to be scaled. + + `clear_color` is an RGB tuple used to clear the screen before the + console is presented, this will affect the border/letterbox color. + + `align` is an (x, y) tuple determining where the console will be placed + when letter-boxing exists. Values of 0 will put the console at the + upper-left corner. Values of 0.5 will center the console. + """ + clear_rgba = (clear_color[0], clear_color[1], clear_color[2], 255) + options = { + "keep_aspect": keep_aspect, + "integer_scaling": integer_scaling, + "clear_color": clear_rgba, + "align_x": align[0], + "align_y": align[1], + } + console_p = console.console_c + with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts: + _check( + lib.TCOD_context_present( + self._context_p, console_p, viewport_opts + ) + ) + + def pixel_to_tile(self, x: int, y: int) -> Tuple[int, int]: + """Convert window pixel coordinates to tile coordinates.""" + with ffi.new("int[2]", (x, y)) as xy: + _check( + lib.TCOD_context_screen_pixel_to_tile_i( + self._context_p, xy, xy + 1 + ) + ) + return xy[0], xy[1] + + def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: + """Convert window pixel coordinates to sub-tile coordinates.""" + with ffi.new("double[2]", (x, y)) as xy: + _check( + lib.TCOD_context_screen_pixel_to_tile_d( + self._context_p, xy, xy + 1 + ) + ) + return xy[0], xy[1] + + def convert_event( + self, event: Union[tcod.event.MouseState, tcod.event.MouseMotion] + ) -> None: + """Fill in the tile coordinates of a mouse event using this context.""" + event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) + if isinstance(event, tcod.event.MouseMotion): + prev_tile = self.pixel_to_tile( + event.pixel[0] - event.pixel_motion[0], + event.pixel[1] - event.pixel_motion[1], + ) + event.tile_motion = tcod.event.Point( + event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1] + ) + + def save_screenshot(self, path: Optional[str] = None) -> None: + """Save a screen-shot to the given file path.""" + c_path = path.encode("utf-8") if path is not None else ffi.NULL + _check(lib.TCOD_context_save_screenshot(self._context_p, c_path)) + + def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None: + """Change the active tileset used by this context.""" + _check( + lib.TCOD_context_change_tileset( + self._context_p, _handle_tileset(tileset) + ) + ) + + def recommended_console_size( + self, min_columns: int = 1, min_rows: int = 1 + ) -> Tuple[int, int]: + """Return the recommended (columns, rows) of a console for this + context. + + The times where it's the most useful to call this method are: + + * After the context is created, even if the console was given a + specific size. + * After the :any:`change_tileset` method is called. + * After any window resized event, or any manual resizing of the window. + + `min_columns`, `min_rows` are the lowest values which will be returned. + """ + with ffi.new("int[2]") as size: + _check( + lib.TCOD_context_recommended_console_size( + self._context_p, size, size + 1 + ) + ) + return max(min_columns, size[0]), max(min_rows, 1) + + @property + def renderer_type(self) -> int: + """Return the libtcod renderer type used by this context.""" + return _check(lib.TCOD_context_get_renderer_type(self._context_p)) + + @property + def sdl_window_p(self) -> Any: + """Return a cffi SDL_Window pointer. + + TypeError will be raised if this context does not have an SDL window. + + This pointer will become invalid if the context is closed or goes out + of scope. + """ + sdl_window_p = lib.TCOD_context_get_sdl_window(self._context_p) + if not sdl_window_p: + raise TypeError("This context does not have an SDL window.") + return sdl_window_p + + +def new_window( + width: int, + height: int, + *, + renderer: Optional[int] = None, + tileset: Optional[tcod.tileset.Tileset] = None, + vsync: bool = True, + sdl_window_flags: Optional[int] = None, + title: Optional[str] = None +) -> Context: + """Create a new context with the desired pixel size. + + `width` and `height` is the desired pixel resolution of the window. + + `renderer` is the desired libtcod renderer to use. + Typical options are :any:`tcod.context.RENDERER_OPENGL2` for a faster + renderer or :any:`tcod.context.RENDERER_SDL2` for a reliable renderer. + + `tileset` is the font/tileset for the new context to render with. + The fall-back tileset available from passing None is useful for + prototyping, but will be unreliable across platforms. + + `vsync` is the Vertical Sync option for the window. The default of True + is recommended but you may want to use False for benchmarking purposes. + + `sdl_window_flags` is a bit-field of SDL window flags, if None is given + then a default of :any:`tcod.context.SDL_WINDOW_RESIZABLE` is used. + There's more info on the SDL documentation: + https://wiki.libsdl.org/SDL_CreateWindow#Remarks + + `title` is the desired title of the window. + """ + context_pp = ffi.new("TCOD_Context**") + if renderer is None: + renderer = RENDERER_SDL2 + if sdl_window_flags is None: + sdl_window_flags = SDL_WINDOW_RESIZABLE + tileset_p = _handle_tileset(tileset) + title = _handle_title(title) + _check_warn( + lib.TCOD_context_new_window( + width, + height, + renderer, + tileset_p, + vsync, + sdl_window_flags, + title.encode("utf-8"), + context_pp, + ) + ) + return Context._claim(context_pp[0]) + + +def new_terminal( + columns: int, + rows: int, + *, + renderer: Optional[int] = None, + tileset: Optional[tcod.tileset.Tileset] = None, + vsync: bool = True, + sdl_window_flags: Optional[int] = None, + title: Optional[str] = None +) -> Context: + """Create a new context with the desired console size. + + `columns` and `rows` are the desired size of the console. + + The remaining parameters are the same as :any:`new_window`. + """ + context_pp = ffi.new("TCOD_Context**") + if renderer is None: + renderer = RENDERER_SDL2 + if sdl_window_flags is None: + sdl_window_flags = SDL_WINDOW_RESIZABLE + tileset_p = _handle_tileset(tileset) + title = _handle_title(title) + _check_warn( + lib.TCOD_context_new_terminal( + columns, + rows, + renderer, + tileset_p, + vsync, + sdl_window_flags, + title.encode("utf-8"), + context_pp, + ) + ) + return Context._claim(context_pp[0]) diff --git a/tcod/event.py b/tcod/event.py index 2f08bb2a..88761ac0 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -81,12 +81,14 @@ def _describe_bitmask( def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: """Convert pixel coordinates to tile coordinates.""" + if not lib.TCOD_ctx.engine: + return 0, 0 xy = ffi.new("double[2]", (x, y)) lib.TCOD_sys_pixel_to_tile(xy, xy + 1) return xy[0], xy[1] -Point = NamedTuple("Point", [("x", float), ("y", float)]) +Point = NamedTuple("Point", [("x", int), ("y", int)]) # manually define names for SDL macros BUTTON_LEFT = lib.SDL_BUTTON_LEFT diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 8bc94095..c4620c29 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1173,7 +1173,7 @@ def console_flush( `console` is the console you want to present. If not given the root console will be used. - If `keep_aspect` is True when the console aspect will be preserved with + If `keep_aspect` is True then the console aspect will be preserved with a letterbox. Otherwise the console will be stretched to fill the screen. If `integer_scaling` is True then the console will be scaled in integer From 0b87a9eb0e1efcc6fefd4bac4a414885f75f7cb9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 28 Apr 2020 09:09:29 -0700 Subject: [PATCH 0312/1101] Add load_tilesheet and character mapping options. --- CHANGELOG.rst | 3 + tcod/libtcodpy.py | 4 +- tcod/tileset.py | 330 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 331 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 83d2e9bd..035b94d9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,9 @@ Unreleased Added - Added `tcod.context` module. You now have more options for making libtcod controlled contexts. + - `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset. + - `Tileset.remap`: Reassign codepoints to tiles on a Tileset. + - `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`. Changed - `EventDispatch.dispatch` can now return the values returned by the `ev_*` diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index c4620c29..b1dee599 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1036,9 +1036,7 @@ def console_map_ascii_code_to_font( ) -> None: """Set a character code to new coordinates on the tile-set. - `asciiCode` must be within the bounds created during the initialization of - the loaded tile-set. For example, you can't use 255 here unless you have a - 256 tile tile-set loaded. This applies to all functions in this group. + `asciiCode` should be any Unicode codepoint. Args: asciiCode (int): The character code to change. diff --git a/tcod/tileset.py b/tcod/tileset.py index 0ffaa3be..2edfb4e9 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -5,13 +5,14 @@ """ import os -from typing import Any, Tuple +import itertools +from typing import Any, Iterable, Optional, Tuple import numpy as np from tcod.loader import lib, ffi import tcod.console -from tcod._internal import _check, _console +from tcod._internal import _check, _console, _raise_tcod_error class Tileset: @@ -141,6 +142,28 @@ def render(self, console: tcod.console.Console) -> np.ndarray: ) return out + def remap(self, codepoint: int, x: int, y: int = 0) -> None: + """Reassign a codepoint to a character in this tileset. + + `codepoint` is the Unicode codepoint to assign. + + `x` and `y` is the position on the tilesheet to assign to `codepoint`. + Large values of `x` will wrap to the next row, so `y` isn't necessary + if you think of the tilesheet as a 1D array. + + This is normally used on loaded tilesheets. Other methods of Tileset + creation won't have reliable tile indexes. + + .. versionadded:: 11.12 + """ + _check( + lib.TCOD_tileset_assign_tile( + self._tileset_p, + codepoint, + x + y * self._tileset_p.virtual_columns, + ) + ) + def get_default() -> Tileset: """Return a reference to the default Tileset. @@ -216,10 +239,311 @@ def load_bdf(path: str) -> Tileset: take effect when `tcod.console_init_root` is called. .. versionadded:: 11.10 - """ + """ # noqa: E501 if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) cdata = lib.TCOD_load_bdf(path.encode()) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) return Tileset._claim(cdata) + + +def load_tilesheet( + path: str, columns: int, rows: int, charmap: Optional[Iterable[int]] +) -> Tileset: + """Return a new Tileset from a simple tilesheet image. + + `path` is the file path to a PNG file with the tileset. + + `columns` and `rows` is the shape of the tileset. Tiles are assumed to + take up the entire space of the image. + + `charmap` is the character mapping to use. This is a list or generator + of codepoints which map the tiles like this: + ``charmap[tile_index] = codepoint``. + For common tilesets `charmap` should be :any:`tcod.tileset.CHARMAP_CP437`. + Generators will be sliced so :any:`itertools.count` can be used which will + give all tiles the same codepoint as their index, but this will not map + tiles onto proper Unicode. + If `None` is used then no tiles will be mapped, you will need to use + :any:`Tileset.remap` to assign codepoints to this Tileset. + + .. versionadded:: 11.12 + """ + if not os.path.exists(path): + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) + mapping = [] + if charmap is not None: + mapping = list(itertools.islice(charmap, columns * rows)) + cdata = lib.TCOD_tileset_load( + path.encode(), columns, rows, len(mapping), mapping + ) + if not cdata: + _raise_tcod_error() + return Tileset._claim(cdata) + + +CHARMAP_CP437 = [ + 0x0000, + 0x263A, + 0x263B, + 0x2665, + 0x2666, + 0x2663, + 0x2660, + 0x2022, + 0x25D8, + 0x25CB, + 0x25D9, + 0x2642, + 0x2640, + 0x266A, + 0x266B, + 0x263C, + 0x25BA, + 0x25C4, + 0x2195, + 0x203C, + 0x00B6, + 0x00A7, + 0x25AC, + 0x21A8, + 0x2191, + 0x2193, + 0x2192, + 0x2190, + 0x221F, + 0x2194, + 0x25B2, + 0x25BC, + 0x0020, + 0x0021, + 0x0022, + 0x0023, + 0x0024, + 0x0025, + 0x0026, + 0x0027, + 0x0028, + 0x0029, + 0x002A, + 0x002B, + 0x002C, + 0x002D, + 0x002E, + 0x002F, + 0x0030, + 0x0031, + 0x0032, + 0x0033, + 0x0034, + 0x0035, + 0x0036, + 0x0037, + 0x0038, + 0x0039, + 0x003A, + 0x003B, + 0x003C, + 0x003D, + 0x003E, + 0x003F, + 0x0040, + 0x0041, + 0x0042, + 0x0043, + 0x0044, + 0x0045, + 0x0046, + 0x0047, + 0x0048, + 0x0049, + 0x004A, + 0x004B, + 0x004C, + 0x004D, + 0x004E, + 0x004F, + 0x0050, + 0x0051, + 0x0052, + 0x0053, + 0x0054, + 0x0055, + 0x0056, + 0x0057, + 0x0058, + 0x0059, + 0x005A, + 0x005B, + 0x005C, + 0x005D, + 0x005E, + 0x005F, + 0x0060, + 0x0061, + 0x0062, + 0x0063, + 0x0064, + 0x0065, + 0x0066, + 0x0067, + 0x0068, + 0x0069, + 0x006A, + 0x006B, + 0x006C, + 0x006D, + 0x006E, + 0x006F, + 0x0070, + 0x0071, + 0x0072, + 0x0073, + 0x0074, + 0x0075, + 0x0076, + 0x0077, + 0x0078, + 0x0079, + 0x007A, + 0x007B, + 0x007C, + 0x007D, + 0x007E, + 0x007F, + 0x00C7, + 0x00FC, + 0x00E9, + 0x00E2, + 0x00E4, + 0x00E0, + 0x00E5, + 0x00E7, + 0x00EA, + 0x00EB, + 0x00E8, + 0x00EF, + 0x00EE, + 0x00EC, + 0x00C4, + 0x00C5, + 0x00C9, + 0x00E6, + 0x00C6, + 0x00F4, + 0x00F6, + 0x00F2, + 0x00FB, + 0x00F9, + 0x00FF, + 0x00D6, + 0x00DC, + 0x00A2, + 0x00A3, + 0x00A5, + 0x20A7, + 0x0192, + 0x00E1, + 0x00ED, + 0x00F3, + 0x00FA, + 0x00F1, + 0x00D1, + 0x00AA, + 0x00BA, + 0x00BF, + 0x2310, + 0x00AC, + 0x00BD, + 0x00BC, + 0x00A1, + 0x00AB, + 0x00BB, + 0x2591, + 0x2592, + 0x2593, + 0x2502, + 0x2524, + 0x2561, + 0x2562, + 0x2556, + 0x2555, + 0x2563, + 0x2551, + 0x2557, + 0x255D, + 0x255C, + 0x255B, + 0x2510, + 0x2514, + 0x2534, + 0x252C, + 0x251C, + 0x2500, + 0x253C, + 0x255E, + 0x255F, + 0x255A, + 0x2554, + 0x2569, + 0x2566, + 0x2560, + 0x2550, + 0x256C, + 0x2567, + 0x2568, + 0x2564, + 0x2565, + 0x2559, + 0x2558, + 0x2552, + 0x2553, + 0x256B, + 0x256A, + 0x2518, + 0x250C, + 0x2588, + 0x2584, + 0x258C, + 0x2590, + 0x2580, + 0x03B1, + 0x00DF, + 0x0393, + 0x03C0, + 0x03A3, + 0x03C3, + 0x00B5, + 0x03C4, + 0x03A6, + 0x0398, + 0x03A9, + 0x03B4, + 0x221E, + 0x03C6, + 0x03B5, + 0x2229, + 0x2261, + 0x00B1, + 0x2265, + 0x2264, + 0x2320, + 0x2321, + 0x00F7, + 0x2248, + 0x00B0, + 0x2219, + 0x00B7, + 0x221A, + 0x207F, + 0x00B2, + 0x25A0, + 0x00A0, +] +"""This is one of the more common character mappings. + +https://en.wikipedia.org/wiki/Code_page_437 + +.. versionadded:: 11.12 +""" From 6aa8f2ff771e3ea543c3b3d788681502e7908bb5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 29 Apr 2020 08:08:53 -0700 Subject: [PATCH 0313/1101] Apply fixes while updating examples. Samples now load valid fonts. eventget.py has been rewritten to use contexts. Context methods have been fixed and updated to be more useful. --- examples/eventget.py | 54 +++++++++++++++++++++------------- examples/samples_libtcodpy.py | 2 +- examples/samples_tcod.py | 2 +- examples/terminal.png | Bin 3429 -> 0 bytes tcod/context.py | 7 +++-- 5 files changed, 40 insertions(+), 25 deletions(-) delete mode 100644 examples/terminal.png diff --git a/examples/eventget.py b/examples/eventget.py index 1c7a7f9e..204df8c2 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -5,36 +5,50 @@ # https://creativecommons.org/publicdomain/zero/1.0/ """An demonstration of event handling using the tcod.event module. """ +from typing import List + import tcod -import tcod.event -def main(): +WIDTH, HEIGHT = 720, 480 +FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED + + +def main() -> None: """Example program for tcod.event""" - WIDTH, HEIGHT = 120, 40 - TITLE = None - - with tcod.console_init_root( - WIDTH, - HEIGHT, - TITLE, - order="F", - renderer=tcod.RENDERER_SDL2, - vsync=True, - ) as console: + + event_log: List[str] = [] + motion_desc = "" + + with tcod.context.new_window( + WIDTH, HEIGHT, sdl_window_flags=FLAGS + ) as context: + console = tcod.Console(*context.recommended_console_size()) while True: - tcod.console_flush() + # Display all event items. + console.clear() + console.print(0, console.height - 1, motion_desc) + for i, item in enumerate(event_log[::-1]): + y = console.height - 3 - i + if y < 0: + break + console.print(0, y, item) + context.present(console, integer_scaling=True) + + # Handle events. for event in tcod.event.wait(): + context.convert_event(event) # Set tile coordinates for event. print(repr(event)) if event.type == "QUIT": raise SystemExit() - elif event.type == "MOUSEMOTION": - console.ch[:, -1] = 0 - console.print(0, HEIGHT - 1, str(event)) + if event.type == "WINDOWRESIZED": + console = tcod.Console( + *context.recommended_console_size() + ) + if event.type == "MOUSEMOTION": + motion_desc = str(event) else: - console.blit(console, 0, 0, 0, 1, WIDTH, HEIGHT - 2) - console.ch[:, -3] = 0 - console.print(0, HEIGHT - 3, str(event)) + event_log.append(str(event)) if __name__ == "__main__": diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 33b27d5b..1bea91d2 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -34,7 +34,7 @@ def get_data(path: str) -> str: SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -font = get_data("fonts/consolas10x10_gs_tc.png") +font = get_data("fonts/dejavu10x10_gs_tc.png") libtcod.console_set_custom_font(font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) libtcod.console_init_root(80, 50, b'libtcod python sample', False) sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 35357509..1c35ebd8 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -32,7 +32,7 @@ def get_data(path: str) -> str: SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -FONT = get_data("fonts/consolas10x10_gs_tc.png") +FONT = get_data("fonts/dejavu10x10_gs_tc.png") root_console = None sample_console = tcod.console.Console( diff --git a/examples/terminal.png b/examples/terminal.png deleted file mode 100644 index 3cffb5b3c3c815955a348103594d5127aad3263a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3429 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 Hu0mjfxK3;J diff --git a/tcod/context.py b/tcod/context.py index cdb3aa40..44327889 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -188,10 +188,11 @@ def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: return xy[0], xy[1] def convert_event( - self, event: Union[tcod.event.MouseState, tcod.event.MouseMotion] + self, event: tcod.event.Event ) -> None: """Fill in the tile coordinates of a mouse event using this context.""" - event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) + if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): + event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) if isinstance(event, tcod.event.MouseMotion): prev_tile = self.pixel_to_tile( event.pixel[0] - event.pixel_motion[0], @@ -235,7 +236,7 @@ def recommended_console_size( self._context_p, size, size + 1 ) ) - return max(min_columns, size[0]), max(min_rows, 1) + return max(min_columns, size[0]), max(min_rows, size[1]) @property def renderer_type(self) -> int: From 58265ec5c1cf869f94eab84bbf70e8f7e2d19482 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 29 Apr 2020 08:15:24 -0700 Subject: [PATCH 0314/1101] Add notice for missing submodule in samples. --- examples/samples_tcod.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 1c35ebd8..3fd38508 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -26,8 +26,13 @@ def get_data(path: str) -> str: """Return the path to a resource in the libtcod data directory,""" SCRIPT_DIR = os.path.dirname(__file__) DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") + assert os.path.exists(DATA_DIR), ( + "Data directory is missing," + " did you forget to run `git submodule update --init`?" + ) return os.path.join(DATA_DIR, path) + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 From 2a923ea616db429a2406722791b23a31b78a5204 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 05:18:23 -0700 Subject: [PATCH 0315/1101] Remove explicit submodule imports from examples. --- examples/distribution/PyInstaller/hello_world.py | 1 - examples/samples_tcod.py | 1 - tcod/bsp.py | 2 +- tcod/console.py | 1 - tcod/event.py | 1 - tcod/image.py | 2 +- tcod/map.py | 2 +- tcod/noise.py | 1 - tcod/path.py | 4 ++-- tcod/tileset.py | 3 --- 10 files changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/hello_world.py index 75d915d9..efcb9434 100755 --- a/examples/distribution/PyInstaller/hello_world.py +++ b/examples/distribution/PyInstaller/hello_world.py @@ -7,7 +7,6 @@ import os.path import tcod -import tcod.event WIDTH, HEIGHT = 80, 60 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 3fd38508..dfcd5987 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -16,7 +16,6 @@ import numpy as np import tcod -import tcod.event if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. diff --git a/tcod/bsp.py b/tcod/bsp.py index caadc137..6f66a1af 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -4,7 +4,7 @@ Example:: - import tcod.bsp + import tcod bsp = tcod.bsp.BSP(x=0, y=0, width=80, height=60) bsp.split_recursive( diff --git a/tcod/console.py b/tcod/console.py index f5410427..c7cb30ea 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -7,7 +7,6 @@ # Make sure 'arial10x10.png' is in the same directory as this script. import tcod - import tcod.event # Setup the font. tcod.console_set_custom_font( diff --git a/tcod/event.py b/tcod/event.py index 88761ac0..62423f56 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -762,7 +762,6 @@ class EventDispatch(Generic[T]): Example:: import tcod - import tcod.event MOVE_KEYS = { # key_symbol: (x, y) # Arrow keys. diff --git a/tcod/image.py b/tcod/image.py index d55ff417..509d97cf 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -37,7 +37,7 @@ def from_array(cls, array: Any) -> "Image": Example: >>> import numpy as np - >>> import tcod.image + >>> import tcod >>> array = np.zeros((5, 5, 3), dtype=np.uint8) >>> image = tcod.image.Image.from_array(array) diff --git a/tcod/map.py b/tcod/map.py index 2bbd6db8..001acecc 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -35,7 +35,7 @@ class Map(object): Example:: - >>> import tcod.map + >>> import tcod >>> m = tcod.map.Map(width=3, height=4) >>> m.walkable array([[False, False, False], diff --git a/tcod/noise.py b/tcod/noise.py index 88194752..548b2676 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -8,7 +8,6 @@ import numpy as np import tcod - import tcod.noise noise = tcod.noise.Noise( dimensions=2, diff --git a/tcod/path.py b/tcod/path.py index 94cad1d9..e2d1749e 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -3,7 +3,7 @@ Example:: >>> import numpy as np - >>> import tcod.path + >>> import tcod >>> dungeon = np.array( ... [ ... [1, 0, 1, 1, 1], @@ -382,7 +382,7 @@ def dijkstra2d( Example: >>> import numpy as np - >>> import tcod.path + >>> import tcod >>> cost = np.ones((3, 3), dtype=np.uint8) >>> cost[:2, 1] = 0 >>> cost diff --git a/tcod/tileset.py b/tcod/tileset.py index 2edfb4e9..77788654 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -1,7 +1,4 @@ """Tileset and font related functions. - -Remember to add the line ``import tcod.tileset``, as importing this module is -not implied by ``import tcod``. """ import os From 1bb1726340a054ce5ce02a7237ffe95b489e344c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 08:38:47 -0700 Subject: [PATCH 0316/1101] Document context module. --- tcod/context.py | 43 +++++++++++++++++++++++++++++++++++++++++++ tcod/tileset.py | 2 -- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 44327889..e2445c7e 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -1,6 +1,41 @@ """This module is used to create and handle libtcod contexts. +:any:`Context`'s are intended to replace several libtcod functions such as +:any:`tcod.console_init_root`, :any:`tcod.console_flush`, +:any:`tcod.console.recommended_size`, and many other functions which rely on +hidden global objects within libtcod. If you use begin using contexts then +most of these functions will no longer work properly. + +Instead of calling :any:`tcod.console_init_root` you can call either +:any:`tcod.context.new_window` or :any:`tcod.context.new_terminal` depending +on how you plan to setup the size of the console. You should use +:any:`tcod.tileset` to load the font for a context. + .. versionadded:: 11.12 + +Example:: + + WIDTH, HEIGHT = 720, 480 + FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED + + with tcod.context.new_window( + WIDTH, HEIGHT, sdl_window_flags=FLAGS + ) as context: + console = tcod.Console(*context.recommended_console_size()) + while True: + # Display the console. + console.clear() + console.print(0, 0, "Hello World") + context.present(console, integer_scaling=True) + + # Handle events. + for event in tcod.event.wait(): + context.convert_event(event) # Set tile coordinates for an event. + print(event) + if event.type == "QUIT": + raise SystemExit() + if event.type == "WINDOWRESIZED": + console = tcod.Console(*context.recommended_console_size()) """ import sys import os @@ -289,6 +324,10 @@ def new_window( https://wiki.libsdl.org/SDL_CreateWindow#Remarks `title` is the desired title of the window. + + After the context is created you can use + :any:`Context.recommended_console_size` to figure out the size of the + console for the context. """ context_pp = ffi.new("TCOD_Context**") if renderer is None: @@ -327,6 +366,10 @@ def new_terminal( `columns` and `rows` are the desired size of the console. The remaining parameters are the same as :any:`new_window`. + + You can use this instead of :any:`new_window` if you plan on using a + :any:`tcod.Console` of a fixed size. This function is the most similar to + :any:`tcod.console_init_root`. """ context_pp = ffi.new("TCOD_Context**") if renderer is None: diff --git a/tcod/tileset.py b/tcod/tileset.py index 77788654..acad5ba0 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -209,8 +209,6 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: tileset. The font will be scaled to fit the given `tile_height` and `tile_width`. - This function will only affect the `SDL2` and `OPENGL2` renderers. - This function must be called before :any:`tcod.console_init_root`. Once the root console is setup you may call this funtion again to change the font. The tileset can be changed but the window will not be resized From d0cea4aeb4b9607c8ef661eebdaa6983858cae11 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 16:15:08 -0700 Subject: [PATCH 0317/1101] Simplify tcod.event example. The command keys dict is simple but not very extendable, so I'll just show an if..elif chain here instead. --- tcod/event.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 62423f56..20189f04 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -796,10 +796,6 @@ class EventDispatch(Generic[T]): tcod.event.K_n: (1, 1), } - COMMAND_KEYS = { # key_symbol: command_name - tcod.event.K_ESCAPE: "escape", - } - class State(tcod.event.EventDispatch[None]): """A state-based superclass that converts `events` into `commands`. @@ -823,10 +819,8 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym in MOVE_KEYS: # Send movement keys to the cmd_move method with parameters. self.cmd_move(*MOVE_KEYS[event.sym]) - elif event.sym in COMMAND_KEYS: - # Send command keys to their respective cmd_X method. - func = getattr(self, "cmd_" + COMMAND_KEYS[event.sym]) - func() + elif event.sym == tcod.event.K_ESCAPE: + self.cmd_escape() def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: """The window was clicked."" From f3e40098ed70c38c82b37cb79af216f7cc473ec6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 17:39:55 -0700 Subject: [PATCH 0318/1101] Resolve minor warnings and formatting. --- tcod/context.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index e2445c7e..1c533528 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -36,11 +36,11 @@ raise SystemExit() if event.type == "WINDOWRESIZED": console = tcod.Console(*context.recommended_console_size()) -""" +""" # noqa: E501 import sys import os -from typing import Any, Optional, Tuple, Union +from typing import Any, Optional, Tuple import tcod from tcod._internal import _check, _check_warn @@ -222,9 +222,7 @@ def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: ) return xy[0], xy[1] - def convert_event( - self, event: tcod.event.Event - ) -> None: + def convert_event(self, event: tcod.event.Event) -> None: """Fill in the tile coordinates of a mouse event using this context.""" if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) From e8ee3d8ba7ec6e0d1104b592bb6b242a2ff6afcd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 17:41:46 -0700 Subject: [PATCH 0319/1101] Add tcod.tileset.CHARMAP_TCOD. --- CHANGELOG.rst | 1 + tcod/tileset.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 035b94d9..272d39dd 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added - `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset. - `Tileset.remap`: Reassign codepoints to tiles on a Tileset. - `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`. + - `tcod.tileset.CHARMAP_TCOD`: Older libtcod layout. Changed - `EventDispatch.dispatch` can now return the values returned by the `ev_*` diff --git a/tcod/tileset.py b/tcod/tileset.py index acad5ba0..8d489f4d 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -542,3 +542,176 @@ def load_tilesheet( .. versionadded:: 11.12 """ + +CHARMAP_TCOD = [ + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2A, + 0x2B, + 0x2C, + 0x2D, + 0x2E, + 0x2F, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3A, + 0x3B, + 0x3C, + 0x3D, + 0x3E, + 0x3F, + 0x40, + 0x5B, + 0x5C, + 0x5D, + 0x5E, + 0x5F, + 0x60, + 0x7B, + 0x7C, + 0x7D, + 0x7E, + 0x2591, + 0x2592, + 0x2593, + 0x2502, + 0x2500, + 0x253C, + 0x2524, + 0x2534, + 0x251C, + 0x252C, + 0x2514, + 0x250C, + 0x2510, + 0x2518, + 0x2598, + 0x259D, + 0x2580, + 0x2596, + 0x259A, + 0x2590, + 0x2597, + 0x2191, + 0x2193, + 0x2190, + 0x2192, + 0x25B2, + 0x25BC, + 0x25C4, + 0x25BA, + 0x2195, + 0x2194, + 0x2610, + 0x2611, + 0x25CB, + 0x25C9, + 0x2551, + 0x2550, + 0x256C, + 0x2563, + 0x2569, + 0x2560, + 0x2566, + 0x255A, + 0x2554, + 0x2557, + 0x255D, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4A, + 0x4B, + 0x4C, + 0x4D, + 0x4E, + 0x4F, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5A, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6A, + 0x6B, + 0x6C, + 0x6D, + 0x6E, + 0x6F, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7A, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +] +"""The layout used by older libtcod fonts, in Unicode. + +This layout is non-standard, and it's not recommend to make a font for it, but +you might need it to load an existing font. + +This character map is in Unicode, so old code using the non-Unicode +`tcod.CHAR_*` constants will need to be updated. + +.. versionadded:: 11.12 +""" From 9869918267add97fb112e73aae3699442993ac60 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Apr 2020 19:04:54 -0700 Subject: [PATCH 0320/1101] Prepare 11.12.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 272d39dd..72cb76ff 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.12.0 - 2020-04-30 +-------------------- Added - Added `tcod.context` module. You now have more options for making libtcod controlled contexts. From cd9cc6919c799b28714bf336da4cee6719d285f4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 1 May 2020 18:56:45 -0700 Subject: [PATCH 0321/1101] Fix malformed code example. --- tcod/event.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 20189f04..84c00ce2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -814,7 +814,7 @@ def ev_quit(self, event: tcod.event.Quit) -> None: self.cmd_quit() def ev_keydown(self, event: tcod.event.KeyDown) -> None: - """A key was pressed."" + """A key was pressed.""" print(event) if event.sym in MOVE_KEYS: # Send movement keys to the cmd_move method with parameters. @@ -823,7 +823,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: self.cmd_escape() def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: - """The window was clicked."" + """The window was clicked.""" print(event) def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: @@ -840,7 +840,7 @@ def cmd_escape(self) -> None: self.cmd_quit() def cmd_quit(self) -> None: - """Intent to exit the game."" + """Intent to exit the game.""" print("Command quit.") raise SystemExit() From 728db8754e1bc6a4570b94b4a40ee837f781bb9e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 May 2020 20:49:55 -0700 Subject: [PATCH 0322/1101] Hotfix double-wide characters. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 72cb76ff..8887e78a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Prevent adding non-existent 2nd halves to potential double-wide charterers. 11.12.0 - 2020-04-30 -------------------- diff --git a/libtcod b/libtcod index 1ce60c09..2608bda5 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 1ce60c0919b71d3dd7fc28df4bfef7094e0e6911 +Subproject commit 2608bda5e53ce4b4f1b5134b5e91aca0e7e23eb8 From 81cf898037a4378a13aa6cf5773eed73beac4042 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 2 May 2020 20:57:03 -0700 Subject: [PATCH 0323/1101] Prepare 11.12.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8887e78a..33063242 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.12.1 - 2020-05-02 +-------------------- Fixed - Prevent adding non-existent 2nd halves to potential double-wide charterers. From a4bac3fe546ddd3223d634038caa5e864706dde5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 4 May 2020 06:52:49 -0700 Subject: [PATCH 0324/1101] List context related deprecated functions in docs. --- tcod/context.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tcod/context.py b/tcod/context.py index 1c533528..30732e8b 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -3,7 +3,7 @@ :any:`Context`'s are intended to replace several libtcod functions such as :any:`tcod.console_init_root`, :any:`tcod.console_flush`, :any:`tcod.console.recommended_size`, and many other functions which rely on -hidden global objects within libtcod. If you use begin using contexts then +hidden global objects within libtcod. If you begin using contexts then most of these functions will no longer work properly. Instead of calling :any:`tcod.console_init_root` you can call either @@ -11,6 +11,38 @@ on how you plan to setup the size of the console. You should use :any:`tcod.tileset` to load the font for a context. +.. note:: + If you use contexts then you should expect the following functions to no + longer be available, because these functions rely on a global console, + tileset, or some other kind of global state: + + :any:`tcod.console_init_root`, + :any:`tcod.console_set_custom_font`, + :any:`tcod.console_flush`, + :any:`tcod.console_is_fullscreen`, + :any:`tcod.console_is_window_closed`, + :any:`tcod.console_set_fade`, + :any:`tcod.console_set_fullscreen`, + :any:`tcod.console_set_window_title`, + :any:`tcod.sys_set_fps`, + :any:`tcod.sys_get_last_frame_length`, + :any:`tcod.sys_set_renderer`, + :any:`tcod.sys_save_screenshot`, + :any:`tcod.sys_force_fullscreen_resolution`, + :any:`tcod.sys_get_current_resolution`, + :any:`tcod.sys_register_SDL_renderer`, + :any:`tcod.console_map_ascii_code_to_font`, + :any:`tcod.sys_get_char_size`, + :any:`tcod.sys_update_char`, + :any:`tcod.console.recommended_size`, + :any:`tcod.tileset.get_default`, + :any:`tcod.tileset.set_default`. + + Some event functions can no longer return tile coordinates for the mouse: + :any:`tcod.sys_check_for_event`, + :any:`tcod.sys_wait_for_event`, + :any:`tcod.mouse_get_status`. + .. versionadded:: 11.12 Example:: From 2ce9cef7dc6525820155941c195b5dd7e99e9e73 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 5 May 2020 08:00:36 -0700 Subject: [PATCH 0325/1101] Update and document sdl_window_p property. Removed the TypeError and just return NULL if the pointer is NULL. Since right now no code will return NULL in normal circumstances I'm going to consider this a minor change. --- tcod/context.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 30732e8b..159c9c04 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -310,17 +310,32 @@ def renderer_type(self) -> int: @property def sdl_window_p(self) -> Any: - """Return a cffi SDL_Window pointer. - - TypeError will be raised if this context does not have an SDL window. + '''A cffi `SDL_Window*` pointer. This pointer might be NULL. This pointer will become invalid if the context is closed or goes out of scope. - """ - sdl_window_p = lib.TCOD_context_get_sdl_window(self._context_p) - if not sdl_window_p: - raise TypeError("This context does not have an SDL window.") - return sdl_window_p + + Python-tcod's FFI provides most SDL functions. So it's possible for + anyone with the SDL2 documentation to work directly with SDL's + pointers. + + Example:: + + import tcod + + def toggle_fullscreen(context: tcod.context.Context) -> None: + """Toggle a context window between fullscreen and windowed modes.""" + if not context.sdl_window_p: + return + fullscreen = tcod.lib.SDL_GetWindowFlags(context.sdl_window_p) & ( + tcod.lib.SDL_WINDOW_FULLSCREEN | tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP + ) + tcod.lib.SDL_SetWindowFullscreen( + context.sdl_window_p, + 0 if fullscreen else tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP, + ) + ''' # noqa: E501 + return lib.TCOD_context_get_sdl_window(self._context_p) def new_window( From 40f84b458779b479ad93a6b1ea26f3e94c33634b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 9 May 2020 15:51:42 -0700 Subject: [PATCH 0326/1101] Convert tcod.cpp to C. --- tcod/{tcod.cpp => tcod.c} | 2 -- 1 file changed, 2 deletions(-) rename tcod/{tcod.cpp => tcod.c} (97%) diff --git a/tcod/tcod.cpp b/tcod/tcod.c similarity index 97% rename from tcod/tcod.cpp rename to tcod/tcod.c index 5eb2d93d..db1382aa 100644 --- a/tcod/tcod.cpp +++ b/tcod/tcod.c @@ -1,6 +1,4 @@ -#include - #include "tcod.h" #include "../libtcod/src/libtcod/bresenham.h" From 3eaede1d556bc8f3bfb35bbcf1c7aea8b1e2bbe5 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 9 May 2020 20:20:19 -0700 Subject: [PATCH 0327/1101] Rewrite pathfinder code to use C99 instead of C++. --- libtcod | 2 +- tcod/path.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++ tcod/path.cpp | 250 ------------------------------------------------ tcod/path.h | 29 +++--- 4 files changed, 276 insertions(+), 265 deletions(-) create mode 100644 tcod/path.c delete mode 100644 tcod/path.cpp diff --git a/libtcod b/libtcod index 2608bda5..3eb359b3 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 2608bda5e53ce4b4f1b5134b5e91aca0e7e23eb8 +Subproject commit 3eb359b3ae17097cd79c1518e421a65bb54e902b diff --git a/tcod/path.c b/tcod/path.c new file mode 100644 index 00000000..4c630191 --- /dev/null +++ b/tcod/path.c @@ -0,0 +1,260 @@ +#include "path.h" + +#include +#include +#include + +#include "../libtcod/src/libtcod/error.h" +#include "../libtcod/src/libtcod/pathfinder_frontier.h" + +static void* pick_array_pointer(const struct PathCostArray *map, int i, int j) +{ + return (void*)(map->array + map->strides[0] * i + map->strides[1] * j); +} +float PathCostArrayFloat32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return *(float*)pick_array_pointer(map, x2, y2); +} +float PathCostArrayInt8( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return *(int8_t*)pick_array_pointer(map, x2, y2); +} +float PathCostArrayInt16( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return *(int16_t*)pick_array_pointer(map, x2, y2); +} +float PathCostArrayInt32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return (float)(*(int32_t*)pick_array_pointer(map, x2, y2)); +} +float PathCostArrayUInt8( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return *(uint8_t*)pick_array_pointer(map, x2, y2); +} +float PathCostArrayUInt16( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return *(uint16_t*)pick_array_pointer(map, x2, y2); +} +float PathCostArrayUInt32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map) +{ + return (float)(*(uint32_t*)pick_array_pointer(map, x2, y2)); +} + +static bool array2d_in_range(const struct NArray4* arr, int i, int j) +{ + return 0 <= i && i < arr->shape[0] && 0 <= j && j < arr->shape[1]; +} + +static void* get_array2d_at(const struct NArray4* arr, int i, int j) +{ + return arr->data + arr->strides[0] * i + arr->strides[1] * j; +} + +static int64_t get_array2d_int64(const struct NArray4* arr, int i, int j) +{ + const void* ptr = get_array2d_at(arr, i, j); + switch (arr->type) { + case np_int8: + return *(const int8_t*)ptr; + case np_int16: + return *(const int16_t*)ptr; + case np_int32: + return *(const int32_t*)ptr; + case np_int64: + return *(const int64_t*)ptr; + case np_uint8: + return *(const uint8_t*)ptr; + case np_uint16: + return *(const uint16_t*)ptr; + case np_uint32: + return *(const uint32_t*)ptr; + case np_uint64: + return *(const uint64_t*)ptr; + default: + return 0; + } +} +static int get_array2d_int(const struct NArray4* arr, int i, int j) +{ + return (int)get_array2d_int64(arr, i, j); +} + + +static void set_array2d_int64(struct NArray4* arr, int i, int j, int64_t value) +{ + void* ptr = get_array2d_at(arr, i, j); + switch (arr->type) { + case np_int8: + *(int8_t*)ptr = (int8_t)value; + case np_int16: + *(int16_t*)ptr = (int16_t)value; + return; + case np_int32: + *(int32_t*)ptr = (int32_t)value; + return; + case np_int64: + *(int64_t*)ptr = value; + return; + case np_uint8: + *(uint8_t*)ptr = (uint8_t)value; + return; + case np_uint16: + *(uint16_t*)ptr = (uint16_t)value; + return; + case np_uint32: + *(uint32_t*)ptr = (uint32_t)value; + return; + case np_uint64: + *(uint64_t*)ptr = (uint64_t)value; + return; + default: + return; + } +} +static void set_array2d_int(struct NArray4* arr, int i, int j, int value) +{ + set_array2d_int64(arr, i, j, value); +} +static int64_t get_array2d_is_max(const struct NArray4* arr, int i, int j) +{ + const void* ptr = get_array2d_at(arr, i, j); + switch (arr->type) { + case np_int8: + return *(const int8_t*)ptr == SCHAR_MAX; + case np_int16: + return *(const int16_t*)ptr == SHRT_MAX; + case np_int32: + return *(const int32_t*)ptr == INT_MAX; + case np_int64: + return *(const int64_t*)ptr == LONG_MAX; + case np_uint8: + return *(const uint8_t*)ptr == UCHAR_MAX; + case np_uint16: + return *(const uint16_t*)ptr == USHRT_MAX; + case np_uint32: + return *(const uint32_t*)ptr == UINT_MAX; + case np_uint64: + return *(const uint64_t*)ptr == ULONG_MAX; + default: + return 0; + } +} + +static const int CARDINAL_[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; +static const int DIAGONAL_[4][2] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}}; + +static void dijkstra2d_add_edge( + struct TCOD_Frontier* frontier, + struct NArray4* dist_array, + const struct NArray4* cost, + int edge_cost, + const int dir[2]) +{ + const int index[2] = { + frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1] + }; + if (!array2d_in_range(dist_array, index[0], index[1])) { return; } + edge_cost *= get_array2d_int(cost, index[0], index[1]); + if (edge_cost <= 0) { return; } + int distance = frontier->active_dist + edge_cost; + if (get_array2d_int(dist_array, index[0], index[1]) <= distance) { return; } + set_array2d_int(dist_array, index[0], index[1], distance); + TCOD_frontier_push(frontier, index, distance, distance); +} +int dijkstra2d( + struct NArray4* dist_array, + const struct NArray4* cost, + int cardinal, + int diagonal) +{ + struct TCOD_Frontier* frontier = TCOD_frontier_new(2); + if (!frontier) { return TCOD_E_ERROR; } + for (int i = 0; i < dist_array->shape[0]; ++i) { + for (int j = 0; j < dist_array->shape[1]; ++j) { + if (get_array2d_is_max(dist_array, i, j)) { continue; } + const int index[2] = {i, j}; + int dist = get_array2d_int(dist_array, i, j); + TCOD_frontier_push(frontier, index, dist, dist); + } + } + while (TCOD_frontier_size(frontier)) { + TCOD_frontier_pop(frontier); + int distance_here = get_array2d_int( + dist_array, frontier->active_index[0], frontier->active_index[1]); + if (frontier->active_dist != distance_here) { continue; } + if (cardinal > 0) { + dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[0]); + dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[1]); + dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[2]); + dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[3]); + } + if (diagonal > 0) { + dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[0]); + dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[1]); + dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[2]); + dijkstra2d_add_edge(frontier, dist_array, cost, diagonal, DIAGONAL_[3]); + } + } + return TCOD_E_OK; +} +static void hillclimb2d_check_edge( + const struct NArray4* dist_array, + int* distance_in_out, + const int origin[2], + const int dir[2], + int index_out[2]) +{ + const int next[2] = {origin[0] + dir[0], origin[1] + dir[1]}; + if (!array2d_in_range(dist_array, next[0], next[1])) { return; } + const int next_distance = get_array2d_int(dist_array, next[0], next[1]); + if (next_distance < *distance_in_out) { + *distance_in_out = next_distance; + index_out[0] = next[0]; + index_out[1] = next[1]; + } +} +int hillclimb2d( + const struct NArray4* dist_array, + int i, + int j, + bool cardinal, + bool diagonal, + int* out) +{ + int old_dist = get_array2d_int(dist_array, i, j); + int new_dist = old_dist; + int next[2] = {i, j}; + int length = 0; + while (1) { + ++length; + if (out) { + out[0] = next[0]; + out[1] = next[1]; + out += 2; + } + const int origin[2] = {next[0], next[1]}; + if (cardinal) { + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[0], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[1], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[2], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[3], next); + } + if (diagonal) { + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[0], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[1], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[2], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[3], next); + } + if (old_dist == new_dist) { + return length; + } + old_dist = new_dist; + } +} diff --git a/tcod/path.cpp b/tcod/path.cpp deleted file mode 100644 index 47bb38f7..00000000 --- a/tcod/path.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "path.h" - -#include -#include - -#include -#include "../libtcod/src/libtcod/error.h" -#include "../libtcod/src/libtcod/pathfinding/generic.h" -#include "../libtcod/src/libtcod/pathfinding/dijkstra.h" -#include "../libtcod/src/libtcod/pathfinding/hill-climb.h" -#include "../libtcod/src/libtcod/utility/matrix.h" - -static char* PickArrayValue(const struct PathCostArray *map, int i, int j) { - return map->array + map->strides[0] * i + map->strides[1] * j; -} - -template -static float GetPathCost(const struct PathCostArray *map, int i, int j) { - return (float)(((T*)PickArrayValue(map, i, j))[0]); -} - -float PathCostArrayFloat32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayInt8(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayInt16(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayInt32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayUInt8(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayUInt16(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -float PathCostArrayUInt32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map){ - return GetPathCost(map, x2, y2); -} - -template -tcod::MatrixView to_matrix(struct NArray4* array) -{ - return tcod::MatrixView( - reinterpret_cast(array->data), - {array->shape[0], array->shape[1]}, - {array->strides[0], array->strides[1]} - ); -} -template -const tcod::MatrixView to_matrix(const struct NArray4* array) -{ - return tcod::MatrixView( - reinterpret_cast(array->data), - {array->shape[0], array->shape[1]}, - {array->strides[0], array->strides[1]} - ); -} - -template -int dijkstra2d_3( - DistType& dist, - const CostType& cost, - int cardinal, - int diagonal) -{ - try { - tcod::pathfinding::dijkstra2d(dist, cost, cardinal, diagonal); - } catch (const std::exception& e) { - return tcod::set_error(e); - } - return 0; -} -template -int dijkstra2d_2( - DistType& dist, - const struct NArray4* cost, - int cardinal, - int diagonal) -{ - switch (cost->type) { - case np_int8: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_int16: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_int32: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_int64: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_uint8: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_uint16: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_uint32: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - case np_uint64: - return dijkstra2d_3(dist, to_matrix(cost), cardinal, diagonal); - default: - return tcod::set_error("Expected distance map to be int type."); - } -} -int dijkstra2d( - struct NArray4* dist, - const struct NArray4* cost, - int cardinal, - int diagonal) -{ - switch (dist->type) { - case np_int8: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_int16: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_int32: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_int64: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_uint8: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_uint16: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_uint32: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - case np_uint64: { - auto dist_ = to_matrix(dist); - return dijkstra2d_2(dist_, cost, cardinal, diagonal); - } - default: - return tcod::set_error("Expected cost map to be int type."); - } -} - -static const std::array, 4> CARDINAL_{{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}}; -static const std::array, 4> DIAGONAL_{{{-1, -1}, {1, -1}, {-1, 1}, {1, 1}}}; - -template -class PlainGraph { - public: - PlainGraph(const IndexType& size, bool cardinal, bool diagonal) - : size_{size}, cardinal_{cardinal}, diagonal_{diagonal} - {} - template - void with_edges(const F& edge_func, const index_type& index) const - { - if (cardinal_) { - for (const auto& edge : CARDINAL_) { - index_type node{index[0] + edge[0], index[1] + edge[1]}; - if (!in_range(node)) { continue; } - edge_func(node, 0); - } - } - if (diagonal_) { - for (const auto& edge : DIAGONAL_) { - index_type node{index[0] + edge[0], index[1] + edge[1]}; - if (!in_range(node)) { continue; } - edge_func(node, 0); - } - } - } - private: - IndexType size_; - bool cardinal_; - bool diagonal_; - bool in_range(const IndexType& index) const noexcept{ - return (0 <= index[0] && index[0] < size_[0] && - 0 <= index[1] && index[1] < size_[1]); - } -}; - -template -int hillclimb2d_2( - const DistType& dist, - int x, - int y, - bool cardinal, - bool diagonal, - int* out) -{ - PlainGraph graph{ - dist.get_shape(), cardinal, diagonal - }; - auto path = tcod::pathfinding::simple_hillclimb(dist, graph, {x, y}); - if (out) { - for (const auto& index : path) { - out[0] = static_cast(index.at(0)); - out[1] = static_cast(index.at(1)); - out += 2; - } - } - return static_cast(path.size()); -} -int hillclimb2d( - const struct NArray4* dist, - int x, - int y, - bool cardinal, - bool diagonal, - int* out) -{ - switch (dist->type) { - case np_int8: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_int16: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_int32: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_int64: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_uint8: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_uint16: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_uint32: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - case np_uint64: - return hillclimb2d_2(to_matrix(dist), x, y, cardinal, diagonal, out); - default: - return tcod::set_error("Expected cost map to be int type."); - } - -} diff --git a/tcod/path.h b/tcod/path.h index 3d5a700c..82252f8f 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -1,6 +1,7 @@ #ifndef PYTHON_TCOD_PATH_H_ #define PYTHON_TCOD_PATH_H_ +#include #include #ifdef __cplusplus @@ -45,26 +46,26 @@ struct PathCostArray { long long strides[2]; }; -float PathCostArrayFloat32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayFloat32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayUInt8(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayUInt8( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayUInt16(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayUInt16( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayUInt32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayUInt32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayInt8(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayInt8( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayInt16(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayInt16( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); -float PathCostArrayInt32(int x1, int y1, int x2, int y2, - const struct PathCostArray *map); +float PathCostArrayInt32( + int x1, int y1, int x2, int y2, const struct PathCostArray *map); int dijkstra2d( struct NArray4* dist, From 28f53f53fb1667240188ad3941e972d56977bf2e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 9 May 2020 20:52:15 -0700 Subject: [PATCH 0328/1101] Add context tests. --- tests/test_tcod.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 36bc8836..1d15b40c 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -235,3 +235,19 @@ def test_cffi_structs(): def test_recommended_size(console): tcod.console.recommended_size() + + +def test_context(): + with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): + pass + WIDTH, HEIGHT = 16, 4 + with tcod.context.new_terminal( + WIDTH, HEIGHT, renderer=tcod.RENDERER_SDL2 + ) as context: + console = tcod.Console(*context.recommended_console_size()) + context.present(console) + context.sdl_window_p + context.renderer_type + context.change_tileset(tcod.tileset.Tileset(16, 16)) + context.pixel_to_tile(0, 0) + context.pixel_to_subtile(0, 0) From 9d17f5eb62acd5c706e5f162a7c6a30a72fb0f64 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 9 May 2020 21:04:00 -0700 Subject: [PATCH 0329/1101] Disable OpenMP by default. --- build_libtcod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index d80f81da..07495244 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -306,9 +306,9 @@ def fix_header(filepath: str) -> None: # Can force the use of OpenMP with this variable. try: - USE_OPENMP = eval(os.environ.get("USE_OPENMP", "None").title()) + USE_OPENMP = eval(os.environ.get("USE_OPENMP", "False").title()) except Exception: - USE_OPENMP = None + USE_OPENMP = False tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() From 20aae8ec347bf6175e8a68c9955afe03225a959b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 May 2020 22:46:46 -0700 Subject: [PATCH 0330/1101] Add edge_map parameter to Dijkstra functions. --- CHANGELOG.rst | 3 + tcod/path.c | 68 ++++++++++++++++++++-- tcod/path.h | 14 +++++ tcod/path.py | 153 +++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 220 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 33063242..1733589e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - Added `edge_map` parameter to `tcod.path.dijkstra2d` and + `tcod.path.hillclimb2d`. 11.12.1 - 2020-05-02 -------------------- diff --git a/tcod/path.c b/tcod/path.c index 4c630191..709969a6 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -168,7 +168,37 @@ static void dijkstra2d_add_edge( set_array2d_int(dist_array, index[0], index[1], distance); TCOD_frontier_push(frontier, index, distance, distance); } + int dijkstra2d( + struct NArray4* dist_array, + const struct NArray4* cost, + int edges_2d_n, + int* edges_2d) +{ + struct TCOD_Frontier* frontier = TCOD_frontier_new(2); + if (!frontier) { return TCOD_E_ERROR; } + for (int i = 0; i < dist_array->shape[0]; ++i) { + for (int j = 0; j < dist_array->shape[1]; ++j) { + if (get_array2d_is_max(dist_array, i, j)) { continue; } + const int index[2] = {i, j}; + int dist = get_array2d_int(dist_array, i, j); + TCOD_frontier_push(frontier, index, dist, dist); + } + } + while (TCOD_frontier_size(frontier)) { + TCOD_frontier_pop(frontier); + int distance_here = get_array2d_int( + dist_array, frontier->active_index[0], frontier->active_index[1]); + if (frontier->active_dist != distance_here) { continue; } + for (int i = 0; i < edges_2d_n; ++i) { + dijkstra2d_add_edge( + frontier, dist_array, cost, edges_2d[i * 3 + 2], &edges_2d[i * 3]); + } + } + return TCOD_E_OK; +} + +int dijkstra2d_basic( struct NArray4* dist_array, const struct NArray4* cost, int cardinal, @@ -222,15 +252,45 @@ static void hillclimb2d_check_edge( } int hillclimb2d( const struct NArray4* dist_array, - int i, - int j, + int start_i, + int start_j, + int edges_2d_n, + int* edges_2d, + int* out) +{ + int old_dist = get_array2d_int(dist_array, start_i, start_j); + int new_dist = old_dist; + int next[2] = {start_i, start_j}; + int length = 0; + while (1) { + ++length; + if (out) { + out[0] = next[0]; + out[1] = next[1]; + out += 2; + } + const int origin[2] = {next[0], next[1]}; + for (int i = 0; i < edges_2d_n; ++i) { + hillclimb2d_check_edge( + dist_array, &new_dist, origin, &edges_2d[i * 2], next); + } + if (old_dist == new_dist) { + return length; + } + old_dist = new_dist; + } +} +int hillclimb2d_basic( + const struct NArray4* dist_array, + int start_i, + int start_j, bool cardinal, bool diagonal, int* out) { - int old_dist = get_array2d_int(dist_array, i, j); + int old_dist = get_array2d_int(dist_array, start_i, start_j); int new_dist = old_dist; - int next[2] = {i, j}; + int next[2] = {start_i, start_j}; int length = 0; while (1) { ++length; diff --git a/tcod/path.h b/tcod/path.h index 82252f8f..76750338 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -68,12 +68,26 @@ float PathCostArrayInt32( int x1, int y1, int x2, int y2, const struct PathCostArray *map); int dijkstra2d( + struct NArray4* dist, + const struct NArray4* cost, + int edges_2d_n, + int* edges_2d); + +int dijkstra2d_basic( struct NArray4* dist, const struct NArray4* cost, int cardinal, int diagonal); int hillclimb2d( + const struct NArray4* dist_array, + int start_i, + int start_j, + int edges_2d_n, + int* edges_2d, + int* out); + +int hillclimb2d_basic( const struct NArray4* dist, int x, int y, diff --git a/tcod/path.py b/tcod/path.py index e2d1749e..9e1c9c8b 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -40,6 +40,7 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ +import functools from typing import Any, Callable, List, Optional, Tuple, Union # noqa: F401 import numpy as np @@ -324,7 +325,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: def maxarray( - shape: Tuple[int, ...], dtype: Any = int, order: str = "C" + shape: Tuple[int, ...], dtype: Any = np.int32, order: str = "C" ) -> np.array: """Return a new array filled with the maximum finite value for `dtype`. @@ -357,11 +358,35 @@ def _export(array: np.array) -> Any: ) +def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: + """Return an edge_cost array using an integer map.""" + edge_map = np.copy(edge_map) + if edge_map.ndim != 2: + raise ValueError( + "edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim + ) + edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 + edge_map[edge_center] = 0 + edge_map[edge_map < 0] = 0 + edge_nz = edge_map.nonzero() + edge_array = np.transpose(edge_nz) + edge_array -= edge_center + c_edges = ffi.new("int[]", len(edge_array) * 3) + edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape( + len(edge_array), 3 + ) + edges[:, :2] = edge_array + edges[:, 2] = edge_map[edge_nz] + return c_edges, len(edge_array) + + def dijkstra2d( distance: np.array, cost: np.array, - cardinal: Optional[int], - diagonal: Optional[int], + cardinal: Optional[int] = None, + diagonal: Optional[int] = None, + *, + edge_map: Any = None ) -> None: """Return the computed distance of all nodes on a 2D Dijkstra grid. @@ -379,7 +404,12 @@ def dijkstra2d( directions. A value of None or 0 will disable those directions. Typical values could be: ``1, None``, ``1, 1``, ``2, 3``, etc. - Example: + `edge_map` is a 2D array of edge costs with the origin point centered on + the array. This can be used to define the edges used from one node to + another. This parameter can be hard to understand so you should see how + it's used in the examples. + + Example:: >>> import numpy as np >>> import tcod @@ -414,7 +444,58 @@ def dijkstra2d( [2, 1] [2, 2] + `edge_map` is used for more complicated graphs. The following example + uses a 'knight move' edge map. + + Example:: + + >>> import numpy as np + >>> import tcod + >>> knight_moves = [ + ... [0, 1, 0, 1, 0], + ... [1, 0, 0, 0, 1], + ... [0, 0, 0, 0, 0], + ... [1, 0, 0, 0, 1], + ... [0, 1, 0, 1, 0], + ... ] + >>> dist = tcod.path.maxarray((8, 8)) + >>> dist[0,0] = 0 + >>> cost = np.ones((8, 8), int) + >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves) + >>> dist + array([[0, 3, 2, 3, 2, 3, 4, 5], + [3, 4, 1, 2, 3, 4, 3, 4], + [2, 1, 4, 3, 2, 3, 4, 5], + [3, 2, 3, 2, 3, 4, 3, 4], + [2, 3, 2, 3, 4, 3, 4, 5], + [3, 4, 3, 4, 3, 4, 5, 4], + [4, 3, 4, 3, 4, 5, 4, 5], + [5, 4, 5, 4, 5, 4, 5, 6]]...) + >>> tcod.path.hillclimb2d(dist, (7, 7), edge_map=knight_moves) + array([[7, 7], + [5, 6], + [3, 5], + [1, 4], + [0, 2], + [2, 1], + [0, 0]], dtype=int32) + + `edge_map` can also be used to define a hex-grid. + See https://www.redblobgames.com/grids/hexagons/ for more info. + The following example is using axial coordinates. + + Example:: + + hex_edges = [ + [0, 1, 1], + [1, 0, 1], + [1, 1, 0], + ] + .. versionadded:: 11.2 + + .. versionchanged:: 11.13 + Added the `edge_map` parameter. """ dist = distance cost = np.asarray(cost) @@ -423,15 +504,40 @@ def dijkstra2d( "distance and cost must have the same shape %r != %r" % (dist.shape, cost.shape) ) - if cardinal is None: - cardinal = 0 - if diagonal is None: - diagonal = 0 - _check(lib.dijkstra2d(_export(dist), _export(cost), cardinal, diagonal)) + c_dist = _export(dist) + if edge_map is not None: + if cardinal is not None or diagonal is not None: + raise TypeError( + "`edge_map` can not be set at the same time as" + " `cardinal` or `diagonal`." + ) + c_edges, n_edges = _compile_cost_edges(edge_map) + _check(lib.dijkstra2d(c_dist, _export(cost), n_edges, c_edges)) + else: + if cardinal is None: + cardinal = 0 + if diagonal is None: + diagonal = 0 + _check(lib.dijkstra2d_basic(c_dist, _export(cost), cardinal, diagonal)) + + +def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: + """Return an edge array using a boolean map.""" + edge_map = np.copy(edge_map) + edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 + edge_map[edge_center] = 0 + edge_array = np.transpose(edge_map.nonzero()) + edge_array -= edge_center + return ffi.new("int[]", list(edge_array.flat)), len(edge_array) def hillclimb2d( - distance: np.array, start: Tuple[int, int], cardinal: bool, diagonal: bool + distance: np.array, + start: Tuple[int, int], + cardinal: Optional[bool] = None, + diagonal: Optional[bool] = None, + *, + edge_map: Any = None ) -> np.array: """Return a path on a grid from `start` to the lowest point. @@ -447,6 +553,10 @@ def hillclimb2d( boolean values `cardinal` and `diagonal`. This process is repeated until all adjacent nodes are equal to or larger than the last point on the path. + If `edge_map` was used with :any:`tcod.path.dijkstra2d` then it should be + reused for this function. Keep in mind that `edge_map` must be + bidirectional since hill-climbing will traverse the map backwards. + The returned array is a 2D NumPy array with the shape: (length, axis). This array always includes both the starting and ending point and will always have at least one item. @@ -456,6 +566,9 @@ def hillclimb2d( can be used to index other arrays using NumPy's advanced indexing rules. .. versionadded:: 11.2 + + .. versionchanged:: 11.13 + Added `edge_map` parameter. """ x, y = start dist = np.asarray(distance) @@ -464,10 +577,22 @@ def hillclimb2d( "Starting point %r not in shape %r" % (start, dist.shape) ) c_dist = _export(dist) - length = _check( - lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, ffi.NULL) - ) + if edge_map is not None: + if cardinal is not None or diagonal is not None: + raise TypeError( + "`edge_map` can not be set at the same time as" + " `cardinal` or `diagonal`." + ) + c_edges, n_edges = _compile_bool_edges(edge_map) + func = functools.partial( + lib.hillclimb2d, c_dist, x, y, n_edges, c_edges + ) + else: + func = functools.partial( + lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal + ) + length = _check(func(ffi.NULL)) path = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.cast("int*", path.ctypes.data) - _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, c_path)) + _check(func(c_path)) return path From b1ecb8e85e6c8e25de4941fd7234e1fbd87f1329 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 11 May 2020 23:29:28 -0700 Subject: [PATCH 0331/1101] Manually set GCC as the linker, otherwise an old version would be used. --- .travis.yml | 8 ++++---- build_libtcod.py | 11 +++++++++-- setup.py | 19 ------------------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9c96dee..25794c32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,20 +3,20 @@ matrix: include: - os: linux python: 3.5 - env: CC=gcc-8 CXX=g++-8 + env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: 3.6 - env: CC=gcc-8 CXX=g++-8 + env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: 3.7 dist: xenial sudo: required - env: CC=gcc-8 CXX=g++-8 + env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: nightly dist: xenial sudo: required - env: CC=gcc-8 CXX=g++-8 + env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: osx language: generic env: MB_PYTHON_VERSION=3.7.1 diff --git a/build_libtcod.py b/build_libtcod.py index 07495244..bb95839e 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -315,8 +315,15 @@ def fix_header(filepath: str) -> None: MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]} MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} GCC_CFLAGS = { - "DEBUG": ["-Og", "-g", "-fPIC"], - "RELEASE": ["-flto", "-O3", "-g", "-fPIC", "-Wno-deprecated-declarations"], + "DEBUG": ["-std=c99", "-Og", "-g", "-fPIC"], + "RELEASE": [ + "-std=c99", + "-flto", + "-O3", + "-g", + "-fPIC", + "-Wno-deprecated-declarations", + ], } if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv: diff --git a/setup.py b/setup.py index 82dd2cd7..c4913432 100755 --- a/setup.py +++ b/setup.py @@ -9,25 +9,6 @@ import platform import warnings -from distutils.unixccompiler import UnixCCompiler - -C_STANDARD = "-std=c99" -CPP_STANDARD = "-std=c++14" - -old_compile = UnixCCompiler._compile - - -def new_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): - cc_args = list(cc_args) - if UnixCCompiler.language_map[ext] == "c": - cc_args.append(C_STANDARD) - elif UnixCCompiler.language_map[ext] == "c++": - cc_args.append(CPP_STANDARD) - return old_compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts) - - -UnixCCompiler._compile = new_compile - def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" From 7f824c203fe46d85ad5ef6b400b795559e5c5428 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 15 May 2020 20:56:31 -0700 Subject: [PATCH 0332/1101] Major refactors of pathfinding code. Added Graph/Pathfinder classes. This is still in progress, but what's in so far works. --- CHANGELOG.rst | 3 + tcod/path.c | 229 +++++++++++++++++++++++++++++++++++++++---------- tcod/path.h | 69 +++++++++++---- tcod/path.py | 233 +++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 463 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1733589e..2dd44709 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Added + - `tcod.path`: New Pathfinder and Graph classes. + Changed - Added `edge_map` parameter to `tcod.path.dijkstra2d` and `tcod.path.hillclimb2d`. diff --git a/tcod/path.c b/tcod/path.c index 709969a6..bd169fc3 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -47,19 +47,28 @@ float PathCostArrayUInt32( return (float)(*(uint32_t*)pick_array_pointer(map, x2, y2)); } -static bool array2d_in_range(const struct NArray4* arr, int i, int j) +static bool array_in_range(const struct NArray* arr, int n, const int* index) +{ + for (int i = 0; i < n; ++i) { + if (index[i] < 0 || index[i] >= arr->shape[i]) { return 0; } + } + return 1; +} +static bool array2d_in_range(const struct NArray* arr, int i, int j) { return 0 <= i && i < arr->shape[0] && 0 <= j && j < arr->shape[1]; } - -static void* get_array2d_at(const struct NArray4* arr, int i, int j) +static void* get_array_ptr(const struct NArray* arr, int n, const int* index) { - return arr->data + arr->strides[0] * i + arr->strides[1] * j; + unsigned char* ptr = (unsigned char*)arr->data; + for (int i = 0; i < n; ++i) { + ptr += arr->strides[i] * index[i]; + } + return (void*)ptr; } - -static int64_t get_array2d_int64(const struct NArray4* arr, int i, int j) +static int64_t get_array_int64(const struct NArray* arr, int n, const int* index) { - const void* ptr = get_array2d_at(arr, i, j); + const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: return *(const int8_t*)ptr; @@ -81,15 +90,14 @@ static int64_t get_array2d_int64(const struct NArray4* arr, int i, int j) return 0; } } -static int get_array2d_int(const struct NArray4* arr, int i, int j) +static int get_array_int(const struct NArray* arr, int n, const int* index) { - return (int)get_array2d_int64(arr, i, j); + return (int)get_array_int64(arr, n, index); } - - -static void set_array2d_int64(struct NArray4* arr, int i, int j, int64_t value) +static void set_array_int64( + struct NArray* arr, int n, const int* index, int64_t value) { - void* ptr = get_array2d_at(arr, i, j); + void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: *(int8_t*)ptr = (int8_t)value; @@ -118,13 +126,22 @@ static void set_array2d_int64(struct NArray4* arr, int i, int j, int64_t value) return; } } -static void set_array2d_int(struct NArray4* arr, int i, int j, int value) +static void set_array2d_int64(struct NArray* arr, int i, int j, int64_t value) +{ + int index[2] = {i, j}; + set_array_int64(arr, 2, index, value); +} +static void set_array_int(struct NArray* arr, int n, const int* index, int value) +{ + set_array_int64(arr, n, index, value); +} +static void set_array2d_int(struct NArray* arr, int i, int j, int value) { set_array2d_int64(arr, i, j, value); } -static int64_t get_array2d_is_max(const struct NArray4* arr, int i, int j) +static int64_t get_array_is_max(const struct NArray* arr, int n, const int* index) { - const void* ptr = get_array2d_at(arr, i, j); + const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: return *(const int8_t*)ptr == SCHAR_MAX; @@ -152,43 +169,42 @@ static const int DIAGONAL_[4][2] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}}; static void dijkstra2d_add_edge( struct TCOD_Frontier* frontier, - struct NArray4* dist_array, - const struct NArray4* cost, + struct NArray* dist_array, + const struct NArray* cost, int edge_cost, const int dir[2]) { const int index[2] = { frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1] }; - if (!array2d_in_range(dist_array, index[0], index[1])) { return; } - edge_cost *= get_array2d_int(cost, index[0], index[1]); + if (!array_in_range(dist_array, 2, index)) { return; } + edge_cost *= get_array_int(cost, 2, index); if (edge_cost <= 0) { return; } int distance = frontier->active_dist + edge_cost; - if (get_array2d_int(dist_array, index[0], index[1]) <= distance) { return; } - set_array2d_int(dist_array, index[0], index[1], distance); + if (get_array_int(dist_array, 2, index) <= distance) { return; } + set_array_int(dist_array, 2, index, distance); TCOD_frontier_push(frontier, index, distance, distance); } int dijkstra2d( - struct NArray4* dist_array, - const struct NArray4* cost, + struct NArray* dist_array, + const struct NArray* cost, int edges_2d_n, - int* edges_2d) + const int* edges_2d) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { for (int j = 0; j < dist_array->shape[1]; ++j) { - if (get_array2d_is_max(dist_array, i, j)) { continue; } const int index[2] = {i, j}; - int dist = get_array2d_int(dist_array, i, j); + if (get_array_is_max(dist_array, 2, index)) { continue; } + int dist = get_array_int(dist_array, 2, index); TCOD_frontier_push(frontier, index, dist, dist); } } while (TCOD_frontier_size(frontier)) { TCOD_frontier_pop(frontier); - int distance_here = get_array2d_int( - dist_array, frontier->active_index[0], frontier->active_index[1]); + int distance_here = get_array_int(dist_array, 2, frontier->active_index); if (frontier->active_dist != distance_here) { continue; } for (int i = 0; i < edges_2d_n; ++i) { dijkstra2d_add_edge( @@ -199,8 +215,8 @@ int dijkstra2d( } int dijkstra2d_basic( - struct NArray4* dist_array, - const struct NArray4* cost, + struct NArray* dist_array, + const struct NArray* cost, int cardinal, int diagonal) { @@ -208,16 +224,15 @@ int dijkstra2d_basic( if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { for (int j = 0; j < dist_array->shape[1]; ++j) { - if (get_array2d_is_max(dist_array, i, j)) { continue; } const int index[2] = {i, j}; - int dist = get_array2d_int(dist_array, i, j); + if (get_array_is_max(dist_array, 2, index)) { continue; } + int dist = get_array_int(dist_array, 2, index); TCOD_frontier_push(frontier, index, dist, dist); } } while (TCOD_frontier_size(frontier)) { TCOD_frontier_pop(frontier); - int distance_here = get_array2d_int( - dist_array, frontier->active_index[0], frontier->active_index[1]); + int distance_here = get_array_int(dist_array, 2, frontier->active_index); if (frontier->active_dist != distance_here) { continue; } if (cardinal > 0) { dijkstra2d_add_edge(frontier, dist_array, cost, cardinal, CARDINAL_[0]); @@ -235,15 +250,15 @@ int dijkstra2d_basic( return TCOD_E_OK; } static void hillclimb2d_check_edge( - const struct NArray4* dist_array, + const struct NArray* dist_array, int* distance_in_out, const int origin[2], const int dir[2], int index_out[2]) { const int next[2] = {origin[0] + dir[0], origin[1] + dir[1]}; - if (!array2d_in_range(dist_array, next[0], next[1])) { return; } - const int next_distance = get_array2d_int(dist_array, next[0], next[1]); + if (!array_in_range(dist_array, 2, next)) { return; } + const int next_distance = get_array_int(dist_array, 2, next); if (next_distance < *distance_in_out) { *distance_in_out = next_distance; index_out[0] = next[0]; @@ -251,16 +266,16 @@ static void hillclimb2d_check_edge( } } int hillclimb2d( - const struct NArray4* dist_array, + const struct NArray* dist_array, int start_i, int start_j, int edges_2d_n, - int* edges_2d, + const int* edges_2d, int* out) { - int old_dist = get_array2d_int(dist_array, start_i, start_j); - int new_dist = old_dist; int next[2] = {start_i, start_j}; + int old_dist = get_array_int(dist_array, 2, next); + int new_dist = old_dist; int length = 0; while (1) { ++length; @@ -281,16 +296,16 @@ int hillclimb2d( } } int hillclimb2d_basic( - const struct NArray4* dist_array, + const struct NArray* dist_array, int start_i, int start_j, bool cardinal, bool diagonal, int* out) { - int old_dist = get_array2d_int(dist_array, start_i, start_j); - int new_dist = old_dist; int next[2] = {start_i, start_j}; + int old_dist = get_array_int(dist_array, 2, next); + int new_dist = old_dist; int length = 0; while (1) { ++length; @@ -318,3 +333,127 @@ int hillclimb2d_basic( old_dist = new_dist; } } +void path_compute_add_edge( + struct TCOD_Frontier* frontier, + struct NArray* dist_map, + struct NArray* travel_map, + const struct NArray* cost_map, + const int* edge_rule) +{ + int dest[TCOD_PATHFINDER_MAX_DIMENSIONS]; + for (int i = 0; i < frontier->ndim; ++i) { + dest[i] = frontier->active_index[i] + edge_rule[i]; + } + if (!array_in_range(dist_map, frontier->ndim, dest)) { return; } + int edge_cost = edge_rule[frontier->ndim]; + edge_cost *= get_array_int(cost_map, frontier->ndim, dest); + if (edge_cost <= 0) { return; } + int distance = frontier->active_dist + edge_cost; + if (get_array_int(dist_map, frontier->ndim, dest) <= distance) { return; } + set_array_int(dist_map, frontier->ndim, dest, distance); + int* path = get_array_ptr(travel_map, frontier->ndim, dest); + for (int i = 0; i < frontier->ndim; ++i) { + path[i] = frontier->active_index[i]; + } + TCOD_frontier_push(frontier, dest, distance, distance); +} + +int path_compute_step( + struct TCOD_Frontier* frontier, + struct NArray* dist_map, + struct NArray* travel_map, + int n, + const struct PathfinderRule* rules) +{ + if (!frontier) { + return TCOD_set_errorv("Missing frontier."); + } + if (frontier->ndim <= 0 || frontier->ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { + return TCOD_set_errorv("Invalid frontier->ndim."); + } + if (!dist_map) { + return TCOD_set_errorv("Missing dist_map."); + } + if (frontier->ndim != dist_map->ndim) { + return TCOD_set_errorv("Invalid or corrupt input."); + } + if (travel_map && frontier->ndim + 1 != travel_map->ndim) { + return TCOD_set_errorv("Invalid or corrupt input."); + } + TCOD_frontier_pop(frontier); + for (int i = 0; i < n; ++i) { + if (rules[i].condition.type) { + if (!get_array_int(&rules[i].condition, frontier->ndim, frontier->active_index)) { + continue; + } + } + for (int edge_i = 0; edge_i < rules[i].edge_count; ++edge_i) { + path_compute_add_edge( + frontier, + dist_map, + travel_map, + &rules[i].cost, + &rules[i].edge_array[edge_i * (frontier->ndim + 1)]); + } + } + return 0; +} +int path_compute( + struct TCOD_Frontier* frontier, + struct NArray* dist_map, + struct NArray* travel_map, + int n, + const struct PathfinderRule* rules) +{ + if (!frontier) { + return TCOD_set_errorv("Missing frontier."); + } + while (TCOD_frontier_size(frontier)) { + int err = path_compute_step( + frontier, + dist_map, + travel_map, + n, + rules); + if (err < 0) { return err; } + } + return 0; +} +size_t get_travel_path( + int8_t ndim, const struct NArray* travel_map, const int* start, int* out) +{ + if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { + return TCOD_set_errorv("Invalid ndim."); + } + if (!travel_map) { + return TCOD_set_errorv("Missing travel_map."); + } + if (!start) { + return TCOD_set_errorv("Missing start."); + } + if (ndim != travel_map->ndim - 1) { + return TCOD_set_errorv("Invalid or corrupt input."); + } + const int* next = get_array_ptr(travel_map, ndim, start); + const int* current = start; + size_t max_loops = 1; + size_t length = 0; + for (int i = 0; i < ndim; ++i) { max_loops *= (size_t)travel_map->shape[i]; } + while (current != next) { + ++length; + if (out) { + for (int i = 0; i < ndim; ++i) { out[i] = current[i]; } + out += ndim; + } + current = next; + if (!array_in_range(travel_map, ndim, next)) { + return TCOD_set_errorvf( + "Index (%i, %i) is out of range.", next[0], next[1]); + } + next = get_array_ptr(travel_map, ndim, next); + if (!out && length == max_loops) { + return TCOD_set_errorv("Possible cyclic loop detected."); + } + } + return length; +} diff --git a/tcod/path.h b/tcod/path.h index 76750338..b1d2a7bf 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -3,6 +3,9 @@ #include #include +#include + +#include "../libtcod/src/libtcod/pathfinder_frontier.h" #ifdef __cplusplus extern "C" { @@ -27,18 +30,30 @@ enum NP_Type { /** * A simple 4D NumPy array ctype. */ -struct NArray4 { +struct NArray { enum NP_Type type; + int8_t ndim; char *data; - ptrdiff_t shape[4]; - ptrdiff_t strides[4]; + ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 + ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 +}; + +struct PathfinderRule { + /** Rule condition, could be uninitialized zeros. */ + struct NArray condition; + /** Edge cost map, required. */ + struct NArray cost; + /** Number of edge rules in `edge_array`. */ + int edge_count; + /** Example of 2D edges: [i, j, cost, i_2, j_2, cost_2, ...] */ + int* edge_array; }; -struct EdgeRule { - int vector[4]; - int cost; - struct NArray4 dest_cost; - struct NArray4 staging; +struct PathfinderHeuristic { + int cardinal; + int diagonal; + int z; + int w; }; struct PathCostArray { @@ -68,33 +83,55 @@ float PathCostArrayInt32( int x1, int y1, int x2, int y2, const struct PathCostArray *map); int dijkstra2d( - struct NArray4* dist, - const struct NArray4* cost, + struct NArray* dist, + const struct NArray* cost, int edges_2d_n, - int* edges_2d); + const int* edges_2d); int dijkstra2d_basic( - struct NArray4* dist, - const struct NArray4* cost, + struct NArray* dist, + const struct NArray* cost, int cardinal, int diagonal); int hillclimb2d( - const struct NArray4* dist_array, + const struct NArray* dist_array, int start_i, int start_j, int edges_2d_n, - int* edges_2d, + const int* edges_2d, int* out); int hillclimb2d_basic( - const struct NArray4* dist, + const struct NArray* dist, int x, int y, bool cardinal, bool diagonal, int* out); +int path_compute_step( + struct TCOD_Frontier* frontier, + struct NArray* dist_map, + struct NArray* travel_map, + int n, + const struct PathfinderRule* rules); + +int path_compute( + struct TCOD_Frontier* frontier, + struct NArray* dist_map, + struct NArray* travel_map, + int n, + const struct PathfinderRule* rules); +/** + Find and get a path along `travel_map`. + + Returns the length of the path, `out` must be NULL or `out[n*ndim]`. + Where `n` is the value return from a previous call with the same + parameters. + */ +size_t get_travel_path( + int8_t ndim, const struct NArray* travel_map, const int* start, int* out); #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index 9e1c9c8b..8564e221 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -41,7 +41,8 @@ array is used.) """ import functools -from typing import Any, Callable, List, Optional, Tuple, Union # noqa: F401 +import itertools +from typing import Any, Callable, Dict, List, Optional, Tuple import numpy as np @@ -316,6 +317,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: np.int8: lib.np_int8, np.int16: lib.np_int16, np.int32: lib.np_int32, + np.intc: lib.np_int32, np.int64: lib.np_int64, np.uint8: lib.np_uint8, np.uint16: lib.np_uint16, @@ -345,17 +347,19 @@ def maxarray( return np.full(shape, np.iinfo(dtype).max, dtype, order) +def _export_dict(array: np.array) -> Dict[str, Any]: + """Convert a NumPy array into a format compatible with CFFI.""" + return { + "type": _INT_TYPES[array.dtype.type], + "ndim": array.ndim, + "data": ffi.cast("void*", array.ctypes.data), + "shape": array.shape, + "strides": array.strides, + } + def _export(array: np.array) -> Any: """Convert a NumPy array into a ctype object.""" - return ffi.new( - "struct NArray4*", - ( - _INT_TYPES[array.dtype.type], - ffi.cast("void*", array.ctypes.data), - array.shape, - array.strides, - ), - ) + return ffi.new("struct NArray*", _export_dict(array)) def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: @@ -596,3 +600,212 @@ def hillclimb2d( c_path = ffi.cast("int*", path.ctypes.data) _check(func(c_path)) return path + + +def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: + return np.ascontiguousarray( + np.transpose( + np.meshgrid( + *(np.arange(i, dtype=np.int32) for i in shape), + indexing="xy", + copy=False, + ) + ) + ) + + +def _as_hashable(obj: Any) -> Any: + if obj is None: + return obj + return obj.ctypes.data, tuple(obj.shape), tuple(obj.strides) + + +class Graph: + """ + Example:: + + >>> import tcod + >>> graph = tcod.path.Graph((5, 5)) + >>> cost = np.ones((5, 5), dtype=np.int8) + >>> CARDINAL = [ + ... [0, 1, 0], + ... [1, 0, 1], + ... [0, 1, 0], + ... ] + >>> graph.add_edges(edge_map=CARDINAL, cost=cost) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.resolve() + >>> pf.distance + array([[0, 1, 2, 3, 4], + [1, 2, 3, 4, 5], + [2, 3, 4, 5, 6], + [3, 4, 5, 6, 7], + [4, 5, 6, 7, 8]]...) + >>> pf.path_to((3, 3)) + array([[0, 0], + [0, 1], + [1, 1], + [2, 1], + [2, 2], + [2, 3], + [3, 3]]...) + + .. versionadded:: 11.13 + """ + + def __init__(self, shape: Tuple[int, ...]): + self._shape = tuple(shape) + self._ndim = len(self._shape) + assert 0 < self._ndim <= 4 + self._graph = {} # type: Dict[Tuple[Any, ...], Dict[str, Any]] + self._edge_rules_keep_alive = [] # type: List[Any] + self._edge_rules_p = None # type: Any + + @property + def ndim(self) -> int: + return self._ndim + + @property + def shape(self) -> Tuple[int, ...]: + return self._shape + + def add_edge( + self, + edge_dir: Tuple[int, ...], + edge_cost: int = 1, + *, + cost: np.ndarray, + condition: Optional[np.ndarray] = None + ) -> None: + self._edge_rules_p = None + edge_dir = tuple(edge_dir) + assert len(edge_dir) == self._ndim + cost = np.asarray(cost) + if condition is not None: + condition = np.asarray(condition) + key = (_as_hashable(cost), _as_hashable(condition)) + try: + rule = self._graph[key] + except KeyError: + rule = self._graph[key] = { + "cost": cost, + "edge_list" : [], + } + if condition is not None: + rule["condition"] = condition + edge = edge_dir + (edge_cost,) + if edge not in rule["edge_list"]: + rule["edge_list"].append(edge) + + def add_edges( + self, + *, + edge_map: Any, + cost: np.ndarray, + condition: Optional[np.ndarray] = None + ) -> None: + edge_map = np.copy(edge_map) + if edge_map.ndim != self._ndim: + raise ValueError( + "edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim) + ) + edge_center = tuple(i // 2 for i in edge_map.shape) + edge_map[edge_center] = 0 + edge_map[edge_map < 0] = 0 + edge_nz = edge_map.nonzero() + edge_array = np.transpose(edge_nz) + edge_array -= edge_center + for edge in edge_array: + edge = tuple(edge) + self.add_edge(edge, edge_map[edge], cost=cost, condition=condition) + + def _compile_rules(self) -> Any: + if not self._edge_rules_p: + self._edge_rules_keep_alive = [] + rules = [] + for rule_ in self._graph.values(): + rule = rule_.copy() + rule["edge_count"] = len(rule["edge_list"]) + # Edge rule format: [i, j, cost, ...] etc. + edge_obj = ffi.new("int[]", len(rule["edge_list"]) * (self._ndim + 1)) + edge_obj[0 : len(edge_obj)] = itertools.chain(*rule["edge_list"]) + self._edge_rules_keep_alive.append(edge_obj) + rule["edge_array"] = edge_obj + self._edge_rules_keep_alive.append(rule["cost"]) + rule["cost"] = _export_dict(rule["cost"]) + if "condition" in rule: + self._edge_rules_keep_alive.append(rule["condition"]) + rule["condition"] = _export_dict(rule["condition"]) + del rule["edge_list"] + rules.append(rule) + self._edge_rules_p = ffi.new("struct PathfinderRule[]", rules) + return self._edge_rules_p, self._edge_rules_keep_alive + + +class Pathfinder: + """ + .. versionadded:: 11.13 + """ + def __init__(self, graph: Graph): + self._graph = graph + self._frontier_p = ffi.gc( + lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete + ) + self._distance = maxarray(self._graph._shape) + self._travel = _world_array(self._graph._shape) + assert self._travel.flags["C_CONTIGUOUS"] + self._distance_p = _export(self._distance) + self._travel_p = _export(self._travel) + + @property + def distance(self) -> np.ndarray: + return self._distance + + def clear(self) -> None: + self._distance[...] = np.iinfo(self._distance.dtype).max + self._travel = _world_array(self._graph._shape) + + def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: + index_ = tuple(index) + assert len(index_) == self._distance.ndim + self._distance[index_] = value + lib.TCOD_frontier_clear(self._frontier_p) + lib.TCOD_frontier_push(self._frontier_p, index_, value, value) + + def resolve(self) -> None: + rules, keep_alive = self._graph._compile_rules() + _check( + lib.path_compute( + self._frontier_p, + self._distance_p, + self._travel_p, + len(rules), + rules, + ) + ) + + def path_from(self, index: Tuple[int, ...]) -> np.ndarray: + self.resolve() + assert len(index) == self._graph._ndim + length = _check( + lib.get_travel_path( + self._graph._ndim, + self._travel_p, + index, + ffi.NULL, + ) + ) + path = np.ndarray((length, self._graph._ndim), dtype=np.intc) + _check( + lib.get_travel_path( + self._graph._ndim, + self._travel_p, + index, + ffi.cast("int*", path.ctypes.data), + ) + ) + return path + + def path_to(self, index: Tuple[int, ...]) -> np.ndarray: + return self.path_from(index)[::-1] From feb0995bf585c5971159a3b9e3d78c69cdb94879 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 17 May 2020 20:48:35 -0700 Subject: [PATCH 0333/1101] Add C heuristic function. --- tcod/path.c | 50 ++++++++++++++++++++++++++++++++++++++++++++------ tcod/path.h | 16 ++++++++++++++-- tcod/path.py | 1 + 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/tcod/path.c b/tcod/path.c index bd169fc3..23e5cb9e 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -3,9 +3,11 @@ #include #include #include +#include #include "../libtcod/src/libtcod/error.h" #include "../libtcod/src/libtcod/pathfinder_frontier.h" +#include "../libtcod/src/libtcod/utility.h" static void* pick_array_pointer(const struct PathCostArray *map, int i, int j) { @@ -333,12 +335,43 @@ int hillclimb2d_basic( old_dist = new_dist; } } +int compute_heuristic( + const struct PathfinderHeuristic* heuristic, int ndim, const int* index) +{ + if (!heuristic) { return 0; } + int x = 0; + int y = 0; + int z = 0; + int w = 0; + switch(ndim) { + case 4: + w = abs(index[ndim - 4] - heuristic->target[ndim - 4]); + //@fallthrough@ + case 3: + z = abs(index[ndim - 3] - heuristic->target[ndim - 3]); + //@fallthrough@ + case 2: + y = abs(index[ndim - 2] - heuristic->target[ndim - 2]); + //@fallthrough@ + case 1: + x = abs(index[ndim - 1] - heuristic->target[ndim - 1]); + break; + default: + return 0; + } + int diagonal = heuristic->diagonal != 0 ? MIN(x, y) : 0; + int straight = MAX(x, y) - diagonal; + return (straight * heuristic->cardinal + diagonal * heuristic->diagonal + + w * heuristic->w + z * heuristic->z); + +} void path_compute_add_edge( struct TCOD_Frontier* frontier, struct NArray* dist_map, struct NArray* travel_map, const struct NArray* cost_map, - const int* edge_rule) + const int* edge_rule, + const struct PathfinderHeuristic* heuristic) { int dest[TCOD_PATHFINDER_MAX_DIMENSIONS]; for (int i = 0; i < frontier->ndim; ++i) { @@ -355,7 +388,8 @@ void path_compute_add_edge( for (int i = 0; i < frontier->ndim; ++i) { path[i] = frontier->active_index[i]; } - TCOD_frontier_push(frontier, dest, distance, distance); + int priority = distance + compute_heuristic(heuristic, frontier->ndim, dest); + TCOD_frontier_push(frontier, dest, distance, priority); } int path_compute_step( @@ -363,7 +397,8 @@ int path_compute_step( struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules) + const struct PathfinderRule* rules, + const struct PathfinderHeuristic* heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); @@ -393,7 +428,8 @@ int path_compute_step( dist_map, travel_map, &rules[i].cost, - &rules[i].edge_array[edge_i * (frontier->ndim + 1)]); + &rules[i].edge_array[edge_i * (frontier->ndim + 1)], + heuristic); } } return 0; @@ -403,7 +439,8 @@ int path_compute( struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules) + const struct PathfinderRule* rules, + const struct PathfinderHeuristic* heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); @@ -414,7 +451,8 @@ int path_compute( dist_map, travel_map, n, - rules); + rules, + heuristic); if (err < 0) { return err; } } return 0; diff --git a/tcod/path.h b/tcod/path.h index b1d2a7bf..d9c94fd8 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -54,6 +54,7 @@ struct PathfinderHeuristic { int diagonal; int z; int w; + int target[TCOD_PATHFINDER_MAX_DIMENSIONS]; }; struct PathCostArray { @@ -82,6 +83,15 @@ float PathCostArrayInt16( float PathCostArrayInt32( int x1, int y1, int x2, int y2, const struct PathCostArray *map); +/** + Return the value to add to the distance to sort nodes by A*. + + `heuristic` can be NULL. + + `index[ndim]` must not be NULL. + */ +int compute_heuristic( + const struct PathfinderHeuristic* heuristic, int ndim, const int* index); int dijkstra2d( struct NArray* dist, const struct NArray* cost, @@ -115,14 +125,16 @@ int path_compute_step( struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules); + const struct PathfinderRule* rules, // rules[n] + const struct PathfinderHeuristic* heuristic); int path_compute( struct TCOD_Frontier* frontier, struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules); + const struct PathfinderRule* rules, // rules[n] + const struct PathfinderHeuristic* heuristic); /** Find and get a path along `travel_map`. diff --git a/tcod/path.py b/tcod/path.py index 8564e221..64a1e6a0 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -782,6 +782,7 @@ def resolve(self) -> None: self._travel_p, len(rules), rules, + ffi.NULL, ) ) From a8df79c7dfb873ee4249f22e42d4ea59737f5760 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 18 May 2020 18:16:04 -0700 Subject: [PATCH 0334/1101] Update libtcod. --- CHANGELOG.rst | 4 ++++ libtcod | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2dd44709..3fb9af2a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,10 @@ Changed - Added `edge_map` parameter to `tcod.path.dijkstra2d` and `tcod.path.hillclimb2d`. +Fixed + - tcod.console_init_root` and context initializing functions were not + raising exceptions on failure. + 11.12.1 - 2020-05-02 -------------------- Fixed diff --git a/libtcod b/libtcod index 3eb359b3..b5142ee0 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 3eb359b3ae17097cd79c1518e421a65bb54e902b +Subproject commit b5142ee02d942c45e6bf1d8e5bb5abbbcb318064 From fa99c0810d0c86e3383706bc43e3aea09ac9cb2d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 18 May 2020 22:11:54 -0700 Subject: [PATCH 0335/1101] Add heuristic support to Graph/Pathfinder classes. --- tcod/path.c | 17 +++++++++ tcod/path.h | 6 +++ tcod/path.py | 104 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 111 insertions(+), 16 deletions(-) diff --git a/tcod/path.c b/tcod/path.c index 23e5cb9e..a47dc4e5 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -495,3 +495,20 @@ size_t get_travel_path( } return length; } +int update_frontier_heuristic( + struct TCOD_Frontier* frontier, + const struct PathfinderHeuristic* heuristic) +{ + if (!frontier) { + return TCOD_set_errorv("Missing frontier."); + } + for (int i = 0; i < frontier->heap.size; ++i) { + unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; + heap_ptr += frontier->heap.node_size * i; + struct TCOD_HeapNode* heap_node = (void*)heap_ptr; + heap_node->priority = + compute_heuristic(heuristic, frontier->ndim, (int*)heap_node->data); + } + TCOD_minheap_heapify(&frontier->heap); + return 0; +} diff --git a/tcod/path.h b/tcod/path.h index d9c94fd8..affe094a 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -144,6 +144,12 @@ int path_compute( */ size_t get_travel_path( int8_t ndim, const struct NArray* travel_map, const int* start, int* out); +/** + Update the priority of nodes on the frontier and sort them. + */ +int update_frontier_heuristic( + struct TCOD_Frontier* frontier, + const struct PathfinderHeuristic* heuristic); #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index 64a1e6a0..92d7f2fe 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -350,13 +350,14 @@ def maxarray( def _export_dict(array: np.array) -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" return { - "type": _INT_TYPES[array.dtype.type], - "ndim": array.ndim, - "data": ffi.cast("void*", array.ctypes.data), - "shape": array.shape, - "strides": array.strides, + "type": _INT_TYPES[array.dtype.type], + "ndim": array.ndim, + "data": ffi.cast("void*", array.ctypes.data), + "shape": array.shape, + "strides": array.strides, } + def _export(array: np.array) -> Any: """Convert a NumPy array into a ctype object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -661,6 +662,7 @@ def __init__(self, shape: Tuple[int, ...]): self._graph = {} # type: Dict[Tuple[Any, ...], Dict[str, Any]] self._edge_rules_keep_alive = [] # type: List[Any] self._edge_rules_p = None # type: Any + self._heuristic = None # type: Optional[Tuple[int, int, int, int]] @property def ndim(self) -> int: @@ -690,7 +692,7 @@ def add_edge( except KeyError: rule = self._graph[key] = { "cost": cost, - "edge_list" : [], + "edge_list": [], } if condition is not None: rule["condition"] = condition @@ -708,7 +710,8 @@ def add_edges( edge_map = np.copy(edge_map) if edge_map.ndim != self._ndim: raise ValueError( - "edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim) + "edge_map must must match graph dimensions (%i). (Got %i)" + % (self.ndim, edge_map.ndim) ) edge_center = tuple(i // 2 for i in edge_map.shape) edge_map[edge_center] = 0 @@ -720,6 +723,20 @@ def add_edges( edge = tuple(edge) self.add_edge(edge, edge_map[edge], cost=cost, condition=condition) + def set_heuristic( + self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0 + ) -> None: + """Sets a pathfinder heuristic so that pathfinding can done with A*.""" + if 0 == cardinal == diagonal == z == w: + self._heuristic = None + if diagonal and cardinal > diagonal: + raise ValueError( + "Diagonal parameter can not be lower than cardinal." + ) + if cardinal < 0 or diagonal < 0 or z < 0 or w < 0: + raise ValueError("Parameters can not be set to negative values..") + self._heuristic = (cardinal, diagonal, z, w) + def _compile_rules(self) -> Any: if not self._edge_rules_p: self._edge_rules_keep_alive = [] @@ -728,8 +745,12 @@ def _compile_rules(self) -> Any: rule = rule_.copy() rule["edge_count"] = len(rule["edge_list"]) # Edge rule format: [i, j, cost, ...] etc. - edge_obj = ffi.new("int[]", len(rule["edge_list"]) * (self._ndim + 1)) - edge_obj[0 : len(edge_obj)] = itertools.chain(*rule["edge_list"]) + edge_obj = ffi.new( + "int[]", len(rule["edge_list"]) * (self._ndim + 1) + ) + edge_obj[0 : len(edge_obj)] = itertools.chain( + *rule["edge_list"] + ) self._edge_rules_keep_alive.append(edge_obj) rule["edge_array"] = edge_obj self._edge_rules_keep_alive.append(rule["cost"]) @@ -747,6 +768,7 @@ class Pathfinder: """ .. versionadded:: 11.13 """ + def __init__(self, graph: Graph): self._graph = graph self._frontier_p = ffi.gc( @@ -757,14 +779,25 @@ def __init__(self, graph: Graph): assert self._travel.flags["C_CONTIGUOUS"] self._distance_p = _export(self._distance) self._travel_p = _export(self._travel) + self._heuristic = ( + None + ) # type: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] + self._heuristic_p = ffi.NULL # type: Any @property def distance(self) -> np.ndarray: return self._distance def clear(self) -> None: + """Reset the pathfinder to its initial state. + + This sets all values on the :any:`distance` array to their maximum + values. Use :any:`numpy.iinfo` if you need to check these. + + """ self._distance[...] = np.iinfo(self._distance.dtype).max self._travel = _world_array(self._graph._shape) + lib.TCOD_frontier_clear(self._frontier_p) def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: index_ = tuple(index) @@ -772,8 +805,34 @@ def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: self._distance[index_] = value lib.TCOD_frontier_clear(self._frontier_p) lib.TCOD_frontier_push(self._frontier_p, index_, value, value) + self._heuristic = None # Reevaluate heuristic on next resolve call. - def resolve(self) -> None: + def _update_heuristic(self, goal: Optional[Tuple[int, ...]]) -> bool: + """Update the active heuristic. Return True if the heuristic changed. + """ + if self._graph._heuristic is None or goal is None: + heuristic = None + else: + heuristic = (*self._graph._heuristic, goal) + if self._heuristic == heuristic: + return False # Frontier does not need updating. + self._heuristic = heuristic + if heuristic is None: + self._heuristic_p = ffi.NULL + else: + self._heuristic_p = ffi.new( + "struct PathfinderHeuristic*", heuristic + ) + return True # Frontier must be updated. + + def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: + """Manually run the pathfinder algorithm.""" + if goal is not None: + assert len(goal) == self._distance.ndim + if self._distance[goal] != np.iinfo(self._distance.dtype).max: + return + if self._update_heuristic(goal): + lib.update_frontier_heuristic(self._frontier_p, self._heuristic_p) rules, keep_alive = self._graph._compile_rules() _check( lib.path_compute( @@ -782,19 +841,28 @@ def resolve(self) -> None: self._travel_p, len(rules), rules, - ffi.NULL, + self._heuristic_p, ) ) def path_from(self, index: Tuple[int, ...]) -> np.ndarray: - self.resolve() + """Return the shortest path from `index` to the nearest root. + + The return value is inclusive, including both the starting and ending + points on the path. If the root point is unreachable or `index` is + already at a root then `index` will be the only point returned. + + This automatically calls :any:`resolve` if the pathfinder has not + yet reached `index`. + + A common usage is to slice off the starting point and convert the array + into a list. + """ + self.resolve(index) assert len(index) == self._graph._ndim length = _check( lib.get_travel_path( - self._graph._ndim, - self._travel_p, - index, - ffi.NULL, + self._graph._ndim, self._travel_p, index, ffi.NULL, ) ) path = np.ndarray((length, self._graph._ndim), dtype=np.intc) @@ -809,4 +877,8 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: return path def path_to(self, index: Tuple[int, ...]) -> np.ndarray: + """Return the shortest path from the nearest root to `index`. + + This is an alias for ``path_from(...)[::-1]``. + """ return self.path_from(index)[::-1] From 89411226583a7c18c772388a2f8850baeb80a146 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 19 May 2020 21:22:04 -0700 Subject: [PATCH 0336/1101] Support rebuilding the frontier from a distance array. --- tcod/path.c | 31 +++++++++++++++++++++++++++++++ tcod/path.h | 7 +++++++ tcod/path.py | 43 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/tcod/path.c b/tcod/path.c index a47dc4e5..d3edc5a1 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -512,3 +512,34 @@ int update_frontier_heuristic( TCOD_minheap_heapify(&frontier->heap); return 0; } +static int update_frontier_from_distance_terator( + struct TCOD_Frontier* frontier, const struct NArray* dist_map, + int dimension, + int* index) +{ + if (dimension == frontier->ndim) { + if (get_array_is_max(dist_map, dimension, index)) { return 0; } + int dist = get_array_int(dist_map, dimension, index); + return TCOD_frontier_push(frontier, index, dist, dist); + } + for (int i = 0; i < dist_map->shape[dimension]; ) { + index[dimension] = i; + int err = update_frontier_from_distance_terator( + frontier, dist_map, dimension + 1, index); + if (err) { return err; } + } + return 0; +} +int rebuild_frontier_from_distance( + struct TCOD_Frontier* frontier, const struct NArray* dist_map) +{ + if (!frontier) { + return TCOD_set_errorv("Missing frontier."); + } + if (!dist_map) { + return TCOD_set_errorv("Missing dist_map."); + } + TCOD_frontier_clear(frontier); + int index[TCOD_PATHFINDER_MAX_DIMENSIONS]; + return update_frontier_from_distance_terator(frontier, dist_map, 0, index); +} diff --git a/tcod/path.h b/tcod/path.h index affe094a..26e410f2 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -150,6 +150,13 @@ size_t get_travel_path( int update_frontier_heuristic( struct TCOD_Frontier* frontier, const struct PathfinderHeuristic* heuristic); +/** + Update a frontier from a distance array. + + Assumes no heuristic is active. + */ +int rebuild_frontier_from_distance( + struct TCOD_Frontier* frontier, const struct NArray* dist_map); #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index 92d7f2fe..2af6ec77 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -786,6 +786,16 @@ def __init__(self, graph: Graph): @property def distance(self) -> np.ndarray: + """The distance values of the pathfinder. + + This array is stored in row-major "C" order. + + Unreachable or unresolved points will be at their maximum values. + You can use :any:`numpy.iinfo` if you need to check for these. + + You may edit this array manually, but the pathfinder won't know of + your changes until :any:`rebuild_frontier` is called. + """ return self._distance def clear(self) -> None: @@ -793,19 +803,25 @@ def clear(self) -> None: This sets all values on the :any:`distance` array to their maximum values. Use :any:`numpy.iinfo` if you need to check these. - """ self._distance[...] = np.iinfo(self._distance.dtype).max self._travel = _world_array(self._graph._shape) lib.TCOD_frontier_clear(self._frontier_p) def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: + """Add a root node and insert it into the pathfinder frontier. + + `index` is the root point to insert. The length of `index` must match + the dimensions of the graph. `index` must also be in 'ij' order. + + `value` is the distance to use for this root. Zero is typical, but + if multiple roots are added they can be given different weights. + """ index_ = tuple(index) assert len(index_) == self._distance.ndim self._distance[index_] = value - lib.TCOD_frontier_clear(self._frontier_p) + self._update_heuristic(None) lib.TCOD_frontier_push(self._frontier_p, index_, value, value) - self._heuristic = None # Reevaluate heuristic on next resolve call. def _update_heuristic(self, goal: Optional[Tuple[int, ...]]) -> bool: """Update the active heuristic. Return True if the heuristic changed. @@ -823,7 +839,23 @@ def _update_heuristic(self, goal: Optional[Tuple[int, ...]]) -> bool: self._heuristic_p = ffi.new( "struct PathfinderHeuristic*", heuristic ) - return True # Frontier must be updated. + lib.update_frontier_heuristic(self._frontier_p, self._heuristic_p) + return True # Frontier was updated. + + def rebuild_frontier(self) -> None: + """Reconstruct the frontier using the current distance array. + + This is needed if the :any:`distance` array is changed manually. + After you are finished editing :any:`distance` you must call this + function before calling :any:`resolve`, :any:`path_from`, etc. + """ + lib.TCOD_frontier_clear(self._frontier_p) + self._update_heuristic(None) + _check( + lib.rebuild_frontier_from_distance( + self._frontier_p, self._distance_p + ) + ) def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: """Manually run the pathfinder algorithm.""" @@ -831,8 +863,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: assert len(goal) == self._distance.ndim if self._distance[goal] != np.iinfo(self._distance.dtype).max: return - if self._update_heuristic(goal): - lib.update_frontier_heuristic(self._frontier_p, self._heuristic_p) + self._update_heuristic(goal) rules, keep_alive = self._graph._compile_rules() _check( lib.path_compute( From f6431c99bc6cb39ff027b60ef6145dbcb272bacb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 20 May 2020 15:57:49 -0700 Subject: [PATCH 0337/1101] Add Pathfinder.traversal and more documentation. --- tcod/path.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 2af6ec77..22b19736 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -604,6 +604,7 @@ def hillclimb2d( def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: + """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( np.meshgrid( @@ -615,14 +616,16 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: ) -def _as_hashable(obj: Any) -> Any: +def _as_hashable(obj: Optional[np.ndarray]) -> Optional[Any]: + """Return NumPy arrays as a more hashable form.""" if obj is None: return obj return obj.ctypes.data, tuple(obj.shape), tuple(obj.strides) class Graph: - """ + """A modular graph defining how a pathfinder traverses the world. + Example:: >>> import tcod @@ -765,7 +768,8 @@ def _compile_rules(self) -> Any: class Pathfinder: - """ + """A generic modular pathfinder. + .. versionadded:: 11.13 """ @@ -793,16 +797,47 @@ def distance(self) -> np.ndarray: Unreachable or unresolved points will be at their maximum values. You can use :any:`numpy.iinfo` if you need to check for these. + Example:: + + pf # Resolved Pathfinder instance. + reachable = pf.distance != numpy.iinfo(pf.distance.dtype).max + reachable # A boolean array of reachable area. + You may edit this array manually, but the pathfinder won't know of your changes until :any:`rebuild_frontier` is called. """ return self._distance + @property + def traversal(self) -> np.ndarray: + """An array used to generate paths from any point to the nearest root. + + This array is stored in row-major "C" order. It has an extra + dimension which includes the index of the next path. + + Example:: + + # This example demonstrates the purpose of the traversal array. + # In real code Pathfinder.path_from(...) should be used instead. + pf # Resolved 2D Pathfinder instance. + i, j = (3, 3) # Starting index. + path = [(i, j)] # List of nodes from the start to the root. + while not (pf.traversal[i, j] == (i, j)).all(): + i, j = pf.traversal[i, j] + path.append((i, j)) + + The above example is slow and will not detect infinite loops. Use + :any:`path_from` or :any:`path_to` when you need to get a path. + + As the pathfinder is resolved this array is filled + """ + return self._travel + def clear(self) -> None: """Reset the pathfinder to its initial state. This sets all values on the :any:`distance` array to their maximum - values. Use :any:`numpy.iinfo` if you need to check these. + value. """ self._distance[...] = np.iinfo(self._distance.dtype).max self._travel = _world_array(self._graph._shape) From 7c1d718a83e5df50053a566c9998086da13d195e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 21 May 2020 22:12:56 -0700 Subject: [PATCH 0338/1101] Rename Graph to CustomGraph, more docs and fixes. --- CHANGELOG.rst | 2 +- tcod/path.c | 26 ++++++-- tcod/path.h | 2 +- tcod/path.py | 177 +++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 190 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3fb9af2a..44071f5d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ v2.0.0 Unreleased ------------------ Added - - `tcod.path`: New Pathfinder and Graph classes. + - `tcod.path`: New Pathfinder and CustomGraph classes. Changed - Added `edge_map` parameter to `tcod.path.dijkstra2d` and diff --git a/tcod/path.c b/tcod/path.c index d3edc5a1..676eacef 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -457,7 +457,7 @@ int path_compute( } return 0; } -size_t get_travel_path( +ptrdiff_t get_travel_path( int8_t ndim, const struct NArray* travel_map, const int* start, int* out) { if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { @@ -474,9 +474,9 @@ size_t get_travel_path( } const int* next = get_array_ptr(travel_map, ndim, start); const int* current = start; - size_t max_loops = 1; - size_t length = 0; - for (int i = 0; i < ndim; ++i) { max_loops *= (size_t)travel_map->shape[i]; } + ptrdiff_t max_loops = 1; + ptrdiff_t length = 0; + for (int i = 0; i < ndim; ++i) { max_loops *= travel_map->shape[i]; } while (current != next) { ++length; if (out) { @@ -485,8 +485,22 @@ size_t get_travel_path( } current = next; if (!array_in_range(travel_map, ndim, next)) { - return TCOD_set_errorvf( - "Index (%i, %i) is out of range.", next[0], next[1]); + switch (ndim) { + case 1: + return TCOD_set_errorvf( + "Index (%i) is out of range.", next[0]); + case 2: + return TCOD_set_errorvf( + "Index (%i, %i) is out of range.", next[0], next[1]); + case 3: + return TCOD_set_errorvf( + "Index (%i, %i, %i) is out of range.", + next[0], next[1], next[2]); + case 4: + return TCOD_set_errorvf( + "Index (%i, %i, %i, %i) is out of range.", + next[0], next[1], next[2], next[3]); + } } next = get_array_ptr(travel_map, ndim, next); if (!out && length == max_loops) { diff --git a/tcod/path.h b/tcod/path.h index 26e410f2..2e7eb9ff 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -142,7 +142,7 @@ int path_compute( Where `n` is the value return from a previous call with the same parameters. */ -size_t get_travel_path( +ptrdiff_t get_travel_path( int8_t ndim, const struct NArray* travel_map, const int* start, int* out); /** Update the priority of nodes on the frontier and sort them. diff --git a/tcod/path.py b/tcod/path.py index 22b19736..80b03d14 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -608,10 +608,11 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: return np.ascontiguousarray( np.transpose( np.meshgrid( - *(np.arange(i, dtype=np.int32) for i in shape), - indexing="xy", + *(np.arange(i, dtype=dtype) for i in shape), + indexing="ij", copy=False, - ) + ), + axes=(*range(1, len(shape) + 1), 0), ) ) @@ -623,13 +624,28 @@ def _as_hashable(obj: Optional[np.ndarray]) -> Optional[Any]: return obj.ctypes.data, tuple(obj.shape), tuple(obj.strides) -class Graph: - """A modular graph defining how a pathfinder traverses the world. +class CustomGraph: + """A customizable graph defining how a pathfinder traverses the world. + + The graph is created with a `shape` defining the size and number of + dimensions of the graph. The `shape` can only be 4 dimensions or lower. + + After this graph is created you'll need to add edges which define the + rules of the pathfinder. These rules usually define movement in the + cardinal and diagonal directions, but can also include stairway type edges. + + After all edge rules are added the graph can be used to make one or more + :any:`Pathfinder` instances. + + Because the arrays used are in row-major order the indexes used in the + examples be reversed from what you expect. + A 2D edge or index is ``(y, x)`` and a 3D they are ``(z, y, x)``. Example:: + >>> import numpy as np >>> import tcod - >>> graph = tcod.path.Graph((5, 5)) + >>> graph = tcod.path.CustomGraph((5, 5)) >>> cost = np.ones((5, 5), dtype=np.int8) >>> CARDINAL = [ ... [0, 1, 0], @@ -669,10 +685,12 @@ def __init__(self, shape: Tuple[int, ...]): @property def ndim(self) -> int: + """The number of dimensions.""" return self._ndim @property def shape(self) -> Tuple[int, ...]: + """The shape of this graph.""" return self._shape def add_edge( @@ -683,9 +701,57 @@ def add_edge( cost: np.ndarray, condition: Optional[np.ndarray] = None ) -> None: + """Add a single edge rule. + + `edge_dir` is a tuple with the same length as the graphs dimensions. + The edge is relative to any node. + + `edge_cost` is the cost multiplier of the edge. Its multiplied with the + `cost` array to the edges actual cost. + + `cost` is a NumPy array where each node has the cost for movement into + that node. Zero or negative values are used to mark blocked areas. + + `condition` is an optional array to mark which nodes have this edge. + If the node in `condition` is zero then the edge will be skipped. + This is useful to mark portals or stairs for some edges. + + Example:: + + >>> import numpy as np + >>> graph3d = tcod.path.CustomGraph((2, 5, 5)) + >>> cost = np.ones((3, 5, 5), dtype=np.int8) + >>> up_stairs = np.zeros((3, 5, 5), dtype=np.int8) + >>> down_stairs = np.zeros((3, 5, 5), dtype=np.int8) + >>> up_stairs[0, 0, 4] = 1 + >>> down_stairs[1, 0, 4] = 1 + >>> CARDINAL = [[0, 1, 0], [1, 0, 1], [0, 1, 0]] + >>> graph3d.add_edges(edge_map=CARDINAL, cost=cost) + >>> graph3d.add_edge((1, 0, 0), 1, cost=cost, condition=up_stairs) + >>> graph3d.add_edge((-1, 0, 0), 1, cost=cost, condition=down_stairs) + >>> pf3d = tcod.path.Pathfinder(graph3d) + >>> pf3d.add_root((0, 1, 1)) + >>> pf3d.path_to((1, 2, 2)) + array([[0, 1, 1], + [0, 1, 2], + [0, 1, 3], + [0, 0, 3], + [0, 0, 4], + [1, 0, 4], + [1, 1, 4], + [1, 1, 3], + [1, 2, 3], + [1, 2, 2]]...) + + Note in the above example that both sets of up/down stairs were added, + but bidirectional edges are not a requirement for the graph. + One directional edges such as pits can be added which will + only allow movement outwards from the root nodes of the pathfinder. + """ # noqa: E501 self._edge_rules_p = None edge_dir = tuple(edge_dir) assert len(edge_dir) == self._ndim + assert edge_cost > 0, (edge_dir, edge_cost) cost = np.asarray(cost) if condition is not None: condition = np.asarray(condition) @@ -710,7 +776,96 @@ def add_edges( cost: np.ndarray, condition: Optional[np.ndarray] = None ) -> None: + """Add a rule with multiple edges. + + `edge_map` is a NumPy array mapping the edges and their costs. + This is easier to understand by looking at the examples below. + Edges are relative to center of the array. The center most value is + always ignored. If `edge_map` has fewer dimensions than the graph then + it will apply to the right-most axes of the graph. + + `cost` is a NumPy array where each node has the cost for movement into + that node. Zero or negative values are used to mark blocked areas. + + `condition` is an optional array to mark which nodes have this edge. + See :any:`add_edge`. + If `condition` is the same array as `cost` then the pathfinder will + not move into open area from a non-open ones. + + Example:: + + # 2D edge maps: + CARDINAL = [ # Simple arrow-key moves. Manhattan distance. + [0, 1, 0], + [1, 0, 1], + [0, 1, 0], + ] + CHEBYSHEV = [ # Chess king moves. Chebyshev distance. + [1, 1, 1], + [1, 0, 1], + [1, 1, 1], + ] + EUCLIDEAN = [ # Approximate euclidean distance. + [99, 70, 99], + [70, 0, 70], + [99, 70, 99], + ] + EUCLIDEAN_SIMPLE = [ # Very approximate euclidean distance. + [3, 2, 3], + [2, 0, 2], + [3, 2, 3], + ] + KNIGHT_MOVE = [ # Chess knight L-moves. + [0, 1, 0, 1, 0], + [1, 0, 0, 0, 1], + [0, 0, 0, 0, 0], + [1, 0, 0, 0, 1], + [0, 1, 0, 1, 0], + ] + AXIAL = [ # https://www.redblobgames.com/grids/hexagons/ + [0, 1, 1], + [1, 0, 1], + [1, 1, 0], + ] + # 3D edge maps: + CARDINAL_PLUS_Z = [ # Cardinal movement with Z up/down edges. + [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ], + [ + [0, 1, 0], + [1, 0, 1], + [0, 1, 0], + ], + [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ], + ] + CHEBYSHEV_3D = [ # Chebyshev distance, but in 3D. + [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + ], + [ + [1, 1, 1], + [1, 0, 1], + [1, 1, 1], + ], + [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + ], + ] + """ edge_map = np.copy(edge_map) + if edge_map.ndim < self._ndim: + edge_map = edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)] if edge_map.ndim != self._ndim: raise ValueError( "edge_map must must match graph dimensions (%i). (Got %i)" @@ -720,11 +875,12 @@ def add_edges( edge_map[edge_center] = 0 edge_map[edge_map < 0] = 0 edge_nz = edge_map.nonzero() + edge_costs = edge_map[edge_nz] edge_array = np.transpose(edge_nz) edge_array -= edge_center - for edge in edge_array: + for edge, edge_cost in zip(edge_array, edge_costs): edge = tuple(edge) - self.add_edge(edge, edge_map[edge], cost=cost, condition=condition) + self.add_edge(edge, edge_cost, cost=cost, condition=condition) def set_heuristic( self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0 @@ -770,10 +926,13 @@ def _compile_rules(self) -> Any: class Pathfinder: """A generic modular pathfinder. + How the pathfinder functions depends on the graph provided. see + :any:`CustomGraph` for how to set these up. + .. versionadded:: 11.13 """ - def __init__(self, graph: Graph): + def __init__(self, graph: CustomGraph): self._graph = graph self._frontier_p = ffi.gc( lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete From 6fe275248f1b3820d20973f28263590c1640a2d1 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 21 May 2020 22:36:21 -0700 Subject: [PATCH 0339/1101] Move pathfinder resolution to the graph class. This is so that other graphs can run a different C function if they want. --- tcod/path.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 80b03d14..8a3f0e6c 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -897,6 +897,7 @@ def set_heuristic( self._heuristic = (cardinal, diagonal, z, w) def _compile_rules(self) -> Any: + """Compile this graph into a C struct array.""" if not self._edge_rules_p: self._edge_rules_keep_alive = [] rules = [] @@ -922,6 +923,20 @@ def _compile_rules(self) -> Any: self._edge_rules_p = ffi.new("struct PathfinderRule[]", rules) return self._edge_rules_p, self._edge_rules_keep_alive + def _resolve(self, pathfinder: "Pathfinder") -> None: + """Run the pathfinding algorithm for this graph.""" + rules, keep_alive = self._compile_rules() + _check( + lib.path_compute( + pathfinder._frontier_p, + pathfinder._distance_p, + pathfinder._travel_p, + len(rules), + rules, + pathfinder._heuristic_p, + ) + ) + class Pathfinder: """A generic modular pathfinder. @@ -1058,17 +1073,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: if self._distance[goal] != np.iinfo(self._distance.dtype).max: return self._update_heuristic(goal) - rules, keep_alive = self._graph._compile_rules() - _check( - lib.path_compute( - self._frontier_p, - self._distance_p, - self._travel_p, - len(rules), - rules, - self._heuristic_p, - ) - ) + self._graph._resolve(self) def path_from(self, index: Tuple[int, ...]) -> np.ndarray: """Return the shortest path from `index` to the nearest root. From d824ce3c51825135e42e8d5a805027fb3654134d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 May 2020 17:26:11 -0700 Subject: [PATCH 0340/1101] Document and fix heuristic function. --- tcod/path.c | 52 +++++++++++++++++++++++++++++++++++++++---- tcod/path.h | 11 +++++++++ tcod/path.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/tcod/path.c b/tcod/path.c index 676eacef..c1ff4fa5 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -391,7 +391,19 @@ void path_compute_add_edge( int priority = distance + compute_heuristic(heuristic, frontier->ndim, dest); TCOD_frontier_push(frontier, dest, distance, priority); } - +/** + Returns true if the heuristic target has been reached by the active_node. + */ +static bool path_compute_at_goal( + const struct TCOD_Frontier* frontier, + const struct PathfinderHeuristic* heuristic) +{ + if (!heuristic) { return 0; } + for (int i = 0; i < frontier->ndim; ++i) { + if (frontier->active_index[i] != heuristic->target[i]) { return 0; } + } + return 1; +} int path_compute_step( struct TCOD_Frontier* frontier, struct NArray* dist_map, @@ -432,6 +444,9 @@ int path_compute_step( heuristic); } } + if (path_compute_at_goal(frontier, heuristic)) { + return 1; // Heuristic target reached. + } return 0; } int path_compute( @@ -453,7 +468,7 @@ int path_compute( n, rules, heuristic); - if (err < 0) { return err; } + if (err != 0) { return err; } } return 0; } @@ -520,8 +535,11 @@ int update_frontier_heuristic( unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; struct TCOD_HeapNode* heap_node = (void*)heap_ptr; - heap_node->priority = - compute_heuristic(heuristic, frontier->ndim, (int*)heap_node->data); + struct FrontierNode* fnode = (struct FrontierNode*)heap_node->data; + heap_node->priority = ( + fnode->distance + + compute_heuristic(heuristic, frontier->ndim, fnode->index) + ); } TCOD_minheap_heapify(&frontier->heap); return 0; @@ -557,3 +575,29 @@ int rebuild_frontier_from_distance( int index[TCOD_PATHFINDER_MAX_DIMENSIONS]; return update_frontier_from_distance_terator(frontier, dist_map, 0, index); } +int frontier_has_index( + const struct TCOD_Frontier* frontier, + const int* index) // index[frontier->ndim] +{ + if (!frontier) { + return TCOD_set_errorv("Missing frontier."); + } + if (!index) { + return TCOD_set_errorv("Missing index."); + } + for (int i = 0; i < frontier->heap.size; ++i) { + const unsigned char* heap_ptr = (const unsigned char*)frontier->heap.heap; + heap_ptr += frontier->heap.node_size * i; + const struct TCOD_HeapNode* heap_node = (const void*)heap_ptr; + const struct FrontierNode* fnode = (const void*)heap_node->data; + bool found = 1; + for (int j = 0; j < frontier->ndim; ++j) { + if (index[j] != fnode->index[j]) { + found = 0; + break; + } + } + if (found) { return 1; } + } + return 0; +} diff --git a/tcod/path.h b/tcod/path.h index 2e7eb9ff..54497f01 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -57,6 +57,11 @@ struct PathfinderHeuristic { int target[TCOD_PATHFINDER_MAX_DIMENSIONS]; }; +struct FrontierNode { + int distance; + int index[]; +}; + struct PathCostArray { char *array; long long strides[2]; @@ -157,6 +162,12 @@ int update_frontier_heuristic( */ int rebuild_frontier_from_distance( struct TCOD_Frontier* frontier, const struct NArray* dist_map); +/** + Return true if `index[frontier->ndim]` is a node in `frontier`. + */ +int frontier_has_index( + const struct TCOD_Frontier* frontier, + const int* index); #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index 8a3f0e6c..3dd49350 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -633,13 +633,15 @@ class CustomGraph: After this graph is created you'll need to add edges which define the rules of the pathfinder. These rules usually define movement in the cardinal and diagonal directions, but can also include stairway type edges. + :any:`set_heuristic` should also be called so that the pathfinder will use + A*. After all edge rules are added the graph can be used to make one or more :any:`Pathfinder` instances. Because the arrays used are in row-major order the indexes used in the examples be reversed from what you expect. - A 2D edge or index is ``(y, x)`` and a 3D they are ``(z, y, x)``. + A 2D edge or index is ``(y, x)`` and in 3D it is ``(z, y, x)``. Example:: @@ -719,6 +721,7 @@ def add_edge( Example:: >>> import numpy as np + >>> import tcod >>> graph3d = tcod.path.CustomGraph((2, 5, 5)) >>> cost = np.ones((3, 5, 5), dtype=np.int8) >>> up_stairs = np.zeros((3, 5, 5), dtype=np.int8) @@ -753,6 +756,7 @@ def add_edge( assert len(edge_dir) == self._ndim assert edge_cost > 0, (edge_dir, edge_cost) cost = np.asarray(cost) + assert cost.ndim == self.ndim if condition is not None: condition = np.asarray(condition) key = (_as_hashable(cost), _as_hashable(condition)) @@ -885,7 +889,55 @@ def add_edges( def set_heuristic( self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0 ) -> None: - """Sets a pathfinder heuristic so that pathfinding can done with A*.""" + """Sets a pathfinder heuristic so that pathfinding can done with A*. + + `cardinal`, `diagonal`, `z, and `w` are the lower-bound cost of + movement in those directions. Values above the lower-bound can be + used to create a greedy heuristic, which will be faster at the cost of + accuracy. + + Example:: + + >>> import numpy as np + >>> import tcod + >>> graph = tcod.path.CustomGraph((5, 5)) + >>> cost = np.ones((5, 5), dtype=np.int8) + >>> EUCLIDEAN = [[99, 70, 99], [70, 0, 70], [99, 70, 99]] + >>> graph.add_edges(edge_map=EUCLIDEAN, cost=cost) + >>> graph.set_heuristic(cardinal=70, diagonal=99) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.path_to((4, 4)) + array([[0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4]]...) + >>> pf.distance + array([[ 0, 70, 198, 2147483647, 2147483647], + [ 70, 99, 169, 297, 2147483647], + [ 198, 169, 198, 268, 396], + [2147483647, 297, 268, 297, 367], + [2147483647, 2147483647, 396, 367, 396]]...) + >>> pf.path_to((2, 0)) + array([[0, 0], + [1, 0], + [2, 0]]...) + >>> pf.distance + array([[ 0, 70, 198, 2147483647, 2147483647], + [ 70, 99, 169, 297, 2147483647], + [ 140, 169, 198, 268, 396], + [ 210, 239, 268, 297, 367], + [2147483647, 2147483647, 396, 367, 396]]...) + + Without a heuristic the above example would need to evaluate the entire + array to reach the opposite side of it. + With a heuristic several nodes can be skipped, which will process + faster. Some of the distances in the above example look incorrect, + that's because those nodes are only partially evaluated, but + pathfinding to those nodes will work correctly as long as the heuristic + isn't greedy. + """ # noqa: E501 if 0 == cardinal == diagonal == z == w: self._heuristic = None if diagonal and cardinal > diagonal: @@ -1035,8 +1087,10 @@ def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: def _update_heuristic(self, goal: Optional[Tuple[int, ...]]) -> bool: """Update the active heuristic. Return True if the heuristic changed. """ - if self._graph._heuristic is None or goal is None: + if goal is None: heuristic = None + elif self._graph._heuristic is None: + heuristic = (0, 0, 0, 0, goal) else: heuristic = (*self._graph._heuristic, goal) if self._heuristic == heuristic: @@ -1071,7 +1125,8 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: if goal is not None: assert len(goal) == self._distance.ndim if self._distance[goal] != np.iinfo(self._distance.dtype).max: - return + if not lib.frontier_has_index(self._frontier_p, goal): + return self._update_heuristic(goal) self._graph._resolve(self) From 4351ad9b20e3620590cd1b6d72b78f4345b45d74 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 22 May 2020 18:59:29 -0700 Subject: [PATCH 0341/1101] Prepare 11.13.0 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 44071f5d..159b12f9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,11 @@ v2.0.0 Unreleased ------------------ + +11.13.0 - 2020-05-22 +-------------------- Added - - `tcod.path`: New Pathfinder and CustomGraph classes. + - `tcod.path`: New `Pathfinder` and `CustomGraph` classes. Changed - Added `edge_map` parameter to `tcod.path.dijkstra2d` and From 555764c23041c804a36523d483e134ec8e974c15 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 26 May 2020 23:02:15 -0700 Subject: [PATCH 0342/1101] Allow Optional[T] return types in EventDispatch methods. This makes the method return types work better with subclasses. The default return value is always None so the return type must include None. It also matches the dispatch method. --- CHANGELOG.rst | 3 +++ tcod/event.py | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 159b12f9..2e8036e4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return + types. 11.13.0 - 2020-05-22 -------------------- diff --git a/tcod/event.py b/tcod/event.py index 84c00ce2..5f2db265 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -885,82 +885,82 @@ def event_wait(self, timeout: Optional[float]) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: Quit) -> T: + def ev_quit(self, event: Quit) -> Optional[T]: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: KeyDown) -> T: + def ev_keydown(self, event: KeyDown) -> Optional[T]: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: KeyUp) -> T: + def ev_keyup(self, event: KeyUp) -> Optional[T]: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: MouseMotion) -> T: + def ev_mousemotion(self, event: MouseMotion) -> Optional[T]: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: MouseButtonDown) -> T: + def ev_mousebuttondown(self, event: MouseButtonDown) -> Optional[T]: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: MouseButtonUp) -> T: + def ev_mousebuttonup(self, event: MouseButtonUp) -> Optional[T]: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: MouseWheel) -> T: + def ev_mousewheel(self, event: MouseWheel) -> Optional[T]: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: TextInput) -> T: + def ev_textinput(self, event: TextInput) -> Optional[T]: """Called to handle Unicode input.""" - def ev_windowshown(self, event: WindowEvent) -> T: + def ev_windowshown(self, event: WindowEvent) -> Optional[T]: """Called when the window is shown.""" - def ev_windowhidden(self, event: WindowEvent) -> T: + def ev_windowhidden(self, event: WindowEvent) -> Optional[T]: """Called when the window is hidden.""" - def ev_windowexposed(self, event: WindowEvent) -> T: + def ev_windowexposed(self, event: WindowEvent) -> Optional[T]: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`tcod.console_flush` is necessary. """ - def ev_windowmoved(self, event: WindowMoved) -> T: + def ev_windowmoved(self, event: WindowMoved) -> Optional[T]: """Called when the window is moved.""" - def ev_windowresized(self, event: WindowResized) -> T: + def ev_windowresized(self, event: WindowResized) -> Optional[T]: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: WindowResized) -> T: + def ev_windowsizechanged(self, event: WindowResized) -> Optional[T]: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: WindowEvent) -> T: + def ev_windowminimized(self, event: WindowEvent) -> Optional[T]: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: WindowEvent) -> T: + def ev_windowmaximized(self, event: WindowEvent) -> Optional[T]: """Called when the window is maximized.""" - def ev_windowrestored(self, event: WindowEvent) -> T: + def ev_windowrestored(self, event: WindowEvent) -> Optional[T]: """Called when the window is restored.""" - def ev_windowenter(self, event: WindowEvent) -> T: + def ev_windowenter(self, event: WindowEvent) -> Optional[T]: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: WindowEvent) -> T: + def ev_windowleave(self, event: WindowEvent) -> Optional[T]: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: WindowEvent) -> T: + def ev_windowfocusgained(self, event: WindowEvent) -> Optional[T]: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: WindowEvent) -> T: + def ev_windowfocuslost(self, event: WindowEvent) -> Optional[T]: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: WindowEvent) -> T: + def ev_windowclose(self, event: WindowEvent) -> Optional[T]: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: WindowEvent) -> T: + def ev_windowtakefocus(self, event: WindowEvent) -> Optional[T]: pass - def ev_windowhittest(self, event: WindowEvent) -> T: + def ev_windowhittest(self, event: WindowEvent) -> Optional[T]: pass - def ev_(self, event: Any) -> T: + def ev_(self, event: Any) -> Optional[T]: pass From eaa3b722647b4a080f10155272985e4ab1048af4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 30 May 2020 15:10:07 -0700 Subject: [PATCH 0343/1101] Prepare 11.13.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2e8036e4..0b795ff3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.1 - 2020-05-30 +-------------------- Fixed - `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return types. From 295c2bda215b2a6f78ec47beeb949e10d6ba66da Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 31 May 2020 03:44:28 -0700 Subject: [PATCH 0344/1101] Add more details to event.get documentation. --- tcod/event.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tcod/event.py b/tcod/event.py index 5f2db265..eec07a4b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -694,6 +694,8 @@ def get() -> Iterator[Any]: Events are processed as the iterator is consumed. Breaking out of, or discarding the iterator will leave the remaining events on the event queue. + It is also safe to call this function inside of a loop that is already + handling events (the event iterator is reentrant.) Example:: @@ -709,6 +711,7 @@ def get() -> Iterator[Any]: print(event) else: print(event) + # For loop exits after all current events are processed. """ sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): @@ -740,6 +743,7 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: print(event) else: print(event) + # For loop exits on timeout or after at least one event is processed. """ if timeout is not None: lib.SDL_WaitEventTimeout(ffi.NULL, int(timeout * 1000)) From bc6d60ae77dac06a7d4257212c6561d197d9b4af Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 31 May 2020 04:03:08 -0700 Subject: [PATCH 0345/1101] Remove unused path module. This module was a temporary experiment, these features were realized in the code and documentation of the current tcod.path module. --- tcod/path2.py | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 tcod/path2.py diff --git a/tcod/path2.py b/tcod/path2.py deleted file mode 100644 index 581be819..00000000 --- a/tcod/path2.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Iterator, Sequence - -import numpy as np - - -def get_2d_edges(cardinal: float, diagonal: float) -> np.ndarray: - return ( - np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) * cardinal - + np.array([[1, 0, 1], [0, 0, 0], [1, 0, 1]]) * diagonal - ) - - -def get_hex_edges(cost: float) -> np.ndarray: - return np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) * cost - - -class EdgeRule: - def __init__(self, vector: Sequence[int], destination: np.ndarray): - self.vector = vector - self.destination = destination - - -def new_rule(edges: np.ndarray) -> Iterator[EdgeRule]: - i_center = (edges.shape[0] - 1) // 2 - j_center = (edges.shape[1] - 1) // 2 - for i in range(edges.shape[0]): - for j in range(edges.shape[1]): - if edges[i, j] == 0: - continue - yield EdgeRule((i - i_center, j - j_center), edges[i, j]) From 55a22a1726fc61398849bd284ee3b681727486b0 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 2 Jun 2020 17:27:39 -0700 Subject: [PATCH 0346/1101] Use ffi.from_buffer instead of a plain cast when possible. This is safer for contiguous arrays and should be the default method when custom strides are not needed. --- tcod/console.py | 8 ++++---- tcod/libtcodpy.py | 20 ++++++++++---------- tcod/map.py | 2 +- tcod/noise.py | 8 ++++---- tcod/path.py | 4 ++-- tcod/sdl.py | 2 +- tcod/tileset.py | 6 +++--- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index c7cb30ea..9d31d30f 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -137,8 +137,8 @@ def __init__( "w": width, "h": height, "elements": width * height, - "tiles": ffi.cast( - "struct TCOD_ConsoleTile*", self._tiles.ctypes.data + "tiles": ffi.from_buffer( + "struct TCOD_ConsoleTile*", self._tiles ), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, @@ -904,8 +904,8 @@ def __setstate__(self, state: Any) -> None: del state["_bg"] self.__dict__.update(state) - self._console_data["tiles"] = ffi.cast( - "struct TCOD_ConsoleTile*", self._tiles.ctypes.data + self._console_data["tiles"] = ffi.from_buffer( + "struct TCOD_ConsoleTile*", self._tiles ) self._console_data = self.console_c = ffi.new( "struct TCOD_Console*", self._console_data diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b1dee599..54c8c289 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1891,9 +1891,9 @@ def console_fill_foreground( r_ = np.ascontiguousarray(r, dtype=np.intc) g_ = np.ascontiguousarray(g, dtype=np.intc) b_ = np.ascontiguousarray(b, dtype=np.intc) - cr = ffi.cast("int *", r_.ctypes.data) - cg = ffi.cast("int *", g_.ctypes.data) - cb = ffi.cast("int *", b_.ctypes.data) + cr = ffi.from_buffer("int *", r_) + cg = ffi.from_buffer("int *", g_) + cb = ffi.from_buffer("int *", b_) else: # otherwise convert using ffi arrays cr = ffi.new("int[]", r) @@ -1932,9 +1932,9 @@ def console_fill_background( r_ = np.ascontiguousarray(r, dtype=np.intc) g_ = np.ascontiguousarray(g, dtype=np.intc) b_ = np.ascontiguousarray(b, dtype=np.intc) - cr = ffi.cast("int *", r_.ctypes.data) - cg = ffi.cast("int *", g_.ctypes.data) - cb = ffi.cast("int *", b_.ctypes.data) + cr = ffi.from_buffer("int *", r_) + cg = ffi.from_buffer("int *", g_) + cb = ffi.from_buffer("int *", b_) else: # otherwise convert using ffi arrays cr = ffi.new("int[]", r) @@ -1957,7 +1957,7 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: if isinstance(arr, np.ndarray): # numpy arrays, use numpy's ctypes functions np_array = np.ascontiguousarray(arr, dtype=np.intc) - carr = ffi.cast("int *", np_array.ctypes.data) + carr = ffi.from_buffer("int *", np_array) else: # otherwise convert using the ffi module carr = ffi.new("int[]", arr) @@ -2319,7 +2319,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) width, height = array.shape - pointer = ffi.cast("float *", array.ctypes.data) + pointer = ffi.from_buffer("float *", array) return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) @@ -3240,8 +3240,8 @@ def line_where( """ length = max(abs(x1 - x2), abs(y1 - y2)) + 1 array = np.ndarray((2, length), dtype=np.intc) - x = ffi.cast("int*", array[0].ctypes.data) - y = ffi.cast("int*", array[1].ctypes.data) + x = ffi.from_buffer("int*", array[0]) + y = ffi.from_buffer("int*", array[1]) lib.LineWhere(x1, y1, x2, y2, x, y) if not inclusive: array = array[:, 1:] diff --git a/tcod/map.py b/tcod/map.py index 001acecc..2a716efe 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -78,7 +78,7 @@ def __as_cdata(self) -> Any: self.width, self.height, self.width * self.height, - ffi.cast("struct TCOD_MapCell*", self.__buffer.ctypes.data), + ffi.from_buffer("struct TCOD_MapCell*", self.__buffer), ), ) diff --git a/tcod/noise.py b/tcod/noise.py index 548b2676..e254389a 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -189,8 +189,8 @@ def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray: lib.NoiseSampleMeshGrid( self._tdl_noise_c, out.size, - ffi.cast("float*", mgrid.ctypes.data), - ffi.cast("float*", out.ctypes.data), + ffi.from_buffer("float*", mgrid), + ffi.from_buffer("float*", out), ) return out @@ -218,8 +218,8 @@ def sample_ogrid(self, ogrid: np.ndarray) -> np.ndarray: self._tdl_noise_c, len(ogrids), out.shape, - [ffi.cast("float*", array.ctypes.data) for array in ogrids], - ffi.cast("float*", out.ctypes.data), + [ffi.from_buffer("float*", array) for array in ogrids], + ffi.from_buffer("float*", out), ) return out diff --git a/tcod/path.py b/tcod/path.py index 3dd49350..cf8aadee 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -598,7 +598,7 @@ def hillclimb2d( ) length = _check(func(ffi.NULL)) path = np.ndarray((length, 2), dtype=np.intc) - c_path = ffi.cast("int*", path.ctypes.data) + c_path = ffi.from_buffer("int*", path) _check(func(c_path)) return path @@ -1156,7 +1156,7 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: self._graph._ndim, self._travel_p, index, - ffi.cast("int*", path.ctypes.data), + ffi.from_buffer("int*", path), ) ) return path diff --git a/tcod/sdl.py b/tcod/sdl.py index 14de2fa5..03b1919c 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -29,7 +29,7 @@ def __init__(self, pixels: np.array) -> None: ) self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( - ffi.cast("void*", self._array.ctypes.data), + ffi.from_buffer("void*", self._array), self._array.shape[1], # Width. self._array.shape[0], # Height. self._array.shape[2] * 8, # Bit depth. diff --git a/tcod/tileset.py b/tcod/tileset.py index 8d489f4d..90eac5cd 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -68,7 +68,7 @@ def get_tile(self, codepoint: int) -> np.ndarray: lib.TCOD_tileset_get_tile_( self._tileset_p, codepoint, - ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data), + ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) return tile @@ -96,7 +96,7 @@ def set_tile(self, codepoint: int, tile: np.ndarray) -> None: lib.TCOD_tileset_set_tile_( self._tileset_p, codepoint, - ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data), + ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) def render(self, console: tcod.console.Console) -> np.ndarray: @@ -118,7 +118,7 @@ def render(self, console: tcod.console.Console) -> np.ndarray: out[:] = 9 surface_p = ffi.gc( lib.SDL_CreateRGBSurfaceWithFormatFrom( - ffi.cast("void*", out.ctypes.data), + ffi.from_buffer("void*", out), width, height, 32, From 831500ad25bb12007b227d4eab2357366c26d66d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 17:22:44 -0700 Subject: [PATCH 0347/1101] Give better errors for old/missing SDL installations. --- CHANGELOG.rst | 2 ++ setup.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b795ff3..309b5f55 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - SDL related errors during package installation are now more readable. 11.13.1 - 2020-05-30 -------------------- diff --git a/setup.py b/setup.py index c4913432..b7c21fb2 100755 --- a/setup.py +++ b/setup.py @@ -9,6 +9,8 @@ import platform import warnings +SDL_VERSION_NEEDED = (2, 0, 5) + def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" @@ -75,6 +77,30 @@ def get_long_description(): return "\n".join([readme, changelog]) +def check_sdl_version(): + """Check the local SDL version on Linux distributions.""" + if not sys.platform.startswith("linux"): + return + needed_version = "%i.%i.%i" % SDL_VERSION_NEEDED + try: + sdl_version_str = check_output( + ["sdl2-config", "--version"], universal_newlines=True + ).strip() + except FileNotFoundError: + raise RuntimeError( + "libsdl2-dev or equivalent must be installed on your system" + " and must be at least version %s." + "\nsdl2-config must be on PATH." % (needed_version,) + ) + print("Found SDL %s." % (sdl_version_str,)) + sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) + if sdl_version < SDL_VERSION_NEEDED: + raise RuntimeError( + "SDL version must be at least %s, (found %s)" + % (needed_version, sdl_version_str) + ) + + if sys.version_info < (3, 5): error = """ This version of python-tcod only supports Python 3.5 and above. @@ -96,6 +122,8 @@ def get_long_description(): print("Did you forget to run 'git submodule update --init'?") sys.exit(1) +check_sdl_version() + needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) pytest_runner = ["pytest-runner"] if needs_pytest else [] From 65711eba0de119d3522319f91f7bec3d37d3bfc6 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 18:04:30 -0700 Subject: [PATCH 0348/1101] Fix TravisCI config warnings. --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25794c32..b0eed3bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python -matrix: +jobs: include: - os: linux python: 3.5 @@ -10,12 +10,10 @@ matrix: - os: linux python: 3.7 dist: xenial - sudo: required env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: nightly dist: xenial - sudo: required env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: osx language: generic @@ -29,8 +27,7 @@ matrix: fast_finish: true -# sudo is required to prevent xvfb crashes from container-based workers. -sudo: required +os: linux dist: trusty cache: pip From 13cae0c43094a059dc68935cba93b54dd021b78d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 18:48:18 -0700 Subject: [PATCH 0349/1101] Prepare 11.13.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 309b5f55..6fa95bd4 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.2 - 2020-06-12 +-------------------- Fixed - SDL related errors during package installation are now more readable. From ddf26f71b2809e3a531139863c0d0b298284dc52 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 19:29:51 -0700 Subject: [PATCH 0350/1101] Change TravisCI to use Ubuntu Bionic environment. Use the system install of SDL when it's up-to-date. --- .travis.yml | 4 +--- .travis/before_install.sh | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index b0eed3bb..333076af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,9 @@ jobs: env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: 3.7 - dist: xenial env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: nightly - dist: xenial env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: osx language: generic @@ -28,7 +26,7 @@ jobs: fast_finish: true os: linux -dist: trusty +dist: bionic cache: pip addons: diff --git a/.travis/before_install.sh b/.travis/before_install.sh index d5b30c61..0617766b 100644 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -1,10 +1,19 @@ #!/bin/bash if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start + SDL_MAJOR=2 + SDL_MINOR=5 + UPDATE_SDL=true - # Update SDL2 to a recent version. - wget -O - https://www.libsdl.org/release/SDL2-2.0.8.tar.gz | tar xz - (cd SDL2-* && ./configure --prefix=$HOME/.local && make -j 3 install) - PATH=~/.local/bin:$PATH + if [[ $(sdl2-config --version) =~ ([0-9]*).([0-9]*).([0-9]*) ]]; then + if (( ${BASH_REMATCH[1]} >= $SDL_MAJOR && ${BASH_REMATCH[3]} >= $SDL_MINOR )); then + UPDATE_SDL=false + fi + fi + + if [[ $UPDATE_SDL == "true" ]]; then + # Update SDL2 to a recent version. + wget -O - https://www.libsdl.org/release/SDL2-2.0.8.tar.gz | tar xz + (cd SDL2-* && ./configure --prefix=$HOME/.local && make -j 3 install) + PATH=~/.local/bin:$PATH + fi fi From 8c7d5a96297da8b55f3470cca20fdbb9b41a2ab3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 20:24:56 -0700 Subject: [PATCH 0351/1101] Use system GCC with TravisCI. --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 333076af..c5b3a8f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,12 @@ jobs: include: - os: linux python: 3.5 - env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: 3.6 - env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: 3.7 - env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: linux python: nightly - env: CC=gcc-8 CXX=g++-8 LDSHARED="gcc-8 -shared" - os: osx language: generic env: MB_PYTHON_VERSION=3.7.1 @@ -31,10 +27,7 @@ cache: pip addons: apt: - sources: - - ubuntu-toolchain-r-test packages: - - g++-8 - libsdl2-dev services: - xvfb From 69d47a0c446a5dfae2bc6d338935f12cec9ebdfa Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 12 Jun 2020 20:37:10 -0700 Subject: [PATCH 0352/1101] Add Python 3.8 job to TravisCI. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c5b3a8f1..7d15def4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ jobs: python: 3.6 - os: linux python: 3.7 + - os: linux + python: 3.8 - os: linux python: nightly - os: osx From 3eb7b61901a0b6f7f20e375b9c1dd764a01d923a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 13 Jun 2020 08:53:13 -0700 Subject: [PATCH 0353/1101] Update required cffi version. The older versions don't support the current uses of from_buffer. --- CHANGELOG.rst | 3 +++ requirements.txt | 4 ++-- setup.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6fa95bd4..ec1a5c3e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Fixed + - `cffi` requirement has been updated to version `1.13.0`. + The older versions raise TypeError's. 11.13.2 - 2020-06-12 -------------------- diff --git a/requirements.txt b/requirements.txt index c7292a91..f4c74570 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cffi>=1.12.0,<2 +cffi~=1.13.0 numpy>=1.10,<2,!=1.16.3; -pycparser>=2.14,<3 +pycparser>=2.14 setuptools>=36.0.1 diff --git a/setup.py b/setup.py index b7c21fb2..357dc1f7 100755 --- a/setup.py +++ b/setup.py @@ -140,11 +140,11 @@ def check_sdl_version(): package_data={"tdl": ["*.png"], "tcod": get_package_data()}, python_requires=">=3.5", install_requires=[ - "cffi>=1.12.0,<2", + "cffi~=1.13.0", "numpy>=1.10,<2" if not is_pypy else "", ], cffi_modules=["build_libtcod.py:ffi"], - setup_requires=["cffi>=1.8.1,<2", "pycparser>=2.14,<3"] + pytest_runner, + setup_requires=["cffi~=1.13.0", "pycparser>=2.14"] + pytest_runner, tests_require=["pytest", "pytest-cov", "pytest-benchmark"], classifiers=[ "Development Status :: 5 - Production/Stable", From 2419c7bdc72f87efd7e44f1319409738e02bed03 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 13 Jun 2020 09:21:59 -0700 Subject: [PATCH 0354/1101] Prepare 11.13.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec1a5c3e..0449b803 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.3 - 2020-06-13 +-------------------- Fixed - `cffi` requirement has been updated to version `1.13.0`. The older versions raise TypeError's. From 8b58b6c65933b920858e7141b73032896f71ed37 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 15 Jun 2020 01:02:49 -0700 Subject: [PATCH 0355/1101] Fix install requirements. --- CHANGELOG.rst | 2 ++ requirements.txt | 4 ++-- setup.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0449b803..052c8c02 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Install requirements will no longer try to downgrade `cffi`. 11.13.3 - 2020-06-13 -------------------- diff --git a/requirements.txt b/requirements.txt index f4c74570..14a5dff4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cffi~=1.13.0 -numpy>=1.10,<2,!=1.16.3; +cffi~=1.13 +numpy~=1.10,!=1.16.3; pycparser>=2.14 setuptools>=36.0.1 diff --git a/setup.py b/setup.py index 357dc1f7..cdf12111 100755 --- a/setup.py +++ b/setup.py @@ -140,8 +140,8 @@ def check_sdl_version(): package_data={"tdl": ["*.png"], "tcod": get_package_data()}, python_requires=">=3.5", install_requires=[ - "cffi~=1.13.0", - "numpy>=1.10,<2" if not is_pypy else "", + "cffi~=1.13", + "numpy~=1.10" if not is_pypy else "", ], cffi_modules=["build_libtcod.py:ffi"], setup_requires=["cffi~=1.13.0", "pycparser>=2.14"] + pytest_runner, From 31c89613c56253818a55d07b4c91bba809613e89 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 15 Jun 2020 01:04:24 -0700 Subject: [PATCH 0356/1101] Prepare 11.13.4 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 052c8c02..93287890 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.4 - 2020-06-15 +-------------------- Fixed - Install requirements will no longer try to downgrade `cffi`. From 6288123e6b5ded6a351338da00a5cce9c41f46da Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 15 Jun 2020 01:44:27 -0700 Subject: [PATCH 0357/1101] Remove unneeded setup requires. Also add a note that cffi is included in pyproject.toml. --- CHANGELOG.rst | 4 ++-- pyproject.toml | 4 ++-- setup.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 93287890..dab0637f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,11 +8,11 @@ v2.0.0 Unreleased ------------------ +Fixed + - Install requirements will no longer try to downgrade `cffi`. 11.13.4 - 2020-06-15 -------------------- -Fixed - - Install requirements will no longer try to downgrade `cffi`. 11.13.3 - 2020-06-13 -------------------- diff --git a/pyproject.toml b/pyproject.toml index 515b1dde..543a9300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,8 +2,8 @@ requires = [ "setuptools", "wheel", - "cffi>=1.12.0,<2", - "pycparser>=2.14,<3", + "cffi~=1.13", + "pycparser>=2.14", ] [tool.black] diff --git a/setup.py b/setup.py index cdf12111..6d77e800 100755 --- a/setup.py +++ b/setup.py @@ -140,11 +140,11 @@ def check_sdl_version(): package_data={"tdl": ["*.png"], "tcod": get_package_data()}, python_requires=">=3.5", install_requires=[ - "cffi~=1.13", + "cffi~=1.13", # Also required by pyproject.toml. "numpy~=1.10" if not is_pypy else "", ], cffi_modules=["build_libtcod.py:ffi"], - setup_requires=["cffi~=1.13.0", "pycparser>=2.14"] + pytest_runner, + setup_requires=pytest_runner, tests_require=["pytest", "pytest-cov", "pytest-benchmark"], classifiers=[ "Development Status :: 5 - Production/Stable", From 7ecb804aaf007970e0e4e0c33acb442d6b1a04c2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 15 Jun 2020 01:47:20 -0700 Subject: [PATCH 0358/1101] Prepare 11.13.5 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dab0637f..bbb43358 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.5 - 2020-06-15 +-------------------- Fixed - Install requirements will no longer try to downgrade `cffi`. From 394fe1e3a5b6cb284436d8614b23643dc77acabe Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 17 Jun 2020 05:31:05 -0700 Subject: [PATCH 0359/1101] Update docs for draw_frame. To make it more clear how the colors are used. --- tcod/console.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 9d31d30f..249d80c5 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1067,12 +1067,13 @@ def draw_frame( `width` and `height` determine the size of the frame. - `title` is a Unicode string. + `title` is a Unicode string. The title is drawn with `bg` as the text + color and `fg` as the background. If `clear` is True than the region inside of the frame will be cleared. - `fg` and `bg` are the foreground text color and background tile color - respectfully. This is a 3-item tuple with (r, g, b) color values from + `fg` and `bg` are the foreground and background colors for the frame + border. This is a 3-item tuple with (r, g, b) color values from 0 to 255. These parameters can also be set to `None` to leave the colors unchanged. From 944714ded00d113ffda0d0efb45c72b43ed3d968 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 18 Jun 2020 08:15:45 -0700 Subject: [PATCH 0360/1101] Move starting script examples to a getting started page. --- docs/index.rst | 1 + docs/installation.rst | 2 + docs/tcod/getting-started.rst | 112 ++++++++++++++++++++++++++++++++++ tcod/console.py | 26 +------- tcod/context.py | 26 +------- 5 files changed, 120 insertions(+), 47 deletions(-) create mode 100644 docs/tcod/getting-started.rst diff --git a/docs/index.rst b/docs/index.rst index ebeeeef1..e0dc6591 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,7 @@ Contents: :maxdepth: 2 :caption: python-tcod API + tcod/getting-started tcod/bsp tcod/console tcod/context diff --git a/docs/installation.rst b/docs/installation.rst index 3feae7e7..6bd1215b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,3 +1,5 @@ +.. _installation: + Installation ============ Once installed, you'll be able to import the `tcod` and `libtcodpy` modules, diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst new file mode 100644 index 00000000..e4ce29cb --- /dev/null +++ b/docs/tcod/getting-started.rst @@ -0,0 +1,112 @@ +.. _getting-started: + +Getting Started +=============== + +Python 3 and python-tcod must be installed, see :ref:`installation`. + +Fixed-size console +------------------ + +The first example is a hello world script which handles font loading, +fixed-sized consoles, window contexts, and event handling. +This example requires the +`dejavu10x10_gs_tc.png `_ +font to be in the same directory as the script. + +By default this will create a window which can be resized and the fixed-size +console will be stretched to fit the window. You can add arguments to +:any:`Context.present` to fix the aspect ratio or only scale the console by +integer increments. + +Example:: + + #!/usr/bin/env python3 + # Make sure 'dejavu10x10_gs_tc.png' is in the same directory as this script. + import tcod + + WIDTH, HEIGHT = 80, 60 # Console width and height in tiles. + + + def main() -> None: + """Script entry point.""" + # Load the font, a 64 by 8 tile font with libtcod's old character layout. + tileset = tcod.tileset.load_tilesheet( + "dejavu10x10_gs_tc.png", 64, 8, tcod.tileset.CHARMAP_TCOD, + ) + # Create the main console. + console = tcod.Console(WIDTH, HEIGHT) + # Create a window based on this console and tileset. + with tcod.context.new_terminal( + console.width, console.height, tileset=tileset, + ) as context: + while True: # Main loop, runs until SystemExit is raised. + console.clear() + console.print(x=0, y=0, string="Hello World!") + context.present(console) # Show the console. + + for event in tcod.event.wait(): + context.convert_event(event) # Sets tile coordinates for mouse events. + print(event) # Print event information to stdout. + if event.type == "QUIT": + raise SystemExit() + # The window will be closed after the above with-block exits. + + + if __name__ == "__main__": + main() + +Dynamically-sized console +------------------------- + +The next example shows a more advanced setup. A maximized window is created +and the console is dynamically scaled to fit within it. If the window is +resized then the console will be replaced by a new dynamically sized console. + +Because a tileset wasn't manually loaded in this example an OS dependent +fallback font will be used. This is useful for prototyping but it's not +recommended to release with this font since it can fail to load on some +platforms. + +The `integer_scaling` parameter to :any:`Context.present` presents the console +from being slightly stretched, since the console will rarely be the prefect +size a small border will exist. + +You'll need to consider things like the console being too small for your code +to handle or the tiles being small compared to an extra large monitor +resolution. :any:`Context.recommended_console_size` can be given a minimum +size that it will never go below. + +Example:: + + #!/usr/bin/env python3 + import tcod + + WIDTH, HEIGHT = 720, 480 # Window pixel resolution (when not maximized.) + FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED + + + def main() -> None: + """Script entry point.""" + with tcod.context.new_window( + WIDTH, HEIGHT, sdl_window_flags=FLAGS + ) as context: + # Create the console based on the context. + console = tcod.Console(*context.recommended_console_size()) + while True: + console.clear() + console.print(0, 0, "Hello World") + context.present(console, integer_scaling=True) + + for event in tcod.event.wait(): + context.convert_event(event) # Sets tile coordinates for mouse events. + print(event) + if event.type == "QUIT": + raise SystemExit() + if event.type == "WINDOWRESIZED": + # Replace the console with one that fits the new resolution. + console = tcod.Console(*context.recommended_console_size()) + + + if __name__ == "__main__": + main() diff --git a/tcod/console.py b/tcod/console.py index 249d80c5..1dc29c4f 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1,27 +1,7 @@ """ -libtcod works with a special 'root' console. You create this console using -the :any:`tcod.console_init_root` function. Usually after setting the font -with :any:`console_set_custom_font` first. - -Example:: - - # Make sure 'arial10x10.png' is in the same directory as this script. - import tcod - - # Setup the font. - tcod.console_set_custom_font( - "arial10x10.png", - tcod.FONT_LAYOUT_TCOD | tcod.FONT_TYPE_GREYSCALE, - ) - # Initialize the root console in a context. - with tcod.console_init_root(80, 60, order="F") as root_console: - root_console.print_(x=0, y=0, string='Hello World!') - while True: - tcod.console_flush() # Show the console. - for event in tcod.event.wait(): - if event.type == "QUIT": - raise SystemExit() - # The libtcod window will be closed at the end of this with-block. +Libtcod consoles are a strictly tile-based representation of text and color. +To render a console you need a tileset and a window to render to. +See :ref:`getting-started` for info on how to set those up. """ from typing import Any, Optional, Tuple # noqa: F401 diff --git a/tcod/context.py b/tcod/context.py index 159c9c04..84c588d7 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -1,5 +1,7 @@ """This module is used to create and handle libtcod contexts. +See :ref:`getting-started` for beginner examples on how to use this module. + :any:`Context`'s are intended to replace several libtcod functions such as :any:`tcod.console_init_root`, :any:`tcod.console_flush`, :any:`tcod.console.recommended_size`, and many other functions which rely on @@ -44,30 +46,6 @@ :any:`tcod.mouse_get_status`. .. versionadded:: 11.12 - -Example:: - - WIDTH, HEIGHT = 720, 480 - FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED - - with tcod.context.new_window( - WIDTH, HEIGHT, sdl_window_flags=FLAGS - ) as context: - console = tcod.Console(*context.recommended_console_size()) - while True: - # Display the console. - console.clear() - console.print(0, 0, "Hello World") - context.present(console, integer_scaling=True) - - # Handle events. - for event in tcod.event.wait(): - context.convert_event(event) # Set tile coordinates for an event. - print(event) - if event.type == "QUIT": - raise SystemExit() - if event.type == "WINDOWRESIZED": - console = tcod.Console(*context.recommended_console_size()) """ # noqa: E501 import sys import os From 3f531eaabc5eb830c45a46b372379b453e661e7d Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 18 Jun 2020 08:29:21 -0700 Subject: [PATCH 0361/1101] Normalize indentation of document index. --- docs/index.rst | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e0dc6591..24553bc9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,38 +13,38 @@ Welcome to the python-tcod documentation! Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - installation - glossary + installation + glossary .. toctree:: - :maxdepth: 1 + :maxdepth: 1 - changelog + changelog .. toctree:: - :maxdepth: 2 - :caption: python-tcod API - - tcod/getting-started - tcod/bsp - tcod/console - tcod/context - tcod/event - tcod/image - tcod/map - tcod/noise - tcod/path - tcod/random - tcod/tileset - libtcodpy + :maxdepth: 2 + :caption: python-tcod API + + tcod/getting-started + tcod/bsp + tcod/console + tcod/context + tcod/event + tcod/image + tcod/map + tcod/noise + tcod/path + tcod/random + tcod/tileset + libtcodpy .. toctree:: - :maxdepth: 2 - :caption: Legacy tdl API + :maxdepth: 2 + :caption: Legacy tdl API - tdl + tdl Indices and tables ================== From e86e1c59eff527b55ed379c9546300dceb8b72d2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 18 Jun 2020 08:31:36 -0700 Subject: [PATCH 0362/1101] Fix easy Sphinx warnings. --- docs/conf.py | 2 +- docs/readme.rst | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 docs/readme.rst diff --git a/docs/conf.py b/docs/conf.py index 70a3fa37..dbbcda12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -167,7 +167,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 72a33558..00000000 --- a/docs/readme.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../README.rst From e247e8f54bb0799bfbb6b02a69a86a9ebcb0af19 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 18 Jun 2020 21:24:30 -0700 Subject: [PATCH 0363/1101] Deprecate the old initialization and font functions. --- CHANGELOG.rst | 3 +++ tcod/libtcodpy.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bbb43358..8f32382b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Deprecated + - `console_init_root` and `console_set_custom_font` have been replaced by the + modern API. 11.13.5 - 2020-06-15 -------------------- diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 54c8c289..19b9737e 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -863,6 +863,11 @@ def color_gen_map( return [Color._new_from_cdata(cdata) for cdata in cres] +@deprecate( + """console_init_root is deprecated in favor of using libtcod contexts. +See the Getting Started documentation: +https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html""" +) def console_init_root( w: int, h: int, @@ -930,6 +935,10 @@ def console_init_root( .. versionchanged:: 10.1 Added the `vsync` parameter. + + .. deprecated:: 11.13 + Use :any:`tcod.context` for window management. + See :ref:`getting-started` for more info. """ if title is None: # Use the scripts filename as the title. @@ -960,6 +969,11 @@ def console_init_root( return console +@deprecate( + """console_set_custom_font is deprecated in favor of using contexts. +See the Getting Started documentation: +https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html""" +) def console_set_custom_font( fontFile: AnyStr, flags: int = FONT_LAYOUT_ASCII_INCOL, @@ -986,6 +1000,10 @@ def console_set_custom_font( `nb_char_horiz` and `nb_char_vertic` are the columns and rows of the font file respectfully. + + .. deprecated:: 11.13 + Load fonts using :any:`tcod.tileset.load_tileheet` instead. + See :ref:`getting-started` for more info. """ if not os.path.exists(fontFile): raise RuntimeError( From 09d6215d0e4acc9fcbc4b244070c1461fdaa7e5a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 19 Jun 2020 01:49:01 -0700 Subject: [PATCH 0364/1101] Deprecate functions using internal global objects. --- CHANGELOG.rst | 2 + tcod/console.py | 5 ++ tcod/libtcodpy.py | 121 ++++++++++++++++++++++++++++++++++++++++------ tcod/tileset.py | 17 ++++++- 4 files changed, 129 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f32382b..0332d28c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,8 @@ Unreleased Deprecated - `console_init_root` and `console_set_custom_font` have been replaced by the modern API. + - All functions which handle SDL windows without a context are deprecated. + - All functions which modify a globally active tileset are deprecated. 11.13.5 - 2020-06-15 -------------------- diff --git a/tcod/console.py b/tcod/console.py index 1dc29c4f..75893db0 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1160,6 +1160,7 @@ def get_height_rect(width: int, string: str) -> int: ) +@deprecate("This function does not support contexts.") def recommended_size() -> Tuple[int, int]: """Return the recommended size of a console for the current active window. @@ -1173,6 +1174,10 @@ def recommended_size() -> Tuple[int, int]: .. seealso:: :any:`tcod.console_init_root` :any:`tcod.console_flush` + + .. deprecated:: 11.13 + This function does not support contexts. + Use :any:`Context.recommended_console_size` instead. """ if not lib.TCOD_ctx.engine: raise RuntimeError("The libtcod engine was not initialized first.") diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 19b9737e..133de0ea 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1048,7 +1048,7 @@ def console_get_height(con: tcod.console.Console) -> int: return int(lib.TCOD_console_get_height(_console(con))) -@pending_deprecate() +@deprecate("Setup fonts using the tcod.tileset module.") def console_map_ascii_code_to_font( asciiCode: int, fontCharX: int, fontCharY: int ) -> None: @@ -1062,13 +1062,17 @@ def console_map_ascii_code_to_font( 0 is the leftmost tile. fontCharY (int): The Y tile coordinate on the loaded tileset. 0 is the topmost tile. + + .. deprecated:: 11.13 + Setup fonts using the :any:`tcod.tileset` module. + :any:`Tileset.remap` replaces this function. """ lib.TCOD_console_map_ascii_code_to_font( _int(asciiCode), fontCharX, fontCharY ) -@pending_deprecate() +@deprecate("Setup fonts using the tcod.tileset module.") def console_map_ascii_codes_to_font( firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int ) -> None: @@ -1086,13 +1090,17 @@ def console_map_ascii_codes_to_font( fontCharY (int): The starting Y tile coordinate on the loaded tileset. 0 is the topmost tile. + .. deprecated:: 11.13 + Setup fonts using the :any:`tcod.tileset` module. + :any:`Tileset.remap` replaces this function. + """ lib.TCOD_console_map_ascii_codes_to_font( _int(firstAsciiCode), nbCodes, fontCharX, fontCharY ) -@pending_deprecate() +@deprecate("Setup fonts using the tcod.tileset module.") def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: """Remap a string of codes to a contiguous set of tiles. @@ -1104,25 +1112,37 @@ def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: 0 is the leftmost tile. fontCharY (int): The starting Y tile coordinate on the loaded tileset. 0 is the topmost tile. + + .. deprecated:: 11.13 + Setup fonts using the :any:`tcod.tileset` module. + :any:`Tileset.remap` replaces this function. """ lib.TCOD_console_map_string_to_font_utf(_unicode(s), fontCharX, fontCharY) +@deprecate("This function is not supported if contexts are being used.") def console_is_fullscreen() -> bool: """Returns True if the display is fullscreen. Returns: bool: True if the display is fullscreen, otherwise False. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ return bool(lib.TCOD_console_is_fullscreen()) +@deprecate("This function is not supported if contexts are being used.") def console_set_fullscreen(fullscreen: bool) -> None: """Change the display to be fullscreen or windowed. Args: fullscreen (bool): Use True to change to fullscreen. Use False to change to windowed. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ lib.TCOD_console_set_fullscreen(fullscreen) @@ -1137,23 +1157,35 @@ def console_is_window_closed() -> bool: return bool(lib.TCOD_console_is_window_closed()) -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def console_has_mouse_focus() -> bool: - """Return True if the window has mouse focus.""" + """Return True if the window has mouse focus. + + .. deprecated:: 11.13 + This function is not supported by contexts. + """ return bool(lib.TCOD_console_has_mouse_focus()) -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def console_is_active() -> bool: - """Return True if the window has keyboard focus.""" + """Return True if the window has keyboard focus. + + .. deprecated:: 11.13 + This function is not supported by contexts. + """ return bool(lib.TCOD_console_is_active()) +@deprecate("This function is not supported if contexts are being used.") def console_set_window_title(title: str) -> None: """Change the current title bar string. Args: title (AnyStr): A string to change the title bar to. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ lib.TCOD_console_set_window_title(_bytes(title)) @@ -1171,6 +1203,7 @@ def console_credits_render(x: int, y: int, alpha: bool) -> bool: return bool(lib.TCOD_console_credits_render(x, y, alpha)) +@deprecate("This function is not supported if contexts are being used.") def console_flush( console: Optional[tcod.console.Console] = None, *, @@ -1217,6 +1250,9 @@ def console_flush( .. seealso:: :any:`tcod.console_init_root` :any:`tcod.console.recommended_size` + + .. deprecated:: 11.13 + This function is not supported by contexts. """ if snap_to_integer is not None: warnings.warn( @@ -1727,18 +1763,30 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def console_set_fade(fade: int, fadingColor: Tuple[int, int, int]) -> None: + """ + .. deprecated:: 11.13 + This function is not supported by contexts. + """ lib.TCOD_console_set_fade(fade, fadingColor) -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def console_get_fade() -> int: + """ + .. deprecated:: 11.13 + This function is not supported by contexts. + """ return int(lib.TCOD_console_get_fade()) -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def console_get_fading_color() -> Color: + """ + .. deprecated:: 11.13 + This function is not supported by contexts. + """ return Color._new_from_cdata(lib.TCOD_console_get_fading_color()) @@ -1846,6 +1894,7 @@ def console_set_key_color( con.set_key_color(col) +@deprecate("This function is no longer needed.") def console_delete(con: tcod.console.Console) -> None: """Closes the window if `con` is the root console. @@ -4006,6 +4055,9 @@ def struct_get_type(struct, name): # type: ignore # high precision time functions + + +@deprecate("This function is not supported if contexts are being used.") def sys_set_fps(fps: int) -> None: """Set the maximum frame rate. @@ -4013,10 +4065,14 @@ def sys_set_fps(fps: int) -> None: Args: fps (int): A frame rate limit (i.e. 60) + + .. deprecated:: 11.13 + This function is not supported by contexts. """ lib.TCOD_sys_set_fps(fps) +@deprecate("This function is not supported if contexts are being used.") def sys_get_fps() -> int: """Return the current frames per second. @@ -4027,15 +4083,22 @@ def sys_get_fps() -> int: Returns: int: The currently measured frame rate. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ return int(lib.TCOD_sys_get_fps()) +@deprecate("This function is not supported if contexts are being used.") def sys_get_last_frame_length() -> float: """Return the delta time of the last rendered frame in seconds. Returns: float: The delta time of the last rendered frame. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ return float(lib.TCOD_sys_get_last_frame_length()) @@ -4079,25 +4142,31 @@ def sys_elapsed_seconds() -> float: return float(lib.TCOD_sys_elapsed_seconds()) +@deprecate("This function is not supported if contexts are being used.") def sys_set_renderer(renderer: int) -> None: """Change the current rendering mode to renderer. - .. deprecated:: 2.0 - RENDERER_GLSL and RENDERER_OPENGL are not currently available. + .. deprecated:: 11.13 + This function is not supported by contexts. """ _check(lib.TCOD_sys_set_renderer(renderer)) if tcod.console._root_console is not None: tcod.console.Console._get_root() +@deprecate("This function is not supported if contexts are being used.") def sys_get_renderer() -> int: """Return the current rendering mode. + .. deprecated:: 11.13 + This function is not supported by contexts. + Check :any:`Context.renderer_type` instead. """ return int(lib.TCOD_sys_get_renderer()) # easy screenshots +@deprecate("This function is not supported if contexts are being used.") def sys_save_screenshot(name: Optional[str] = None) -> None: """Save a screenshot to a file. @@ -4109,6 +4178,10 @@ def sys_save_screenshot(name: Optional[str] = None) -> None: Args: file Optional[AnyStr]: File path to save screenshot. + + .. deprecated:: 11.13 + This function is not supported by contexts. + Use :any:`Context.save_screenshot` instead. """ lib.TCOD_sys_save_screenshot( _bytes(name) if name is not None else ffi.NULL @@ -4116,7 +4189,7 @@ def sys_save_screenshot(name: Optional[str] = None) -> None: # custom fullscreen resolution -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def sys_force_fullscreen_resolution(width: int, height: int) -> None: """Force a specific resolution in fullscreen. @@ -4130,15 +4203,22 @@ def sys_force_fullscreen_resolution(width: int, height: int) -> None: Args: width (int): The desired resolution width. height (int): The desired resolution height. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ lib.TCOD_sys_force_fullscreen_resolution(width, height) +@deprecate("This function is not supported if contexts are being used.") def sys_get_current_resolution() -> Tuple[int, int]: """Return the current resolution as (width, height) Returns: Tuple[int,int]: The current resolution. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ w = ffi.new("int *") h = ffi.new("int *") @@ -4146,11 +4226,15 @@ def sys_get_current_resolution() -> Tuple[int, int]: return w[0], h[0] +@deprecate("This function is not supported if contexts are being used.") def sys_get_char_size() -> Tuple[int, int]: """Return the current fonts character size as (width, height) Returns: Tuple[int,int]: The current font glyph size in (width, height) + + .. deprecated:: 11.13 + This function is not supported by contexts. """ w = ffi.new("int *") h = ffi.new("int *") @@ -4159,7 +4243,7 @@ def sys_get_char_size() -> Tuple[int, int]: # update font bitmap -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def sys_update_char( asciiCode: int, fontx: int, @@ -4182,11 +4266,15 @@ def sys_update_char( img (Image): An image containing the new character bitmap. x (int): Left pixel of the character in the image. y (int): Top pixel of the character in the image. + + .. deprecated:: 11.13 + This function is not supported by contexts. + Use :any:`Tileset.set_tile` instead to update tiles. """ lib.TCOD_sys_update_char(_int(asciiCode), fontx, fonty, img, x, y) -@pending_deprecate() +@deprecate("This function is not supported if contexts are being used.") def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: """Register a custom randering function with libtcod. @@ -4201,6 +4289,9 @@ def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: Args: callback Callable[[CData], None]: A function which takes a single argument. + + .. deprecated:: 11.13 + This function is not supported by contexts. """ with _PropagateException() as propagate: diff --git a/tcod/tileset.py b/tcod/tileset.py index 90eac5cd..b9c1b18c 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -9,7 +9,7 @@ from tcod.loader import lib, ffi import tcod.console -from tcod._internal import _check, _console, _raise_tcod_error +from tcod._internal import _check, _console, _raise_tcod_error, deprecate class Tileset: @@ -162,20 +162,30 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: ) +@deprecate("Using the default tileset is deprecated.") def get_default() -> Tileset: """Return a reference to the default Tileset. .. versionadded:: 11.10 + + .. deprecated:: 11.13 + The default tileset is deprecated. + With contexts this is no longer needed. """ return Tileset._claim(lib.TCOD_get_default_tileset()) +@deprecate("Using the default tileset is deprecated.") def set_default(tileset: Tileset) -> None: """Set the default tileset. The display will use this new tileset immediately. .. versionadded:: 11.10 + + .. deprecated:: 11.13 + The default tileset is deprecated. + With contexts this is no longer needed. """ lib.TCOD_set_default_tileset(tileset._tileset_p) @@ -200,6 +210,7 @@ def load_truetype_font( return Tileset._claim(cdata) +@deprecate("Accessing the default tileset is deprecated.") def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: """Set the default tileset from a `.ttf` or `.otf` file. @@ -215,6 +226,10 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: automatically. .. versionadded:: 9.2 + + .. deprecated:: 11.13 + This function does not support contexts. + Use :any:`load_truetype_font` instead. """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) From 5fe98a9765392c9233180162217330d3a4146f90 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 19 Jun 2020 03:06:02 -0700 Subject: [PATCH 0365/1101] Deprecate tcod.map.Map. --- CHANGELOG.rst | 2 ++ tcod/map.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0332d28c..859ed8b0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,8 @@ Deprecated modern API. - All functions which handle SDL windows without a context are deprecated. - All functions which modify a globally active tileset are deprecated. + - `tcod.map.Map` is deprecated, NumPy arrays should be passed to functions + directly instead of through this class. 11.13.5 - 2020-06-15 -------------------- diff --git a/tcod/map.py b/tcod/map.py index 2a716efe..5a5e0405 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -61,9 +61,19 @@ class Map(object): [False, False, True]]...) >>> m.fov[3,1] False + + .. deprecated:: 11.13 + You no longer need to use this class to hold data for field-of-view + or pathfinding as those functions can now take NumPy arrays directly. + See :any:`tcod.map.compute_fov` and :any:`tcod.path`. """ def __init__(self, width: int, height: int, order: str = "C"): + warnings.warn( + "This class may perform poorly and is no longer needed.", + DeprecationWarning, + stacklevel=2, + ) self.width = width self.height = height self._order = tcod._internal.verify_order(order) @@ -238,9 +248,21 @@ def compute_fov( RuntimeWarning, stacklevel=2, ) - map_ = Map(transparency.shape[1], transparency.shape[0]) - map_.transparent[...] = transparency + map_buffer = np.empty( + transparency.shape, + dtype=[("transparent", bool), ("walkable", bool), ("fov", bool)], + ) + map_cdata = ffi.new( + "struct TCOD_Map*", + ( + map_buffer.shape[1], + map_buffer.shape[0], + map_buffer.shape[1] * map_buffer.shape[0], + ffi.from_buffer("struct TCOD_MapCell*", map_buffer), + ), + ) + map_buffer["transparent"] = transparency lib.TCOD_map_compute_fov( - map_.map_c, pov[1], pov[0], radius, light_walls, algorithm + map_cdata, pov[1], pov[0], radius, light_walls, algorithm ) - return map_.fov + return map_buffer["fov"] From 0a11f05c06bd904f6e14e81496951bb860890bed Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 19 Jun 2020 07:57:19 -0700 Subject: [PATCH 0366/1101] Minor documentation update. --- docs/tcod/getting-started.rst | 4 ++-- tcod/event.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index e4ce29cb..956e941b 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -8,7 +8,7 @@ Python 3 and python-tcod must be installed, see :ref:`installation`. Fixed-size console ------------------ -The first example is a hello world script which handles font loading, +This example is a hello world script which handles font loading, fixed-sized consoles, window contexts, and event handling. This example requires the `dejavu10x10_gs_tc.png `_ @@ -68,7 +68,7 @@ fallback font will be used. This is useful for prototyping but it's not recommended to release with this font since it can fail to load on some platforms. -The `integer_scaling` parameter to :any:`Context.present` presents the console +The `integer_scaling` parameter to :any:`Context.present` prevents the console from being slightly stretched, since the console will rarely be the prefect size a small border will exist. diff --git a/tcod/event.py b/tcod/event.py index eec07a4b..fe141746 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -9,8 +9,7 @@ Printing any event will tell you its attributes in a human readable format. An events type attribute if omitted is just the classes name with all letters -upper-case. Do not use :any:`isinstance` to tell events apart as that method -won't be forward compatible. +upper-case. As a general guideline, you should use :any:`KeyboardEvent.sym` for command inputs, and :any:`TextInput.text` for name entry fields. From b007ebe9f1b9ea1e22cfdf7eddbf96b9f1bc6aae Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 19 Jun 2020 19:42:25 -0700 Subject: [PATCH 0367/1101] Prepare 11.13.6 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 859ed8b0..52371404 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.13.6 - 2020-06-19 +-------------------- Deprecated - `console_init_root` and `console_set_custom_font` have been replaced by the modern API. From 5aab93897d8e0b34bde7b9749326a2e656ad64ea Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 23 Jun 2020 03:18:39 -0700 Subject: [PATCH 0368/1101] Add tcod.los.bresenham. Deprecate older LOS functions. Update C parser to handle restrict. --- CHANGELOG.rst | 6 +++++ build_libtcod.py | 2 +- docs/index.rst | 1 + docs/tcod/los.rst | 5 ++++ tcod/__init__.py | 2 ++ tcod/libtcodpy.py | 18 +++++++++----- tcod/los.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ tcod/tcod.c | 37 ++++++++++++++++++---------- tcod/tcod.h | 2 +- 9 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 docs/tcod/los.rst create mode 100644 tcod/los.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52371404..8cf3c8a3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Added + - New `tcod.los` module for NumPy-based line-of-sight algorithms. + Includes `tcod.los.bresenham`. + +Deprecated + - `tcod.line_where` and `tcod.line_iter` have been deprecated. 11.13.6 - 2020-06-19 -------------------- diff --git a/build_libtcod.py b/build_libtcod.py index bb95839e..94d4ba12 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -42,7 +42,7 @@ RE_INCLUDE = re.compile(r'#include "([^"]*)"') RE_TAGS = re.compile( r"TCODLIB_C?API|TCOD_PUBLIC|TCOD_NODISCARD|TCOD_DEPRECATED_NOMESSAGE" - r"|(TCOD_DEPRECATED|TCODLIB_FORMAT)\([^)]*\)" + r"|(TCOD_DEPRECATED|TCODLIB_FORMAT)\([^)]*\)|__restrict" ) RE_VAFUNC = re.compile(r".*\(.*va_list.*\);") RE_INLINE = re.compile( diff --git a/docs/index.rst b/docs/index.rst index 24553bc9..cd6e2dd4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,6 +33,7 @@ Contents: tcod/context tcod/event tcod/image + tcod/los tcod/map tcod/noise tcod/path diff --git a/docs/tcod/los.rst b/docs/tcod/los.rst new file mode 100644 index 00000000..1cfbcc67 --- /dev/null +++ b/docs/tcod/los.rst @@ -0,0 +1,5 @@ +tcod.los +======== + +.. automodule:: tcod.los + :members: diff --git a/tcod/__init__.py b/tcod/__init__.py index 6a059121..3673edb8 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -25,6 +25,7 @@ context, event, image, + los, map, noise, path, @@ -47,6 +48,7 @@ "event", "tileset", "image", + "los", "map", "noise", "path", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 133de0ea..821e1de1 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -49,6 +49,7 @@ from tcod.color import Color import tcod.console import tcod.image +import tcod.los import tcod.map import tcod.noise import tcod.path @@ -3260,6 +3261,7 @@ def line( return False +@deprecate("This function has been replaced by tcod.los.bresenham.") def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: """ returns an Iterable @@ -3273,6 +3275,9 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: Returns: Iterable[Tuple[int,int]]: An Iterable of (x,y) points. + + .. deprecated:: 11.14 + This function was replaced by :any:`tcod.los.bresenham`. """ data = ffi.new("TCOD_bresenham_data_t *") lib.TCOD_line_init_mt(xo, yo, xd, yd, data) @@ -3283,6 +3288,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: yield (x[0], y[0]) +@deprecate("This function has been replaced by tcod.los.bresenham.") def line_where( x1: int, y1: int, x2: int, y2: int, inclusive: bool = True ) -> Tuple[np.ndarray, np.ndarray]: @@ -3304,15 +3310,15 @@ def line_where( [0, 0, 0, 0, 0]]...) .. versionadded:: 4.6 + + .. deprecated:: 11.14 + This function was replaced by :any:`tcod.los.bresenham`. """ - length = max(abs(x1 - x2), abs(y1 - y2)) + 1 - array = np.ndarray((2, length), dtype=np.intc) - x = ffi.from_buffer("int*", array[0]) - y = ffi.from_buffer("int*", array[1]) - lib.LineWhere(x1, y1, x2, y2, x, y) + array = tcod.los.bresenham((x1, y1), (x2, y2)).T if not inclusive: array = array[:, 1:] - return tuple(array) # type: ignore + i, j = array + return i, j @deprecate("Call tcod.map.Map(width, height) instead.") diff --git a/tcod/los.py b/tcod/los.py new file mode 100644 index 00000000..011d416c --- /dev/null +++ b/tcod/los.py @@ -0,0 +1,61 @@ +"""This modules holds functions for NumPy-based line of sight algorithms. +""" +from typing import Tuple + +import numpy as np + +from tcod.loader import ffi, lib + + +def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> np.ndarray: + """Return a thin Bresenham line as a NumPy array of shape (length, 2). + + `start` and `end` are the endpoints of the line. + The result always includes both endpoints, and will always contain at + least one index. + + You might want to use the results as is, convert them into a list with + :any:`numpy.ndarray.tolist` or transpose them and use that to index + another 2D array. + + Example:: + + >>> import tcod + >>> tcod.los.bresenham((3, 5),(7, 7)).tolist() # Convert into list. + [[3, 5], [4, 5], [5, 6], [6, 6], [7, 7]] + >>> tcod.los.bresenham((0, 0), (0, 0)) + array([[0, 0]]...) + >>> tcod.los.bresenham((0, 0), (4, 4))[1:-1] # Clip both endpoints. + array([[1, 1], + [2, 2], + [3, 3]]...) + + >>> array = np.zeros((5, 5), dtype=np.int8) + >>> array + array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], dtype=int8) + >>> tcod.los.bresenham((0, 0), (3, 4)).T # Transposed results. + array([[0, 1, 1, 2, 3], + [0, 1, 2, 3, 4]]...) + >>> indexes_ij = tuple(tcod.los.bresenham((0, 0), (3, 4)).T) + >>> array[indexes_ij] = np.arange(len(indexes_ij[0])) + >>> array + array([[0, 0, 0, 0, 0], + [0, 1, 2, 0, 0], + [0, 0, 0, 3, 0], + [0, 0, 0, 0, 4], + [0, 0, 0, 0, 0]], dtype=int8) + >>> array[indexes_ij] + array([0, 1, 2, 3, 4], dtype=int8) + + .. versionadded:: 11.14 + """ + x1, y1 = start + x2, y2 = end + length = lib.bresenham(x1, y1, x2, y2, 0, ffi.NULL) + array = np.ndarray((length, 2), dtype=np.intc) + lib.bresenham(x1, y1, x2, y2, length, ffi.from_buffer("int*", array)) + return array diff --git a/tcod/tcod.c b/tcod/tcod.c index db1382aa..28847fc0 100644 --- a/tcod/tcod.c +++ b/tcod/tcod.c @@ -1,23 +1,36 @@ #include "tcod.h" +#include + #include "../libtcod/src/libtcod/bresenham.h" #include "../libtcod/src/libtcod/console_drawing.h" #include "../libtcod/src/libtcod/console_printing.h" +#include "../libtcod/src/libtcod/error.h" +#include "../libtcod/src/libtcod/utility.h" /** - * Write a Bresenham line to the `x_out` and `y_out` arrays. - * - * `x_out` and `y_out` must be large enough to contain the entire line that - * this will output. Typically `max(abs(x1 - x2), abs(y1 - y2)) + 1`. - * - * This function includes both endpoints. + Write a Bresenham line to the `out[n * 2]` array. + + The result includes both endpoints. + + The length of the array is returned, when `out` is given `n` must be equal + or greater then the length. */ -int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out) { +int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out) { + // Bresenham length is Chebyshev distance. + int length = MAX(abs(x1 - x2), abs(y1 - y2)) + 1; + if (!out) { return length; } + if (n < length) { + return TCOD_set_errorv("Bresenham output length mismatched."); + } TCOD_bresenham_data_t bresenham; - *x_out = x1; - *y_out = y1; - if (x1 == x2 && y1 == y2) { return 0; } + out[0] = x1; + out[1] = y1; + out += 2; + if (x1 == x2 && y1 == y2) { return length; } TCOD_line_init_mt(x1, y1, x2, y2, &bresenham); - while (!TCOD_line_step_mt(++x_out, ++y_out, &bresenham)) {} - return 0; + while (!TCOD_line_step_mt(&out[0], &out[1], &bresenham)) { + out += 2; + } + return length; } diff --git a/tcod/tcod.h b/tcod/tcod.h index 062642c1..e79c465a 100644 --- a/tcod/tcod.h +++ b/tcod/tcod.h @@ -8,7 +8,7 @@ #ifdef __cplusplus extern "C" { #endif -int LineWhere(int x1, int y1, int x2, int y2, int *x_out, int *y_out); +int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out); #ifdef __cplusplus } // extern "C" #endif From fc0082d96c3aa480de869258289b702118e3cd68 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 23 Jun 2020 03:24:43 -0700 Subject: [PATCH 0369/1101] Handle MyPy warnings. --- tcod/console.py | 4 ++-- tcod/libtcodpy.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 75893db0..ba583b70 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -366,11 +366,11 @@ def clear( Added the `ch`, `fg`, and `bg` parameters. Non-white-on-black default values are deprecated. """ - if fg is ...: + if fg is ...: # type: ignore fg = self.default_fg if fg != (255, 255, 255): self.__clear_warning("fg", fg) - if bg is ...: + if bg is ...: # type: ignore bg = self.default_bg if bg != (0, 0, 0): self.__clear_warning("bg", bg) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 821e1de1..5ea00190 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1006,7 +1006,7 @@ def console_set_custom_font( Load fonts using :any:`tcod.tileset.load_tileheet` instead. See :ref:`getting-started` for more info. """ - if not os.path.exists(fontFile): + if not os.path.exists(_unicode(fontFile)): raise RuntimeError( "File not found:\n\t%s" % (str(os.path.realpath(fontFile)),) ) From 762d4eddbe8301ecc3ce0e8ec7cae7b1c9095cf3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 23 Jun 2020 03:54:07 -0700 Subject: [PATCH 0370/1101] Pin NumPy version for PyPy build job. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7d15def4..0d69456e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ jobs: env: MB_PYTHON_VERSION=3.7.1 - os: osx language: generic - env: PYPY3_VERSION=pypy3.6-v7.3.0 + env: PYPY3_VERSION=pypy3.6-v7.3.0 CUSTOM_REQUIREMENTS="numpy==1.18" allow_failures: - python: nightly - python: pypy3.5-7.0 @@ -42,6 +42,7 @@ before_install: - python get-pip.py - pip install --upgrade setuptools - pip install --pre --upgrade wheel +- if [[ -n "$CUSTOM_REQUIREMENTS" ]]; then pip install $CUSTOM_REQUIREMENTS; fi - pip install --requirement requirements.txt install: - if [[ -z "$TRAVIS_TAG" ]]; then export BUILD_FLAGS="-g"; fi From 56ddb8957fceae0bd87a09cdea35e5803fc2577a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 23 Jun 2020 04:30:39 -0700 Subject: [PATCH 0371/1101] Prepare 11.14.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8cf3c8a3..a256476c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.14.0 - 2020-06-23 +-------------------- Added - New `tcod.los` module for NumPy-based line-of-sight algorithms. Includes `tcod.los.bresenham`. From 502bc787bdedaf29e992d38c2acaf5147931337a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Tue, 23 Jun 2020 07:18:27 -0700 Subject: [PATCH 0372/1101] Update see also section for FOV to include np.where. And describes what these are, since they're important. --- tcod/map.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tcod/map.py b/tcod/map.py index 5a5e0405..0ce3c4dd 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -222,9 +222,11 @@ def compute_fov( >>> explored |= visible # Keep track of an explored area. .. seealso:: - :any:`numpy.nonzero` - :any:`numpy.choose` - :any:`numpy.select` + :any:`numpy.where`: For selecting between two arrays using a boolean + array, like the one returned by this function. + + :any:`numpy.select`: Select between arrays based on multiple + conditions. """ transparency = np.asarray(transparency) if len(transparency.shape) != 2: From 3339f04539142fd3e7249f78c8d822bf06fe6dce Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 24 Jun 2020 14:36:50 -0700 Subject: [PATCH 0373/1101] Update the intersphinx mapping for NumPy. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index dbbcda12..9ce3163b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -368,7 +368,7 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'https://docs.python.org/3/': None, - 'https://docs.scipy.org/doc/numpy/': None, + 'https://numpy.org/doc/stable/': None, } os.environ['READTHEDOCS'] = 'True' From 1ef9d0ea16fbdddff6f0900fdef8760953bede4a Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Wed, 24 Jun 2020 14:37:45 -0700 Subject: [PATCH 0374/1101] Update all of the documentation headers. Also some other doc updates, mostly to the glossary. --- docs/glossary.rst | 18 +++++++++++++----- docs/libtcodpy.rst | 14 ++++++++++++-- docs/tcod/bsp.rst | 4 ++-- docs/tcod/console.rst | 4 ++-- docs/tcod/context.rst | 4 ++-- docs/tcod/event.rst | 4 ++-- docs/tcod/image.rst | 4 ++-- docs/tcod/los.rst | 4 ++-- docs/tcod/map.rst | 4 ++-- docs/tcod/noise.rst | 4 ++-- docs/tcod/path.rst | 4 ++-- docs/tcod/random.rst | 4 ++-- docs/tcod/tileset.rst | 4 ++-- tcod/image.py | 9 +++++++++ tcod/noise.py | 9 ++++----- tcod/path.py | 2 +- tcod/tileset.py | 10 ++++++++++ tdl/__init__.py | 4 +++- 18 files changed, 74 insertions(+), 36 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 3e9c2747..3faaa446 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -14,7 +14,9 @@ Glossary This is the `cffi` implementation of libtcodpy, the original was made using `ctypes` which was more difficult to maintain. - `libtcod-cffi` is now part of :term:`python-tcod`. + `libtcod-cffi` has since been part of :term:`python-tcod` providing + all of the :term:`libtcodpy` API until the newer features could be + implemented. python-tcod `python-tcod` is a superset of the :term:`libtcodpy` API. The major @@ -22,14 +24,19 @@ Glossary memory management, pickle-able objects, and `numpy` array attributes in most objects. - The `numpy` attributes in particular can be used to dramatically speed - up the performance of your program compared to using :term:`libtcodpy`. + The `numpy` functions in particular can be used to dramatically speed + up the performance of a program compared to using :term:`libtcodpy`. python-tdl `tdl` is a high-level wrapper over :term:`libtcodpy` although it now uses :term:`python-tcod`, it doesn't do anything that you couldn't do yourself with just :term:`libtcodpy` and Python. + It included a lot of core functions written in Python that most + definitely shouldn't have been. `tdl` was very to use, but the cost + was severe performance issues throughout the entire module. + This left it impractical for any real use as a roguelike library. + Currently no new features are planned for `tdl`, instead new features are added to `libtcod` itself and then ported to :term:`python-tcod`. @@ -38,8 +45,9 @@ Glossary libtcodpy `libtcodpy` is more or less a direct port of `libtcod`'s C API to - Python. This caused a handful of issues including instances needing - to be freed manually or a memory leak will occur, and some functions + Python. + This caused a handful of issues including instances needing to be + freed manually or else a memory leak would occur, and many functions performing badly in Python due to the need to call them frequently. These issues are fixed in :term:`python-tcod` which implements the full diff --git a/docs/libtcodpy.rst b/docs/libtcodpy.rst index 71ee904d..1783b5e1 100644 --- a/docs/libtcodpy.rst +++ b/docs/libtcodpy.rst @@ -1,5 +1,15 @@ -libtcodpy -========= +libtcodpy - Old API Functions +============================= + +This is all the functions included since the start of the Python port. +This collection is often called :term:`libtcodpy`, the name of the original +Python port. These functions are reproduced by python-tcod in their entirely. + +**A large majority of these functions are deprecated and will be removed in +the future. +In general this entire section should be avoided whenever possible.** +See :ref:`getting-started` for how to make a new python-tcod project with its +modern API. bsp --- diff --git a/docs/tcod/bsp.rst b/docs/tcod/bsp.rst index 0de72ff1..b0859baa 100644 --- a/docs/tcod/bsp.rst +++ b/docs/tcod/bsp.rst @@ -1,5 +1,5 @@ -tcod.bsp -========= +tcod.bsp - Binary Space Partitioning +==================================== .. automodule:: tcod.bsp :members: diff --git a/docs/tcod/console.rst b/docs/tcod/console.rst index f2217687..6078f274 100644 --- a/docs/tcod/console.rst +++ b/docs/tcod/console.rst @@ -1,5 +1,5 @@ -tcod.console -============ +tcod.console - Tile Drawing/Printing +==================================== .. automodule:: tcod.console :members: diff --git a/docs/tcod/context.rst b/docs/tcod/context.rst index fbd3b2f5..46ed6178 100644 --- a/docs/tcod/context.rst +++ b/docs/tcod/context.rst @@ -1,5 +1,5 @@ -tcod.context -============ +tcod.context - Window Management +================================ .. automodule:: tcod.context :members: diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 20297694..0a2b977f 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -1,5 +1,5 @@ -tcod.event -========== +tcod.event - SDL2 Event Handling +================================ .. automodule:: tcod.event :members: diff --git a/docs/tcod/image.rst b/docs/tcod/image.rst index b902d9ff..58706d3f 100644 --- a/docs/tcod/image.rst +++ b/docs/tcod/image.rst @@ -1,5 +1,5 @@ -tcod.image -========== +tcod.image - Image Handling +=========================== .. automodule:: tcod.image :members: diff --git a/docs/tcod/los.rst b/docs/tcod/los.rst index 1cfbcc67..73a501a1 100644 --- a/docs/tcod/los.rst +++ b/docs/tcod/los.rst @@ -1,5 +1,5 @@ -tcod.los -======== +tcod.los - Line of Sight +======================== .. automodule:: tcod.los :members: diff --git a/docs/tcod/map.rst b/docs/tcod/map.rst index aa2e0662..08efaf86 100644 --- a/docs/tcod/map.rst +++ b/docs/tcod/map.rst @@ -1,5 +1,5 @@ -tcod.map -======== +tcod.map - Field of View +======================== .. automodule:: tcod.map :members: diff --git a/docs/tcod/noise.rst b/docs/tcod/noise.rst index 55c9e707..36f4ddb6 100644 --- a/docs/tcod/noise.rst +++ b/docs/tcod/noise.rst @@ -1,5 +1,5 @@ -tcod.noise -========== +tcod.noise - Noise Map Generators +================================= .. automodule:: tcod.noise :members: diff --git a/docs/tcod/path.rst b/docs/tcod/path.rst index e3c750e0..75c1dcb1 100644 --- a/docs/tcod/path.rst +++ b/docs/tcod/path.rst @@ -1,5 +1,5 @@ -tcod.path -========= +tcod.path - Pathfinding +======================= .. automodule:: tcod.path :members: diff --git a/docs/tcod/random.rst b/docs/tcod/random.rst index 47fdfb3e..3e4d08b0 100644 --- a/docs/tcod/random.rst +++ b/docs/tcod/random.rst @@ -1,5 +1,5 @@ -tcod.random -=========== +tcod.random - Random Number Generators +====================================== .. automodule:: tcod.random :members: diff --git a/docs/tcod/tileset.rst b/docs/tcod/tileset.rst index f05de8c6..47d0b0e8 100644 --- a/docs/tcod/tileset.rst +++ b/docs/tcod/tileset.rst @@ -1,5 +1,5 @@ -tcod.tileset -============ +tcod.tileset - Font Loading Functions +===================================== .. automodule:: tcod.tileset :members: diff --git a/tcod/image.py b/tcod/image.py index 509d97cf..4b6e91e9 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,3 +1,12 @@ +"""Functionality for handling images. + +**Python-tcod is unable to render pixels to the screen directly.** +If your image can't be represented as tiles then you'll need to use +`an alternative library for graphics rendering +`_. +""" + + from typing import Any, Dict, Tuple import numpy as np diff --git a/tcod/noise.py b/tcod/noise.py index e254389a..c95130bd 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -1,8 +1,7 @@ -""" -The :any:`Noise.sample_mgrid` and :any:`Noise.sample_ogrid` methods are -multi-threaded operations when the Python runtime supports OpenMP. -Even when single threaded these methods will perform much better than -multiple calls to :any:`Noise.get_point`. +"""Noise map generators are provided by this module. + +The :any:`Noise.sample_mgrid` and :any:`Noise.sample_ogrid` methods perform +much better than multiple calls to :any:`Noise.get_point`. Example:: diff --git a/tcod/path.py b/tcod/path.py index cf8aadee..31999592 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1,4 +1,4 @@ -""" +"""This module provides a fast configurable pathfinding implementation. Example:: diff --git a/tcod/tileset.py b/tcod/tileset.py index b9c1b18c..094aca31 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -1,4 +1,14 @@ """Tileset and font related functions. + +Tilesets can be loaded as a whole from tile-sheets or True-Type fonts, or they +can be put together from multiple tile images by loading them separately +using :any:`Tileset.set_tile`. + +A major restriction with libtcod is that all tiles must be the same size and +tiles can't overlap when rendered. For sprite-based rendering it can be +useful to use `an alternative library for graphics rendering +`_ while continuing to use +python-tcod's pathfinding and field-of-view algorithms. """ import os diff --git a/tdl/__init__.py b/tdl/__init__.py index 30c7d730..d64e180c 100755 --- a/tdl/__init__.py +++ b/tdl/__init__.py @@ -1,6 +1,8 @@ """ .. deprecated:: 8.4 - This module has been deprecated. + The :term:`python-tdl` module has been a total disaster and now exists + mainly as a historical curiosity and as a stepping stone for what would + eventually become :term:`python-tcod`. Getting Started =============== From 8bb68e4fa2e529d5f1baf5f51149a9f0459f07f2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 28 Jun 2020 07:32:23 -0700 Subject: [PATCH 0375/1101] Add BasicGraph and graph order. This should make it more realistic to deprecate the old pathfinders. --- CHANGELOG.rst | 5 + tcod/path.c | 6 +- tcod/path.py | 293 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 232 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a256476c..975b4403 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Added + - `tcod.path.BasicGraph` for pathfinding on simple 2D arrays. + +Changed + - `tcod.path.CustomGraph` now accepts an `order` parameter. 11.14.0 - 2020-06-23 -------------------- diff --git a/tcod/path.c b/tcod/path.c index c1ff4fa5..373f8ce2 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -544,7 +544,7 @@ int update_frontier_heuristic( TCOD_minheap_heapify(&frontier->heap); return 0; } -static int update_frontier_from_distance_terator( +static int update_frontier_from_distance_iterator( struct TCOD_Frontier* frontier, const struct NArray* dist_map, int dimension, int* index) @@ -556,7 +556,7 @@ static int update_frontier_from_distance_terator( } for (int i = 0; i < dist_map->shape[dimension]; ) { index[dimension] = i; - int err = update_frontier_from_distance_terator( + int err = update_frontier_from_distance_iterator( frontier, dist_map, dimension + 1, index); if (err) { return err; } } @@ -573,7 +573,7 @@ int rebuild_frontier_from_distance( } TCOD_frontier_clear(frontier); int index[TCOD_PATHFINDER_MAX_DIMENSIONS]; - return update_frontier_from_distance_terator(frontier, dist_map, 0, index); + return update_frontier_from_distance_iterator(frontier, dist_map, 0, index); } int frontier_has_index( const struct TCOD_Frontier* frontier, diff --git a/tcod/path.py b/tcod/path.py index 31999592..092f2033 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1,40 +1,15 @@ """This module provides a fast configurable pathfinding implementation. -Example:: - - >>> import numpy as np - >>> import tcod - >>> dungeon = np.array( - ... [ - ... [1, 0, 1, 1, 1], - ... [1, 0, 1, 0, 1], - ... [1, 1, 1, 0, 1], - ... ], - ... dtype=np.int8, - ... ) - ... - - # Create a pathfinder from a numpy array. - # This is the recommended way to use the tcod.path module. - >>> astar = tcod.path.AStar(dungeon) - >>> print(astar.get_path(0, 0, 2, 4)) - [(1, 0), (2, 1), (1, 2), (0, 3), (1, 4), (2, 4)] - >>> astar.cost[0, 1] = 1 # You can access the map array via this attribute. - >>> print(astar.get_path(0, 0, 2, 4)) - [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4)] - - # Create a pathfinder from an edge_cost function. - # Calling Python functions from C is known to be very slow. - >>> def edge_cost(my_x, my_y, dest_x, dest_y): - ... return dungeon[dest_x, dest_y] - ... - >>> dijkstra = tcod.path.Dijkstra( - ... tcod.path.EdgeCostCallback(edge_cost, dungeon.shape), - ... ) - ... - >>> dijkstra.set_goal(0, 0) - >>> print(dijkstra.get_path(2, 4)) - [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4)] +To get started create a 2D NumPy array of integers where a value of zero is a +blocked node and any higher value is the cost to move to that node. +You then pass this array to :any:`BasicGraph`, and then pass that graph to +:any:`Pathfinder`. + +Once you have a :any:`Pathfinder` you call :any:`Pathfinder.add_root` to set +the root node. You can then get a path towards or away from the root with +:any:`Pathfinder.path_from` and :any:`Pathfinder.path_to` respectively. + +:any:`BasicGraph` includes a code example of the above process. .. versionchanged:: 5.0 All path-finding functions now respect the NumPy array shape (if a NumPy @@ -42,7 +17,7 @@ """ import functools import itertools -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np @@ -627,9 +602,20 @@ def _as_hashable(obj: Optional[np.ndarray]) -> Optional[Any]: class CustomGraph: """A customizable graph defining how a pathfinder traverses the world. + If you only need to path over a 2D array with typical edge rules then you + should use :any:`BasicGraph`. + This is an advanced interface for defining custom edge rules which would + allow things such as 3D movement. + The graph is created with a `shape` defining the size and number of dimensions of the graph. The `shape` can only be 4 dimensions or lower. + `order` determines what style of indexing the interface expects. + This is inherited by the pathfinder and will affect the `ij/xy` indexing + order of all methods in the graph and pathfinder objects. + The default order of `"C"` is for `ij` indexing. + The `order` can be set to `"F"` for `xy` indexing. + After this graph is created you'll need to add edges which define the rules of the pathfinder. These rules usually define movement in the cardinal and diagonal directions, but can also include stairway type edges. @@ -674,12 +660,19 @@ class CustomGraph: [3, 3]]...) .. versionadded:: 11.13 + + .. versionchanged:: 11.15 + Added the `order` parameter. """ - def __init__(self, shape: Tuple[int, ...]): - self._shape = tuple(shape) + def __init__(self, shape: Tuple[int, ...], *, order: str = "C"): + self._shape = self._shape_c = tuple(shape) self._ndim = len(self._shape) - assert 0 < self._ndim <= 4 + self._order = order + if self._order == "F": + self._shape_c = self._shape_c[::-1] + if not 0 < self._ndim <= 4: + raise TypeError("Graph dimensions must be 1 <= n <= 4.") self._graph = {} # type: Dict[Tuple[Any, ...], Dict[str, Any]] self._edge_rules_keep_alive = [] # type: List[Any] self._edge_rules_p = None # type: Any @@ -718,14 +711,17 @@ def add_edge( If the node in `condition` is zero then the edge will be skipped. This is useful to mark portals or stairs for some edges. + The expected indexing for `edge_dir`, `cost`, and `condition` depend + on the graphs `order`. + Example:: >>> import numpy as np >>> import tcod >>> graph3d = tcod.path.CustomGraph((2, 5, 5)) - >>> cost = np.ones((3, 5, 5), dtype=np.int8) - >>> up_stairs = np.zeros((3, 5, 5), dtype=np.int8) - >>> down_stairs = np.zeros((3, 5, 5), dtype=np.int8) + >>> cost = np.ones((2, 5, 5), dtype=np.int8) + >>> up_stairs = np.zeros((2, 5, 5), dtype=np.int8) + >>> down_stairs = np.zeros((2, 5, 5), dtype=np.int8) >>> up_stairs[0, 0, 4] = 1 >>> down_stairs[1, 0, 4] = 1 >>> CARDINAL = [[0, 1, 0], [1, 0, 1], [0, 1, 0]] @@ -753,12 +749,34 @@ def add_edge( """ # noqa: E501 self._edge_rules_p = None edge_dir = tuple(edge_dir) - assert len(edge_dir) == self._ndim - assert edge_cost > 0, (edge_dir, edge_cost) cost = np.asarray(cost) - assert cost.ndim == self.ndim + if len(edge_dir) != self._ndim: + raise TypeError( + "edge_dir must have exactly %i items, got %r" + % (self._ndim, edge_dir) + ) + if edge_cost <= 0: + raise ValueError( + "edge_cost must be greater than zero, got %r" % (edge_cost,) + ) + if cost.shape != self._shape: + raise TypeError( + "cost array must be shape %r, got %r" + % (self._shape, cost.shape) + ) if condition is not None: condition = np.asarray(condition) + if condition.shape != self._shape: + raise TypeError( + "condition array must be shape %r, got %r" + % (self._shape, condition.shape) + ) + if self._order == "F": + # Inputs need to be converted to C. + edge_dir = edge_dir[::-1] + cost = cost.T + if condition is not None: + condition = condition.T key = (_as_hashable(cost), _as_hashable(condition)) try: rule = self._graph[key] @@ -796,6 +814,10 @@ def add_edges( If `condition` is the same array as `cost` then the pathfinder will not move into open area from a non-open ones. + The expected indexing for `edge_map`, `cost`, and `condition` depend + on the graphs `order`. You may need to transpose the examples below + if you're using `xy` indexing. + Example:: # 2D edge maps: @@ -871,10 +893,14 @@ def add_edges( if edge_map.ndim < self._ndim: edge_map = edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)] if edge_map.ndim != self._ndim: - raise ValueError( + raise TypeError( "edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim) ) + if self._order == "F": + # edge_map needs to be converted into C. + # The other parameters are converted by the add_edge method. + edge_map = edge_map.T edge_center = tuple(i // 2 for i in edge_map.shape) edge_map[edge_center] = 0 edge_map[edge_map < 0] = 0 @@ -990,23 +1016,112 @@ def _resolve(self, pathfinder: "Pathfinder") -> None: ) +class BasicGraph: + """A simple 2D graph implementation. + + `cost` is a NumPy array where each node has the cost for movement into + that node. Zero or negative values are used to mark blocked areas. + A reference of this array is used. Any changes to the array will be + reflected in the graph. + + `cardinal` and `diagonal` are the cost to move along the edges for those + directions. The total cost to move from one node to another is the `cost` + array value multiplied by the edge cost. + A value of zero will block that direction. + + `greed` is used to define the heuristic. + To get the fastest accurate heuristic `greed` should be the lowest + non-zero value on the `cost` array. + Higher values may be used for an inaccurate but faster heuristic. + + Example:: + + >>> import numpy as np + >>> import tcod + >>> cost = np.ones((5, 10), dtype=np.int8, order="F") + >>> graph = tcod.path.BasicGraph(cost=cost, cardinal=2, diagonal=3) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((2, 4)) + >>> pf.path_to((3, 7)).tolist() + [[2, 4], [2, 5], [2, 6], [3, 7]] + + .. versionadded:: 11.15 + """ + + def __init__( + self, *, cost: np.ndarray, cardinal: int, diagonal: int, greed: int = 1 + ): + cost = np.asarray(cost) + if cost.ndim != 2: + raise TypeError( + "The cost array must e 2 dimensional, array of shape %r given." + % (cost.shape,) + ) + if greed <= 0: + raise ValueError( + "Greed must be greater than zero, got %r" % (greed,) + ) + edge_map = ( + (diagonal, cardinal, diagonal), + (cardinal, 0, cardinal), + (diagonal, cardinal, diagonal), + ) + self._order = "C" if cost.strides[0] > cost.strides[1] else "F" + self._subgraph = CustomGraph(cost.shape, order=self._order) + self._ndim = 2 + self._shape = self._subgraph._shape[0], self._subgraph._shape[1] + self._shape_c = self._subgraph._shape_c + self._subgraph.add_edges(edge_map=edge_map, cost=cost) + self.set_heuristic( + cardinal=cardinal * greed, diagonal=diagonal * greed + ) + + @property + def ndim(self) -> int: + return 2 + + @property + def shape(self) -> Tuple[int, int]: + return self._shape + + @property + def _heuristic(self) -> Optional[Tuple[int, int, int, int]]: + return self._subgraph._heuristic + + def set_heuristic(self, *, cardinal: int, diagonal: int) -> None: + """Change the heuristic for this graph. + + When created a :any:`BasicGraph` will automatically have a heuristic. + So calling this method is often unnecessary. + + `cardinal` and `diagonal` are weights for the heuristic. + Higher values are more greedy. + The default values are set to ``cardinal * greed`` and + ``diagonal * greed`` when the :any:`BasicGraph` is created. + """ + self._subgraph.set_heuristic(cardinal=cardinal, diagonal=diagonal) + + def _resolve(self, pathfinder: "Pathfinder") -> None: + self._subgraph._resolve(pathfinder) + + class Pathfinder: """A generic modular pathfinder. How the pathfinder functions depends on the graph provided. see - :any:`CustomGraph` for how to set these up. + :any:`BasicGraph` for how to set one up. .. versionadded:: 11.13 """ - def __init__(self, graph: CustomGraph): + def __init__(self, graph: Union[CustomGraph, BasicGraph]): self._graph = graph + self._order = graph._order self._frontier_p = ffi.gc( lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete ) - self._distance = maxarray(self._graph._shape) - self._travel = _world_array(self._graph._shape) - assert self._travel.flags["C_CONTIGUOUS"] + self._distance = maxarray(self._graph._shape_c) + self._travel = _world_array(self._graph._shape_c) self._distance_p = _export(self._distance) self._travel_p = _export(self._travel) self._heuristic = ( @@ -1018,7 +1133,7 @@ def __init__(self, graph: CustomGraph): def distance(self) -> np.ndarray: """The distance values of the pathfinder. - This array is stored in row-major "C" order. + The array returned from this property maintains the graphs `order`. Unreachable or unresolved points will be at their maximum values. You can use :any:`numpy.iinfo` if you need to check for these. @@ -1032,14 +1147,14 @@ def distance(self) -> np.ndarray: You may edit this array manually, but the pathfinder won't know of your changes until :any:`rebuild_frontier` is called. """ - return self._distance + return self._distance.T if self._order == "F" else self._distance @property def traversal(self) -> np.ndarray: """An array used to generate paths from any point to the nearest root. - This array is stored in row-major "C" order. It has an extra - dimension which includes the index of the next path. + The array returned from this property maintains the graphs `order`. + It has an extra dimension which includes the index of the next path. Example:: @@ -1057,6 +1172,9 @@ def traversal(self) -> np.ndarray: As the pathfinder is resolved this array is filled """ + if self._order == "F": + axes = range(self._travel.ndim) + return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] return self._travel def clear(self) -> None: @@ -1066,33 +1184,38 @@ def clear(self) -> None: value. """ self._distance[...] = np.iinfo(self._distance.dtype).max - self._travel = _world_array(self._graph._shape) + self._travel = _world_array(self._graph._shape_c) lib.TCOD_frontier_clear(self._frontier_p) def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: """Add a root node and insert it into the pathfinder frontier. `index` is the root point to insert. The length of `index` must match - the dimensions of the graph. `index` must also be in 'ij' order. + the dimensions of the graph. `value` is the distance to use for this root. Zero is typical, but if multiple roots are added they can be given different weights. """ - index_ = tuple(index) - assert len(index_) == self._distance.ndim - self._distance[index_] = value + index = tuple(index) # Check for bad input. + if self._order == "F": # Convert to ij indexing order. + index = index[::-1] + if len(index) != self._distance.ndim: + raise TypeError( + "Index must be %i items, got %r" % (self._distance.ndim, index) + ) + self._distance[index] = value self._update_heuristic(None) - lib.TCOD_frontier_push(self._frontier_p, index_, value, value) + lib.TCOD_frontier_push(self._frontier_p, index, value, value) - def _update_heuristic(self, goal: Optional[Tuple[int, ...]]) -> bool: + def _update_heuristic(self, goal_ij: Optional[Tuple[int, ...]]) -> bool: """Update the active heuristic. Return True if the heuristic changed. """ - if goal is None: + if goal_ij is None: heuristic = None elif self._graph._heuristic is None: - heuristic = (0, 0, 0, 0, goal) + heuristic = (0, 0, 0, 0, goal_ij) else: - heuristic = (*self._graph._heuristic, goal) + heuristic = (*self._graph._heuristic, goal_ij) if self._heuristic == heuristic: return False # Frontier does not need updating. self._heuristic = heuristic @@ -1121,9 +1244,30 @@ def rebuild_frontier(self) -> None: ) def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: - """Manually run the pathfinder algorithm.""" + """Manually run the pathfinder algorithm. + + The :any:`path_from` and :any:`path_to` methods will automatically + call this method on demand. + + If `goal` is `None` then this will attempt to complete the entire + :any:`distance` and :any:`traversal` arrays without a heuristic. + This is similar to Dijkstra. + + If `goal` is given an index then it will attempt to resolve the + :any:`distance` and :any:`traversal` arrays only up to the `goal`. + If the graph has set a heuristic then it will be used and this call + will be similar to `A*`. + """ if goal is not None: - assert len(goal) == self._distance.ndim + goal = tuple(goal) # Check for bad input. + if len(goal) != self._distance.ndim: + raise TypeError( + "Goal must be %i items, got %r" + % (self._distance.ndim, goal) + ) + if self._order == "F": + # Goal is now ij indexed for the rest of this function. + goal = goal[::-1] if self._distance[goal] != np.iinfo(self._distance.dtype).max: if not lib.frontier_has_index(self._frontier_p, goal): return @@ -1133,6 +1277,10 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: def path_from(self, index: Tuple[int, ...]) -> np.ndarray: """Return the shortest path from `index` to the nearest root. + The returned array is of shape `(length, ndim)` where `length` is the + total inclusive length of the path and `ndim` is the dimensions of the + pathfinder defined by the graph. + The return value is inclusive, including both the starting and ending points on the path. If the root point is unreachable or `index` is already at a root then `index` will be the only point returned. @@ -1143,8 +1291,14 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: A common usage is to slice off the starting point and convert the array into a list. """ + index = tuple(index) # Check for bad input. + if len(index) != self._graph._ndim: + raise TypeError( + "Index must be %i items, got %r" % (self._distance.ndim, index) + ) self.resolve(index) - assert len(index) == self._graph._ndim + if self._order == "F": # Convert to ij indexing order. + index = index[::-1] length = _check( lib.get_travel_path( self._graph._ndim, self._travel_p, index, ffi.NULL, @@ -1159,11 +1313,12 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: ffi.from_buffer("int*", path), ) ) - return path + return path[:, ::-1] if self._order == "F" else path def path_to(self, index: Tuple[int, ...]) -> np.ndarray: """Return the shortest path from the nearest root to `index`. + See :any:`path_from`. This is an alias for ``path_from(...)[::-1]``. """ return self.path_from(index)[::-1] From 0dcc84ac03a76a2b9656fee3a9e52b77fc69dabb Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 28 Jun 2020 09:57:27 -0700 Subject: [PATCH 0376/1101] Remove docs on ij indexing. --- tcod/path.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 092f2033..2f47aed0 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -625,10 +625,6 @@ class CustomGraph: After all edge rules are added the graph can be used to make one or more :any:`Pathfinder` instances. - Because the arrays used are in row-major order the indexes used in the - examples be reversed from what you expect. - A 2D edge or index is ``(y, x)`` and in 3D it is ``(z, y, x)``. - Example:: >>> import numpy as np From 42002d587352e142dc0911c34e6e60bbd1d9b322 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 28 Jun 2020 10:16:55 -0700 Subject: [PATCH 0377/1101] Add Clang format file for C sources. --- tcod/.clang-format | 13 +++ tcod/cdef.h | 22 ++-- tcod/cffi.h | 3 +- tcod/noise.c | 42 ++++---- tcod/noise.h | 17 +-- tcod/path.c | 231 ++++++++++++++++------------------------ tcod/path.h | 32 +++--- tcod/tcod.c | 4 +- tcod/tcod.h | 2 +- tcod/tdl.c | 260 ++++++++++++++++++++++----------------------- tcod/tdl.h | 39 ++++--- 11 files changed, 319 insertions(+), 346 deletions(-) create mode 100644 tcod/.clang-format diff --git a/tcod/.clang-format b/tcod/.clang-format new file mode 100644 index 00000000..322668fd --- /dev/null +++ b/tcod/.clang-format @@ -0,0 +1,13 @@ +BasedOnStyle: Google +IndentWidth: 2 +Language: Cpp + +DerivePointerAlignment: false +PointerAlignment: Left + +ColumnLimit: 79 +AlignAfterOpenBracket: AlwaysBreak +BinPackArguments: false +BinPackParameters: false +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortBlocksOnASingleLine: Always diff --git a/tcod/cdef.h b/tcod/cdef.h index f2a369c3..4cfb6041 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -1,20 +1,20 @@ /* Python specific cdefs which are loaded directly into cffi. */ #include "../libtcod/src/libtcod/libtcod.h" extern "Python" { -bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char *name); -bool _pycall_parser_new_flag(const char *name); +bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char* name); +bool _pycall_parser_new_flag(const char* name); bool _pycall_parser_new_property( - const char *propname, TCOD_value_type_t type, TCOD_value_t value); -bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char *name); -void _pycall_parser_error(const char *msg); + const char* propname, TCOD_value_type_t type, TCOD_value_t value); +bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char* name); +void _pycall_parser_error(const char* msg); -bool _pycall_bsp_callback(TCOD_bsp_t *node, void *userData); +bool _pycall_bsp_callback(TCOD_bsp_t* node, void* userData); -float _pycall_path_old(int x, int y, int xDest, int yDest, void *user_data); -float _pycall_path_simple(int x, int y, int xDest, int yDest, void *user_data); -float _pycall_path_swap_src_dest(int x1, int y1, - int x2, int y2, void *user_data); -float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data); +float _pycall_path_old(int x, int y, int xDest, int yDest, void* user_data); +float _pycall_path_simple(int x, int y, int xDest, int yDest, void* user_data); +float _pycall_path_swap_src_dest( + int x1, int y1, int x2, int y2, void* user_data); +float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data); void _pycall_sdl_hook(struct SDL_Surface*); diff --git a/tcod/cffi.h b/tcod/cffi.h index 98db5972..a1f3ce52 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -2,9 +2,8 @@ Anything included here will be accessible from tcod.loader.lib */ #include "../libtcod/src/libtcod/libtcod.h" #include "../libtcod/src/libtcod/libtcod_int.h" -#include "../libtcod/src/libtcod/wrappers.h" #include "../libtcod/src/libtcod/tileset_truetype.h" - +#include "../libtcod/src/libtcod/wrappers.h" #include "noise.h" #include "path.h" #include "random.h" diff --git a/tcod/noise.c b/tcod/noise.c index 3372619d..9527966b 100644 --- a/tcod/noise.c +++ b/tcod/noise.c @@ -2,7 +2,7 @@ #include "../libtcod/src/libtcod/libtcod.h" -float NoiseGetSample(TDLNoise *noise, float *xyzw) { +float NoiseGetSample(TDLNoise* noise, float* xyzw) { switch (noise->implementation) { default: case kNoiseImplementationSimple: @@ -16,11 +16,11 @@ float NoiseGetSample(TDLNoise *noise, float *xyzw) { } } void NoiseSampleMeshGrid( - TDLNoise *noise, const long len, const float *in, float *out) { -# pragma omp parallel + TDLNoise* noise, const long len, const float* in, float* out) { +#pragma omp parallel { long i; -# pragma omp for schedule(static) +#pragma omp for schedule(static) for (i = 0; i < len; ++i) { int axis; float xyzw[TCOD_NOISE_MAX_DIMENSIONS]; @@ -31,18 +31,19 @@ void NoiseSampleMeshGrid( } } } -static long GetSizeFromShape(const int ndim, const long *shape){ - long size=1; +static long GetSizeFromShape(const int ndim, const long* shape) { + long size = 1; long i; - for (i = 0; i < ndim; ++i){ - size *= shape[i]; - } + for (i = 0; i < ndim; ++i) { size *= shape[i]; } return size; } static float GetOpenMeshGridValue( - TDLNoise *noise, const int ndim, const long *shape, - const float **ogrid_in, const long index) { - int axis=ndim - 1; + TDLNoise* noise, + const int ndim, + const long* shape, + const float** ogrid_in, + const long index) { + int axis = ndim - 1; long xyzw_indexes[TCOD_NOISE_MAX_DIMENSIONS]; float xyzw_values[TCOD_NOISE_MAX_DIMENSIONS]; /* Convert index -> xyzw_indexes -> xyzw_values */ @@ -54,15 +55,18 @@ static float GetOpenMeshGridValue( } return NoiseGetSample(noise, xyzw_values); } -void NoiseSampleOpenMeshGrid(TDLNoise *noise, const int ndim_in, - const long *shape, - const float **ogrid_in, float *out) { -# pragma omp parallel +void NoiseSampleOpenMeshGrid( + TDLNoise* noise, + const int ndim_in, + const long* shape, + const float** ogrid_in, + float* out) { +#pragma omp parallel { long i; - long len=GetSizeFromShape(ndim_in, shape); -# pragma omp for schedule(static) - for (i = 0; i < len; ++i){ + long len = GetSizeFromShape(ndim_in, shape); +#pragma omp for schedule(static) + for (i = 0; i < len; ++i) { out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i); } } diff --git a/tcod/noise.h b/tcod/noise.h index be56d501..ded38d6b 100644 --- a/tcod/noise.h +++ b/tcod/noise.h @@ -1,12 +1,12 @@ #ifndef PYTHON_TCOD_NOISE_H_ #define PYTHON_TCOD_NOISE_H_ +#include "../libtcod/src/libtcod/mersenne.h" #include "../libtcod/src/libtcod/noise.h" #include "../libtcod/src/libtcod/noise_defaults.h" -#include "../libtcod/src/libtcod/mersenne.h" typedef enum NoiseImplementation { - kNoiseImplementationSimple, + kNoiseImplementationSimple, kNoiseImplementationFBM, kNoiseImplementationTurbulence, } NoiseImplementation; @@ -19,15 +19,18 @@ typedef struct TDLNoise { } TDLNoise; /* Return a single sample from noise. */ -float NoiseGetSample(TDLNoise *noise, float *xyzw); +float NoiseGetSample(TDLNoise* noise, float* xyzw); /* Fill `out` with samples derived from the mesh-grid `in`. */ void NoiseSampleMeshGrid( - TDLNoise *noise, const long len, const float *in, float *out); + TDLNoise* noise, const long len, const float* in, float* out); /* Fill `out` with samples derived from the open mesh-grid `in`. */ -void NoiseSampleOpenMeshGrid(TDLNoise *noise, - const int ndim, const long *shape, - const float **ogrid_in, float *out); +void NoiseSampleOpenMeshGrid( + TDLNoise* noise, + const int ndim, + const long* shape, + const float** ogrid_in, + float* out); #endif /* PYTHON_TCOD_NOISE_H_ */ diff --git a/tcod/path.c b/tcod/path.c index 373f8ce2..71f80042 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -9,67 +9,55 @@ #include "../libtcod/src/libtcod/pathfinder_frontier.h" #include "../libtcod/src/libtcod/utility.h" -static void* pick_array_pointer(const struct PathCostArray *map, int i, int j) -{ +static void* pick_array_pointer( + const struct PathCostArray* map, int i, int j) { return (void*)(map->array + map->strides[0] * i + map->strides[1] * j); } float PathCostArrayFloat32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(float*)pick_array_pointer(map, x2, y2); } float PathCostArrayInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(int8_t*)pick_array_pointer(map, x2, y2); } float PathCostArrayInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(int16_t*)pick_array_pointer(map, x2, y2); } float PathCostArrayInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return (float)(*(int32_t*)pick_array_pointer(map, x2, y2)); } float PathCostArrayUInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(uint8_t*)pick_array_pointer(map, x2, y2); } float PathCostArrayUInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(uint16_t*)pick_array_pointer(map, x2, y2); } float PathCostArrayUInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map) -{ + int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return (float)(*(uint32_t*)pick_array_pointer(map, x2, y2)); } -static bool array_in_range(const struct NArray* arr, int n, const int* index) -{ +static bool array_in_range(const struct NArray* arr, int n, const int* index) { for (int i = 0; i < n; ++i) { if (index[i] < 0 || index[i] >= arr->shape[i]) { return 0; } } return 1; } -static bool array2d_in_range(const struct NArray* arr, int i, int j) -{ +static bool array2d_in_range(const struct NArray* arr, int i, int j) { return 0 <= i && i < arr->shape[0] && 0 <= j && j < arr->shape[1]; } -static void* get_array_ptr(const struct NArray* arr, int n, const int* index) -{ +static void* get_array_ptr(const struct NArray* arr, int n, const int* index) { unsigned char* ptr = (unsigned char*)arr->data; - for (int i = 0; i < n; ++i) { - ptr += arr->strides[i] * index[i]; - } + for (int i = 0; i < n; ++i) { ptr += arr->strides[i] * index[i]; } return (void*)ptr; } -static int64_t get_array_int64(const struct NArray* arr, int n, const int* index) -{ +static int64_t get_array_int64( + const struct NArray* arr, int n, const int* index) { const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -92,13 +80,11 @@ static int64_t get_array_int64(const struct NArray* arr, int n, const int* index return 0; } } -static int get_array_int(const struct NArray* arr, int n, const int* index) -{ +static int get_array_int(const struct NArray* arr, int n, const int* index) { return (int)get_array_int64(arr, n, index); } static void set_array_int64( - struct NArray* arr, int n, const int* index, int64_t value) -{ + struct NArray* arr, int n, const int* index, int64_t value) { void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -128,21 +114,20 @@ static void set_array_int64( return; } } -static void set_array2d_int64(struct NArray* arr, int i, int j, int64_t value) -{ +static void set_array2d_int64( + struct NArray* arr, int i, int j, int64_t value) { int index[2] = {i, j}; set_array_int64(arr, 2, index, value); } -static void set_array_int(struct NArray* arr, int n, const int* index, int value) -{ +static void set_array_int( + struct NArray* arr, int n, const int* index, int value) { set_array_int64(arr, n, index, value); } -static void set_array2d_int(struct NArray* arr, int i, int j, int value) -{ +static void set_array2d_int(struct NArray* arr, int i, int j, int value) { set_array2d_int64(arr, i, j, value); } -static int64_t get_array_is_max(const struct NArray* arr, int n, const int* index) -{ +static int64_t get_array_is_max( + const struct NArray* arr, int n, const int* index) { const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -174,11 +159,9 @@ static void dijkstra2d_add_edge( struct NArray* dist_array, const struct NArray* cost, int edge_cost, - const int dir[2]) -{ + const int dir[2]) { const int index[2] = { - frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1] - }; + frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1]}; if (!array_in_range(dist_array, 2, index)) { return; } edge_cost *= get_array_int(cost, 2, index); if (edge_cost <= 0) { return; } @@ -192,8 +175,7 @@ int dijkstra2d( struct NArray* dist_array, const struct NArray* cost, int edges_2d_n, - const int* edges_2d) -{ + const int* edges_2d) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { @@ -220,8 +202,7 @@ int dijkstra2d_basic( struct NArray* dist_array, const struct NArray* cost, int cardinal, - int diagonal) -{ + int diagonal) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { @@ -256,8 +237,7 @@ static void hillclimb2d_check_edge( int* distance_in_out, const int origin[2], const int dir[2], - int index_out[2]) -{ + int index_out[2]) { const int next[2] = {origin[0] + dir[0], origin[1] + dir[1]}; if (!array_in_range(dist_array, 2, next)) { return; } const int next_distance = get_array_int(dist_array, 2, next); @@ -273,8 +253,7 @@ int hillclimb2d( int start_j, int edges_2d_n, const int* edges_2d, - int* out) -{ + int* out) { int next[2] = {start_i, start_j}; int old_dist = get_array_int(dist_array, 2, next); int new_dist = old_dist; @@ -291,9 +270,7 @@ int hillclimb2d( hillclimb2d_check_edge( dist_array, &new_dist, origin, &edges_2d[i * 2], next); } - if (old_dist == new_dist) { - return length; - } + if (old_dist == new_dist) { return length; } old_dist = new_dist; } } @@ -303,8 +280,7 @@ int hillclimb2d_basic( int start_j, bool cardinal, bool diagonal, - int* out) -{ + int* out) { int next[2] = {start_i, start_j}; int old_dist = get_array_int(dist_array, 2, next); int new_dist = old_dist; @@ -318,32 +294,37 @@ int hillclimb2d_basic( } const int origin[2] = {next[0], next[1]}; if (cardinal) { - hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[0], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[1], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[2], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[3], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, CARDINAL_[0], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, CARDINAL_[1], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, CARDINAL_[2], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, CARDINAL_[3], next); } if (diagonal) { - hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[0], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[1], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[2], next); - hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[3], next); - } - if (old_dist == new_dist) { - return length; + hillclimb2d_check_edge( + dist_array, &new_dist, origin, DIAGONAL_[0], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, DIAGONAL_[1], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, DIAGONAL_[2], next); + hillclimb2d_check_edge( + dist_array, &new_dist, origin, DIAGONAL_[3], next); } + if (old_dist == new_dist) { return length; } old_dist = new_dist; } } int compute_heuristic( - const struct PathfinderHeuristic* heuristic, int ndim, const int* index) -{ + const struct PathfinderHeuristic* heuristic, int ndim, const int* index) { if (!heuristic) { return 0; } int x = 0; int y = 0; int z = 0; int w = 0; - switch(ndim) { + switch (ndim) { case 4: w = abs(index[ndim - 4] - heuristic->target[ndim - 4]); //@fallthrough@ @@ -361,9 +342,9 @@ int compute_heuristic( } int diagonal = heuristic->diagonal != 0 ? MIN(x, y) : 0; int straight = MAX(x, y) - diagonal; - return (straight * heuristic->cardinal + diagonal * heuristic->diagonal - + w * heuristic->w + z * heuristic->z); - + return ( + straight * heuristic->cardinal + diagonal * heuristic->diagonal + + w * heuristic->w + z * heuristic->z); } void path_compute_add_edge( struct TCOD_Frontier* frontier, @@ -371,8 +352,7 @@ void path_compute_add_edge( struct NArray* travel_map, const struct NArray* cost_map, const int* edge_rule, - const struct PathfinderHeuristic* heuristic) -{ + const struct PathfinderHeuristic* heuristic) { int dest[TCOD_PATHFINDER_MAX_DIMENSIONS]; for (int i = 0; i < frontier->ndim; ++i) { dest[i] = frontier->active_index[i] + edge_rule[i]; @@ -396,8 +376,7 @@ void path_compute_add_edge( */ static bool path_compute_at_goal( const struct TCOD_Frontier* frontier, - const struct PathfinderHeuristic* heuristic) -{ + const struct PathfinderHeuristic* heuristic) { if (!heuristic) { return 0; } for (int i = 0; i < frontier->ndim; ++i) { if (frontier->active_index[i] != heuristic->target[i]) { return 0; } @@ -410,17 +389,12 @@ int path_compute_step( struct NArray* travel_map, int n, const struct PathfinderRule* rules, - const struct PathfinderHeuristic* heuristic) -{ - if (!frontier) { - return TCOD_set_errorv("Missing frontier."); - } + const struct PathfinderHeuristic* heuristic) { + if (!frontier) { return TCOD_set_errorv("Missing frontier."); } if (frontier->ndim <= 0 || frontier->ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid frontier->ndim."); } - if (!dist_map) { - return TCOD_set_errorv("Missing dist_map."); - } + if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); } if (frontier->ndim != dist_map->ndim) { return TCOD_set_errorv("Invalid or corrupt input."); } @@ -430,7 +404,8 @@ int path_compute_step( TCOD_frontier_pop(frontier); for (int i = 0; i < n; ++i) { if (rules[i].condition.type) { - if (!get_array_int(&rules[i].condition, frontier->ndim, frontier->active_index)) { + if (!get_array_int( + &rules[i].condition, frontier->ndim, frontier->active_index)) { continue; } } @@ -445,7 +420,7 @@ int path_compute_step( } } if (path_compute_at_goal(frontier, heuristic)) { - return 1; // Heuristic target reached. + return 1; // Heuristic target reached. } return 0; } @@ -455,35 +430,22 @@ int path_compute( struct NArray* travel_map, int n, const struct PathfinderRule* rules, - const struct PathfinderHeuristic* heuristic) -{ - if (!frontier) { - return TCOD_set_errorv("Missing frontier."); - } + const struct PathfinderHeuristic* heuristic) { + if (!frontier) { return TCOD_set_errorv("Missing frontier."); } while (TCOD_frontier_size(frontier)) { - int err = path_compute_step( - frontier, - dist_map, - travel_map, - n, - rules, - heuristic); + int err = + path_compute_step(frontier, dist_map, travel_map, n, rules, heuristic); if (err != 0) { return err; } } return 0; } ptrdiff_t get_travel_path( - int8_t ndim, const struct NArray* travel_map, const int* start, int* out) -{ + int8_t ndim, const struct NArray* travel_map, const int* start, int* out) { if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid ndim."); } - if (!travel_map) { - return TCOD_set_errorv("Missing travel_map."); - } - if (!start) { - return TCOD_set_errorv("Missing start."); - } + if (!travel_map) { return TCOD_set_errorv("Missing travel_map."); } + if (!start) { return TCOD_set_errorv("Missing start."); } if (ndim != travel_map->ndim - 1) { return TCOD_set_errorv("Invalid or corrupt input."); } @@ -502,19 +464,23 @@ ptrdiff_t get_travel_path( if (!array_in_range(travel_map, ndim, next)) { switch (ndim) { case 1: - return TCOD_set_errorvf( - "Index (%i) is out of range.", next[0]); + return TCOD_set_errorvf("Index (%i) is out of range.", next[0]); case 2: return TCOD_set_errorvf( "Index (%i, %i) is out of range.", next[0], next[1]); case 3: return TCOD_set_errorvf( "Index (%i, %i, %i) is out of range.", - next[0], next[1], next[2]); + next[0], + next[1], + next[2]); case 4: return TCOD_set_errorvf( "Index (%i, %i, %i, %i) is out of range.", - next[0], next[1], next[2], next[3]); + next[0], + next[1], + next[2], + next[3]); } } next = get_array_ptr(travel_map, ndim, next); @@ -526,35 +492,31 @@ ptrdiff_t get_travel_path( } int update_frontier_heuristic( struct TCOD_Frontier* frontier, - const struct PathfinderHeuristic* heuristic) -{ - if (!frontier) { - return TCOD_set_errorv("Missing frontier."); - } + const struct PathfinderHeuristic* heuristic) { + if (!frontier) { return TCOD_set_errorv("Missing frontier."); } for (int i = 0; i < frontier->heap.size; ++i) { unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; struct TCOD_HeapNode* heap_node = (void*)heap_ptr; struct FrontierNode* fnode = (struct FrontierNode*)heap_node->data; - heap_node->priority = ( - fnode->distance - + compute_heuristic(heuristic, frontier->ndim, fnode->index) - ); + heap_node->priority = + (fnode->distance + + compute_heuristic(heuristic, frontier->ndim, fnode->index)); } TCOD_minheap_heapify(&frontier->heap); return 0; } static int update_frontier_from_distance_iterator( - struct TCOD_Frontier* frontier, const struct NArray* dist_map, + struct TCOD_Frontier* frontier, + const struct NArray* dist_map, int dimension, - int* index) -{ + int* index) { if (dimension == frontier->ndim) { if (get_array_is_max(dist_map, dimension, index)) { return 0; } int dist = get_array_int(dist_map, dimension, index); return TCOD_frontier_push(frontier, index, dist, dist); } - for (int i = 0; i < dist_map->shape[dimension]; ) { + for (int i = 0; i < dist_map->shape[dimension];) { index[dimension] = i; int err = update_frontier_from_distance_iterator( frontier, dist_map, dimension + 1, index); @@ -563,28 +525,19 @@ static int update_frontier_from_distance_iterator( return 0; } int rebuild_frontier_from_distance( - struct TCOD_Frontier* frontier, const struct NArray* dist_map) -{ - if (!frontier) { - return TCOD_set_errorv("Missing frontier."); - } - if (!dist_map) { - return TCOD_set_errorv("Missing dist_map."); - } + struct TCOD_Frontier* frontier, const struct NArray* dist_map) { + if (!frontier) { return TCOD_set_errorv("Missing frontier."); } + if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); } TCOD_frontier_clear(frontier); int index[TCOD_PATHFINDER_MAX_DIMENSIONS]; return update_frontier_from_distance_iterator(frontier, dist_map, 0, index); } int frontier_has_index( const struct TCOD_Frontier* frontier, - const int* index) // index[frontier->ndim] + const int* index) // index[frontier->ndim] { - if (!frontier) { - return TCOD_set_errorv("Missing frontier."); - } - if (!index) { - return TCOD_set_errorv("Missing index."); - } + if (!frontier) { return TCOD_set_errorv("Missing frontier."); } + if (!index) { return TCOD_set_errorv("Missing index."); } for (int i = 0; i < frontier->heap.size; ++i) { const unsigned char* heap_ptr = (const unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; diff --git a/tcod/path.h b/tcod/path.h index 54497f01..c57975e2 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -33,9 +33,9 @@ enum NP_Type { struct NArray { enum NP_Type type; int8_t ndim; - char *data; - ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 - ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 + char* data; + ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 + ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 }; struct PathfinderRule { @@ -63,30 +63,30 @@ struct FrontierNode { }; struct PathCostArray { - char *array; - long long strides[2]; + char* array; + long long strides[2]; }; float PathCostArrayFloat32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayUInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayUInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayUInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); float PathCostArrayInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray *map); + int x1, int y1, int x2, int y2, const struct PathCostArray* map); /** Return the value to add to the distance to sort nodes by A*. @@ -130,7 +130,7 @@ int path_compute_step( struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules, // rules[n] + const struct PathfinderRule* rules, // rules[n] const struct PathfinderHeuristic* heuristic); int path_compute( @@ -138,7 +138,7 @@ int path_compute( struct NArray* dist_map, struct NArray* travel_map, int n, - const struct PathfinderRule* rules, // rules[n] + const struct PathfinderRule* rules, // rules[n] const struct PathfinderHeuristic* heuristic); /** Find and get a path along `travel_map`. @@ -165,9 +165,7 @@ int rebuild_frontier_from_distance( /** Return true if `index[frontier->ndim]` is a node in `frontier`. */ -int frontier_has_index( - const struct TCOD_Frontier* frontier, - const int* index); +int frontier_has_index(const struct TCOD_Frontier* frontier, const int* index); #ifdef __cplusplus } #endif diff --git a/tcod/tcod.c b/tcod/tcod.c index 28847fc0..1f8abffb 100644 --- a/tcod/tcod.c +++ b/tcod/tcod.c @@ -29,8 +29,6 @@ int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out) { out += 2; if (x1 == x2 && y1 == y2) { return length; } TCOD_line_init_mt(x1, y1, x2, y2, &bresenham); - while (!TCOD_line_step_mt(&out[0], &out[1], &bresenham)) { - out += 2; - } + while (!TCOD_line_step_mt(&out[0], &out[1], &bresenham)) { out += 2; } return length; } diff --git a/tcod/tcod.h b/tcod/tcod.h index e79c465a..3648a340 100644 --- a/tcod/tcod.h +++ b/tcod/tcod.h @@ -10,6 +10,6 @@ extern "C" { #endif int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out); #ifdef __cplusplus -} // extern "C" +} // extern "C" #endif #endif /* TCOD_TCOD_H_ */ diff --git a/tcod/tdl.c b/tcod/tdl.c index 5745ca36..149095dd 100644 --- a/tcod/tdl.c +++ b/tcod/tdl.c @@ -5,182 +5,176 @@ void SDL_main(void){}; -TCOD_value_t TDL_list_get_union(TCOD_list_t l,int idx){ - TCOD_value_t item; - item.custom = TCOD_list_get(l, idx); - return item; +TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx) { + TCOD_value_t item; + item.custom = TCOD_list_get(l, idx); + return item; } -bool TDL_list_get_bool(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).b; +bool TDL_list_get_bool(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).b; } -char TDL_list_get_char(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).c; +char TDL_list_get_char(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).c; } -int TDL_list_get_int(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).i; +int TDL_list_get_int(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).i; } -float TDL_list_get_float(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).f; +float TDL_list_get_float(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).f; } -char* TDL_list_get_string(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).s; +char* TDL_list_get_string(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).s; } -TCOD_color_t TDL_list_get_color(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).col; +TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).col; } -TCOD_dice_t TDL_list_get_dice(TCOD_list_t l,int idx){ - return TDL_list_get_union(l, idx).dice; +TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx) { + return TDL_list_get_union(l, idx).dice; } - /* get a TCOD color type from a 0xRRGGBB formatted integer */ -TCOD_color_t TDL_color_from_int(int color){ - TCOD_color_t tcod_color={(color >> 16) & 0xff, - (color >> 8) & 0xff, - color & 0xff}; - return tcod_color; +TCOD_color_t TDL_color_from_int(int color) { + TCOD_color_t tcod_color = { + (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; + return tcod_color; } -int TDL_color_to_int(TCOD_color_t *color){ - return (color->r << 16) | (color->g << 8) | color->b; +int TDL_color_to_int(TCOD_color_t* color) { + return (color->r << 16) | (color->g << 8) | color->b; } -int* TDL_color_int_to_array(int color){ - static int array[3]; - array[0] = (color >> 16) & 0xff; - array[1] = (color >> 8) & 0xff; - array[2] = color & 0xff; - return array; +int* TDL_color_int_to_array(int color) { + static int array[3]; + array[0] = (color >> 16) & 0xff; + array[1] = (color >> 8) & 0xff; + array[2] = color & 0xff; + return array; } -int TDL_color_RGB(int r, int g, int b){ - return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); +int TDL_color_RGB(int r, int g, int b) { + return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } -int TDL_color_HSV(float h, float s, float v){ - TCOD_color_t tcod_color=TCOD_color_HSV(h, s, v); - return TDL_color_to_int(&tcod_color); +int TDL_color_HSV(float h, float s, float v) { + TCOD_color_t tcod_color = TCOD_color_HSV(h, s, v); + return TDL_color_to_int(&tcod_color); } -bool TDL_color_equals(int c1, int c2){ - return (c1 == c2); -} +bool TDL_color_equals(int c1, int c2) { return (c1 == c2); } -int TDL_color_add(int c1, int c2){ - TCOD_color_t tc1=TDL_color_from_int(c1); - TCOD_color_t tc2=TDL_color_from_int(c2); - tc1=TCOD_color_add(tc1, tc2); - return TDL_color_to_int(&tc1); +int TDL_color_add(int c1, int c2) { + TCOD_color_t tc1 = TDL_color_from_int(c1); + TCOD_color_t tc2 = TDL_color_from_int(c2); + tc1 = TCOD_color_add(tc1, tc2); + return TDL_color_to_int(&tc1); } -int TDL_color_subtract(int c1, int c2){ - TCOD_color_t tc1=TDL_color_from_int(c1); - TCOD_color_t tc2=TDL_color_from_int(c2); - tc1=TCOD_color_subtract(tc1, tc2); - return TDL_color_to_int(&tc1); +int TDL_color_subtract(int c1, int c2) { + TCOD_color_t tc1 = TDL_color_from_int(c1); + TCOD_color_t tc2 = TDL_color_from_int(c2); + tc1 = TCOD_color_subtract(tc1, tc2); + return TDL_color_to_int(&tc1); } -int TDL_color_multiply(int c1, int c2){ - TCOD_color_t tc1=TDL_color_from_int(c1); - TCOD_color_t tc2=TDL_color_from_int(c2); - tc1=TCOD_color_multiply(tc1, tc2); - return TDL_color_to_int(&tc1); +int TDL_color_multiply(int c1, int c2) { + TCOD_color_t tc1 = TDL_color_from_int(c1); + TCOD_color_t tc2 = TDL_color_from_int(c2); + tc1 = TCOD_color_multiply(tc1, tc2); + return TDL_color_to_int(&tc1); } -int TDL_color_multiply_scalar(int c, float value){ - TCOD_color_t tc=TDL_color_from_int(c); - tc=TCOD_color_multiply_scalar(tc, value); - return TDL_color_to_int(&tc); +int TDL_color_multiply_scalar(int c, float value) { + TCOD_color_t tc = TDL_color_from_int(c); + tc = TCOD_color_multiply_scalar(tc, value); + return TDL_color_to_int(&tc); } -int TDL_color_lerp(int c1, int c2, float coef){ - TCOD_color_t tc1=TDL_color_from_int(c1); - TCOD_color_t tc2=TDL_color_from_int(c2); - tc1=TCOD_color_lerp(tc1, tc2, coef); - return TDL_color_to_int(&tc1); +int TDL_color_lerp(int c1, int c2, float coef) { + TCOD_color_t tc1 = TDL_color_from_int(c1); + TCOD_color_t tc2 = TDL_color_from_int(c2); + tc1 = TCOD_color_lerp(tc1, tc2, coef); + return TDL_color_to_int(&tc1); } -float TDL_color_get_hue(int color){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - return TCOD_color_get_hue(tcod_color); +float TDL_color_get_hue(int color) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + return TCOD_color_get_hue(tcod_color); } -float TDL_color_get_saturation(int color){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - return TCOD_color_get_saturation(tcod_color); +float TDL_color_get_saturation(int color) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + return TCOD_color_get_saturation(tcod_color); } -float TDL_color_get_value(int color){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - return TCOD_color_get_value(tcod_color); +float TDL_color_get_value(int color) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + return TCOD_color_get_value(tcod_color); } -int TDL_color_set_hue(int color, float h){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - TCOD_color_set_hue(&tcod_color, h); - return TDL_color_to_int(&tcod_color); +int TDL_color_set_hue(int color, float h) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + TCOD_color_set_hue(&tcod_color, h); + return TDL_color_to_int(&tcod_color); } -int TDL_color_set_saturation(int color, float h){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - TCOD_color_set_saturation(&tcod_color, h); - return TDL_color_to_int(&tcod_color); +int TDL_color_set_saturation(int color, float h) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + TCOD_color_set_saturation(&tcod_color, h); + return TDL_color_to_int(&tcod_color); } -int TDL_color_set_value(int color, float h){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - TCOD_color_set_value(&tcod_color, h); - return TDL_color_to_int(&tcod_color); +int TDL_color_set_value(int color, float h) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + TCOD_color_set_value(&tcod_color, h); + return TDL_color_to_int(&tcod_color); } -int TDL_color_shift_hue(int color, float hshift){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - TCOD_color_shift_hue(&tcod_color, hshift); - return TDL_color_to_int(&tcod_color); +int TDL_color_shift_hue(int color, float hshift) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + TCOD_color_shift_hue(&tcod_color, hshift); + return TDL_color_to_int(&tcod_color); } -int TDL_color_scale_HSV(int color, float scoef, float vcoef){ - TCOD_color_t tcod_color=TDL_color_from_int(color); - TCOD_color_scale_HSV(&tcod_color, scoef, vcoef); - return TDL_color_to_int(&tcod_color); +int TDL_color_scale_HSV(int color, float scoef, float vcoef) { + TCOD_color_t tcod_color = TDL_color_from_int(color); + TCOD_color_scale_HSV(&tcod_color, scoef, vcoef); + return TDL_color_to_int(&tcod_color); } - #define TRANSPARENT_BIT 1 #define WALKABLE_BIT 2 #define FOV_BIT 4 /* set map transparent and walkable flags from a buffer */ -void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer) { - int width=TCOD_map_get_width(map); - int height=TCOD_map_get_height(map); +void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer) { + int width = TCOD_map_get_width(map); + int height = TCOD_map_get_height(map); int x; int y; - for (y = 0; y < height; y++){ - for (x = 0; x < width; x++){ + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { int i = y * width + x; - TCOD_map_set_properties(map, x, y, - (buffer[i] & TRANSPARENT_BIT) != 0, - (buffer[i] & WALKABLE_BIT) != 0); + TCOD_map_set_properties( + map, + x, + y, + (buffer[i] & TRANSPARENT_BIT) != 0, + (buffer[i] & WALKABLE_BIT) != 0); } } } /* get fov from tcod map */ -void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative) { - int width=TCOD_map_get_width(map); - int height=TCOD_map_get_height(map); +void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative) { + int width = TCOD_map_get_width(map); + int height = TCOD_map_get_height(map); int x; int y; - for (y = 0; y < height; y++){ - for (x = 0; x < width; x++){ + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { int i = y * width + x; - if (!cumulative) { - buffer[i] &= ~FOV_BIT; - } - if (TCOD_map_is_in_fov(map, x, y)) { - buffer[i] |= FOV_BIT; - } + if (!cumulative) { buffer[i] &= ~FOV_BIT; } + if (TCOD_map_is_in_fov(map, x, y)) { buffer[i] |= FOV_BIT; } } } } @@ -189,10 +183,16 @@ void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative) { colors are converted to TCOD_color_t types in C and is much faster than in Python. Also Python indexing is used, negative x/y will index to (width-x, etc.) */ -int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, - int ch, int fg, int bg, TCOD_bkgnd_flag_t blend) { - int width=TCOD_console_get_width(console); - int height=TCOD_console_get_height(console); +int TDL_console_put_char_ex( + TCOD_console_t console, + int x, + int y, + int ch, + int fg, + int bg, + TCOD_bkgnd_flag_t blend) { + int width = TCOD_console_get_width(console); + int height = TCOD_console_get_height(console); TCOD_color_t color; if (x < -width || x >= width || y < -height || y >= height) { @@ -203,9 +203,7 @@ int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, if (x < 0) { x += width; }; if (y < 0) { y += height; }; - if (ch != -1) { - TCOD_console_set_char(console, x, y, ch); - } + if (ch != -1) { TCOD_console_set_char(console, x, y, ch); } if (fg != -1) { color = TDL_color_from_int(fg); TCOD_console_set_char_foreground(console, x, y, color); @@ -218,21 +216,21 @@ int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, } int TDL_console_get_bg(TCOD_console_t console, int x, int y) { - TCOD_color_t tcod_color=TCOD_console_get_char_background(console, x, y); - return TDL_color_to_int(&tcod_color); + TCOD_color_t tcod_color = TCOD_console_get_char_background(console, x, y); + return TDL_color_to_int(&tcod_color); } -int TDL_console_get_fg(TCOD_console_t console, int x, int y){ - TCOD_color_t tcod_color=TCOD_console_get_char_foreground(console, x, y); - return TDL_color_to_int(&tcod_color); +int TDL_console_get_fg(TCOD_console_t console, int x, int y) { + TCOD_color_t tcod_color = TCOD_console_get_char_foreground(console, x, y); + return TDL_color_to_int(&tcod_color); } -void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, - TCOD_bkgnd_flag_t flag){ - TCOD_console_set_char_background(console, x, y, TDL_color_from_int(color), - flag); +void TDL_console_set_bg( + TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag) { + TCOD_console_set_char_background( + console, x, y, TDL_color_from_int(color), flag); } -void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color){ - TCOD_console_set_char_foreground(console, x, y, TDL_color_from_int(color)); +void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color) { + TCOD_console_set_char_foreground(console, x, y, TDL_color_from_int(color)); } diff --git a/tcod/tdl.h b/tcod/tdl.h index 2f752bb1..f3fc61f1 100644 --- a/tcod/tdl.h +++ b/tcod/tdl.h @@ -5,19 +5,20 @@ /* TDL FUNCTONS ----------------------------------------------------------- */ -TCOD_value_t TDL_list_get_union(TCOD_list_t l,int idx); -bool TDL_list_get_bool(TCOD_list_t l,int idx); -char TDL_list_get_char(TCOD_list_t l,int idx); -int TDL_list_get_int(TCOD_list_t l,int idx); -float TDL_list_get_float(TCOD_list_t l,int idx); -char* TDL_list_get_string(TCOD_list_t l,int idx); -TCOD_color_t TDL_list_get_color(TCOD_list_t l,int idx); -TCOD_dice_t TDL_list_get_dice(TCOD_list_t l,int idx); -/*bool (*TDL_parser_new_property_func)(const char *propname, TCOD_value_type_t type, TCOD_value_t *value);*/ +TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx); +bool TDL_list_get_bool(TCOD_list_t l, int idx); +char TDL_list_get_char(TCOD_list_t l, int idx); +int TDL_list_get_int(TCOD_list_t l, int idx); +float TDL_list_get_float(TCOD_list_t l, int idx); +char* TDL_list_get_string(TCOD_list_t l, int idx); +TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx); +TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx); +/*bool (*TDL_parser_new_property_func)(const char *propname, TCOD_value_type_t + * type, TCOD_value_t *value);*/ /* color functions modified to use integers instead of structs */ TCOD_color_t TDL_color_from_int(int color); -int TDL_color_to_int(TCOD_color_t *color); +int TDL_color_to_int(TCOD_color_t* color); int* TDL_color_int_to_array(int color); int TDL_color_RGB(int r, int g, int b); int TDL_color_HSV(float h, float s, float v); @@ -41,15 +42,21 @@ int TDL_color_scale_HSV(int color, float scoef, float vcoef); * 2 = is_walkable * 4 = in_fov */ -void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer); -void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative); +void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer); +void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative); -int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, - int ch, int fg, int bg, TCOD_bkgnd_flag_t flag); +int TDL_console_put_char_ex( + TCOD_console_t console, + int x, + int y, + int ch, + int fg, + int bg, + TCOD_bkgnd_flag_t flag); int TDL_console_get_bg(TCOD_console_t console, int x, int y); int TDL_console_get_fg(TCOD_console_t console, int x, int y); -void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, - TCOD_bkgnd_flag_t flag); +void TDL_console_set_bg( + TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag); void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color); #endif /* PYTHON_TCOD_TDL_H_ */ From 4ad92631380bc05532cba2439c0e91376149b9e4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 28 Jun 2020 10:58:16 -0700 Subject: [PATCH 0378/1101] Add restrict qualifiers to C sources. --- tcod/noise.c | 21 +++++----- tcod/noise.h | 15 ++++--- tcod/path.c | 112 ++++++++++++++++++++++++++++----------------------- tcod/path.h | 64 ++++++++++++++++------------- 4 files changed, 119 insertions(+), 93 deletions(-) diff --git a/tcod/noise.c b/tcod/noise.c index 9527966b..b5903402 100644 --- a/tcod/noise.c +++ b/tcod/noise.c @@ -2,7 +2,7 @@ #include "../libtcod/src/libtcod/libtcod.h" -float NoiseGetSample(TDLNoise* noise, float* xyzw) { +float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw) { switch (noise->implementation) { default: case kNoiseImplementationSimple: @@ -16,7 +16,10 @@ float NoiseGetSample(TDLNoise* noise, float* xyzw) { } } void NoiseSampleMeshGrid( - TDLNoise* noise, const long len, const float* in, float* out) { + TDLNoise* noise, + const long len, + const float* __restrict in, + float* __restrict out) { #pragma omp parallel { long i; @@ -38,10 +41,10 @@ static long GetSizeFromShape(const int ndim, const long* shape) { return size; } static float GetOpenMeshGridValue( - TDLNoise* noise, + TDLNoise* __restrict noise, const int ndim, - const long* shape, - const float** ogrid_in, + const long* __restrict shape, + const float* __restrict* __restrict ogrid_in, const long index) { int axis = ndim - 1; long xyzw_indexes[TCOD_NOISE_MAX_DIMENSIONS]; @@ -56,11 +59,11 @@ static float GetOpenMeshGridValue( return NoiseGetSample(noise, xyzw_values); } void NoiseSampleOpenMeshGrid( - TDLNoise* noise, + TDLNoise* __restrict noise, const int ndim_in, - const long* shape, - const float** ogrid_in, - float* out) { + const long* __restrict shape, + const float* __restrict* __restrict ogrid_in, + float* __restrict out) { #pragma omp parallel { long i; diff --git a/tcod/noise.h b/tcod/noise.h index ded38d6b..a63a7a10 100644 --- a/tcod/noise.h +++ b/tcod/noise.h @@ -19,18 +19,21 @@ typedef struct TDLNoise { } TDLNoise; /* Return a single sample from noise. */ -float NoiseGetSample(TDLNoise* noise, float* xyzw); +float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw); /* Fill `out` with samples derived from the mesh-grid `in`. */ void NoiseSampleMeshGrid( - TDLNoise* noise, const long len, const float* in, float* out); + TDLNoise* noise, + const long len, + const float* __restrict in, + float* __restrict out); /* Fill `out` with samples derived from the open mesh-grid `in`. */ void NoiseSampleOpenMeshGrid( - TDLNoise* noise, + TDLNoise* __restrict noise, const int ndim, - const long* shape, - const float** ogrid_in, - float* out); + const long* __restrict shape, + const float* __restrict* __restrict ogrid_in, + float* __restrict out); #endif /* PYTHON_TCOD_NOISE_H_ */ diff --git a/tcod/path.c b/tcod/path.c index 71f80042..a09dfa20 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -84,7 +84,10 @@ static int get_array_int(const struct NArray* arr, int n, const int* index) { return (int)get_array_int64(arr, n, index); } static void set_array_int64( - struct NArray* arr, int n, const int* index, int64_t value) { + struct NArray* __restrict arr, + int n, + const int* __restrict index, + int64_t value) { void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -115,15 +118,16 @@ static void set_array_int64( } } static void set_array2d_int64( - struct NArray* arr, int i, int j, int64_t value) { + struct NArray* __restrict arr, int i, int j, int64_t value) { int index[2] = {i, j}; set_array_int64(arr, 2, index, value); } static void set_array_int( - struct NArray* arr, int n, const int* index, int value) { + struct NArray* __restrict arr, int n, const int* index, int value) { set_array_int64(arr, n, index, value); } -static void set_array2d_int(struct NArray* arr, int i, int j, int value) { +static void set_array2d_int( + struct NArray* __restrict arr, int i, int j, int value) { set_array2d_int64(arr, i, j, value); } static int64_t get_array_is_max( @@ -155,11 +159,12 @@ static const int CARDINAL_[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; static const int DIAGONAL_[4][2] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}}; static void dijkstra2d_add_edge( - struct TCOD_Frontier* frontier, - struct NArray* dist_array, - const struct NArray* cost, + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_array, + const struct NArray* __restrict cost, int edge_cost, - const int dir[2]) { + const int* __restrict dir // dir[2] +) { const int index[2] = { frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1]}; if (!array_in_range(dist_array, 2, index)) { return; } @@ -172,10 +177,10 @@ static void dijkstra2d_add_edge( } int dijkstra2d( - struct NArray* dist_array, - const struct NArray* cost, + struct NArray* __restrict dist_array, + const struct NArray* __restrict cost, int edges_2d_n, - const int* edges_2d) { + const int* __restrict edges_2d) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { @@ -199,8 +204,8 @@ int dijkstra2d( } int dijkstra2d_basic( - struct NArray* dist_array, - const struct NArray* cost, + struct NArray* __restrict dist_array, + const struct NArray* __restrict cost, int cardinal, int diagonal) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); @@ -233,11 +238,12 @@ int dijkstra2d_basic( return TCOD_E_OK; } static void hillclimb2d_check_edge( - const struct NArray* dist_array, - int* distance_in_out, - const int origin[2], - const int dir[2], - int index_out[2]) { + const struct NArray* __restrict dist_array, + int* __restrict distance_in_out, + const int* __restrict origin, // origin[2] + const int* __restrict dir, // dir[2] + int* __restrict index_out // index_out[2] +) { const int next[2] = {origin[0] + dir[0], origin[1] + dir[1]}; if (!array_in_range(dist_array, 2, next)) { return; } const int next_distance = get_array_int(dist_array, 2, next); @@ -248,12 +254,12 @@ static void hillclimb2d_check_edge( } } int hillclimb2d( - const struct NArray* dist_array, + const struct NArray* __restrict dist_array, int start_i, int start_j, int edges_2d_n, - const int* edges_2d, - int* out) { + const int* __restrict edges_2d, + int* __restrict out) { int next[2] = {start_i, start_j}; int old_dist = get_array_int(dist_array, 2, next); int new_dist = old_dist; @@ -275,12 +281,12 @@ int hillclimb2d( } } int hillclimb2d_basic( - const struct NArray* dist_array, + const struct NArray* __restrict dist_array, int start_i, int start_j, bool cardinal, bool diagonal, - int* out) { + int* __restrict out) { int next[2] = {start_i, start_j}; int old_dist = get_array_int(dist_array, 2, next); int new_dist = old_dist; @@ -318,7 +324,9 @@ int hillclimb2d_basic( } } int compute_heuristic( - const struct PathfinderHeuristic* heuristic, int ndim, const int* index) { + const struct PathfinderHeuristic* __restrict heuristic, + int ndim, + const int* __restrict index) { if (!heuristic) { return 0; } int x = 0; int y = 0; @@ -347,12 +355,12 @@ int compute_heuristic( w * heuristic->w + z * heuristic->z); } void path_compute_add_edge( - struct TCOD_Frontier* frontier, - struct NArray* dist_map, - struct NArray* travel_map, - const struct NArray* cost_map, - const int* edge_rule, - const struct PathfinderHeuristic* heuristic) { + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_map, + struct NArray* __restrict travel_map, + const struct NArray* __restrict cost_map, + const int* __restrict edge_rule, + const struct PathfinderHeuristic* __restrict heuristic) { int dest[TCOD_PATHFINDER_MAX_DIMENSIONS]; for (int i = 0; i < frontier->ndim; ++i) { dest[i] = frontier->active_index[i] + edge_rule[i]; @@ -375,8 +383,8 @@ void path_compute_add_edge( Returns true if the heuristic target has been reached by the active_node. */ static bool path_compute_at_goal( - const struct TCOD_Frontier* frontier, - const struct PathfinderHeuristic* heuristic) { + const struct TCOD_Frontier* __restrict frontier, + const struct PathfinderHeuristic* __restrict heuristic) { if (!heuristic) { return 0; } for (int i = 0; i < frontier->ndim; ++i) { if (frontier->active_index[i] != heuristic->target[i]) { return 0; } @@ -384,12 +392,12 @@ static bool path_compute_at_goal( return 1; } int path_compute_step( - struct TCOD_Frontier* frontier, - struct NArray* dist_map, - struct NArray* travel_map, + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_map, + struct NArray* __restrict travel_map, int n, - const struct PathfinderRule* rules, - const struct PathfinderHeuristic* heuristic) { + const struct PathfinderRule* __restrict rules, + const struct PathfinderHeuristic* __restrict heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } if (frontier->ndim <= 0 || frontier->ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid frontier->ndim."); @@ -425,12 +433,12 @@ int path_compute_step( return 0; } int path_compute( - struct TCOD_Frontier* frontier, - struct NArray* dist_map, - struct NArray* travel_map, + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_map, + struct NArray* __restrict travel_map, int n, - const struct PathfinderRule* rules, - const struct PathfinderHeuristic* heuristic) { + const struct PathfinderRule* __restrict rules, + const struct PathfinderHeuristic* __restrict heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } while (TCOD_frontier_size(frontier)) { int err = @@ -440,7 +448,10 @@ int path_compute( return 0; } ptrdiff_t get_travel_path( - int8_t ndim, const struct NArray* travel_map, const int* start, int* out) { + int8_t ndim, + const struct NArray* __restrict travel_map, + const int* __restrict start, + int* __restrict out) { if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid ndim."); } @@ -491,8 +502,8 @@ ptrdiff_t get_travel_path( return length; } int update_frontier_heuristic( - struct TCOD_Frontier* frontier, - const struct PathfinderHeuristic* heuristic) { + struct TCOD_Frontier* __restrict frontier, + const struct PathfinderHeuristic* __restrict heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } for (int i = 0; i < frontier->heap.size; ++i) { unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; @@ -507,8 +518,8 @@ int update_frontier_heuristic( return 0; } static int update_frontier_from_distance_iterator( - struct TCOD_Frontier* frontier, - const struct NArray* dist_map, + struct TCOD_Frontier* __restrict frontier, + const struct NArray* __restrict dist_map, int dimension, int* index) { if (dimension == frontier->ndim) { @@ -525,7 +536,8 @@ static int update_frontier_from_distance_iterator( return 0; } int rebuild_frontier_from_distance( - struct TCOD_Frontier* frontier, const struct NArray* dist_map) { + struct TCOD_Frontier* __restrict frontier, + const struct NArray* __restrict dist_map) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); } TCOD_frontier_clear(frontier); @@ -533,8 +545,8 @@ int rebuild_frontier_from_distance( return update_frontier_from_distance_iterator(frontier, dist_map, 0, index); } int frontier_has_index( - const struct TCOD_Frontier* frontier, - const int* index) // index[frontier->ndim] + const struct TCOD_Frontier* __restrict frontier, + const int* __restrict index) // index[frontier->ndim] { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } if (!index) { return TCOD_set_errorv("Missing index."); } diff --git a/tcod/path.h b/tcod/path.h index c57975e2..17c281aa 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -33,7 +33,7 @@ enum NP_Type { struct NArray { enum NP_Type type; int8_t ndim; - char* data; + char* __restrict data; ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 }; @@ -46,7 +46,7 @@ struct PathfinderRule { /** Number of edge rules in `edge_array`. */ int edge_count; /** Example of 2D edges: [i, j, cost, i_2, j_2, cost_2, ...] */ - int* edge_array; + int* __restrict edge_array; }; struct PathfinderHeuristic { @@ -63,7 +63,7 @@ struct FrontierNode { }; struct PathCostArray { - char* array; + char* __restrict array; long long strides[2]; }; @@ -96,50 +96,52 @@ float PathCostArrayInt32( `index[ndim]` must not be NULL. */ int compute_heuristic( - const struct PathfinderHeuristic* heuristic, int ndim, const int* index); + const struct PathfinderHeuristic* __restrict heuristic, + int ndim, + const int* __restrict index); int dijkstra2d( - struct NArray* dist, - const struct NArray* cost, + struct NArray* __restrict dist, + const struct NArray* __restrict cost, int edges_2d_n, - const int* edges_2d); + const int* __restrict edges_2d); int dijkstra2d_basic( - struct NArray* dist, - const struct NArray* cost, + struct NArray* __restrict dist, + const struct NArray* __restrict cost, int cardinal, int diagonal); int hillclimb2d( - const struct NArray* dist_array, + const struct NArray* __restrict dist_array, int start_i, int start_j, int edges_2d_n, - const int* edges_2d, - int* out); + const int* __restrict edges_2d, + int* __restrict out); int hillclimb2d_basic( - const struct NArray* dist, + const struct NArray* __restrict dist, int x, int y, bool cardinal, bool diagonal, - int* out); + int* __restrict out); int path_compute_step( - struct TCOD_Frontier* frontier, - struct NArray* dist_map, - struct NArray* travel_map, + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_map, + struct NArray* __restrict travel_map, int n, - const struct PathfinderRule* rules, // rules[n] + const struct PathfinderRule* __restrict rules, // rules[n] const struct PathfinderHeuristic* heuristic); int path_compute( - struct TCOD_Frontier* frontier, - struct NArray* dist_map, - struct NArray* travel_map, + struct TCOD_Frontier* __restrict frontier, + struct NArray* __restrict dist_map, + struct NArray* __restrict travel_map, int n, - const struct PathfinderRule* rules, // rules[n] - const struct PathfinderHeuristic* heuristic); + const struct PathfinderRule* __restrict rules, // rules[n] + const struct PathfinderHeuristic* __restrict heuristic); /** Find and get a path along `travel_map`. @@ -148,24 +150,30 @@ int path_compute( parameters. */ ptrdiff_t get_travel_path( - int8_t ndim, const struct NArray* travel_map, const int* start, int* out); + int8_t ndim, + const struct NArray* __restrict travel_map, + const int* __restrict start, + int* __restrict out); /** Update the priority of nodes on the frontier and sort them. */ int update_frontier_heuristic( - struct TCOD_Frontier* frontier, - const struct PathfinderHeuristic* heuristic); + struct TCOD_Frontier* __restrict frontier, + const struct PathfinderHeuristic* __restrict heuristic); /** Update a frontier from a distance array. Assumes no heuristic is active. */ int rebuild_frontier_from_distance( - struct TCOD_Frontier* frontier, const struct NArray* dist_map); + struct TCOD_Frontier* __restrict frontier, + const struct NArray* __restrict dist_map); /** Return true if `index[frontier->ndim]` is a node in `frontier`. */ -int frontier_has_index(const struct TCOD_Frontier* frontier, const int* index); +int frontier_has_index( + const struct TCOD_Frontier* __restrict frontier, + const int* __restrict index); #ifdef __cplusplus } #endif From 03a8a27ff78de9e051770dccf8c3067e006e266c Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 28 Jun 2020 13:06:37 -0700 Subject: [PATCH 0379/1101] Rename BasicGraph to SimpleGraph. --- CHANGELOG.rst | 2 +- tcod/path.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 975b4403..4ccb8d16 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ v2.0.0 Unreleased ------------------ Added - - `tcod.path.BasicGraph` for pathfinding on simple 2D arrays. + - `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays. Changed - `tcod.path.CustomGraph` now accepts an `order` parameter. diff --git a/tcod/path.py b/tcod/path.py index 2f47aed0..3431de39 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -2,14 +2,14 @@ To get started create a 2D NumPy array of integers where a value of zero is a blocked node and any higher value is the cost to move to that node. -You then pass this array to :any:`BasicGraph`, and then pass that graph to +You then pass this array to :any:`SimpleGraph`, and then pass that graph to :any:`Pathfinder`. Once you have a :any:`Pathfinder` you call :any:`Pathfinder.add_root` to set the root node. You can then get a path towards or away from the root with :any:`Pathfinder.path_from` and :any:`Pathfinder.path_to` respectively. -:any:`BasicGraph` includes a code example of the above process. +:any:`SimpleGraph` includes a code example of the above process. .. versionchanged:: 5.0 All path-finding functions now respect the NumPy array shape (if a NumPy @@ -603,7 +603,7 @@ class CustomGraph: """A customizable graph defining how a pathfinder traverses the world. If you only need to path over a 2D array with typical edge rules then you - should use :any:`BasicGraph`. + should use :any:`SimpleGraph`. This is an advanced interface for defining custom edge rules which would allow things such as 3D movement. @@ -1012,7 +1012,7 @@ def _resolve(self, pathfinder: "Pathfinder") -> None: ) -class BasicGraph: +class SimpleGraph: """A simple 2D graph implementation. `cost` is a NumPy array where each node has the cost for movement into @@ -1035,7 +1035,7 @@ class BasicGraph: >>> import numpy as np >>> import tcod >>> cost = np.ones((5, 10), dtype=np.int8, order="F") - >>> graph = tcod.path.BasicGraph(cost=cost, cardinal=2, diagonal=3) + >>> graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3) >>> pf = tcod.path.Pathfinder(graph) >>> pf.add_root((2, 4)) >>> pf.path_to((3, 7)).tolist() @@ -1087,13 +1087,13 @@ def _heuristic(self) -> Optional[Tuple[int, int, int, int]]: def set_heuristic(self, *, cardinal: int, diagonal: int) -> None: """Change the heuristic for this graph. - When created a :any:`BasicGraph` will automatically have a heuristic. + When created a :any:`SimpleGraph` will automatically have a heuristic. So calling this method is often unnecessary. `cardinal` and `diagonal` are weights for the heuristic. Higher values are more greedy. The default values are set to ``cardinal * greed`` and - ``diagonal * greed`` when the :any:`BasicGraph` is created. + ``diagonal * greed`` when the :any:`SimpleGraph` is created. """ self._subgraph.set_heuristic(cardinal=cardinal, diagonal=diagonal) @@ -1105,12 +1105,12 @@ class Pathfinder: """A generic modular pathfinder. How the pathfinder functions depends on the graph provided. see - :any:`BasicGraph` for how to set one up. + :any:`SimpleGraph` for how to set one up. .. versionadded:: 11.13 """ - def __init__(self, graph: Union[CustomGraph, BasicGraph]): + def __init__(self, graph: Union[CustomGraph, SimpleGraph]): self._graph = graph self._order = graph._order self._frontier_p = ffi.gc( From 2cbb27ea0f9f691925c253b76acc101f708cc109 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 29 Jun 2020 14:42:03 -0700 Subject: [PATCH 0380/1101] Prepare 11.15.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4ccb8d16..0d329b91 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.15.0 - 2020-06-29 +-------------------- Added - `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays. From 50df424d288a22d6ca55ebedb203fa9d0f8b239f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Fri, 24 Jul 2020 12:04:44 -0700 Subject: [PATCH 0381/1101] Use absolute names for hints in EventDispatch methods. Helps with IDE auto-completes otherwise you'd need to rename the type hints or import all of TCOD's events. --- CHANGELOG.rst | 3 +++ tcod/event.py | 54 ++++++++++++++++++++++++++++----------------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d329b91..ea61000d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - `tcod.event.EventDispatch` now uses the absolute names for event type hints + so that IDE's can better auto-complete method overrides. 11.15.0 - 2020-06-29 -------------------- diff --git a/tcod/event.py b/tcod/event.py index fe141746..d1d871d4 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -888,79 +888,85 @@ def event_wait(self, timeout: Optional[float]) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: Quit) -> Optional[T]: + def ev_quit(self, event: "tcod.event.Quit") -> Optional[T]: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: KeyDown) -> Optional[T]: + def ev_keydown(self, event: "tcod.event.KeyDown") -> Optional[T]: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: KeyUp) -> Optional[T]: + def ev_keyup(self, event: "tcod.event.KeyUp") -> Optional[T]: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: MouseMotion) -> Optional[T]: + def ev_mousemotion(self, event: "tcod.event.MouseMotion") -> Optional[T]: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: MouseButtonDown) -> Optional[T]: + def ev_mousebuttondown( + self, event: "tcod.event.MouseButtonDown" + ) -> Optional[T]: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: MouseButtonUp) -> Optional[T]: + def ev_mousebuttonup(self, event: "tcod.event.MouseButtonUp") -> Optional[T]: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: MouseWheel) -> Optional[T]: + def ev_mousewheel(self, event: "tcod.event.MouseWheel") -> Optional[T]: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: TextInput) -> Optional[T]: + def ev_textinput(self, event: "tcod.event.TextInput") -> Optional[T]: """Called to handle Unicode input.""" - def ev_windowshown(self, event: WindowEvent) -> Optional[T]: + def ev_windowshown(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is shown.""" - def ev_windowhidden(self, event: WindowEvent) -> Optional[T]: + def ev_windowhidden(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is hidden.""" - def ev_windowexposed(self, event: WindowEvent) -> Optional[T]: + def ev_windowexposed(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`tcod.console_flush` is necessary. """ - def ev_windowmoved(self, event: WindowMoved) -> Optional[T]: + def ev_windowmoved(self, event: "tcod.event.WindowMoved") -> Optional[T]: """Called when the window is moved.""" - def ev_windowresized(self, event: WindowResized) -> Optional[T]: + def ev_windowresized(self, event: "tcod.event.WindowResized") -> Optional[T]: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: WindowResized) -> Optional[T]: + def ev_windowsizechanged( + self, event: "tcod.event.WindowResized" + ) -> Optional[T]: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: WindowEvent) -> Optional[T]: + def ev_windowminimized(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: WindowEvent) -> Optional[T]: + def ev_windowmaximized(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is maximized.""" - def ev_windowrestored(self, event: WindowEvent) -> Optional[T]: + def ev_windowrestored(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is restored.""" - def ev_windowenter(self, event: WindowEvent) -> Optional[T]: + def ev_windowenter(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: WindowEvent) -> Optional[T]: + def ev_windowleave(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: WindowEvent) -> Optional[T]: + def ev_windowfocusgained( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: WindowEvent) -> Optional[T]: + def ev_windowfocuslost(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: WindowEvent) -> Optional[T]: + def ev_windowclose(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: WindowEvent) -> Optional[T]: + def ev_windowtakefocus(self, event: "tcod.event.WindowEvent") -> Optional[T]: pass - def ev_windowhittest(self, event: WindowEvent) -> Optional[T]: + def ev_windowhittest(self, event: "tcod.event.WindowEvent") -> Optional[T]: pass def ev_(self, event: Any) -> Optional[T]: From 9bdb4f052c49420a8fc83fb5cb5ab72ef22a8b51 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 25 Jul 2020 19:04:06 -0700 Subject: [PATCH 0382/1101] Fix font columns in the getting started example. --- docs/tcod/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 956e941b..3aa1bf32 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -32,7 +32,7 @@ Example:: """Script entry point.""" # Load the font, a 64 by 8 tile font with libtcod's old character layout. tileset = tcod.tileset.load_tilesheet( - "dejavu10x10_gs_tc.png", 64, 8, tcod.tileset.CHARMAP_TCOD, + "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD, ) # Create the main console. console = tcod.Console(WIDTH, HEIGHT) From 54d162b5ffb0809a460149fe6aedcbcd9b36aba2 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sat, 25 Jul 2020 19:14:20 -0700 Subject: [PATCH 0383/1101] Update comments of the previous commit. --- docs/tcod/getting-started.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 3aa1bf32..416c49eb 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -30,7 +30,7 @@ Example:: def main() -> None: """Script entry point.""" - # Load the font, a 64 by 8 tile font with libtcod's old character layout. + # Load the font, a 32 by 8 tile font with libtcod's old character layout. tileset = tcod.tileset.load_tilesheet( "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD, ) From 5fb882d900aa9381016794aac27b98361da58433 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Jul 2020 01:32:01 -0700 Subject: [PATCH 0384/1101] Fix libtcodpy heightmap alignment. --- CHANGELOG.rst | 3 +++ tcod/libtcodpy.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea61000d..1aa58c15 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,9 @@ Changed - `tcod.event.EventDispatch` now uses the absolute names for event type hints so that IDE's can better auto-complete method overrides. +Fixed + - Fixed libtcodpy heightmap data alignment issues on non-square maps. + 11.15.0 - 2020-06-29 -------------------- Added diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 5ea00190..3058a54b 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2386,7 +2386,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: raise ValueError("array must be a contiguous segment.") if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) - width, height = array.shape + height, width = array.shape pointer = ffi.from_buffer("float *", array) return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) From 873111abbbbb40955042d72aa8ba43ab051aa2b3 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Jul 2020 02:57:23 -0700 Subject: [PATCH 0385/1101] Prepare 11.15.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1aa58c15..9c946de9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.15.1 - 2020-07-26 +-------------------- Changed - `tcod.event.EventDispatch` now uses the absolute names for event type hints so that IDE's can better auto-complete method overrides. From 2acf2245bfb07b90940379f729a0667ea8e0ab43 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Sun, 26 Jul 2020 22:26:54 -0700 Subject: [PATCH 0386/1101] Update FOV sample. --- examples/samples_tcod.py | 161 ++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 87 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index dfcd5987..0c056c56 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -537,31 +537,26 @@ class FOVSample(Sample): def __init__(self): self.name = "Field of view" - self.px = 20 - self.py = 10 - self.recompute = True + self.player_x = 20 + self.player_y = 10 self.torch = False - self.map = None - self.noise = None - self.torchx = 0.0 self.light_walls = True self.algo_num = 0 - # 1d noise for the torch flickering - self.noise = tcod.noise_new(1, 1.0, 1.0) + self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering. - self.map = tcod.map.Map( - SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F" - ) - self.map.walkable[:] = SAMPLE_MAP[:] == " " - self.map.transparent[:] = self.map.walkable[:] | (SAMPLE_MAP == "=") + map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) - self.light_map_bg = np.full( - SAMPLE_MAP.shape + (3,), LIGHT_GROUND, dtype=np.uint8 - ) + self.walkable = np.zeros(map_shape, dtype=bool, order="F") + self.walkable[:] = SAMPLE_MAP[:] == " " + + self.transparent = np.zeros(map_shape, dtype=bool, order="F") + self.transparent[:] = self.walkable[:] | (SAMPLE_MAP == "=") + + # Lit background colors for the map. + self.light_map_bg = np.full(SAMPLE_MAP.shape, LIGHT_GROUND, dtype="3B") self.light_map_bg[SAMPLE_MAP[:] == "#"] = LIGHT_WALL - self.dark_map_bg = np.full( - SAMPLE_MAP.shape + (3,), DARK_GROUND, dtype=np.uint8 - ) + # Dark background colors for the map. + self.dark_map_bg = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B") self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL def draw_ui(self): @@ -583,64 +578,64 @@ def draw_ui(self): def on_enter(self): tcod.sys_set_fps(60) - # we draw the foreground only the first time. - # during the player movement, only the @ is redrawn. - # the rest impacts only the background color - # draw the help text & player @ + + def on_draw(self): sample_console.clear() + # Draw the help text & player @. self.draw_ui() - tcod.console_put_char( - sample_console, self.px, self.py, "@", tcod.BKGND_NONE + sample_console.print(self.player_x, self.player_y, "@") + # Draw windows. + sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = tcod.CHAR_DHLINE + sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = tcod.black + + # Get a 2D boolean array of visible cells. + fov = tcod.map.compute_fov( + transparency=self.transparent, + pov=(self.player_x, self.player_y), + radius=TORCH_RADIUS if self.torch else 0, + light_walls=self.light_walls, + algorithm=self.algo_num, ) - # draw windows - sample_console.ch[np.where(SAMPLE_MAP == "=")] = tcod.CHAR_DHLINE - sample_console.fg[np.where(SAMPLE_MAP == "=")] = tcod.black - def on_draw(self): - dx = 0.0 - dy = 0.0 - di = 0.0 - if self.recompute: - self.recompute = False - self.map.compute_fov( - self.px, - self.py, - TORCH_RADIUS if self.torch else 0, - self.light_walls, - self.algo_num, - ) - sample_console.bg[:] = self.dark_map_bg[:] if self.torch: - # slightly change the perlin noise parameter - self.torchx += 0.1 - # randomize the light position between -1.5 and 1.5 - tdx = [self.torchx + 20.0] - dx = tcod.noise_get(self.noise, tdx, tcod.NOISE_SIMPLEX) * 1.5 - tdx[0] += 30.0 - dy = tcod.noise_get(self.noise, tdx, tcod.NOISE_SIMPLEX) * 1.5 - di = 0.2 * tcod.noise_get( - self.noise, [self.torchx], tcod.NOISE_SIMPLEX + # Derive the touch from noise based on the current time. + torch_t = time.perf_counter() * 5 + # Randomize the light position between -1.5 and 1.5 + torch_x = self.player_x + self.noise.get_point(torch_t) * 1.5 + torch_y = self.player_y + self.noise.get_point(torch_t + 11) * 1.5 + # Extra light brightness. + brightness = 0.2 * self.noise.get_point(torch_t + 17) + + # Get the squared distance using a mesh grid. + x, y = np.mgrid[:SAMPLE_SCREEN_WIDTH, :SAMPLE_SCREEN_HEIGHT] + # Center the mesh grid on the torch position. + x = x.astype(np.float32) - torch_x + y = y.astype(np.float32) - torch_y + + distance_squared = x ** 2 + y ** 2 # 2D squared distance array. + + # Get the currently visible cells. + visible = (distance_squared < SQUARED_TORCH_RADIUS) & fov + + # Invert the values, so that the center is the 'brightest' point. + light = SQUARED_TORCH_RADIUS - distance_squared + light /= SQUARED_TORCH_RADIUS # Convert into non-squared distance. + light += brightness # Add random brightness. + light.clip(0, 1, out=light) # Clamp values in-place. + light[~visible] = 0 # Set non-visible areas to darkness. + + # Setup background colors for floating point math. + light_bg = self.light_map_bg.astype(np.float16) + dark_bg = self.dark_map_bg.astype(np.float16) + + # Linear interpolation between colors. + sample_console.tiles_rgb["bg"] = ( + dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] ) - # where_fov = np.where(self.map.fov[:]) - mgrid = np.mgrid[:SAMPLE_SCREEN_WIDTH, :SAMPLE_SCREEN_HEIGHT] - # get squared distance - light = (mgrid[0] - self.px + dx) ** 2 + ( - mgrid[1] - self.py + dy - ) ** 2 - light = light.astype(np.float16) - visible = (light < SQUARED_TORCH_RADIUS) & self.map.fov[:] - light[...] = SQUARED_TORCH_RADIUS - light - light[...] /= SQUARED_TORCH_RADIUS - light[...] += di - light[...] = light.clip(0, 1) - light[~visible] = 0 - - sample_console.bg[...] = ( - self.light_map_bg.astype(np.float16) - self.dark_map_bg - ) * light[..., np.newaxis] + self.dark_map_bg else: - where_fov = np.where(self.map.fov[:]) - sample_console.bg[where_fov] = self.light_map_bg[where_fov] + sample_console.bg[...] = np.where( + fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg + ) def ev_keydown(self, event: tcod.event.KeyDown): MOVE_KEYS = { @@ -649,32 +644,24 @@ def ev_keydown(self, event: tcod.event.KeyDown): ord("k"): (0, 1), ord("l"): (1, 0), } - FOV_SELECT_KEYS = {ord("-"): -1, ord("="): 1} + FOV_SELECT_KEYS = { + ord("-"): -1, + ord("="): 1, + tcod.event.K_KP_MINUS: -1, + tcod.event.K_KP_PLUS: 1, + } if event.sym in MOVE_KEYS: x, y = MOVE_KEYS[event.sym] - if SAMPLE_MAP[self.px + x, self.py + y] == " ": - tcod.console_put_char( - sample_console, self.px, self.py, " ", tcod.BKGND_NONE - ) - self.px += x - self.py += y - tcod.console_put_char( - sample_console, self.px, self.py, "@", tcod.BKGND_NONE - ) - self.recompute = True + if self.walkable[self.player_x + x, self.player_y + y]: + self.player_x += x + self.player_y += y elif event.sym == ord("t"): self.torch = not self.torch - self.draw_ui() - self.recompute = True elif event.sym == ord("w"): self.light_walls = not self.light_walls - self.draw_ui() - self.recompute = True elif event.sym in FOV_SELECT_KEYS: self.algo_num += FOV_SELECT_KEYS[event.sym] self.algo_num %= tcod.NB_FOV_ALGORITHMS - self.draw_ui() - self.recompute = True else: super().ev_keydown(event) From c76fa32b70acf78724155e2873a3ef90aa3e5f9b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 27 Jul 2020 18:58:53 -0700 Subject: [PATCH 0387/1101] Fix missing case break in NumPy to C array handler. This would corrupt int8 NumPy arrays when written to from C. --- CHANGELOG.rst | 2 ++ tcod/path.c | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9c946de9..eb06750a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays. 11.15.1 - 2020-07-26 -------------------- diff --git a/tcod/path.c b/tcod/path.c index a09dfa20..13fd8c6a 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -92,6 +92,7 @@ static void set_array_int64( switch (arr->type) { case np_int8: *(int8_t*)ptr = (int8_t)value; + return; case np_int16: *(int16_t*)ptr = (int16_t)value; return; From f30a736ab55b5bd28b99e93a0f4bf9e98c65971e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Mon, 27 Jul 2020 19:53:12 -0700 Subject: [PATCH 0388/1101] Prepare 11.15.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eb06750a..8da28ced 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.15.2 - 2020-07-27 +-------------------- Fixed - `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays. From 5d6a3eeb2f99245a9facd94a9a58699a452393ad Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Jul 2020 02:22:36 -0700 Subject: [PATCH 0389/1101] Fix Tileset.remap method. --- CHANGELOG.rst | 2 ++ tcod/tileset.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8da28ced..e1e45c59 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - `tcod.tileset.Tileset.remap`, codepoint and index were swapped. 11.15.2 - 2020-07-27 -------------------- diff --git a/tcod/tileset.py b/tcod/tileset.py index 094aca31..f17b1c92 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -163,11 +163,18 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: .. versionadded:: 11.12 """ + tile_i = x + y * self._tileset_p.virtual_columns + if not (0 <= tile_i < self._tileset_p.tiles_count): + raise IndexError( + "Tile %i is non-existent and can't be assigned." + " (Tileset has %i tiles.)" + % (tile_i, self._tileset_p.tiles_count) + ) _check( lib.TCOD_tileset_assign_tile( self._tileset_p, + tile_i, codepoint, - x + y * self._tileset_p.virtual_columns, ) ) From acc83fdc509c290af9a841bca868851fc7330137 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65@gmail.com> Date: Thu, 30 Jul 2020 02:44:25 -0700 Subject: [PATCH 0390/1101] Prepare 11.15.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e1e45c59..fd0dd046 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.15.3 - 2020-07-30 +-------------------- Fixed - `tcod.tileset.Tileset.remap`, codepoint and index were swapped. From 88c56dcdc7f93f55d7858e4906f49006fbfbcd77 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Thu, 1 Oct 2020 05:12:33 -0700 Subject: [PATCH 0391/1101] Update PyPy jobs. Backported changes from python-esdl. --- .appveyor/install_python.ps1 | 4 ++-- appveyor.yml | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 index 7d35e900..c1213cc8 100644 --- a/.appveyor/install_python.ps1 +++ b/.appveyor/install_python.ps1 @@ -10,7 +10,7 @@ if ($env:PYPY -or $env:PYPY3) { $env:PYPY = $env:PYPY + '-win32' $env:PYTHON = 'C:\' + $env:PYPY + '\' + $env:PYPY_EXE $env:PATH += ';' + 'C:\' + $env:PYPY + '\' - $PYPY_DOWNLOAD = 'https://bitbucket.org/pypy/pypy/downloads/' + $env:PYPY + '.zip' + $PYPY_DOWNLOAD = 'https://downloads.python.org/pypy/' + $env:PYPY + '.zip' Invoke-WebRequest $PYPY_DOWNLOAD -OutFile C:\pypy.zip & '7z' x C:\pypy.zip -oC:\ & $env:PYTHON -m ensurepip @@ -22,6 +22,6 @@ if ($env:WEB_PYTHON) { $env:PYTHON = 'C:\UserPython\python.exe' } & $env:PYTHON -m pip install --disable-pip-version-check --no-warn-script-location "virtualenv>=20" -& $env:PYTHON -m virtualenv --download venv +& $env:PYTHON -m virtualenv venv if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } diff --git a/appveyor.yml b/appveyor.yml index 1b994e55..a2763617 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,11 +26,9 @@ environment: platform: x64 - PYTHON: C:\Python38\python.exe platform: Any CPU - - PYPY3: pypy3.6-v7.2.0 + - PYPY3: pypy3.6-v7.3.2 platform: Any CPU - - PYPY3: pypy3.6-v7.3.0 - platform: Any CPU - - PYPY3: pypy3.6-v7.3.1 + - PYPY3: pypy3.7-v7.3.2 platform: Any CPU matrix: @@ -48,7 +46,7 @@ install: - cmd: "set PATH=%APPVEYOR_BUILD_FOLDER%\\venv\\bin;%PATH%" - cmd: "set PATH=%PATH%;C:\\MinGW\\bin" - cmd: "python --version" -- cmd: "pip --version" +- cmd: "python -m pip install --upgrade pip" - cmd: "pip install --requirement requirements.txt" - cmd: "python -O setup.py build sdist develop bdist_wheel %EXTRA_SETUP_ARGS%" build: off From 4b2be31130f491d4af95bdb2a7fd5d96a0c32896 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Thu, 1 Oct 2020 05:23:32 -0700 Subject: [PATCH 0392/1101] Fix PyPy download URL. --- .travis/install_python.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/install_python.sh b/.travis/install_python.sh index 67c22ea9..f20a7f8a 100755 --- a/.travis/install_python.sh +++ b/.travis/install_python.sh @@ -38,7 +38,7 @@ set -e shell_session_update() { :; } MACPYTHON_URL=https://www.python.org/ftp/python -PYPY_URL=https://bitbucket.org/pypy/pypy/downloads +PYPY_URL=https://downloads.python.org/pypy MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions WORKING_SDIR=working DOWNLOADS_SDIR=/tmp From d8aec76911ee4eb14dc411df0a105d5365943717 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Thu, 1 Oct 2020 06:57:02 -0700 Subject: [PATCH 0393/1101] Update MacOS builds to use the latest PyPy versions. --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0d69456e..3c5a2ab8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,10 @@ jobs: env: MB_PYTHON_VERSION=3.7.1 - os: osx language: generic - env: PYPY3_VERSION=pypy3.6-v7.3.0 CUSTOM_REQUIREMENTS="numpy==1.18" + env: PYPY3_VERSION=pypy3.6-v7.3.2 CUSTOM_REQUIREMENTS="numpy==1.18" + - os: osx + language: generic + env: PYPY3_VERSION=pypy3.7-v7.3.2 CUSTOM_REQUIREMENTS="numpy==1.18" allow_failures: - python: nightly - python: pypy3.5-7.0 From a04ffd4b1016221254b879b766fa3f24b0dc53fa Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sat, 10 Oct 2020 09:05:10 -0700 Subject: [PATCH 0394/1101] Add missing bool type to tcod.path. --- CHANGELOG.rst | 2 ++ tcod/path.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fd0dd046..e3aa6b90 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Pathfinders will now work with boolean arrays. 11.15.3 - 2020-07-30 -------------------- diff --git a/tcod/path.py b/tcod/path.py index 3431de39..7756744c 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -289,6 +289,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: _INT_TYPES = { + np.bool_: lib.np_uint8, np.int8: lib.np_int8, np.int16: lib.np_int16, np.int32: lib.np_int32, @@ -324,6 +325,11 @@ def maxarray( def _export_dict(array: np.array) -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" + if array.dtype.type not in _INT_TYPES: + raise TypeError( + "dtype was %s, but must be one of %s." + % (array.dtype.type, tuple(_INT_TYPES.keys())) + ) return { "type": _INT_TYPES[array.dtype.type], "ndim": array.ndim, From 2f7c767b6ca9d9811cb0fbc7748fbd9a735d5347 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sat, 10 Oct 2020 09:11:27 -0700 Subject: [PATCH 0395/1101] Clean up formatting. Needed to keep up with changes to Black. --- tcod/_internal.py | 22 +++++++++++----------- tcod/console.py | 6 ++---- tcod/event.py | 28 +++++++++++++++++++++------- tcod/libtcodpy.py | 8 +++----- tcod/path.py | 11 ++++------- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/tcod/_internal.py b/tcod/_internal.py index d58c5d42..00da7e7b 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -130,11 +130,11 @@ def _fmt(string: str, stacklevel: int = 2) -> bytes: class _PropagateException: - """ context manager designed to propagate exceptions outside of a cffi - callback context. normally cffi suppresses the exception + """Context manager designed to propagate exceptions outside of a cffi + callback context. Normally cffi suppresses the exception. - when propagate is called this class will hold onto the error until the - control flow leaves the context, then the error will be raised + When propagate is called this class will hold onto the error until the + control flow leaves the context, then the error will be raised. with _PropagateException as propagate: # give propagate as onerror parameter for ffi.def_extern @@ -145,25 +145,25 @@ def __init__(self) -> None: self.exc_info = None # type: Any def propagate(self, *exc_info: Any) -> None: - """ set an exception to be raised once this context exits + """Set an exception to be raised once this context exits. - if multiple errors are caught, only keep the first exception raised + If multiple errors are caught, only keep the first exception raised. """ if not self.exc_info: self.exc_info = exc_info def __enter__(self) -> Callable[[Any], None]: - """ once in context, only the propagate call is needed to use this - class effectively + """Once in context, only the propagate call is needed to use this + class effectively. """ return self.propagate def __exit__(self, type: Any, value: Any, traceback: Any) -> None: - """ if we're holding on to an exception, raise it now + """If we're holding on to an exception, raise it now. - prefers our held exception over any current raising error + Prefers our held exception over any current raising error. - self.exc_info is reset now in case of nested manager shenanigans + self.exc_info is reset now in case of nested manager shenanigans. """ if self.exc_info: type, value, traceback = self.exc_info diff --git a/tcod/console.py b/tcod/console.py index ba583b70..f8effa8a 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -132,8 +132,7 @@ def __init__( @classmethod def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": - """Return a Console instance which wraps this `TCOD_Console*` object. - """ + """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): return cdata self = object.__new__(cls) # type: Console @@ -419,8 +418,7 @@ def __deprecate_defaults( alignment: Any = ..., clear: Any = ..., ) -> None: - """Return the parameters needed to recreate the current default state. - """ + """Return the parameters needed to recreate the current default state.""" if not __debug__: return diff --git a/tcod/event.py b/tcod/event.py index d1d871d4..d66f6670 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -905,7 +905,9 @@ def ev_mousebuttondown( ) -> Optional[T]: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: "tcod.event.MouseButtonUp") -> Optional[T]: + def ev_mousebuttonup( + self, event: "tcod.event.MouseButtonUp" + ) -> Optional[T]: """Called when a mouse button is released.""" def ev_mousewheel(self, event: "tcod.event.MouseWheel") -> Optional[T]: @@ -929,7 +931,9 @@ def ev_windowexposed(self, event: "tcod.event.WindowEvent") -> Optional[T]: def ev_windowmoved(self, event: "tcod.event.WindowMoved") -> Optional[T]: """Called when the window is moved.""" - def ev_windowresized(self, event: "tcod.event.WindowResized") -> Optional[T]: + def ev_windowresized( + self, event: "tcod.event.WindowResized" + ) -> Optional[T]: """Called when the window is resized.""" def ev_windowsizechanged( @@ -937,13 +941,19 @@ def ev_windowsizechanged( ) -> Optional[T]: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowminimized( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowmaximized( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: """Called when the window is maximized.""" - def ev_windowrestored(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowrestored( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: """Called when the window is restored.""" def ev_windowenter(self, event: "tcod.event.WindowEvent") -> Optional[T]: @@ -957,13 +967,17 @@ def ev_windowfocusgained( ) -> Optional[T]: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowfocuslost( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: """Called when the window loses keyboard focus.""" def ev_windowclose(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowtakefocus( + self, event: "tcod.event.WindowEvent" + ) -> Optional[T]: pass def ev_windowhittest(self, event: "tcod.event.WindowEvent") -> Optional[T]: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 3058a54b..b6deb00b 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3231,7 +3231,7 @@ def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: def line( xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool] ) -> bool: - """ Iterate over a line using a callback function. + """Iterate over a line using a callback function. Your callback function will take x and y parameters and return True to continue iteration or False to stop iteration and return. @@ -3263,7 +3263,7 @@ def line( @deprecate("This function has been replaced by tcod.los.bresenham.") def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: - """ returns an Iterable + """returns an Iterable This Iterable does not include the origin point. @@ -3627,9 +3627,7 @@ def noise_delete(n: tcod.noise.Noise) -> None: def _unpack_union(type_: int, union: Any) -> Any: - """ - unpack items from parser new_property (value_converter) - """ + """Unpack items from parser new_property (value_converter)""" if type_ == lib.TCOD_TYPE_BOOL: return bool(union.b) elif type_ == lib.TCOD_TYPE_CHAR: diff --git a/tcod/path.py b/tcod/path.py index 7756744c..e6df5b27 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -272,13 +272,11 @@ class Dijkstra(_PathFinder): _path_delete = lib.TCOD_dijkstra_delete def set_goal(self, x: int, y: int) -> None: - """Set the goal point and recompute the Dijkstra path-finder. - """ + """Set the goal point and recompute the Dijkstra path-finder.""" lib.TCOD_dijkstra_compute(self._path_c, x, y) def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: - """Return a list of (x, y) steps to reach the goal point, if possible. - """ + """Return a list of (x, y) steps to reach the goal point, if possible.""" lib.TCOD_dijkstra_path_set(self._path_c, x, y) path = [] pointer_x = ffi.new("int[2]") @@ -1210,8 +1208,7 @@ def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: lib.TCOD_frontier_push(self._frontier_p, index, value, value) def _update_heuristic(self, goal_ij: Optional[Tuple[int, ...]]) -> bool: - """Update the active heuristic. Return True if the heuristic changed. - """ + """Update the active heuristic. Return True if the heuristic changed.""" if goal_ij is None: heuristic = None elif self._graph._heuristic is None: @@ -1303,7 +1300,7 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: index = index[::-1] length = _check( lib.get_travel_path( - self._graph._ndim, self._travel_p, index, ffi.NULL, + self._graph._ndim, self._travel_p, index, ffi.NULL ) ) path = np.ndarray((length, self._graph._ndim), dtype=np.intc) From 625a5700661349c6a2d5437cf93fd46a80f0edfd Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sat, 10 Oct 2020 10:21:10 -0700 Subject: [PATCH 0396/1101] Add project_urls to setup.py. --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 6d77e800..92efc15a 100755 --- a/setup.py +++ b/setup.py @@ -135,6 +135,10 @@ def check_sdl_version(): description="Pythonic cffi port of libtcod.", long_description=get_long_description(), url="https://github.com/libtcod/python-tcod", + project_urls={ + "Documentation": "https://python-tcod.readthedocs.io", + "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.rst", + }, py_modules=["libtcodpy"], packages=["tdl", "tcod"], package_data={"tdl": ["*.png"], "tcod": get_package_data()}, From 4de36060ea9dc6cf70cff4b4cdc428fd282a8ee9 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 23 Oct 2020 12:51:29 -0700 Subject: [PATCH 0397/1101] Update libtcod to 1.16.0-alpha.13. New context and Context.present logic. --- CHANGELOG.rst | 20 +++++ build_libtcod.py | 3 + libtcod | 2 +- setup.py | 1 + tcod/cdef.h | 2 + tcod/context.py | 212 +++++++++++++++++++++++++++++++---------------- 6 files changed, 169 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e3aa6b90..f14dee84 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,28 @@ v2.0.0 Unreleased ------------------ +Added + - Added `tcod.context.new` function. + - Contexts now have CLI support. + - You can now provide the window x,y position when making contexts. + +Changed + - Using `libtcod 1.16.0-alpha.13`. + - The OpenGL 2 renderer can now use `SDL_HINT_RENDER_SCALE_QUALITY` to + determine the tileset upscaling filter. + - Improved performance of the FOV_BASIC algorithm. + +Deprecated + - `tcod.context.new_window` and `tcod.context.new_terminal` have been replaced + by `tcod.context.new`. + Fixed - Pathfinders will now work with boolean arrays. + - Console blits now ignore alpha compositing which would result in division by + zero. + - `tcod.console_is_key_pressed` should work even if libtcod events are ignored. + - The `TCOD_RENDERER` and `TCOD_VSYNC` environment variables should work now. + - `FOV_PERMISSIVE` algorithm is now reentrant. 11.15.3 - 2020-07-30 -------------------- diff --git a/build_libtcod.py b/build_libtcod.py index 94d4ba12..454342eb 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -356,6 +356,9 @@ def fix_header(filepath: str) -> None: for i, line in enumerate(include.header.split("\n"), 1): print("%03i %s" % (i, line)) raise +ffi.cdef(""" +#define TCOD_COMPILEDVERSION ... +""") ffi.set_source( module_name, "#include \n#include ", diff --git a/libtcod b/libtcod index b5142ee0..1f9a5b3d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit b5142ee02d942c45e6bf1d8e5bb5abbbcb318064 +Subproject commit 1f9a5b3d9a0855a0474f601a149150518b30eb46 diff --git a/setup.py b/setup.py index 92efc15a..7097ed1a 100755 --- a/setup.py +++ b/setup.py @@ -166,6 +166,7 @@ def check_sdl_version(): "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", diff --git a/tcod/cdef.h b/tcod/cdef.h index 4cfb6041..f3b75599 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -19,4 +19,6 @@ float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data); void _pycall_sdl_hook(struct SDL_Surface*); int _pycall_event_watch(void* userdata, union SDL_Event* event); + +void _pycall_cli_output(void* userdata, const char* output); } diff --git a/tcod/context.py b/tcod/context.py index 84c588d7..eeb945f1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -8,9 +8,9 @@ hidden global objects within libtcod. If you begin using contexts then most of these functions will no longer work properly. -Instead of calling :any:`tcod.console_init_root` you can call either -:any:`tcod.context.new_window` or :any:`tcod.context.new_terminal` depending -on how you plan to setup the size of the console. You should use +Instead of calling :any:`tcod.console_init_root` you can call +:any:`tcod.context.new` with different keywords depending on how you plan +to setup the size of the console. You should use :any:`tcod.tileset` to load the font for a context. .. note:: @@ -50,10 +50,10 @@ import sys import os -from typing import Any, Optional, Tuple +from typing import Any, Iterable, List, Optional, Tuple import tcod -from tcod._internal import _check, _check_warn +from tcod._internal import _check, _check_warn, deprecate from tcod.loader import ffi, lib import tcod.event import tcod.tileset @@ -129,11 +129,14 @@ def _handle_tileset(tileset: Optional[tcod.tileset.Tileset]) -> Any: return tileset._tileset_p if tileset else ffi.NULL -def _handle_title(title: Optional[str]) -> str: - """Return title, or if title is None then return a decent default title.""" +def _handle_title(title: Optional[str]) -> Any: + """Return title as a CFFI string. + + If title is None then return a decent default title is returned. + """ if title is None: title = os.path.basename(sys.argv[0]) - return title + return ffi.new("char[]", title.encode("utf-8")) class Context: @@ -197,20 +200,22 @@ def present( upper-left corner. Values of 0.5 will center the console. """ clear_rgba = (clear_color[0], clear_color[1], clear_color[2], 255) - options = { - "keep_aspect": keep_aspect, - "integer_scaling": integer_scaling, - "clear_color": clear_rgba, - "align_x": align[0], - "align_y": align[1], - } - console_p = console.console_c - with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts: - _check( - lib.TCOD_context_present( - self._context_p, console_p, viewport_opts - ) + viewport_args = ffi.new( + "TCOD_ViewportOptions*", + { + "tcod_version": lib.TCOD_COMPILEDVERSION, + "keep_aspect": keep_aspect, + "integer_scaling": integer_scaling, + "clear_color": clear_rgba, + "align_x": align[0], + "align_y": align[1], + }, + ) + _check( + lib.TCOD_context_present( + self._context_p, console.console_c, viewport_args ) + ) def pixel_to_tile(self, x: int, y: int) -> Tuple[int, int]: """Convert window pixel coordinates to tile coordinates.""" @@ -316,19 +321,39 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: return lib.TCOD_context_get_sdl_window(self._context_p) -def new_window( - width: int, - height: int, +@ffi.def_extern() # type: ignore +def _pycall_cli_output(catch_reference: Any, output: Any) -> None: + """Callback for the libtcod context CLI. Catches the CLI output.""" + catch = ffi.from_handle(catch_reference) # type: List[str] + catch.append(ffi.string(output).decode("utf-8")) + + +def new( *, + x: Optional[int] = None, + y: Optional[int] = None, + width: Optional[int] = None, + height: Optional[int] = None, + columns: Optional[int] = None, + rows: Optional[int] = None, renderer: Optional[int] = None, tileset: Optional[tcod.tileset.Tileset] = None, vsync: bool = True, sdl_window_flags: Optional[int] = None, - title: Optional[str] = None + title: Optional[str] = None, + argv: Optional[Iterable[str]] = None ) -> Context: """Create a new context with the desired pixel size. - `width` and `height` is the desired pixel resolution of the window. + `x`, `y`, `width`, and `height` are the desired position and size of the + window. If these are None then they will be derived from `columns` and + `rows`. So if you plan on having a console of a fixed size then you should + set `columns` and `rows` instead of the window keywords. + + `columns` and `rows` is the desired size of the console. Can be left as + `None` when you're setting a context by a window size instead of a console. + + Providing no size information at all is also acceptable. `renderer` is the desired libtcod renderer to use. Typical options are :any:`tcod.context.RENDERER_OPENGL2` for a faster @@ -348,32 +373,95 @@ def new_window( `title` is the desired title of the window. - After the context is created you can use - :any:`Context.recommended_console_size` to figure out the size of the - console for the context. + `argv` these arguments are passed to libtcod and allow an end-user to make + last second changes such as forcing fullscreen or windowed mode, or + changing the libtcod renderer. + By default this will pass in `sys.argv` but you can disable this feature + by providing an empty list instead. + Certain commands such as ``-help`` will raise a SystemExit exception from + this function with the output message. + + When a window size is given instead of a console size then you can use + :any:`Context.recommended_console_size` to automatically find the size of + the console which should be used. + + .. versionadded:: 11.16 """ - context_pp = ffi.new("TCOD_Context**") if renderer is None: - renderer = RENDERER_SDL2 + renderer = RENDERER_OPENGL2 if sdl_window_flags is None: sdl_window_flags = SDL_WINDOW_RESIZABLE - tileset_p = _handle_tileset(tileset) - title = _handle_title(title) - _check_warn( - lib.TCOD_context_new_window( - width, - height, - renderer, - tileset_p, - vsync, - sdl_window_flags, - title.encode("utf-8"), - context_pp, - ) + if argv is None: + argv = sys.argv + argv_encoded = [ # Needs to be kept alive for argv_c. + ffi.new("char[]", arg.encode("utf-8")) for arg in argv + ] + argv_c = ffi.new("char*[]", argv_encoded) + + catch_msg = [] # type: List[str] + catch_handle = ffi.new_handle(catch_msg) # Keep alive. + + params = ffi.new( + "struct TCOD_ContextParams*", + { + "tcod_version": lib.TCOD_COMPILEDVERSION, + "x": x if x is not None else lib.SDL_WINDOWPOS_UNDEFINED, + "y": y if y is not None else lib.SDL_WINDOWPOS_UNDEFINED, + "pixel_width": width or 0, + "pixel_height": height or 0, + "columns": columns or 0, + "rows": rows or 0, + "renderer_type": renderer, + "tileset": _handle_tileset(tileset), + "vsync": vsync, + "sdl_window_flags": sdl_window_flags, + "window_title": _handle_title(title), + "argc": len(argv_c), + "argv": argv_c, + "cli_output": ffi.addressof(lib, "_pycall_cli_output"), + "cli_userdata": catch_handle, + }, ) + context_pp = ffi.new("TCOD_Context**") + error = lib.TCOD_context_new(params, context_pp) + if error == lib.TCOD_E_REQUIRES_ATTENTION: + raise SystemExit(catch_msg[0]) + _check_warn(error) return Context._claim(context_pp[0]) +@deprecate( + "Call tcod.context.new with width and height as keyword parameters." +) +def new_window( + width: int, + height: int, + *, + renderer: Optional[int] = None, + tileset: Optional[tcod.tileset.Tileset] = None, + vsync: bool = True, + sdl_window_flags: Optional[int] = None, + title: Optional[str] = None +) -> Context: + """Create a new context with the desired pixel size. + + .. deprecated:: 11.16 + :any:`tcod.context.new` provides more options, such as window position. + """ + return new( + width=width, + height=height, + renderer=renderer, + tileset=tileset, + vsync=vsync, + sdl_window_flags=sdl_window_flags, + title=title, + ) + + +@deprecate( + "Call tcod.context.new with columns and rows as keyword parameters." +) def new_terminal( columns: int, rows: int, @@ -386,31 +474,15 @@ def new_terminal( ) -> Context: """Create a new context with the desired console size. - `columns` and `rows` are the desired size of the console. - - The remaining parameters are the same as :any:`new_window`. - - You can use this instead of :any:`new_window` if you plan on using a - :any:`tcod.Console` of a fixed size. This function is the most similar to - :any:`tcod.console_init_root`. + .. deprecated:: 11.16 + :any:`tcod.context.new` provides more options. """ - context_pp = ffi.new("TCOD_Context**") - if renderer is None: - renderer = RENDERER_SDL2 - if sdl_window_flags is None: - sdl_window_flags = SDL_WINDOW_RESIZABLE - tileset_p = _handle_tileset(tileset) - title = _handle_title(title) - _check_warn( - lib.TCOD_context_new_terminal( - columns, - rows, - renderer, - tileset_p, - vsync, - sdl_window_flags, - title.encode("utf-8"), - context_pp, - ) + return new( + columns=columns, + rows=rows, + renderer=renderer, + tileset=tileset, + vsync=vsync, + sdl_window_flags=sdl_window_flags, + title=title, ) - return Context._claim(context_pp[0]) From db61d922d1c2457674e828a4313a3fc85c3e7395 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 23 Oct 2020 14:17:36 -0700 Subject: [PATCH 0398/1101] Add Noise indexing. This might be more intuitive with NumPy, and could replace all other forms of sampling. --- CHANGELOG.rst | 1 + tcod/noise.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f14dee84..5d766f71 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Added - Added `tcod.context.new` function. - Contexts now have CLI support. - You can now provide the window x,y position when making contexts. + - `tcod.noise.Noise` instances can now be indexed to generate noise maps. Changed - Using `libtcod 1.16.0-alpha.13`. diff --git a/tcod/noise.py b/tcod/noise.py index c95130bd..b9efa4e5 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -157,6 +157,61 @@ def get_point( """ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) + def __getitem__(self, indexes: Any) -> np.ndarray: + """Sample a noise map through NumPy indexing. + + This follows NumPy's advanced indexing rules, but allows for floating + point values. + + .. versionadded:: 11.16 + """ + if not isinstance(indexes, tuple): + indexes = (indexes,) + if len(indexes) > self.dimensions: + raise IndexError( + "This noise generator has %i dimensions, but was indexed with %i." + % (self.dimensions, len(indexes)) + ) + indexes = np.broadcast_arrays(*indexes) + c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] + for i, index in enumerate(indexes): + if index.dtype.type == np.object_: + raise TypeError("Index arrays can not be of dtype np.object_.") + indexes[i] = np.ascontiguousarray(index, dtype=np.float32) + c_input[i] = ffi.from_buffer("float*", indexes[i]) + + out = np.empty(indexes[0].shape, dtype=np.float32) + if self.implementation == SIMPLE: + lib.TCOD_noise_get_vectorized( + self.noise_c, + self.algorithm, + out.size, + *c_input, + ffi.from_buffer("float*", out), + ) + elif self.implementation == SIMPLE: + lib.TCOD_noise_get_fbm_vectorized( + self.noise_c, + self.algorithm, + self.octaves, + out.size, + *c_input, + ffi.from_buffer("float*", out), + ) + elif self.implementation == TURBULENCE: + lib.TCOD_noise_get_turbulence_vectorized( + self.noise_c, + self.algorithm, + self.octaves, + out.size, + *c_input, + ffi.from_buffer("float*", out), + ) + else: + raise TypeError("Unexpected %r" % self.implementation) + + return out + def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray: """Sample a mesh-grid array and return the result. From 52a573863457959248ede0b696f41df522c7047f Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 23 Oct 2020 15:41:04 -0700 Subject: [PATCH 0399/1101] Prepare 11.16.0 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5d766f71..6b472a1b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,9 +8,12 @@ v2.0.0 Unreleased ------------------ + +11.16.0 - 2020-10-23 +-------------------- Added - Added `tcod.context.new` function. - - Contexts now have CLI support. + - Contexts now support a CLI. - You can now provide the window x,y position when making contexts. - `tcod.noise.Noise` instances can now be indexed to generate noise maps. From b0c8d84f9e65170af86c38d9e09ca6dfa2a569c4 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sun, 25 Oct 2020 14:15:18 -0700 Subject: [PATCH 0400/1101] Convert path example into doctest, add more examples. --- tcod/path.py | 109 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index e6df5b27..42fa42f6 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1159,13 +1159,26 @@ def traversal(self) -> np.ndarray: Example:: # This example demonstrates the purpose of the traversal array. - # In real code Pathfinder.path_from(...) should be used instead. - pf # Resolved 2D Pathfinder instance. - i, j = (3, 3) # Starting index. - path = [(i, j)] # List of nodes from the start to the root. - while not (pf.traversal[i, j] == (i, j)).all(): - i, j = pf.traversal[i, j] - path.append((i, j)) + >>> graph = tcod.path.SimpleGraph( + ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, + ... ) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.resolve() + >>> pf.traversal[3, 3].tolist() # Faster. + [2, 2] + >>> pf.path_from((3, 3))[1].tolist() # Slower. + [2, 2] + >>> i, j = (3, 3) # Starting index. + >>> path = [(i, j)] # List of nodes from the start to the root. + >>> while not (pf.traversal[i, j] == (i, j)).all(): + ... i, j = pf.traversal[i, j] + ... path.append((i, j)) + >>> path # Slower. + [(3, 3), (2, 2), (1, 1), (0, 0)] + >>> pf.path_from((3, 3)).tolist() # Faster. + [[3, 3], [2, 2], [1, 1], [0, 0]] + The above example is slow and will not detect infinite loops. Use :any:`path_from` or :any:`path_to` when you need to get a path. @@ -1230,9 +1243,13 @@ def _update_heuristic(self, goal_ij: Optional[Tuple[int, ...]]) -> bool: def rebuild_frontier(self) -> None: """Reconstruct the frontier using the current distance array. - This is needed if the :any:`distance` array is changed manually. + If you are using :any:`add_root` then you will not need to call this + function. This is only needed if the :any:`distance` array has been + modified manually. + After you are finished editing :any:`distance` you must call this - function before calling :any:`resolve`, :any:`path_from`, etc. + function before calling :any:`resolve` or any function which calls + :any:`resolve` implicitly such as :any:`path_from` or :any:`path_to`. """ lib.TCOD_frontier_clear(self._frontier_p) self._update_heuristic(None) @@ -1254,8 +1271,38 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: If `goal` is given an index then it will attempt to resolve the :any:`distance` and :any:`traversal` arrays only up to the `goal`. - If the graph has set a heuristic then it will be used and this call - will be similar to `A*`. + If the graph has set a heuristic then it will be used with a process + similar to `A*`. + + Example:: + + >>> graph = tcod.path.SimpleGraph( + ... cost=np.ones((4, 4), np.int8), cardinal=2, diagonal=3, + ... ) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.distance + array([[2147483647, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647]]...) + >>> pf.add_root((0, 0)) + >>> pf.distance + array([[ 0, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647]]...) + >>> pf.resolve((1, 1)) # Resolve up to (1, 1) as A*. + >>> pf.distance # Partially resolved distance. + array([[ 0, 2, 6, 2147483647], + [ 2, 3, 5, 2147483647], + [ 6, 5, 6, 2147483647], + [2147483647, 2147483647, 2147483647, 2147483647]]...) + >>> pf.resolve() # Resolve the full graph as Dijkstra. + >>> pf.distance # Fully resolved distance. + array([[0, 2, 4, 6], + [2, 3, 5, 7], + [4, 5, 6, 8], + [6, 7, 8, 9]]...) """ if goal is not None: goal = tuple(goal) # Check for bad input. @@ -1289,7 +1336,24 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: A common usage is to slice off the starting point and convert the array into a list. - """ + + Example:: + + >>> cost = np.ones((5, 5), dtype=np.int8) + >>> cost[:, 3:] = 0 + >>> graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.path_from((2, 2)).tolist() + [[2, 2], [1, 1], [0, 0]] + >>> pf.path_from((2, 2))[1:].tolist() # Exclude the starting point by slicing the array. + [[1, 1], [0, 0]] + >>> pf.path_from((4, 4)).tolist() # Blocked paths will only have the index point. + [[4, 4]] + >>> pf.path_from((4, 4))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. + [] + + """ # noqa: E501 index = tuple(index) # Check for bad input. if len(index) != self._graph._ndim: raise TypeError( @@ -1319,5 +1383,24 @@ def path_to(self, index: Tuple[int, ...]) -> np.ndarray: See :any:`path_from`. This is an alias for ``path_from(...)[::-1]``. - """ + + This is the method to call when the root is an entity to move to a + position rather than a destination itself. + + Example:: + + >>> graph = tcod.path.SimpleGraph( + ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, + ... ) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.path_to((0, 0)).tolist() # This method always returns at least one point. + [[0, 0]] + >>> pf.path_to((3, 3)).tolist() # Always includes both ends on a valid path. + [[0, 0], [1, 1], [2, 2], [3, 3]] + >>> pf.path_to((3, 3))[1:].tolist() # Exclude the starting point by slicing the array. + [[1, 1], [2, 2], [3, 3]] + >>> pf.path_to((0, 0))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. + [] + """ # noqa: E501 return self.path_from(index)[::-1] From 286fa1c3c4921b1c143380636db8f24bea0f8e35 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sun, 25 Oct 2020 14:18:19 -0700 Subject: [PATCH 0401/1101] Add a new strict config option for MyPy. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index cf232986..8e34d073 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ warn_redundant_casts = True warn_unused_ignores = True warn_return_any = True implicit_reexport = False +strict_equality = True [mypy-numpy] ignore_missing_imports = True From a15da5f76c9f77096f79b9d19ca660a5663f7e99 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Tue, 27 Oct 2020 13:16:42 -0700 Subject: [PATCH 0402/1101] Change context deprecations to PendingDeprecationWarning. Since the main tutorial was using these functions I was going to have to deal with endless issues from people who don't know that deprecation warnings are an optional suggestion at their skill level. --- CHANGELOG.rst | 3 +++ tcod/context.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6b472a1b..0b5abc00 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Deprecated + - Changed context deprecations to PendingDeprecationWarning to reduce mass + panic from tutorial followers. 11.16.0 - 2020-10-23 -------------------- diff --git a/tcod/context.py b/tcod/context.py index eeb945f1..8c5607d3 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -53,7 +53,7 @@ from typing import Any, Iterable, List, Optional, Tuple import tcod -from tcod._internal import _check, _check_warn, deprecate +from tcod._internal import _check, _check_warn, pending_deprecate from tcod.loader import ffi, lib import tcod.event import tcod.tileset @@ -430,7 +430,7 @@ def new( return Context._claim(context_pp[0]) -@deprecate( +@pending_deprecate( "Call tcod.context.new with width and height as keyword parameters." ) def new_window( @@ -459,7 +459,7 @@ def new_window( ) -@deprecate( +@pending_deprecate( "Call tcod.context.new with columns and rows as keyword parameters." ) def new_terminal( From e4f0af28da24cf8fb1bb1bee4a16a2b8e9d7af73 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Wed, 28 Oct 2020 10:16:52 -0700 Subject: [PATCH 0403/1101] Fix dangling title pointers in contexts. The context parameters struct was taking a CFFI `char[]` object. This object owns its data but was not kept alive itself by the struct so it could be garbage collected before the parameters struct was used. Now this string is explicitly kept alive. Fixes #99 --- CHANGELOG.rst | 3 +++ tcod/context.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b5abc00..494f6534 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,9 @@ Deprecated - Changed context deprecations to PendingDeprecationWarning to reduce mass panic from tutorial followers. +Fixed + - Fixed garbled titles and crashing on some platforms. + 11.16.0 - 2020-10-23 -------------------- Added diff --git a/tcod/context.py b/tcod/context.py index 8c5607d3..06733068 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -401,6 +401,8 @@ def new( catch_msg = [] # type: List[str] catch_handle = ffi.new_handle(catch_msg) # Keep alive. + title_p = _handle_title(title) # Keep alive. + params = ffi.new( "struct TCOD_ContextParams*", { @@ -415,7 +417,7 @@ def new( "tileset": _handle_tileset(tileset), "vsync": vsync, "sdl_window_flags": sdl_window_flags, - "window_title": _handle_title(title), + "window_title": title_p, "argc": len(argv_c), "argv": argv_c, "cli_output": ffi.addressof(lib, "_pycall_cli_output"), From 550924e0dd8424f223b23c26aee3a9e1cb86876b Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Wed, 28 Oct 2020 10:34:39 -0700 Subject: [PATCH 0404/1101] Prepare 11.16.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 494f6534..b47ceb26 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.16.1 - 2020-10-28 +-------------------- Deprecated - Changed context deprecations to PendingDeprecationWarning to reduce mass panic from tutorial followers. From 67c41c99327094afbfc9d986a74a555775bac551 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 30 Oct 2020 14:05:14 -0700 Subject: [PATCH 0405/1101] Update libtcod, adds new FOV implementation. --- CHANGELOG.rst | 5 +++++ build_libtcod.py | 4 ++++ examples/samples_tcod.py | 3 ++- libtcod | 2 +- tcod/constants.py | 2 ++ tcod/map.py | 4 ++++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b47ceb26..0dfb0c49 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Added + - Add new FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. + +Changed + - Using `libtcod 1.16.0-alpha.14`. 11.16.1 - 2020-10-28 -------------------- diff --git a/build_libtcod.py b/build_libtcod.py index 454342eb..af2e691b 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -430,6 +430,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: "TCOD_MAJOR_VERSION", "TCOD_MINOR_VERSION", "TCOD_PATCHLEVEL", + "TCOD_COMPILEDVERSION", "TCOD_PATHFINDER_MAX_DIMENSIONS", "TCOD_KEY_TEXT_SIZE", "TCOD_NOISE_MAX_DIMENSIONS", @@ -440,6 +441,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: "TCOD_E_", "TCOD_HEAP_", "TCOD_LEX_", + "TCOD_CHARMAP_", ] @@ -452,6 +454,8 @@ def write_library_constants() -> None: all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): + # To exclude specific names use either EXCLUDE_CONSTANTS or + # EXCLUDE_CONSTANT_PREFIXES before editing this. if name.endswith("_"): continue if name in EXCLUDE_CONSTANTS: diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 0c056c56..c4b4469b 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -527,6 +527,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): "PERMISSIVE7", "PERMISSIVE8", "RESTRICTIVE", + "SYMMETRIC_SHADOWCAST", ] TORCH_RADIUS = 10 @@ -661,7 +662,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): self.light_walls = not self.light_walls elif event.sym in FOV_SELECT_KEYS: self.algo_num += FOV_SELECT_KEYS[event.sym] - self.algo_num %= tcod.NB_FOV_ALGORITHMS + self.algo_num %= len(FOV_ALGO_NAMES) else: super().ev_keydown(event) diff --git a/libtcod b/libtcod index 1f9a5b3d..1bd31396 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 1f9a5b3d9a0855a0474f601a149150518b30eb46 +Subproject commit 1bd313965d773441b472ad79ecbfd5bb066744cf diff --git a/tcod/constants.py b/tcod/constants.py index 50b53a88..681f8792 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -18,6 +18,7 @@ FOV_PERMISSIVE_8 = 11 FOV_RESTRICTIVE = 12 FOV_SHADOW = 2 +FOV_SYMMETRIC_SHADOWCAST = 13 KEY_0 = 24 KEY_1 = 25 KEY_2 = 26 @@ -515,6 +516,7 @@ "FOV_PERMISSIVE_8", "FOV_RESTRICTIVE", "FOV_SHADOW", + "FOV_SYMMETRIC_SHADOWCAST", "KEY_0", "KEY_1", "KEY_2", diff --git a/tcod/map.py b/tcod/map.py index 0ce3c4dd..d76ff57b 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -200,12 +200,16 @@ def compute_fov( * `tcod.FOV_PERMISSIVE(n)`: `n` starts at 0 (most restrictive) and goes up to 8 (most permissive.) * `tcod.FOV_RESTRICTIVE` + * `tcod.FOV_SYMMETRIC_SHADOWCAST` .. versionadded:: 9.3 .. versionchanged:: 11.0 The parameters `x` and `y` have been changed to `pov`. + .. versionchanged:: 11.17 + Added `tcod.FOV_SYMMETRIC_SHADOWCAST` option. + Example: >>> explored = np.zeros((3, 5), dtype=bool, order="F") >>> transparency = np.ones((3, 5), dtype=bool, order="F") From 8fc71f758e18c93fd66495c2d0fe2e52251a52ff Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 30 Oct 2020 15:13:51 -0700 Subject: [PATCH 0406/1101] Prepare 11.17.0 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0dfb0c49..dc5c5c77 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,11 @@ v2.0.0 Unreleased ------------------ + +11.17.0 - 2020-10-30 +-------------------- Added - - Add new FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. + - New FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. Changed - Using `libtcod 1.16.0-alpha.14`. From 7e36a57850b259dc2f47b327879786edceaab91e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Sat, 31 Oct 2020 14:40:19 -0700 Subject: [PATCH 0407/1101] Add lib/ffi to __all__ so that scripts can use them without MyPy complaining. --- tcod/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tcod/__init__.py b/tcod/__init__.py index 3673edb8..eaabe7d2 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -41,6 +41,8 @@ __all__ = [ # noqa: F405 "__version__", + "lib", + "ffi", "bsp", "color", "console", From c937f0fa1d349c7679a5045e1e709887f356f353 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 6 Nov 2020 15:12:22 -0800 Subject: [PATCH 0408/1101] Add source and issue tracker project URLs. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 7097ed1a..07c52da2 100755 --- a/setup.py +++ b/setup.py @@ -138,6 +138,8 @@ def check_sdl_version(): project_urls={ "Documentation": "https://python-tcod.readthedocs.io", "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.rst", + "Source": "https://github.com/libtcod/python-tcod", + "Tracker": "https://github.com/libtcod/python-tcod/issues", }, py_modules=["libtcodpy"], packages=["tdl", "tcod"], From 9eaf8e23ce31074c556c7c7f24a64365d3be1a03 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Fri, 6 Nov 2020 16:00:27 -0800 Subject: [PATCH 0409/1101] Update context examples. --- docs/tcod/getting-started.rst | 8 ++++---- examples/eventget.py | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 416c49eb..158e006b 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -37,8 +37,8 @@ Example:: # Create the main console. console = tcod.Console(WIDTH, HEIGHT) # Create a window based on this console and tileset. - with tcod.context.new_terminal( - console.width, console.height, tileset=tileset, + with tcod.context.new( # New window for a console of size columns×rows. + columns=console.width, rows=console.height, tileset=tileset, ) as context: while True: # Main loop, runs until SystemExit is raised. console.clear() @@ -88,8 +88,8 @@ Example:: def main() -> None: """Script entry point.""" - with tcod.context.new_window( - WIDTH, HEIGHT, sdl_window_flags=FLAGS + with tcod.context.new( # New window with pixel resolution of width×height. + width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: # Create the console based on the context. console = tcod.Console(*context.recommended_console_size()) diff --git a/examples/eventget.py b/examples/eventget.py index 204df8c2..cb2e7e5f 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -20,8 +20,8 @@ def main() -> None: event_log: List[str] = [] motion_desc = "" - with tcod.context.new_window( - WIDTH, HEIGHT, sdl_window_flags=FLAGS + with tcod.context.new( + width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: console = tcod.Console(*context.recommended_console_size()) while True: @@ -42,9 +42,7 @@ def main() -> None: if event.type == "QUIT": raise SystemExit() if event.type == "WINDOWRESIZED": - console = tcod.Console( - *context.recommended_console_size() - ) + console = tcod.Console(*context.recommended_console_size()) if event.type == "MOUSEMOTION": motion_desc = str(event) else: From 3c37c594ed6e3d713b9251bba65399a56a70507e Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Mon, 9 Nov 2020 22:02:25 -0800 Subject: [PATCH 0410/1101] Add Context.new_console method. Examples have been updated to use the new method, the older way to do this has been deprecated. --- CHANGELOG.rst | 6 ++++ docs/tcod/getting-started.rst | 20 ++++++++------ examples/eventget.py | 4 +-- libtcod | 2 +- tcod/context.py | 52 +++++++++++++++++++++++++++++------ 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc5c5c77..21c16a35 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Added + - New context method `Context.new_console`. + +Deprecated + - `Context.recommended_console_size` has been replaced with + `Context.new_console`. 11.17.0 - 2020-10-30 -------------------- diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 158e006b..62ebff83 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -61,7 +61,7 @@ Dynamically-sized console The next example shows a more advanced setup. A maximized window is created and the console is dynamically scaled to fit within it. If the window is -resized then the console will be replaced by a new dynamically sized console. +resized then the console will be resized to match it. Because a tileset wasn't manually loaded in this example an OS dependent fallback font will be used. This is useful for prototyping but it's not @@ -70,12 +70,17 @@ platforms. The `integer_scaling` parameter to :any:`Context.present` prevents the console from being slightly stretched, since the console will rarely be the prefect -size a small border will exist. +size a small border will exist. This border is black by default but can be +changed to another color. You'll need to consider things like the console being too small for your code to handle or the tiles being small compared to an extra large monitor -resolution. :any:`Context.recommended_console_size` can be given a minimum -size that it will never go below. +resolution. :any:`Context.new_console` can be given a minimum size that it +will never go below. + +You can call :any:`Context.new_console` every frame or only when the window +is resized. This example creates a new console every frame instead of +clearing the console every frame and replacing it only on resizing the window. Example:: @@ -91,10 +96,8 @@ Example:: with tcod.context.new( # New window with pixel resolution of width×height. width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: - # Create the console based on the context. - console = tcod.Console(*context.recommended_console_size()) while True: - console.clear() + console = context.new_console() console.print(0, 0, "Hello World") context.present(console, integer_scaling=True) @@ -104,8 +107,7 @@ Example:: if event.type == "QUIT": raise SystemExit() if event.type == "WINDOWRESIZED": - # Replace the console with one that fits the new resolution. - console = tcod.Console(*context.recommended_console_size()) + pass # The next call to context.new_console may return a different size. if __name__ == "__main__": diff --git a/examples/eventget.py b/examples/eventget.py index cb2e7e5f..a034ecd7 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -23,7 +23,7 @@ def main() -> None: with tcod.context.new( width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: - console = tcod.Console(*context.recommended_console_size()) + console = context.new_console() while True: # Display all event items. console.clear() @@ -42,7 +42,7 @@ def main() -> None: if event.type == "QUIT": raise SystemExit() if event.type == "WINDOWRESIZED": - console = tcod.Console(*context.recommended_console_size()) + console = context.new_console() if event.type == "MOUSEMOTION": motion_desc = str(event) else: diff --git a/libtcod b/libtcod index 1bd31396..69dd379e 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 1bd313965d773441b472ad79ecbfd5bb066744cf +Subproject commit 69dd379e187b937d721b96e24bd84ed26eec4e18 diff --git a/tcod/context.py b/tcod/context.py index 06733068..896958ba 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -53,7 +53,7 @@ from typing import Any, Iterable, List, Optional, Tuple import tcod -from tcod._internal import _check, _check_warn, pending_deprecate +from tcod._internal import _check, _check_warn, pending_deprecate, deprecate from tcod.loader import ffi, lib import tcod.event import tcod.tileset @@ -263,25 +263,61 @@ def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None: ) ) - def recommended_console_size( - self, min_columns: int = 1, min_rows: int = 1 - ) -> Tuple[int, int]: - """Return the recommended (columns, rows) of a console for this - context. + def new_console( + self, + min_columns: int = 1, + min_rows: int = 1, + magnification: float = 1.0, + ) -> tcod.console.Console: + """Return a new console sized for this context. - The times where it's the most useful to call this method are: + `min_columns` and `min_rows` are the minimum size to use for the new + console. + + `magnification` determines the apparent size of the tiles on the output + display. A `magnification` larger then 1.0 will output smaller + consoles, which will show as larger tiles when presented. + `magnification` must be greater than zero. + + The times where it is the most useful to call this method are: * After the context is created, even if the console was given a specific size. * After the :any:`change_tileset` method is called. * After any window resized event, or any manual resizing of the window. + .. versionadded:: 11.18 + """ + if magnification < 0: + raise ValueError( + "Magnification must be greater than zero. (Got %f)" + % magnification + ) + size = ffi.new("int[2]") + _check( + lib.TCOD_context_recommended_console_size( + self._context_p, magnification, size, size + 1 + ) + ) + width, height = max(min_columns, size[0]), max(min_rows, size[1]) + return tcod.console.Console(width, height) + + @deprecate("This method has been replaced by Context.new_console.") + def recommended_console_size( + self, min_columns: int = 1, min_rows: int = 1 + ) -> Tuple[int, int]: + """Return the recommended (columns, rows) of a console for this + context. + `min_columns`, `min_rows` are the lowest values which will be returned. + + .. deprecated:: + This method has been replaced by :any:`Context.new_console`. """ with ffi.new("int[2]") as size: _check( lib.TCOD_context_recommended_console_size( - self._context_p, size, size + 1 + self._context_p, 1.0, size, size + 1 ) ) return max(min_columns, size[0]), max(min_rows, size[1]) From 429fa5dc9adf298d413d6e85edb5f33ca302cf13 Mon Sep 17 00:00:00 2001 From: Kyle Stewart <4b796c65+github@gmail.com> Date: Mon, 9 Nov 2020 22:27:48 -0800 Subject: [PATCH 0411/1101] Add test jobs for Python 3.9. Stop deployment of non-ABI3 wheels on Windows. --- .travis.yml | 2 ++ appveyor.yml | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c5a2ab8..49fdd067 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ jobs: python: 3.7 - os: linux python: 3.8 + - os: linux + python: 3.9 - os: linux python: nightly - os: osx diff --git a/appveyor.yml b/appveyor.yml index a2763617..2fc820da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,5 @@ +image: Visual Studio 2019 + environment: TWINE_USERNAME: secure: sUo+lYht329nQC5JCxEB8w== @@ -16,16 +18,16 @@ environment: EXTRA_SETUP_ARGS: "--py-limited-api cp35" - PYTHON: C:\Python36-x64\python.exe platform: x64 - - PYTHON: C:\Python36\python.exe - platform: Any CPU + NO_DEPLOY: true - PYTHON: C:\Python37-x64\python.exe platform: x64 - - PYTHON: C:\Python37\python.exe - platform: Any CPU + NO_DEPLOY: true - PYTHON: C:\Python38-x64\python.exe platform: x64 - - PYTHON: C:\Python38\python.exe - platform: Any CPU + NO_DEPLOY: true + - PYTHON: C:\Python39-x64\python.exe + platform: x64 + NO_DEPLOY: true - PYPY3: pypy3.6-v7.3.2 platform: Any CPU - PYPY3: pypy3.7-v7.3.2 @@ -61,5 +63,5 @@ on_success: - if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml || cd . deploy_script: -- "if defined APPVEYOR_REPO_TAG_NAME pip install twine" -- "if defined APPVEYOR_REPO_TAG_NAME twine upload --skip-existing dist/*" +- "if defined APPVEYOR_REPO_TAG_NAME if not defined NO_DEPLOY pip install twine" +- "if defined APPVEYOR_REPO_TAG_NAME if not defined NO_DEPLOY twine upload --skip-existing dist/*" From c5eaba1bc33237f129092789535b6c54c8cbe684 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Nov 2020 15:57:46 -0800 Subject: [PATCH 0412/1101] Update libtcod. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- tcod/context.py | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 21c16a35..624cae66 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,9 @@ Unreleased Added - New context method `Context.new_console`. +Changed + - Using `libtcod 1.16.0-alpha.15`. + Deprecated - `Context.recommended_console_size` has been replaced with `Context.new_console`. diff --git a/libtcod b/libtcod index 69dd379e..838456f0 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 69dd379e187b937d721b96e24bd84ed26eec4e18 +Subproject commit 838456f03be9c3e0bc5f596e77b05e3388bebe27 diff --git a/tcod/context.py b/tcod/context.py index 896958ba..82353d44 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -443,8 +443,8 @@ def new( "struct TCOD_ContextParams*", { "tcod_version": lib.TCOD_COMPILEDVERSION, - "x": x if x is not None else lib.SDL_WINDOWPOS_UNDEFINED, - "y": y if y is not None else lib.SDL_WINDOWPOS_UNDEFINED, + "window_x": x if x is not None else lib.SDL_WINDOWPOS_UNDEFINED, + "window_y": y if y is not None else lib.SDL_WINDOWPOS_UNDEFINED, "pixel_width": width or 0, "pixel_height": height or 0, "columns": columns or 0, @@ -458,6 +458,7 @@ def new( "argv": argv_c, "cli_output": ffi.addressof(lib, "_pycall_cli_output"), "cli_userdata": catch_handle, + "window_xy_defined": True, }, ) context_pp = ffi.new("TCOD_Context**") From 05b6302ad2426668fc33cb5e982308394ff32fd5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Nov 2020 16:01:15 -0800 Subject: [PATCH 0413/1101] Undeprecate recommended console size method. There are still rare use cases for this function. Such as filtering the result to be divisible by 2. --- CHANGELOG.rst | 4 ---- tcod/context.py | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 624cae66..aa1c5ae9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,10 +14,6 @@ Added Changed - Using `libtcod 1.16.0-alpha.15`. -Deprecated - - `Context.recommended_console_size` has been replaced with - `Context.new_console`. - 11.17.0 - 2020-10-30 -------------------- Added diff --git a/tcod/context.py b/tcod/context.py index 82353d44..0d3d8883 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -302,7 +302,6 @@ def new_console( width, height = max(min_columns, size[0]), max(min_rows, size[1]) return tcod.console.Console(width, height) - @deprecate("This method has been replaced by Context.new_console.") def recommended_console_size( self, min_columns: int = 1, min_rows: int = 1 ) -> Tuple[int, int]: @@ -311,8 +310,8 @@ def recommended_console_size( `min_columns`, `min_rows` are the lowest values which will be returned. - .. deprecated:: - This method has been replaced by :any:`Context.new_console`. + If result is only used to create a new console then you may want to + call :any:`Context.new_console` instead. """ with ffi.new("int[2]") as size: _check( From 4a79dfa1779fbe0f6e5981394f175030576e9bd6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Nov 2020 18:17:14 -0800 Subject: [PATCH 0414/1101] Prepare 11.18.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa1c5ae9..125ff83b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.18.0 - 2020-11-13 +-------------------- Added - New context method `Context.new_console`. From 5cb861090ea5400d21ba0c875e6d85a37e1a0e37 Mon Sep 17 00:00:00 2001 From: Sergey N Date: Mon, 16 Nov 2020 01:02:12 +0100 Subject: [PATCH 0415/1101] Made context.new visible in docs --- tcod/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tcod/context.py b/tcod/context.py index 0d3d8883..62816de2 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -61,6 +61,7 @@ __all__ = ( "Context", + "new", "new_window", "new_terminal", "SDL_WINDOW_FULLSCREEN", From fc176c21cf737c7980f13a227e99ee8aa49fde97 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 30 Nov 2020 09:55:51 -0800 Subject: [PATCH 0416/1101] Pull fixes from libtcod. Hotfix for printing the Unicode PUA. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 125ff83b..c78d141e 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Code points from the Private Use Area will now print correctly. 11.18.0 - 2020-11-13 -------------------- diff --git a/libtcod b/libtcod index 838456f0..788e2682 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 838456f03be9c3e0bc5f596e77b05e3388bebe27 +Subproject commit 788e2682633758543cf87512f093a91dab4c251f From 34fee4b6026841420fbda42866c354c0e0a22eb6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 30 Nov 2020 10:00:30 -0800 Subject: [PATCH 0417/1101] Prepare 11.18.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c78d141e..2f80aea1 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.18.1 - 2020-11-30 +-------------------- Fixed - Code points from the Private Use Area will now print correctly. From b0eee10dfbe2ca033c95e77f03971706879fa450 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 01:03:59 -0800 Subject: [PATCH 0418/1101] Test package with GitHub Actions. --- .github/workflows/python-package.yml | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..5c017fdd --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,58 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9'] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Needed for git describe to find tags. + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install APT dependencies + run: | + sudo apt update + sudo apt install libsdl2-dev + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Initialize package + run: | + python setup.py check # Creates tcod/version.py. + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 tcod/ --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 tcod/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Check type hints with MyPy + run: | + mypy tcod/ + - name: Check formatting with Black + run: | + black --check tcod/ + - name: Build package. + run: | + python setup.py develop # Install the package in-place. + - name: Test with pytest + uses: GabrielBB/xvfb-action@v1 + with: + run: | + pytest From c4127f9b6ca9e709823577956df30b4b22338a8b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 07:50:01 -0800 Subject: [PATCH 0419/1101] Add GitHub Actions for MacOS. Needed to add a definition for MacOS to build the vendored zlib correctly. --- .github/workflows/python-package-macos.yml | 46 ++++++++++++++++++++++ .github/workflows/python-package.yml | 2 +- build_libtcod.py | 3 ++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/python-package-macos.yml diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml new file mode 100644 index 00000000..d1651134 --- /dev/null +++ b/.github/workflows/python-package-macos.yml @@ -0,0 +1,46 @@ +name: Python package (MacOS) + +on: [push, pull_request] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['macos-11.0'] + python-version: ['3.5'] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Needed for git describe to find tags. + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest pytest-cov pytest-benchmark delocate wheel twine + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Build package. + run: | + python setup.py sdist develop bdist_wheel --py-limited-api=cp35 + - name: Package binary files + run: | + delocate-wheel -v dist/*.whl + delocate-listdeps --all dist/*.whl + - name: Test with pytest + run: | + pytest --no-window + - name: Upload to PyPI + if: startsWith(github.ref, 'refs/tags/') # Only run on tags. + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing dist/* diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5c017fdd..bd5fdc73 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Python package (Linux) on: [push, pull_request] diff --git a/build_libtcod.py b/build_libtcod.py index af2e691b..d24877c0 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -287,6 +287,9 @@ def fix_header(filepath: str) -> None: extra_link_args += ["-rpath", "%s/.." % SDL2_BUNDLE_PATH] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] + # Fix "implicit declaration of function 'close'" in zlib. + define_macros.append(("HAVE_UNISTD_H", 1)) + if sys.platform not in ["win32", "darwin"]: extra_parse_args += ( subprocess.check_output( From dc12a87744d920bdbbd2430f8371009929aae532 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 11:06:25 -0800 Subject: [PATCH 0420/1101] Add Codecov steps to GitHub Actions. --- .github/workflows/python-package-macos.yml | 3 ++- .github/workflows/python-package.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index d1651134..03090aee 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -36,7 +36,8 @@ jobs: delocate-listdeps --all dist/*.whl - name: Test with pytest run: | - pytest --no-window + pytest --no-window --cov-report=xml + - uses: codecov/codecov-action@v1 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') # Only run on tags. env: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bd5fdc73..9cafacbc 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -55,4 +55,5 @@ jobs: uses: GabrielBB/xvfb-action@v1 with: run: | - pytest + pytest --cov-report=xml + - uses: codecov/codecov-action@v1 From 090d1487b1ff7b1e050cd91854d92dab13f84842 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 11:43:28 -0800 Subject: [PATCH 0421/1101] Remove pipe from XVFB action. This isn't a normal run argument so the pipe might have been messing up the action. --- .github/workflows/python-package.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9cafacbc..6923ef87 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -54,6 +54,5 @@ jobs: - name: Test with pytest uses: GabrielBB/xvfb-action@v1 with: - run: | - pytest --cov-report=xml + run: pytest --cov-report=xml - uses: codecov/codecov-action@v1 From 2c91d7ae61d5d84c61f97f4ea4bb7ebdb9eb0885 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 12:20:57 -0800 Subject: [PATCH 0422/1101] Begin using isort to organize imports. --- .github/workflows/python-package.yml | 7 ++- examples/cavegen.py | 2 +- .../distribution/PyInstaller/hello_world.py | 2 +- .../distribution/cx_Freeze/hello_world.py | 1 - examples/distribution/cx_Freeze/setup.py | 2 +- examples/eventget.py | 1 - examples/samples_libtcodpy.py | 5 +- examples/samples_tcod.py | 5 +- examples/thread_jobs.py | 6 +- pyproject.toml | 6 ++ tcod/__init__.py | 5 +- tcod/_internal.py | 4 +- tcod/bsp.py | 4 +- tcod/color.py | 2 +- tcod/console.py | 6 +- tcod/context.py | 8 +-- tcod/event.py | 7 +-- tcod/image.py | 2 +- tcod/libtcodpy.py | 57 ++++++++++--------- tcod/loader.py | 8 +-- tcod/map.py | 4 +- tcod/noise.py | 4 +- tcod/path.py | 4 +- tcod/random.py | 2 +- tcod/sdl.py | 2 +- tcod/tileset.py | 5 +- tests/conftest.py | 1 + tests/test_console.py | 2 +- tests/test_libtcodpy.py | 6 +- tests/test_noise.py | 1 + tests/test_parser.py | 1 + tests/test_random.py | 1 + tests/test_tcod.py | 2 +- tests/test_tdl.py | 13 ++--- tests/test_tdl_map.py | 4 +- 35 files changed, 100 insertions(+), 92 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6923ef87..69ee1220 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -31,7 +31,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black + python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black isort if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | @@ -48,6 +48,11 @@ jobs: - name: Check formatting with Black run: | black --check tcod/ + - name: Check import order + run: | + isort tcod/ --check --diff + isort tests/ --check --diff + isort examples/ --check --diff --thirdparty tcod - name: Build package. run: | python setup.py develop # Install the package in-place. diff --git a/examples/cavegen.py b/examples/cavegen.py index 1710cc85..a6f08d80 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -6,8 +6,8 @@ This will print the result to the console, so be sure to run this from the command line. """ -import scipy.signal # type: ignore import numpy as np # type: ignore +import scipy.signal # type: ignore def convolve(tiles: np.array, wall_rule: int = 5) -> np.array: diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/hello_world.py index efcb9434..a1a4df83 100755 --- a/examples/distribution/PyInstaller/hello_world.py +++ b/examples/distribution/PyInstaller/hello_world.py @@ -3,8 +3,8 @@ # copyright and related or neighboring rights for the "hello world" PyInstaller # example script. This work is published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ -import sys import os.path +import sys import tcod diff --git a/examples/distribution/cx_Freeze/hello_world.py b/examples/distribution/cx_Freeze/hello_world.py index 893045fb..7f91bbef 100755 --- a/examples/distribution/cx_Freeze/hello_world.py +++ b/examples/distribution/cx_Freeze/hello_world.py @@ -2,7 +2,6 @@ import tdl - WIDTH, HEIGHT = 80, 60 console = None diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py index 5a5e5219..4962268c 100755 --- a/examples/distribution/cx_Freeze/setup.py +++ b/examples/distribution/cx_Freeze/setup.py @@ -1,7 +1,7 @@ import sys -from cx_Freeze import setup, Executable +from cx_Freeze import Executable, setup # cx_Freeze options, see documentation. build_exe_options = { diff --git a/examples/eventget.py b/examples/eventget.py index a034ecd7..e325763b 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -9,7 +9,6 @@ import tcod - WIDTH, HEIGHT = 720, 480 FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 1bea91d2..cf1fa84c 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -6,10 +6,9 @@ # from __future__ import division -import sys -import os - import math +import os +import sys import warnings import tcod as libtcod diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index c4b4469b..f6bc1837 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -5,12 +5,11 @@ # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ -import sys -import os - import copy import math +import os import random +import sys import time import warnings diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index 7afe4a4f..8aa2b808 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -13,17 +13,15 @@ Typically the field-of-view tasks run good but not great, and the path-finding tasks run poorly. """ -import sys - import concurrent.futures import multiprocessing import platform +import sys import timeit -from typing import List, Tuple, Callable +from typing import Callable, List, Tuple import tcod - THREADS = multiprocessing.cpu_count() MAP_WIDTH = 100 diff --git a/pyproject.toml b/pyproject.toml index 543a9300..8c110f45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,3 +10,9 @@ requires = [ line-length = 79 py36 = false target-version = ["py35"] + +[tool.isort] +profile= "black" +py_version = "35" +skip_gitignore = true +line_length = 79 diff --git a/tcod/__init__.py b/tcod/__init__.py index eaabe7d2..1783abd6 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,9 +15,6 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ -from tcod.loader import lib, ffi, __sdl_version__ # noqa: F4 -from tcod.libtcodpy import * # noqa: F4 - from tcod import ( bsp, color, @@ -33,6 +30,8 @@ tileset, ) from tcod.console import Console # noqa: F401 +from tcod.libtcodpy import * # noqa: F4 +from tcod.loader import __sdl_version__, ffi, lib # noqa: F4 try: from tcod.version import __version__ diff --git a/tcod/_internal.py b/tcod/_internal.py index 00da7e7b..5f43f0b7 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,12 +1,12 @@ """This module internal helper functions used by the rest of the library. """ import functools -from typing import Any, AnyStr, Callable, NoReturn, TypeVar, cast import warnings +from typing import Any, AnyStr, Callable, NoReturn, TypeVar, cast import numpy as np -from tcod.loader import lib, ffi +from tcod.loader import ffi, lib FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) diff --git a/tcod/bsp.py b/tcod/bsp.py index 6f66a1af..485676e0 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -25,9 +25,9 @@ """ from typing import Any, Iterator, List, Optional, Tuple, Union # noqa: F401 -from tcod.loader import lib, ffi -from tcod._internal import deprecate import tcod.random +from tcod._internal import deprecate +from tcod.loader import ffi, lib class BSP(object): diff --git a/tcod/color.py b/tcod/color.py index 915109ee..35db9b39 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,8 +1,8 @@ """ """ -from typing import Any, List import warnings +from typing import Any, List from tcod._internal import deprecate from tcod.loader import lib diff --git a/tcod/console.py b/tcod/console.py index f8effa8a..7df58138 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -4,15 +4,15 @@ See :ref:`getting-started` for info on how to set those up. """ -from typing import Any, Optional, Tuple # noqa: F401 import warnings +from typing import Any, Optional, Tuple # noqa: F401 import numpy as np -import tcod.constants -from tcod.loader import ffi, lib import tcod._internal +import tcod.constants from tcod._internal import deprecate +from tcod.loader import ffi, lib def _fmt(string: str) -> bytes: diff --git a/tcod/context.py b/tcod/context.py index 62816de2..98cb7281 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -47,17 +47,15 @@ .. versionadded:: 11.12 """ # noqa: E501 -import sys import os - +import sys from typing import Any, Iterable, List, Optional, Tuple import tcod -from tcod._internal import _check, _check_warn, pending_deprecate, deprecate -from tcod.loader import ffi, lib import tcod.event import tcod.tileset - +from tcod._internal import _check, _check_warn, deprecate, pending_deprecate +from tcod.loader import ffi, lib __all__ = ( "Context", diff --git a/tcod/event.py b/tcod/event.py index d66f6670..9fe98481 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -25,19 +25,18 @@ Callable, Dict, Generic, + Iterator, Mapping, NamedTuple, Optional, - Iterator, Tuple, TypeVar, ) import tcod.event_constants -from tcod.loader import ffi, lib from tcod.event_constants import * # noqa: F4 -from tcod.event_constants import KMOD_SHIFT, KMOD_CTRL, KMOD_ALT, KMOD_GUI - +from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT +from tcod.loader import ffi, lib T = TypeVar("T") diff --git a/tcod/image.py b/tcod/image.py index 4b6e91e9..114e761d 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -12,8 +12,8 @@ import numpy as np import tcod.console -from tcod.loader import ffi, lib from tcod._internal import _console +from tcod.loader import ffi, lib class Image(object): diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b6deb00b..e135b58a 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1,10 +1,10 @@ """This module handles backward compatibility with the ctypes libtcodpy module. """ +import atexit import os import sys - -import atexit import threading +import warnings from typing import ( Any, AnyStr, @@ -18,35 +18,10 @@ Tuple, Union, ) -import warnings import numpy as np -from tcod.loader import ffi, lib - -from tcod.constants import * # noqa: F4 -from tcod.constants import ( - BKGND_DEFAULT, - BKGND_SET, - BKGND_ALPH, - BKGND_ADDA, - FONT_LAYOUT_ASCII_INCOL, - FOV_RESTRICTIVE, - FOV_PERMISSIVE_0, - NOISE_DEFAULT, - KEY_RELEASED, -) - -from tcod._internal import deprecate, pending_deprecate, _check, _check_warn - -from tcod._internal import _int, _unpack_char_p -from tcod._internal import _bytes, _unicode, _fmt -from tcod._internal import _CDataWrapper -from tcod._internal import _PropagateException -from tcod._internal import _console - import tcod.bsp -from tcod.color import Color import tcod.console import tcod.image import tcod.los @@ -54,6 +29,34 @@ import tcod.noise import tcod.path import tcod.random +from tcod._internal import ( + _bytes, + _CDataWrapper, + _check, + _check_warn, + _console, + _fmt, + _int, + _PropagateException, + _unicode, + _unpack_char_p, + deprecate, + pending_deprecate, +) +from tcod.color import Color +from tcod.constants import * # noqa: F4 +from tcod.constants import ( + BKGND_ADDA, + BKGND_ALPH, + BKGND_DEFAULT, + BKGND_SET, + FONT_LAYOUT_ASCII_INCOL, + FOV_PERMISSIVE_0, + FOV_RESTRICTIVE, + KEY_RELEASED, + NOISE_DEFAULT, +) +from tcod.loader import ffi, lib Bsp = tcod.bsp.BSP diff --git a/tcod/loader.py b/tcod/loader.py index c7dbe86c..43a2a0de 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -1,12 +1,12 @@ """This module handles loading of the libtcod cffi API. """ -import sys import os - -import cffi # type: ignore import platform +import sys from typing import Any # noqa: F401 +import cffi # type: ignore + from tcod import __path__ # type: ignore __sdl_version__ = "" @@ -108,7 +108,7 @@ def __str__(self) -> Any: lib = ffi = _Mock() else: verify_dependencies() - from tcod._libtcod import lib, ffi # type: ignore # noqa: F401 + from tcod._libtcod import ffi, lib # type: ignore # noqa: F401 __sdl_version__ = get_sdl_version() diff --git a/tcod/map.py b/tcod/map.py index d76ff57b..b23b9b22 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -2,14 +2,14 @@ """ -from typing import Any, Tuple import warnings +from typing import Any, Tuple import numpy as np -from tcod.loader import lib, ffi import tcod._internal import tcod.constants +from tcod.loader import ffi, lib class Map(object): diff --git a/tcod/noise.py b/tcod/noise.py index b9efa4e5..d0db3626 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -35,10 +35,10 @@ import numpy as np -from tcod._internal import deprecate -from tcod.loader import ffi, lib import tcod.constants import tcod.random +from tcod._internal import deprecate +from tcod.loader import ffi, lib """Noise implementation constants""" SIMPLE = 0 diff --git a/tcod/path.py b/tcod/path.py index 42fa42f6..86d3046a 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,9 +21,9 @@ import numpy as np -from tcod.loader import lib, ffi -from tcod._internal import _check import tcod.map # noqa: F401 +from tcod._internal import _check +from tcod.loader import ffi, lib @ffi.def_extern() # type: ignore diff --git a/tcod/random.py b/tcod/random.py index 316ee6d6..e516f222 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -8,8 +8,8 @@ import random from typing import Any, Hashable, Optional -from tcod.loader import ffi, lib import tcod.constants +from tcod.loader import ffi, lib MERSENNE_TWISTER = tcod.constants.RNG_MT COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC diff --git a/tcod/sdl.py b/tcod/sdl.py index 03b1919c..25996ce6 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -7,7 +7,7 @@ import numpy as np -from tcod.loader import lib, ffi +from tcod.loader import ffi, lib __all__ = ("Window",) diff --git a/tcod/tileset.py b/tcod/tileset.py index f17b1c92..a8aeebdd 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -10,16 +10,15 @@ `_ while continuing to use python-tcod's pathfinding and field-of-view algorithms. """ -import os - import itertools +import os from typing import Any, Iterable, Optional, Tuple import numpy as np -from tcod.loader import lib, ffi import tcod.console from tcod._internal import _check, _console, _raise_tcod_error, deprecate +from tcod.loader import ffi, lib class Tileset: diff --git a/tests/conftest.py b/tests/conftest.py index f752c831..25de2125 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ import tcod + def pytest_addoption(parser): parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") diff --git a/tests/test_console.py b/tests/test_console.py index dd299ff4..1cdea8b0 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -1,8 +1,8 @@ import pickle import numpy as np -from numpy import array import pytest +from numpy import array import tcod diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 90836ca8..6ab5115d 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -import pytest - import numpy import numpy as np -import tcod as libtcodpy +import pytest + import tcod +import tcod as libtcodpy def test_console_behaviour(console): diff --git a/tests/test_noise.py b/tests/test_noise.py index 9044de95..8ccc5b9f 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -6,6 +6,7 @@ import tdl + class NoiseTests(unittest.TestCase): def test_noise(self): diff --git a/tests/test_parser.py b/tests/test_parser.py index cc7c9934..a6a8d370 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -6,6 +6,7 @@ import tcod as libtcod + @pytest.mark.filterwarnings("ignore:.*") def test_parser(): print ('***** File Parser test *****') diff --git a/tests/test_random.py b/tests/test_random.py index afe16be0..f423289f 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -4,6 +4,7 @@ import tcod + def test_tcod_random(): rand = tcod.random.Random(tcod.random.COMPLEMENTARY_MULTIPLY_WITH_CARRY) assert 0 <= rand.randint(0, 100) <= 100 diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 1d15b40c..f5d1a7ef 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -5,8 +5,8 @@ import numpy as np import pytest +from common import raise_Exception, tcod -from common import tcod, raise_Exception import tcod.noise import tcod.path diff --git a/tests/test_tdl.py b/tests/test_tdl.py index ae782e93..9af4262d 100755 --- a/tests/test_tdl.py +++ b/tests/test_tdl.py @@ -1,13 +1,12 @@ #!/usr/bin/env python -import sys -import os - -import unittest -import random -import itertools import copy -import pickle import gc +import itertools +import os +import pickle +import random +import sys +import unittest import tdl diff --git a/tests/test_tdl_map.py b/tests/test_tdl_map.py index bf99335c..12979da4 100644 --- a/tests/test_tdl_map.py +++ b/tests/test_tdl_map.py @@ -1,11 +1,13 @@ #!/usr/bin/env python -import unittest import itertools +import unittest + import pytest import tdl + class MapTests(unittest.TestCase): MAP = ( From 95387ca4eed923badb98fb87c49938c4cfe8126c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 12:31:29 -0800 Subject: [PATCH 0423/1101] Remove references to OpenMP. --- .travis.yml | 1 - .travis/install_openmp.sh | 7 ------- README.rst | 2 +- build_libtcod.py | 18 ------------------ tcod/noise.c | 4 ---- 5 files changed, 1 insertion(+), 31 deletions(-) delete mode 100755 .travis/install_openmp.sh diff --git a/.travis.yml b/.travis.yml index 49fdd067..b800adfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,6 @@ services: before_install: - source .travis/before_install.sh -- 'if [[ "$TRAVIS_OS_NAME" == "osx" && "$USE_OPENMP" == "true" ]]; then source .travis/install_openmp.sh; fi' - source .travis/install_python.sh - 'wget https://bootstrap.pypa.io/get-pip.py' - python get-pip.py diff --git a/.travis/install_openmp.sh b/.travis/install_openmp.sh deleted file mode 100755 index ed440425..00000000 --- a/.travis/install_openmp.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -brew update -brew install llvm -export PATH=/usr/local/opt/llvm/bin:$PATH -export LDFLAGS=-L/usr/local/opt/llvm/lib $LDFLAGS -export CPPFLAGS=-I/usr/local/opt/llvm/include $CPPFLAGS -# Installs clang with OpenMP via brew diff --git a/README.rst b/README.rst index 9a511d25..6976a233 100755 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ For the most part it's just:: * Python 3.5+ * Windows, Linux, or MacOS X 10.9+. * On Windows, requires the Visual C++ runtime 2015 or later. -* On Linux, requires libsdl2 (2.0.5+) and libomp5 to run. +* On Linux, requires libsdl2 (2.0.5+). ========= License diff --git a/build_libtcod.py b/build_libtcod.py index d24877c0..4b85f0ae 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -307,12 +307,6 @@ def fix_header(filepath: str) -> None: .split() ) -# Can force the use of OpenMP with this variable. -try: - USE_OPENMP = eval(os.environ.get("USE_OPENMP", "False").title()) -except Exception: - USE_OPENMP = False - tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]} @@ -332,21 +326,9 @@ def fix_header(filepath: str) -> None: if sys.platform == "win32" and "--compiler=mingw32" not in sys.argv: extra_compile_args.extend(MSVC_CFLAGS[tdl_build]) extra_link_args.extend(MSVC_LDFLAGS[tdl_build]) - - if USE_OPENMP is None: - USE_OPENMP = sys.version_info[:2] >= (3, 5) - - if USE_OPENMP: - extra_compile_args.append("/openmp") else: extra_compile_args.extend(GCC_CFLAGS[tdl_build]) extra_link_args.extend(GCC_CFLAGS[tdl_build]) - if USE_OPENMP is None: - USE_OPENMP = sys.platform != "darwin" - - if USE_OPENMP: - extra_compile_args.append("-fopenmp") - extra_link_args.append("-fopenmp") ffi = FFI() parse_sdl2.add_to_ffi(ffi, SDL2_INCLUDE) diff --git a/tcod/noise.c b/tcod/noise.c index b5903402..38424406 100644 --- a/tcod/noise.c +++ b/tcod/noise.c @@ -20,10 +20,8 @@ void NoiseSampleMeshGrid( const long len, const float* __restrict in, float* __restrict out) { -#pragma omp parallel { long i; -#pragma omp for schedule(static) for (i = 0; i < len; ++i) { int axis; float xyzw[TCOD_NOISE_MAX_DIMENSIONS]; @@ -64,11 +62,9 @@ void NoiseSampleOpenMeshGrid( const long* __restrict shape, const float* __restrict* __restrict ogrid_in, float* __restrict out) { -#pragma omp parallel { long i; long len = GetSizeFromShape(ndim_in, shape); -#pragma omp for schedule(static) for (i = 0; i < len; ++i) { out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i); } From b9beef99f8376980c311789f00057d7914454a4d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Dec 2020 13:53:48 -0800 Subject: [PATCH 0424/1101] Remove MSVC runtime check. The code compiles from C now so this likely isn't needed anymore. The runtime bundled with Python should be enough. --- README.rst | 1 - tcod/loader.py | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/README.rst b/README.rst index 6976a233..feaa897e 100755 --- a/README.rst +++ b/README.rst @@ -50,7 +50,6 @@ For the most part it's just:: ============== * Python 3.5+ * Windows, Linux, or MacOS X 10.9+. -* On Windows, requires the Visual C++ runtime 2015 or later. * On Linux, requires libsdl2 (2.0.5+). ========= diff --git a/tcod/loader.py b/tcod/loader.py index 43a2a0de..9a210bb6 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -37,19 +37,6 @@ def verify_dependencies() -> None: raise RuntimeError( "Tried to load an old version of SDL %r" % (version,) ) - try: - ffi_check.dlopen("vcruntime140.dll") # Make sure VC++ 2015 exists. - except OSError: - print( - "You will need to install 'vc_redist.{arch}.exe'" - " from Microsoft at:\n" - "https://support.microsoft.com/en-us/help/2977003/" - "the-latest-supported-visual-c-downloads\n".format( - arch=get_architecture() - ), - file=sys.stderr, - ) - raise def get_architecture() -> str: From 251303fa8c095a5691c947108609957188c6b4c2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 03:08:27 -0800 Subject: [PATCH 0425/1101] Use official builds of Python on MacOS to generate portable wheels. --- .github/workflows/python-package-macos.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 03090aee..84cbac7f 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: ['macos-11.0'] - python-version: ['3.5'] + python-version: ['3.7.9'] steps: - uses: actions/checkout@v2 @@ -18,10 +18,19 @@ jobs: - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} + - name: Set up Mac Python ${{ matrix.python-version }} + # actions/setup-python can't be used as it builds less portable extensions. + env: + MB_PYTHON_VERSION: ${{ matrix.python-version }} + run: | + source .travis/install_python.sh + install_python + $PYTHON_EXE -m venv venv + source venv/bin/activate + echo "$PATH" >> $GITHUB_PATH + - name: Print Python version + run: | + python -VV - name: Install Python dependencies run: | python -m pip install --upgrade pip From 426ebe43e9e3f364f36e5562a7b8c5d4bc540639 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 14:29:54 -0800 Subject: [PATCH 0426/1101] Add missing FOV_SYMMETRIC_SHADOWCAST constant. Also adds an import from constants to make it less bad when __all__ isn't updated correctly. Fixes #101 --- CHANGELOG.rst | 2 ++ tcod/__init__.py | 2 ++ tcod/libtcodpy.py | 1 + 3 files changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f80aea1..47f35ad0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. 11.18.1 - 2020-11-30 -------------------- diff --git a/tcod/__init__.py b/tcod/__init__.py index 1783abd6..0574e7cc 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -31,6 +31,7 @@ ) from tcod.console import Console # noqa: F401 from tcod.libtcodpy import * # noqa: F4 +from tcod.constants import * # noqa: F4 from tcod.loader import __sdl_version__, ffi, lib # noqa: F4 try: @@ -325,6 +326,7 @@ "FOV_PERMISSIVE_8", "FOV_RESTRICTIVE", "FOV_SHADOW", + "FOV_SYMMETRIC_SHADOWCAST", "KEY_0", "KEY_1", "KEY_2", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index e135b58a..6e03e0f3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4667,6 +4667,7 @@ def _atexit_verify() -> None: "FOV_PERMISSIVE_8", "FOV_RESTRICTIVE", "FOV_SHADOW", + "FOV_SYMMETRIC_SHADOWCAST", "KEY_0", "KEY_1", "KEY_2", From be423db8c209ad088e64f9ad5bab7e05baf1ec90 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 14:52:42 -0800 Subject: [PATCH 0427/1101] Automatically update __all__ in more places with constants. Now I don't have to remember to do this, because I won't. --- build_libtcod.py | 16 ++++++++++++++++ tcod/__init__.py | 1 + tcod/libtcodpy.py | 1 + 3 files changed, 18 insertions(+) diff --git a/build_libtcod.py b/build_libtcod.py index 4b85f0ae..79aa7931 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -430,6 +430,20 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: ] +def update_module_all(filename: str, new_all: str) -> None: + """Update the __all__ of a file with the constants from new_all.""" + RE_CONSTANTS_ALL = re.compile( + r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)", + re.DOTALL, + ) + with open(filename, "r") as f: + match = RE_CONSTANTS_ALL.match(f.read()) + assert match, "Can't determine __all__ subsection in %s!" % (filename,) + header, footer = match.groups() + with open(filename, "w") as f: + f.write("%s\n %s,\n %s" % (header, new_all, footer)) + + def write_library_constants() -> None: """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi @@ -476,6 +490,8 @@ def write_library_constants() -> None: all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) + update_module_all("tcod/__init__.py", all_names) + update_module_all("tcod/libtcodpy.py", all_names) with open("tcod/event_constants.py", "w") as f: all_names = [] diff --git a/tcod/__init__.py b/tcod/__init__.py index 0574e7cc..55a01c1c 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -807,4 +807,5 @@ "violet", "white", "yellow", + # --- End constants.py --- ] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 6e03e0f3..8e2e53d2 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -5148,4 +5148,5 @@ def _atexit_verify() -> None: "violet", "white", "yellow", + # --- End constants.py --- ] From 4590f18975becff70635a9845b1fdfe098aa30a3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 15:01:05 -0800 Subject: [PATCH 0428/1101] Pull fixes to sys_get_current_resolution. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- tcod/libtcodpy.py | 11 +++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 47f35ad0..ad6b52b8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ Unreleased ------------------ Fixed - Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. + - Fixed regression in `tcod.sys_get_current_resolution` behavior. This + function now returns the monitor resolution as was previously expected. 11.18.1 - 2020-11-30 -------------------- diff --git a/libtcod b/libtcod index 788e2682..b20ca439 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 788e2682633758543cf87512f093a91dab4c251f +Subproject commit b20ca439155f72eb6ad5a063ab988f41d08298fb diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 8e2e53d2..e4fe6a26 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4217,15 +4217,14 @@ def sys_force_fullscreen_resolution(width: int, height: int) -> None: lib.TCOD_sys_force_fullscreen_resolution(width, height) -@deprecate("This function is not supported if contexts are being used.") +@deprecate( + "This function is deprecated, which monitor is detected is ambiguous." +) def sys_get_current_resolution() -> Tuple[int, int]: - """Return the current resolution as (width, height) - - Returns: - Tuple[int,int]: The current resolution. + """Return a monitors pixel resolution as (width, height). .. deprecated:: 11.13 - This function is not supported by contexts. + This function is deprecated, which monitor is detected is ambiguous. """ w = ffi.new("int *") h = ffi.new("int *") From 4df23a3eb5ffe77d0d5632c31717536a5cb70628 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 15:03:42 -0800 Subject: [PATCH 0429/1101] Use isort import order. --- tcod/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 55a01c1c..910f7eb8 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -30,8 +30,8 @@ tileset, ) from tcod.console import Console # noqa: F401 -from tcod.libtcodpy import * # noqa: F4 from tcod.constants import * # noqa: F4 +from tcod.libtcodpy import * # noqa: F4 from tcod.loader import __sdl_version__, ffi, lib # noqa: F4 try: From db1c5d474be19e86999a4a83576c1aae52b83a07 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Dec 2020 15:19:31 -0800 Subject: [PATCH 0430/1101] Prepare 11.18.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ad6b52b8..a7253161 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.18.2 - 2020-12-03 +-------------------- Fixed - Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. - Fixed regression in `tcod.sys_get_current_resolution` behavior. This From b46bd07b24ccd3d6926c8fad9e5079aab526a729 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Dec 2020 01:46:41 -0800 Subject: [PATCH 0431/1101] Try to use GitHub releases to sync deployment of MacOS. The previous tag method has failed since it didn't use the existing tag but the previous one. I need to look into this more. --- .github/workflows/python-package-macos.yml | 14 ++++++++++++-- setup.py | 4 +++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 84cbac7f..a5e07e15 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -1,6 +1,11 @@ name: Python package (MacOS) -on: [push, pull_request] +on: + push: + pull_request: + release: + types: + - created jobs: build: @@ -18,6 +23,9 @@ jobs: - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 + - name: Print git describe + run: | + git describe - name: Set up Mac Python ${{ matrix.python-version }} # actions/setup-python can't be used as it builds less portable extensions. env: @@ -37,6 +45,8 @@ jobs: python -m pip install pytest pytest-cov pytest-benchmark delocate wheel twine if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Build package. + env: + TCOD_TAG: ${{ github.event.release.tag_name }} run: | python setup.py sdist develop bdist_wheel --py-limited-api=cp35 - name: Package binary files @@ -48,7 +58,7 @@ jobs: pytest --no-window --cov-report=xml - uses: codecov/codecov-action@v1 - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') # Only run on tags. + if: github.event_name == 'release' env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/setup.py b/setup.py index 07c52da2..ed686cc9 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -import os.path +import os from setuptools import setup @@ -14,6 +14,8 @@ def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" + if os.environ.get("TCOD_TAG"): + return os.environ["TCOD_TAG"] try: tag = check_output( ["git", "describe", "--abbrev=0"], universal_newlines=True From f67830d69676278e905f303131214d8eec07a9d1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Dec 2020 01:46:41 -0800 Subject: [PATCH 0432/1101] Try to use GitHub releases to sync deployment of MacOS. The previous tag method has failed since it didn't use the existing tag but the previous one. I need to look into this more. --- .github/workflows/python-package-macos.yml | 14 ++++++++++++-- setup.py | 4 +++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 84cbac7f..a5e07e15 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -1,6 +1,11 @@ name: Python package (MacOS) -on: [push, pull_request] +on: + push: + pull_request: + release: + types: + - created jobs: build: @@ -18,6 +23,9 @@ jobs: - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 + - name: Print git describe + run: | + git describe - name: Set up Mac Python ${{ matrix.python-version }} # actions/setup-python can't be used as it builds less portable extensions. env: @@ -37,6 +45,8 @@ jobs: python -m pip install pytest pytest-cov pytest-benchmark delocate wheel twine if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Build package. + env: + TCOD_TAG: ${{ github.event.release.tag_name }} run: | python setup.py sdist develop bdist_wheel --py-limited-api=cp35 - name: Package binary files @@ -48,7 +58,7 @@ jobs: pytest --no-window --cov-report=xml - uses: codecov/codecov-action@v1 - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') # Only run on tags. + if: github.event_name == 'release' env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/setup.py b/setup.py index 07c52da2..ed686cc9 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -import os.path +import os from setuptools import setup @@ -14,6 +14,8 @@ def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" + if os.environ.get("TCOD_TAG"): + return os.environ["TCOD_TAG"] try: tag = check_output( ["git", "describe", "--abbrev=0"], universal_newlines=True From b6eaead0dbbceac13efee5ec810b0909c79ec803 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Dec 2020 08:04:51 -0800 Subject: [PATCH 0433/1101] Link to GitHub Discussions from the project URLs. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ed686cc9..4e20eed0 100755 --- a/setup.py +++ b/setup.py @@ -142,6 +142,7 @@ def check_sdl_version(): "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.rst", "Source": "https://github.com/libtcod/python-tcod", "Tracker": "https://github.com/libtcod/python-tcod/issues", + "Forum": "https://github.com/libtcod/python-tcod/discussions", }, py_modules=["libtcodpy"], packages=["tdl", "tcod"], From 3387f45df89454a0015498114dc4ec71b5ed1fe2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Dec 2020 08:15:37 -0800 Subject: [PATCH 0434/1101] Update setup.py. --- setup.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 4e20eed0..1e77c7db 100755 --- a/setup.py +++ b/setup.py @@ -103,22 +103,6 @@ def check_sdl_version(): ) -if sys.version_info < (3, 5): - error = """ - This version of python-tcod only supports Python 3.5 and above. - The last version supporting Python 2.7/3.4 was 'tcod==6.0.7'. - - The end-of-life for Python 2 is the year 2020. - https://pythonclock.org/ - - Python {py} detected. - """.format( - py=".".join([str(v) for v in sys.version_info[:3]]) - ) - - print(error) - sys.exit(1) - if not os.path.exists("libtcod/src"): print("Libtcod submodule is uninitialized.") print("Did you forget to run 'git submodule update --init'?") @@ -133,8 +117,8 @@ def check_sdl_version(): name="tcod", version=get_version(), author="Kyle Stewart", - author_email="4B796C65+tdl@gmail.com", - description="Pythonic cffi port of libtcod.", + author_email="4b796c65+tcod@gmail.com", + description="The official Python port of libtcod.", long_description=get_long_description(), url="https://github.com/libtcod/python-tcod", project_urls={ @@ -178,7 +162,7 @@ def check_sdl_version(): "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries :: Python Modules", ], - keywords="roguelike cffi Unicode libtcod fov heightmap namegen", + keywords="roguelike cffi Unicode libtcod field-of-view pathfinding", platforms=["Windows", "MacOS", "Linux"], license="Simplified BSD License", ) From 4e343cb9a41fc57acaf754b2007600164bf89963 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 23 Dec 2020 22:18:16 -0800 Subject: [PATCH 0435/1101] Fix REXPaint error handling and deprecate an old function. --- CHANGELOG.rst | 6 ++++++ tcod/_internal.py | 9 ++++++++- tcod/libtcodpy.py | 25 ++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7253161..26955584 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Deprecated + - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load + these files without modifying an existing console. + +Fixed + - `tcod.console_from_xp` now has better error handling (instead of crashing.) 11.18.2 - 2020-12-03 -------------------- diff --git a/tcod/_internal.py b/tcod/_internal.py index 5f43f0b7..7c39cdb6 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -63,12 +63,19 @@ def _raise_tcod_error() -> NoReturn: def _check(error: int) -> int: - """Detect and convert a libtcod error code it into an exception.""" + """Detect and convert a libtcod error code into an exception.""" if error < 0: _raise_tcod_error() return error +def _check_p(pointer: Any) -> Any: + """Treats NULL pointers as errors and raises a libtcod exception.""" + if not pointer: + _raise_tcod_error() + return pointer + + def _check_warn(error: int, stacklevel: int = 2) -> int: """Like _check, but raises a warning on positive error codes.""" if _check(error) > 0: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index e4fe6a26..9e7d9919 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -33,6 +33,7 @@ _bytes, _CDataWrapper, _check, + _check_p, _check_warn, _console, _fmt, @@ -1853,8 +1854,12 @@ def console_from_file(filename: str) -> tcod.console.Console: Returns: A new :any`Console` instance. """ + if not os.path.exists(filename): + raise RuntimeError( + "File not found:\n\t%s" % (os.path.realpath(filename),) + ) return tcod.console.Console._from_cdata( - lib.TCOD_console_from_file(filename.encode("utf-8")) + _check_p(lib.TCOD_console_from_file(filename.encode("utf-8"))) ) @@ -2068,8 +2073,14 @@ def console_save_apf(con: tcod.console.Console, filename: str) -> bool: ) +@deprecate("Use tcod.console_from_xp to load this file.") def console_load_xp(con: tcod.console.Console, filename: str) -> bool: - """Update a console from a REXPaint `.xp` file.""" + """Update a console from a REXPaint `.xp` file. + + .. deprecated:: 11.18 + Functions modifying console objects in-place are deprecated. + Use :any:`tcod.console_from_xp` to load a Console from a file. + """ return bool( lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8")) ) @@ -2088,8 +2099,12 @@ def console_save_xp( def console_from_xp(filename: str) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file.""" + if not os.path.exists(filename): + raise RuntimeError( + "File not found:\n\t%s" % (os.path.realpath(filename),) + ) return tcod.console.Console._from_cdata( - lib.TCOD_console_from_xp(filename.encode("utf-8")) + _check_p(lib.TCOD_console_from_xp(filename.encode("utf-8"))) ) @@ -2097,6 +2112,10 @@ def console_list_load_xp( filename: str, ) -> Optional[List[tcod.console.Console]]: """Return a list of consoles from a REXPaint `.xp` file.""" + if not os.path.exists(filename): + raise RuntimeError( + "File not found:\n\t%s" % (os.path.realpath(filename),) + ) tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) if tcod_list == ffi.NULL: return None From fe6352b067a7ce0bc67e2b8ca3984c2e0a7da5e3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 26 Dec 2020 04:18:19 -0800 Subject: [PATCH 0436/1101] Fix NumPy related type hints. These are still too strict but now they're no longer broken. --- tcod/path.py | 14 +++++++------- tcod/sdl.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 86d3046a..4e93a0c1 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -302,7 +302,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: def maxarray( shape: Tuple[int, ...], dtype: Any = np.int32, order: str = "C" -) -> np.array: +) -> np.ndarray: """Return a new array filled with the maximum finite value for `dtype`. `shape` is of the new array. Same as other NumPy array initializers. @@ -321,7 +321,7 @@ def maxarray( return np.full(shape, np.iinfo(dtype).max, dtype, order) -def _export_dict(array: np.array) -> Dict[str, Any]: +def _export_dict(array: np.ndarray) -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" if array.dtype.type not in _INT_TYPES: raise TypeError( @@ -337,7 +337,7 @@ def _export_dict(array: np.array) -> Dict[str, Any]: } -def _export(array: np.array) -> Any: +def _export(array: np.ndarray) -> Any: """Convert a NumPy array into a ctype object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -365,8 +365,8 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: def dijkstra2d( - distance: np.array, - cost: np.array, + distance: np.ndarray, + cost: np.ndarray, cardinal: Optional[int] = None, diagonal: Optional[int] = None, *, @@ -516,13 +516,13 @@ def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: def hillclimb2d( - distance: np.array, + distance: np.ndarray, start: Tuple[int, int], cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, *, edge_map: Any = None -) -> np.array: +) -> np.ndarray: """Return a path on a grid from `start` to the lowest point. `distance` should be a fully computed distance array. This kind of array diff --git a/tcod/sdl.py b/tcod/sdl.py index 25996ce6..3ead35ff 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -15,7 +15,7 @@ class _TempSurface: """Holds a temporary surface derived from a NumPy array.""" - def __init__(self, pixels: np.array) -> None: + def __init__(self, pixels: np.ndarray) -> None: self._array = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: raise TypeError( @@ -33,7 +33,7 @@ def __init__(self, pixels: np.array) -> None: self._array.shape[1], # Width. self._array.shape[0], # Height. self._array.shape[2] * 8, # Bit depth. - self._array.stride[1], # Pitch. + self._array.strides[1], # Pitch. 0x000000FF, 0x0000FF00, 0x00FF0000, @@ -59,7 +59,7 @@ def __init__(self, sdl_window_p: Any) -> None: def __eq__(self, other: Any) -> bool: return bool(self.p == other.p) - def set_icon(self, image: np.array) -> None: + def set_icon(self, image: np.ndarray) -> None: """Set the window icon from an image. `image` is a C memory order RGB or RGBA NumPy array. From 27a32785cb6edb96d98a3030a63d9389a14b7286 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Dec 2020 04:29:59 -0800 Subject: [PATCH 0437/1101] Deprecate support for Python 3.5. Use typing_extensions to better support types on older versions of Python. This version actually supports 3.5 better than previous versions, which makes it a better point to drop support from. --- CHANGELOG.rst | 1 + setup.py | 1 + tcod/__init__.py | 10 ++++++++++ tcod/_internal.py | 17 ++++++----------- tcod/console.py | 21 ++++++++++++++------- tcod/libtcodpy.py | 3 ++- tcod/map.py | 16 +++++++++++----- tcod/path.py | 5 ++++- 8 files changed, 49 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 26955584..9404c757 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased Deprecated - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load these files without modifying an existing console. + - Support for Python 3.5 will be dropped soon. Fixed - `tcod.console_from_xp` now has better error handling (instead of crashing.) diff --git a/setup.py b/setup.py index 1e77c7db..8b20e9f7 100755 --- a/setup.py +++ b/setup.py @@ -135,6 +135,7 @@ def check_sdl_version(): install_requires=[ "cffi~=1.13", # Also required by pyproject.toml. "numpy~=1.10" if not is_pypy else "", + "typing_extensions", ], cffi_modules=["build_libtcod.py:ffi"], setup_requires=pytest_runner, diff --git a/tcod/__init__.py b/tcod/__init__.py index 910f7eb8..a560d2ef 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -15,6 +15,9 @@ Bring any issues or requests to GitHub: https://github.com/HexDecimal/libtcod-cffi """ +import sys +import warnings + from tcod import ( bsp, color, @@ -39,6 +42,13 @@ except ImportError: # Gets imported without version.py by ReadTheDocs __version__ = "" +if sys.version_info < (3, 6): + warnings.warn( + "Support for Python 3.5 is being dropped from python-tcod.", + DeprecationWarning, + stacklevel=2, + ) + __all__ = [ # noqa: F405 "__version__", "lib", diff --git a/tcod/_internal.py b/tcod/_internal.py index 7c39cdb6..1d0d8d06 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -2,9 +2,10 @@ """ import functools import warnings -from typing import Any, AnyStr, Callable, NoReturn, TypeVar, cast +from typing import Any, AnyStr, Callable, TypeVar, Union, cast import numpy as np +from typing_extensions import Literal, NoReturn from tcod.loader import ffi, lib @@ -42,21 +43,15 @@ def pending_deprecate( return deprecate(message, category, stacklevel) -def verify_order(order: str) -> str: - order = order.upper() +def verify_order( + order: Union[Literal["C"], Literal["F"]] +) -> Union[Literal["C"], Literal["F"]]: + order = order.upper() # type: ignore if order not in ("C", "F"): raise TypeError("order must be 'C' or 'F', not %r" % (order,)) return order -def handle_order(shape: Any, order: str) -> Any: - order = verify_order(order) - if order == "C": - return shape - else: - return tuple(reversed(shape)) - - def _raise_tcod_error() -> NoReturn: """Raise an error from libtcod, this function assumes an error exists.""" raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode("utf-8")) diff --git a/tcod/console.py b/tcod/console.py index 7df58138..6a055b2b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -5,9 +5,10 @@ """ import warnings -from typing import Any, Optional, Tuple # noqa: F401 +from typing import Any, Optional, Tuple, Union # noqa: F401 import numpy as np +from typing_extensions import Literal import tcod._internal import tcod.constants @@ -92,7 +93,7 @@ def __init__( self, width: int, height: int, - order: str = "C", + order: Union[Literal["C"], Literal["F"]] = "C", buffer: Optional[np.ndarray] = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] @@ -131,7 +132,9 @@ def __init__( self.clear() @classmethod - def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": + def _from_cdata( + cls, cdata: Any, order: Union[Literal["C"], Literal["F"]] = "C" + ) -> "Console": """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): return cdata @@ -141,7 +144,9 @@ def _from_cdata(cls, cdata: Any, order: str = "C") -> "Console": return self @classmethod - def _get_root(cls, order: Optional[str] = None) -> "Console": + def _get_root( + cls, order: Optional[Union[Literal["C"], Literal["F"]]] = None + ) -> "Console": """Return a root console singleton with valid buffers. This function will also update an already active root console. @@ -156,7 +161,9 @@ def _get_root(cls, order: Optional[str] = None) -> "Console": self._init_setup_console_data(self._order) return self - def _init_setup_console_data(self, order: str = "C") -> None: + def _init_setup_console_data( + self, order: Union[Literal["C"], Literal["F"]] = "C" + ) -> None: """Setup numpy arrays over libtcod data buffers.""" global _root_console self._key_color = None @@ -195,7 +202,7 @@ def bg(self) -> np.ndarray: ``console.bg[x, y, channel] # order='F'``. """ - bg = self._tiles["bg"][..., :3] + bg = self._tiles["bg"][..., :3] # type: np.ndarray if self._order == "F": bg = bg.transpose(1, 0, 2) return bg @@ -209,7 +216,7 @@ def fg(self) -> np.ndarray: Index this array with ``console.fg[i, j, channel] # order='C'`` or ``console.fg[x, y, channel] # order='F'``. """ - fg = self._tiles["fg"][..., :3] + fg = self._tiles["fg"][..., :3] # type: np.ndarray if self._order == "F": fg = fg.transpose(1, 0, 2) return fg diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 9e7d9919..a3bf3ec3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -20,6 +20,7 @@ ) import numpy as np +from typing_extensions import Literal import tcod.bsp import tcod.console @@ -879,7 +880,7 @@ def console_init_root( title: Optional[str] = None, fullscreen: bool = False, renderer: Optional[int] = None, - order: str = "C", + order: Union[Literal["C"], Literal["F"]] = "C", vsync: Optional[bool] = None, ) -> tcod.console.Console: """Set up the primary display and return the root console. diff --git a/tcod/map.py b/tcod/map.py index b23b9b22..463c57b1 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,9 +3,10 @@ """ import warnings -from typing import Any, Tuple +from typing import Any, Tuple, Union import numpy as np +from typing_extensions import Literal import tcod._internal import tcod.constants @@ -68,7 +69,12 @@ class Map(object): See :any:`tcod.map.compute_fov` and :any:`tcod.path`. """ - def __init__(self, width: int, height: int, order: str = "C"): + def __init__( + self, + width: int, + height: int, + order: Union[Literal["C"], Literal["F"]] = "C", + ): warnings.warn( "This class may perform poorly and is no longer needed.", DeprecationWarning, @@ -94,17 +100,17 @@ def __as_cdata(self) -> Any: @property def transparent(self) -> np.ndarray: - buffer = self.__buffer[:, :, 0] + buffer = self.__buffer[:, :, 0] # type: np.ndarray return buffer.T if self._order == "F" else buffer @property def walkable(self) -> np.ndarray: - buffer = self.__buffer[:, :, 1] + buffer = self.__buffer[:, :, 1] # type: np.ndarray return buffer.T if self._order == "F" else buffer @property def fov(self) -> np.ndarray: - buffer = self.__buffer[:, :, 2] + buffer = self.__buffer[:, :, 2] # type: np.ndarray return buffer.T if self._order == "F" else buffer def compute_fov( diff --git a/tcod/path.py b/tcod/path.py index 4e93a0c1..20bc2af7 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -20,6 +20,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np +from typing_extensions import Literal import tcod.map # noqa: F401 from tcod._internal import _check @@ -301,7 +302,9 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: def maxarray( - shape: Tuple[int, ...], dtype: Any = np.int32, order: str = "C" + shape: Tuple[int, ...], + dtype: Any = np.int32, + order: Union[Literal["C"], Literal["F"]] = "C", ) -> np.ndarray: """Return a new array filled with the maximum finite value for `dtype`. From 14264651c3b02835d9ac8f930b5de36304c96907 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Dec 2020 15:48:26 -0800 Subject: [PATCH 0438/1101] Fix SDL parser to work with SDL 2.0.14. A new constant definition was a float which broke the CFFI parser. This constant has been blacklisted. Update the bundled version of SDL to the current latest version. --- CHANGELOG.rst | 4 ++++ build_libtcod.py | 2 +- parse_sdl2.py | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9404c757..1a2caa6f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - Now bundles SDL 2.0.14 for Windows/MacOS. + Deprecated - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load these files without modifying an existing console. @@ -15,6 +18,7 @@ Deprecated Fixed - `tcod.console_from_xp` now has better error handling (instead of crashing.) + - Can now compile with SDL 2.0.14 headers. 11.18.2 - 2020-12-03 -------------------- diff --git a/build_libtcod.py b/build_libtcod.py index 79aa7931..7fc8bb8f 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -24,7 +24,7 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.10") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") diff --git a/parse_sdl2.py b/parse_sdl2.py index ebcb772f..237eeb8b 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -61,6 +61,7 @@ def get_header(name: str) -> str: "SDL_BlitScaled", "SDL_BlitSurface", "SDL_Colour", + "SDL_IPHONE_MAX_GFORCE", ] From 1a611231ef53bc3dc6e0b8faf261dfca55ac4803 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Dec 2020 17:01:09 -0800 Subject: [PATCH 0439/1101] Rephrase dropping support of Python 3.5. --- CHANGELOG.rst | 2 +- tcod/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1a2caa6f..778e69fc 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,9 +12,9 @@ Changed - Now bundles SDL 2.0.14 for Windows/MacOS. Deprecated + - Support for Python 3.5 will be dropped. - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load these files without modifying an existing console. - - Support for Python 3.5 will be dropped soon. Fixed - `tcod.console_from_xp` now has better error handling (instead of crashing.) diff --git a/tcod/__init__.py b/tcod/__init__.py index a560d2ef..f7020a16 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -44,7 +44,7 @@ if sys.version_info < (3, 6): warnings.warn( - "Support for Python 3.5 is being dropped from python-tcod.", + "Support for Python 3.5 has been dropped from python-tcod.", DeprecationWarning, stacklevel=2, ) From 5b5345e20ea56d8b43ee3b2b3bc322ca5f6db098 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Dec 2020 17:07:04 -0800 Subject: [PATCH 0440/1101] Ignore discarded restrict qualifiers. Python-cffi may discard these but they should still work in practice. This might accidentally ignore const discards too, but I've only added this flag to releases for now. --- build_libtcod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build_libtcod.py b/build_libtcod.py index 7fc8bb8f..298b91c8 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -320,6 +320,7 @@ def fix_header(filepath: str) -> None: "-g", "-fPIC", "-Wno-deprecated-declarations", + "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers. ], } From 6fd153bc67b56b122a5af7ac4331b47a6771a73b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Dec 2020 19:56:28 -0800 Subject: [PATCH 0441/1101] Prepare 11.18.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 778e69fc..7b75910f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.18.3 - 2020-12-28 +-------------------- Changed - Now bundles SDL 2.0.14 for Windows/MacOS. From a3dc83c5ed435645b114ee6463d8b61cd78d6fa8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 00:56:27 -0800 Subject: [PATCH 0442/1101] Add order parameter to Context.new_console. I've also added the common use of order to the getting started examples. --- CHANGELOG.rst | 2 ++ docs/tcod/getting-started.rst | 4 ++-- tcod/context.py | 16 ++++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7b75910f..cf706c3a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added + - Added the important `order` parameter to `Context.new_console`. 11.18.3 - 2020-12-28 -------------------- diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 62ebff83..7c958b7a 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -35,7 +35,7 @@ Example:: "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD, ) # Create the main console. - console = tcod.Console(WIDTH, HEIGHT) + console = tcod.Console(WIDTH, HEIGHT, order="F") # Create a window based on this console and tileset. with tcod.context.new( # New window for a console of size columns×rows. columns=console.width, rows=console.height, tileset=tileset, @@ -97,7 +97,7 @@ Example:: width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: while True: - console = context.new_console() + console = context.new_console(order="F") console.print(0, 0, "Hello World") context.present(console, integer_scaling=True) diff --git a/tcod/context.py b/tcod/context.py index 98cb7281..9b9f1162 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -49,7 +49,9 @@ """ # noqa: E501 import os import sys -from typing import Any, Iterable, List, Optional, Tuple +from typing import Any, Iterable, List, Optional, Tuple, Union + +from typing_extensions import Literal import tcod import tcod.event @@ -267,6 +269,7 @@ def new_console( min_columns: int = 1, min_rows: int = 1, magnification: float = 1.0, + order: Union[Literal["C"], Literal["F"]] = "C", ) -> tcod.console.Console: """Return a new console sized for this context. @@ -278,6 +281,9 @@ def new_console( consoles, which will show as larger tiles when presented. `magnification` must be greater than zero. + `order` is passed to :any:`tcod.console.Console` to determine the + memory order of its NumPy attributes. + The times where it is the most useful to call this method are: * After the context is created, even if the console was given a @@ -286,6 +292,12 @@ def new_console( * After any window resized event, or any manual resizing of the window. .. versionadded:: 11.18 + + .. versionchanged:: 11.19 + Added `order` parameter. + + .. seealso:: + :any:`tcod.console.Console` """ if magnification < 0: raise ValueError( @@ -299,7 +311,7 @@ def new_console( ) ) width, height = max(min_columns, size[0]), max(min_rows, size[1]) - return tcod.console.Console(width, height) + return tcod.console.Console(width, height, order=order) def recommended_console_size( self, min_columns: int = 1, min_rows: int = 1 From 63a3bf285d50a0fc756291a123ed89f4d2d12cb7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 15:44:30 -0800 Subject: [PATCH 0443/1101] Add GitHub action for automated releases. --- .github/workflows/release-on-tag.yml | 28 ++++++++++++++++++++++++++++ scripts/get_release_description.py | 24 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .github/workflows/release-on-tag.yml create mode 100644 scripts/get_release_description.py diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml new file mode 100644 index 00000000..3662f1ba --- /dev/null +++ b/.github/workflows/release-on-tag.yml @@ -0,0 +1,28 @@ +on: + push: + tags: + - '*.*.*' + +name: Create Release + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Generate body + run: | + scripts/get_release_description.py | tee release_body.md + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body_path: release_body.md + draft: true + prerelease: false diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py new file mode 100644 index 00000000..aa7acf64 --- /dev/null +++ b/scripts/get_release_description.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +"""Print the description used for GitHub Releases.""" +import re + +TAG_BANNER = r"\d+\.\d+\.\d+\S* - \d+-\d+-\d+\n--------------------\n" + +RE_BODY = re.compile(fr".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) +RE_SECTION = re.compile(r"^(\w+)$", re.MULTILINE) + + +def main() -> None: + # Get the most recent tag. + with open("CHANGELOG.rst", "r") as f: + match = RE_BODY.match(f.read()) + assert match + body = match.groups()[0].strip() + + # Add Markdown formatting to sections. + body = RE_SECTION.sub(r"### \1", body) + print(body) + + +if __name__ == "__main__": + main() From a44b8fe17ae80fd8ece89f4ba76699bfa2609888 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 16:19:23 -0800 Subject: [PATCH 0444/1101] Prepare 11.19.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf706c3a..2da23ee3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.19.0 - 2020-12-29 +-------------------- Added - Added the important `order` parameter to `Context.new_console`. From f2bf572f5b2ef856544235875e324103c4fafc82 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 16:23:05 -0800 Subject: [PATCH 0445/1101] Fix GitHub release action. Release name should be blank, but this parameter is documented as non-optional. Forgot to set the executable flag on the release description script. --- .github/workflows/release-on-tag.yml | 2 +- scripts/get_release_description.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 scripts/get_release_description.py diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 3662f1ba..a75db46f 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -22,7 +22,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} + release_name: '' body_path: release_body.md draft: true prerelease: false diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py old mode 100644 new mode 100755 From fec6c2e6a74ed0e17be67f8218ca24ded436a0b6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 19:07:53 -0800 Subject: [PATCH 0446/1101] Simplify XVFB usage. The previous system has been unreliable. I've redone it as my own script which I can continue to update as needed. --- .github/workflows/python-package.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 69ee1220..c60190cf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -27,7 +27,7 @@ jobs: - name: Install APT dependencies run: | sudo apt update - sudo apt install libsdl2-dev + sudo apt install libsdl2-dev xvfb - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -57,7 +57,6 @@ jobs: run: | python setup.py develop # Install the package in-place. - name: Test with pytest - uses: GabrielBB/xvfb-action@v1 - with: - run: pytest --cov-report=xml + run: | + xvfb-run --auto-servernum pytest --cov-report=xml - uses: codecov/codecov-action@v1 From 55040dcf2f232391e9874bbe83d043de338e5754 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 21:24:37 -0800 Subject: [PATCH 0447/1101] Add a personal token so that this action can trigger other actions. --- .github/workflows/release-on-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index a75db46f..da619c66 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -19,7 +19,7 @@ jobs: id: create_release uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} with: tag_name: ${{ github.ref }} release_name: '' From 2ffa87bafbe015455ca6f933098d5c7bf5549c4e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 21:27:08 -0800 Subject: [PATCH 0448/1101] Prepare 11.19.1 release. --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2da23ee3..6f03c108 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +11.19.1 - 2020-12-29 +-------------------- +Fixed + - MacOS wheels failed to deploy for the previous version. + 11.19.0 - 2020-12-29 -------------------- Added From a2e87982611cc3841c5fcc24184acbc9760d1298 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Dec 2020 21:40:36 -0800 Subject: [PATCH 0449/1101] Use more aggressive release triggers. These might need to be tweaked, but it's mostly finished. This no longer makes drafts, the release bot itself works fine at this point. --- .github/workflows/python-package-macos.yml | 3 +-- .github/workflows/release-on-tag.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index a5e07e15..50c5ebc8 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -4,8 +4,7 @@ on: push: pull_request: release: - types: - - created + types: [created, published, released] jobs: build: diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index da619c66..dac3b19b 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -24,5 +24,5 @@ jobs: tag_name: ${{ github.ref }} release_name: '' body_path: release_body.md - draft: true + draft: false prerelease: false From acc670d6ea18140bba0ad924b0b263988edbdc60 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Dec 2020 03:49:23 -0800 Subject: [PATCH 0450/1101] Bundle different versions of SDL for Windows and MacOS. This puts off issues I've been having with MacOS. --- CHANGELOG.rst | 5 +++++ build_libtcod.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6f03c108..8d50410c 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,11 @@ v2.0.0 Unreleased ------------------ +Changed + - Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows. + +Fixed + - MacOS wheels were failing to bundle dependencies for SDL2. 11.19.1 - 2020-12-29 -------------------- diff --git a/build_libtcod.py b/build_libtcod.py index 298b91c8..d007a655 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -23,8 +23,13 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") + # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") +# MacOS delocate-wheel will fail without throwing an error when bundling +# modern versions of SDL. +SDL2_BUNDLE_VERSION = os.environ.get( + "SDL_VERSION", "2.0.10" if sys.platform == "darwin" else "2.0.14" +) HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") From 0b0042b5ecc650d06ac71ece82683e4bad1aa3ad Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Dec 2020 04:11:26 -0800 Subject: [PATCH 0451/1101] Prepare 11.19.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8d50410c..cf97e43f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +11.19.2 - 2020-12-30 +-------------------- Changed - Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows. From f864dc95527fde0b65dcb52fc80c7fcf59c19a5c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Dec 2020 05:05:13 -0800 Subject: [PATCH 0452/1101] Remove extra release triggers. The last release triggered all 3 of these. I should be able to trust that they work now. --- .github/workflows/python-package-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 50c5ebc8..01b4a4c4 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -4,7 +4,7 @@ on: push: pull_request: release: - types: [created, published, released] + types: [created] jobs: build: From 2823d534d276d7e88da2497de5c36aadf8d8f630 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 1 Jan 2021 00:16:54 -0800 Subject: [PATCH 0453/1101] Update copyright year. --- LICENSE.txt | 2 +- docs/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b407f8da..c00f298e 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2009-2020, Kyle Stewart and the python-tcod contributors. +Copyright (c) 2009-2021, Kyle Benesch and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index 9ce3163b..bf2acdd7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,8 +56,8 @@ # General information about the project. project = u'python-tcod' -copyright = u'2009-2020, Kyle Stewart' -author = u'Kyle Stewart' +copyright = u'2009-2021, Kyle Benesch' +author = u'Kyle Benesch' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/setup.py b/setup.py index 8b20e9f7..e70c5faf 100755 --- a/setup.py +++ b/setup.py @@ -116,7 +116,7 @@ def check_sdl_version(): setup( name="tcod", version=get_version(), - author="Kyle Stewart", + author="Kyle Benesch", author_email="4b796c65+tcod@gmail.com", description="The official Python port of libtcod.", long_description=get_long_description(), From ef815aef97260d57b6f38a419febfac33d3a453d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 2 Jan 2021 16:01:50 -0800 Subject: [PATCH 0454/1101] Add missing typing_exentions package to requirements.txt. Docs were failing to import tcod due to this missing module. Fixes #105 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 14a5dff4..06e369f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ cffi~=1.13 numpy~=1.10,!=1.16.3; pycparser>=2.14 setuptools>=36.0.1 +typing_extensions From 3d55abd19cfff785d4b299d28c0ff042c795564c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 5 Jan 2021 03:37:51 -0800 Subject: [PATCH 0455/1101] Add a character table reference. Will help devs with using Unicode characters in libtcod. --- docs/index.rst | 1 + docs/tcod/charmap-reference.rst | 477 ++++++++++++++++++++++++++++++ scripts/generate_charmap_table.py | 70 +++++ tcod/tileset.py | 16 +- 4 files changed, 558 insertions(+), 6 deletions(-) create mode 100644 docs/tcod/charmap-reference.rst create mode 100755 scripts/generate_charmap_table.py diff --git a/docs/index.rst b/docs/index.rst index cd6e2dd4..c315d72a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,6 +28,7 @@ Contents: :caption: python-tcod API tcod/getting-started + tcod/charmap-reference tcod/bsp tcod/console tcod/context diff --git a/docs/tcod/charmap-reference.rst b/docs/tcod/charmap-reference.rst new file mode 100644 index 00000000..9576b122 --- /dev/null +++ b/docs/tcod/charmap-reference.rst @@ -0,0 +1,477 @@ +Character Table Reference +========================= + +This document exists as an easy reference for using non-ASCII glyphs in +standard tcod functions. + +*Tile Index* is the position of the glyph in the tileset image. +This is relevant for loading the tileset and for using :any:`Tileset.remap` to +reassign tiles to new code points. + +*Unicode* is the Unicode code point as a hexadecimal number. +You can use :any:`chr` to convert these numbers into a string. +Character maps such as :any:`tcod.tileset.CHARMAP_CP437` are simply a list of +Unicode numbers, where the index of the list is the *Tile Index*. + +*String* is the Python string for that character. +This lets you use that character inline with print functions. +These will work with :any:`ord` to convert them into a number. + +*Name* is the official name of a Unicode character. + +The symbols currently shown under *String* are provided by your browser, they +typically won't match the graphics provided by your tileset or could even be +missing from your browsers font entirely. You could experience similar issues +with your editor and IDE. + + +.. _code-page-437: + +Code Page 437 +------------- + +The layout for tilesets loaded with: :any:`tcod.tileset.CHARMAP_CP437` + +This is one of the more common character mappings. +Used for several games in the DOS era, and still used today whenever you want +an old school aesthetic. + +The Dwarf Fortress community is known to have a large collection of tilesets in +this format: +https://dwarffortresswiki.org/index.php/Tileset_repository + +Wikipedia also has a good reference for this character mapping: +https://en.wikipedia.org/wiki/Code_page_437 + +============ ========= ======== ================================================== + Tile Index Unicode String Name +============ ========= ======== ================================================== + 0 0x00 '\\x00' + 1 0x263A '☺' WHITE SMILING FACE + 2 0x263B '☻' BLACK SMILING FACE + 3 0x2665 '♥' BLACK HEART SUIT + 4 0x2666 '♦' BLACK DIAMOND SUIT + 5 0x2663 '♣' BLACK CLUB SUIT + 6 0x2660 '♠' BLACK SPADE SUIT + 7 0x2022 '•' BULLET + 8 0x25D8 '◘' INVERSE BULLET + 9 0x25CB '○' WHITE CIRCLE + 10 0x25D9 '◙' INVERSE WHITE CIRCLE + 11 0x2642 '♂' MALE SIGN + 12 0x2640 '♀' FEMALE SIGN + 13 0x266A '♪' EIGHTH NOTE + 14 0x266B '♫' BEAMED EIGHTH NOTES + 15 0x263C '☼' WHITE SUN WITH RAYS + 16 0x25BA '►' BLACK RIGHT-POINTING POINTER + 17 0x25C4 '◄' BLACK LEFT-POINTING POINTER + 18 0x2195 '↕' UP DOWN ARROW + 19 0x203C '‼' DOUBLE EXCLAMATION MARK + 20 0xB6 '¶' PILCROW SIGN + 21 0xA7 '§' SECTION SIGN + 22 0x25AC '▬' BLACK RECTANGLE + 23 0x21A8 '↨' UP DOWN ARROW WITH BASE + 24 0x2191 '↑' UPWARDS ARROW + 25 0x2193 '↓' DOWNWARDS ARROW + 26 0x2192 '→' RIGHTWARDS ARROW + 27 0x2190 '←' LEFTWARDS ARROW + 28 0x221F '∟' RIGHT ANGLE + 29 0x2194 '↔' LEFT RIGHT ARROW + 30 0x25B2 '▲' BLACK UP-POINTING TRIANGLE + 31 0x25BC '▼' BLACK DOWN-POINTING TRIANGLE + 32 0x20 ' ' SPACE + 33 0x21 '!' EXCLAMATION MARK + 34 0x22 '"' QUOTATION MARK + 35 0x23 '#' NUMBER SIGN + 36 0x24 '$' DOLLAR SIGN + 37 0x25 '%' PERCENT SIGN + 38 0x26 '&' AMPERSAND + 39 0x27 "'" APOSTROPHE + 40 0x28 '(' LEFT PARENTHESIS + 41 0x29 ')' RIGHT PARENTHESIS + 42 0x2A '\*' ASTERISK + 43 0x2B '+' PLUS SIGN + 44 0x2C ',' COMMA + 45 0x2D '-' HYPHEN-MINUS + 46 0x2E '.' FULL STOP + 47 0x2F '/' SOLIDUS + 48 0x30 '0' DIGIT ZERO + 49 0x31 '1' DIGIT ONE + 50 0x32 '2' DIGIT TWO + 51 0x33 '3' DIGIT THREE + 52 0x34 '4' DIGIT FOUR + 53 0x35 '5' DIGIT FIVE + 54 0x36 '6' DIGIT SIX + 55 0x37 '7' DIGIT SEVEN + 56 0x38 '8' DIGIT EIGHT + 57 0x39 '9' DIGIT NINE + 58 0x3A ':' COLON + 59 0x3B ';' SEMICOLON + 60 0x3C '<' LESS-THAN SIGN + 61 0x3D '=' EQUALS SIGN + 62 0x3E '>' GREATER-THAN SIGN + 63 0x3F '?' QUESTION MARK + 64 0x40 '@' COMMERCIAL AT + 65 0x41 'A' LATIN CAPITAL LETTER A + 66 0x42 'B' LATIN CAPITAL LETTER B + 67 0x43 'C' LATIN CAPITAL LETTER C + 68 0x44 'D' LATIN CAPITAL LETTER D + 69 0x45 'E' LATIN CAPITAL LETTER E + 70 0x46 'F' LATIN CAPITAL LETTER F + 71 0x47 'G' LATIN CAPITAL LETTER G + 72 0x48 'H' LATIN CAPITAL LETTER H + 73 0x49 'I' LATIN CAPITAL LETTER I + 74 0x4A 'J' LATIN CAPITAL LETTER J + 75 0x4B 'K' LATIN CAPITAL LETTER K + 76 0x4C 'L' LATIN CAPITAL LETTER L + 77 0x4D 'M' LATIN CAPITAL LETTER M + 78 0x4E 'N' LATIN CAPITAL LETTER N + 79 0x4F 'O' LATIN CAPITAL LETTER O + 80 0x50 'P' LATIN CAPITAL LETTER P + 81 0x51 'Q' LATIN CAPITAL LETTER Q + 82 0x52 'R' LATIN CAPITAL LETTER R + 83 0x53 'S' LATIN CAPITAL LETTER S + 84 0x54 'T' LATIN CAPITAL LETTER T + 85 0x55 'U' LATIN CAPITAL LETTER U + 86 0x56 'V' LATIN CAPITAL LETTER V + 87 0x57 'W' LATIN CAPITAL LETTER W + 88 0x58 'X' LATIN CAPITAL LETTER X + 89 0x59 'Y' LATIN CAPITAL LETTER Y + 90 0x5A 'Z' LATIN CAPITAL LETTER Z + 91 0x5B '[' LEFT SQUARE BRACKET + 92 0x5C '\\\\' REVERSE SOLIDUS + 93 0x5D ']' RIGHT SQUARE BRACKET + 94 0x5E '^' CIRCUMFLEX ACCENT + 95 0x5F '_' LOW LINE + 96 0x60 '\`' GRAVE ACCENT + 97 0x61 'a' LATIN SMALL LETTER A + 98 0x62 'b' LATIN SMALL LETTER B + 99 0x63 'c' LATIN SMALL LETTER C + 100 0x64 'd' LATIN SMALL LETTER D + 101 0x65 'e' LATIN SMALL LETTER E + 102 0x66 'f' LATIN SMALL LETTER F + 103 0x67 'g' LATIN SMALL LETTER G + 104 0x68 'h' LATIN SMALL LETTER H + 105 0x69 'i' LATIN SMALL LETTER I + 106 0x6A 'j' LATIN SMALL LETTER J + 107 0x6B 'k' LATIN SMALL LETTER K + 108 0x6C 'l' LATIN SMALL LETTER L + 109 0x6D 'm' LATIN SMALL LETTER M + 110 0x6E 'n' LATIN SMALL LETTER N + 111 0x6F 'o' LATIN SMALL LETTER O + 112 0x70 'p' LATIN SMALL LETTER P + 113 0x71 'q' LATIN SMALL LETTER Q + 114 0x72 'r' LATIN SMALL LETTER R + 115 0x73 's' LATIN SMALL LETTER S + 116 0x74 't' LATIN SMALL LETTER T + 117 0x75 'u' LATIN SMALL LETTER U + 118 0x76 'v' LATIN SMALL LETTER V + 119 0x77 'w' LATIN SMALL LETTER W + 120 0x78 'x' LATIN SMALL LETTER X + 121 0x79 'y' LATIN SMALL LETTER Y + 122 0x7A 'z' LATIN SMALL LETTER Z + 123 0x7B '{' LEFT CURLY BRACKET + 124 0x7C '|' VERTICAL LINE + 125 0x7D '}' RIGHT CURLY BRACKET + 126 0x7E '~' TILDE + 127 0x7F '\\x7f' + 128 0xC7 'Ç' LATIN CAPITAL LETTER C WITH CEDILLA + 129 0xFC 'ü' LATIN SMALL LETTER U WITH DIAERESIS + 130 0xE9 'é' LATIN SMALL LETTER E WITH ACUTE + 131 0xE2 'â' LATIN SMALL LETTER A WITH CIRCUMFLEX + 132 0xE4 'ä' LATIN SMALL LETTER A WITH DIAERESIS + 133 0xE0 'à' LATIN SMALL LETTER A WITH GRAVE + 134 0xE5 'å' LATIN SMALL LETTER A WITH RING ABOVE + 135 0xE7 'ç' LATIN SMALL LETTER C WITH CEDILLA + 136 0xEA 'ê' LATIN SMALL LETTER E WITH CIRCUMFLEX + 137 0xEB 'ë' LATIN SMALL LETTER E WITH DIAERESIS + 138 0xE8 'è' LATIN SMALL LETTER E WITH GRAVE + 139 0xEF 'ï' LATIN SMALL LETTER I WITH DIAERESIS + 140 0xEE 'î' LATIN SMALL LETTER I WITH CIRCUMFLEX + 141 0xEC 'ì' LATIN SMALL LETTER I WITH GRAVE + 142 0xC4 'Ä' LATIN CAPITAL LETTER A WITH DIAERESIS + 143 0xC5 'Å' LATIN CAPITAL LETTER A WITH RING ABOVE + 144 0xC9 'É' LATIN CAPITAL LETTER E WITH ACUTE + 145 0xE6 'æ' LATIN SMALL LETTER AE + 146 0xC6 'Æ' LATIN CAPITAL LETTER AE + 147 0xF4 'ô' LATIN SMALL LETTER O WITH CIRCUMFLEX + 148 0xF6 'ö' LATIN SMALL LETTER O WITH DIAERESIS + 149 0xF2 'ò' LATIN SMALL LETTER O WITH GRAVE + 150 0xFB 'û' LATIN SMALL LETTER U WITH CIRCUMFLEX + 151 0xF9 'ù' LATIN SMALL LETTER U WITH GRAVE + 152 0xFF 'ÿ' LATIN SMALL LETTER Y WITH DIAERESIS + 153 0xD6 'Ö' LATIN CAPITAL LETTER O WITH DIAERESIS + 154 0xDC 'Ü' LATIN CAPITAL LETTER U WITH DIAERESIS + 155 0xA2 '¢' CENT SIGN + 156 0xA3 '£' POUND SIGN + 157 0xA5 '¥' YEN SIGN + 158 0x20A7 '₧' PESETA SIGN + 159 0x0192 'ƒ' LATIN SMALL LETTER F WITH HOOK + 160 0xE1 'á' LATIN SMALL LETTER A WITH ACUTE + 161 0xED 'í' LATIN SMALL LETTER I WITH ACUTE + 162 0xF3 'ó' LATIN SMALL LETTER O WITH ACUTE + 163 0xFA 'ú' LATIN SMALL LETTER U WITH ACUTE + 164 0xF1 'ñ' LATIN SMALL LETTER N WITH TILDE + 165 0xD1 'Ñ' LATIN CAPITAL LETTER N WITH TILDE + 166 0xAA 'ª' FEMININE ORDINAL INDICATOR + 167 0xBA 'º' MASCULINE ORDINAL INDICATOR + 168 0xBF '¿' INVERTED QUESTION MARK + 169 0x2310 '⌐' REVERSED NOT SIGN + 170 0xAC '¬' NOT SIGN + 171 0xBD '½' VULGAR FRACTION ONE HALF + 172 0xBC '¼' VULGAR FRACTION ONE QUARTER + 173 0xA1 '¡' INVERTED EXCLAMATION MARK + 174 0xAB '«' LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 175 0xBB '»' RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 176 0x2591 '░' LIGHT SHADE + 177 0x2592 '▒' MEDIUM SHADE + 178 0x2593 '▓' DARK SHADE + 179 0x2502 '│' BOX DRAWINGS LIGHT VERTICAL + 180 0x2524 '┤' BOX DRAWINGS LIGHT VERTICAL AND LEFT + 181 0x2561 '╡' BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + 182 0x2562 '╢' BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + 183 0x2556 '╖' BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + 184 0x2555 '╕' BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + 185 0x2563 '╣' BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 186 0x2551 '║' BOX DRAWINGS DOUBLE VERTICAL + 187 0x2557 '╗' BOX DRAWINGS DOUBLE DOWN AND LEFT + 188 0x255D '╝' BOX DRAWINGS DOUBLE UP AND LEFT + 189 0x255C '╜' BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + 190 0x255B '╛' BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + 191 0x2510 '┐' BOX DRAWINGS LIGHT DOWN AND LEFT + 192 0x2514 '└' BOX DRAWINGS LIGHT UP AND RIGHT + 193 0x2534 '┴' BOX DRAWINGS LIGHT UP AND HORIZONTAL + 194 0x252C '┬' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 195 0x251C '├' BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 196 0x2500 '─' BOX DRAWINGS LIGHT HORIZONTAL + 197 0x253C '┼' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 198 0x255E '╞' BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + 199 0x255F '╟' BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + 200 0x255A '╚' BOX DRAWINGS DOUBLE UP AND RIGHT + 201 0x2554 '╔' BOX DRAWINGS DOUBLE DOWN AND RIGHT + 202 0x2569 '╩' BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 203 0x2566 '╦' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 204 0x2560 '╠' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 205 0x2550 '═' BOX DRAWINGS DOUBLE HORIZONTAL + 206 0x256C '╬' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 207 0x2567 '╧' BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + 208 0x2568 '╨' BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + 209 0x2564 '╤' BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + 210 0x2565 '╥' BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + 211 0x2559 '╙' BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + 212 0x2558 '╘' BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + 213 0x2552 '╒' BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + 214 0x2553 '╓' BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + 215 0x256B '╫' BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + 216 0x256A '╪' BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + 217 0x2518 '┘' BOX DRAWINGS LIGHT UP AND LEFT + 218 0x250C '┌' BOX DRAWINGS LIGHT DOWN AND RIGHT + 219 0x2588 '█' FULL BLOCK + 220 0x2584 '▄' LOWER HALF BLOCK + 221 0x258C '▌' LEFT HALF BLOCK + 222 0x2590 '▐' RIGHT HALF BLOCK + 223 0x2580 '▀' UPPER HALF BLOCK + 224 0x03B1 'α' GREEK SMALL LETTER ALPHA + 225 0xDF 'ß' LATIN SMALL LETTER SHARP S + 226 0x0393 'Γ' GREEK CAPITAL LETTER GAMMA + 227 0x03C0 'π' GREEK SMALL LETTER PI + 228 0x03A3 'Σ' GREEK CAPITAL LETTER SIGMA + 229 0x03C3 'σ' GREEK SMALL LETTER SIGMA + 230 0xB5 'µ' MICRO SIGN + 231 0x03C4 'τ' GREEK SMALL LETTER TAU + 232 0x03A6 'Φ' GREEK CAPITAL LETTER PHI + 233 0x0398 'Θ' GREEK CAPITAL LETTER THETA + 234 0x03A9 'Ω' GREEK CAPITAL LETTER OMEGA + 235 0x03B4 'δ' GREEK SMALL LETTER DELTA + 236 0x221E '∞' INFINITY + 237 0x03C6 'φ' GREEK SMALL LETTER PHI + 238 0x03B5 'ε' GREEK SMALL LETTER EPSILON + 239 0x2229 '∩' INTERSECTION + 240 0x2261 '≡' IDENTICAL TO + 241 0xB1 '±' PLUS-MINUS SIGN + 242 0x2265 '≥' GREATER-THAN OR EQUAL TO + 243 0x2264 '≤' LESS-THAN OR EQUAL TO + 244 0x2320 '⌠' TOP HALF INTEGRAL + 245 0x2321 '⌡' BOTTOM HALF INTEGRAL + 246 0xF7 '÷' DIVISION SIGN + 247 0x2248 '≈' ALMOST EQUAL TO + 248 0xB0 '°' DEGREE SIGN + 249 0x2219 '∙' BULLET OPERATOR + 250 0xB7 '·' MIDDLE DOT + 251 0x221A '√' SQUARE ROOT + 252 0x207F 'ⁿ' SUPERSCRIPT LATIN SMALL LETTER N + 253 0xB2 '²' SUPERSCRIPT TWO + 254 0x25A0 '■' BLACK SQUARE + 255 0xA0 '\\xa0' NO-BREAK SPACE +============ ========= ======== ================================================== + +.. _deprecated-tcod-layout: + +Deprecated TCOD Layout +---------------------- + +The layout for tilesets loaded with: :any:`tcod.tileset.CHARMAP_TCOD` + +============ ========= ======== =========================================== + Tile Index Unicode String Name +============ ========= ======== =========================================== + 0 0x20 ' ' SPACE + 1 0x21 '!' EXCLAMATION MARK + 2 0x22 '"' QUOTATION MARK + 3 0x23 '#' NUMBER SIGN + 4 0x24 '$' DOLLAR SIGN + 5 0x25 '%' PERCENT SIGN + 6 0x26 '&' AMPERSAND + 7 0x27 "'" APOSTROPHE + 8 0x28 '(' LEFT PARENTHESIS + 9 0x29 ')' RIGHT PARENTHESIS + 10 0x2A '\*' ASTERISK + 11 0x2B '+' PLUS SIGN + 12 0x2C ',' COMMA + 13 0x2D '-' HYPHEN-MINUS + 14 0x2E '.' FULL STOP + 15 0x2F '/' SOLIDUS + 16 0x30 '0' DIGIT ZERO + 17 0x31 '1' DIGIT ONE + 18 0x32 '2' DIGIT TWO + 19 0x33 '3' DIGIT THREE + 20 0x34 '4' DIGIT FOUR + 21 0x35 '5' DIGIT FIVE + 22 0x36 '6' DIGIT SIX + 23 0x37 '7' DIGIT SEVEN + 24 0x38 '8' DIGIT EIGHT + 25 0x39 '9' DIGIT NINE + 26 0x3A ':' COLON + 27 0x3B ';' SEMICOLON + 28 0x3C '<' LESS-THAN SIGN + 29 0x3D '=' EQUALS SIGN + 30 0x3E '>' GREATER-THAN SIGN + 31 0x3F '?' QUESTION MARK + 32 0x40 '@' COMMERCIAL AT + 33 0x5B '[' LEFT SQUARE BRACKET + 34 0x5C '\\' REVERSE SOLIDUS + 35 0x5D ']' RIGHT SQUARE BRACKET + 36 0x5E '^' CIRCUMFLEX ACCENT + 37 0x5F '_' LOW LINE + 38 0x60 '\`' GRAVE ACCENT + 39 0x7B '{' LEFT CURLY BRACKET + 40 0x7C '|' VERTICAL LINE + 41 0x7D '}' RIGHT CURLY BRACKET + 42 0x7E '~' TILDE + 43 0x2591 '░' LIGHT SHADE + 44 0x2592 '▒' MEDIUM SHADE + 45 0x2593 '▓' DARK SHADE + 46 0x2502 '│' BOX DRAWINGS LIGHT VERTICAL + 47 0x2500 '─' BOX DRAWINGS LIGHT HORIZONTAL + 48 0x253C '┼' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 49 0x2524 '┤' BOX DRAWINGS LIGHT VERTICAL AND LEFT + 50 0x2534 '┴' BOX DRAWINGS LIGHT UP AND HORIZONTAL + 51 0x251C '├' BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 52 0x252C '┬' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 53 0x2514 '└' BOX DRAWINGS LIGHT UP AND RIGHT + 54 0x250C '┌' BOX DRAWINGS LIGHT DOWN AND RIGHT + 55 0x2510 '┐' BOX DRAWINGS LIGHT DOWN AND LEFT + 56 0x2518 '┘' BOX DRAWINGS LIGHT UP AND LEFT + 57 0x2598 '▘' QUADRANT UPPER LEFT + 58 0x259D '▝' QUADRANT UPPER RIGHT + 59 0x2580 '▀' UPPER HALF BLOCK + 60 0x2596 '▖' QUADRANT LOWER LEFT + 61 0x259A '▚' QUADRANT UPPER LEFT AND LOWER RIGHT + 62 0x2590 '▐' RIGHT HALF BLOCK + 63 0x2597 '▗' QUADRANT LOWER RIGHT + 64 0x2191 '↑' UPWARDS ARROW + 65 0x2193 '↓' DOWNWARDS ARROW + 66 0x2190 '←' LEFTWARDS ARROW + 67 0x2192 '→' RIGHTWARDS ARROW + 68 0x25B2 '▲' BLACK UP-POINTING TRIANGLE + 69 0x25BC '▼' BLACK DOWN-POINTING TRIANGLE + 70 0x25C4 '◄' BLACK LEFT-POINTING POINTER + 71 0x25BA '►' BLACK RIGHT-POINTING POINTER + 72 0x2195 '↕' UP DOWN ARROW + 73 0x2194 '↔' LEFT RIGHT ARROW + 74 0x2610 '☐' BALLOT BOX + 75 0x2611 '☑' BALLOT BOX WITH CHECK + 76 0x25CB '○' WHITE CIRCLE + 77 0x25C9 '◉' FISHEYE + 78 0x2551 '║' BOX DRAWINGS DOUBLE VERTICAL + 79 0x2550 '═' BOX DRAWINGS DOUBLE HORIZONTAL + 80 0x256C '╬' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 81 0x2563 '╣' BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 82 0x2569 '╩' BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 83 0x2560 '╠' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 84 0x2566 '╦' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 85 0x255A '╚' BOX DRAWINGS DOUBLE UP AND RIGHT + 86 0x2554 '╔' BOX DRAWINGS DOUBLE DOWN AND RIGHT + 87 0x2557 '╗' BOX DRAWINGS DOUBLE DOWN AND LEFT + 88 0x255D '╝' BOX DRAWINGS DOUBLE UP AND LEFT + 89 0x00 '\\x00' + 90 0x00 '\\x00' + 91 0x00 '\\x00' + 92 0x00 '\\x00' + 93 0x00 '\\x00' + 94 0x00 '\\x00' + 95 0x00 '\\x00' + 96 0x41 'A' LATIN CAPITAL LETTER A + 97 0x42 'B' LATIN CAPITAL LETTER B + 98 0x43 'C' LATIN CAPITAL LETTER C + 99 0x44 'D' LATIN CAPITAL LETTER D + 100 0x45 'E' LATIN CAPITAL LETTER E + 101 0x46 'F' LATIN CAPITAL LETTER F + 102 0x47 'G' LATIN CAPITAL LETTER G + 103 0x48 'H' LATIN CAPITAL LETTER H + 104 0x49 'I' LATIN CAPITAL LETTER I + 105 0x4A 'J' LATIN CAPITAL LETTER J + 106 0x4B 'K' LATIN CAPITAL LETTER K + 107 0x4C 'L' LATIN CAPITAL LETTER L + 108 0x4D 'M' LATIN CAPITAL LETTER M + 109 0x4E 'N' LATIN CAPITAL LETTER N + 110 0x4F 'O' LATIN CAPITAL LETTER O + 111 0x50 'P' LATIN CAPITAL LETTER P + 112 0x51 'Q' LATIN CAPITAL LETTER Q + 113 0x52 'R' LATIN CAPITAL LETTER R + 114 0x53 'S' LATIN CAPITAL LETTER S + 115 0x54 'T' LATIN CAPITAL LETTER T + 116 0x55 'U' LATIN CAPITAL LETTER U + 117 0x56 'V' LATIN CAPITAL LETTER V + 118 0x57 'W' LATIN CAPITAL LETTER W + 119 0x58 'X' LATIN CAPITAL LETTER X + 120 0x59 'Y' LATIN CAPITAL LETTER Y + 121 0x5A 'Z' LATIN CAPITAL LETTER Z + 122 0x00 '\\x00' + 123 0x00 '\\x00' + 124 0x00 '\\x00' + 125 0x00 '\\x00' + 126 0x00 '\\x00' + 127 0x00 '\\x00' + 128 0x61 'a' LATIN SMALL LETTER A + 129 0x62 'b' LATIN SMALL LETTER B + 130 0x63 'c' LATIN SMALL LETTER C + 131 0x64 'd' LATIN SMALL LETTER D + 132 0x65 'e' LATIN SMALL LETTER E + 133 0x66 'f' LATIN SMALL LETTER F + 134 0x67 'g' LATIN SMALL LETTER G + 135 0x68 'h' LATIN SMALL LETTER H + 136 0x69 'i' LATIN SMALL LETTER I + 137 0x6A 'j' LATIN SMALL LETTER J + 138 0x6B 'k' LATIN SMALL LETTER K + 139 0x6C 'l' LATIN SMALL LETTER L + 140 0x6D 'm' LATIN SMALL LETTER M + 141 0x6E 'n' LATIN SMALL LETTER N + 142 0x6F 'o' LATIN SMALL LETTER O + 143 0x70 'p' LATIN SMALL LETTER P + 144 0x71 'q' LATIN SMALL LETTER Q + 145 0x72 'r' LATIN SMALL LETTER R + 146 0x73 's' LATIN SMALL LETTER S + 147 0x74 't' LATIN SMALL LETTER T + 148 0x75 'u' LATIN SMALL LETTER U + 149 0x76 'v' LATIN SMALL LETTER V + 150 0x77 'w' LATIN SMALL LETTER W + 151 0x78 'x' LATIN SMALL LETTER X + 152 0x79 'y' LATIN SMALL LETTER Y + 153 0x7A 'z' LATIN SMALL LETTER Z + 154 0x00 '\\x00' + 155 0x00 '\\x00' + 156 0x00 '\\x00' + 157 0x00 '\\x00' + 158 0x00 '\\x00' + 159 0x00 '\\x00' +============ ========= ======== =========================================== diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py new file mode 100755 index 00000000..205b87f0 --- /dev/null +++ b/scripts/generate_charmap_table.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""This script is used to generate the tables for `charmap-reference.rst`. + +Uses the tabulate module from PyPI. +""" +import argparse +import unicodedata +from typing import Iterable, Iterator + +import tcod.tileset +from tabulate import tabulate + + +def get_charmaps() -> Iterator[str]: + """Return an iterator of the current character maps from tcod.tilest.""" + for name in dir(tcod.tileset): + if name.startswith("CHARMAP_"): + yield name[len("CHARMAP_"):].lower() + + +def generate_table(charmap: Iterable[int]) -> str: + """Generate and RST table for `charmap`.""" + headers = ("Tile Index", "Unicode", "String", "Name") + table = [] + + for i, ch in enumerate(charmap): + hex_len = len(f"{ch:x}") + if hex_len % 2: # Prevent an odd number of hex digits. + hex_len += 1 + try: + name = unicodedata.name(chr(ch)) + except ValueError: + # Skip names rather than guessing, the official names are enough. + name = "" + string = ( + f"{chr(ch)!r}".replace("\\", "\\\\") + .replace("*", "\\*") # Escape RST symbols. + .replace("`", "\\`") + ) + table.append((i, f"0x{ch:0{hex_len}X}", string, name)) + return tabulate(table, headers, tablefmt="rst") + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate an RST table for a tcod character map.", + ) + parser.add_argument( + "charmap", + action="store", + choices=list(get_charmaps()), + type=str, + help="which character map to generate a table from", + ) + parser.add_argument( + "-o", + "--out-file", + action="store", + type=argparse.FileType("w", encoding="utf-8"), + default="-", + help="where to write the table to (stdout by default)", + ) + args = parser.parse_args() + charmap = getattr(tcod.tileset, f"CHARMAP_{args.charmap.upper()}") + with args.out_file as f: + f.write(generate_table(charmap)) + + +if __name__ == "__main__": + main() diff --git a/tcod/tileset.py b/tcod/tileset.py index a8aeebdd..c3e3a661 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -153,9 +153,10 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: `codepoint` is the Unicode codepoint to assign. - `x` and `y` is the position on the tilesheet to assign to `codepoint`. - Large values of `x` will wrap to the next row, so `y` isn't necessary - if you think of the tilesheet as a 1D array. + `x` and `y` is the position of the tilesheet to assign to `codepoint`. + This is the tile position itself, not the pixel position of the tile. + Large values of `x` will wrap to the next row, so using `x` by itself + is equivalent to `Tile Index` in the :any:`charmap-reference`. This is normally used on loaded tilesheets. Other methods of Tileset creation won't have reliable tile indexes. @@ -567,9 +568,9 @@ def load_tilesheet( 0x25A0, 0x00A0, ] -"""This is one of the more common character mappings. +"""A code page 437 character mapping. -https://en.wikipedia.org/wiki/Code_page_437 +See :ref:`code-page-437` for more info and a table of glyphs. .. versionadded:: 11.12 """ @@ -739,10 +740,13 @@ def load_tilesheet( """The layout used by older libtcod fonts, in Unicode. This layout is non-standard, and it's not recommend to make a font for it, but -you might need it to load an existing font. +you might need it to load an existing font made for libtcod. This character map is in Unicode, so old code using the non-Unicode `tcod.CHAR_*` constants will need to be updated. +See :ref:`deprecated-tcod-layout` for a table of glyphs used in this character +map. + .. versionadded:: 11.12 """ From 3fa8e773f7dccf6621335307ca3197dd1c864d98 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 7 Jan 2021 11:20:08 -0800 Subject: [PATCH 0456/1101] Fix missing metadata in MacOS wheels. The TCOD_TAG environment variable was skipping writing to version.py. --- CHANGELOG.rst | 2 ++ setup.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf97e43f..2f7bf065 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - MacOS wheels had broken version metadata. 11.19.2 - 2020-12-30 -------------------- diff --git a/setup.py b/setup.py index e70c5faf..230f11ee 100755 --- a/setup.py +++ b/setup.py @@ -15,6 +15,10 @@ def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" if os.environ.get("TCOD_TAG"): + # Force a tag version from an environment variable. + # Needed to work with GitHub Actions. + with open("tcod/version.py", "w") as f: + f.write('__version__ = "%s"\n' % os.environ["TCOD_TAG"]) return os.environ["TCOD_TAG"] try: tag = check_output( @@ -33,7 +37,8 @@ def get_version(): version += ".dev%i" % commits_since_tag # update tcod/version.py - open("tcod/version.py", "w").write('__version__ = "%s"\n' % version) + with open("tcod/version.py", "w") as f: + f.write('__version__ = "%s"\n' % version) return version except: try: From 7afa5a4758fd41f27e18dcd58f6d42f456e3cc71 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 7 Jan 2021 11:36:12 -0800 Subject: [PATCH 0457/1101] Prepare 11.19.3 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f7bf065..72950fed 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,11 @@ v2.0.0 Unreleased ------------------ + +11.19.3 - 2021-01-07 +-------------------- Fixed - - MacOS wheels had broken version metadata. + - Some wheels had broken version metadata. 11.19.2 - 2020-12-30 -------------------- From 99626413ce62b287e2d1e5e14f4216632d68133b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 12 Jan 2021 01:25:11 -0800 Subject: [PATCH 0458/1101] Resolve various tcod warnings. --- setup.cfg | 6 +++ tcod/console.py | 2 +- tests/test_console.py | 5 +- tests/test_libtcodpy.py | 102 +++++++++++++++++++++------------------- tests/test_parser.py | 2 +- tests/test_tcod.py | 11 ++++- 6 files changed, 74 insertions(+), 54 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8e34d073..54e5dabf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,12 @@ test=pytest [tool:pytest] addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl --capture=sys +filterwarnings = + ignore:The tdl module has been deprecated:DeprecationWarning + ignore:::tdl.map + ignore::DeprecationWarning:tcod.libtcodpy + ignore::PendingDeprecationWarning:tcod.libtcodpy + ignore:This class may perform poorly and is no longer needed.::tcod.map [flake8] ignore = E203 W503 diff --git a/tcod/console.py b/tcod/console.py index 6a055b2b..7cb1942a 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -901,7 +901,7 @@ def __repr__(self) -> str: return ( "tcod.console.Console(width=%i, height=%i, " "order=%r,buffer=\n%r)" - % (self.width, self.height, self._order, self.buffer) + % (self.width, self.height, self._order, self.tiles) ) def __str__(self) -> str: diff --git a/tests/test_console.py b/tests/test_console.py index 1cdea8b0..60b85cc1 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -39,7 +39,7 @@ def test_array_read_write(): assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_defaults(): console = tcod.console.Console(width=12, height=10) @@ -58,6 +58,7 @@ def test_console_defaults(): @pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") @pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instea") +@pytest.mark.filterwarnings("ignore:.*default values have been deprecated") def test_console_methods(): console = tcod.console.Console(width=12, height=10) console.put_char(0, 0, ord('@')) @@ -97,7 +98,7 @@ def test_console_repr(): eval(repr(tcod.console.Console(10, 2))) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_str(): console = tcod.console.Console(10, 2) console.print_(0, 0, "Test") diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 6ab5115d..5bf053a3 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -7,13 +7,18 @@ import tcod import tcod as libtcodpy +pytestmark = [ + pytest.mark.filterwarnings("ignore::DeprecationWarning"), + pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"), +] + def test_console_behaviour(console): assert not console @pytest.mark.skip('takes too long') -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_credits_long(console): libtcodpy.console_credits() @@ -36,7 +41,7 @@ def assert_char(console, x, y, ch=None, fg=None, bg=None): assert (console.bg[y, x] == bg).all() -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_defaults(console, fg, bg): libtcodpy.console_set_default_foreground(console, fg) libtcodpy.console_set_default_background(console, bg) @@ -44,37 +49,37 @@ def test_console_defaults(console, fg, bg): assert_char(console, 0, 0, None, fg, bg) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_set_char_background(console, bg): libtcodpy.console_set_char_background(console, 0, 0, bg, libtcodpy.BKGND_SET) assert_char(console, 0, 0, bg=bg) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_set_char_foreground(console, fg): libtcodpy.console_set_char_foreground(console, 0, 0, fg) assert_char(console, 0, 0, fg=fg) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_set_char(console, ch): libtcodpy.console_set_char(console, 0, 0, ch) assert_char(console, 0, 0, ch=ch) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_put_char(console, ch): libtcodpy.console_put_char(console, 0, 0, ch, libtcodpy.BKGND_SET) assert_char(console, 0, 0, ch=ch) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def console_put_char_ex(console, ch, fg, bg): libtcodpy.console_put_char_ex(console, 0, 0, ch, fg, bg) assert_char(console, 0, 0, ch=ch, fg=fg, bg=bg) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_printing(console, fg, bg): libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET) @@ -103,23 +108,23 @@ def test_console_printing(console, fg, bg): libtcodpy.console_set_color_control(libtcodpy.COLCTRL_1, fg, bg) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_rect(console): libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_lines(console): libtcodpy.console_hline(console, 0, 0, 4) libtcodpy.console_vline(console, 0, 0, 4) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_print_frame(console): libtcodpy.console_print_frame(console, 0, 0, 9, 9) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_fade(console): libtcodpy.console_set_fade(0, libtcodpy.Color(0, 0, 0)) libtcodpy.console_get_fade() @@ -132,7 +137,7 @@ def assertConsolesEqual(a, b): (a.ch[:] == b.ch[:]).all()) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_blit(console, offscreen): libtcodpy.console_print(offscreen, 0, 0, 'test') libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1) @@ -140,7 +145,7 @@ def test_console_blit(console, offscreen): libtcodpy.console_set_key_color(offscreen, libtcodpy.black) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_asc_read_write(console, offscreen, tmpdir): libtcodpy.console_print(console, 0, 0, 'test') @@ -150,7 +155,7 @@ def test_console_asc_read_write(console, offscreen, tmpdir): assertConsolesEqual(console, offscreen) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_apf_read_write(console, offscreen, tmpdir): libtcodpy.console_print(console, 0, 0, 'test') @@ -160,7 +165,7 @@ def test_console_apf_read_write(console, offscreen, tmpdir): assertConsolesEqual(console, offscreen) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_rexpaint_load_test_file(console): xp_console = libtcodpy.console_from_xp('libtcod/data/rexpaint/test.xp') assert xp_console @@ -174,7 +179,7 @@ def test_console_rexpaint_load_test_file(console): libtcodpy.Color(0, 0, 255)) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): libtcodpy.console_print(console, 0, 0, 'test') libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) @@ -187,7 +192,7 @@ def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): assertConsolesEqual(console, xp_console) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_rexpaint_list_save_load(console, tmpdir): con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) @@ -201,25 +206,25 @@ def test_console_rexpaint_list_save_load(console, tmpdir): libtcodpy.console_delete(b) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_fullscreen(console): libtcodpy.console_set_fullscreen(False) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_key_input(console): libtcodpy.console_check_for_keypress() libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_fill_errors(console): with pytest.raises(TypeError): libtcodpy.console_fill_background(console, [0], [], []) with pytest.raises(TypeError): libtcodpy.console_fill_foreground(console, [0], [], []) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_fill(console): width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) @@ -240,7 +245,7 @@ def test_console_fill(console): assert fill == ch -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_fill_numpy(console): width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) @@ -266,7 +271,7 @@ def test_console_fill_numpy(console): assert fill == fg.tolist() assert fill == ch.tolist() -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_buffer(console): buffer = libtcodpy.ConsoleBuffer( libtcodpy.console_get_width(console), @@ -284,13 +289,13 @@ def test_console_buffer_error(console): with pytest.raises(ValueError): buffer.blit(console) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_console_font_mapping(console): libtcodpy.console_map_ascii_code_to_font('@', 1, 1) libtcodpy.console_map_ascii_codes_to_font('@', 1, 0, 0) libtcodpy.console_map_string_to_font('@', 0, 0) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_mouse(console): libtcodpy.mouse_show_cursor(True) libtcodpy.mouse_is_cursor_visible() @@ -298,7 +303,7 @@ def test_mouse(console): repr(mouse) libtcodpy.mouse_move(0, 0) -@pytest.mark.filterwarnings("ignore:Use Python's standard 'time' module") +@pytest.mark.filterwarnings("ignore") def test_sys_time(console): libtcodpy.sys_set_fps(0) libtcodpy.sys_get_fps() @@ -307,11 +312,11 @@ def test_sys_time(console): libtcodpy.sys_elapsed_milli() libtcodpy.sys_elapsed_seconds() -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_sys_screenshot(console, tmpdir): libtcodpy.sys_save_screenshot(tmpdir.join('test.png').strpath) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_sys_custom_render(console): if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL: pytest.xfail(reason='Only supports SDL') @@ -324,7 +329,7 @@ def sdl_callback(sdl_surface): libtcodpy.console_flush() assert escape, 'proof that sdl_callback was called' -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_image(console, tmpdir): img = libtcodpy.image_new(16, 16) libtcodpy.image_clear(img, libtcodpy.Color(0, 0, 0)) @@ -356,7 +361,7 @@ def test_image(console, tmpdir): @pytest.mark.parametrize('sample', ['@', u'\u2603']) # Unicode snowman @pytest.mark.xfail(reason='Unreliable') -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_clipboard(console, sample): saved = libtcodpy.sys_clipboard_get() try: @@ -373,7 +378,7 @@ def test_clipboard(console, sample): INCLUSIVE_RESULTS = [(-5, 0)] + EXCLUSIVE_RESULTS -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_line_step(): """ libtcodpy.line_init and libtcodpy.line_step @@ -384,7 +389,7 @@ def test_line_step(): assert libtcodpy.line_step() == (None, None) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_line(): """ tests normal use, lazy evaluation, and error propagation @@ -406,6 +411,7 @@ def return_false(*test_xy): assert test_result == INCLUSIVE_RESULTS[:1] +@pytest.mark.filterwarnings("ignore") def test_line_iter(): """ libtcodpy.line_iter @@ -413,7 +419,7 @@ def test_line_iter(): assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_bsp(): """ commented out statements work in libtcod-cffi @@ -476,7 +482,7 @@ def traverse(node, user_data): libtcodpy.bsp_delete(bsp) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_map(): map = libtcodpy.map_new(16, 16) assert libtcodpy.map_get_width(map) == 16 @@ -490,7 +496,7 @@ def test_map(): libtcodpy.map_delete(map) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_color(): color_a = libtcodpy.Color(0, 1, 2) assert list(color_a) == [0, 1, 2] @@ -519,7 +525,7 @@ def test_color_repr(): assert eval(repr(col)) == col -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_color_math(): color_a = libtcodpy.Color(0, 1, 2) color_b = libtcodpy.Color(0, 10, 20) @@ -530,14 +536,14 @@ def test_color_math(): assert color_a * 100 == libtcodpy.Color(0, 100, 200) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_color_gen_map(): colors = libtcodpy.color_gen_map([(0, 0, 0), (255, 255, 255)], [0, 8]) assert colors[0] == libtcodpy.Color(0, 0, 0) assert colors[-1] == libtcodpy.Color(255, 255, 255) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_namegen_parse(): libtcodpy.namegen_parse('libtcod/data/namegen/jice_celtic.cfg') assert libtcodpy.namegen_generate('Celtic female') @@ -545,7 +551,7 @@ def test_namegen_parse(): libtcodpy.namegen_destroy() -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_noise(): noise = libtcodpy.noise_new(1) libtcodpy.noise_set_type(noise, libtcodpy.NOISE_SIMPLEX) @@ -555,7 +561,7 @@ def test_noise(): libtcodpy.noise_delete(noise) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_random(): rand = libtcodpy.random_get_instance() rand = libtcodpy.random_new() @@ -576,7 +582,7 @@ def test_random(): libtcodpy.random_delete(backup) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_heightmap(): hmap = libtcodpy.heightmap_new(16, 16) repr(hmap) @@ -658,12 +664,12 @@ def callback(ox, oy, dx, dy, user_data): return callback -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_map_fov(map_): libtcodpy.map_compute_fov(map_, *POINT_A) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_astar(map_): astar = libtcodpy.path_new_using_map(map_) @@ -689,7 +695,7 @@ def test_astar(map_): libtcodpy.path_delete(astar) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_astar_callback(map_, path_callback): astar = libtcodpy.path_new_using_function( libtcodpy.map_get_width(map_), @@ -700,7 +706,7 @@ def test_astar_callback(map_, path_callback): libtcodpy.path_delete(astar) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_dijkstra(map_): path = libtcodpy.dijkstra_new(map_) @@ -724,7 +730,7 @@ def test_dijkstra(map_): libtcodpy.dijkstra_delete(path) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_dijkstra_callback(map_, path_callback): path = libtcodpy.dijkstra_new_using_function( libtcodpy.map_get_width(map_), @@ -735,7 +741,7 @@ def test_dijkstra_callback(map_, path_callback): libtcodpy.dijkstra_delete(path) -@pytest.mark.filterwarnings("ignore:.") +@pytest.mark.filterwarnings("ignore") def test_alpha_blend(console): for i in range(256): libtcodpy.console_put_char(console, 0, 0, 'x', diff --git a/tests/test_parser.py b/tests/test_parser.py index a6a8d370..3bb62ac7 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,7 +7,7 @@ import tcod as libtcod -@pytest.mark.filterwarnings("ignore:.*") +@pytest.mark.filterwarnings("ignore") def test_parser(): print ('***** File Parser test *****') parser=libtcod.parser_new() diff --git a/tests/test_tcod.py b/tests/test_tcod.py index f5d1a7ef..27fd1f8f 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -54,6 +54,7 @@ def test_tcod_bsp(): @pytest.mark.filterwarnings("ignore:Use map.+ to check for this") +@pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_set_bits(): map_ = tcod.map.Map(2,2) @@ -69,17 +70,20 @@ def test_tcod_map_set_bits(): assert tcod.map_is_in_fov(map_, 0, 1) == True +@pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_get_bits(): map_ = tcod.map.Map(2,2) map_.transparent[0] +@pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_copy(): map_ = tcod.map.Map(3, 3) map_.transparent[:] = True assert (map_.transparent.tolist() == copy.copy(map_).transparent.tolist()) +@pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_pickle(): map_ = tcod.map.Map(3, 3) map_.transparent[:] = True @@ -87,6 +91,7 @@ def test_tcod_map_pickle(): assert (map_.transparent[:].tolist() == map2.transparent[:].tolist()) +@pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_pickle_fortran(): map_ = tcod.map.Map(2, 3, order='F') map2 = pickle.loads(pickle.dumps(copy.copy(map_))) @@ -152,7 +157,7 @@ def test_noise_copy(): noise2.sample_ogrid(np.ogrid[:3,:1])).all() -@pytest.mark.filterwarnings("ignore:.*") +@pytest.mark.filterwarnings("ignore") def test_color_class(): assert tcod.black == tcod.black assert tcod.black == (0, 0, 0) @@ -233,16 +238,18 @@ def test_cffi_structs(): tcod.ffi.new("SDL_AudioCVT*") +@pytest.mark.filterwarnings("ignore") def test_recommended_size(console): tcod.console.recommended_size() +@pytest.mark.filterwarnings("ignore") def test_context(): with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): pass WIDTH, HEIGHT = 16, 4 with tcod.context.new_terminal( - WIDTH, HEIGHT, renderer=tcod.RENDERER_SDL2 + columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2 ) as context: console = tcod.Console(*context.recommended_console_size()) context.present(console) From 16900814a0f888f8b85dd57e7a95f0322952ec97 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 14 Jan 2021 23:06:49 -0800 Subject: [PATCH 0459/1101] Test experimental branch of delocate. Update MacOS to use SDL 2.0.14, a version known to have issues with delocate. --- .github/workflows/python-package-macos.yml | 41 +++++++++++++++++++--- build_libtcod.py | 4 +-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 01b4a4c4..5069f11f 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -41,21 +41,28 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest pytest-cov pytest-benchmark delocate wheel twine + python -m pip install pytest pytest-cov pytest-benchmark wheel twine + python -m pip install git+https://github.com/HexDecimal/delocate.git@loader_path if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Build package. env: TCOD_TAG: ${{ github.event.release.tag_name }} run: | python setup.py sdist develop bdist_wheel --py-limited-api=cp35 - - name: Package binary files - run: | - delocate-wheel -v dist/*.whl - delocate-listdeps --all dist/*.whl - name: Test with pytest run: | pytest --no-window --cov-report=xml - uses: codecov/codecov-action@v1 + - name: Package binary files + run: | + delocate-wheel -v dist/*.whl + delocate-listdeps --all dist/*.whl + - name: Archive wheel + uses: actions/upload-artifact@v2 + with: + name: wheel-macos + path: dist/*.whl + retention-days: 1 - name: Upload to PyPI if: github.event_name == 'release' env: @@ -63,3 +70,27 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | twine upload --skip-existing dist/* + + isolated_test: + name: Verify wheel dependencies + needs: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['macos-11.0'] + python-version: ['3.7.9'] + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/download-artifact@v2 + with: + name: wheel-macos + path: dist + - name: Install tcod from wheel + run: | + python -m pip install dist/*.whl + - name: Verify dependency correctness + run: | + python -c "import tcod" diff --git a/build_libtcod.py b/build_libtcod.py index d007a655..15f13c50 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -27,9 +27,7 @@ # The SDL2 version to include in binary distributions. # MacOS delocate-wheel will fail without throwing an error when bundling # modern versions of SDL. -SDL2_BUNDLE_VERSION = os.environ.get( - "SDL_VERSION", "2.0.10" if sys.platform == "darwin" else "2.0.14" -) +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") From ce0d6fd14215070b3498f367c29e144df300bdfa Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Jan 2021 07:36:24 -0800 Subject: [PATCH 0460/1101] Replace apt with apt-get, which is better for scripts. --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c60190cf..501f52b4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,8 +26,8 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install APT dependencies run: | - sudo apt update - sudo apt install libsdl2-dev xvfb + sudo apt-get update + sudo apt-get install libsdl2-dev xvfb - name: Install Python dependencies run: | python -m pip install --upgrade pip From 5f508f7d639dd38cd19729ba36f13980ebead943 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 08:58:47 -0800 Subject: [PATCH 0461/1101] Drop Python 3.5 support. --- .github/workflows/python-package-macos.yml | 2 +- .github/workflows/python-package.yml | 1 + CHANGELOG.rst | 2 ++ appveyor.yml | 11 ++++------- build_libtcod.py | 2 +- pyproject.toml | 6 +++--- requirements.txt | 3 ++- setup.cfg | 2 +- setup.py | 3 +-- tcod/console.py | 2 +- tcod/context.py | 8 ++++---- tcod/libtcodpy.py | 4 ++-- tcod/map.py | 2 +- tcod/path.py | 18 +++++++++--------- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 5069f11f..8cc3448d 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -48,7 +48,7 @@ jobs: env: TCOD_TAG: ${{ github.event.release.tag_name }} run: | - python setup.py sdist develop bdist_wheel --py-limited-api=cp35 + python setup.py sdist develop bdist_wheel --py-limited-api=cp36 - name: Test with pytest run: | pytest --no-window --cov-report=xml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 501f52b4..a4e13099 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -43,6 +43,7 @@ jobs: # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 tcod/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Check type hints with MyPy + if: ${{ matrix.python-version != '3.6' }} run: | mypy tcod/ - name: Check formatting with Black diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 72950fed..1d7f9021 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Removed + - Python 3.5 is no longer supported. 11.19.3 - 2021-01-07 -------------------- diff --git a/appveyor.yml b/appveyor.yml index 2fc820da..2e8a3444 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,15 +10,12 @@ environment: DEPLOY_ONLY: false EXTRA_SETUP_ARGS: "" matrix: - - PYTHON: C:\Python35-x64\python.exe - platform: x64 - EXTRA_SETUP_ARGS: "--py-limited-api cp35" - - PYTHON: C:\Python35\python.exe - platform: Any CPU - EXTRA_SETUP_ARGS: "--py-limited-api cp35" - PYTHON: C:\Python36-x64\python.exe platform: x64 - NO_DEPLOY: true + EXTRA_SETUP_ARGS: "--py-limited-api cp36" + - PYTHON: C:\Python36\python.exe + platform: Any CPU + EXTRA_SETUP_ARGS: "--py-limited-api cp36" - PYTHON: C:\Python37-x64\python.exe platform: x64 NO_DEPLOY: true diff --git a/build_libtcod.py b/build_libtcod.py index 15f13c50..b54c4bbb 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -207,7 +207,7 @@ def unpack_sdl2(version: str) -> str: libraries = [] library_dirs = [] -define_macros = [("Py_LIMITED_API", 0x03050000)] # type: List[Tuple[str, Any]] +define_macros = [("Py_LIMITED_API", 0x03060000)] # type: List[Tuple[str, Any]] sources += walk_sources("tcod/", cpp=True) sources += walk_sources("libtcod/src/libtcod/", cpp=False) diff --git a/pyproject.toml b/pyproject.toml index 8c110f45..d6aa8694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,11 @@ requires = [ [tool.black] line-length = 79 -py36 = false -target-version = ["py35"] +py36 = true +target-version = ["py36"] [tool.isort] profile= "black" -py_version = "35" +py_version = "36" skip_gitignore = true line_length = 79 diff --git a/requirements.txt b/requirements.txt index 06e369f6..3b43ea13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ cffi~=1.13 -numpy~=1.10,!=1.16.3; +numpy>=1.20.0rc1; python_version >= '3.7' +numpy>=1.19.5; python_version <= '3.6' pycparser>=2.14 setuptools>=36.0.1 typing_extensions diff --git a/setup.cfg b/setup.cfg index 54e5dabf..a2ccaa80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ filterwarnings = ignore = E203 W503 [mypy] -python_version = 3.5 +python_version = 3.6 warn_unused_configs = True disallow_subclassing_any = True disallow_any_generics = True diff --git a/setup.py b/setup.py index 230f11ee..09c74f98 100755 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ def check_sdl_version(): py_modules=["libtcodpy"], packages=["tdl", "tcod"], package_data={"tdl": ["*.png"], "tcod": get_package_data()}, - python_requires=">=3.5", + python_requires=">=3.6", install_requires=[ "cffi~=1.13", # Also required by pyproject.toml. "numpy~=1.10" if not is_pypy else "", @@ -157,7 +157,6 @@ def check_sdl_version(): "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tcod/console.py b/tcod/console.py index 7cb1942a..6a26d1b1 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -230,7 +230,7 @@ def ch(self) -> np.ndarray: Index this array with ``console.ch[i, j] # order='C'`` or ``console.ch[x, y] # order='F'``. """ - return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] + return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] # type: ignore @property def tiles(self) -> np.ndarray: diff --git a/tcod/context.py b/tcod/context.py index 9b9f1162..3739089e 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -178,7 +178,7 @@ def present( keep_aspect: bool = False, integer_scaling: bool = False, clear_color: Tuple[int, int, int] = (0, 0, 0), - align: Tuple[float, float] = (0.5, 0.5) + align: Tuple[float, float] = (0.5, 0.5), ) -> None: """Present a console to this context's display. @@ -387,7 +387,7 @@ def new( vsync: bool = True, sdl_window_flags: Optional[int] = None, title: Optional[str] = None, - argv: Optional[Iterable[str]] = None + argv: Optional[Iterable[str]] = None, ) -> Context: """Create a new context with the desired pixel size. @@ -490,7 +490,7 @@ def new_window( tileset: Optional[tcod.tileset.Tileset] = None, vsync: bool = True, sdl_window_flags: Optional[int] = None, - title: Optional[str] = None + title: Optional[str] = None, ) -> Context: """Create a new context with the desired pixel size. @@ -519,7 +519,7 @@ def new_terminal( tileset: Optional[tcod.tileset.Tileset] = None, vsync: bool = True, sdl_window_flags: Optional[int] = None, - title: Optional[str] = None + title: Optional[str] = None, ) -> Context: """Create a new context with the desired console size. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a3bf3ec3..61f768a5 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -492,7 +492,7 @@ def __init__( cy: int = 0, dcx: int = 0, dcy: int = 0, - **kargs: Any + **kargs: Any, ): if isinstance(x, ffi.CData): self.cdata = x @@ -1221,7 +1221,7 @@ def console_flush( 0, 0, ), - align: Tuple[float, float] = (0.5, 0.5) + align: Tuple[float, float] = (0.5, 0.5), ) -> None: """Update the display to represent the root consoles current state. diff --git a/tcod/map.py b/tcod/map.py index 463c57b1..469c1c12 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -277,4 +277,4 @@ def compute_fov( lib.TCOD_map_compute_fov( map_cdata, pov[1], pov[0], radius, light_walls, algorithm ) - return map_buffer["fov"] + return map_buffer["fov"] # type: ignore diff --git a/tcod/path.py b/tcod/path.py index 20bc2af7..2d3778ad 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -117,7 +117,7 @@ def __init__( super(EdgeCostCallback, self).__init__(callback, shape) -class NodeCostArray(np.ndarray): # type: ignore +class NodeCostArray(np.ndarray): """Calculate cost from a numpy array of nodes. `array` is a NumPy array holding the path-cost of each node. @@ -138,7 +138,7 @@ class NodeCostArray(np.ndarray): # type: ignore def __new__(cls, array: np.ndarray) -> "NodeCostArray": """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) - return self # type: ignore + return self def __repr__(self) -> str: return "%s(%r)" % ( @@ -373,7 +373,7 @@ def dijkstra2d( cardinal: Optional[int] = None, diagonal: Optional[int] = None, *, - edge_map: Any = None + edge_map: Any = None, ) -> None: """Return the computed distance of all nodes on a 2D Dijkstra grid. @@ -524,7 +524,7 @@ def hillclimb2d( cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, *, - edge_map: Any = None + edge_map: Any = None, ) -> np.ndarray: """Return a path on a grid from `start` to the lowest point. @@ -697,7 +697,7 @@ def add_edge( edge_cost: int = 1, *, cost: np.ndarray, - condition: Optional[np.ndarray] = None + condition: Optional[np.ndarray] = None, ) -> None: """Add a single edge rule. @@ -799,7 +799,7 @@ def add_edges( *, edge_map: Any, cost: np.ndarray, - condition: Optional[np.ndarray] = None + condition: Optional[np.ndarray] = None, ) -> None: """Add a rule with multiple edges. @@ -1190,7 +1190,7 @@ def traversal(self) -> np.ndarray: """ if self._order == "F": axes = range(self._travel.ndim) - return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] + return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] # type: ignore return self._travel def clear(self) -> None: @@ -1379,7 +1379,7 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: ffi.from_buffer("int*", path), ) ) - return path[:, ::-1] if self._order == "F" else path + return path[:, ::-1] if self._order == "F" else path # type: ignore def path_to(self, index: Tuple[int, ...]) -> np.ndarray: """Return the shortest path from the nearest root to `index`. @@ -1406,4 +1406,4 @@ def path_to(self, index: Tuple[int, ...]) -> np.ndarray: >>> pf.path_to((0, 0))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. [] """ # noqa: E501 - return self.path_from(index)[::-1] + return self.path_from(index)[::-1] # type: ignore From ac369020e7225184b54bcc3f6fc78924bd10ce38 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 09:44:08 -0800 Subject: [PATCH 0462/1101] Disable fail-fast on GitHub workflow. --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a4e13099..888ca4ef 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,6 +12,7 @@ jobs: strategy: matrix: python-version: ['3.6', '3.7', '3.8', '3.9'] + fail-fast: false steps: - uses: actions/checkout@v2 From 9a2f60242b8bbff1648ec93e62c15a7e43992161 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 10:24:55 -0800 Subject: [PATCH 0463/1101] Remove the tdl package. --- CHANGELOG.rst | 1 + docs/index.rst | 6 - docs/tdl.rst | 47 -- setup.cfg | 4 +- setup.py | 4 +- tdl/__init__.py | 1341 ----------------------------------------- tdl/event.py | 511 ---------------- tdl/map.py | 353 ----------- tdl/noise.py | 169 ------ tdl/style.py | 31 - tdl/terminal8x8.png | Bin 3150 -> 0 bytes tdl/version.py | 1 - tests/test_noise.py | 30 - tests/test_tdl.py | 306 ---------- tests/test_tdl_map.py | 106 ---- 15 files changed, 4 insertions(+), 2906 deletions(-) delete mode 100644 docs/tdl.rst delete mode 100755 tdl/__init__.py delete mode 100755 tdl/event.py delete mode 100644 tdl/map.py delete mode 100755 tdl/noise.py delete mode 100644 tdl/style.py delete mode 100644 tdl/terminal8x8.png delete mode 100644 tdl/version.py delete mode 100755 tests/test_tdl.py delete mode 100644 tests/test_tdl_map.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1d7f9021..637fda02 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Unreleased ------------------ Removed - Python 3.5 is no longer supported. + - The `tdl` module has been dropped. 11.19.3 - 2021-01-07 -------------------- diff --git a/docs/index.rst b/docs/index.rst index c315d72a..b4cf4a0b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,12 +42,6 @@ Contents: tcod/tileset libtcodpy -.. toctree:: - :maxdepth: 2 - :caption: Legacy tdl API - - tdl - Indices and tables ================== diff --git a/docs/tdl.rst b/docs/tdl.rst deleted file mode 100644 index 46eb9223..00000000 --- a/docs/tdl.rst +++ /dev/null @@ -1,47 +0,0 @@ - -.. currentmodule:: tdl - -tdl -=== -.. automodule:: tdl - -tdl API -------------- -.. autofunction:: tdl.set_font -.. autofunction:: tdl.init -.. autofunction:: tdl.flush -.. autofunction:: tdl.screenshot -.. autofunction:: tdl.get_fullscreen -.. autofunction:: tdl.set_fullscreen -.. autofunction:: tdl.set_title -.. autofunction:: tdl.get_fps -.. autofunction:: tdl.set_fps -.. autofunction:: tdl.force_resolution - -.. autoexception:: tdl.TDLError - -tdl.Console ------------ -.. autoclass:: tdl.Console - :members: - :inherited-members: - -tdl.Window ----------- -.. autoclass:: tdl.Window - :members: - -tdl.event -========= -.. automodule:: tdl.event - :members: - -tdl.map -======= -.. automodule:: tdl.map - :members: - -tdl.noise -========= -.. automodule:: tdl.noise - :members: diff --git a/setup.cfg b/setup.cfg index a2ccaa80..4a1f21d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,10 +2,8 @@ test=pytest [tool:pytest] -addopts=tcod/ tdl/ tests/ --doctest-modules --cov=tcod --cov=tdl --capture=sys +addopts=tcod/ tests/ --doctest-modules --cov=tcod --capture=sys filterwarnings = - ignore:The tdl module has been deprecated:DeprecationWarning - ignore:::tdl.map ignore::DeprecationWarning:tcod.libtcodpy ignore::PendingDeprecationWarning:tcod.libtcodpy ignore:This class may perform poorly and is no longer needed.::tcod.map diff --git a/setup.py b/setup.py index 09c74f98..f45600de 100755 --- a/setup.py +++ b/setup.py @@ -134,8 +134,8 @@ def check_sdl_version(): "Forum": "https://github.com/libtcod/python-tcod/discussions", }, py_modules=["libtcodpy"], - packages=["tdl", "tcod"], - package_data={"tdl": ["*.png"], "tcod": get_package_data()}, + packages=["tcod"], + package_data={"tcod": get_package_data()}, python_requires=">=3.6", install_requires=[ "cffi~=1.13", # Also required by pyproject.toml. diff --git a/tdl/__init__.py b/tdl/__init__.py deleted file mode 100755 index d64e180c..00000000 --- a/tdl/__init__.py +++ /dev/null @@ -1,1341 +0,0 @@ -""" - .. deprecated:: 8.4 - The :term:`python-tdl` module has been a total disaster and now exists - mainly as a historical curiosity and as a stepping stone for what would - eventually become :term:`python-tcod`. - - Getting Started - =============== - Once the library is imported you can load the font you want to use with - :any:`tdl.set_font`. - This is optional and when skipped will use a decent default font. - - After that you call :any:`tdl.init` to set the size of the window and - get the root console in return. - This console is the canvas to what will appear on the screen. - - Indexing Consoles - ================= - For most methods taking a position you can use Python-style negative - indexes to refer to the opposite side of a console with (-1, -1) - starting at the bottom right. - You can also check if a point is part of a console using containment - logic i.e. ((x, y) in console). - - You may also iterate over a console using a for statement. This returns - every x,y coordinate available to draw on but it will be extremely slow - to actually operate on every coordinate individualy. - Try to minimize draws by using an offscreen :any:`Console`, only drawing - what needs to be updated, and using :any:`Console.blit`. - - Drawing and Colors - ================== - - Once you have the root console from :any:`tdl.init` you can start drawing - on it using a method such as :any:`Console.draw_char`. - When using this method you can have the char parameter be an integer or a - single character string. - - The fg and bg parameters expect a variety of types. - The parameters default to Ellipsis which will tell the function to - use the colors previously set by the :any:`Console.set_colors` method. - The colors set by :any`Console.set_colors` are per each - :any:`Console`/:any:`Window` and default to white on black. - You can use a 3-item list/tuple of [red, green, blue] with integers in - the 0-255 range with [0, 0, 0] being black and [255, 255, 255] being - white. - You can even use a single integer of 0xRRGGBB if you like. - - Using None in the place of any of the three parameters (char, fg, bg) - will tell the function to not overwrite that color or character. - - After the drawing functions are called a call to :any:`tdl.flush` will - update the screen. -""" - -from __future__ import (absolute_import, division, - print_function, unicode_literals) - -import sys as _sys -import os as _os - -import array as _array -import weakref as _weakref -import itertools as _itertools -import textwrap as _textwrap -import struct as _struct -import re as _re -import warnings as _warnings - -from tcod import ffi as _ffi -from tcod import lib as _lib - -import tdl.event -import tdl.noise -import tdl.map -import tdl.style as _style - -from tcod import __version__ - -_warnings.warn( - "The tdl module has been deprecated.", - DeprecationWarning, - stacklevel=2, -) - -_IS_PYTHON3 = (_sys.version_info[0] == 3) - -if _IS_PYTHON3: # some type lists to use with isinstance - _INTTYPES = (int,) - _NUMTYPES = (int, float) - _STRTYPES = (str, bytes) -else: - _INTTYPES = (int, long) - _NUMTYPES = (int, long, float) - _STRTYPES = (str, unicode) - -def _encodeString(string): # still used for filepaths, and that's about it - "changes string into bytes if running in python 3, for sending to ctypes" - if isinstance(string, _STRTYPES): - return string.encode() - return string - -def _format_char(char): - """Prepares a single character for passing to ctypes calls, needs to return - an integer but can also pass None which will keep the current character - instead of overwriting it. - - This is called often and needs to be optimized whenever possible. - """ - if char is None: - return -1 - if isinstance(char, _STRTYPES) and len(char) == 1: - return ord(char) - try: - return int(char) # allow all int-like objects - except: - raise TypeError('char single character string, integer, or None\nReceived: ' + repr(char)) - -_utf32_codec = {'little': 'utf-32le', 'big': 'utf-32le'}[_sys.byteorder] - -def _format_str(string): - """Attempt fast string handing by decoding directly into an array.""" - if isinstance(string, _STRTYPES): - if _IS_PYTHON3: - array = _array.array('I') - array.frombytes(string.encode(_utf32_codec)) - else: # Python 2 - if isinstance(string, unicode): - array = _array.array(b'I') - array.fromstring(string.encode(_utf32_codec)) - else: - array = _array.array(b'B') - array.fromstring(string) - return array - return string - -_fontinitialized = False -_rootinitialized = False -_rootConsoleRef = None - -_put_char_ex = _lib.TDL_console_put_char_ex - -# python 2 to 3 workaround -if _sys.version_info[0] == 2: - int_types = (int, long) -else: - int_types = int - - -def _format_color(color, default=Ellipsis): - if color is Ellipsis: - return default - if color is None: - return -1 - if isinstance(color, (tuple, list)) and len(color) == 3: - return (color[0] << 16) + (color[1] << 8) + color[2] - try: - return int(color) # allow all int-like objects - except: - raise TypeError('fg and bg must be a 3 item tuple, integer, Ellipsis, or None\nReceived: ' + repr(color)) - -def _to_tcod_color(color): - return _ffi.new('TCOD_color_t *', (color >> 16 & 0xff, - color >> 8 & 0xff, - color & 0xff)) - -def _getImageSize(filename): - """Try to get the width and height of a bmp of png image file""" - result = None - file = open(filename, 'rb') - if file.read(8) == b'\x89PNG\r\n\x1a\n': # PNG - while 1: - length, = _struct.unpack('>i', file.read(4)) - chunkID = file.read(4) - if chunkID == '': # EOF - break - if chunkID == b'IHDR': - # return width, height - result = _struct.unpack('>ii', file.read(8)) - break - file.seek(4 + length, 1) - file.close() - return result - file.seek(0) - if file.read(8) == b'BM': # Bitmap - file.seek(18, 0) # skip to size data - result = _struct.unpack('= width: - x -= width - y += 1 - while y >= height: - if self._scrollMode == 'scroll': - y -= 1 - self.scroll(0, -1) - elif self._scrollMode == 'error': - # reset the cursor on error - self._cursor = (0, 0) - raise TDLError('Cursor has reached the end of the console') - return (x, y) - - def set_mode(self, mode): - """Configure how this console will react to the cursor writing past the - end if the console. - - This is for methods that use the virtual cursor, such as - :any:`print_str`. - - Args: - mode (Text): The mode to set. - - Possible settings are: - - - 'error' - A TDLError will be raised once the cursor - reaches the end of the console. Everything up until - the error will still be drawn. - This is the default setting. - - 'scroll' - The console will scroll up as stuff is - written to the end. - You can restrict the region with :any:`tdl.Window` when - doing this. - - ..seealso:: :any:`write`, :any:`print_str` - """ - MODES = ['error', 'scroll'] - if mode.lower() not in MODES: - raise TDLError('mode must be one of %s, got %s' % (MODES, repr(mode))) - self._scrollMode = mode.lower() - - def set_colors(self, fg=None, bg=None): - """Sets the colors to be used with the L{print_str} and draw_* methods. - - Values of None will only leave the current values unchanged. - - Args: - fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - .. seealso:: :any:`move`, :any:`print_str` - """ - if fg is not None: - self._fg = _format_color(fg, self._fg) - if bg is not None: - self._bg = _format_color(bg, self._bg) - - def print_str(self, string): - """Print a string at the virtual cursor. - - Handles special characters such as '\\n' and '\\r'. - Printing past the bottom of the console will scroll everything upwards - if :any:`set_mode` is set to 'scroll'. - - Colors can be set with :any:`set_colors` and the virtual cursor can - be moved with :any:`move`. - - Args: - string (Text): The text to print. - - .. seealso:: :any:`draw_str`, :any:`move`, :any:`set_colors`, - :any:`set_mode`, :any:`write`, :any:`Window` - """ - x, y = self._cursor - for char in string: - if char == '\n': # line break - x = 0 - y += 1 - continue - if char == '\r': # return - x = 0 - continue - x, y = self._normalizeCursor(x, y) - self.draw_char(x, y, char, self._fg, self._bg) - x += 1 - self._cursor = (x, y) - - def write(self, string): - """This method mimics basic file-like behaviour. - - Because of this method you can replace sys.stdout or sys.stderr with - a :any:`Console` or :any:`Window` instance. - - This is a convoluted process and behaviour seen now can be excepted to - change on later versions. - - Args: - string (Text): The text to write out. - - .. seealso:: :any:`set_colors`, :any:`set_mode`, :any:`Window` - """ - # some 'basic' line buffer stuff. - # there must be an easier way to do this. The textwrap module didn't - # help much. - x, y = self._normalizeCursor(*self._cursor) - width, height = self.get_size() - wrapper = _textwrap.TextWrapper(initial_indent=(' '*x), width=width) - writeLines = [] - for line in string.split('\n'): - if line: - writeLines += wrapper.wrap(line) - wrapper.initial_indent = '' - else: - writeLines.append([]) - - for line in writeLines: - x, y = self._normalizeCursor(x, y) - self.draw_str(x, y, line[x:], self._fg, self._bg) - y += 1 - x = 0 - y -= 1 - self._cursor = (x, y) - - def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis): - """Draws a single character. - - Args: - x (int): x-coordinate to draw on. - y (int): y-coordinate to draw on. - char (Optional[Union[int, Text]]): An integer, single character - string, or None. - - You can set the char parameter as None if you only want to change - the colors of the tile. - fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - - Raises: AssertionError: Having x or y values that can't be placed - inside of the console will raise an AssertionError. - You can use always use ((x, y) in console) to - check if a tile is drawable. - - .. seealso:: :any:`get_char` - """ - #x, y = self._normalizePoint(x, y) - _put_char_ex(self.console_c, x, y, _format_char(char), - _format_color(fg, self._fg), _format_color(bg, self._bg), 1) - - def draw_str(self, x, y, string, fg=Ellipsis, bg=Ellipsis): - """Draws a string starting at x and y. - - A string that goes past the right side will wrap around. A string - wrapping to below the console will raise :any:`tdl.TDLError` but will - still be written out. - This means you can safely ignore the errors with a - try..except block if you're fine with partially written strings. - - \\r and \\n are drawn on the console as normal character tiles. No - special encoding is done and any string will translate to the character - table as is. - - For a string drawing operation that respects special characters see - :any:`print_str`. - - Args: - x (int): x-coordinate to start at. - y (int): y-coordinate to start at. - string (Union[Text, Iterable[int]]): A string or an iterable of - numbers. - - Special characters are ignored and rendered as any other - character. - fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - - Raises: - AssertionError: Having x or y values that can't be placed inside - of the console will raise an AssertionError. - - You can use always use ``((x, y) in console)`` to check if - a tile is drawable. - - .. seealso:: :any:`print_str` - """ - - x, y = self._normalizePoint(x, y) - fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg) - width, height = self.get_size() - def _drawStrGen(x=x, y=y, string=string, width=width, height=height): - """Generator for draw_str - - Iterates over ((x, y), ch) data for _set_batch, raising an - error if the end of the console is reached. - """ - for char in _format_str(string): - if y == height: - raise TDLError('End of console reached.') - yield((x, y), char) - x += 1 # advance cursor - if x == width: # line break - x = 0 - y += 1 - self._set_batch(_drawStrGen(), fg, bg) - - def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis): - """Draws a rectangle starting from x and y and extending to width and height. - - If width or height are None then it will extend to the edge of the console. - - Args: - x (int): x-coordinate for the top side of the rect. - y (int): y-coordinate for the left side of the rect. - width (Optional[int]): The width of the rectangle. - - Can be None to extend to the bottom right of the - console or can be a negative number to be sized reltive - to the total size of the console. - height (Optional[int]): The height of the rectangle. - string (Optional[Union[Text, int]]): An integer, single character - string, or None. - - You can set the string parameter as None if you only want - to change the colors of an area. - fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - - Raises: - AssertionError: Having x or y values that can't be placed inside - of the console will raise an AssertionError. - - You can use always use ``((x, y) in console)`` to check if a tile - is drawable. - .. seealso:: :any:`clear`, :any:`draw_frame` - """ - x, y, width, height = self._normalizeRect(x, y, width, height) - fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg) - char = _format_char(string) - # use itertools to make an x,y grid - # using ctypes here reduces type converstions later - #grid = _itertools.product((_ctypes.c_int(x) for x in range(x, x + width)), - # (_ctypes.c_int(y) for y in range(y, y + height))) - grid = _itertools.product((x for x in range(x, x + width)), - (y for y in range(y, y + height))) - # zip the single character in a batch variable - batch = zip(grid, _itertools.repeat(char, width * height)) - self._set_batch(batch, fg, bg, nullChar=(char is None)) - - def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis): - """Similar to L{draw_rect} but only draws the outline of the rectangle. - - `width or `height` can be None to extend to the bottom right of the - console or can be a negative number to be sized reltive - to the total size of the console. - - Args: - x (int): The x-coordinate to start on. - y (int): The y-coordinate to start on. - width (Optional[int]): Width of the rectangle. - height (Optional[int]): Height of the rectangle. - string (Optional[Union[Text, int]]): An integer, single character - string, or None. - - You can set this parameter as None if you only want - to change the colors of an area. - fg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - bg (Optional[Union[Tuple[int, int, int], int, Ellipsis]]) - - Raises: - AssertionError: Having x or y values that can't be placed inside - of the console will raise an AssertionError. - - You can use always use ``((x, y) in console)`` to check if a tile - is drawable. - .. seealso:: :any:`draw_rect`, :any:`Window` - """ - x, y, width, height = self._normalizeRect(x, y, width, height) - fg, bg = _format_color(fg, self._fg), _format_color(bg, self._bg) - char = _format_char(string) - if width == 1 or height == 1: # it's just a single width line here - return self.draw_rect(x, y, width, height, char, fg, bg) - - # draw sides of frame with draw_rect - self.draw_rect(x, y, 1, height, char, fg, bg) - self.draw_rect(x, y, width, 1, char, fg, bg) - self.draw_rect(x + width - 1, y, 1, height, char, fg, bg) - self.draw_rect(x, y + height - 1, width, 1, char, fg, bg) - - def blit(self, source, x=0, y=0, width=None, height=None, srcX=0, srcY=0, - fg_alpha=1.0, bg_alpha=1.0): - """Blit another console or Window onto the current console. - - By default it blits the entire source to the topleft corner. - - Args: - source (Union[tdl.Console, tdl.Window]): The blitting source. - A console can blit to itself without any problems. - x (int): x-coordinate of this console to blit on. - y (int): y-coordinate of this console to blit on. - width (Optional[int]): Width of the rectangle. - - Can be None to extend as far as possible to the - bottom right corner of the blit area or can be a negative - number to be sized reltive to the total size of the - B{destination} console. - height (Optional[int]): Height of the rectangle. - srcX (int): x-coordinate of the source region to blit. - srcY (int): y-coordinate of the source region to blit. - fg_alpha (float): The foreground alpha. - """ - assert isinstance(source, (Console, Window)), "source muse be a Window or Console instance" - - # handle negative indexes and rects - # negative width and height will be set realtive to the destination - # and will also be clamped to the smallest Console - x, y, width, height = self._normalizeRect(x, y, width, height) - srcX, srcY, width, height = source._normalizeRect(srcX, srcY, width, height) - - # translate source and self if any of them are Window instances - srcX, srcY = source._translate(srcX, srcY) - source = source.console - x, y = self._translate(x, y) - self = self.console - - if self == source: - # if we are the same console then we need a third console to hold - # onto the data, otherwise it tries to copy into itself and - # starts destroying everything - tmp = Console(width, height) - _lib.TCOD_console_blit(source.console_c, - srcX, srcY, width, height, - tmp.console_c, 0, 0, fg_alpha, bg_alpha) - _lib.TCOD_console_blit(tmp.console_c, 0, 0, width, height, - self.console_c, x, y, fg_alpha, bg_alpha) - else: - _lib.TCOD_console_blit(source.console_c, - srcX, srcY, width, height, - self.console_c, x, y, fg_alpha, bg_alpha) - - def get_cursor(self): - """Return the virtual cursor position. - - The cursor can be moved with the :any:`move` method. - - Returns: - Tuple[int, int]: The (x, y) coordinate of where :any:`print_str` - will continue from. - - .. seealso:: :any:move` - """ - x, y = self._cursor - width, height = self.parent.get_size() - while x >= width: - x -= width - y += 1 - if y >= height and self.scrollMode == 'scroll': - y = height - 1 - return x, y - - def get_size(self): - """Return the size of the console as (width, height) - - Returns: - Tuple[int, int]: A (width, height) tuple. - """ - return self.width, self.height - - def __iter__(self): - """Return an iterator with every possible (x, y) value for this console. - - It goes without saying that working on the console this way is a - slow process, especially for Python, and should be minimized. - - Returns: - Iterator[Tuple[int, int]]: An ((x, y), ...) iterator. - """ - return _itertools.product(range(self.width), range(self.height)) - - def move(self, x, y): - """Move the virtual cursor. - - Args: - x (int): x-coordinate to place the cursor. - y (int): y-coordinate to place the cursor. - - .. seealso:: :any:`get_cursor`, :any:`print_str`, :any:`write` - """ - self._cursor = self._normalizePoint(x, y) - - def scroll(self, x, y): - """Scroll the contents of the console in the direction of x,y. - - Uncovered areas will be cleared to the default background color. - Does not move the virutal cursor. - - Args: - x (int): Distance to scroll along the x-axis. - y (int): Distance to scroll along the y-axis. - - Returns: - Iterator[Tuple[int, int]]: An iterator over the (x, y) coordinates - of any tile uncovered after scrolling. - - .. seealso:: :any:`set_colors` - """ - assert isinstance(x, _INTTYPES), "x must be an integer, got %s" % repr(x) - assert isinstance(y, _INTTYPES), "y must be an integer, got %s" % repr(x) - def getSlide(x, length): - """get the parameters needed to scroll the console in the given - direction with x - returns (x, length, srcx) - """ - if x > 0: - srcx = 0 - length -= x - elif x < 0: - srcx = abs(x) - x = 0 - length -= srcx - else: - srcx = 0 - return x, length, srcx - def getCover(x, length): - """return the (x, width) ranges of what is covered and uncovered""" - cover = (0, length) # everything covered - uncover = None # nothing uncovered - if x > 0: # left side uncovered - cover = (x, length - x) - uncover = (0, x) - elif x < 0: # right side uncovered - x = abs(x) - cover = (0, length - x) - uncover = (length - x, x) - return cover, uncover - - width, height = self.get_size() - if abs(x) >= width or abs(y) >= height: - return self.clear() # just clear the console normally - - # get the ranges of the areas that will be uncovered - coverX, uncoverX = getCover(x, width) - coverY, uncoverY = getCover(y, height) - # so at this point we know that coverX and coverY makes a rect that - # encases the area that we end up blitting to. uncoverX/Y makes a - # rect in the corner of the uncovered area. So we need to combine - # the uncoverX/Y with coverY/X to make what's left of the uncovered - # area. Explaining it makes it mush easier to do now. - - # But first we need to blit. - x, width, srcx = getSlide(x, width) - y, height, srcy = getSlide(y, height) - self.blit(self, x, y, width, height, srcx, srcy) - if uncoverX: # clear sides (0x20 is space) - self.draw_rect(uncoverX[0], coverY[0], uncoverX[1], coverY[1], - 0x20, self._fg, self._bg) - if uncoverY: # clear top/bottom - self.draw_rect(coverX[0], uncoverY[0], coverX[1], uncoverY[1], - 0x20, self._fg, self._bg) - if uncoverX and uncoverY: # clear corner - self.draw_rect(uncoverX[0], uncoverY[0], uncoverX[1], uncoverY[1], - 0x20, self._fg, self._bg) - - def clear(self, fg=Ellipsis, bg=Ellipsis): - """Clears the entire L{Console}/L{Window}. - - Unlike other drawing functions, fg and bg can not be None. - - Args: - fg (Union[Tuple[int, int, int], int, Ellipsis]) - bg (Union[Tuple[int, int, int], int, Ellipsis]) - - .. seealso:: :any:`draw_rect` - """ - raise NotImplementedError('this method is overwritten by subclasses') - - def get_char(self, x, y): - """Return the character and colors of a tile as (ch, fg, bg) - - This method runs very slowly as is not recommended to be called - frequently. - - Args: - x (int): The x-coordinate to pick. - y (int): The y-coordinate to pick. - - Returns: - Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]: A 3-item - tuple: `(int, fg, bg)` - - The first item is an integer of the - character at the position (x, y) the second and third are the - foreground and background colors respectfully. - - .. seealso:: :any:`draw_char` - """ - raise NotImplementedError('Method here only exists for the docstring') - - def __contains__(self, position): - """Use ``((x, y) in console)`` to check if a position is drawable on - this console. - """ - x, y = position - return (0 <= x < self.width) and (0 <= y < self.height) - -class Console(_BaseConsole): - """Contains character and color data and can be drawn to. - - The console created by the :any:`tdl.init` function is the root console - and is the console that is rendered to the screen with :any:`flush`. - - Any console created from the Console class is an off-screen console that - can be drawn on before being :any:`blit` to the root console. - - Args: - width (int): Width of the new console in tiles - height (int): Height of the new console in tiles - """ - - def __init__(self, width, height): - _BaseConsole.__init__(self) - self.console_c = _lib.TCOD_console_new(width, height) - self.console = self - self.width = width - self.height = height - - @property - def tcod_console(self): - return self.console_c - @tcod_console.setter - def tcod_console(self, value): - self.console_c = value - - @classmethod - def _newConsole(cls, console): - """Make a Console instance, from a console ctype""" - self = cls.__new__(cls) - _BaseConsole.__init__(self) - self.console_c = console - self.console = self - self.width = _lib.TCOD_console_get_width(console) - self.height = _lib.TCOD_console_get_height(console) - return self - - def _root_unhook(self): - """Change this root console into a normal Console object and - delete the root console from TCOD - """ - global _rootinitialized, _rootConsoleRef - # do we recognise this as the root console? - # if not then assume the console has already been taken care of - if(_rootConsoleRef and _rootConsoleRef() is self): - # turn this console into a regular console - unhooked = _lib.TCOD_console_new(self.width, self.height) - _lib.TCOD_console_blit(self.console_c, - 0, 0, self.width, self.height, - unhooked, 0, 0, 1, 1) - # delete root console from TDL and TCOD - _rootinitialized = False - _rootConsoleRef = None - _lib.TCOD_console_delete(self.console_c) - # this Console object is now a regular console - self.console_c = unhooked - - def __del__(self): - """ - If the main console is garbage collected then the window will be closed as well - """ - if self.console_c is None: - return # this console was already deleted - if self.console_c is _ffi.NULL: - # a pointer to the special root console - self._root_unhook() # unhook the console and leave it to the GC - return - # this is a normal console pointer and can be safely deleted - _lib.TCOD_console_delete(self.console_c) - self.console_c = None - - def __copy__(self): - # make a new class and blit - clone = self.__class__(self.width, self.height) - clone.blit(self) - return clone - - def __getstate__(self): - # save data from get_char - data = [self.get_char(x, y) for x,y in - _itertools.product(range(self.width), range(self.height))] - return self.width, self.height, data - - def __setstate__(self, state): - # make console from __init__ and unpack a get_char array - width, height, data = state - self.__init__(width, height) - for (x, y), graphic in zip(_itertools.product(range(width), - range(height)), data): - self.draw_char(x, y, *graphic) - - @staticmethod - def _translate(x, y): - """Convertion x and y to their position on the root Console for this Window - - Because this is a Console instead of a Window we return the paramaters - untouched""" - return x, y - - def clear(self, fg=Ellipsis, bg=Ellipsis): - # inherit docstring - assert fg is not None and bg is not None, 'Can not use None with clear' - fg = _format_color(fg, self._fg) - bg = _format_color(bg, self._bg) - _lib.TCOD_console_set_default_foreground(self.console_c, - _to_tcod_color(fg)[0]) - _lib.TCOD_console_set_default_background(self.console_c, - _to_tcod_color(bg)[0]) - _lib.TCOD_console_clear(self.console_c) - - - def _set_char(self, x, y, char, fg=None, bg=None, - bgblend=_lib.TCOD_BKGND_SET): - """ - Sets a character. - This is called often and is designed to be as fast as possible. - - Because of the need for speed this function will do NO TYPE CHECKING - AT ALL, it's up to the drawing functions to use the functions: - _format_char and _format_color before passing to this.""" - # values are already formatted, honestly this function is redundant - return _put_char_ex(self.console_c, x, y, char, fg, bg, bgblend) - - def _set_batch(self, batch, fg, bg, bgblend=1, nullChar=False): - """ - Try to perform a batch operation otherwise fall back to _set_char. - If fg and bg are defined then this is faster but not by very - much. - - if any character is None then nullChar is True - - batch is a iterable of [(x, y), ch] items - """ - for (x, y), char in batch: - self._set_char(x, y, char, fg, bg, bgblend) - - def get_char(self, x, y): - # inherit docstring - x, y = self._normalizePoint(x, y) - char = _lib.TCOD_console_get_char(self.console_c, x, y) - bg = _lib.TCOD_console_get_char_background(self.console_c, x, y) - fg = _lib.TCOD_console_get_char_foreground(self.console_c, x, y) - return char, (fg.r, fg.g, fg.b), (bg.r, bg.g, bg.b) - - # Copy docstrings for Sphinx - clear.__doc__ = _BaseConsole.clear.__doc__ - get_char.__doc__ = _BaseConsole.get_char.__doc__ - - def __repr__(self): - return "" % (self.width, self.height) - - -class Window(_BaseConsole): - """Isolate part of a :any:`Console` or :any:`Window` instance. - - This classes methods are the same as :any:`tdl.Console` - - Making a Window and setting its width or height to None will extend it to - the edge of the console. - - This follows the normal rules for indexing so you can use a - negative integer to place the Window relative to the bottom - right of the parent Console instance. - - `width` or `height` can be set to None to extend as far as possible - to the bottom right corner of the parent Console or can be a - negative number to be sized reltive to the Consoles total size. - - Args: - console (Union(tdl.Console, tdl.Window)): The parent object. - x (int): x-coordinate to place the Window. - y (int): y-coordinate to place the Window. - width (Optional[int]): Width of the Window. - height (Optional[int]): Height of the Window. - """ - - __slots__ = ('parent', 'x', 'y') - - def __init__(self, console, x, y, width, height): - _BaseConsole.__init__(self) - assert isinstance(console, (Console, Window)), 'console parameter must be a Console or Window instance, got %s' % repr(console) - self.parent = console - self.x, self.y, self.width, self.height = console._normalizeRect(x, y, width, height) - if isinstance(console, Console): - self.console = console - else: - self.console = self.parent.console - - def _translate(self, x, y): - """Convertion x and y to their position on the root Console""" - # we add our position relative to our parent and then call then next parent up - return self.parent._translate((x + self.x), (y + self.y)) - - def clear(self, fg=Ellipsis, bg=Ellipsis): - # inherit docstring - assert fg is not None and bg is not None, 'Can not use None with clear' - if fg is Ellipsis: - fg = self._fg - if bg is Ellipsis: - bg = self._bg - self.draw_rect(0, 0, None, None, 0x20, fg, bg) - - def _set_char(self, x, y, char=None, fg=None, bg=None, bgblend=1): - self.parent._set_char((x + self.x), (y + self.y), char, fg, bg, bgblend) - - def _set_batch(self, batch, *args, **kargs): - # positional values will need to be translated to the parent console - myX = self.x # remove dots for speed up - myY = self.y - self.parent._set_batch((((x + myX, y + myY), ch) - for ((x, y), ch) in batch), *args, **kargs) - - - def draw_char(self, x, y, char, fg=Ellipsis, bg=Ellipsis): - # inherit docstring - x, y = self._normalizePoint(x, y) - if fg is Ellipsis: - fg = self._fg - if bg is Ellipsis: - bg = self._bg - self.parent.draw_char(x + self.x, y + self.y, char, fg, bg) - - def draw_rect(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis): - # inherit docstring - x, y, width, height = self._normalizeRect(x, y, width, height) - if fg is Ellipsis: - fg = self._fg - if bg is Ellipsis: - bg = self._bg - self.parent.draw_rect(x + self.x, y + self.y, width, height, - string, fg, bg) - - def draw_frame(self, x, y, width, height, string, fg=Ellipsis, bg=Ellipsis): - # inherit docstring - x, y, width, height = self._normalizeRect(x, y, width, height) - if fg is Ellipsis: - fg = self._fg - if bg is Ellipsis: - bg = self._bg - self.parent.draw_frame(x + self.x, y + self.y, width, height, - string, fg, bg) - - def get_char(self, x, y): - # inherit docstring - x, y = self._normalizePoint(x, y) - xtrans, ytrans = self._translate(x, y) - return self.console.get_char(xtrans, ytrans) - - def __repr__(self): - return "" % (self.x, self.y, - self.width, - self.height) - - -def init(width, height, title=None, fullscreen=False, renderer='SDL'): - """Start the main console with the given width and height and return the - root console. - - Call the consoles drawing functions. Then remember to use L{tdl.flush} to - make what's drawn visible on the console. - - Args: - width (int): width of the root console (in tiles) - height (int): height of the root console (in tiles) - title (Optiona[Text]): - Text to display as the window title. - - If left None it defaults to the running scripts filename. - fullscreen (bool): Can be set to True to start in fullscreen mode. - renderer (Text): Can be one of 'GLSL', 'OPENGL', or 'SDL'. - - Due to way Python works you're unlikely to see much of an - improvement by using 'GLSL' over 'OPENGL' as most of the - time Python is slow interacting with the console and the - rendering itself is pretty fast even on 'SDL'. - - Returns: - tdl.Console: The root console. - - Only what is drawn on the root console is - what's visible after a call to :any:`tdl.flush`. - After the root console is garbage collected, the window made by - this function will close. - - .. seealso:: - :any:`Console` :any:`set_font` - """ - RENDERERS = {'GLSL': 0, 'OPENGL': 1, 'SDL': 2} - global _rootinitialized, _rootConsoleRef - if not _fontinitialized: # set the default font to the one that comes with tdl - set_font(_os.path.join(__path__[0], 'terminal8x8.png'), - None, None, True, True) - - if renderer.upper() not in RENDERERS: - raise TDLError('No such render type "%s", expected one of "%s"' % (renderer, '", "'.join(RENDERERS))) - renderer = RENDERERS[renderer.upper()] - - # If a console already exists then make a clone to replace it - if _rootConsoleRef and _rootConsoleRef(): - # unhook the root console, turning into a regular console and deleting - # the root console from libTCOD - _rootConsoleRef()._root_unhook() - - if title is None: # use a default title - if _sys.argv: - # Use the script filename as the title. - title = _os.path.basename(_sys.argv[0]) - else: - title = 'python-tdl' - - _lib.TCOD_console_init_root(width, height, _encodeString(title), fullscreen, renderer) - - #event.get() # flush the libtcod event queue to fix some issues - # issues may be fixed already - - event._eventsflushed = False - _rootinitialized = True - rootconsole = Console._newConsole(_ffi.NULL) - _rootConsoleRef = _weakref.ref(rootconsole) - - return rootconsole - -def flush(): - """Make all changes visible and update the screen. - - Remember to call this function after drawing operations. - Calls to flush will enfore the frame rate limit set by :any:`tdl.set_fps`. - - This function can only be called after :any:`tdl.init` - """ - if not _rootinitialized: - raise TDLError('Cannot flush without first initializing with tdl.init') - # flush the OS event queue, preventing lock-ups if not done manually - event.get() - _lib.TCOD_console_flush() - -def set_font(path, columns=None, rows=None, columnFirst=False, - greyscale=False, altLayout=False): - """Changes the font to be used for this session. - This should be called before :any:`tdl.init` - - If the font specifies its size in its filename (i.e. font_NxN.png) then this - function can auto-detect the tileset formatting and the parameters columns - and rows can be left None. - - While it's possible you can change the font mid program it can sometimes - break in rare circumstances. So use caution when doing this. - - Args: - path (Text): A file path to a `.bmp` or `.png` file. - columns (int): - Number of columns in the tileset. - - Can be left None for auto-detection. - rows (int): - Number of rows in the tileset. - - Can be left None for auto-detection. - columnFirst (bool): - Defines if the characer order goes along the rows or colomns. - - It should be True if the charater codes 0-15 are in the - first column, and should be False if the characters 0-15 - are in the first row. - greyscale (bool): - Creates an anti-aliased font from a greyscale bitmap. - Otherwise it uses the alpha channel for anti-aliasing. - - Unless you actually need anti-aliasing from a font you - know uses a smooth greyscale channel you should leave - this on False. - altLayout (bool) - An alternative layout with space in the upper left corner. - The colomn parameter is ignored if this is True, - find examples of this layout in the `font/libtcod/` - directory included with the python-tdl source. - - Raises: - TDLError: Will be raised if no file is found at path or if auto- - detection fails. - """ - # put up some constants that are only used here - FONT_LAYOUT_ASCII_INCOL = 1 - FONT_LAYOUT_ASCII_INROW = 2 - FONT_TYPE_GREYSCALE = 4 - FONT_LAYOUT_TCOD = 8 - global _fontinitialized - _fontinitialized = True - flags = 0 - if altLayout: - flags |= FONT_LAYOUT_TCOD - elif columnFirst: - flags |= FONT_LAYOUT_ASCII_INCOL - else: - flags |= FONT_LAYOUT_ASCII_INROW - if greyscale: - flags |= FONT_TYPE_GREYSCALE - if not _os.path.exists(path): - raise TDLError('Font %r not found.' % _os.path.abspath(path)) - path = _os.path.abspath(path) - - # and the rest is the auto-detect script - imgSize = _getImageSize(path) # try to find image size - if imgSize: - fontWidth, fontHeight = None, None - imgWidth, imgHeight = imgSize - # try to get font size from filename - match = _re.match('.*?([0-9]+)[xX]([0-9]+)', _os.path.basename(path)) - if match: - fontWidth, fontHeight = match.groups() - fontWidth, fontHeight = int(fontWidth), int(fontHeight) - - # estimate correct tileset size - estColumns, remC = divmod(imgWidth, fontWidth) - estRows, remR = divmod(imgHeight, fontHeight) - if remC or remR: - _warnings.warn("Font may be incorrectly formatted.") - - if not columns: - columns = estColumns - if not rows: - rows = estRows - else: - # filename doesn't contain NxN, but we can still estimate the fontWidth - # and fontHeight given number of columns and rows. - if columns and rows: - fontWidth, remC = divmod(imgWidth, columns) - fontHeight, remR = divmod(imgHeight, rows) - if remC or remR: - _warnings.warn("Font may be incorrectly formatted.") - - # the font name excluded the fonts size - if not (columns and rows): - # no matched font size and no tileset is given - raise TDLError('%s has no font size in filename' % _os.path.basename(path)) - - if columns and rows: - # confirm user set options - if (fontWidth * columns != imgWidth or - fontHeight * rows != imgHeight): - _warnings.warn("set_font parameters are set as if the image size is (%d, %d) when the detected size is actually (%i, %i)" - % (fontWidth * columns, fontHeight * rows, - imgWidth, imgHeight)) - else: - _warnings.warn("%s is probably not an image." % _os.path.basename(path)) - - if not (columns and rows): - # didn't auto-detect - raise TDLError('Can not auto-detect the tileset of %s' % _os.path.basename(path)) - - _lib.TCOD_console_set_custom_font(_encodeString(path), flags, columns, rows) - -def get_fullscreen(): - """Returns True if program is fullscreen. - - Returns: - bool: Returns True if the application is in full-screen mode. - Otherwise returns False. - """ - if not _rootinitialized: - raise TDLError('Initialize first with tdl.init') - return _lib.TCOD_console_is_fullscreen() - -def set_fullscreen(fullscreen): - """Changes the fullscreen state. - - Args: - fullscreen (bool): True for full-screen, False for windowed mode. - """ - if not _rootinitialized: - raise TDLError('Initialize first with tdl.init') - _lib.TCOD_console_set_fullscreen(fullscreen) - -def set_title(title): - """Change the window title. - - Args: - title (Text): The new title text. - """ - if not _rootinitialized: - raise TDLError('Not initilized. Set title with tdl.init') - _lib.TCOD_console_set_window_title(_encodeString(title)) - -def screenshot(path=None): - """Capture the screen and save it as a png file. - - If path is None then the image will be placed in the current - folder with the names: - ``screenshot001.png, screenshot002.png, ...`` - - Args: - path (Optional[Text]): The file path to save the screenshot. - """ - if not _rootinitialized: - raise TDLError('Initialize first with tdl.init') - if isinstance(path, str): - _lib.TCOD_sys_save_screenshot(_encodeString(path)) - elif path is None: # save to screenshot001.png, screenshot002.png, ... - filelist = _os.listdir('.') - n = 1 - filename = 'screenshot%.3i.png' % n - while filename in filelist: - n += 1 - filename = 'screenshot%.3i.png' % n - _lib.TCOD_sys_save_screenshot(_encodeString(filename)) - else: # assume file like obj - #save to temp file and copy to file-like obj - tmpname = _os.tempnam() - _lib.TCOD_sys_save_screenshot(_encodeString(tmpname)) - with tmpname as tmpfile: - path.write(tmpfile.read()) - _os.remove(tmpname) - #else: - # raise TypeError('path is an invalid type: %s' % type(path)) - -def set_fps(fps): - """Set the maximum frame rate. - - Further calls to :any:`tdl.flush` will limit the speed of - the program to run at `fps` frames per second. This can - also be set to None to remove the frame rate limit. - - Args: - fps (optional[int]): The frames per second limit, or None. - """ - _lib.TCOD_sys_set_fps(fps or 0) - -def get_fps(): - """Return the current frames per second of the running program set by - :any:`set_fps` - - Returns: - int: The frame rate set by :any:`set_fps`. - If there is no current limit, this will return 0. - """ - return _lib.TCOD_sys_get_fps() - -def force_resolution(width, height): - """Change the fullscreen resoulution. - - Args: - width (int): Width in pixels. - height (int): Height in pixels. - """ - _lib.TCOD_sys_force_fullscreen_resolution(width, height) - - -__all__ = [_var for _var in locals().keys() if _var[0] != '_'] # remove modules from __all__ -__all__ += ['_BaseConsole'] # keep this object public to show the documentation in epydoc -__all__.remove('absolute_import') -__all__.remove('division') -__all__.remove('print_function') -__all__.remove('unicode_literals') - -# backported function names -_BaseConsole.setMode = _style.backport(_BaseConsole.set_mode) -_BaseConsole.setColors = _style.backport(_BaseConsole.set_colors) -_BaseConsole.printStr = _style.backport(_BaseConsole.print_str) -_BaseConsole.drawChar = _style.backport(_BaseConsole.draw_char) -_BaseConsole.drawStr = _style.backport(_BaseConsole.draw_str) -_BaseConsole.drawRect = _style.backport(_BaseConsole.draw_rect) -_BaseConsole.drawFrame = _style.backport(_BaseConsole.draw_frame) -_BaseConsole.getCursor = _style.backport(_BaseConsole.get_cursor) -_BaseConsole.getSize = _style.backport(_BaseConsole.get_size) -_BaseConsole.getChar = _style.backport(_BaseConsole.get_char) - -Console.getChar = _style.backport(Console.get_char) - -Window.drawChar = _style.backport(Window.draw_char) -Window.drawRect = _style.backport(Window.draw_rect) -Window.drawFrame = _style.backport(Window.draw_frame) -Window.getChar = _style.backport(Window.get_char) - -setFont = _style.backport(set_font) -getFullscreen = _style.backport(get_fullscreen) -setFullscreen = _style.backport(set_fullscreen) -setTitle = _style.backport(set_title) -setFPS = _style.backport(set_fps) -getFPS = _style.backport(get_fps) -forceResolution = _style.backport(force_resolution) diff --git a/tdl/event.py b/tdl/event.py deleted file mode 100755 index 82f8aa48..00000000 --- a/tdl/event.py +++ /dev/null @@ -1,511 +0,0 @@ -""" - This module handles user input. - - To handle user input you will likely want to use the :any:`event.get` - function or create a subclass of :any:`event.App`. - - - :any:`tdl.event.get` iterates over recent events. - - :any:`tdl.event.App` passes events to the overridable methods: ``ev_*`` - and ``key_*``. - - But there are other options such as :any:`event.key_wait` and - :any:`event.is_window_closed`. - - A few event attributes are actually string constants. - Here's a reference for those: - - - :any:`Event.type`: - 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', or 'MOUSEMOTION.' - - :any:`MouseButtonEvent.button` (found in :any:`MouseDown` and - :any:`MouseUp` events): - 'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN' - - :any:`KeyEvent.key` (found in :any:`KeyDown` and :any:`KeyUp` events): - 'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL', - 'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP', - 'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN', - 'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9', - 'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2', - 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', - 'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR' -""" - -import time as _time - -from tcod import ffi as _ffi -from tcod import lib as _lib - -import tdl as _tdl -from . import style as _style - -_eventQueue = [] -_pushedEvents = [] -_eventsflushed = False - -_mousel = 0 -_mousem = 0 -_mouser = 0 - -# this interprets the constants from libtcod and makes a key -> keyname dictionary -def _parseKeyNames(lib): - """ - returns a dictionary mapping of human readable key names to their keycodes - this parses constants with the names of K_* and makes code=name pairs - this is for KeyEvent.key variable and that enables things like: - if (event.key == 'PAGEUP'): - """ - _keyNames = {} - for attr in dir(lib): # from the modules variables - if attr[:6] == 'TCODK_': # get the K_* constants - _keyNames[getattr(lib, attr)] = attr[6:] # and make CODE=NAME pairs - return _keyNames - -_keyNames = _parseKeyNames(_lib) - -class Event(object): - """Base Event class. - - You can easily subclass this to make your own events. Be sure to set - the class attribute L{Event.type} for it to be passed to a custom - :any:`App` ev_* method. - """ - type = None - """String constant representing the type of event. - - The :any:`App` ev_* methods depend on this attribute. - - Can be: 'QUIT', 'KEYDOWN', 'KEYUP', 'MOUSEDOWN', 'MOUSEUP', - or 'MOUSEMOTION.' - """ - - def __repr__(self): - """List an events public attributes when printed. - """ - attrdict = {} - for varname in dir(self): - if '_' == varname[0]: - continue - attrdict[varname] = self.__getattribute__(varname) - return '%s Event %s' % (self.__class__.__name__, repr(attrdict)) - -class Quit(Event): - """Fired when the window is closed by the user. - """ - __slots__ = () - type = 'QUIT' - -class KeyEvent(Event): - """Base class for key events.""" - - def __init__(self, key='', char='', text='', shift=False, - left_alt=False, right_alt=False, - left_control=False, right_control=False, - left_meta=False, right_meta=False): - # Convert keycodes into string, but use string if passed - self.key = key if isinstance(key, str) else _keyNames[key] - """Text: Human readable names of the key pressed. - Non special characters will show up as 'CHAR'. - - Can be one of - 'NONE', 'ESCAPE', 'BACKSPACE', 'TAB', 'ENTER', 'SHIFT', 'CONTROL', - 'ALT', 'PAUSE', 'CAPSLOCK', 'PAGEUP', 'PAGEDOWN', 'END', 'HOME', 'UP', - 'LEFT', 'RIGHT', 'DOWN', 'PRINTSCREEN', 'INSERT', 'DELETE', 'LWIN', - 'RWIN', 'APPS', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'KP0', 'KP1', 'KP2', 'KP3', 'KP4', 'KP5', 'KP6', 'KP7', 'KP8', 'KP9', - 'KPADD', 'KPSUB', 'KPDIV', 'KPMUL', 'KPDEC', 'KPENTER', 'F1', 'F2', - 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', - 'NUMLOCK', 'SCROLLLOCK', 'SPACE', 'CHAR' - - For the actual character instead of 'CHAR' use :any:`keychar`. - """ - self.char = char.replace('\x00', '') # change null to empty string - """Text: A single character string of the letter or symbol pressed. - - Special characters like delete and return are not cross-platform. - L{key} or L{keychar} should be used instead for special keys. - Characters are also case sensitive. - """ - # get the best out of self.key and self.char - self.keychar = self.char if self.key == 'CHAR' else self.key - """Similar to L{key} but returns a case sensitive letter or symbol - instead of 'CHAR'. - - This variable makes available the widest variety of symbols and should - be used for key-mappings or anywhere where a narrower sample of keys - isn't needed. - """ - self.text = text - - self.left_alt = self.leftAlt = bool(left_alt) - """bool:""" - self.right_alt = self.rightAlt = bool(right_alt) - """bool:""" - self.left_control = self.leftCtrl = bool(left_control) - """bool:""" - self.right_control = self.rightCtrl = bool(right_control) - """bool:""" - self.shift = bool(shift) - """bool: True if shift was held down during this event.""" - self.alt = self.left_alt or self.right_alt - """bool: True if alt was held down during this event.""" - self.control = self.left_control or self.right_control - """bool: True if control was held down during this event.""" - self.left_meta = bool(left_meta) - self.right_meta = bool(right_meta) - self.meta = self.left_meta or self.right_meta - - def __repr__(self): - parameters = [] - for attr in ('key', 'char', 'text', 'shift', - 'left_alt', 'right_alt', - 'left_control', 'right_control', - 'left_meta', 'right_meta'): - value = getattr(self, attr) - if value: - parameters.append('%s=%r' % (attr, value)) - return '%s(%s)' % (self.__class__.__name__, ', '.join(parameters)) - -class KeyDown(KeyEvent): - """Fired when the user presses a key on the keyboard or a key repeats. - """ - type = 'KEYDOWN' - -class KeyUp(KeyEvent): - """Fired when the user releases a key on the keyboard. - """ - type = 'KEYUP' - -_mouseNames = {1: 'LEFT', 2: 'MIDDLE', 3: 'RIGHT', 4: 'SCROLLUP', 5: 'SCROLLDOWN'} -class MouseButtonEvent(Event): - """Base class for mouse button events.""" - - def __init__(self, button, pos, cell): - self.button = _mouseNames[button] - """Text: Can be one of - 'LEFT', 'MIDDLE', 'RIGHT', 'SCROLLUP', 'SCROLLDOWN' - """ - self.pos = pos - """Tuple[int, int]: (x, y) position of the mouse on the screen.""" - self.cell = cell - """Tuple[int, int]: (x, y) position of the mouse snapped to a cell on - the root console - """ - -class MouseDown(MouseButtonEvent): - """Fired when a mouse button is pressed.""" - __slots__ = () - type = 'MOUSEDOWN' - -class MouseUp(MouseButtonEvent): - """Fired when a mouse button is released.""" - __slots__ = () - type = 'MOUSEUP' - -class MouseMotion(Event): - """Fired when the mouse is moved.""" - type = 'MOUSEMOTION' - - def __init__(self, pos, cell, motion, cellmotion): - self.pos = pos - """(x, y) position of the mouse on the screen. - type: (int, int)""" - self.cell = cell - """(x, y) position of the mouse snapped to a cell on the root console. - type: (int, int)""" - self.motion = motion - """(x, y) motion of the mouse on the screen. - type: (int, int)""" - self.cellmotion = cellmotion - """(x, y) mostion of the mouse moving over cells on the root console. - type: (int, int)""" - -class App(object): - """ - Application framework. - - - ev_*: Events are passed to methods based on their :any:`Event.type` - attribute. - If an event type is 'KEYDOWN' the ev_KEYDOWN method will be called - with the event instance as a parameter. - - - key_*: When a key is pressed another method will be called based on the - :any:`KeyEvent.key` attribute. For example the 'ENTER' key will call - key_ENTER with the associated :any:`KeyDown` event as its parameter. - - - :any:`update`: This method is called every loop. It is passed a single - parameter detailing the time in seconds since the last update - (often known as deltaTime.) - - You may want to call drawing routines in this method followed by - :any:`tdl.flush`. - - """ - __slots__ = ('__running', '__prevTime') - - def ev_QUIT(self, event): - """Unless overridden this method raises a SystemExit exception closing - the program.""" - raise SystemExit() - - def ev_KEYDOWN(self, event): - """Override this method to handle a :any:`KeyDown` event.""" - - def ev_KEYUP(self, event): - """Override this method to handle a :any:`KeyUp` event.""" - - def ev_MOUSEDOWN(self, event): - """Override this method to handle a :any:`MouseDown` event.""" - - def ev_MOUSEUP(self, event): - """Override this method to handle a :any:`MouseUp` event.""" - - def ev_MOUSEMOTION(self, event): - """Override this method to handle a :any:`MouseMotion` event.""" - - def update(self, deltaTime): - """Override this method to handle per frame logic and drawing. - - Args: - deltaTime (float): - This parameter tells the amount of time passed since - the last call measured in seconds as a floating point - number. - - You can use this variable to make your program - frame rate independent. - Use this parameter to adjust the speed of motion, - timers, and other game logic. - """ - pass - - def suspend(self): - """When called the App will begin to return control to where - :any:`App.run` was called. - - Some further events are processed and the :any:`App.update` method - will be called one last time before exiting - (unless suspended during a call to :any:`App.update`.) - """ - self.__running = False - - def run(self): - """Delegate control over to this App instance. This function will - process all events and send them to the special methods ev_* and key_*. - - A call to :any:`App.suspend` will return the control flow back to where - this function is called. And then the App can be run again. - But a single App instance can not be run multiple times simultaneously. - """ - if getattr(self, '_App__running', False): - raise _tdl.TDLError('An App can not be run multiple times simultaneously') - self.__running = True - while self.__running: - self.runOnce() - - def run_once(self): - """Pump events to this App instance and then return. - - This works in the way described in :any:`App.run` except it immediately - returns after the first :any:`update` call. - - Having multiple :any:`App` instances and selectively calling runOnce on - them is a decent way to create a state machine. - """ - if not hasattr(self, '_App__prevTime'): - self.__prevTime = _time.clock() # initiate __prevTime - for event in get(): - if event.type: # exclude custom events with a blank type variable - # call the ev_* methods - method = 'ev_%s' % event.type # ev_TYPE - getattr(self, method)(event) - if event.type == 'KEYDOWN': - # call the key_* methods - method = 'key_%s' % event.key # key_KEYNAME - if hasattr(self, method): # silently exclude undefined methods - getattr(self, method)(event) - newTime = _time.clock() - self.update(newTime - self.__prevTime) - self.__prevTime = newTime - #_tdl.flush() - -def _processEvents(): - """Flushes the event queue from libtcod into the global list _eventQueue""" - global _mousel, _mousem, _mouser, _eventsflushed, _pushedEvents - _eventsflushed = True - events = _pushedEvents # get events from event.push - _pushedEvents = [] # then clear the pushed events queue - - mouse = _ffi.new('TCOD_mouse_t *') - libkey = _ffi.new('TCOD_key_t *') - while 1: - libevent = _lib.TCOD_sys_check_for_event(_lib.TCOD_EVENT_ANY, libkey, mouse) - if not libevent: # no more events from libtcod - break - - #if mouse.dx or mouse.dy: - if libevent & _lib.TCOD_EVENT_MOUSE_MOVE: - events.append(MouseMotion((mouse.x, mouse.y), - (mouse.cx, mouse.cy), - (mouse.dx, mouse.dy), - (mouse.dcx, mouse.dcy))) - - mousepos = ((mouse.x, mouse.y), (mouse.cx, mouse.cy)) - - for oldstate, newstate, released, button in \ - zip((_mousel, _mousem, _mouser), - (mouse.lbutton, mouse.mbutton, mouse.rbutton), - (mouse.lbutton_pressed, mouse.mbutton_pressed, - mouse.rbutton_pressed), - (1, 2, 3)): - if released: - if not oldstate: - events.append(MouseDown(button, *mousepos)) - events.append(MouseUp(button, *mousepos)) - if newstate: - events.append(MouseDown(button, *mousepos)) - elif newstate and not oldstate: - events.append(MouseDown(button, *mousepos)) - - if mouse.wheel_up: - events.append(MouseDown(4, *mousepos)) - if mouse.wheel_down: - events.append(MouseDown(5, *mousepos)) - - _mousel = mouse.lbutton - _mousem = mouse.mbutton - _mouser = mouse.rbutton - - if libkey.vk == _lib.TCODK_NONE: - break - if libkey.pressed: - keyevent = KeyDown - else: - keyevent = KeyUp - - events.append( - keyevent( - libkey.vk, - libkey.c.decode('ascii', errors='ignore'), - _ffi.string(libkey.text).decode('utf-8'), - libkey.shift, - libkey.lalt, - libkey.ralt, - libkey.lctrl, - libkey.rctrl, - libkey.lmeta, - libkey.rmeta, - ) - ) - - if _lib.TCOD_console_is_window_closed(): - events.append(Quit()) - - _eventQueue.extend(events) - -def get(): - """Flushes the event queue and returns the list of events. - - This function returns :any:`Event` objects that can be identified by their - type attribute or their class. - - Returns: Iterator[Type[Event]]: An iterable of Events or anything - put in a :any:`push` call. - - If the iterator is deleted or otherwise interrupted before finishing - the excess items are preserved for the next call. - """ - _processEvents() - return _event_generator() - -def _event_generator(): - while _eventQueue: - # if there is an interruption the rest of the events stay untouched - # this means you can break out of a event.get loop without losing - # the leftover events - yield(_eventQueue.pop(0)) - - -def wait(timeout=None, flush=True): - """Wait for an event. - - Args: - timeout (Optional[int]): The time in seconds that this function will - wait before giving up and returning None. - - With the default value of None, this will block forever. - flush (bool): If True a call to :any:`tdl.flush` will be made before - listening for events. - - Returns: Type[Event]: An event, or None if the function - has timed out. - Anything added via :any:`push` will also be returned. - """ - if timeout is not None: - timeout = timeout + _time.clock() # timeout at this time - while True: - if _eventQueue: - return _eventQueue.pop(0) - if flush: - # a full 'round' of events need to be processed before flushing - _tdl.flush() - if timeout and _time.clock() >= timeout: - return None # return None on timeout - _time.sleep(0.001) # sleep 1ms - _processEvents() - - -def push(event): - """Push an event into the event buffer. - - Args: - event (Any): This event will be available on the next call to - :any:`event.get`. - - An event pushed in the middle of a :any:`get` will not show until - the next time :any:`get` called preventing push related - infinite loops. - - This object should at least have a 'type' attribute. - """ - _pushedEvents.append(event) - -def key_wait(): - """Waits until the user presses a key. - Then returns a :any:`KeyDown` event. - - Key events will repeat if held down. - - A click to close the window will be converted into an Alt+F4 KeyDown event. - - Returns: - tdl.event.KeyDown: The pressed key. - """ - while 1: - for event in get(): - if event.type == 'KEYDOWN': - return event - if event.type == 'QUIT': - # convert QUIT into alt+F4 - return KeyDown('F4', '', True, False, True, False, False) - _time.sleep(.001) - -def set_key_repeat(delay=500, interval=0): - """Does nothing. - """ - pass - -def is_window_closed(): - """Returns True if the exit button on the window has been clicked and - stays True afterwards. - - Returns: bool: - """ - return _lib.TCOD_console_is_window_closed() - -__all__ = [_var for _var in locals().keys() if _var[0] != '_'] - -App.runOnce = _style.backport(App.run_once) -keyWait = _style.backport(key_wait) -setKeyRepeat = _style.backport(set_key_repeat) -isWindowClosed = _style.backport(is_window_closed) - diff --git a/tdl/map.py b/tdl/map.py deleted file mode 100644 index e8344071..00000000 --- a/tdl/map.py +++ /dev/null @@ -1,353 +0,0 @@ -""" - Rogue-like map utilitys such as line-of-sight, field-of-view, and path-finding. - - .. deprecated:: 3.2 - The features provided here are better realized in the - :any:`tcod.map` and :any:`tcod.path` modules. -""" - -from __future__ import absolute_import - -import itertools as _itertools -import math as _math - -import numpy as np - -from tcod import ffi as _ffi -from tcod import lib as _lib -from tcod import ffi, lib - -from tcod._internal import deprecate -import tcod.map -import tcod.path -import tdl as _tdl -from . import style as _style - -_FOVTYPES = {'BASIC' : 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12, - 'PERMISSIVE': 11} - -def _get_fov_type(fov): - "Return a FOV from a string" - oldFOV = fov - fov = str(fov).upper() - if fov in _FOVTYPES: - return _FOVTYPES[fov] - if fov[:10] == 'PERMISSIVE' and fov[10].isdigit() and fov[10] != '9': - return 4 + int(fov[10]) - raise _tdl.TDLError('No such fov option as %s' % oldFOV) - -class Map(tcod.map.Map): - """Field-of-view and path-finding on stored data. - - .. versionchanged:: 4.1 - `transparent`, `walkable`, and `fov` are now numpy boolean arrays. - - .. versionchanged:: 4.3 - Added `order` parameter. - - .. deprecated:: 3.2 - :any:`tcod.map.Map` should be used instead. - - Set map conditions with the walkable and transparency attributes, this - object can be iterated and checked for containment similar to consoles. - - For example, you can set all tiles and transparent and walkable with the - following code: - - Example: - >>> import tdl.map - >>> map_ = tdl.map.Map(80, 60) - >>> map_.transparent[:] = True - >>> map_.walkable[:] = True - - Attributes: - transparent: Map transparency - - Access this attribute with ``map.transparent[x,y]`` - - Set to True to allow field-of-view rays, False will - block field-of-view. - - Transparent tiles only affect field-of-view. - walkable: Map accessibility - - Access this attribute with ``map.walkable[x,y]`` - - Set to True to allow path-finding through that tile, - False will block passage to that tile. - - Walkable tiles only affect path-finding. - - fov: Map tiles touched by a field-of-view computation. - - Access this attribute with ``map.fov[x,y]`` - - Is True if a the tile is if view, otherwise False. - - You can set to this attribute if you want, but you'll typically - be using it to read the field-of-view of a :any:`compute_fov` call. - """ - - def __init__(self, width, height, order='F'): - super(Map, self).__init__(width, height, order) - - def compute_fov(self, x, y, fov='PERMISSIVE', radius=None, - light_walls=True, sphere=True, cumulative=False): - """Compute the field-of-view of this Map and return an iterator of the - points touched. - - Args: - x (int): Point of view, x-coordinate. - y (int): Point of view, y-coordinate. - fov (Text): The type of field-of-view to be used. - - Available types are: - 'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE', - 'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8' - radius (Optional[int]): Maximum view distance from the point of - view. - - A value of 0 will give an infinite distance. - light_walls (bool): Light up walls, or only the floor. - sphere (bool): If True the lit area will be round instead of - square. - cumulative (bool): If True the lit cells will accumulate instead - of being cleared before the computation. - - Returns: - Iterator[Tuple[int, int]]: An iterator of (x, y) points of tiles - touched by the field-of-view. - """ - # refresh cdata - if radius is None: # infinite radius - radius = 0 - if cumulative: - fov_copy = self.fov.copy() - lib.TCOD_map_compute_fov( - self.map_c, x, y, radius, light_walls, _get_fov_type(fov)) - if cumulative: - self.fov[:] |= fov_copy - return zip(*np.where(self.fov)) - - - def compute_path(self, start_x, start_y, dest_x, dest_y, - diagonal_cost=_math.sqrt(2)): - """Get the shortest path between two points. - - Args: - start_x (int): Starting x-position. - start_y (int): Starting y-position. - dest_x (int): Destination x-position. - dest_y (int): Destination y-position. - diagonal_cost (float): Multiplier for diagonal movement. - - Can be set to zero to disable diagonal movement entirely. - - Returns: - List[Tuple[int, int]]: The shortest list of points to the - destination position from the starting position. - - The start point is not included in this list. - """ - return tcod.path.AStar(self, diagonal_cost).get_path(start_x, start_y, - dest_x, dest_y) - - def __iter__(self): - return _itertools.product(range(self.width), range(self.height)) - - def __contains__(self, position): - x, y = position - return (0 <= x < self.width) and (0 <= y < self.height) - - - -class AStar(tcod.path.AStar): - """An A* pathfinder using a callback. - - .. deprecated:: 3.2 - See :any:`tcod.path`. - - Before creating this instance you should make one of two types of - callbacks: - - - A function that returns the cost to move to (x, y) - - A function that returns the cost to move between - (destX, destY, sourceX, sourceY) - - If path is blocked the function should return zero or None. - When using the second type of callback be sure to set advanced=True - - Args: - width (int): Width of the pathfinding area (in tiles.) - height (int): Height of the pathfinding area (in tiles.) - callback (Union[Callable[[int, int], float], - Callable[[int, int, int, int], float]]): A callback - returning the cost of a tile or edge. - - A callback taking parameters depending on the setting - of 'advanced' and returning the cost of - movement for an open tile or zero for a - blocked tile. - diagnalCost (float): Multiplier for diagonal movement. - - Can be set to zero to disable diagonal movement entirely. - advanced (bool): Give 2 additional parameters to the callback. - - A simple callback with 2 positional parameters may not - provide enough information. Setting this to True will - call the callback with 2 additional parameters giving - you both the destination and the source of movement. - - When True the callback will need to accept - (destX, destY, sourceX, sourceY) as parameters. - Instead of just (destX, destY). - """ - - class __DeprecatedEdgeCost(tcod.path.EdgeCostCallback): - _CALLBACK_P = lib._pycall_path_swap_src_dest - - class __DeprecatedNodeCost(tcod.path.EdgeCostCallback): - _CALLBACK_P = lib._pycall_path_dest_only - - def __init__(self, width, height, callback, - diagnalCost=_math.sqrt(2), advanced=False): - if advanced: - cost = self.__DeprecatedEdgeCost(callback, (width, height)) - else: - cost = self.__DeprecatedNodeCost(callback, (width, height)) - super(AStar, self).__init__(cost, diagnalCost or 0.0) - - def get_path(self, origX, origY, destX, destY): - """ - Get the shortest path from origXY to destXY. - - Returns: - List[Tuple[int, int]]: Returns a list walking the path from orig - to dest. - - This excludes the starting point and includes the destination. - - If no path is found then an empty list is returned. - """ - return super(AStar, self).get_path(origX, origY, destX, destY) - -@deprecate("This function is very slow.") -def quick_fov(x, y, callback, fov='PERMISSIVE', radius=7.5, lightWalls=True, - sphere=True): - """All field-of-view functionality in one call. - - Before using this call be sure to make a function, lambda, or method that takes 2 - positional parameters and returns True if light can pass through the tile or False - for light-blocking tiles and for indexes that are out of bounds of the - dungeon. - - This function is 'quick' as in no hassle but can quickly become a very slow - function call if a large radius is used or the callback provided itself - isn't optimized. - - Always check if the index is in bounds both in the callback and in the - returned values. These values can go into the negatives as well. - - Args: - x (int): x center of the field-of-view - y (int): y center of the field-of-view - callback (Callable[[int, int], bool]): - - This should be a function that takes two positional arguments x,y - and returns True if the tile at that position is transparent - or False if the tile blocks light or is out of bounds. - fov (Text): The type of field-of-view to be used. - - Available types are: - 'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE', - 'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8' - radius (float) Radius of the field-of-view. - - When sphere is True a floating point can be used to fine-tune - the range. Otherwise the radius is just rounded up. - - Be careful as a large radius has an exponential affect on - how long this function takes. - lightWalls (bool): Include or exclude wall tiles in the field-of-view. - sphere (bool): True for a spherical field-of-view. - False for a square one. - - Returns: - Set[Tuple[int, int]]: A set of (x, y) points that are within the - field-of-view. - """ - trueRadius = radius - radius = int(_math.ceil(radius)) - mapSize = radius * 2 + 1 - fov = _get_fov_type(fov) - - setProp = _lib.TCOD_map_set_properties # make local - inFOV = _lib.TCOD_map_is_in_fov - - tcodMap = _lib.TCOD_map_new(mapSize, mapSize) - try: - # pass no.1, write callback data to the tcodMap - for x_, y_ in _itertools.product(range(mapSize), range(mapSize)): - pos = (x_ + x - radius, - y_ + y - radius) - transparent = bool(callback(*pos)) - setProp(tcodMap, x_, y_, transparent, False) - - # pass no.2, compute fov and build a list of points - _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov) - touched = set() # points touched by field of view - for x_, y_ in _itertools.product(range(mapSize), range(mapSize)): - if sphere and _math.hypot(x_ - radius, y_ - radius) > trueRadius: - continue - if inFOV(tcodMap, x_, y_): - touched.add((x_ + x - radius, y_ + y - radius)) - finally: - _lib.TCOD_map_delete(tcodMap) - return touched - -def bresenham(x1, y1, x2, y2): - """ - Return a list of points in a bresenham line. - - Implementation hastily copied from RogueBasin. - - Returns: - List[Tuple[int, int]]: A list of (x, y) points, - including both the start and end-points. - """ - points = [] - issteep = abs(y2-y1) > abs(x2-x1) - if issteep: - x1, y1 = y1, x1 - x2, y2 = y2, x2 - rev = False - if x1 > x2: - x1, x2 = x2, x1 - y1, y2 = y2, y1 - rev = True - deltax = x2 - x1 - deltay = abs(y2-y1) - error = int(deltax / 2) - y = y1 - ystep = None - if y1 < y2: - ystep = 1 - else: - ystep = -1 - for x in range(x1, x2 + 1): - if issteep: - points.append((y, x)) - else: - points.append((x, y)) - error -= deltay - if error < 0: - y += ystep - error += deltax - # Reverse the list if the coordinates were reversed - if rev: - points.reverse() - return points - - -quickFOV = _style.backport(quick_fov) -AStar.getPath = _style.backport(AStar.get_path) diff --git a/tdl/noise.py b/tdl/noise.py deleted file mode 100755 index 66606a0a..00000000 --- a/tdl/noise.py +++ /dev/null @@ -1,169 +0,0 @@ -""" - This module provides advanced noise generation. - - Noise is sometimes used for over-world generation, height-maps, and - cloud/mist/smoke effects among other things. - - You can see examples of the available noise algorithms in the libtcod - documentation `here - `_. -""" - - -import random as _random - -from tcod import ffi as _ffi -from tcod import lib as _lib - -import tdl as _tdl -from . import style as _style - -_MERSENNE_TWISTER = 1 -_CARRY_WITH_MULTIPLY = 2 - -_MAX_DIMENSIONS = 4 -_MAX_OCTAVES = 128 - -_NOISE_TYPES = {'PERLIN': 1, 'SIMPLEX': 2, 'WAVELET': 4} -_NOISE_MODES = {'FLAT': _lib.TCOD_noise_get, - 'FBM': _lib.TCOD_noise_get_fbm, - 'TURBULENCE': _lib.TCOD_noise_get_turbulence} - -class Noise(object): - """An advanced noise generator. - - .. deprecated:: 3.2 - This class has been replaced by :any:`tcod.noise.Noise`. - - Args: - algorithm (Text): The primary noise algorithm to be used. - - Can be one of 'PERLIN', 'SIMPLEX', 'WAVELET' - - - 'PERLIN' - - A popular noise generator. - - 'SIMPLEX' - - In theory this is a slightly faster generator with - less noticeable directional artifacts. - - 'WAVELET' - - A noise generator designed to reduce aliasing and - not lose detail when summed into a fractal - (as with the 'FBM' and 'TURBULENCE' modes.) - This works faster at higher dimensions. - - mode (Text): A secondary parameter to determine how noise is generated. - - Can be one of 'FLAT', 'FBM', 'TURBULENCE' - - - 'FLAT' - - Generates the simplest form of noise. - This mode does not use the hurst, lacunarity, - and octaves parameters. - - 'FBM' - - Generates fractal brownian motion. - - 'TURBULENCE' - - Generates detailed noise with smoother and more - natural transitions. - - hurst (float): The hurst exponent. - - This describes the raggedness of the resultant noise, - with a higher value leading to a smoother noise. - It should be in the 0.0-1.0 range. - - This is only used in 'FBM' and 'TURBULENCE' modes. - - lacunarity (float): A multiplier that determines how quickly the - frequency increases for each successive octave. - - The frequency of each successive octave is equal to - the product of the previous octave's frequency and - the lacunarity value. - - This is only used in 'FBM' and 'TURBULENCE' modes. - - octaves (float): Controls the amount of detail in the noise. - - This is only used in 'FBM' and 'TURBULENCE' modes. - - seed (Hashable): You can use any hashable object to be a seed for the - noise generator. - - If None is used then a random seed will be generated. - """ - - def __init__(self, algorithm='PERLIN', mode='FLAT', - hurst=0.5, lacunarity=2.0, octaves=4.0, seed=None, dimensions=4): - if algorithm.upper() not in _NOISE_TYPES: - raise _tdl.TDLError('No such noise algorithm as %s' % algorithm) - self._algorithm = algorithm.upper() - - if mode.upper() not in _NOISE_MODES: - raise _tdl.TDLError('No such mode as %s' % mode) - self._mode = mode.upper() - - if seed is None: - seed = _random.getrandbits(32) - try: - seed = int(seed) - except TypeError: - seed = hash(seed) - self._seed = seed - # convert values into ctypes to speed up later functions - self._dimensions = min(_MAX_DIMENSIONS, int(dimensions)) - if self._algorithm == 'WAVELET': - self._dimensions = min(self._dimensions, 3) # Wavelet only goes up to 3 - self._random = _lib.TCOD_random_new_from_seed( - _MERSENNE_TWISTER, - _ffi.cast('uint32_t', self._seed), - ) - self._hurst = hurst - self._lacunarity = lacunarity - self._noise = _lib.TCOD_noise_new(self._dimensions, self._hurst, - self._lacunarity, self._random) - _lib.TCOD_noise_set_type(self._noise, _NOISE_TYPES[self._algorithm]) - self._noiseFunc = _NOISE_MODES[self._mode] - self._octaves = octaves - self._useOctaves = (self._mode != 'FLAT') - self._arrayType = 'float[%i]' % self._dimensions - #self._cFloatArray = _ctypes.c_float * self._dimensions - #self._array = self._cFloatArray() - - def __copy__(self): - # using the pickle method is a convenient way to clone this object - return self.__class__(*self.__getstate__()) - - def __getstate__(self): - return (self._algorithm, self._mode, - self._hurst, self._lacunarity, self._octaves, - self._seed, self._dimensions) - - def __setstate__(self, state): - self.__init__(*state) - - def get_point(self, *position): - """Return the noise value of a specific position. - - Example usage: value = noise.getPoint(x, y, z) - - Args: - position (Tuple[float, ...]): The point to sample at. - - Returns: - float: The noise value at position. - - This will be a floating point in the 0.0-1.0 range. - """ - #array = self._array - #for d, pos in enumerate(position): - # array[d] = pos - #array = self._cFloatArray(*position) - array = _ffi.new(self._arrayType, position) - if self._useOctaves: - return (self._noiseFunc(self._noise, array, self._octaves) + 1) * 0.5 - return (self._noiseFunc(self._noise, array) + 1) * 0.5 - - -__all__ = [_var for _var in locals().keys() if _var[0] != '_'] - -Noise.getPoint = _style.backport(Noise.get_point) diff --git a/tdl/style.py b/tdl/style.py deleted file mode 100644 index cdf643b4..00000000 --- a/tdl/style.py +++ /dev/null @@ -1,31 +0,0 @@ -""" - Used internally to handle style changes without breaking backwards - compatibility. -""" - -import warnings as _warnings -import functools as _functools - -def backport(func): - """ - Backport a function name into an old style for compatibility. - - The docstring is updated to reflect that the new function returned is - deprecated and that the other function is preferred. - A DeprecationWarning is also raised for using this function. - - If the script is run with an optimization flag then the real function - will be returned without being wrapped. - """ - if not __debug__: - return func - - @_functools.wraps(func) - def deprecated_function(*args, **kargs): - _warnings.warn('This function name is deprecated', - DeprecationWarning, 2) - return func(*args, **kargs) - deprecated_function.__doc__ = None - return deprecated_function - -__all__ = [] diff --git a/tdl/terminal8x8.png b/tdl/terminal8x8.png deleted file mode 100644 index b0392fc788351aef41420803ddc465de93b62501..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3150 zcmV-U46*ZxP)Px>2uVaiRCwC$UDwM=>LD&_aUvL&X6F0p`4_*dgw`QS<5UW!1Wl(@q9jy z;~2{Ed_G@)Uw?c3{PorJVfYjB0kZqzONF0b-+CH>>HOD!!_U`49nA6keMslyzrEAE z`{L__XJ#t~UmRQqfB11cq&HO8qkWxqm%M%sR>pB0*OdrnH`_Yb<&2+}KNBAhV}To^ z&s=%hq}S2BzUu39e6QDUkKf01<>?~pfMTKXy29ohoYImc&y=1<7LMGi1RrL*XPw|o za~R{E>AbUdfWXOJ13eAF$9zv8g8OEoV;%Z+M`rWQOhfki$D2`5h!!NY6Q0a0a{yFA zI`P@Qktg809bm#F%*re{WAT+*5KRDvEz%J6%J)2H*>b%QN`!s};04?f{>D@H*W6@Y=r<^U{< z9Y8XzYg-S4o03ZSm!xS~;NOH0?DGI`2kh{G)7W2(Dco3|)8{}~tA-$w!CmFPq=&Po zV&?CG?bo5@7QcTsj?-7==j~=V=3B`eeOwM3I6N{K3iuZ`&o;`0(54T3AQ{$X9@rps z&&%Os?owuX-oer(S$+<5IL`?fFa{tdesRgZQkbJX_RCus zKvmThm?BE{+-Q>_d3=qgx20b;^ra*)A_x9}q0HK0?zD+^u5TVf_0k;85IS(F)w1SU zL*?dPd@%W8IKyX0}L^GeWsEe;P8_cPn^dz!8CxS`TFl;eNQarR$(%& zWvmZZivs2r(<)|k%&kWjXJ`0DKF!gY58$g%5Mxy>cL28g8?bwm2ZVxg{Ghm?H?sjg z;%4(kXSoB)O@yVS%8*Na4#M_%E2g1HjbJCRBWH8;6(N=I5y#_a&kb}e4->=dhj<($ zBDA2OA;v9a<9UuKM%6QUmcgo5yIUFhS^_YvO8~NH=K(ttd1etZnK71$X6S-?B=inR zfTzsw;{n-tA|6l*>lI&1n~AiscoT+sKt}XbPXq4{$X@Ckv;zpQAOp#5?h@6DOX^s4 zi)_BmW3{VDMkvE}gulXTbB(=OY2do{?=T}z8on!<@82=rJemgp-4faJ!qZydpqH#L z=*YV+`59yA0TZc73qcvFI+FOOmI;vNAap$%mr|cOeG+m9kZv+;T>%LigNv}^&+nbc zk>_-HJK$4?*&;$(^6ErNc>ulw;0MyUGjH)n ze!v=(0YnovO=Bj~2(CRIkgbL?xpk8{2O+b-%E-&#(gn!8Kkp|9B_sx5w&>g0^i1q! zD$%-ZCVvw@*qngag5c@&(l<-)fD-cVlniS3(1ADvPm>S6S_&g}aq8&mP3;ovYTOYh z)wIU@GYe%$JRF-Vj?qyb4n+nEu^ffkdno9WJVT0q;6d~JDx$Q!( zg**IKOz_eWBb~|}uO5$HOcEmfu2cX9U|>N3o`)>AGnVpxn|Z<~DUL_vNMXb)A2fhh z98aurPg%U;ncU=L>;5b$i#+5 z&xT|3fE{17zyo-4e>YJuX!*CzWL_(N1v}m@)}h9&kQPvb650uim{H>rNjv2-2cIRG z)ds{uU`JW0m^yrttPVmcG5Dwx1ME(A%sDCo7wZ^FJgl#LF3-VivTVnAdBUJ@>lM*v zbC4CZ=tn_(GB`#fS*ih+2ASYuCZlZN08)<2GEZ5;D1&`>6SYYc0r(`|l6WBlxap+| zZCa4jbJkB#y&#FQ?@SnZL3P50(uQOeIBO969mlDzpll9Q2a5`S!Gwjk2JkF(HHKa@(dO4+e$^iia6Id1TeT`7t_xRpZ|tM zdaYWqK9$$V%y{233tuQf=N+cOj4paMnan?PeZj0sx=psT=gf#(8w^#eoAb)hgTLz} zVKVO-u{%-FzCS<>q-ksfVMwrPnn~r^E-Fv{5lJO^lWmeLqbiL%--`3Tt#Fh^C<6uU z#8wWy+M%QaJQO6MBMFK2=ehl8T;DPQZ6fGah60KasY=p_rv6r3Oy&VLgeRK`d+*$$ z9y1=m^8=~o{~&wF@DZF$RK%33CwbON(6w2fGV^?e3j7vh9F7wPot<|2DoL%I$U39@ zlq&qTrKB;X1G06EJy4QKBrIWIOxVyn{_(#eeddOqpC^Eib}Dlj3>%h|3%1VIeF|3c zc2Poq+EPPwMjNI4~9yw{Ns7R7bYt-nxh3Kyt8D~(d08~$N*ELWg(+f zc60+@RCP@G0ikl+jFprM>yyKmg=>&?{NG@<8g!G=vm_Blw&H&Dvh)+Oq`KOWa#)Fo zWqaMAYGN2Un`I2x1xq(aABd)soq!7YS><(J67CYQvfGdpZeI2Pw|m6eL5~@K3{yEu zD77>0;o7bJ0qT&As%dg#pKx;Y`CUbp9`t4+e36HwA@S!%Xw*pJ2D6tB(>Fl<(Gf*U zPMf$_39AVsZRwWdr*#~|wIWM@Kvz7~T)KEG$V(4dAi*-vJV#Y`sEY>}DG1?nK1ORE zFNnk^O`bMVTA#lJQTJ`x48N@1#Lk;gBe{t7)hyG8o3WyIWoq)^b*pS;mjY;Mr5X+~ z(`U}>{c01jke8gJDX^Iz%GHv*!w>ND45GiWv;(?$z*rkPGnt-vzcWJ#7=Le|BS;1r zNe^9hwW;$I-r^j3Q#7(m3;^l)ng%2TKM883T5I2skW{vdA;kZ(4#T{?kKqBbF&IBxy8om{zFY8|O$zn>>D~7(UZ*a{6_HDx8(Mw(DxjZ-P_IT)}z5CV(V?%Ad2TVGk z?h82wSlwzMa(N33iBWS1-h;G|^ZeC_LX7aD`}2Yeqy^{|~e zz?lwZAy(c>GdlX0d2{boSNn8Hs`S!(rjVC!0zMMR;!3zU+;OUI+1@+#VGQx8>Uv@y z$W4%jY?qtOL6)<|2jG}WxC;V(t(h8k>cKUoZ8yE)w+K%A*Nm9l*=iLPcIjlb@oasI zO`$DiAyoEs2idgit;R5PqBf)%)+@@|(i1tPa(Hf}rVU`eneh?GQwEQAW?1bJ#^Cy) z*N(=PINQoQT0;{bS7KR)fUBdwqsc*-TiFO~73jE}F4gq$9=oJduIBx(845KND2V@WL of%oqPL+^uejkmZp#CY@g52`TN4aQi8@&Et;07*qoM6N<$f_Odv!2kdN diff --git a/tdl/version.py b/tdl/version.py deleted file mode 100644 index 0e58e4ae..00000000 --- a/tdl/version.py +++ /dev/null @@ -1 +0,0 @@ -from tcod import __version__ diff --git a/tests/test_noise.py b/tests/test_noise.py index 8ccc5b9f..4265cc3e 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -1,31 +1 @@ #!/usr/bin/env python - -import copy -import pickle -import unittest - -import tdl - - -class NoiseTests(unittest.TestCase): - - def test_noise(self): - n = tdl.noise.Noise() - self.assertIsInstance(n.get_point(0, 0), float) - n = tdl.noise.Noise('Wavelet', 'FBM', seed=-1) - self.assertIsInstance(n.get_point(0, 0), float) - - def test_noise_exceptions(self): - with self.assertRaises(tdl.TDLError): - tdl.noise.Noise(algorithm='') - with self.assertRaises(tdl.TDLError): - tdl.noise.Noise(mode='') - - def test_noise_copy(self): - self.assertIsInstance(copy.copy(tdl.noise.Noise()), tdl.noise.Noise) - - def test_noise_pickle(self): - self.assertIsInstance(pickle.loads(pickle.dumps(tdl.noise.Noise())), - tdl.noise.Noise) - - diff --git a/tests/test_tdl.py b/tests/test_tdl.py deleted file mode 100755 index 9af4262d..00000000 --- a/tests/test_tdl.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python -import copy -import gc -import itertools -import os -import pickle -import random -import sys -import unittest - -import tdl - -#ERROR_RANGE = 100 # a number to test out of bound errors -WIDTH, HEIGHT = 30, 20 -WINWIDTH, WINHEIGHT = 10, 10 - -DEFAULT_CHAR = (0x20, (0, 0, 0), (0, 0, 0)) - -IS_PYTHON2 = (sys.version_info[0] == 2) - -class TDLTemplate(unittest.TestCase): - "Nearly all tests need tdl.init to be called" - - @classmethod - def setUpClass(cls): - cls.console = tdl.init(WIDTH, HEIGHT, 'TDL UnitTest', False, renderer='SDL') - # make a small window in the corner - cls.window = tdl.Window(cls.console, 0, 0, WINWIDTH, WINHEIGHT) - - def setUp(self): - tdl.event.get() - self.console.set_colors((0,0,0), (0,0,0)) - self.console.clear() - - @classmethod - def tearDownClass(cls): - del cls.console - gc.collect() # make sure console.__del__ is called quickly - - def in_window(self, x, y): - "returns True if this point is in the Window" - return 0 <= x < WINWIDTH and 0 <= y < WINHEIGHT - - def randomize_console(self): - "Randomize the console returning the random data" - noise = [((x, y), self.get_random_character()) for x,y in self.get_drawables()] - for (x, y), graphic in noise: - self.console.draw_char(x, y, *graphic) - return noise # [((x, y), (cg, fg, bg)), ...] - - def flush(self): - 'Pump events and refresh screen so show progress' - pass # There are no important side-effects from flushing. - - def get_random_character(self): - "returns a tuple with a random character and colors (ch, fg, bg)" - return (random.getrandbits(8), self.get_random_color(), self.get_random_color()) - - def get_random_color(self): - "returns a single random color" - return (random.getrandbits(8), random.getrandbits(8), random.getrandbits(8)) - - def get_drawables(self, console=None): - """return a list of all drawable (x,y) positions - defaults to self.console - """ - if console is None: - console = self.console - w, h = console.get_size() - return itertools.product(range(w), range(h)) - - def get_undrawables(self, console=None): - """return a list of (x,y) positions that should raise errors when used - positions are mostly random and will have at least one over the bounds of each side and each corner""" - if console is None: - console = self.console - w, h = console.get_size() - for y in range(-1, h+1): - yield -w-1, y - yield w, y - for x in range(0, w): - yield x, h - yield x, -h-1 - - def compare_consoles(self, consoleA, consoleB, errorMsg='colors should be the same'): - "Compare two console assuming they match and failing if they don't" - self.assertEqual(consoleA.get_size(), consoleB.get_size(), 'consoles should be the same size') - for x, y in self.get_drawables(consoleA): - self.assertEqual(consoleA.get_char(x, y), - consoleB.get_char(x, y), '%s, position: (%i, %i)' % (errorMsg, x, y)) - -class BasicTests(TDLTemplate): - - def test_clearConsole(self): - self.randomize_console() - _, fg, bg = self.get_random_character() - ch = 0x20 # space - self.console.clear(fg, bg) - self.flush() - for x,y in self.get_drawables(): - self.assertEqual((ch, fg, bg), self.console.get_char(x, y), 'color should be changed with clear') - _, fg2, bg2 = self.get_random_character() - self.window.clear(fg2, bg2) - self.flush() - for x,y in self.get_drawables(): - if self.in_window(x, y): - self.assertEqual((ch, fg2, bg2), self.console.get_char(x, y), 'color in window should be changed') - else: - self.assertEqual((ch, fg, bg), self.console.get_char(x, y), 'color outside of window should persist') - - def test_cloneConsole(self): - noiseData = self.randomize_console() - clone = copy.copy(self.console) - self.compare_consoles(self.console, clone, 'console clone should match root console') - - def test_pickleConsole(self): - noiseData = self.randomize_console() - pickled = pickle.dumps(self.console) - clone = pickle.loads(pickled) - self.compare_consoles(self.console, clone, 'pickled console should match root console') - - -class DrawingTests(TDLTemplate): - - def test_draw_charTuples(self): - "Test passing tuple colors and int characters to draw_char" - record = {} - for x,y in self.get_drawables(): - ch, fg, bg = self.get_random_character() - record[x,y] = (ch, fg, bg) - self.console.draw_char(x, y, ch, fg, bg) - self.assertEqual(record[x,y], self.console.get_char(x, y), 'console data should be overwritten') - self.flush() # show progress - - for (x,y), data in record.items(): - self.assertEqual(data, self.console.get_char(x, y), 'draw_char should not overwrite any other tiles') - - def test_draw_charWebcolor(self): - "Test passing web style colors and string characters to draw_char" - record = {} - for x,y in self.get_drawables(): - ch, fg, bg = self.get_random_character() - record[x,y] = (ch, fg, bg) - ch = chr(ch) - fg = fg[0] << 16 | fg[1] << 8 | fg[2] # convert to a 0xRRGGBB style number - bg = bg[0] << 16 | bg[1] << 8 | bg[2] - self.console.draw_char(x, y, ch, fg, bg) - self.assertEqual(record[x,y], self.console.get_char(x, y), 'console data should be overwritten') - self.flush() # show progress - for (x,y), data in record.items(): - self.assertEqual(data, self.console.get_char(x, y), 'draw_char should not overwrite any other tiles') - - #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test') - #def test_draw_charErrors(self): - # "test out of bounds assertion errors" - # for x,y in self.get_undrawables(): - # with self.assertRaisesRegexp(AssertionError, r"\(%i, %i\)" % (x, y)): - # self.console.draw_char(x, y, *(self.get_random_character())) - - def test_draw_str(self): - """quick regression test for draw_str""" - width, height = self.console.get_size() - def str_check(array, string, desc): - fg, bg = self.get_random_color(), self.get_random_color() - self.console.clear() - self.console.draw_str(0, 0, string, fg, bg) - self.flush() - i = 0 - for y in range(height): - for x in range(width): - self.assertEqual(self.console.get_char(x, y), (array[i], fg, bg), - '%s should be written out' % desc) - i += 1 - - # array of numbers - array = [random.getrandbits(8) for _ in range(width * height)] - str_check(array, array, 'array of numbers') - - # array of strings - #array = [random.getrandbits(8) for _ in range(width * height)] - #array_str = [chr(c) for c in array] - #str_check(array, array_str, 'array of characters') - - # standard string - array = [random.getrandbits(8) for _ in range(width * height)] - string = ''.join((chr(c) for c in array)) - str_check(array, string, 'standatd string') - - # Unicode string - Python 2 - if IS_PYTHON2: - array = [random.getrandbits(7) for _ in range(width * height)] - ucode = unicode().join((chr(c) for c in array)) - str_check(array, ucode, 'Unicode string') - - - def test_draw_strArray(self): - """strings will raise errors if they pass over the end of the console. - The data will still be written however.""" - width, height = self.console.get_size() - for x,y in self.get_drawables(): - string = [random.getrandbits(8) for _ in range(random.randint(2, 10))] - fg, bg = self.get_random_color(), self.get_random_color() - if len(string) > ((height - y) * width - x): # compare length of string to remaining space on the console - with self.assertRaises(tdl.TDLError): # expect end of console error - self.console.draw_str(x, y, string, fg, bg) - else: - self.console.draw_str(x, y, string, fg, bg) - for ch in string: # inspect console for changes - self.assertEqual(self.console.get_char(x, y), (ch, fg, bg), 'console data should be overwritten, even after an error') - x += 1 - if x == width: - x = 0 - y += 1 - if y == height: - break # end of console - self.flush() # show progress - - #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test') - #def test_draw_strErrors(self): - # "test out of bounds assertion errors" - # for x,y in self.get_undrawables(): - # with self.assertRaisesRegexp(AssertionError, r"\(%i, %i\)" % (x, y)): - # self.console.draw_str(x, y, 'foo', self.get_random_color(), self.get_random_color()) - - def test_draw_rect(self): - consoleCopy = tdl.Console(*(self.console.get_size())) - for x,y in random.sample(list(self.get_drawables()), 20): - consoleCopy.blit(self.console) # copy the console to compare untouched areas - ch, fg, bg = self.get_random_character() - width, height = self.console.get_size() - width, height = random.randint(1, width - x), random.randint(1, height - y) - self.console.draw_rect(x, y, width, height, ch, fg, bg) - self.flush() # show progress - for testX,testY in self.get_drawables(): - if x <= testX < x + width and y <= testY < y + height: - self.assertEqual(self.console.get_char(testX, testY), (ch, fg, bg), 'rectangle area should be overwritten') - else: - self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'this area should remain untouched') - - def test_draw_frame(self): - consoleCopy = tdl.Console(*(self.console.get_size())) - for x,y in random.sample(list(self.get_drawables()), 20): - consoleCopy.blit(self.console) # copy the console to compare untouched areas - ch, fg, bg = self.get_random_character() - width, height = self.console.get_size() - width, height = random.randint(1, width - x), random.randint(1, height - y) - self.console.draw_frame(x, y, width, height, ch, fg, bg) - self.flush() # show progress - for testX,testY in self.get_drawables(): - if x + 1 <= testX < x + width - 1 and y + 1 <= testY < y + height - 1: - self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'inner frame should remain untouched') - elif x <= testX < x + width and y <= testY < y + height: - self.assertEqual(self.console.get_char(testX, testY), (ch, fg, bg), 'frame area should be overwritten') - else: - self.assertEqual(self.console.get_char(testX, testY), consoleCopy.get_char(testX, testY), 'outer frame should remain untouched') - - #@unittest.skipIf(not __debug__, 'python run with optimized flag, skipping an AssertionError test') - #def test_draw_rectFrameErrors(self): - # for x,y in self.get_drawables(): - # ch, fg, bg = self.get_random_character() - # width, height = self.console.get_size() - # width, height = random.randint(x + width, x + width + ERROR_RANGE), random.randint(y + height, y + height + ERROR_RANGE) - # with self.assertRaises(AssertionError): - # self.console.draw_rect(x, y, width, height, ch, fg, bg) - # with self.assertRaises(AssertionError): - # self.console.draw_frame(x, y, width, height, ch, fg, bg) - - #@unittest.skip("Need this to be faster before unskipping") - def test_scrolling(self): - """marks a spot and then scrolls the console, checks to make sure no - other spots are marked, test also knows if it's out of bounds. - - This test is a bit slow, it could be made more efficent by marking - several areas and not clearing the console every loop. - """ - scrollTests = set([(0, 0), (WIDTH, HEIGHT)]) # include zero and out of bounds - while len(scrollTests) < 10: # add 3 more randoms - scrollTests.add((random.randint(-WIDTH, WIDTH), - random.randint(-HEIGHT, HEIGHT))) - for sx, sy in scrollTests: - noiseData = dict(self.randomize_console()) - self.console.set_colors((0, 0, 0), (0, 0, 0)) - self.console.scroll(sx, sy) - self.flush() # show progress - for x, y in self.get_drawables(): - nX = x - sx - nY = y - sy - if (nX, nY) in noiseData: - self.assertEqual(self.console.get_char(x, y), noiseData[nX, nY], 'random noise should be scrolled') - else: - self.assertEqual(self.console.get_char(x, y), DEFAULT_CHAR, 'scrolled away positions should be clear') - - -def test_fps(): - tdl.set_fps(0) - tdl.get_fps() - -def suite(): - loader = unittest.TestLoader() - load = loader.loadTestsFromTestCase - return unittest.TestSuite([load(BasicTests), load(DrawingTests)]) - -if __name__ == '__main__': - suite = suite() - unittest.TextTestRunner().run(suite) - diff --git a/tests/test_tdl_map.py b/tests/test_tdl_map.py deleted file mode 100644 index 12979da4..00000000 --- a/tests/test_tdl_map.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python - -import itertools -import unittest - -import pytest - -import tdl - - -class MapTests(unittest.TestCase): - - MAP = ( - '############', - '# ### #', - '# ### #', - '# ### ####', - '## #### # ##', - '## ####', - '############', - ) - - WIDTH = len(MAP[0]) - HEIGHT = len(MAP) - - POINT_A = (2, 2) - POINT_B = (9, 2) - POINT_C = (9, 4) - - POINTS_AB = POINT_A + POINT_B - POINTS_AC = POINT_A + POINT_C - - @classmethod - def map_is_transparant(cls, x, y): - try: - return cls.MAP[y][x] == ' ' - except IndexError: - return False - - @classmethod - def path_cost(cls, src_x, src_y, dest_x, dest_y): - if cls.map_is_transparant(dest_x, dest_y): - return 1 - return 0 - - - def setUp(self): - self.map = tdl.map.Map(self.WIDTH, self.HEIGHT) - for y, line in enumerate(self.MAP): - for x, ch in enumerate(line): - trans = ch == ' ' - self.map.transparent[x,y] = self.map.walkable[x,y] = trans - assert self.map.transparent[x,y] == trans - assert self.map.walkable[x,y] == trans - - def test_map_compute_fov(self): - fov = self.map.compute_fov(*self.POINT_A) - assert list(fov), 'should be non-empty' - fov = self.map.compute_fov(*self.POINT_A, fov='PERMISSIVE8') - assert list(fov), 'should be non-empty' - with self.assertRaises(tdl.TDLError): - self.map.compute_fov(*self.POINT_A, fov='invalid option') - - def test_map_compute_path(self): - self.assertTrue(self.map.compute_path(*self.POINTS_AB), - 'should be non-empty') - self.assertFalse(self.map.compute_path(*self.POINTS_AC), - 'invalid path should return an empty list') - - def test_map_specials(self): - for x,y in self.map: - self.assertTrue((x, y) in self.map) - self.assertFalse((-1, -1) in self.map) - - @pytest.mark.filterwarnings("ignore:This function is very slow.") - def test_quick_fov(self): - fov = tdl.map.quick_fov(self.POINT_B[0], self.POINT_B[1], - self.map_is_transparant, radius=2.5) - self.assertTrue(fov, 'should be non-empty') - - def test_bresenham(self): - for x1, x2, y1, y2 in itertools.permutations([-4, -2, 4, 4], 4): - self.assertTrue(tdl.map.bresenham(x1, x2, y1, y2), - 'should be non-empty') - - def test_astar(self): - pathfinder = tdl.map.AStar(self.WIDTH, self.HEIGHT, - self.map_is_transparant) - self.assertTrue(pathfinder.get_path(*self.POINTS_AB)) - - pathfinder = tdl.map.AStar(self.WIDTH, self.HEIGHT, - self.path_cost, None, True) - self.assertTrue(pathfinder.get_path(*self.POINTS_AB)) - self.assertFalse(pathfinder.get_path(*self.POINTS_AC), - 'invalid path should return an empty list') - - -def test_map_fov_cumulative(): - map_ = tdl.map.Map(3, 3) - map_.transparent[::2,:] = True # Add map with small divider. - assert not map_.fov.any() - map_.compute_fov(1, 0, cumulative=True) # Light left side. - assert map_.fov.any() - assert not map_.fov.all() - map_.compute_fov(1, 2, cumulative=True) # Light both sides. - assert map_.fov.all() From c1dba7a0f67784edad542d0b1be07e072550ba70 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 11:00:35 -0800 Subject: [PATCH 0464/1101] Clean up mock object. --- tcod/event.py | 20 ++++++++++---------- tcod/libtcodpy.py | 2 +- tcod/loader.py | 18 ++++++------------ tcod/path.py | 4 +++- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 9fe98481..8b4dbe4b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -89,16 +89,16 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: Point = NamedTuple("Point", [("x", int), ("y", int)]) # manually define names for SDL macros -BUTTON_LEFT = lib.SDL_BUTTON_LEFT -BUTTON_MIDDLE = lib.SDL_BUTTON_MIDDLE -BUTTON_RIGHT = lib.SDL_BUTTON_RIGHT -BUTTON_X1 = lib.SDL_BUTTON_X1 -BUTTON_X2 = lib.SDL_BUTTON_X2 -BUTTON_LMASK = lib.SDL_BUTTON_LMASK -BUTTON_MMASK = lib.SDL_BUTTON_MMASK -BUTTON_RMASK = lib.SDL_BUTTON_RMASK -BUTTON_X1MASK = lib.SDL_BUTTON_X1MASK -BUTTON_X2MASK = lib.SDL_BUTTON_X2MASK +BUTTON_LEFT = 1 +BUTTON_MIDDLE = 2 +BUTTON_RIGHT = 3 +BUTTON_X1 = 4 +BUTTON_X2 = 5 +BUTTON_LMASK = 0x1 +BUTTON_MMASK = 0x2 +BUTTON_RMASK = 0x4 +BUTTON_X1MASK = 0x8 +BUTTON_X2MASK = 0x10 # reverse tables are used to get the tcod.event name from the value. _REVERSE_BUTTON_TABLE = { diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 61f768a5..71971622 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4404,7 +4404,7 @@ def sys_clipboard_get() -> str: @atexit.register def _atexit_verify() -> None: """Warns if the libtcod root console is implicitly deleted.""" - if lib.TCOD_ctx.root: + if lib and lib.TCOD_ctx.root: warnings.warn( "The libtcod root console was implicitly deleted.\n" "Make sure the 'with' statement is used with the root console to" diff --git a/tcod/loader.py b/tcod/loader.py index 9a210bb6..74b8e2c1 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -66,24 +66,18 @@ def get_sdl_version() -> str: class _Mock(object): """Mock object needed for ReadTheDocs.""" - CData = () # This gets passed to an isinstance call. - @staticmethod def def_extern() -> Any: """Pass def_extern call silently.""" return lambda func: func - def __getattr__(self, attr: Any) -> Any: - """This object pretends to have everything.""" - return self - - def __call__(self, *args: Any, **kargs: Any) -> Any: - """Suppress any other calls""" - return self + def __getattr__(self, attr: str) -> None: + """Return None on any attribute.""" + return None - def __str__(self) -> Any: - """Just have ? in case anything leaks as a parameter default.""" - return "?" + def __bool__(self) -> bool: + """Allow checking for this mock object at import time.""" + return False lib = None # type: Any diff --git a/tcod/path.py b/tcod/path.py index 2d3778ad..6bb10887 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -62,6 +62,8 @@ def _get_pathcost_func( name: str, ) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" + if not ffi: + return lambda x1, y1, x2, y2, _: 0 return ffi.cast( # type: ignore "TCOD_path_func_t", ffi.addressof(lib, name) ) @@ -162,7 +164,7 @@ def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: "struct PathCostArray*", (ffi.cast("char*", self.ctypes.data), self.strides), ) - return callback, userdata, self.shape + return callback, userdata, (self.shape[0], self.shape[1]) class _PathFinder(object): From eafb42358ce926ca0161677b9ce764bd2e12fee3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 11:48:58 -0800 Subject: [PATCH 0465/1101] Resolve all Sphinx warnings. Setup configuration files for ReadTheDocs. --- .readthedocs.yml | 24 ++++++++++++++++++++++++ docs/glossary.rst | 6 ++++++ docs/tcod.rst | 14 -------------- tcod/event.py | 2 +- tcod/libtcodpy.py | 22 +++++++++++----------- 5 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 .readthedocs.yml delete mode 100644 docs/tcod.rst diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..51084f18 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,24 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + image: latest + +sphinx: + builder: html + configuration: docs/conf.py + fail_on_warning: true + +formats: + - htmlzip + - pdf + - epub + +python: + version: 3.8 + install: + - requirements: requirements.txt + - requirements: docs/requirements.txt diff --git a/docs/glossary.rst b/docs/glossary.rst index 3faaa446..7cd9359a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -56,3 +56,9 @@ Glossary So if you come across a project using the original `libtcodpy` you can delete the `libtcodpy/` folder and then :term:`python-tcod` will load instead. + + color control + color controls + Libtcod's old system which assigns colors to specific codepoints. + This is deprecated in favor of the codes which set the foreground and + background colors directly. diff --git a/docs/tcod.rst b/docs/tcod.rst deleted file mode 100644 index 7a7f6331..00000000 --- a/docs/tcod.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. toctree:: - :maxdepth: 1 - :caption: python-tcod API - - tcod/bsp - tcod/console - tcod/event - tcod/image - tcod/map - tcod/noise - tcod/path - tcod/random - tcod/tileset - libtcodpy diff --git a/tcod/event.py b/tcod/event.py index 8b4dbe4b..aaa35491 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -601,7 +601,7 @@ class WindowMoved(WindowEvent): Attributes: type (str): Always "WINDOWMOVED". x (int): Movement on the x-axis. - x (int): Movement on the y-axis. + y (int): Movement on the y-axis. """ def __init__(self, x: int, y: int) -> None: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 71971622..6a7735dc 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1008,7 +1008,7 @@ def console_set_custom_font( file respectfully. .. deprecated:: 11.13 - Load fonts using :any:`tcod.tileset.load_tileheet` instead. + Load fonts using :any:`tcod.tileset.load_tilesheet` instead. See :ref:`getting-started` for more info. """ if not os.path.exists(_unicode(fontFile)): @@ -1692,7 +1692,7 @@ def console_print_frame( def console_set_color_control( con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int] ) -> None: - """Configure :any:`color controls`. + """Configure :term:`color controls`. Args: con (int): :any:`Color control` constant to modify. @@ -2426,7 +2426,7 @@ def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: following are true:: * The array is 2 dimensional. * The array has the C_CONTIGUOUS or F_CONTIGUOUS flag. - * The array's dtype is :any:`dtype.float32`. + * The array's dtype is `dtype.float32`. The returned NumPy array will fit all these conditions. @@ -3576,10 +3576,10 @@ def noise_get( ) -> float: """Return the noise value sampled from the ``f`` coordinate. - ``f`` should be a tuple or list with a length matching - :any:`Noise.dimensions`. - If ``f`` is shoerter than :any:`Noise.dimensions` the missing coordinates - will be filled with zeros. + ``f`` should be a tuple or list with a length matching the `dimensions` + attribute of :any:`Noise`. + If ``f`` is shorter than `dimensions` the missing coordinates will be + filled with zeros. Args: n (Noise): A Noise instance. @@ -4151,7 +4151,7 @@ def sys_elapsed_milli() -> int: int: Time since the progeam has started in milliseconds. .. deprecated:: 2.0 - Use :any:`time.clock` instead. + Use Python's :mod:`time` module instead. """ return int(lib.TCOD_sys_elapsed_milli()) @@ -4164,7 +4164,7 @@ def sys_elapsed_seconds() -> float: float: Time since the progeam has started in seconds. .. deprecated:: 2.0 - Use :any:`time.clock` instead. + Use Python's :mod:`time` module instead. """ return float(lib.TCOD_sys_elapsed_seconds()) @@ -4307,8 +4307,8 @@ def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: Note: This callback will only be called by the SDL renderer. - The callack will receive a :any:`CData ` void* to an - SDL_Surface* struct. + The callback will receive a CData `void*` pointer to an + `SDL_Surface*` struct. The callback is called on every call to :any:`tcod.console_flush`. From 4b78c085f72a9cfb35795cef5c3028220eacd8a3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 13:30:09 -0800 Subject: [PATCH 0466/1101] Redirect all changelogs to one location. --- README.rst | 7 +++++++ docs/changelog.rst | 7 ++++++- setup.py | 6 +----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index feaa897e..8f37e1b3 100755 --- a/README.rst +++ b/README.rst @@ -52,6 +52,13 @@ For the most part it's just:: * Windows, Linux, or MacOS X 10.9+. * On Linux, requires libsdl2 (2.0.5+). +=========== + Changelog +=========== + +You can find the most recent changelog +`here `_. + ========= License ========= diff --git a/docs/changelog.rst b/docs/changelog.rst index 565b0521..1b4f389c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1 +1,6 @@ -.. include:: ../CHANGELOG.rst +=========== + Changelog +=========== + +You can find the most recent changelog +`here `_. diff --git a/setup.py b/setup.py index f45600de..49bd7c2a 100755 --- a/setup.py +++ b/setup.py @@ -77,11 +77,7 @@ def get_package_data(): def get_long_description(): """Return this projects description.""" with open("README.rst", "r") as f: - readme = f.read() - with open("CHANGELOG.rst", "r") as f: - changelog = f.read() - changelog = changelog.replace("\nUnreleased\n------------------", "") - return "\n".join([readme, changelog]) + return f.read() def check_sdl_version(): From 6f0224bc8e4c85006029bbca8ae1302f75f09718 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Jan 2021 21:41:29 -0800 Subject: [PATCH 0467/1101] Fix out-of-date info. --- CHANGELOG.rst | 3 +++ README.rst | 2 +- build_libtcod.py | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 637fda02..70d04c9a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Changed + - Now bundles SDL 2.0.14 for MacOS. + Removed - Python 3.5 is no longer supported. - The `tdl` module has been dropped. diff --git a/README.rst b/README.rst index 8f37e1b3..b2f41a3e 100755 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ For the most part it's just:: ============== Requirements ============== -* Python 3.5+ +* Python 3.6+ * Windows, Linux, or MacOS X 10.9+. * On Linux, requires libsdl2 (2.0.5+). diff --git a/build_libtcod.py b/build_libtcod.py index b54c4bbb..e2fbeb8c 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -25,8 +25,6 @@ SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") # The SDL2 version to include in binary distributions. -# MacOS delocate-wheel will fail without throwing an error when bundling -# modern versions of SDL. SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") From 007ad8dab9ea042c5fbad1be387e6a4ce8d44059 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Jan 2021 12:54:58 -0800 Subject: [PATCH 0468/1101] Switch MacOS jobs to the most supported version. --- .github/workflows/python-package-macos.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 8cc3448d..e5281331 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['macos-11.0'] + os: ['macos-latest'] python-version: ['3.7.9'] steps: @@ -77,8 +77,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['macos-11.0'] - python-version: ['3.7.9'] + os: ['macos-latest'] + python-version: ['3.x'] steps: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 From 9554fabf61b85a722d86520a8391feac9de7df4c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Jan 2021 18:58:38 -0800 Subject: [PATCH 0469/1101] Switch Python samples to use contexts. This removes the credits banner from the samples since that doesn't support contexts. It's possible this won't be placed in the library with a working version. --- examples/samples_tcod.py | 161 ++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 72 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f6bc1837..e84f6691 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -5,6 +5,8 @@ # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ +from __future__ import annotations + import copy import math import os @@ -37,10 +39,16 @@ def get_data(path: str) -> str: SAMPLE_SCREEN_Y = 10 FONT = get_data("fonts/dejavu10x10_gs_tc.png") -root_console = None +# Mutable global names. +context: tcod.context.Context +tileset: tcod.tileset.Tileset +root_console = tcod.Console(80, 50, order="F") sample_console = tcod.console.Console( SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F" ) +cur_sample = 0 # Current selected sample. +frame_times = [time.perf_counter()] +frame_length = [0.0] class Sample(tcod.event.EventDispatch): @@ -54,7 +62,7 @@ def on_draw(self): pass def ev_keydown(self, event: tcod.event.KeyDown): - global cur_sample + global cur_sample, context if event.sym == tcod.event.K_DOWN: cur_sample = (cur_sample + 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() @@ -71,7 +79,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): elif event.sym == tcod.event.K_PRINTSCREEN or event.sym == ord("p"): print("screenshot") if event.mod & tcod.event.KMOD_LALT: - tcod.console_save_apf(None, "samples.apf") + tcod.console_save_apf(root_console, "samples.apf") print("apf") else: tcod.sys_save_screenshot() @@ -79,8 +87,9 @@ def ev_keydown(self, event: tcod.event.KeyDown): elif event.sym == tcod.event.K_ESCAPE: raise SystemExit() elif event.sym in RENDERER_KEYS: - tcod.sys_set_renderer(RENDERER_KEYS[event.sym]) - draw_renderer_menu() + # Swap the active context for one with a different renderer. + context.close() + context = init_context(RENDERER_KEYS[event.sym]) def ev_quit(self, event: tcod.event.Quit): raise SystemExit() @@ -101,9 +110,6 @@ def __init__(self): # corner indexes self.corners = np.array([0, 1, 2, 3]) - def on_enter(self): - tcod.sys_set_fps(0) - def on_draw(self): self.slide_corner_colors() self.interpolate_corner_colors() @@ -200,7 +206,6 @@ def __init__(self): def on_enter(self): self.counter = time.perf_counter() - tcod.sys_set_fps(0) # get a "screenshot" of the current sample screen sample_console.blit(dest=self.screenshot) @@ -273,9 +278,6 @@ def ev_keydown(self, event: tcod.event.KeyDown): else: super().ev_keydown(event) - def on_enter(self): - tcod.sys_set_fps(0) - def on_draw(self): alpha = 0.0 if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: @@ -378,9 +380,6 @@ def get_noise(self): seed=None, ) - def on_enter(self): - tcod.sys_set_fps(0) - def on_draw(self): self.dx = time.perf_counter() * 0.25 self.dy = time.perf_counter() * 0.25 @@ -487,7 +486,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): DARK_GROUND = (50, 50, 150) LIGHT_GROUND = (200, 180, 50) -SAMPLE_MAP = [ +SAMPLE_MAP_ = [ "##############################################", "####################### #################", "##################### # ###############", @@ -510,7 +509,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): "##############################################", ] -SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP]).transpose() +SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP_]).transpose() FOV_ALGO_NAMES = [ "BASIC ", @@ -576,9 +575,6 @@ def draw_ui(self): bg=None, ) - def on_enter(self): - tcod.sys_set_fps(60) - def on_draw(self): sample_console.clear() # Draw the help text & player @. @@ -696,7 +692,6 @@ def __init__(self): self.dijk = tcod.dijkstra_new(self.map) def on_enter(self): - tcod.sys_set_fps(60) # we draw the foreground only the first time. # during the player movement, only the @ is redrawn. # the rest impacts only the background color @@ -785,7 +780,7 @@ def on_draw(self): ) # move the creature - self.busy -= tcod.sys_get_last_frame_length() + self.busy -= frame_length[-1] if self.busy <= 0.0: self.busy = 0.2 if self.using_astar: @@ -1110,9 +1105,6 @@ def __init__(self): self.img.set_key_color(tcod.black) self.circle = tcod.image_load(get_data("img/circle.png")) - def on_enter(self): - tcod.sys_set_fps(0) - def on_draw(self): sample_console.clear() x = sample_console.width / 2 + math.cos(time.time()) * 10.0 @@ -1162,7 +1154,6 @@ def __init__(self): def on_enter(self): tcod.mouse_move(320, 200) tcod.mouse_show_cursor(True) - tcod.sys_set_fps(60) def ev_mousemotion(self, event: tcod.event.MouseMotion): self.motion = event @@ -1188,9 +1179,9 @@ def on_draw(self): sample_console.print( 1, 1, - "Mouse position : %4dx%4d\n" - "Mouse cell : %4dx%4d\n" - "Mouse movement : %4dx%4d\n" + "Pixel position : %4dx%4d\n" + "Tile position : %4dx%4d\n" + "Tile movement : %4dx%4d\n" "Left button : %s\n" "Right button : %s\n" "Middle button : %s\n" @@ -1235,9 +1226,6 @@ def __init__(self): self.names = [] self.sets = None - def on_enter(self): - tcod.sys_set_fps(60) - def on_draw(self): if self.nbsets == 0: # parse all *.cfg files in data/namegen @@ -1268,7 +1256,7 @@ def on_draw(self): bg=None, alignment=tcod.RIGHT, ) - self.delay += tcod.sys_get_last_frame_length() + self.delay += frame_length[-1] if self.delay > 0.5: self.delay -= 0.5 self.names.append(tcod.namegen_generate(self.sets[self.curset])) @@ -1341,7 +1329,6 @@ def __init__(self): def on_enter(self): global frac_t, abs_t, lights, tex_r, tex_g, tex_b - tcod.sys_set_fps(0) sample_console.clear() # render status message sample_console.print( 1, SCREEN_H - 3, "Renderer: NumPy", fg=tcod.white, bg=None @@ -1358,7 +1345,7 @@ def on_draw(self): global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc global texture, texture2, brightness2, R2, G2, B2 - time_delta = tcod.sys_get_last_frame_length() * SPEED # advance time + time_delta = frame_length[-1] * SPEED # advance time frac_t += time_delta # increase fractional (always < 1.0) time abs_t += time_delta # increase absolute elapsed time # integer time units that passed this frame (number of texture pixels @@ -1508,41 +1495,70 @@ def on_draw(self): FastRenderSample(), ) -cur_sample = 0 +def init_context(renderer: int) -> tcod.context.Context: + """Return a new context with common parameters set. + + This function exists to more easily switch between renderers. + """ + libtcod_version = "%i.%i.%i" % ( + tcod.lib.TCOD_MAJOR_VERSION, + tcod.lib.TCOD_MINOR_VERSION, + tcod.lib.TCOD_PATCHLEVEL, + ) + return tcod.context.new( + columns=root_console.width, + rows=root_console.height, + title=f"python-tcod samples" + f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})", + renderer=renderer, + vsync=False, # VSync turned off since this is for benchmarking. + tileset=tileset, + ) def main(): - global cur_sample, root_console - tcod.console_set_custom_font( - FONT, tcod.FONT_TYPE_GREYSCALE | tcod.FONT_LAYOUT_TCOD - ) - root_console = tcod.console_init_root( - 80, 50, "python-tcod samples", False, tcod.RENDERER_SDL2, order="F" + global context, tileset + tileset = tcod.tileset.load_tilesheet( + FONT, 32, 8, tcod.tileset.CHARMAP_TCOD ) - credits_end = False - SAMPLES[cur_sample].on_enter() - draw_samples_menu() - draw_renderer_menu() - - while not tcod.console_is_window_closed(): - root_console.clear() - draw_samples_menu() - draw_renderer_menu() - # render credits - if not credits_end: - credits_end = tcod.console_credits_render(60, 43, 0) - - # render the sample - SAMPLES[cur_sample].on_draw() - sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) - draw_stats() - tcod.console_flush() - handle_events() + context = init_context(tcod.RENDERER_SDL2) + try: + credits_end = False + SAMPLES[cur_sample].on_enter() + + while True: + root_console.clear() + draw_samples_menu() + draw_renderer_menu() + + # render the sample + SAMPLES[cur_sample].on_draw() + sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) + draw_stats() + context.present(root_console) + handle_time() + handle_events() + finally: + # Normally context would be used in a with block and closed + # automatically. but since this context might be switched to one with a + # different renderer it is closed manually here. + context.close() + + +def handle_time(): + if len(frame_times) > 100: + frame_times.pop(0) + frame_length.pop(0) + frame_times.append(time.perf_counter()) + frame_length.append(frame_times[-1] - frame_times[-2]) def handle_events(): for event in tcod.event.get(): + context.convert_event(event) SAMPLES[cur_sample].dispatch(event) + if isinstance(event, tcod.event.Quit): + raise SystemExit() def draw_samples_menu(): @@ -1564,28 +1580,29 @@ def draw_samples_menu(): def draw_stats(): + try: + fps = 1 / (sum(frame_length) / len(frame_length)) + except ZeroDivisionError: + fps = 0 + + style = {"fg": tcod.grey, "bg": None, "alignment": tcod.RIGHT} root_console.print( 79, 46, - " last frame : %3d ms (%3d fps)" - % (tcod.sys_get_last_frame_length() * 1000.0, tcod.sys_get_fps()), - fg=tcod.grey, - bg=None, - alignment=tcod.RIGHT, + "last frame :%5.1f ms (%4d fps)" + % (frame_length[-1] * 1000.0, fps), + **style, ) root_console.print( 79, 47, - "elapsed : %8d ms %4.2fs" + "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), - fg=tcod.grey, - bg=None, - alignment=tcod.RIGHT, + **style, ) def draw_renderer_menu(): - current_renderer = tcod.sys_get_renderer() root_console.print( 42, 46 - (tcod.NB_RENDERERS + 1), @@ -1594,7 +1611,7 @@ def draw_renderer_menu(): bg=tcod.black, ) for i, name in enumerate(RENDERER_NAMES): - if i == current_renderer: + if i == context.renderer_type: fg = tcod.white bg = tcod.light_blue else: From 5c39d6dafe307f45e0995ae0bb6edfbadab5aaa8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 26 Jan 2021 18:03:38 -0800 Subject: [PATCH 0470/1101] Add type-hinting to samples_tcod.py. Remaining type errors need to be fixed with refactoring. --- examples/samples_tcod.py | 393 ++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 188 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index e84f6691..ec37c7d6 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -14,6 +14,7 @@ import sys import time import warnings +from typing import List import numpy as np import tcod @@ -33,6 +34,12 @@ def get_data(path: str) -> str: return os.path.join(DATA_DIR, path) +WHITE = (255, 255, 255) +GREY = (127, 127, 127) +BLACK = (0, 0, 0) +LIGHT_BLUE = (63, 63, 255) +LIGHT_YELLOW = (255, 255, 63) + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 @@ -51,17 +58,17 @@ def get_data(path: str) -> str: frame_length = [0.0] -class Sample(tcod.event.EventDispatch): - def __init__(self, name: str = ""): +class Sample(tcod.event.EventDispatch[None]): + def __init__(self, name: str = "") -> None: self.name = name - def on_enter(self): + def on_enter(self) -> None: pass - def on_draw(self): + def on_draw(self) -> None: pass - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: global cur_sample, context if event.sym == tcod.event.K_DOWN: cur_sample = (cur_sample + 1) % len(SAMPLES) @@ -91,12 +98,12 @@ def ev_keydown(self, event: tcod.event.KeyDown): context.close() context = init_context(RENDERER_KEYS[event.sym]) - def ev_quit(self, event: tcod.event.Quit): + def ev_quit(self, event: tcod.event.Quit) -> None: raise SystemExit() class TrueColorSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "True colors" # corner colors self.colors = np.array( @@ -110,14 +117,14 @@ def __init__(self): # corner indexes self.corners = np.array([0, 1, 2, 3]) - def on_draw(self): + def on_draw(self) -> None: self.slide_corner_colors() self.interpolate_corner_colors() self.darken_background_characters() self.randomize_sample_conole() self.print_banner() - def slide_corner_colors(self): + def slide_corner_colors(self) -> None: # pick random RGB channels for each corner rand_channels = np.random.randint(low=0, high=3, size=4) @@ -130,18 +137,22 @@ def slide_corner_colors(self): self.slide_dir[self.colors[:] == 255] = -1 self.slide_dir[self.colors[:] == 0] = 1 - def interpolate_corner_colors(self): + def interpolate_corner_colors(self) -> None: # interpolate corner colors across the sample console - left = np.linspace(self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT) - right = np.linspace(self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT) + left = np.linspace( + self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT + ) + right = np.linspace( + self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT + ) sample_console.bg[:] = np.linspace(left, right, SAMPLE_SCREEN_WIDTH) - def darken_background_characters(self): + def darken_background_characters(self) -> None: # darken background characters sample_console.fg[:] = sample_console.bg[:] sample_console.fg[:] //= 2 - def randomize_sample_conole(self): + def randomize_sample_conole(self) -> None: # randomize sample console characters sample_console.ch[:] = np.random.randint( low=ord("a"), @@ -150,7 +161,7 @@ def randomize_sample_conole(self): dtype=np.intc, ).reshape(sample_console.ch.shape) - def print_banner(self): + def print_banner(self) -> None: # print text on top of samples sample_console.print_box( x=1, @@ -159,15 +170,15 @@ def print_banner(self): height=sample_console.height - 1, string="The Doryen library uses 24 bits colors, for both " "background and foreground.", - fg=tcod.white, - bg=tcod.grey, + fg=WHITE, + bg=GREY, bg_blend=tcod.BKGND_MULTIPLY, alignment=tcod.CENTER, ) class OffscreenConsoleSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Offscreen console" self.secondary = tcod.console.Console( sample_console.width // 2, sample_console.height // 2 @@ -175,7 +186,7 @@ def __init__(self): self.screenshot = tcod.console.Console( sample_console.width, sample_console.height ) - self.counter = 0 + self.counter = 0.0 self.x = 0 self.y = 0 self.xdir = 1 @@ -188,8 +199,8 @@ def __init__(self): sample_console.height // 2, "Offscreen console", False, - fg=tcod.white, - bg=tcod.black, + fg=WHITE, + bg=BLACK, ) self.secondary.print_box( @@ -199,17 +210,17 @@ def __init__(self): sample_console.height // 2, "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", - fg=tcod.white, + fg=WHITE, bg=None, alignment=tcod.CENTER, ) - def on_enter(self): + def on_enter(self) -> None: self.counter = time.perf_counter() # get a "screenshot" of the current sample screen sample_console.blit(dest=self.screenshot) - def on_draw(self): + def on_draw(self) -> None: if time.perf_counter() - self.counter >= 1: self.counter = time.perf_counter() self.x += self.xdir @@ -254,7 +265,7 @@ class LineDrawingSample(Sample): "BKGND_ALPHA", ] - def __init__(self): + def __init__(self) -> None: self.name = "Line drawing" self.mk_flag = tcod.BKGND_SET self.bk_flag = tcod.BKGND_SET @@ -270,7 +281,7 @@ def __init__(self): ) / 2 self.bk.ch[:] = ord(" ") - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): self.bk_flag += 1 if (self.bk_flag & 0xFF) > tcod.BKGND_ALPH: @@ -278,23 +289,24 @@ def ev_keydown(self, event: tcod.event.KeyDown): else: super().ev_keydown(event) - def on_draw(self): + def on_draw(self) -> None: alpha = 0.0 if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: # for the alpha mode, update alpha every frame alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 - self.bk_flag = tcod.BKGND_ALPHA(alpha) + self.bk_flag = tcod.BKGND_ALPHA(int(alpha)) elif (self.bk_flag & 0xFF) == tcod.BKGND_ADDA: # for the add alpha mode, update alpha every frame alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 - self.bk_flag = tcod.BKGND_ADDALPHA(alpha) + self.bk_flag = tcod.BKGND_ADDALPHA(int(alpha)) self.bk.blit(sample_console) recty = int( (sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0) ) for x in range(sample_console.width): - col = [x * 255 // sample_console.width] * 3 + value = x * 255 // sample_console.width + col = (value, value, value) tcod.console_set_char_background( sample_console, x, recty, col, self.bk_flag ) @@ -323,31 +335,31 @@ def on_draw(self): and 0 <= y < sample_console.height ): tcod.console_set_char_background( - sample_console, x, y, tcod.light_blue, self.bk_flag + sample_console, x, y, LIGHT_BLUE, self.bk_flag ) sample_console.print( 2, 2, "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF], - fg=tcod.white, + fg=WHITE, bg=None, ) class NoiseSample(Sample): - NOISE_OPTIONS = [ # [name, algorithm, implementation], - ["perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE], - ["simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE], - ["wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE], - ["perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM], - ["perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE], - ["simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM], - ["simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE], - ["wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM], - ["wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE], + NOISE_OPTIONS = [ # (name, algorithm, implementation) + ("perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE), + ("simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE), + ("wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE), + ("perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM), + ("perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE), + ("simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM), + ("simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE), + ("wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM), + ("wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE), ] - def __init__(self): + def __init__(self) -> None: self.name = "Noise" self.func = 0 self.dx = 0.0 @@ -362,14 +374,14 @@ def __init__(self): ) @property - def algorithm(self): + def algorithm(self) -> int: return self.NOISE_OPTIONS[self.func][1] @property - def implementation(self): + def implementation(self) -> int: return self.NOISE_OPTIONS[self.func][2] - def get_noise(self): + def get_noise(self) -> tcod.noise.Noise: return tcod.noise.Noise( 2, self.algorithm, @@ -380,7 +392,7 @@ def get_noise(self): seed=None, ) - def on_draw(self): + def on_draw(self) -> None: self.dx = time.perf_counter() * 0.25 self.dy = time.perf_counter() * 0.25 for y in range(2 * sample_console.height): @@ -405,50 +417,48 @@ def on_draw(self): recth, ch=0, fg=None, - bg=tcod.grey, + bg=GREY, bg_blend=tcod.BKGND_MULTIPLY, ) sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = ( - sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * tcod.grey / 255 + sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * GREY / 255 ) for curfunc in range(len(self.NOISE_OPTIONS)): text = "%i : %s" % (curfunc + 1, self.NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: sample_console.print( - 2, 2 + curfunc, text, fg=tcod.white, bg=tcod.light_blue + 2, 2 + curfunc, text, fg=WHITE, bg=LIGHT_BLUE ) else: - sample_console.print( - 2, 2 + curfunc, text, fg=tcod.grey, bg=None - ) + sample_console.print(2, 2 + curfunc, text, fg=GREY, bg=None) sample_console.print( - 2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=tcod.white, bg=None + 2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None ) if self.implementation != tcod.noise.SIMPLE: sample_console.print( 2, 12, "E/D : hurst (%2.1f)" % self.hurst, - fg=tcod.white, + fg=WHITE, bg=None, ) sample_console.print( 2, 13, "R/F : lacunarity (%2.1f)" % self.lacunarity, - fg=tcod.white, + fg=WHITE, bg=None, ) sample_console.print( 2, 14, "T/G : octaves (%2.1f)" % self.octaves, - fg=tcod.white, + fg=WHITE, bg=None, ) - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: if ord("9") >= event.sym >= ord("1"): self.func = event.sym - ord("1") self.noise = self.get_noise() @@ -533,7 +543,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): class FOVSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Field of view" self.player_x = 20 @@ -558,7 +568,7 @@ def __init__(self): self.dark_map_bg = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B") self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL - def draw_ui(self): + def draw_ui(self) -> None: sample_console.print( 1, 1, @@ -571,18 +581,18 @@ def draw_ui(self): "on " if self.light_walls else "off", FOV_ALGO_NAMES[self.algo_num], ), - fg=tcod.white, + fg=WHITE, bg=None, ) - def on_draw(self): + def on_draw(self) -> None: sample_console.clear() # Draw the help text & player @. self.draw_ui() sample_console.print(self.player_x, self.player_y, "@") # Draw windows. sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = tcod.CHAR_DHLINE - sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = tcod.black + sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = BLACK # Get a 2D boolean array of visible cells. fov = tcod.map.compute_fov( @@ -633,7 +643,7 @@ def on_draw(self): fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg ) - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: MOVE_KEYS = { ord("i"): (0, -1), ord("j"): (-1, 0), @@ -663,18 +673,15 @@ def ev_keydown(self, event: tcod.event.KeyDown): class PathfindingSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Path finding" self.px = 20 self.py = 10 self.dx = 24 self.dy = 1 - self.map = None - self.path = None self.dijk_dist = 0.0 self.using_astar = True - self.dijk = None self.recalculate = False self.busy = 0.0 self.oldchar = " " @@ -691,24 +698,24 @@ def __init__(self): self.path = tcod.path_new_using_map(self.map) self.dijk = tcod.dijkstra_new(self.map) - def on_enter(self): + def on_enter(self) -> None: # we draw the foreground only the first time. # during the player movement, only the @ is redrawn. # the rest impacts only the background color # draw the help text & player @ sample_console.clear() sample_console.ch[self.dx, self.dy] = ord("+") - sample_console.fg[self.dx, self.dy] = tcod.white + sample_console.fg[self.dx, self.dy] = WHITE sample_console.ch[self.px, self.py] = ord("@") - sample_console.fg[self.px, self.py] = tcod.white + sample_console.fg[self.px, self.py] = WHITE sample_console.print( 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", - fg=tcod.white, + fg=WHITE, bg=None, ) - sample_console.print(1, 4, "Using : A*", fg=tcod.white, bg=None) + sample_console.print(1, 4, "Using : A*", fg=WHITE, bg=None) # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -718,7 +725,7 @@ def on_enter(self): ) self.recalculate = True - def on_draw(self): + def on_draw(self) -> None: if self.recalculate: if self.using_astar: tcod.path_compute( @@ -803,7 +810,7 @@ def on_draw(self): ) self.recalculate = True - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == ord("i") and self.dy > 0: # destination move north tcod.console_put_char( @@ -862,7 +869,7 @@ def ev_keydown(self, event: tcod.event.KeyDown): else: super().ev_keydown(event) - def ev_mousemotion(self, event: tcod.event.MouseMotion): + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if ( @@ -895,51 +902,51 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion): # draw a vertical line -def vline(m, x, y1, y2): +def vline(m: np.ndarray, x: int, y1: int, y2: int) -> None: if y1 > y2: y1, y2 = y2, y1 for y in range(y1, y2 + 1): - m[x][y] = True + m[x, y] = True # draw a vertical line up until we reach an empty space -def vline_up(m, x, y): - while y >= 0 and not m[x][y]: - m[x][y] = True +def vline_up(m: np.ndarray, x: int, y: int) -> None: + while y >= 0 and not m[x, y]: + m[x, y] = True y -= 1 # draw a vertical line down until we reach an empty space -def vline_down(m, x, y): - while y < SAMPLE_SCREEN_HEIGHT and not m[x][y]: - m[x][y] = True +def vline_down(m: np.ndarray, x: int, y: int) -> None: + while y < SAMPLE_SCREEN_HEIGHT and not m[x, y]: + m[x, y] = True y += 1 # draw a horizontal line -def hline(m, x1, y, x2): +def hline(m: np.ndarray, x1: int, y: int, x2: int) -> None: if x1 > x2: x1, x2 = x2, x1 for x in range(x1, x2 + 1): - m[x][y] = True + m[x, y] = True # draw a horizontal line left until we reach an empty space -def hline_left(m, x, y): - while x >= 0 and not m[x][y]: - m[x][y] = True +def hline_left(m: np.ndarray, x: int, y: int) -> None: + while x >= 0 and not m[x, y]: + m[x, y] = True x -= 1 # draw a horizontal line right until we reach an empty space -def hline_right(m, x, y): - while x < SAMPLE_SCREEN_WIDTH and not m[x][y]: - m[x][y] = True +def hline_right(m: np.ndarray, x: int, y: int) -> None: + while x < SAMPLE_SCREEN_WIDTH and not m[x, y]: + m[x, y] = True x += 1 # the class building the dungeon from the bsp nodes -def traverse_node(bsp_map, node): +def traverse_node(bsp_map: np.ndarray, node: tcod.bsp.BSP) -> None: if not node.children: # calculate the room size if bsp_room_walls: @@ -956,9 +963,9 @@ def traverse_node(bsp_map, node): node.y += random.randint(0, node.height - new_height) node.width, node.height = new_width, new_height # dig the room - for x in range(node.x, node.x + node.width): - for y in range(node.y, node.y + node.height): - bsp_map[x][y] = True + bsp_map[ + node.x : node.x + node.width, node.y : node.y + node.height + ] = True else: # resize the node to fit its sons left, right = node.children @@ -1001,11 +1008,10 @@ def traverse_node(bsp_map, node): y = random.randint(miny, maxy) hline_left(bsp_map, right.x - 1, y) hline_right(bsp_map, right.x, y) - return True class BSPSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Bsp toolkit" self.bsp = tcod.bsp.BSP( 1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1 @@ -1015,7 +1021,7 @@ def __init__(self): ) self.bsp_generate() - def bsp_generate(self): + def bsp_generate(self) -> None: self.bsp.children = () if bsp_room_walls: self.bsp.split_recursive( @@ -1031,12 +1037,12 @@ def bsp_generate(self): ) self.bsp_refresh() - def bsp_refresh(self): + def bsp_refresh(self) -> None: self.bsp_map[...] = False for node in copy.deepcopy(self.bsp).inverted_level_order(): traverse_node(self.bsp_map, node) - def on_draw(self): + def on_draw(self) -> None: sample_console.clear() rooms = "OFF" if bsp_random_room: @@ -1049,7 +1055,7 @@ def on_draw(self): "+-: bsp depth %d\n" "*/: room size %d\n" "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), - fg=tcod.white, + fg=WHITE, bg=None, ) if bsp_random_room: @@ -1057,7 +1063,7 @@ def on_draw(self): if bsp_room_walls: walls = "ON" sample_console.print( - 1, 6, "2 : room walls %s" % walls, fg=tcod.white, bg=None + 1, 6, "2 : room walls %s" % walls, fg=WHITE, bg=None ) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): @@ -1067,7 +1073,7 @@ def on_draw(self): sample_console, x, y, color, tcod.BKGND_SET ) - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): self.bsp_generate() @@ -1098,14 +1104,14 @@ def ev_keydown(self, event: tcod.event.KeyDown): class ImageSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Image toolkit" self.img = tcod.image_load(get_data("img/skull.png")) - self.img.set_key_color(tcod.black) + self.img.set_key_color(BLACK) self.circle = tcod.image_load(get_data("img/circle.png")) - def on_draw(self): + def on_draw(self) -> None: sample_console.clear() x = sample_console.width / 2 + math.cos(time.time()) * 10.0 y = sample_console.height / 2 @@ -1144,21 +1150,21 @@ def on_draw(self): class MouseSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Mouse support" self.motion = tcod.event.MouseMotion() self.lbut = self.mbut = self.rbut = 0 - self.log = [] + self.log: List[str] = [] - def on_enter(self): + def on_enter(self) -> None: tcod.mouse_move(320, 200) tcod.mouse_show_cursor(True) - def ev_mousemotion(self, event: tcod.event.MouseMotion): + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: self.motion = event - def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown): + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: if event.button == tcod.event.BUTTON_LEFT: self.lbut = True elif event.button == tcod.event.BUTTON_MIDDLE: @@ -1166,7 +1172,7 @@ def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown): elif event.button == tcod.event.BUTTON_RIGHT: self.rbut = True - def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp): + def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> None: if event.button == tcod.event.BUTTON_LEFT: self.lbut = False elif event.button == tcod.event.BUTTON_MIDDLE: @@ -1174,8 +1180,8 @@ def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp): elif event.button == tcod.event.BUTTON_RIGHT: self.rbut = False - def on_draw(self): - sample_console.clear(bg=tcod.grey) + def on_draw(self) -> None: + sample_console.clear(bg=GREY) sample_console.print( 1, 1, @@ -1196,18 +1202,18 @@ def on_draw(self): ("OFF", "ON")[self.rbut], ("OFF", "ON")[self.mbut], ), - fg=tcod.light_yellow, + fg=LIGHT_YELLOW, bg=None, ) sample_console.print( 1, 10, "1 : Hide cursor\n2 : Show cursor", - fg=tcod.light_yellow, + fg=LIGHT_YELLOW, bg=None, ) - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == ord("1"): tcod.mouse_show_cursor(False) elif event.sym == ord("2"): @@ -1217,16 +1223,16 @@ def ev_keydown(self, event: tcod.event.KeyDown): class NameGeneratorSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Name generator" self.curset = 0 self.nbsets = 0 self.delay = 0.0 - self.names = [] - self.sets = None + self.names: List[str] = [] + self.sets: List[str] = [] - def on_draw(self): + def on_draw(self) -> None: if self.nbsets == 0: # parse all *.cfg files in data/namegen for file in os.listdir(get_data("namegen")): @@ -1238,13 +1244,13 @@ def on_draw(self): self.nbsets = len(self.sets) while len(self.names) > 15: self.names.pop(0) - sample_console.clear(bg=tcod.grey) + sample_console.clear(bg=GREY) sample_console.print( 1, 1, "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.curset], - fg=tcod.white, + fg=WHITE, bg=None, ) for i in range(len(self.names)): @@ -1252,7 +1258,7 @@ def on_draw(self): SAMPLE_SCREEN_WIDTH - 2, 2 + i, self.names[i], - fg=tcod.white, + fg=WHITE, bg=None, alignment=tcod.RIGHT, ) @@ -1261,7 +1267,7 @@ def on_draw(self): self.delay -= 0.5 self.names.append(tcod.namegen_generate(self.sets[self.curset])) - def ev_keydown(self, event: tcod.event.KeyDown): + def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == ord("="): self.curset += 1 if self.curset == self.nbsets: @@ -1291,14 +1297,14 @@ def ev_keydown(self, event: tcod.event.KeyDown): TEX_STRETCH = 5 # texture stretching with tunnel depth SPEED = 15 LIGHT_BRIGHTNESS = ( - 3.5 -) # brightness multiplier for all lights (changes their radius) + 3.5 # brightness multiplier for all lights (changes their radius) +) LIGHTS_CHANCE = 0.07 # chance of a light appearing MAX_LIGHTS = 6 MIN_LIGHT_STRENGTH = 0.2 LIGHT_UPDATE = ( - 0.05 -) # how much the ambient light changes to reflect current light sources + 0.05 # how much the ambient light changes to reflect current light sources +) AMBIENT_LIGHT = 0.8 # brightness of tunnel texture # the coordinates of all tiles in the screen, as numpy arrays. @@ -1317,58 +1323,68 @@ def ev_keydown(self, event: tcod.event.KeyDown): class Light: - def __init__(self, x, y, z, r, g, b, strength): + def __init__( + self, + x: float, + y: float, + z: float, + r: int, + g: int, + b: int, + strength: float, + ) -> None: self.x, self.y, self.z = x, y, z # pos. self.r, self.g, self.b = r, g, b # color self.strength = strength # between 0 and 1, defines brightness class FastRenderSample(Sample): - def __init__(self): + def __init__(self) -> None: self.name = "Python fast render" - def on_enter(self): - global frac_t, abs_t, lights, tex_r, tex_g, tex_b + def on_enter(self) -> None: sample_console.clear() # render status message sample_console.print( - 1, SCREEN_H - 3, "Renderer: NumPy", fg=tcod.white, bg=None + 1, SCREEN_H - 3, "Renderer: NumPy", fg=WHITE, bg=None ) # time is represented in number of pixels of the texture, start later # in time to initialize texture - frac_t = RES_V - 1 - abs_t = RES_V - 1 - lights = [] # lights list, and current color of the tunnel texture - tex_r, tex_g, tex_b = 0, 0, 0 + self.frac_t: float = RES_V - 1 + self.abs_t: float = RES_V - 1 + # light and current color of the tunnel texture + self.lights: List[Light] = [] + self.tex_r = 0.0 + self.tex_g = 0.0 + self.tex_b = 0.0 - def on_draw(self): - global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc - global texture, texture2, brightness2, R2, G2, B2 + def on_draw(self) -> None: + global texture time_delta = frame_length[-1] * SPEED # advance time - frac_t += time_delta # increase fractional (always < 1.0) time - abs_t += time_delta # increase absolute elapsed time + self.frac_t += time_delta # increase fractional (always < 1.0) time + self.abs_t += time_delta # increase absolute elapsed time # integer time units that passed this frame (number of texture pixels # to advance) - int_t = int(frac_t) - frac_t -= int_t # keep this < 1.0 + int_t = int(self.frac_t) + self.frac_t %= 1.0 # keep this < 1.0 # change texture color according to presence of lights (basically, sum # them to get ambient light and smoothly change the current color into # that) ambient_r = AMBIENT_LIGHT * sum( - light.r * light.strength for light in lights + light.r * light.strength for light in self.lights ) ambient_g = AMBIENT_LIGHT * sum( - light.g * light.strength for light in lights + light.g * light.strength for light in self.lights ) ambient_b = AMBIENT_LIGHT * sum( - light.b * light.strength for light in lights + light.b * light.strength for light in self.lights ) alpha = LIGHT_UPDATE * time_delta - tex_r = tex_r * (1 - alpha) + ambient_r * alpha - tex_g = tex_g * (1 - alpha) + ambient_g * alpha - tex_b = tex_b * (1 - alpha) + ambient_b * alpha + self.tex_r = self.tex_r * (1 - alpha) + ambient_r * alpha + self.tex_g = self.tex_g * (1 - alpha) + ambient_g * alpha + self.tex_b = self.tex_b * (1 - alpha) + ambient_b * alpha if int_t >= 1: # roll texture (ie, advance in tunnel) according to int_t @@ -1376,7 +1392,7 @@ def on_draw(self): # time_delta is large) int_t = int_t % RES_V # new pixels are based on absolute elapsed time - int_abs_t = int(abs_t) + int_abs_t = int(self.abs_t) texture = np.roll(texture, -int_t, 1) # replace new stretch of texture with new values @@ -1395,24 +1411,24 @@ def on_draw(self): sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2) # one coordinate into the texture, represents depth in the tunnel - v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t - v = v.clip(0, RES_V - 1) + vv = TEX_STRETCH * float(RES_V) / sqr_dist + self.frac_t + vv = vv.clip(0, RES_V - 1) # another coordinate, represents rotation around the tunnel - u = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U) + uu = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U) # retrieve corresponding pixels from texture - brightness = texture[u.astype(int), v.astype(int)] / 4.0 + 0.5 + brightness = texture[uu.astype(int), vv.astype(int)] / 4.0 + 0.5 # use the brightness map to compose the final color of the tunnel - R = brightness * tex_r - G = brightness * tex_g - B = brightness * tex_b + R = brightness * self.tex_r + G = brightness * self.tex_g + B = brightness * self.tex_b # create new light source if ( random.random() <= time_delta * LIGHTS_CHANCE - and len(lights) < MAX_LIGHTS + and len(self.lights) < MAX_LIGHTS ): x = random.uniform(-0.5, 0.5) y = random.uniform(-0.5, 0.5) @@ -1421,16 +1437,18 @@ def on_draw(self): color = tcod.Color(0, 0, 0) # create bright colors with random hue hue = random.uniform(0, 360) tcod.color_set_hsv(color, hue, 0.5, strength) - lights.append( + self.lights.append( Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength) ) # eliminate lights that are going to be out of view - lights = [ - light for light in lights if light.z - time_delta > 1.0 / RES_V + self.lights = [ + light + for light in self.lights + if light.z - time_delta > 1.0 / RES_V ] - for light in lights: # render lights + for light in self.lights: # render lights # move light's Z coordinate with time, then project its XYZ # coordinates to screen-space light.z -= float(time_delta) / TEX_STRETCH @@ -1495,6 +1513,7 @@ def on_draw(self): FastRenderSample(), ) + def init_context(renderer: int) -> tcod.context.Context: """Return a new context with common parameters set. @@ -1516,14 +1535,13 @@ def init_context(renderer: int) -> tcod.context.Context: ) -def main(): +def main() -> None: global context, tileset tileset = tcod.tileset.load_tilesheet( FONT, 32, 8, tcod.tileset.CHARMAP_TCOD ) context = init_context(tcod.RENDERER_SDL2) try: - credits_end = False SAMPLES[cur_sample].on_enter() while True: @@ -1545,7 +1563,7 @@ def main(): context.close() -def handle_time(): +def handle_time() -> None: if len(frame_times) > 100: frame_times.pop(0) frame_length.pop(0) @@ -1553,7 +1571,7 @@ def handle_time(): frame_length.append(frame_times[-1] - frame_times[-2]) -def handle_events(): +def handle_events() -> None: for event in tcod.event.get(): context.convert_event(event) SAMPLES[cur_sample].dispatch(event) @@ -1561,14 +1579,14 @@ def handle_events(): raise SystemExit() -def draw_samples_menu(): +def draw_samples_menu() -> None: for i, sample in enumerate(SAMPLES): if i == cur_sample: - fg = tcod.white - bg = tcod.light_blue + fg = WHITE + bg = LIGHT_BLUE else: - fg = tcod.grey - bg = tcod.black + fg = GREY + bg = BLACK root_console.print( 2, 46 - (len(SAMPLES) - i), @@ -1579,44 +1597,43 @@ def draw_samples_menu(): ) -def draw_stats(): +def draw_stats() -> None: try: fps = 1 / (sum(frame_length) / len(frame_length)) except ZeroDivisionError: fps = 0 - - style = {"fg": tcod.grey, "bg": None, "alignment": tcod.RIGHT} root_console.print( 79, 46, - "last frame :%5.1f ms (%4d fps)" - % (frame_length[-1] * 1000.0, fps), - **style, + "last frame :%5.1f ms (%4d fps)" % (frame_length[-1] * 1000.0, fps), + fg=GREY, + alignment=tcod.RIGHT, ) root_console.print( 79, 47, "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), - **style, + fg=GREY, + alignment=tcod.RIGHT, ) -def draw_renderer_menu(): +def draw_renderer_menu() -> None: root_console.print( 42, 46 - (tcod.NB_RENDERERS + 1), "Renderer :", - fg=tcod.grey, - bg=tcod.black, + fg=GREY, + bg=BLACK, ) for i, name in enumerate(RENDERER_NAMES): if i == context.renderer_type: - fg = tcod.white - bg = tcod.light_blue + fg = WHITE + bg = LIGHT_BLUE else: - fg = tcod.grey - bg = tcod.black + fg = GREY + bg = BLACK root_console.print(42, 46 - tcod.NB_RENDERERS + i, name, fg, bg) From 1010431ec1b62c4f0465efb224227a830e8d39ec Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Feb 2021 16:44:43 -0800 Subject: [PATCH 0471/1101] Detect and warn about uninitialized tile attributes on events. --- CHANGELOG.rst | 2 ++ tcod/event.py | 78 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 70d04c9a..884337c8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ Unreleased ------------------ Changed - Now bundles SDL 2.0.14 for MacOS. + - `tcod.event` can now detect and will warn about uninitialized tile + attributes on mouse events. Removed - Python 3.5 is no longer supported. diff --git a/tcod/event.py b/tcod/event.py index aaa35491..7485c751 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -77,10 +77,10 @@ def _describe_bitmask( return "|".join(result) -def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: +def _pixel_to_tile(x: float, y: float) -> Optional[Tuple[float, float]]: """Convert pixel coordinates to tile coordinates.""" if not lib.TCOD_ctx.engine: - return 0, 0 + return None xy = ffi.new("double[2]", (x, y)) lib.TCOD_sys_pixel_to_tile(xy, xy + 1) return xy[0], xy[1] @@ -88,6 +88,24 @@ def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: Point = NamedTuple("Point", [("x", int), ("y", int)]) + +def _verify_tile_coordinates(xy: Optional[Point]) -> Point: + """Check if an events tile coordinate is initialized and warn if not. + + Always returns a valid Point object for backwards compatibility. + """ + if xy is not None: + return xy + warnings.warn( + "This events tile coordinates are uninitialized!" + "\nYou MUST pass this event to `Context.convert_event` before you can" + " read its tile attributes.", + RuntimeWarning, + stacklevel=3, # Called within other functions, never directly. + ) + return Point(0, 0) + + # manually define names for SDL macros BUTTON_LEFT = 1 BUTTON_MIDDLE = 2 @@ -314,14 +332,22 @@ class MouseState(Event): def __init__( self, pixel: Tuple[int, int] = (0, 0), - tile: Tuple[int, int] = (0, 0), + tile: Optional[Tuple[int, int]] = (0, 0), state: int = 0, ): super().__init__() self.pixel = Point(*pixel) - self.tile = Point(*tile) + self.__tile = Point(*tile) if tile is not None else None self.state = state + @property + def tile(self) -> Point: + return _verify_tile_coordinates(self.__tile) + + @tile.setter + def tile(self, xy: Tuple[int, int]) -> None: + self.__tile = Point(*xy) + def __repr__(self) -> str: return ("tcod.event.%s(pixel=%r, tile=%r, state=%s)") % ( self.__class__.__name__, @@ -362,13 +388,23 @@ def __init__( self, pixel: Tuple[int, int] = (0, 0), pixel_motion: Tuple[int, int] = (0, 0), - tile: Tuple[int, int] = (0, 0), - tile_motion: Tuple[int, int] = (0, 0), + tile: Optional[Tuple[int, int]] = (0, 0), + tile_motion: Optional[Tuple[int, int]] = (0, 0), state: int = 0, ): super().__init__(pixel, tile, state) self.pixel_motion = Point(*pixel_motion) - self.tile_motion = Point(*tile_motion) + self.__tile_motion = ( + Point(*tile_motion) if tile_motion is not None else None + ) + + @property + def tile_motion(self) -> Point: + return _verify_tile_coordinates(self.__tile_motion) + + @tile_motion.setter + def tile_motion(self, xy: Tuple[int, int]) -> None: + self.__tile_motion = Point(*xy) @classmethod def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": @@ -377,12 +413,15 @@ def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": pixel = motion.x, motion.y pixel_motion = motion.xrel, motion.yrel subtile = _pixel_to_tile(*pixel) - tile = int(subtile[0]), int(subtile[1]) - prev_pixel = pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1] - prev_subtile = _pixel_to_tile(*prev_pixel) - prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) - tile_motion = tile[0] - prev_tile[0], tile[1] - prev_tile[1] - self = cls(pixel, pixel_motion, tile, tile_motion, motion.state) + if subtile is None: + self = cls(pixel, pixel_motion, None, None, motion.state) + else: + tile = int(subtile[0]), int(subtile[1]) + prev_pixel = pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1] + prev_subtile = _pixel_to_tile(*prev_pixel) or (0, 0) + prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) + tile_motion = tile[0] - prev_tile[0], tile[1] - prev_tile[1] + self = cls(pixel, pixel_motion, tile, tile_motion, motion.state) self.sdl_event = sdl_event return self @@ -430,7 +469,7 @@ class MouseButtonEvent(MouseState): def __init__( self, pixel: Tuple[int, int] = (0, 0), - tile: Tuple[int, int] = (0, 0), + tile: Optional[Tuple[int, int]] = (0, 0), button: int = 0, ): super().__init__(pixel, tile, button) @@ -448,7 +487,10 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: button = sdl_event.button pixel = button.x, button.y subtile = _pixel_to_tile(*pixel) - tile = int(subtile[0]), int(subtile[1]) + if subtile is None: + tile: Optional[Tuple[int, int]] = None + else: + tile = int(subtile[0]), int(subtile[1]) self = cls(pixel, tile, button.button) self.sdl_event = sdl_event return self @@ -993,8 +1035,10 @@ def get_mouse_state() -> MouseState: """ xy = ffi.new("int[2]") buttons = lib.SDL_GetMouseState(xy, xy + 1) - x, y = _pixel_to_tile(*xy) - return MouseState((xy[0], xy[1]), (int(x), int(y)), buttons) + tile = _pixel_to_tile(*xy) + if tile is None: + return MouseState((xy[0], xy[1]), None, buttons) + return MouseState((xy[0], xy[1]), (int(tile[0]), int(tile[1])), buttons) @ffi.def_extern() # type: ignore From 1cd2b9f8a79d9e0ebbd3a7a37f4d0e27d92247e3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 Feb 2021 18:22:35 -0800 Subject: [PATCH 0472/1101] Detect and warn about non-deterministic random seeds. --- CHANGELOG.rst | 4 ++++ tcod/random.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 884337c8..f496c072 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Deprecated + - The Random class will now warn if the seed it's given will not used + deterministically. It will no longer accept non-integer seeds in the future. + Changed - Now bundles SDL 2.0.14 for MacOS. - `tcod.event` can now detect and will warn about uninitialized tile diff --git a/tcod/random.py b/tcod/random.py index e516f222..144f9825 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -5,7 +5,9 @@ However, you will need to use these generators to get deterministic results from the :any:`Noise` and :any:`BSP` classes. """ +import os import random +import warnings from typing import Any, Hashable, Optional import tcod.constants @@ -31,6 +33,11 @@ class Random(object): Attributes: random_c (CData): A cffi pointer to a TCOD_random_t object. + .. warning:: + A non-integer seed is only deterministic if the environment variable + ``PYTHONHASHSEED`` is set. In the future this function will only + accept `int`'s as a seed. + .. versionchanged:: 9.1 Added `tcod.random.MULTIPLY_WITH_CARRY` constant. `algorithm` parameter now defaults to `tcod.random.MERSENNE_TWISTER`. @@ -44,12 +51,30 @@ def __init__( """Create a new instance using this algorithm and seed.""" if seed is None: seed = random.getrandbits(32) + elif not isinstance(seed, int): + warnings.warn( + "In the future this class will only accept integer seeds.", + DeprecationWarning, + stacklevel=2, + ) + if __debug__ and "PYTHONHASHSEED" not in os.environ: + warnings.warn( + "Python's hash algorithm is not configured to be" + " deterministic so this non-integer seed will not be" + " deterministic." + "\nYou should do one of the following to fix this error:" + "\n* Use an integer as a seed instead (recommended.)" + "\n* Set the PYTHONHASHSEED environment variable before" + " starting Python.", + RuntimeWarning, + stacklevel=2, + ) + seed = hash(seed) + self.random_c = ffi.gc( ffi.cast( "mersenne_data_t*", - lib.TCOD_random_new_from_seed( - algorithm, hash(seed) % (1 << 32) - ), + lib.TCOD_random_new_from_seed(algorithm, seed & 0xFFFFFFFF), ), lib.TCOD_random_delete, ) From 1d85c7cbd16e5f8a475e4d49eb1f393b023a67b4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 Feb 2021 04:19:35 -0800 Subject: [PATCH 0473/1101] Setup VSCode launchers for the Python samples. --- .vscode/launch.json | 19 +++++++++++++++++++ .vscode/tasks.json | 13 +++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..50009d17 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + // Run the Python samples. + // tcod will be built and installed in editalbe mode. + "name": "Python: Python samples", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/examples/samples_tcod.py", + "cwd": "${workspaceFolder}/examples", + "console": "integratedTerminal", + "preLaunchTask": "develop python-tcod", + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..08f9ed85 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + // Installs tcod in editable mode. + "label": "develop python-tcod", + "type": "shell", + "command": "python setup.py develop" + } + ] +} From 5b909a7c571f6494e41260ce869c8c1af384f3e6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 Feb 2021 04:27:58 -0800 Subject: [PATCH 0474/1101] VSCode: add current file launch option. --- .vscode/launch.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 50009d17..52963e4e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "preLaunchTask": "develop python-tcod", + }, { // Run the Python samples. // tcod will be built and installed in editalbe mode. From 8e934601e5c96275ffa25ab33809a47d3ebe8852 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 5 Mar 2021 19:18:05 -0800 Subject: [PATCH 0475/1101] Prepare 12.0.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f496c072..331092c9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.0.0 - 2021-03-05 +------------------- Deprecated - The Random class will now warn if the seed it's given will not used deterministically. It will no longer accept non-integer seeds in the future. From c7281c99a37447ae916df256aedba3095a063084 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Mar 2021 00:51:34 -0800 Subject: [PATCH 0476/1101] Make cave generator example more clear. This version works better if the input isn't a boolean array. --- examples/cavegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cavegen.py b/examples/cavegen.py index a6f08d80..43d2e807 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -20,7 +20,7 @@ def convolve(tiles: np.array, wall_rule: int = 5) -> np.array: """ # Use convolve2d, the 2nd input is a 3x3 ones array. neighbors = scipy.signal.convolve2d( - ~tiles, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same" + tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same" ) return neighbors < wall_rule # Apply the wall rule. From 055acbc23b741f57ad234d6d03241d49c81f40ae Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 23 Mar 2021 10:51:50 -0700 Subject: [PATCH 0477/1101] Update cx_Freeze example. --- examples/distribution/cx_Freeze/README.rst | 9 ++-- .../distribution/cx_Freeze/hello_world.py | 18 ------- examples/distribution/cx_Freeze/main.py | 25 +++++++++ .../distribution/cx_Freeze/requirements.txt | 2 + examples/distribution/cx_Freeze/setup.py | 51 +++++++++++++++---- 5 files changed, 74 insertions(+), 31 deletions(-) delete mode 100755 examples/distribution/cx_Freeze/hello_world.py create mode 100755 examples/distribution/cx_Freeze/main.py create mode 100644 examples/distribution/cx_Freeze/requirements.txt diff --git a/examples/distribution/cx_Freeze/README.rst b/examples/distribution/cx_Freeze/README.rst index 507d4f36..12d768c0 100644 --- a/examples/distribution/cx_Freeze/README.rst +++ b/examples/distribution/cx_Freeze/README.rst @@ -1,11 +1,14 @@ cx_Freeze Example ================= -First, install the packages: ``tdl`` and ``cx_Freeze``. +It's recommended to use a virtual environment to package Python executables. +Use the following guide on how to set one up: +https://docs.python.org/3/tutorial/venv.html -Then run the command:: +Once the virtual environment is active you should install `tcod` and `cx_Freeze` from the `requirements.txt` file, then build using `setup.py`: - python setup.py build_exe + pip install -r requirements.txt + python setup.py build An executable package will be placed in ``build//`` diff --git a/examples/distribution/cx_Freeze/hello_world.py b/examples/distribution/cx_Freeze/hello_world.py deleted file mode 100755 index 7f91bbef..00000000 --- a/examples/distribution/cx_Freeze/hello_world.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -import tdl - -WIDTH, HEIGHT = 80, 60 -console = None - -def main(): - global console - tdl.set_font('data/terminal8x8_gs_ro.png') - console = tdl.init(WIDTH, HEIGHT) - console.draw_str(0, 0, "Hello World") - tdl.flush() - tdl.event.key_wait() - - -if __name__ == '__main__': - main() diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py new file mode 100755 index 00000000..6cbec00f --- /dev/null +++ b/examples/distribution/cx_Freeze/main.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import tcod + +WIDTH, HEIGHT = 80, 60 +console = None + + +def main(): + tileset = tcod.tileset.load_tilesheet( + "data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437 + ) + with tcod.context.new( + columns=WIDTH, rows=HEIGHT, tileset=tileset + ) as context: + while True: + console = tcod.console.Console(WIDTH, HEIGHT) + console.print(0, 0, "Hello World") + context.present(console) + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + + +if __name__ == "__main__": + main() diff --git a/examples/distribution/cx_Freeze/requirements.txt b/examples/distribution/cx_Freeze/requirements.txt new file mode 100644 index 00000000..a5d45074 --- /dev/null +++ b/examples/distribution/cx_Freeze/requirements.txt @@ -0,0 +1,2 @@ +tcod==12.0.0 +cx-freeze==6.5.3 diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py index 4962268c..ce66eae0 100755 --- a/examples/distribution/cx_Freeze/setup.py +++ b/examples/distribution/cx_Freeze/setup.py @@ -1,14 +1,37 @@ - +#!/usr/bin/env python3 import sys from cx_Freeze import Executable, setup -# cx_Freeze options, see documentation. +# cx_Freeze options, see documentation: +# https://cx-freeze.readthedocs.io/en/latest/distutils.html#build-exe build_exe_options = { - 'packages': ['cffi'], - 'excludes': [], - 'include_files': ['data'], - } + "packages": [], + "excludes": [ + "numpy.core.tests", + "numpy.distutils", + "numpy.doc", + "numpy.f2py.tests", + "numpy.lib.tests", + "numpy.ma.tests", + "numpy.ma.testutils", + "numpy.matrixlib.tests", + "numpy.polynomial.tests", + "numpy.random.tests", + "numpy.testing", + "numpy.tests", + "numpy.typing.tests", + "distutils", + "setuptools", + "msilib", + "test", + "tkinter", + "unittest", + ], + "include_files": ["data"], # Bundle the data directory. + "optimize": 1, # Enable release mode. + "include_msvcr": False, +} # Hide the terminal on Windows apps. base = None @@ -16,7 +39,15 @@ base = "Win32GUI" setup( - name='tdl cxfreeze example', - options = {'build_exe': build_exe_options}, - executables = [Executable('hello_world.py', base=base)], - ) + name="tcod cx_Freeze example", + options={"build_exe": build_exe_options}, + executables=[ + # cx_Freeze Executable options: + # https://cx-freeze.readthedocs.io/en/latest/distutils.html#cx-freeze-executable + Executable( + script="main.py", + base=base, + target_name="start", # Name of the target executable. + ) + ], +) From 3542ae4d6b6f6bd6f3081cde136e8f6f20785fdd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 12:58:56 -0700 Subject: [PATCH 0478/1101] Add package-level PyInstaller hook. This mostly matches the uploaded hook. If this hook needs updating then the contributed hook must be removed. --- CHANGELOG.rst | 2 ++ setup.cfg | 4 ++++ setup.py | 2 +- tcod/__pyinstaller/__init__.py | 8 ++++++++ tcod/__pyinstaller/hook-tcod.py | 13 +++++++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tcod/__pyinstaller/__init__.py create mode 100644 tcod/__pyinstaller/hook-tcod.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 331092c9..50315db7 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added + - Added package-level PyInstaller hook. 12.0.0 - 2021-03-05 ------------------- diff --git a/setup.cfg b/setup.cfg index 4a1f21d2..0bccb760 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,7 @@ +[options.entry_points] +pyinstaller40 = + hook-dirs = tcod.__pyinstaller:get_hook_dirs + [aliases] test=pytest diff --git a/setup.py b/setup.py index 49bd7c2a..27994bb6 100755 --- a/setup.py +++ b/setup.py @@ -130,7 +130,7 @@ def check_sdl_version(): "Forum": "https://github.com/libtcod/python-tcod/discussions", }, py_modules=["libtcodpy"], - packages=["tcod"], + packages=["tcod", "tcod.__pyinstaller"], package_data={"tcod": get_package_data()}, python_requires=">=3.6", install_requires=[ diff --git a/tcod/__pyinstaller/__init__.py b/tcod/__pyinstaller/__init__.py new file mode 100644 index 00000000..a2a36e88 --- /dev/null +++ b/tcod/__pyinstaller/__init__.py @@ -0,0 +1,8 @@ +"""PyInstaller entry point for tcod.""" +import os +from typing import List + + +def get_hook_dirs() -> List[str]: + """Return the current directory.""" + return [os.path.dirname(__file__)] diff --git a/tcod/__pyinstaller/hook-tcod.py b/tcod/__pyinstaller/hook-tcod.py new file mode 100644 index 00000000..81c69fd7 --- /dev/null +++ b/tcod/__pyinstaller/hook-tcod.py @@ -0,0 +1,13 @@ +"""PyInstaller hook for tcod. + +Added here after tcod 12.0.0. + +If this hook is modified then the contributed hook needs to be removed from: +https://github.com/pyinstaller/pyinstaller-hooks-contrib +""" +from PyInstaller.utils.hooks import collect_dynamic_libs + +hiddenimports = ["_cffi_backend"] + +# Install shared libraries to the working directory. +binaries = collect_dynamic_libs("tcod") From ce0f7007ac533a30dc9fa758774f585195aa013a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 13:12:35 -0700 Subject: [PATCH 0479/1101] Clean up manifest file. Mostly removing the `.c*` wildcards since those are grabbing way too much. --- MANIFEST.in | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index eea46284..c3ba4522 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,17 +1,8 @@ -prune examples -prune fonts -prune libtcod -prune tests - include *.py *.cfg *.txt *.rst -recursive-include tdl *.py *.png *.txt *.md *.rst - -recursive-include tcod *.py *.c* *.h +recursive-include tcod *.py *.c *.h -recursive-include libtcod/src *.c* *.h* *.glsl* +recursive-include libtcod/src *.glsl* *.c *.h *.cpp *.hpp include libtcod/*.txt libtcod/*.md -recursive-include dependencies/fake_libc_include *.h - exclude tcod/*/SDL2.dll From 19ddcd3d73b65a348c5dfa11584a852fe8d897c6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 13:38:18 -0700 Subject: [PATCH 0480/1101] Remove references to C++ source files. --- MANIFEST.in | 2 +- build_libtcod.py | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index c3ba4522..9f7781d1 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,7 @@ include *.py *.cfg *.txt *.rst recursive-include tcod *.py *.c *.h -recursive-include libtcod/src *.glsl* *.c *.h *.cpp *.hpp +recursive-include libtcod/src *.glsl* *.c *.h include libtcod/*.txt libtcod/*.md exclude tcod/*/SDL2.dll diff --git a/build_libtcod.py b/build_libtcod.py index e2fbeb8c..c27fcfd5 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -138,21 +138,11 @@ def parse_includes() -> List[ParsedHeader]: return resolve_dependencies(includes) -def walk_sources(directory: str, cpp: bool) -> Iterator[str]: +def walk_sources(directory: str) -> Iterator[str]: for path, dirs, files in os.walk(directory): for source in files: if source.endswith(".c"): yield os.path.join(path, source) - elif cpp and source.endswith(".cpp"): - yield os.path.join(path, source) - - -def find_sources(directory: str) -> List[str]: - return [ - os.path.join(directory, source) - for source in os.listdir(directory) - if source.endswith(".c") - ] def get_sdl2_file(version: str) -> str: @@ -207,8 +197,8 @@ def unpack_sdl2(version: str) -> str: library_dirs = [] define_macros = [("Py_LIMITED_API", 0x03060000)] # type: List[Tuple[str, Any]] -sources += walk_sources("tcod/", cpp=True) -sources += walk_sources("libtcod/src/libtcod/", cpp=False) +sources += walk_sources("tcod/") +sources += walk_sources("libtcod/src/libtcod/") sources += ["libtcod/src/vendor/stb.c"] sources += ["libtcod/src/vendor/glad.c"] sources += ["libtcod/src/vendor/lodepng.c"] From 1677c5f848ed0c26ae1a214c177296f4dfce4505 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 14:02:30 -0700 Subject: [PATCH 0481/1101] Clone repository manually to fix git describe. Remove TCOD_TAG hack. --- .github/workflows/python-package-macos.yml | 11 ++++++----- .github/workflows/python-package.yml | 9 ++++++--- setup.py | 6 ------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index e5281331..0afe99d5 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -16,9 +16,12 @@ jobs: python-version: ['3.7.9'] steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Needed for git describe to find tags. + - name: Clone ${{github.repository}} + # actions/checkout breaks `git describe` so a manual clone is needed. + # https://github.com/actions/checkout/issues/272 + run: | + git clone https://github.com/${{github.repository}}.git ${{github.workspace}} + git checkout ${{github.sha}} - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -45,8 +48,6 @@ jobs: python -m pip install git+https://github.com/HexDecimal/delocate.git@loader_path if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Build package. - env: - TCOD_TAG: ${{ github.event.release.tag_name }} run: | python setup.py sdist develop bdist_wheel --py-limited-api=cp36 - name: Test with pytest diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 888ca4ef..90600e93 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,9 +15,12 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Needed for git describe to find tags. + - name: Clone ${{github.repository}} + # actions/checkout breaks `git describe` so a manual clone is needed. + # https://github.com/actions/checkout/issues/272 + run: | + git clone https://github.com/${{github.repository}}.git ${{github.workspace}} + git checkout ${{github.sha}} - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 diff --git a/setup.py b/setup.py index 27994bb6..4a4d946a 100755 --- a/setup.py +++ b/setup.py @@ -14,12 +14,6 @@ def get_version(): """Get the current version from a git tag, or by reading tcod/version.py""" - if os.environ.get("TCOD_TAG"): - # Force a tag version from an environment variable. - # Needed to work with GitHub Actions. - with open("tcod/version.py", "w") as f: - f.write('__version__ = "%s"\n' % os.environ["TCOD_TAG"]) - return os.environ["TCOD_TAG"] try: tag = check_output( ["git", "describe", "--abbrev=0"], universal_newlines=True From e07b38baad3d077b4fc72d8ed0f21587c5912568 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 14:16:29 -0700 Subject: [PATCH 0482/1101] Have Pytest ignore PyInstaller hook directory. Testing it would require PyInstaller which seems unnecessary. --- setup.cfg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0bccb760..b0078109 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,13 @@ pyinstaller40 = test=pytest [tool:pytest] -addopts=tcod/ tests/ --doctest-modules --cov=tcod --capture=sys +addopts= + tcod/ + tests/ + --doctest-modules + --cov=tcod + --capture=sys + --ignore=tcod/__pyinstaller filterwarnings = ignore::DeprecationWarning:tcod.libtcodpy ignore::PendingDeprecationWarning:tcod.libtcodpy From 58c914d965cc392cb95cfc7ad2f3c350f424146d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 Mar 2021 14:29:48 -0700 Subject: [PATCH 0483/1101] Ignore PyInstaller types. --- tcod/__pyinstaller/hook-tcod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/__pyinstaller/hook-tcod.py b/tcod/__pyinstaller/hook-tcod.py index 81c69fd7..2df121e6 100644 --- a/tcod/__pyinstaller/hook-tcod.py +++ b/tcod/__pyinstaller/hook-tcod.py @@ -5,7 +5,7 @@ If this hook is modified then the contributed hook needs to be removed from: https://github.com/pyinstaller/pyinstaller-hooks-contrib """ -from PyInstaller.utils.hooks import collect_dynamic_libs +from PyInstaller.utils.hooks import collect_dynamic_libs # type: ignore hiddenimports = ["_cffi_backend"] From e356c2e110ccf38205afb93d0f50f0ac30ddb222 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 Mar 2021 21:37:21 -0700 Subject: [PATCH 0484/1101] Add TTF example script. This will need continued updates. It's currently unstable. --- examples/VeraMono.ttf | Bin 0 -> 49224 bytes examples/ttf.py | 82 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 examples/VeraMono.ttf create mode 100644 examples/ttf.py diff --git a/examples/VeraMono.ttf b/examples/VeraMono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..139f0b4311ad2e0369a347b3be6c46e6c2b730d5 GIT binary patch literal 49224 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 literal 0 HcmV?d00001 diff --git a/examples/ttf.py b/examples/ttf.py new file mode 100644 index 00000000..fb0528e6 --- /dev/null +++ b/examples/ttf.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""A TrueType Font example using the FreeType library. + +You will need to get this external library from PyPI: + + pip install freetype-py + +This script has known issues and may crash when the window is resized. +""" +from typing import Tuple + +import freetype # type: ignore # pip install freetype-py +import numpy as np +import tcod + +FONT = "VeraMono.ttf" + + +def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: + """Load a TTF file as a tcod tileset.""" + ttf = freetype.Face(path) + ttf.set_pixel_sizes(*size) + half_advance = size[0] - (ttf.bbox.xMax - ttf.bbox.xMin) // 64 + + tileset = tcod.tileset.Tileset(*size) + for codepoint, glyph_index in ttf.get_chars(): + ttf.load_glyph(glyph_index) + bitmap = ttf.glyph.bitmap + assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY + bitmap_array = np.asarray(bitmap.buffer).reshape( + (bitmap.width, bitmap.rows), order="F" + ) + if bitmap_array.size == 0: + continue + output_image = np.zeros(size, dtype=np.uint8, order="F") + out_slice = output_image + left = ttf.glyph.bitmap_left + half_advance + top = size[1] - ttf.glyph.bitmap_top + ttf.bbox.yMin // 64 + out_slice = out_slice[max(0, left) :, max(0, top) :] + out_slice[ + : bitmap_array.shape[0], : bitmap_array.shape[1] + ] = bitmap_array[: out_slice.shape[0], : out_slice.shape[1]] + tileset.set_tile(codepoint, output_image.transpose()) + return tileset + + +def main() -> None: + console = tcod.Console(16, 12, order="F") + with tcod.context.new( + columns=console.width, + rows=console.height, + tileset=load_ttf(FONT, (24, 24)), + ) as context: + while True: + console.clear() + console.tiles_rgb["bg"][::2, ::2] = 0x20 + console.tiles_rgb["bg"][1::2, 1::2] = 0x20 + console.tiles_rgb["ch"][:16, :6] = np.arange(0x20, 0x80).reshape( + 0x10, -1, order="F" + ) + console.print(0, 7, "Example text.") + context.present(console, integer_scaling=True) + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + if ( + isinstance(event, tcod.event.WindowResized) + and event.type == "WINDOWSIZECHANGED" + ): + context.change_tileset( + load_ttf( + path=FONT, + size=( + event.width // console.width, + event.height // console.height, + ), + ) + ) + + +if __name__ == "__main__": + main() From 0dee1608ad78ddf1d3f615f5e2d9f9cb85b98d58 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 Mar 2021 12:48:16 -0700 Subject: [PATCH 0485/1101] Update libtcod. --- CHANGELOG.rst | 8 ++++++++ build_libtcod.py | 2 +- libtcod | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 50315db7..dc705172 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,14 @@ Unreleased Added - Added package-level PyInstaller hook. +Changed + - Using `libtcod 1.16.7`. + +Fixed + - Fixed crashes from loading tilesets with non-square tile sizes. + - Tilesets with a size of 0 should no longer crash when used. + - Prevent division by zero from recommended-console-size functions. + 12.0.0 - 2021-03-05 ------------------- Deprecated diff --git a/build_libtcod.py b/build_libtcod.py index c27fcfd5..eecbccd4 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -186,7 +186,7 @@ def unpack_sdl2(version: str) -> str: includes = parse_includes() module_name = "tcod._libtcod" -include_dirs = [".", "libtcod/src/vendor/", "libtcod/src/vendor/zlib/"] +include_dirs = [".", "libtcod/src/vendor/", "libtcod/src/vendor/utf8proc", "libtcod/src/vendor/zlib/"] extra_parse_args = [] extra_compile_args = [] diff --git a/libtcod b/libtcod index b20ca439..b0f84018 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit b20ca439155f72eb6ad5a063ab988f41d08298fb +Subproject commit b0f8401863882516f9bf0657a656343d258a97b6 From 12d0e114b3fbfacea1782c8361050c0169d59033 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 Mar 2021 14:14:03 -0700 Subject: [PATCH 0486/1101] Update TTF example. --- examples/ttf.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/examples/ttf.py b/examples/ttf.py index fb0528e6..b73c311f 100644 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -4,9 +4,10 @@ You will need to get this external library from PyPI: pip install freetype-py - -This script has known issues and may crash when the window is resized. """ +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights to this example script. +# https://creativecommons.org/publicdomain/zero/1.0/ from typing import Tuple import freetype # type: ignore # pip install freetype-py @@ -17,13 +18,21 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: - """Load a TTF file as a tcod tileset.""" + """Load a TTF file and return a tcod Tileset. + + `path` is the file path to the font, this can be any font supported by the + FreeType library. + + `size` is the (width, height) of the Tileset in pixels. + + Feel free to use this function in your own code. + """ ttf = freetype.Face(path) ttf.set_pixel_sizes(*size) - half_advance = size[0] - (ttf.bbox.xMax - ttf.bbox.xMin) // 64 tileset = tcod.tileset.Tileset(*size) for codepoint, glyph_index in ttf.get_chars(): + # Add every glyph to the Tileset. ttf.load_glyph(glyph_index) bitmap = ttf.glyph.bitmap assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY @@ -31,15 +40,20 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: (bitmap.width, bitmap.rows), order="F" ) if bitmap_array.size == 0: - continue + continue # Skip blank glyphs. output_image = np.zeros(size, dtype=np.uint8, order="F") out_slice = output_image - left = ttf.glyph.bitmap_left + half_advance - top = size[1] - ttf.glyph.bitmap_top + ttf.bbox.yMin // 64 + + # Adjust the position to center this glyph on the tile. + left = (size[0] - bitmap.width) // 2 + top = size[1] - ttf.glyph.bitmap_top + ttf.size.descender // 64 + + # `max` is used because I was too lazy to properly slice the array. out_slice = out_slice[max(0, left) :, max(0, top) :] out_slice[ : bitmap_array.shape[0], : bitmap_array.shape[1] ] = bitmap_array[: out_slice.shape[0], : out_slice.shape[1]] + tileset.set_tile(codepoint, output_image.transpose()) return tileset @@ -53,8 +67,10 @@ def main() -> None: ) as context: while True: console.clear() + # Draw checkerboard. console.tiles_rgb["bg"][::2, ::2] = 0x20 console.tiles_rgb["bg"][1::2, 1::2] = 0x20 + # Print ASCII characters. console.tiles_rgb["ch"][:16, :6] = np.arange(0x20, 0x80).reshape( 0x10, -1, order="F" ) @@ -67,6 +83,7 @@ def main() -> None: isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWSIZECHANGED" ): + # Resize the Tileset to match the new screen size. context.change_tileset( load_ttf( path=FONT, From 4a7952865312c32389787663095db2a1d30ea6d3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 Mar 2021 19:28:30 -0700 Subject: [PATCH 0487/1101] Update tcod.path.dijkstra2d function. Change the way it handles to be more like other NumPy functions and be more intuitive in general. --- CHANGELOG.rst | 4 ++++ tcod/path.py | 66 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc705172..8b54d8d6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,10 @@ Added Changed - Using `libtcod 1.16.7`. + - `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter. + +Deprecated + - In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given. Fixed - Fixed crashes from loading tilesets with non-square tile sizes. diff --git a/tcod/path.py b/tcod/path.py index 6bb10887..dfcf3804 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -17,6 +17,7 @@ """ import functools import itertools +import warnings from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np @@ -26,6 +27,11 @@ from tcod._internal import _check from tcod.loader import ffi, lib +try: + from numpy.typing import ArrayLike +except ImportError: # Python < 3.7, Numpy < 1.20 + from typing import Any as ArrayLike + @ffi.def_extern() # type: ignore def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: @@ -137,7 +143,7 @@ class NodeCostArray(np.ndarray): np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), } - def __new__(cls, array: np.ndarray) -> "NodeCostArray": + def __new__(cls, array: ArrayLike) -> "NodeCostArray": """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) return self @@ -370,20 +376,20 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: def dijkstra2d( - distance: np.ndarray, - cost: np.ndarray, + distance: ArrayLike, + cost: ArrayLike, cardinal: Optional[int] = None, diagonal: Optional[int] = None, *, edge_map: Any = None, -) -> None: + out: Optional[np.ndarray] = ..., # type: ignore +) -> np.ndarray: """Return the computed distance of all nodes on a 2D Dijkstra grid. - `distance` is an input/output array of node distances. Is this often an + `distance` is an input array of node distances. Is this often an array filled with maximum finite values and 1 or more points with a low value such as 0. Distance will flow from these low values to adjacent - nodes based the cost to reach those nodes. This array is modified - in-place. + nodes based the cost to reach those nodes. `cost` is an array of node costs. Any node with a cost less than or equal to 0 is considered blocked off. Positive values are the distance needed to @@ -398,6 +404,11 @@ def dijkstra2d( another. This parameter can be hard to understand so you should see how it's used in the examples. + `out` is the array to fill with the computed Dijkstra distance map. + Having `out` be the same as `distance` will modify the array in-place, + which is normally the fastest option. + If `out` is `None` then the result is returned as a new array. + Example:: >>> import numpy as np @@ -414,8 +425,7 @@ def dijkstra2d( array([[ 0, 2147483647, 2147483647], [2147483647, 2147483647, 2147483647], [2147483647, 2147483647, 2147483647]]...) - >>> tcod.path.dijkstra2d(dist, cost, 2, 3) - >>> dist + >>> tcod.path.dijkstra2d(dist, cost, 2, 3, out=dist) array([[ 0, 2147483647, 10], [ 2, 2147483647, 8], [ 4, 5, 7]]...) @@ -450,8 +460,7 @@ def dijkstra2d( >>> dist = tcod.path.maxarray((8, 8)) >>> dist[0,0] = 0 >>> cost = np.ones((8, 8), int) - >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves) - >>> dist + >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves, out=dist) array([[0, 3, 2, 3, 2, 3, 4, 5], [3, 4, 1, 2, 3, 4, 3, 4], [2, 1, 4, 3, 2, 3, 4, 5], @@ -485,15 +494,39 @@ def dijkstra2d( .. versionchanged:: 11.13 Added the `edge_map` parameter. + + .. versionchanged:: 12.1 + Added `out` parameter. Now returns the output array. """ - dist = distance + dist = np.asarray(distance) + if out is ...: # type: ignore + out = dist + warnings.warn( + "No `out` parameter was given. " + "Currently this modifies the distance array in-place, but this " + "will change in the future to return a copy instead. " + "To ensure the existing behavior is kept you must add an `out` " + "parameter with the same array as the `distance` parameter.", + DeprecationWarning, + stacklevel=2, + ) + elif out is None: + out = np.copy(distance) + else: + out[...] = dist + + if dist.shape != out.shape: + raise TypeError( + "distance and output must have the same shape %r != %r" + % (dist.shape, out.shape) + ) cost = np.asarray(cost) if dist.shape != cost.shape: raise TypeError( - "distance and cost must have the same shape %r != %r" - % (dist.shape, cost.shape) + "output and cost must have the same shape %r != %r" + % (out.shape, cost.shape) ) - c_dist = _export(dist) + c_dist = _export(out) if edge_map is not None: if cardinal is not None or diagonal is not None: raise TypeError( @@ -508,6 +541,7 @@ def dijkstra2d( if diagonal is None: diagonal = 0 _check(lib.dijkstra2d_basic(c_dist, _export(cost), cardinal, diagonal)) + return out def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: @@ -521,7 +555,7 @@ def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: def hillclimb2d( - distance: np.ndarray, + distance: ArrayLike, start: Tuple[int, int], cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, From 07258cbcf41b36e8cdf765b7ef43b779866aa74c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Apr 2021 12:05:01 -0700 Subject: [PATCH 0488/1101] Prepare 12.1.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b54d8d6..8f0e887d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.1.0 - 2021-04-01 +------------------- Added - Added package-level PyInstaller hook. From 023cd70b6a3c6f39afcc6b45227578785b6098b3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Apr 2021 12:14:36 -0700 Subject: [PATCH 0489/1101] Fix release description script. --- scripts/get_release_description.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index aa7acf64..6c3b0f02 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -2,7 +2,7 @@ """Print the description used for GitHub Releases.""" import re -TAG_BANNER = r"\d+\.\d+\.\d+\S* - \d+-\d+-\d+\n--------------------\n" +TAG_BANNER = r"\d+\.\d+\.\d+\S* - \d+-\d+-\d+\n-+\n" RE_BODY = re.compile(fr".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) RE_SECTION = re.compile(r"^(\w+)$", re.MULTILINE) From 6b4d88401013fda8d5b2538992765afd3c92e2d1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 2 Apr 2021 10:32:41 -0700 Subject: [PATCH 0490/1101] Prevent older versions of tcod from running TTF example. Older versions will crash the script. --- examples/ttf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/ttf.py b/examples/ttf.py index b73c311f..638655b0 100644 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -96,4 +96,8 @@ def main() -> None: if __name__ == "__main__": + tcod_version = tuple( + int(n) for n in tcod.__version__.split(".") if n.isdigit() + ) + assert tcod_version[:2] >= (12, 1), "Must be using tcod 12.1 or later." main() From a62270d3dd2ab58e57d503c2893dfd7bc0c01ed5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 5 Apr 2021 11:17:23 -0700 Subject: [PATCH 0491/1101] Corrected FBM check in Noise class. --- CHANGELOG.rst | 2 ++ tcod/noise.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f0e887d..06355eec 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Indexing Noise classes now works with the FBM implementation. 12.1.0 - 2021-04-01 ------------------- diff --git a/tcod/noise.py b/tcod/noise.py index d0db3626..cfaffd6d 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -189,7 +189,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: *c_input, ffi.from_buffer("float*", out), ) - elif self.implementation == SIMPLE: + elif self.implementation == FBM: lib.TCOD_noise_get_fbm_vectorized( self.noise_c, self.algorithm, From be69c34df3e4980b9b437669e7b369efc62a6aa5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 5 Apr 2021 23:12:16 -0700 Subject: [PATCH 0492/1101] Refactor noise module. Add enums for noise types. Refactor noise related tests. --- CHANGELOG.rst | 6 +++ docs/tcod/noise.rst | 1 + tcod/noise.py | 128 ++++++++++++++++++++++++++++++++++++-------- tests/test_noise.py | 103 ++++++++++++++++++++++++++++++++++- tests/test_tcod.py | 56 ------------------- 5 files changed, 215 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06355eec..ab0f0a51 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ +Added + - Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. + +Deprecated + - The non-enum noise implementation names have been deprecated. + Fixed - Indexing Noise classes now works with the FBM implementation. diff --git a/docs/tcod/noise.rst b/docs/tcod/noise.rst index 36f4ddb6..03a18984 100644 --- a/docs/tcod/noise.rst +++ b/docs/tcod/noise.rst @@ -3,3 +3,4 @@ tcod.noise - Noise Map Generators .. automodule:: tcod.noise :members: + :member-order: bysource diff --git a/tcod/noise.py b/tcod/noise.py index cfaffd6d..2f521e29 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -10,8 +10,8 @@ noise = tcod.noise.Noise( dimensions=2, - algorithm=tcod.NOISE_SIMPLEX, - implementation=tcod.noise.TURBULENCE, + algorithm=tcod.noise.Algorithm.SIMPLEX, + implementation=tcod.noise.Implementation.TURBULENCE, hurst=0.5, lacunarity=2.0, octaves=4, @@ -31,7 +31,9 @@ samples = noise.sample_ogrid(ogrid) print(samples) """ -from typing import Any, Optional +import enum +import warnings +from typing import Any, Optional, Sequence, Union import numpy as np @@ -40,10 +42,63 @@ from tcod._internal import deprecate from tcod.loader import ffi, lib -"""Noise implementation constants""" -SIMPLE = 0 -FBM = 1 -TURBULENCE = 2 +try: + from numpy.typing import ArrayLike +except ImportError: # Python < 3.7, Numpy < 1.20 + from typing import Any as ArrayLike + + +class Algorithm(enum.IntEnum): + """Libtcod noise algorithms. + + .. versionadded:: 12.2 + """ + + PERLIN = 1 + """Perlin noise.""" + + SIMPLEX = 2 + """Simplex noise.""" + + WAVELET = 4 + """Wavelet noise.""" + + def __repr__(self) -> str: + return f"tcod.noise.Algorithm.{self.name}" + + +class Implementation(enum.IntEnum): + """Noise implementations. + + .. versionadded:: 12.2 + """ + + SIMPLE = 0 + """Generate plain noise.""" + + FBM = 1 + """Fractional Brownian motion. + + https://en.wikipedia.org/wiki/Fractional_Brownian_motion + """ + + TURBULENCE = 2 + """Turbulence noise implementation.""" + + def __repr__(self) -> str: + return f"tcod.noise.Implementation.{self.name}" + + +def __getattr__(name: str) -> Implementation: + if hasattr(Implementation, name): + warnings.warn( + f"'tcod.noise.{name}' is deprecated," + f" use 'tcod.noise.Implementation.{name}' instead.", + DeprecationWarning, + stacklevel=2, + ) + return Implementation[name] + raise AttributeError(f"module {__name__} has no attribute {name}") class Noise(object): @@ -59,8 +114,9 @@ class Noise(object): Args: dimensions (int): Must be from 1 to 4. - algorithm (int): Defaults to NOISE_SIMPLEX - implementation (int): Defaults to tcod.noise.SIMPLE + algorithm (int): Defaults to :any:`tcod.noise.Algorithm.SIMPLEX` + implementation (int): + Defaults to :any:`tcod.noise.Implementation.SIMPLE` hurst (float): The hurst exponent. Should be in the 0.0-1.0 range. lacunarity (float): The noise lacunarity. octaves (float): The level of detail on fBm and turbulence @@ -74,21 +130,21 @@ class Noise(object): def __init__( self, dimensions: int, - algorithm: int = 2, - implementation: int = SIMPLE, + algorithm: int = Algorithm.SIMPLEX, + implementation: int = Implementation.SIMPLE, hurst: float = 0.5, lacunarity: float = 2.0, octaves: float = 4, - seed: Optional[tcod.random.Random] = None, + seed: Optional[Union[int, tcod.random.Random]] = None, ): if not 0 < dimensions <= 4: raise ValueError( "dimensions must be in range 0 < n <= 4, got %r" % (dimensions,) ) - self._random = seed - _random_c = seed.random_c if seed else ffi.NULL - self._algorithm = algorithm + self._seed = seed + self._random = self.__rng_from_seed(seed) + _random_c = self._random.random_c self.noise_c = ffi.gc( ffi.cast( "struct TCOD_Noise*", @@ -99,8 +155,35 @@ def __init__( self._tdl_noise_c = ffi.new( "TDLNoise*", (self.noise_c, dimensions, 0, octaves) ) + self.algorithm = algorithm self.implementation = implementation # sanity check + @staticmethod + def __rng_from_seed( + seed: Union[None, int, tcod.random.Random] + ) -> tcod.random.Random: + if seed is None or isinstance(seed, int): + return tcod.random.Random( + seed=seed, algorithm=tcod.random.MERSENNE_TWISTER + ) + return seed + + def __repr__(self) -> str: + parameters = [ + f"dimensions={self.dimensions}", + f"algorithm={self.algorithm!r}", + f"implementation={Implementation(self.implementation)!r}", + ] + if self.hurst != 0.5: + parameters.append(f"hurst={self.hurst}") + if self.lacunarity != 2: + parameters.append(f"lacunarity={self.lacunarity}") + if self.octaves != 4: + parameters.append(f"octaves={self.octaves}") + if self._seed is not None: + parameters.append(f"seed={self._seed}") + return f"tcod.noise.Noise({', '.join(parameters)})" + @property def dimensions(self) -> int: return int(self._tdl_noise_c.dimensions) @@ -112,7 +195,8 @@ def dimentions(self) -> int: @property def algorithm(self) -> int: - return int(self.noise_c.noise_type) + noise_type = self.noise_c.noise_type + return Algorithm(noise_type) if noise_type else Algorithm.SIMPLEX @algorithm.setter def algorithm(self, value: int) -> None: @@ -120,7 +204,7 @@ def algorithm(self, value: int) -> None: @property def implementation(self) -> int: - return int(self._tdl_noise_c.implementation) + return Implementation(self._tdl_noise_c.implementation) @implementation.setter def implementation(self, value: int) -> None: @@ -181,7 +265,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: c_input[i] = ffi.from_buffer("float*", indexes[i]) out = np.empty(indexes[0].shape, dtype=np.float32) - if self.implementation == SIMPLE: + if self.implementation == Implementation.SIMPLE: lib.TCOD_noise_get_vectorized( self.noise_c, self.algorithm, @@ -189,7 +273,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: *c_input, ffi.from_buffer("float*", out), ) - elif self.implementation == FBM: + elif self.implementation == Implementation.FBM: lib.TCOD_noise_get_fbm_vectorized( self.noise_c, self.algorithm, @@ -198,7 +282,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: *c_input, ffi.from_buffer("float*", out), ) - elif self.implementation == TURBULENCE: + elif self.implementation == Implementation.TURBULENCE: lib.TCOD_noise_get_turbulence_vectorized( self.noise_c, self.algorithm, @@ -212,7 +296,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: return out - def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray: + def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -248,7 +332,7 @@ def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray: ) return out - def sample_ogrid(self, ogrid: np.ndarray) -> np.ndarray: + def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> np.ndarray: """Sample an open mesh-grid array and return the result. Args diff --git a/tests/test_noise.py b/tests/test_noise.py index 4265cc3e..ce1c22c7 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -1 +1,102 @@ -#!/usr/bin/env python +import copy +import pickle + +import numpy as np +import pytest + +import tcod + + +@pytest.mark.parametrize("implementation", tcod.noise.Implementation) +@pytest.mark.parametrize("algorithm", tcod.noise.Algorithm) +@pytest.mark.parametrize("hurst", [0.5, 0.75]) +@pytest.mark.parametrize("lacunarity", [2, 3]) +@pytest.mark.parametrize("octaves", [4, 6]) +def test_noise_class( + implementation: tcod.noise.Implementation, + algorithm: tcod.noise.Algorithm, + hurst: float, + lacunarity: float, + octaves: float, +) -> None: + noise = tcod.noise.Noise( + 2, + algorithm=algorithm, + implementation=implementation, + hurst=hurst, + lacunarity=lacunarity, + octaves=octaves, + ) + # cover attributes + assert noise.dimensions == 2 + noise.algorithm = noise.algorithm + noise.implementation = noise.implementation + noise.octaves = noise.octaves + assert noise.hurst + assert noise.lacunarity + + assert noise.get_point(0, 0) == noise[0, 0] + assert noise[0] == noise[0, 0] + noise.sample_mgrid(np.mgrid[:2, :3]) + noise.sample_ogrid(np.ogrid[:2, :3]) + + np.testing.assert_equal( + noise.sample_mgrid(np.mgrid[:2, :3]), + noise.sample_ogrid(np.ogrid[:2, :3]), + ) + np.testing.assert_equal( + noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])] + ) + repr(noise) + + +def test_noise_samples() -> None: + noise = tcod.noise.Noise( + 2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE + ) + np.testing.assert_equal( + noise.sample_mgrid(np.mgrid[:32, :24]), + noise.sample_ogrid(np.ogrid[:32, :24]), + ) + + +def test_noise_errors() -> None: + with pytest.raises(ValueError): + tcod.noise.Noise(0) + with pytest.raises(ValueError): + tcod.noise.Noise(1, implementation=-1) + noise = tcod.noise.Noise(2) + with pytest.raises(ValueError): + noise.sample_mgrid(np.mgrid[:2, :2, :2]) + with pytest.raises(ValueError): + noise.sample_ogrid(np.ogrid[:2, :2, :2]) + with pytest.raises(IndexError): + noise[0, 0, 0, 0, 0] + with pytest.raises(TypeError): + noise[object] + + +@pytest.mark.parametrize("implementation", tcod.noise.Implementation) +def test_noise_pickle(implementation: tcod.noise.Implementation) -> None: + rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) + noise = tcod.noise.Noise(2, implementation, seed=rand) + noise2 = copy.copy(noise) + np.testing.assert_equal( + noise.sample_ogrid(np.ogrid[:3, :1]), + noise2.sample_ogrid(np.ogrid[:3, :1]), + ) + + +def test_noise_copy() -> None: + rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) + noise = tcod.noise.Noise(2, seed=rand) + noise2 = pickle.loads(pickle.dumps(noise)) + np.testing.assert_equal( + noise.sample_ogrid(np.ogrid[:3, :1]), + noise2.sample_ogrid(np.ogrid[:3, :1]), + ) + + noise3 = tcod.noise.Noise(2, seed=None) + assert repr(noise3) == repr(pickle.loads(pickle.dumps(noise3))) + noise4 = tcod.noise.Noise(2, seed=42) + assert repr(noise4) == repr(pickle.loads(pickle.dumps(noise4))) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 27fd1f8f..66db56a2 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -101,62 +101,6 @@ def test_tcod_map_pickle_fortran(): assert map_.fov.strides == map2.fov.strides -@pytest.mark.parametrize('implementation', [tcod.noise.SIMPLE, - tcod.noise.FBM, - tcod.noise.TURBULENCE]) -def test_noise_class(implementation): - noise = tcod.noise.Noise(2, tcod.NOISE_SIMPLEX, implementation) - # cover attributes - assert noise.dimensions == 2 - noise.algorithm = noise.algorithm - noise.implementation = noise.implementation - noise.octaves = noise.octaves - assert noise.hurst - assert noise.lacunarity - - noise.get_point(0, 0) - noise.sample_mgrid(np.mgrid[:2,:3]) - noise.sample_ogrid(np.ogrid[:2,:3]) - - -def test_noise_samples(): - noise = tcod.noise.Noise(2, tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE) - np.testing.assert_equal( - noise.sample_mgrid(np.mgrid[:32,:24]), - noise.sample_ogrid(np.ogrid[:32,:24]), - ) - - -def test_noise_errors(): - with pytest.raises(ValueError): - tcod.noise.Noise(0) - with pytest.raises(ValueError): - tcod.noise.Noise(1, implementation=-1) - noise = tcod.noise.Noise(2) - with pytest.raises(ValueError): - noise.sample_mgrid(np.mgrid[:2,:2,:2]) - with pytest.raises(ValueError): - noise.sample_ogrid(np.ogrid[:2,:2,:2]) - - -@pytest.mark.parametrize('implementation', - [tcod.noise.SIMPLE, tcod.noise.FBM, tcod.noise.TURBULENCE]) -def test_noise_pickle(implementation): - rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) - noise = tcod.noise.Noise(2, implementation, seed=rand) - noise2 = copy.copy(noise) - assert (noise.sample_ogrid(np.ogrid[:3,:1]) == - noise2.sample_ogrid(np.ogrid[:3,:1])).all() - - -def test_noise_copy(): - rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) - noise = tcod.noise.Noise(2, seed=rand) - noise2 = pickle.loads(pickle.dumps(noise)) - assert (noise.sample_ogrid(np.ogrid[:3,:1]) == - noise2.sample_ogrid(np.ogrid[:3,:1])).all() - - @pytest.mark.filterwarnings("ignore") def test_color_class(): assert tcod.black == tcod.black From 174c6848f4bbd55250963644e05ba34b4deee420 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 8 Apr 2021 20:34:21 -0700 Subject: [PATCH 0493/1101] Add tcod.noise.grid function. Update noise docs to use helper function. --- CHANGELOG.rst | 1 + tcod/noise.py | 105 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab0f0a51..9f5aa7a9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Unreleased ------------------ Added - Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. + - Added `tcod.noise.grid` helper function. Deprecated - The non-enum noise implementation names have been deprecated. diff --git a/tcod/noise.py b/tcod/noise.py index 2f521e29..152dd2f7 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -5,35 +5,34 @@ Example:: - import numpy as np - import tcod - - noise = tcod.noise.Noise( - dimensions=2, - algorithm=tcod.noise.Algorithm.SIMPLEX, - implementation=tcod.noise.Implementation.TURBULENCE, - hurst=0.5, - lacunarity=2.0, - octaves=4, - seed=None, - ) - - # Create a 5x5 open multi-dimensional mesh-grid. - ogrid = [np.arange(5, dtype=np.float32), - np.arange(5, dtype=np.float32)] - print(ogrid) - - # Scale the grid. - ogrid[0] *= 0.25 - ogrid[1] *= 0.25 - - # Return the sampled noise from this grid of points. - samples = noise.sample_ogrid(ogrid) - print(samples) -""" + >>> import numpy as np + >>> import tcod + >>> noise = tcod.noise.Noise( + ... dimensions=2, + ... algorithm=tcod.noise.Algorithm.SIMPLEX, + ... seed=42, + ... ) + >>> samples = noise[tcod.noise.grid(shape=(5, 5), scale=0.25, origin=(0, 0))] + >>> samples # Samples are a grid of floats between -1.0 and 1.0 + array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785], + [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625], + [-0.40398532, -0.57662135, -0.33160293, 0.12860827, 0.2864191 ], + [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ], + [-0.64945626, -0.12529983, 0.5346834 , 0.80402255, 0.52655405]], + dtype=float32) + >>> (samples + 1.0) * 0.5 # You can normalize samples to 0.0 - 1.0 + array([[0.5 , 0.22476822, 0.11963567, 0.14556766, 0.15917107], + [0.36238313, 0.1397433 , 0.12971482, 0.28040403, 0.35402188], + [0.29800734, 0.21168932, 0.33419853, 0.5643041 , 0.6432096 ], + [0.24613297, 0.3678193 , 0.6222316 , 0.8195127 , 0.79614234], + [0.17527187, 0.4373501 , 0.76734173, 0.9020113 , 0.76327705]], + dtype=float32) + + +""" # noqa: E501 import enum import warnings -from typing import Any, Optional, Sequence, Union +from typing import Any, Optional, Sequence, Tuple, Union import numpy as np @@ -432,3 +431,55 @@ def _setstate_old(self, state: Any) -> None: self._tdl_noise_c = ffi.new( "TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2]) ) + + +def grid( + shape: Tuple[int, ...], + scale: Union[Tuple[float, ...], float], + origin: Optional[Tuple[int, ...]] = None, +) -> Tuple[np.ndarray, ...]: + """A helper function for generating a grid of noise samples. + + `shape` is the shape of the returned mesh grid. This can be any number of + dimensions, but :class:`Noise` classes only support up to 4. + + `scale` is the step size of indexes away from `origin`. + This can be a single float, or it can be a tuple of floats with one float + for each axis in `shape`. A lower scale gives smoother transitions + between noise values. + + `origin` is the first sample of the grid. + If `None` then the `origin` will be zero on each axis. + `origin` is not scaled by the `scale` parameter. + + Example:: + + >>> noise = tcod.noise.Noise(dimensions=2, seed=42) + >>> noise[tcod.noise.grid(shape=(5, 5), scale=0.25)] + array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785], + [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625], + [-0.40398532, -0.57662135, -0.33160293, 0.12860827, 0.2864191 ], + [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ], + [-0.64945626, -0.12529983, 0.5346834 , 0.80402255, 0.52655405]], + dtype=float32) + >>> noise[tcod.noise.grid(shape=(5, 5), scale=(0.5, 0.25), origin=(1, 1))] + array([[ 0.52655405, -0.5037453 , -0.81221616, -0.7057655 , 0.24630858], + [ 0.25038874, -0.75348294, -0.6379566 , -0.5817767 , -0.02789652], + [-0.03488023, -0.73630923, -0.12449139, -0.22774395, -0.22243626], + [-0.18455243, -0.35063767, 0.4495706 , 0.02399864, -0.42226675], + [-0.16333057, 0.18149695, 0.7547447 , -0.07006818, -0.6546707 ]], + dtype=float32) + """ # noqa: E501 + if isinstance(scale, float): + scale = (scale,) * len(shape) + if origin is None: + origin = (0,) * len(shape) + if len(shape) != len(scale): + raise TypeError("shape must have the same length as scale") + if len(shape) != len(origin): + raise TypeError("shape must have the same length as origin") + indexes = ( + np.arange(i_shape) * i_scale + i_origin + for i_shape, i_scale, i_origin in zip(shape, scale, origin) + ) + return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing="xy")) From 7f66cf7cc8f3fdbcc9cdfa10893aed749a8f23e6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 8 Apr 2021 23:03:44 -0700 Subject: [PATCH 0494/1101] Fix bad noise module __getattr__ function. Add example for noise samples to uint8. --- tcod/noise.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tcod/noise.py b/tcod/noise.py index 152dd2f7..8a2b0aea 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -27,8 +27,12 @@ [0.24613297, 0.3678193 , 0.6222316 , 0.8195127 , 0.79614234], [0.17527187, 0.4373501 , 0.76734173, 0.9020113 , 0.76327705]], dtype=float32) - - + >>> ((samples + 1.0) * (256 / 2)).astype(np.uint8) # Or as 8-bit unsigned bytes. + array([[128, 57, 30, 37, 40], + [ 92, 35, 33, 71, 90], + [ 76, 54, 85, 144, 164], + [ 63, 94, 159, 209, 203], + [ 44, 111, 196, 230, 195]], dtype=uint8) """ # noqa: E501 import enum import warnings @@ -89,7 +93,7 @@ def __repr__(self) -> str: def __getattr__(name: str) -> Implementation: - if hasattr(Implementation, name): + if name in Implementation.__members__: warnings.warn( f"'tcod.noise.{name}' is deprecated," f" use 'tcod.noise.Implementation.{name}' instead.", From 20e5c16c8bfe2bb7e89da86d1d2fa1d984abaecb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Apr 2021 00:08:07 -0700 Subject: [PATCH 0495/1101] Add indexing parameter to tcod.noise.grid. --- tcod/noise.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tcod/noise.py b/tcod/noise.py index 8a2b0aea..4fce8221 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -39,6 +39,7 @@ from typing import Any, Optional, Sequence, Tuple, Union import numpy as np +from typing_extensions import Literal import tcod.constants import tcod.random @@ -441,6 +442,7 @@ def grid( shape: Tuple[int, ...], scale: Union[Tuple[float, ...], float], origin: Optional[Tuple[int, ...]] = None, + indexing: Union[Literal["ij"], Literal["xy"]] = "xy", ) -> Tuple[np.ndarray, ...]: """A helper function for generating a grid of noise samples. @@ -456,6 +458,8 @@ def grid( If `None` then the `origin` will be zero on each axis. `origin` is not scaled by the `scale` parameter. + `indexing` is passed to :any:`numpy.meshgrid`. + Example:: >>> noise = tcod.noise.Noise(dimensions=2, seed=42) @@ -486,4 +490,6 @@ def grid( np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin) ) - return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing="xy")) + return tuple( + np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing) + ) From 4e20a5dd976d71d2189d7d9589e97c93e8c9d7f7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Apr 2021 00:37:09 -0700 Subject: [PATCH 0496/1101] Clean up Literal types. Literal types with multiple parameters are automatically Union's. --- tcod/_internal.py | 6 ++---- tcod/console.py | 12 ++++-------- tcod/context.py | 4 ++-- tcod/libtcodpy.py | 2 +- tcod/map.py | 4 ++-- tcod/noise.py | 2 +- tcod/path.py | 2 +- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/tcod/_internal.py b/tcod/_internal.py index 1d0d8d06..64ac6bc0 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -2,7 +2,7 @@ """ import functools import warnings -from typing import Any, AnyStr, Callable, TypeVar, Union, cast +from typing import Any, AnyStr, Callable, TypeVar, cast import numpy as np from typing_extensions import Literal, NoReturn @@ -43,9 +43,7 @@ def pending_deprecate( return deprecate(message, category, stacklevel) -def verify_order( - order: Union[Literal["C"], Literal["F"]] -) -> Union[Literal["C"], Literal["F"]]: +def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]: order = order.upper() # type: ignore if order not in ("C", "F"): raise TypeError("order must be 'C' or 'F', not %r" % (order,)) diff --git a/tcod/console.py b/tcod/console.py index 6a26d1b1..13dc45d3 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -93,7 +93,7 @@ def __init__( self, width: int, height: int, - order: Union[Literal["C"], Literal["F"]] = "C", + order: Literal["C", "F"] = "C", buffer: Optional[np.ndarray] = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] @@ -133,7 +133,7 @@ def __init__( @classmethod def _from_cdata( - cls, cdata: Any, order: Union[Literal["C"], Literal["F"]] = "C" + cls, cdata: Any, order: Literal["C", "F"] = "C" ) -> "Console": """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): @@ -144,9 +144,7 @@ def _from_cdata( return self @classmethod - def _get_root( - cls, order: Optional[Union[Literal["C"], Literal["F"]]] = None - ) -> "Console": + def _get_root(cls, order: Optional[Literal["C", "F"]] = None) -> "Console": """Return a root console singleton with valid buffers. This function will also update an already active root console. @@ -161,9 +159,7 @@ def _get_root( self._init_setup_console_data(self._order) return self - def _init_setup_console_data( - self, order: Union[Literal["C"], Literal["F"]] = "C" - ) -> None: + def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: """Setup numpy arrays over libtcod data buffers.""" global _root_console self._key_color = None diff --git a/tcod/context.py b/tcod/context.py index 3739089e..8e1b670c 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -49,7 +49,7 @@ """ # noqa: E501 import os import sys -from typing import Any, Iterable, List, Optional, Tuple, Union +from typing import Any, Iterable, List, Optional, Tuple from typing_extensions import Literal @@ -269,7 +269,7 @@ def new_console( min_columns: int = 1, min_rows: int = 1, magnification: float = 1.0, - order: Union[Literal["C"], Literal["F"]] = "C", + order: Literal["C", "F"] = "C", ) -> tcod.console.Console: """Return a new console sized for this context. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 6a7735dc..2031ecb6 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -880,7 +880,7 @@ def console_init_root( title: Optional[str] = None, fullscreen: bool = False, renderer: Optional[int] = None, - order: Union[Literal["C"], Literal["F"]] = "C", + order: Literal["C", "F"] = "C", vsync: Optional[bool] = None, ) -> tcod.console.Console: """Set up the primary display and return the root console. diff --git a/tcod/map.py b/tcod/map.py index 469c1c12..fc90bfda 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,7 +3,7 @@ """ import warnings -from typing import Any, Tuple, Union +from typing import Any, Tuple import numpy as np from typing_extensions import Literal @@ -73,7 +73,7 @@ def __init__( self, width: int, height: int, - order: Union[Literal["C"], Literal["F"]] = "C", + order: Literal["C", "F"] = "C", ): warnings.warn( "This class may perform poorly and is no longer needed.", diff --git a/tcod/noise.py b/tcod/noise.py index 4fce8221..b910e9fa 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -442,7 +442,7 @@ def grid( shape: Tuple[int, ...], scale: Union[Tuple[float, ...], float], origin: Optional[Tuple[int, ...]] = None, - indexing: Union[Literal["ij"], Literal["xy"]] = "xy", + indexing: Literal["ij", "xy"] = "xy", ) -> Tuple[np.ndarray, ...]: """A helper function for generating a grid of noise samples. diff --git a/tcod/path.py b/tcod/path.py index dfcf3804..b689ff36 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -312,7 +312,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: def maxarray( shape: Tuple[int, ...], dtype: Any = np.int32, - order: Union[Literal["C"], Literal["F"]] = "C", + order: Literal["C", "F"] = "C", ) -> np.ndarray: """Return a new array filled with the maximum finite value for `dtype`. From 6ccbb3a5ca6cf3d8382990daa5f5b25586bc5bbf Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Apr 2021 20:08:48 -0700 Subject: [PATCH 0497/1101] Prepare 12.2.0 release. --- CHANGELOG.rst | 3 +++ tcod/noise.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9f5aa7a9..25769129 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.2.0 - 2021-04-09 +------------------- Added - Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. - Added `tcod.noise.grid` helper function. diff --git a/tcod/noise.py b/tcod/noise.py index b910e9fa..bf83e421 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -477,6 +477,8 @@ def grid( [-0.18455243, -0.35063767, 0.4495706 , 0.02399864, -0.42226675], [-0.16333057, 0.18149695, 0.7547447 , -0.07006818, -0.6546707 ]], dtype=float32) + + .. versionadded:: 12.2 """ # noqa: E501 if isinstance(scale, float): scale = (scale,) * len(shape) From 597f2b84dc5d1d380203492ad000b8ce156b5c74 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 10 Apr 2021 20:52:17 -0700 Subject: [PATCH 0498/1101] Add experimental rgb and rgba attributes to Console. I want to test using these names for this attribute but don't want to officially add another name for the same thing until I know these work well in practice. --- tcod/console.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tcod/console.py b/tcod/console.py index 13dc45d3..30acce12 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -294,6 +294,16 @@ def tiles2(self) -> np.ndarray: """ return self.tiles_rgb + @property + def rgba(self) -> np.ndarray: + # This attribute is provisional and may be changed at anytime. + return self.tiles + + @property + def rgb(self) -> np.ndarray: + # This attribute is provisional and may be changed at anytime. + return self.tiles_rgb + @property def default_bg(self) -> Tuple[int, int, int]: """Tuple[int, int, int]: The default background color.""" From 0361d8dea8b29a5990829a68b1bcc17143f0b0f6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 11 Apr 2021 21:11:45 -0700 Subject: [PATCH 0499/1101] Update PyInstaller example. --- examples/distribution/PyInstaller/README.rst | 23 ++++++---- .../{ => data}/terminal8x8_gs_ro.png | Bin examples/distribution/PyInstaller/icon.ico | Bin 0 -> 20159 bytes .../PyInstaller/{hello_world.py => main.py} | 19 ++++---- examples/distribution/PyInstaller/main.spec | 42 ++++++++++++++++++ .../distribution/PyInstaller/requirements.txt | 3 ++ 6 files changed, 70 insertions(+), 17 deletions(-) rename examples/distribution/PyInstaller/{ => data}/terminal8x8_gs_ro.png (100%) create mode 100644 examples/distribution/PyInstaller/icon.ico rename examples/distribution/PyInstaller/{hello_world.py => main.py} (61%) create mode 100644 examples/distribution/PyInstaller/main.spec create mode 100644 examples/distribution/PyInstaller/requirements.txt diff --git a/examples/distribution/PyInstaller/README.rst b/examples/distribution/PyInstaller/README.rst index 677b9d76..ee863880 100644 --- a/examples/distribution/PyInstaller/README.rst +++ b/examples/distribution/PyInstaller/README.rst @@ -1,19 +1,26 @@ PyInstaller Example =================== -First, install the packages: ``tcod`` and ``PyInstaller``. +It's recommended to use a virtual environment to package Python executables. +Use the following guide on how to set one up: +https://docs.python.org/3/tutorial/venv.html -On Windows you must also install the ``pywin32`` package -(named ``pypiwin32`` if you're using pip install.) +Once the virtual environment is active you should install ``tcod``, ``PyInstaller``, and ``pypiwin32`` if on Windows from the ``requirements.txt`` file: -Then run the PyInstaller script with this command:: + pip install -r requirements.txt - PyInstaller hello_world.py --add-data "terminal8x8_gs_ro.png;." +Then run PyInstaller on the included Spec file:: + + PyInstaller main.spec The finished build will be placed in the ``dist/`` directory. -You can also build to one executable file using the following command:: +You can also build to one file using the following command:: + + PyInstaller main.spec --onefile + +Single file distributions have performance downsides so it is preferred to distribute a larger program this way. - PyInstaller hello_world.py --add-data "terminal8x8_gs_ro.png;." --onefile +For `tcod` it is recommended to set the ``PYTHONOPTIMIZE=1`` environment variable before running PyInstaller. This disables warnings for `tcod`'s deprecated functions and will improve the performance of those functions if you were using them. -The PyInstaller manual can be found at: https://pythonhosted.org/PyInstaller/ +The PyInstaller documentation can be found at: https://pythonhosted.org/PyInstaller/ diff --git a/examples/distribution/PyInstaller/terminal8x8_gs_ro.png b/examples/distribution/PyInstaller/data/terminal8x8_gs_ro.png similarity index 100% rename from examples/distribution/PyInstaller/terminal8x8_gs_ro.png rename to examples/distribution/PyInstaller/data/terminal8x8_gs_ro.png diff --git a/examples/distribution/PyInstaller/icon.ico b/examples/distribution/PyInstaller/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..465ee268c3cb2e58daa092f245db14874244f30e GIT binary patch literal 20159 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 CUK(Kl literal 0 HcmV?d00001 diff --git a/examples/distribution/PyInstaller/hello_world.py b/examples/distribution/PyInstaller/main.py similarity index 61% rename from examples/distribution/PyInstaller/hello_world.py rename to examples/distribution/PyInstaller/main.py index a1a4df83..805c4218 100755 --- a/examples/distribution/PyInstaller/hello_world.py +++ b/examples/distribution/PyInstaller/main.py @@ -13,21 +13,22 @@ # The base directory, this is sys._MEIPASS when in one-file mode. BASE_DIR = getattr(sys, "_MEIPASS", ".") -FONT_PATH = os.path.join(BASE_DIR, "terminal8x8_gs_ro.png") +FONT_PATH = os.path.join(BASE_DIR, "data/terminal8x8_gs_ro.png") def main(): - tcod.console_set_custom_font(FONT_PATH, tcod.FONT_LAYOUT_CP437) - with tcod.console_init_root( - WIDTH, HEIGHT, renderer=tcod.RENDERER_SDL2, vsync=True - ) as console: + tileset = tcod.tileset.load_tilesheet( + FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437 + ) + with tcod.context.new( + columns=WIDTH, rows=HEIGHT, tileset=tileset + ) as context: while True: - console.clear() + console = tcod.console.Console(WIDTH, HEIGHT) console.print(0, 0, "Hello World") - tcod.console_flush() - + context.present(console) for event in tcod.event.wait(): - if event.type == "QUIT": + if isinstance(event, tcod.event.Quit): raise SystemExit() diff --git a/examples/distribution/PyInstaller/main.spec b/examples/distribution/PyInstaller/main.spec new file mode 100644 index 00000000..8f1375ee --- /dev/null +++ b/examples/distribution/PyInstaller/main.spec @@ -0,0 +1,42 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + + +a = Analysis( + ["main.py"], + binaries=[], + datas=[("data", "data")], # Include all files in the 'data' directory. + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name="start", # Name of the executable. + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, # Set to False to disable the Windows terminal. + icon="icon.ico", # Windows icon file. +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name="hello_world", # Name of the distribution directory. +) diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt new file mode 100644 index 00000000..94790ef3 --- /dev/null +++ b/examples/distribution/PyInstaller/requirements.txt @@ -0,0 +1,3 @@ +tcod==12.2.0 +pyinstaller==4.2 +pypiwin32; sys_platform=="win32" From 1d7479afe7498386ec3d978cae8aaa815542783c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 15 Apr 2021 17:31:47 -0700 Subject: [PATCH 0500/1101] Move Windows deployment to GitHub workflows. --- .github/workflows/python-package.yml | 43 ++++++++++++++++++++-------- appveyor.yml | 12 -------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 90600e93..2c036dbd 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,26 +1,32 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package (Linux) +name: Package -on: [push, pull_request] +on: + push: + pull_request: + release: + types: [created] + +defaults: + run: + shell: bash jobs: build: - - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] + os: ['ubuntu-20.04', 'windows-2019'] + python-version: ['3.6', '3.7', '3.8', '3.9', 'pypy-3.6', 'pypy-3.7'] fail-fast: false steps: - - name: Clone ${{github.repository}} - # actions/checkout breaks `git describe` so a manual clone is needed. + - name: Checkout code + # v2 breaks `git describe` so v1 is needed. # https://github.com/actions/checkout/issues/272 - run: | - git clone https://github.com/${{github.repository}}.git ${{github.workspace}} - git checkout ${{github.sha}} + uses: actions/checkout@v1 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -29,13 +35,14 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install APT dependencies + if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install libsdl2-dev xvfb - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black isort + python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black isort wheel if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | @@ -60,8 +67,20 @@ jobs: isort examples/ --check --diff --thirdparty tcod - name: Build package. run: | - python setup.py develop # Install the package in-place. + python setup.py build sdist develop bdist_wheel --py-limited-api cp36 # Install the package in-place. - name: Test with pytest + if: runner.os == 'Windows' + run: | + pytest --cov-report=xml + - name: Test with pytest + if: runner.os != 'Windows' run: | xvfb-run --auto-servernum pytest --cov-report=xml - uses: codecov/codecov-action@v1 + - name: Upload to PyPI + if: github.event_name == 'release' && runner.os != 'Linux' + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing dist/* diff --git a/appveyor.yml b/appveyor.yml index 2e8a3444..71f56068 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,6 @@ image: Visual Studio 2019 environment: - TWINE_USERNAME: - secure: sUo+lYht329nQC5JCxEB8w== - TWINE_PASSWORD: - secure: mjSxOmR8POpeVSL9mwOnH2XQAn9Trj5wn6dbmCIwgTSr2qjfm/y1UoQ2XkYV9QpATp1xLUKalIDc62bORmIMKe6GkhDayUymLC6RM8KRZqM93aPoNXst9c5YfdUVbJuubk1QsJ/wE8KZrr6jrcXM3H7rfiq9Z8NtEud1XDD/d8MKbR9LkL4Y9ACI12jfayQ+uwaTOMhs9/1XlT92FEPGqy0Qlr/zXLd7TixOkAB/cyqYJdD3B6+fyvqxFxPVrA+iWhldv60ERQmlXW/j7WbPCw== CODACY_PROJECT_TOKEN: secure: xprpiCGL823NKrs/K2Cps1UVBEmpezXReLxcfLyU1M43ZBBOK91xvjdIJamYKi8D DEPLOY_ONLY: false @@ -25,10 +21,6 @@ environment: - PYTHON: C:\Python39-x64\python.exe platform: x64 NO_DEPLOY: true - - PYPY3: pypy3.6-v7.3.2 - platform: Any CPU - - PYPY3: pypy3.7-v7.3.2 - platform: Any CPU matrix: allow_failures: @@ -58,7 +50,3 @@ on_success: - pip install codacy-coverage - coverage xml - if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml || cd . - -deploy_script: -- "if defined APPVEYOR_REPO_TAG_NAME if not defined NO_DEPLOY pip install twine" -- "if defined APPVEYOR_REPO_TAG_NAME if not defined NO_DEPLOY twine upload --skip-existing dist/*" From 8336007470557f1753f3c475d3553fa13a7a3837 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 15 Apr 2021 18:35:23 -0700 Subject: [PATCH 0501/1101] Split linting and packaging workflows. Fix minor Flake8 warnings. Add an isort config to the example directory so that the tcod import is treated correctly. --- .github/workflows/python-lint.yml | 54 ++++++++++++++++++++++++++++ .github/workflows/python-package.yml | 20 +---------- examples/.isort.cfg | 5 +++ setup.cfg | 1 + tcod/context.py | 2 +- tcod/libtcodpy.py | 4 +-- 6 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/python-lint.yml create mode 100644 examples/.isort.cfg diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 00000000..9fa20f82 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,54 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Lint + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-20.04 + env: + python-version: '3.9' + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ env.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.python-version }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 mypy black isort + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Fake initialize package + run: | + echo '__version__ = ""' > tcod/version.py + - name: Flake8 + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: flake8 + run: flake8 tcod/ + - name: MyPy + if: always() + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: mypy + run: mypy --show-column-numbers tcod/ + - name: isort + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort tcod/ tests/ --check + - name: isort (examples) + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort examples/ --check --thirdparty tcod + - name: Black + run: | + black --check tcod/ diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2c036dbd..d3ac1b02 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -42,29 +42,11 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 mypy pytest pytest-cov pytest-benchmark black isort wheel + python -m pip install pytest pytest-cov pytest-benchmark wheel if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | python setup.py check # Creates tcod/version.py. - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 tcod/ --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 tcod/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Check type hints with MyPy - if: ${{ matrix.python-version != '3.6' }} - run: | - mypy tcod/ - - name: Check formatting with Black - run: | - black --check tcod/ - - name: Check import order - run: | - isort tcod/ --check --diff - isort tests/ --check --diff - isort examples/ --check --diff --thirdparty tcod - name: Build package. run: | python setup.py build sdist develop bdist_wheel --py-limited-api cp36 # Install the package in-place. diff --git a/examples/.isort.cfg b/examples/.isort.cfg new file mode 100644 index 00000000..bb09c95c --- /dev/null +++ b/examples/.isort.cfg @@ -0,0 +1,5 @@ +[isort] +profile= black +py_version = 36 +skip_gitignore = true +line_length = 79 diff --git a/setup.cfg b/setup.cfg index b0078109..935985bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ filterwarnings = [flake8] ignore = E203 W503 +max-line-length = 120 [mypy] python_version = 3.6 diff --git a/tcod/context.py b/tcod/context.py index 8e1b670c..a358312c 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -56,7 +56,7 @@ import tcod import tcod.event import tcod.tileset -from tcod._internal import _check, _check_warn, deprecate, pending_deprecate +from tcod._internal import _check, _check_warn, pending_deprecate from tcod.loader import ffi, lib __all__ = ( diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 2031ecb6..e43721d8 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1628,7 +1628,7 @@ def console_hline( con: tcod.console.Console, x: int, y: int, - l: int, + l: int, # noqa: E741 flag: int = BKGND_DEFAULT, ) -> None: """Draw a horizontal line on the console. @@ -1646,7 +1646,7 @@ def console_vline( con: tcod.console.Console, x: int, y: int, - l: int, + l: int, # noqa: E741 flag: int = BKGND_DEFAULT, ) -> None: """Draw a vertical line on the console. From 81134855372dabe94806fdd46b8e418f129e4154 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 16 Apr 2021 22:32:07 -0700 Subject: [PATCH 0502/1101] Increment PyInstaller version. This new version is expected to have fewer anti-virus false positives. --- examples/distribution/PyInstaller/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt index 94790ef3..f28443fd 100644 --- a/examples/distribution/PyInstaller/requirements.txt +++ b/examples/distribution/PyInstaller/requirements.txt @@ -1,3 +1,3 @@ tcod==12.2.0 -pyinstaller==4.2 +pyinstaller==4.3 pypiwin32; sys_platform=="win32" From a47b52a402ae02562a21ec565dc09d896f188021 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 2 May 2021 14:00:15 -0700 Subject: [PATCH 0503/1101] Added keyboard functions. Includes a frequently requested keyboard state function. --- CHANGELOG.rst | 6 +++++ tcod/event.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 25769129..87cddc26 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,12 @@ v2.0.0 Unreleased ------------------ + - Added some keyboard functions: + - `tcod.event.get_keyboard_state` + - `tcod.event.get_modifier_state` + - `tcod.event.key_from_scancode` + - `tcod.event.scancode_from_key` + - `tcod.event.get_key_name` 12.2.0 - 2021-04-09 ------------------- diff --git a/tcod/event.py b/tcod/event.py index 7485c751..d8edb941 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -33,6 +33,8 @@ TypeVar, ) +import numpy as np + import tcod.event_constants from tcod.event_constants import * # noqa: F4 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT @@ -1046,6 +1048,69 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: return 0 +def get_keyboard_state() -> np.ndarray: + """Return a boolean array with the current keyboard state. + + Index this array with a scancode. The value will be True if the key is + currently held. + + Example:: + + state = tcod.event.get_keyboard_state() + is_w_held = state[tcod.event.SCANCODE_W] + + .. versionadded:: 12.3 + """ + numkeys = ffi.new("int[1]") + keyboard_state = lib.SDL_GetKeyboardState(numkeys) + out: np.ndarray = np.frombuffer( + ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool + ) + out.flags["WRITEABLE"] = False # This buffer is supposed to be const. + return out + + +def get_modifier_state() -> int: + """Return a bitmask of the active keyboard modifiers. + + .. versionadded:: 12.3 + """ + return int(lib.SDL_GetModState()) + + +def key_from_scancode(scancode: int) -> int: + """Return a keycode from a scancode. Based on the current keyboard layout. + + .. versionadded:: 12.3 + """ + return int(lib.SDL_GetKeyFromScancode(scancode)) + + +def scancode_from_key(keycode: int) -> int: + """Return a scancode from a keycode. Based on the current keyboard layout. + + .. versionadded:: 12.3 + """ + return int(lib.SDL_GetScancodeFromKey(keycode)) + + +def get_key_name(keycode: int) -> str: + """Return a human-readable name of a keycode. + + Returns "" if the keycode doesn't have a name. + + Example:: + + >>> tcod.event.get_key_name(tcod.event.K_F1) + 'F1' + >>> tcod.event.get_key_name(tcod.event.K_BACKSPACE) + 'Backspace' + + .. versionadded:: 12.3 + """ + return str(ffi.string(lib.SDL_GetKeyName(keycode)), encoding="utf-8") + + __all__ = [ # noqa: F405 "Point", "BUTTON_LEFT", @@ -1077,6 +1142,11 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: "wait", "get_mouse_state", "EventDispatch", + "get_keyboard_state", + "get_modifier_state", + "key_from_scancode", + "scancode_from_key", + "get_key_name", # --- From event_constants.py --- "SCANCODE_UNKNOWN", "SCANCODE_A", From f458c4d997adcaeae95835d5a337cc46e793525a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 8 May 2021 18:49:50 -0700 Subject: [PATCH 0504/1101] Raise a better error when contexts are pickled. --- CHANGELOG.rst | 4 ++++ tcod/context.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 87cddc26..81bb7e78 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ v2.0.0 Unreleased ------------------ +Added - Added some keyboard functions: - `tcod.event.get_keyboard_state` - `tcod.event.get_modifier_state` @@ -15,6 +16,9 @@ Unreleased - `tcod.event.scancode_from_key` - `tcod.event.get_key_name` +Fixed + - Contexts now give a more useful error when pickled. + 12.2.0 - 2021-04-09 ------------------- Added diff --git a/tcod/context.py b/tcod/context.py index a358312c..0f5b4bc5 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -48,10 +48,11 @@ .. versionadded:: 11.12 """ # noqa: E501 import os +import pickle import sys from typing import Any, Iterable, List, Optional, Tuple -from typing_extensions import Literal +from typing_extensions import Literal, NoReturn import tcod import tcod.event @@ -366,6 +367,12 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: ''' # noqa: E501 return lib.TCOD_context_get_sdl_window(self._context_p) + def __reduce__(self) -> NoReturn: + """Contexts can not be pickled, so this class will raise + :class:`pickle.PicklingError`. + """ + raise pickle.PicklingError("Python-tcod contexts can not be pickled.") + @ffi.def_extern() # type: ignore def _pycall_cli_output(catch_reference: Any, output: Any) -> None: From e139a94b8988d894931eb323da3b3b0540f7c520 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 12:28:40 -0700 Subject: [PATCH 0505/1101] Add keyboard enums. --- CHANGELOG.rst | 9 +- build_libtcod.py | 34 ++ docs/tcod/event.rst | 21 + tcod/event.py | 1121 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 1155 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81bb7e78..d3d6526b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,12 +9,13 @@ v2.0.0 Unreleased ------------------ Added - - Added some keyboard functions: + - New keyboard enums: + - `tcod.event.KeySym` + - `tcod.event.Scancode` + - `tcod.event.Modifier` + - New functions: - `tcod.event.get_keyboard_state` - `tcod.event.get_modifier_state` - - `tcod.event.key_from_scancode` - - `tcod.event.scancode_from_key` - - `tcod.event.get_key_name` Fixed - Contexts now give a more useful error when pickled. diff --git a/build_libtcod.py b/build_libtcod.py index eecbccd4..bedc75a8 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -436,6 +436,21 @@ def update_module_all(filename: str, new_all: str) -> None: f.write("%s\n %s,\n %s" % (header, new_all, footer)) +def generate_enums(prefix: str) -> Iterator[str]: + """Generate attribute assignments suitable for a Python enum.""" + prefix_len = len(prefix) - len("SDL_") + 1 + for name, value in sorted( + find_sdl_attrs(prefix), key=lambda item: item[1] + ): + name = name.split("_", 1)[1] + if name.isdigit(): + name = f"N{name}" + if name in "IOl": # Handle Flake8 warnings. + yield f"{name} = {value} # noqa: E741" + else: + yield f"{name} = {value}" + + def write_library_constants() -> None: """Write libtcod constants into the tcod.constants module.""" from tcod._libtcod import lib, ffi @@ -514,6 +529,25 @@ def write_library_constants() -> None: all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) + with open("tcod/event.py", "r") as f: + event_py = f.read() + + event_py = re.sub( + r"(?<=# --- SDL scancodes ---\n ).*?(?=\n # --- end ---\n)", + "\n ".join(generate_enums("SDL_SCANCODE")), + event_py, + flags=re.DOTALL, + ) + event_py = re.sub( + r"(?<=# --- SDL keyboard symbols ---\n ).*?(?=\n # --- end ---\n)", + "\n ".join(generate_enums("SDLK")), + event_py, + flags=re.DOTALL, + ) + + with open("tcod/event.py", "w") as f: + f.write(event_py) + if __name__ == "__main__": write_library_constants() diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 0a2b977f..7aa9be4c 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -3,3 +3,24 @@ tcod.event - SDL2 Event Handling .. automodule:: tcod.event :members: + :member-order: bysource + :exclude-members: KeySym, Scancode, Modifier + + +Keyboard Enums +-------------- + +- :class:`KeySym`: Keys based on their glyph. +- :class:`Scancode`: Keys based on their physical location. +- :class:`Modifier`: Keyboard modifier keys. + +.. autoclass:: KeySym + :members: + +.. autoclass:: Scancode + :members: + +.. autoclass:: Modifier + :members: + :member-order: bysource + :undoc-members: diff --git a/tcod/event.py b/tcod/event.py index d8edb941..1fda76c9 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -19,6 +19,7 @@ .. versionadded:: 8.4 """ +import enum import warnings from typing import ( Any, @@ -108,6 +109,42 @@ def _verify_tile_coordinates(xy: Optional[Point]) -> Point: return Point(0, 0) +class Modifier(enum.IntFlag): + """Keyboard modifiers.""" + + NONE = 0 + LSHIFT = 1 + """Left shift.""" + RSHIFT = 2 + """Right shift.""" + SHIFT = LSHIFT | RSHIFT + """LSHIFT | RSHIFT""" + LCTRL = 64 + """Left control.""" + RCTRL = 128 + """Right control.""" + CTRL = LCTRL | RCTRL + """LCTRL | RCTRL""" + LALT = 256 + """Left alt.""" + RALT = 512 + """Right alt.""" + ALT = LALT | RALT + """LALT | RALT""" + LGUI = 1024 + """Left meta key.""" + RGUI = 2048 + """Right meta key.""" + GUI = LGUI | RGUI + """LGUI | RGUI""" + NUM = 4096 + """Numpad lock.""" + CAPS = 8192 + """Caps lock.""" + MODE = 16384 + """Alt graph.""" + + # manually define names for SDL macros BUTTON_LEFT = 1 BUTTON_MIDDLE = 2 @@ -1057,7 +1094,11 @@ def get_keyboard_state() -> np.ndarray: Example:: state = tcod.event.get_keyboard_state() - is_w_held = state[tcod.event.SCANCODE_W] + is_w_held = state[tcod.event.Scancode.W] + + # Get a WASD vector: + x = int(state[tcod.event.Scancode.E]) - int(state[tcod.event.Scancode.A]) + y = int(state[tcod.event.Scancode.S]) - int(state[tcod.event.Scancode.W]) .. versionadded:: 12.3 """ @@ -1070,48 +1111,1077 @@ def get_keyboard_state() -> np.ndarray: return out -def get_modifier_state() -> int: +def get_modifier_state() -> Modifier: """Return a bitmask of the active keyboard modifiers. .. versionadded:: 12.3 """ - return int(lib.SDL_GetModState()) + return Modifier(lib.SDL_GetModState()) + + +class Scancode(enum.IntEnum): + """A Scancode represents the physical location of a key. + + For example the scan codes for WASD remain in the same physical location + regardless of the actual keyboard layout. + + These names are derived from SDL expect for the numbers which are prefixed + with ``N`` (since raw numbers can not be a Python name.) + + ================== === + UNKNOWN 0 + A 4 + B 5 + C 6 + D 7 + E 8 + F 9 + G 10 + H 11 + I 12 + J 13 + K 14 + L 15 + M 16 + N 17 + O 18 + P 19 + Q 20 + R 21 + S 22 + T 23 + U 24 + V 25 + W 26 + X 27 + Y 28 + Z 29 + N1 30 + N2 31 + N3 32 + N4 33 + N5 34 + N6 35 + N7 36 + N8 37 + N9 38 + N0 39 + RETURN 40 + ESCAPE 41 + BACKSPACE 42 + TAB 43 + SPACE 44 + MINUS 45 + EQUALS 46 + LEFTBRACKET 47 + RIGHTBRACKET 48 + BACKSLASH 49 + NONUSHASH 50 + SEMICOLON 51 + APOSTROPHE 52 + GRAVE 53 + COMMA 54 + PERIOD 55 + SLASH 56 + CAPSLOCK 57 + F1 58 + F2 59 + F3 60 + F4 61 + F5 62 + F6 63 + F7 64 + F8 65 + F9 66 + F10 67 + F11 68 + F12 69 + PRINTSCREEN 70 + SCROLLLOCK 71 + PAUSE 72 + INSERT 73 + HOME 74 + PAGEUP 75 + DELETE 76 + END 77 + PAGEDOWN 78 + RIGHT 79 + LEFT 80 + DOWN 81 + UP 82 + NUMLOCKCLEAR 83 + KP_DIVIDE 84 + KP_MULTIPLY 85 + KP_MINUS 86 + KP_PLUS 87 + KP_ENTER 88 + KP_1 89 + KP_2 90 + KP_3 91 + KP_4 92 + KP_5 93 + KP_6 94 + KP_7 95 + KP_8 96 + KP_9 97 + KP_0 98 + KP_PERIOD 99 + NONUSBACKSLASH 100 + APPLICATION 101 + POWER 102 + KP_EQUALS 103 + F13 104 + F14 105 + F15 106 + F16 107 + F17 108 + F18 109 + F19 110 + F20 111 + F21 112 + F22 113 + F23 114 + F24 115 + EXECUTE 116 + HELP 117 + MENU 118 + SELECT 119 + STOP 120 + AGAIN 121 + UNDO 122 + CUT 123 + COPY 124 + PASTE 125 + FIND 126 + MUTE 127 + VOLUMEUP 128 + VOLUMEDOWN 129 + KP_COMMA 133 + KP_EQUALSAS400 134 + INTERNATIONAL1 135 + INTERNATIONAL2 136 + INTERNATIONAL3 137 + INTERNATIONAL4 138 + INTERNATIONAL5 139 + INTERNATIONAL6 140 + INTERNATIONAL7 141 + INTERNATIONAL8 142 + INTERNATIONAL9 143 + LANG1 144 + LANG2 145 + LANG3 146 + LANG4 147 + LANG5 148 + LANG6 149 + LANG7 150 + LANG8 151 + LANG9 152 + ALTERASE 153 + SYSREQ 154 + CANCEL 155 + CLEAR 156 + PRIOR 157 + RETURN2 158 + SEPARATOR 159 + OUT 160 + OPER 161 + CLEARAGAIN 162 + CRSEL 163 + EXSEL 164 + KP_00 176 + KP_000 177 + THOUSANDSSEPARATOR 178 + DECIMALSEPARATOR 179 + CURRENCYUNIT 180 + CURRENCYSUBUNIT 181 + KP_LEFTPAREN 182 + KP_RIGHTPAREN 183 + KP_LEFTBRACE 184 + KP_RIGHTBRACE 185 + KP_TAB 186 + KP_BACKSPACE 187 + KP_A 188 + KP_B 189 + KP_C 190 + KP_D 191 + KP_E 192 + KP_F 193 + KP_XOR 194 + KP_POWER 195 + KP_PERCENT 196 + KP_LESS 197 + KP_GREATER 198 + KP_AMPERSAND 199 + KP_DBLAMPERSAND 200 + KP_VERTICALBAR 201 + KP_DBLVERTICALBAR 202 + KP_COLON 203 + KP_HASH 204 + KP_SPACE 205 + KP_AT 206 + KP_EXCLAM 207 + KP_MEMSTORE 208 + KP_MEMRECALL 209 + KP_MEMCLEAR 210 + KP_MEMADD 211 + KP_MEMSUBTRACT 212 + KP_MEMMULTIPLY 213 + KP_MEMDIVIDE 214 + KP_PLUSMINUS 215 + KP_CLEAR 216 + KP_CLEARENTRY 217 + KP_BINARY 218 + KP_OCTAL 219 + KP_DECIMAL 220 + KP_HEXADECIMAL 221 + LCTRL 224 + LSHIFT 225 + LALT 226 + LGUI 227 + RCTRL 228 + RSHIFT 229 + RALT 230 + RGUI 231 + MODE 257 + AUDIONEXT 258 + AUDIOPREV 259 + AUDIOSTOP 260 + AUDIOPLAY 261 + AUDIOMUTE 262 + MEDIASELECT 263 + WWW 264 + MAIL 265 + CALCULATOR 266 + COMPUTER 267 + AC_SEARCH 268 + AC_HOME 269 + AC_BACK 270 + AC_FORWARD 271 + AC_STOP 272 + AC_REFRESH 273 + AC_BOOKMARKS 274 + BRIGHTNESSDOWN 275 + BRIGHTNESSUP 276 + DISPLAYSWITCH 277 + KBDILLUMTOGGLE 278 + KBDILLUMDOWN 279 + KBDILLUMUP 280 + EJECT 281 + SLEEP 282 + APP1 283 + APP2 284 + ================== === + """ -def key_from_scancode(scancode: int) -> int: - """Return a keycode from a scancode. Based on the current keyboard layout. + # --- SDL scancodes --- + UNKNOWN = 0 + A = 4 + B = 5 + C = 6 + D = 7 + E = 8 + F = 9 + G = 10 + H = 11 + I = 12 # noqa: E741 + J = 13 + K = 14 + L = 15 + M = 16 + N = 17 + O = 18 # noqa: E741 + P = 19 + Q = 20 + R = 21 + S = 22 + T = 23 + U = 24 + V = 25 + W = 26 + X = 27 + Y = 28 + Z = 29 + N1 = 30 + N2 = 31 + N3 = 32 + N4 = 33 + N5 = 34 + N6 = 35 + N7 = 36 + N8 = 37 + N9 = 38 + N0 = 39 + RETURN = 40 + ESCAPE = 41 + BACKSPACE = 42 + TAB = 43 + SPACE = 44 + MINUS = 45 + EQUALS = 46 + LEFTBRACKET = 47 + RIGHTBRACKET = 48 + BACKSLASH = 49 + NONUSHASH = 50 + SEMICOLON = 51 + APOSTROPHE = 52 + GRAVE = 53 + COMMA = 54 + PERIOD = 55 + SLASH = 56 + CAPSLOCK = 57 + F1 = 58 + F2 = 59 + F3 = 60 + F4 = 61 + F5 = 62 + F6 = 63 + F7 = 64 + F8 = 65 + F9 = 66 + F10 = 67 + F11 = 68 + F12 = 69 + PRINTSCREEN = 70 + SCROLLLOCK = 71 + PAUSE = 72 + INSERT = 73 + HOME = 74 + PAGEUP = 75 + DELETE = 76 + END = 77 + PAGEDOWN = 78 + RIGHT = 79 + LEFT = 80 + DOWN = 81 + UP = 82 + NUMLOCKCLEAR = 83 + KP_DIVIDE = 84 + KP_MULTIPLY = 85 + KP_MINUS = 86 + KP_PLUS = 87 + KP_ENTER = 88 + KP_1 = 89 + KP_2 = 90 + KP_3 = 91 + KP_4 = 92 + KP_5 = 93 + KP_6 = 94 + KP_7 = 95 + KP_8 = 96 + KP_9 = 97 + KP_0 = 98 + KP_PERIOD = 99 + NONUSBACKSLASH = 100 + APPLICATION = 101 + POWER = 102 + KP_EQUALS = 103 + F13 = 104 + F14 = 105 + F15 = 106 + F16 = 107 + F17 = 108 + F18 = 109 + F19 = 110 + F20 = 111 + F21 = 112 + F22 = 113 + F23 = 114 + F24 = 115 + EXECUTE = 116 + HELP = 117 + MENU = 118 + SELECT = 119 + STOP = 120 + AGAIN = 121 + UNDO = 122 + CUT = 123 + COPY = 124 + PASTE = 125 + FIND = 126 + MUTE = 127 + VOLUMEUP = 128 + VOLUMEDOWN = 129 + KP_COMMA = 133 + KP_EQUALSAS400 = 134 + INTERNATIONAL1 = 135 + INTERNATIONAL2 = 136 + INTERNATIONAL3 = 137 + INTERNATIONAL4 = 138 + INTERNATIONAL5 = 139 + INTERNATIONAL6 = 140 + INTERNATIONAL7 = 141 + INTERNATIONAL8 = 142 + INTERNATIONAL9 = 143 + LANG1 = 144 + LANG2 = 145 + LANG3 = 146 + LANG4 = 147 + LANG5 = 148 + LANG6 = 149 + LANG7 = 150 + LANG8 = 151 + LANG9 = 152 + ALTERASE = 153 + SYSREQ = 154 + CANCEL = 155 + CLEAR = 156 + PRIOR = 157 + RETURN2 = 158 + SEPARATOR = 159 + OUT = 160 + OPER = 161 + CLEARAGAIN = 162 + CRSEL = 163 + EXSEL = 164 + KP_00 = 176 + KP_000 = 177 + THOUSANDSSEPARATOR = 178 + DECIMALSEPARATOR = 179 + CURRENCYUNIT = 180 + CURRENCYSUBUNIT = 181 + KP_LEFTPAREN = 182 + KP_RIGHTPAREN = 183 + KP_LEFTBRACE = 184 + KP_RIGHTBRACE = 185 + KP_TAB = 186 + KP_BACKSPACE = 187 + KP_A = 188 + KP_B = 189 + KP_C = 190 + KP_D = 191 + KP_E = 192 + KP_F = 193 + KP_XOR = 194 + KP_POWER = 195 + KP_PERCENT = 196 + KP_LESS = 197 + KP_GREATER = 198 + KP_AMPERSAND = 199 + KP_DBLAMPERSAND = 200 + KP_VERTICALBAR = 201 + KP_DBLVERTICALBAR = 202 + KP_COLON = 203 + KP_HASH = 204 + KP_SPACE = 205 + KP_AT = 206 + KP_EXCLAM = 207 + KP_MEMSTORE = 208 + KP_MEMRECALL = 209 + KP_MEMCLEAR = 210 + KP_MEMADD = 211 + KP_MEMSUBTRACT = 212 + KP_MEMMULTIPLY = 213 + KP_MEMDIVIDE = 214 + KP_PLUSMINUS = 215 + KP_CLEAR = 216 + KP_CLEARENTRY = 217 + KP_BINARY = 218 + KP_OCTAL = 219 + KP_DECIMAL = 220 + KP_HEXADECIMAL = 221 + LCTRL = 224 + LSHIFT = 225 + LALT = 226 + LGUI = 227 + RCTRL = 228 + RSHIFT = 229 + RALT = 230 + RGUI = 231 + MODE = 257 + AUDIONEXT = 258 + AUDIOPREV = 259 + AUDIOSTOP = 260 + AUDIOPLAY = 261 + AUDIOMUTE = 262 + MEDIASELECT = 263 + WWW = 264 + MAIL = 265 + CALCULATOR = 266 + COMPUTER = 267 + AC_SEARCH = 268 + AC_HOME = 269 + AC_BACK = 270 + AC_FORWARD = 271 + AC_STOP = 272 + AC_REFRESH = 273 + AC_BOOKMARKS = 274 + BRIGHTNESSDOWN = 275 + BRIGHTNESSUP = 276 + DISPLAYSWITCH = 277 + KBDILLUMTOGGLE = 278 + KBDILLUMDOWN = 279 + KBDILLUMUP = 280 + EJECT = 281 + SLEEP = 282 + APP1 = 283 + APP2 = 284 + # --- end --- - .. versionadded:: 12.3 - """ - return int(lib.SDL_GetKeyFromScancode(scancode)) + @property + def label(self) -> str: + """Return a human-readable name of a key based on a scancode. + .. seealso:: + :any:`KeySym.label` -def scancode_from_key(keycode: int) -> int: - """Return a scancode from a keycode. Based on the current keyboard layout. + .. versionadded:: 12.3 + """ + return self.keysym.label - .. versionadded:: 12.3 - """ - return int(lib.SDL_GetScancodeFromKey(keycode)) + @property + def keysym(self) -> "KeySym": + """Return a :class:`KeySym` from a scancode. + Based on the current keyboard layout. -def get_key_name(keycode: int) -> str: - """Return a human-readable name of a keycode. + .. versionadded:: 12.3 + """ + return KeySym(lib.SDL_GetKeyFromScancode(self.value)) - Returns "" if the keycode doesn't have a name. + @property + def scancode(self) -> "Scancode": + """Return a scancode from a keycode. - Example:: + Returns itself since it is already a :class:`Scancode`. - >>> tcod.event.get_key_name(tcod.event.K_F1) - 'F1' - >>> tcod.event.get_key_name(tcod.event.K_BACKSPACE) - 'Backspace' + .. seealso:: + :any:`KeySym.scancode` - .. versionadded:: 12.3 + .. versionadded:: 12.3 + """ + return self + + +class KeySym(enum.IntEnum): + """Key syms + + ================== ========== + UNKNOWN 0 + BACKSPACE 8 + TAB 9 + RETURN 13 + ESCAPE 27 + SPACE 32 + EXCLAIM 33 + QUOTEDBL 34 + HASH 35 + DOLLAR 36 + PERCENT 37 + AMPERSAND 38 + QUOTE 39 + LEFTPAREN 40 + RIGHTPAREN 41 + ASTERISK 42 + PLUS 43 + COMMA 44 + MINUS 45 + PERIOD 46 + SLASH 47 + N0 48 + N1 49 + N2 50 + N3 51 + N4 52 + N5 53 + N6 54 + N7 55 + N8 56 + N9 57 + COLON 58 + SEMICOLON 59 + LESS 60 + EQUALS 61 + GREATER 62 + QUESTION 63 + AT 64 + LEFTBRACKET 91 + BACKSLASH 92 + RIGHTBRACKET 93 + CARET 94 + UNDERSCORE 95 + BACKQUOTE 96 + a 97 + b 98 + c 99 + d 100 + e 101 + f 102 + g 103 + h 104 + i 105 + j 106 + k 107 + l 108 + m 109 + n 110 + o 111 + p 112 + q 113 + r 114 + s 115 + t 116 + u 117 + v 118 + w 119 + x 120 + y 121 + z 122 + DELETE 127 + SCANCODE_MASK 1073741824 + CAPSLOCK 1073741881 + F1 1073741882 + F2 1073741883 + F3 1073741884 + F4 1073741885 + F5 1073741886 + F6 1073741887 + F7 1073741888 + F8 1073741889 + F9 1073741890 + F10 1073741891 + F11 1073741892 + F12 1073741893 + PRINTSCREEN 1073741894 + SCROLLLOCK 1073741895 + PAUSE 1073741896 + INSERT 1073741897 + HOME 1073741898 + PAGEUP 1073741899 + END 1073741901 + PAGEDOWN 1073741902 + RIGHT 1073741903 + LEFT 1073741904 + DOWN 1073741905 + UP 1073741906 + NUMLOCKCLEAR 1073741907 + KP_DIVIDE 1073741908 + KP_MULTIPLY 1073741909 + KP_MINUS 1073741910 + KP_PLUS 1073741911 + KP_ENTER 1073741912 + KP_1 1073741913 + KP_2 1073741914 + KP_3 1073741915 + KP_4 1073741916 + KP_5 1073741917 + KP_6 1073741918 + KP_7 1073741919 + KP_8 1073741920 + KP_9 1073741921 + KP_0 1073741922 + KP_PERIOD 1073741923 + APPLICATION 1073741925 + POWER 1073741926 + KP_EQUALS 1073741927 + F13 1073741928 + F14 1073741929 + F15 1073741930 + F16 1073741931 + F17 1073741932 + F18 1073741933 + F19 1073741934 + F20 1073741935 + F21 1073741936 + F22 1073741937 + F23 1073741938 + F24 1073741939 + EXECUTE 1073741940 + HELP 1073741941 + MENU 1073741942 + SELECT 1073741943 + STOP 1073741944 + AGAIN 1073741945 + UNDO 1073741946 + CUT 1073741947 + COPY 1073741948 + PASTE 1073741949 + FIND 1073741950 + MUTE 1073741951 + VOLUMEUP 1073741952 + VOLUMEDOWN 1073741953 + KP_COMMA 1073741957 + KP_EQUALSAS400 1073741958 + ALTERASE 1073741977 + SYSREQ 1073741978 + CANCEL 1073741979 + CLEAR 1073741980 + PRIOR 1073741981 + RETURN2 1073741982 + SEPARATOR 1073741983 + OUT 1073741984 + OPER 1073741985 + CLEARAGAIN 1073741986 + CRSEL 1073741987 + EXSEL 1073741988 + KP_00 1073742000 + KP_000 1073742001 + THOUSANDSSEPARATOR 1073742002 + DECIMALSEPARATOR 1073742003 + CURRENCYUNIT 1073742004 + CURRENCYSUBUNIT 1073742005 + KP_LEFTPAREN 1073742006 + KP_RIGHTPAREN 1073742007 + KP_LEFTBRACE 1073742008 + KP_RIGHTBRACE 1073742009 + KP_TAB 1073742010 + KP_BACKSPACE 1073742011 + KP_A 1073742012 + KP_B 1073742013 + KP_C 1073742014 + KP_D 1073742015 + KP_E 1073742016 + KP_F 1073742017 + KP_XOR 1073742018 + KP_POWER 1073742019 + KP_PERCENT 1073742020 + KP_LESS 1073742021 + KP_GREATER 1073742022 + KP_AMPERSAND 1073742023 + KP_DBLAMPERSAND 1073742024 + KP_VERTICALBAR 1073742025 + KP_DBLVERTICALBAR 1073742026 + KP_COLON 1073742027 + KP_HASH 1073742028 + KP_SPACE 1073742029 + KP_AT 1073742030 + KP_EXCLAM 1073742031 + KP_MEMSTORE 1073742032 + KP_MEMRECALL 1073742033 + KP_MEMCLEAR 1073742034 + KP_MEMADD 1073742035 + KP_MEMSUBTRACT 1073742036 + KP_MEMMULTIPLY 1073742037 + KP_MEMDIVIDE 1073742038 + KP_PLUSMINUS 1073742039 + KP_CLEAR 1073742040 + KP_CLEARENTRY 1073742041 + KP_BINARY 1073742042 + KP_OCTAL 1073742043 + KP_DECIMAL 1073742044 + KP_HEXADECIMAL 1073742045 + LCTRL 1073742048 + LSHIFT 1073742049 + LALT 1073742050 + LGUI 1073742051 + RCTRL 1073742052 + RSHIFT 1073742053 + RALT 1073742054 + RGUI 1073742055 + MODE 1073742081 + AUDIONEXT 1073742082 + AUDIOPREV 1073742083 + AUDIOSTOP 1073742084 + AUDIOPLAY 1073742085 + AUDIOMUTE 1073742086 + MEDIASELECT 1073742087 + WWW 1073742088 + MAIL 1073742089 + CALCULATOR 1073742090 + COMPUTER 1073742091 + AC_SEARCH 1073742092 + AC_HOME 1073742093 + AC_BACK 1073742094 + AC_FORWARD 1073742095 + AC_STOP 1073742096 + AC_REFRESH 1073742097 + AC_BOOKMARKS 1073742098 + BRIGHTNESSDOWN 1073742099 + BRIGHTNESSUP 1073742100 + DISPLAYSWITCH 1073742101 + KBDILLUMTOGGLE 1073742102 + KBDILLUMDOWN 1073742103 + KBDILLUMUP 1073742104 + EJECT 1073742105 + SLEEP 1073742106 + ================== ========== """ - return str(ffi.string(lib.SDL_GetKeyName(keycode)), encoding="utf-8") + + # --- SDL keyboard symbols --- + UNKNOWN = 0 + BACKSPACE = 8 + TAB = 9 + RETURN = 13 + ESCAPE = 27 + SPACE = 32 + EXCLAIM = 33 + QUOTEDBL = 34 + HASH = 35 + DOLLAR = 36 + PERCENT = 37 + AMPERSAND = 38 + QUOTE = 39 + LEFTPAREN = 40 + RIGHTPAREN = 41 + ASTERISK = 42 + PLUS = 43 + COMMA = 44 + MINUS = 45 + PERIOD = 46 + SLASH = 47 + N0 = 48 + N1 = 49 + N2 = 50 + N3 = 51 + N4 = 52 + N5 = 53 + N6 = 54 + N7 = 55 + N8 = 56 + N9 = 57 + COLON = 58 + SEMICOLON = 59 + LESS = 60 + EQUALS = 61 + GREATER = 62 + QUESTION = 63 + AT = 64 + LEFTBRACKET = 91 + BACKSLASH = 92 + RIGHTBRACKET = 93 + CARET = 94 + UNDERSCORE = 95 + BACKQUOTE = 96 + a = 97 + b = 98 + c = 99 + d = 100 + e = 101 + f = 102 + g = 103 + h = 104 + i = 105 + j = 106 + k = 107 + l = 108 # noqa: E741 + m = 109 + n = 110 + o = 111 + p = 112 + q = 113 + r = 114 + s = 115 + t = 116 + u = 117 + v = 118 + w = 119 + x = 120 + y = 121 + z = 122 + DELETE = 127 + SCANCODE_MASK = 1073741824 + CAPSLOCK = 1073741881 + F1 = 1073741882 + F2 = 1073741883 + F3 = 1073741884 + F4 = 1073741885 + F5 = 1073741886 + F6 = 1073741887 + F7 = 1073741888 + F8 = 1073741889 + F9 = 1073741890 + F10 = 1073741891 + F11 = 1073741892 + F12 = 1073741893 + PRINTSCREEN = 1073741894 + SCROLLLOCK = 1073741895 + PAUSE = 1073741896 + INSERT = 1073741897 + HOME = 1073741898 + PAGEUP = 1073741899 + END = 1073741901 + PAGEDOWN = 1073741902 + RIGHT = 1073741903 + LEFT = 1073741904 + DOWN = 1073741905 + UP = 1073741906 + NUMLOCKCLEAR = 1073741907 + KP_DIVIDE = 1073741908 + KP_MULTIPLY = 1073741909 + KP_MINUS = 1073741910 + KP_PLUS = 1073741911 + KP_ENTER = 1073741912 + KP_1 = 1073741913 + KP_2 = 1073741914 + KP_3 = 1073741915 + KP_4 = 1073741916 + KP_5 = 1073741917 + KP_6 = 1073741918 + KP_7 = 1073741919 + KP_8 = 1073741920 + KP_9 = 1073741921 + KP_0 = 1073741922 + KP_PERIOD = 1073741923 + APPLICATION = 1073741925 + POWER = 1073741926 + KP_EQUALS = 1073741927 + F13 = 1073741928 + F14 = 1073741929 + F15 = 1073741930 + F16 = 1073741931 + F17 = 1073741932 + F18 = 1073741933 + F19 = 1073741934 + F20 = 1073741935 + F21 = 1073741936 + F22 = 1073741937 + F23 = 1073741938 + F24 = 1073741939 + EXECUTE = 1073741940 + HELP = 1073741941 + MENU = 1073741942 + SELECT = 1073741943 + STOP = 1073741944 + AGAIN = 1073741945 + UNDO = 1073741946 + CUT = 1073741947 + COPY = 1073741948 + PASTE = 1073741949 + FIND = 1073741950 + MUTE = 1073741951 + VOLUMEUP = 1073741952 + VOLUMEDOWN = 1073741953 + KP_COMMA = 1073741957 + KP_EQUALSAS400 = 1073741958 + ALTERASE = 1073741977 + SYSREQ = 1073741978 + CANCEL = 1073741979 + CLEAR = 1073741980 + PRIOR = 1073741981 + RETURN2 = 1073741982 + SEPARATOR = 1073741983 + OUT = 1073741984 + OPER = 1073741985 + CLEARAGAIN = 1073741986 + CRSEL = 1073741987 + EXSEL = 1073741988 + KP_00 = 1073742000 + KP_000 = 1073742001 + THOUSANDSSEPARATOR = 1073742002 + DECIMALSEPARATOR = 1073742003 + CURRENCYUNIT = 1073742004 + CURRENCYSUBUNIT = 1073742005 + KP_LEFTPAREN = 1073742006 + KP_RIGHTPAREN = 1073742007 + KP_LEFTBRACE = 1073742008 + KP_RIGHTBRACE = 1073742009 + KP_TAB = 1073742010 + KP_BACKSPACE = 1073742011 + KP_A = 1073742012 + KP_B = 1073742013 + KP_C = 1073742014 + KP_D = 1073742015 + KP_E = 1073742016 + KP_F = 1073742017 + KP_XOR = 1073742018 + KP_POWER = 1073742019 + KP_PERCENT = 1073742020 + KP_LESS = 1073742021 + KP_GREATER = 1073742022 + KP_AMPERSAND = 1073742023 + KP_DBLAMPERSAND = 1073742024 + KP_VERTICALBAR = 1073742025 + KP_DBLVERTICALBAR = 1073742026 + KP_COLON = 1073742027 + KP_HASH = 1073742028 + KP_SPACE = 1073742029 + KP_AT = 1073742030 + KP_EXCLAM = 1073742031 + KP_MEMSTORE = 1073742032 + KP_MEMRECALL = 1073742033 + KP_MEMCLEAR = 1073742034 + KP_MEMADD = 1073742035 + KP_MEMSUBTRACT = 1073742036 + KP_MEMMULTIPLY = 1073742037 + KP_MEMDIVIDE = 1073742038 + KP_PLUSMINUS = 1073742039 + KP_CLEAR = 1073742040 + KP_CLEARENTRY = 1073742041 + KP_BINARY = 1073742042 + KP_OCTAL = 1073742043 + KP_DECIMAL = 1073742044 + KP_HEXADECIMAL = 1073742045 + LCTRL = 1073742048 + LSHIFT = 1073742049 + LALT = 1073742050 + LGUI = 1073742051 + RCTRL = 1073742052 + RSHIFT = 1073742053 + RALT = 1073742054 + RGUI = 1073742055 + MODE = 1073742081 + AUDIONEXT = 1073742082 + AUDIOPREV = 1073742083 + AUDIOSTOP = 1073742084 + AUDIOPLAY = 1073742085 + AUDIOMUTE = 1073742086 + MEDIASELECT = 1073742087 + WWW = 1073742088 + MAIL = 1073742089 + CALCULATOR = 1073742090 + COMPUTER = 1073742091 + AC_SEARCH = 1073742092 + AC_HOME = 1073742093 + AC_BACK = 1073742094 + AC_FORWARD = 1073742095 + AC_STOP = 1073742096 + AC_REFRESH = 1073742097 + AC_BOOKMARKS = 1073742098 + BRIGHTNESSDOWN = 1073742099 + BRIGHTNESSUP = 1073742100 + DISPLAYSWITCH = 1073742101 + KBDILLUMTOGGLE = 1073742102 + KBDILLUMDOWN = 1073742103 + KBDILLUMUP = 1073742104 + EJECT = 1073742105 + SLEEP = 1073742106 + # --- end --- + + @property + def label(self) -> str: + """A human-readable name of a keycode. + + Returns "" if the keycode doesn't have a name. + + Example:: + + >>> tcod.event.KeySym.F1.label + 'F1' + >>> tcod.event.KeySym.BACKSPACE.label + 'Backspace' + + .. versionadded:: 12.3 + """ + return str( + ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8" + ) + + @property + def keysym(self) -> "KeySym": + """Return a keycode from a scancode. + + Returns itself since it is already a :class:`KeySym`. + + .. seealso:: + :any:`Scancode.keysym` + + .. versionadded:: 12.3 + """ + return self + + @property + def scancode(self) -> Scancode: + """Return a scancode from a keycode. + + Based on the current keyboard layout. + + .. versionadded:: 12.3 + """ + return Scancode(lib.SDL_GetScancodeFromKey(self.value)) __all__ = [ # noqa: F405 + "Modifier", "Point", "BUTTON_LEFT", "BUTTON_MIDDLE", @@ -1144,9 +2214,8 @@ def get_key_name(keycode: int) -> str: "EventDispatch", "get_keyboard_state", "get_modifier_state", - "key_from_scancode", - "scancode_from_key", - "get_key_name", + "Scancode", + "KeySym", # --- From event_constants.py --- "SCANCODE_UNKNOWN", "SCANCODE_A", From 6bb68afcdb69c651b6fa2638ef799df8341ad7b8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 12:30:09 -0700 Subject: [PATCH 0506/1101] Reformat build_libtcod.py. --- build_libtcod.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index bedc75a8..7aa5782d 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -186,7 +186,12 @@ def unpack_sdl2(version: str) -> str: includes = parse_includes() module_name = "tcod._libtcod" -include_dirs = [".", "libtcod/src/vendor/", "libtcod/src/vendor/utf8proc", "libtcod/src/vendor/zlib/"] +include_dirs = [ + ".", + "libtcod/src/vendor/", + "libtcod/src/vendor/utf8proc", + "libtcod/src/vendor/zlib/", +] extra_parse_args = [] extra_compile_args = [] @@ -333,9 +338,11 @@ def fix_header(filepath: str) -> None: for i, line in enumerate(include.header.split("\n"), 1): print("%03i %s" % (i, line)) raise -ffi.cdef(""" +ffi.cdef( + """ #define TCOD_COMPILEDVERSION ... -""") +""" +) ffi.set_source( module_name, "#include \n#include ", From 1304109bbc158d0f9c3a61c22e57a6408240ff8f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 16:46:39 -0700 Subject: [PATCH 0507/1101] Update keyboard state docs. --- tcod/event.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 1fda76c9..c2680c7c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1094,12 +1094,15 @@ def get_keyboard_state() -> np.ndarray: Example:: state = tcod.event.get_keyboard_state() - is_w_held = state[tcod.event.Scancode.W] - # Get a WASD vector: + # Get a WASD movement vector: x = int(state[tcod.event.Scancode.E]) - int(state[tcod.event.Scancode.A]) y = int(state[tcod.event.Scancode.S]) - int(state[tcod.event.Scancode.W]) + # Key with 'z' glyph is held: + is_z_held = state[tcod.event.KeySym.z.scancode] + + .. versionadded:: 12.3 """ numkeys = ffi.new("int[1]") From d6a59f3edee100b074cc71cd53542e4dca422b9a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 16:58:46 -0700 Subject: [PATCH 0508/1101] Add a framerate handling example. --- examples/framerate.py | 146 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 examples/framerate.py diff --git a/examples/framerate.py b/examples/framerate.py new file mode 100644 index 00000000..cb363374 --- /dev/null +++ b/examples/framerate.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# To the extent possible under law, the libtcod maintainers have waived all +# copyright and related or neighboring rights for this example. This work is +# published from: United States. +# https://creativecommons.org/publicdomain/zero/1.0/ +"""A system to control time since the original libtcod tools are deprecated. +""" +import statistics +import time +from collections import deque +from typing import Deque, List, Optional, Tuple + +import tcod + +WIDTH, HEIGHT = 720, 480 + + +class Clock: + """Measure framerate performance and sync to a given framerate. + + Everything important is handled by `Clock.sync`. You can use the fps + properties to track the performance of an application. + """ + + def __init__(self) -> None: + self.last_time = time.perf_counter() # Last time this was synced. + self.time_samples: Deque[float] = deque() # Delta time samples. + self.max_samples = 64 # Number of fps samples to log. Can be changed. + self.drift_time = 0.0 # Tracks how much the last frame was overshot. + + def sync(self, fps: Optional[float] = None) -> float: + """Sync to a given framerate and return the delta time. + + `fps` is the desired framerate in frames-per-second. If None is given + then this function will track the time and framerate without waiting. + + `fps` must be above zero when given. + """ + if fps is not None: + # Wait until a target time based on the last time and framerate. + desired_frame_time = 1 / fps + target_time = self.last_time + desired_frame_time - self.drift_time + # Sleep might take slightly longer than asked. + sleep_time = max(0, target_time - self.last_time - 0.001) + if sleep_time: + time.sleep(sleep_time) + # Busy wait until the target_time is reached. + while (drift_time := time.perf_counter() - target_time) < 0: + pass + self.drift_time = min(drift_time, desired_frame_time) + + # Get the delta time. + current_time = time.perf_counter() + delta_time = max(0, current_time - self.last_time) + self.last_time = current_time + + # Record the performance of the current frame. + self.time_samples.append(delta_time) + while len(self.time_samples) > self.max_samples: + self.time_samples.popleft() + + return delta_time + + @property + def min_fps(self) -> float: + """The FPS of the slowest frame.""" + try: + return 1 / max(self.time_samples) + except (ValueError, ZeroDivisionError): + return 0 + + @property + def max_fps(self) -> float: + """The FPS of the fastest frame.""" + try: + return 1 / min(self.time_samples) + except (ValueError, ZeroDivisionError): + return 0 + + @property + def mean_fps(self) -> float: + """The FPS of the sampled frames overall.""" + if not self.time_samples: + return 0 + try: + return 1 / statistics.fmean(self.time_samples) + except ZeroDivisionError: + return 0 + + @property + def median_fps(self) -> float: + """The FPS of the median frame.""" + if not self.time_samples: + return 0 + try: + return 1 / statistics.median(self.time_samples) + except ZeroDivisionError: + return 0 + + @property + def last_fps(self) -> float: + """The FPS of the most recent frame.""" + if not self.time_samples or self.time_samples[-1] == 0: + return 0 + return 1 / self.time_samples[-1] + + +def main() -> None: + """Example program for Clock.""" + # vsync is False in this example, but you'll want it to be True unless you + # need to benchmark or set framerates above 60 FPS. + with tcod.context.new(width=WIDTH, height=HEIGHT, vsync=False) as context: + line_x = 0 # Highlight a line, helpful to measure frames visually. + clock = Clock() + delta_time = 0.0 # The time passed between frames. + desired_fps = 50 + while True: + console = context.new_console(order="F") + console.rgb["bg"][line_x % console.width, :] = (255, 0, 0) + console.print( + 1, + 1, + f"Current time:{time.perf_counter() * 1000:8.2f}ms" + f"\nDelta time:{delta_time * 1000:8.2f}ms" + f"\nDesired FPS:{desired_fps:3d} (use scroll whell to adjust)" + f"\n last:{clock.last_fps:.2f} fps" + f"\n mean:{clock.mean_fps:.2f} fps" + f"\nmedian:{clock.median_fps:.2f} fps" + f"\n min:{clock.min_fps:.2f} fps" + f"\n max:{clock.max_fps:.2f} fps", + ) + context.present(console, integer_scaling=True) + delta_time = clock.sync(fps=desired_fps) + line_x += 1 + + # Handle events. + for event in tcod.event.get(): + context.convert_event(event) # Set tile coordinates for event. + if isinstance(event, tcod.event.Quit): + raise SystemExit() + elif isinstance(event, tcod.event.MouseWheel): + desired_fps = max(1, desired_fps + event.y) + + +if __name__ == "__main__": + main() From f63883317f81112efa282378cbf00a12ea94491b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 17:28:54 -0700 Subject: [PATCH 0509/1101] Add a frequently asked questions section to the docs. --- docs/faq.rst | 10 ++++++++++ docs/index.rst | 5 +---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 docs/faq.rst diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 00000000..0981c29c --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,10 @@ +Frequently Asked Questions +========================== + +How do you set a frames-per-second while using contexts? +-------------------------------------------------------- + +You'll need to use an external tool to manage the framerate. +This can either be your own custom tool or you can copy the Clock class from the +`framerate.py `_ +example. diff --git a/docs/index.rst b/docs/index.rst index b4cf4a0b..1ae35b97 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,11 +17,8 @@ Contents: installation glossary - -.. toctree:: - :maxdepth: 1 - changelog + faq .. toctree:: :maxdepth: 2 From 39007c94079d0386e57ec2bf9ee4c28ad6e79dfc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 20:12:57 -0700 Subject: [PATCH 0510/1101] Use tiles_rgb in example. The alternative attribute name isn't available yet. --- examples/framerate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/framerate.py b/examples/framerate.py index cb363374..1e116e9e 100644 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -116,7 +116,7 @@ def main() -> None: desired_fps = 50 while True: console = context.new_console(order="F") - console.rgb["bg"][line_x % console.width, :] = (255, 0, 0) + console.tiles_rgb["bg"][line_x % console.width, :] = (255, 0, 0) console.print( 1, 1, From b5081ef344680af8748300bb084eba836de25e6b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 9 May 2021 20:29:17 -0700 Subject: [PATCH 0511/1101] Update and add Console dtypes. --- CHANGELOG.rst | 1 + tcod/console.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d3d6526b..7533ca04 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ Added - New functions: - `tcod.event.get_keyboard_state` - `tcod.event.get_modifier_state` + - Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. Fixed - Contexts now give a more useful error when pickled. diff --git a/tcod/console.py b/tcod/console.py index 30acce12..17e724c2 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -23,6 +23,17 @@ def _fmt(string: str) -> bytes: _root_console = None +rgba_graphic = np.dtype([("ch", np.intc), ("fg", "4u1"), ("bg", "4u1")]) +"""A NumPy :any:`dtype` compatible with :any:`Console.buffer`. + +.. versionadded:: 12.3 +""" + +rgb_graphic = np.dtype([("ch", np.intc), ("fg", "3u1"), ("bg", "3u1")]) +"""A NumPy :any:`dtype` compatible with :any:`Console.tiles_rgb`. + +.. versionadded:: 12.3 +""" class Console: """A console object containing a grid of characters with @@ -77,13 +88,13 @@ class Console: Added an alpha channel to the color types. """ - DTYPE = np.dtype([("ch", np.intc), ("fg", "(4,)u1"), ("bg", "(4,)u1")]) + DTYPE = np.dtype([("ch", np.intc), ("fg", "4u1"), ("bg", "4u1")]) # A structured arrays type with the added "fg_rgb" and "bg_rgb" fields. _DTYPE_RGB = np.dtype( { "names": ["ch", "fg", "bg"], - "formats": [np.int32, "(3,)u1", "(3,)u1"], + "formats": [np.int32, "3u1", "3u1"], "offsets": [0, 4, 8], "itemsize": 12, } From 4dc1b3d79dad471dbe52ff61502999b15a424b7f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 May 2021 14:57:21 -0700 Subject: [PATCH 0512/1101] Fix frame drawing regressions. --- CHANGELOG.rst | 2 ++ libtcod | 2 +- tcod/console.py | 9 ++++++--- tcod/libtcodpy.py | 6 +++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7533ca04..c3ce4868 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,8 @@ Added Fixed - Contexts now give a more useful error when pickled. + - Fixed regressions with `tcod.console_print_frame` and `Console.print_frame` + when given empty strings as the banner. 12.2.0 - 2021-04-09 ------------------- diff --git a/libtcod b/libtcod index b0f84018..1e80845d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit b0f8401863882516f9bf0657a656343d258a97b6 +Subproject commit 1e80845df8e36fa91e27347ebb1dbb9aed7b840c diff --git a/tcod/console.py b/tcod/console.py index 17e724c2..d59b5796 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -12,7 +12,7 @@ import tcod._internal import tcod.constants -from tcod._internal import deprecate +from tcod._internal import _check, deprecate from tcod.loader import ffi, lib @@ -35,6 +35,7 @@ def _fmt(string: str) -> bytes: .. versionadded:: 12.3 """ + class Console: """A console object containing a grid of characters with foreground/background colors. @@ -723,8 +724,10 @@ def print_frame( """ self.__deprecate_defaults("draw_frame", bg_blend) string = _fmt(string) if string else ffi.NULL - lib.TCOD_console_printf_frame( - self.console_c, x, y, width, height, clear, bg_blend, string + _check( + lib.TCOD_console_printf_frame( + self.console_c, x, y, width, height, clear, bg_blend, string + ) ) def blit( diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index e43721d8..12b908a8 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1685,7 +1685,11 @@ def console_print_frame( Use :any:`Console.print_frame` instead. """ fmt = _fmt(fmt) if fmt else ffi.NULL - lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt) + _check( + lib.TCOD_console_printf_frame( + _console(con), x, y, w, h, clear, flag, fmt + ) + ) @pending_deprecate() From e4f0faa44637cd5fdb2e5bd61f3e31d44dde8f35 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 May 2021 15:32:01 -0700 Subject: [PATCH 0513/1101] Officially add the new rgb and rgba attributes. --- CHANGELOG.rst | 5 +++ tcod/console.py | 89 ++++++++++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c3ce4868..c5a96fe9 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,11 @@ Added - `tcod.event.get_keyboard_state` - `tcod.event.get_modifier_state` - Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. + - Another name for the Console array attributes: `Console.rgb` and `Console.rgba`. + +Deprecated + - `Console_tiles_rgb` is being renamed to `Console.rgb`. + - `Console_tiles` being renamed to `Console.rgba`. Fixed - Contexts now give a more useful error when pickled. diff --git a/tcod/console.py b/tcod/console.py index d59b5796..3cfa0389 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -240,7 +240,8 @@ def ch(self) -> np.ndarray: """ return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] # type: ignore - @property + @property # type: ignore + @deprecate("This attribute has been renamed to `rgba`.") def tiles(self) -> np.ndarray: """An array of this consoles raw tile data. @@ -248,73 +249,85 @@ def tiles(self) -> np.ndarray: Colors include an alpha channel but how alpha works is currently undefined. - Example: - >>> con = tcod.console.Console(10, 2) - >>> con.tiles[0, 0] = ( - ... ord("X"), - ... (*tcod.white, 255), - ... (*tcod.black, 255), - ... ) - >>> con.tiles[0, 0] - (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) - .. versionadded:: 10.0 + + .. deprecated:: 12.3 + Use :any:`Console.rgba` instead. """ - return self._tiles.T if self._order == "F" else self._tiles + return self.rgba @property # type: ignore - @deprecate("This attribute has been renamed to `tiles`.") + @deprecate("This attribute has been renamed to `rgba`.") def buffer(self) -> np.ndarray: """An array of this consoles raw tile data. .. versionadded:: 11.4 .. deprecated:: 11.8 - Use :any:`Console.tiles` instead. + Use :any:`Console.rgba` instead. """ - return self._tiles.T if self._order == "F" else self._tiles + return self.rgba - @property + @property # type: ignore + @deprecate("This attribute has been renamed to `rgb`.") def tiles_rgb(self) -> np.ndarray: - """An array of this consoles tile data without the alpha channel. - - The dtype of this array is effectively: - ``[("ch", np.intc), ("fg", "(3,)u1"), ("bg", "(3,)u1")]`` - - Example: - >>> con = tcod.console.Console(10, 2) - >>> con.tiles_rgb[0, 0] = ord("@"), tcod.yellow, tcod.black - >>> con.tiles_rgb[0, 0] - (64, [255, 255, 0], [0, 0, 0]) - >>> con.tiles_rgb["bg"] = tcod.blue - >>> con.tiles_rgb[0, 0] - (64, [255, 255, 0], [ 0, 0, 255]) + """An array of this consoles data without the alpha channel. .. versionadded:: 11.8 + + .. deprecated:: 12.3 + Use :any:`Console.rgb` instead. """ - return self.tiles.view(self._DTYPE_RGB) + return self.rgb @property # type: ignore - @deprecate("This attribute has been renamed to `tiles_rgb`.") + @deprecate("This attribute has been renamed to `rgb`.") def tiles2(self) -> np.ndarray: - """This name is deprecated in favour of :any:`tiles_rgb`. + """This name is deprecated in favour of :any:`rgb`. .. versionadded:: 11.3 .. deprecated:: 11.8 - Use :any:`Console.tiles_rgb` instead. + Use :any:`Console.rgb` instead. """ - return self.tiles_rgb + return self.rgb @property def rgba(self) -> np.ndarray: - # This attribute is provisional and may be changed at anytime. - return self.tiles + """An array of this consoles raw tile data. + + Example: + >>> con = tcod.console.Console(10, 2) + >>> con.rgba[0, 0] = ( + ... ord("X"), + ... (*tcod.white, 255), + ... (*tcod.black, 255), + ... ) + >>> con.rgba[0, 0] + (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) + + .. versionadded:: 12.3 + """ + return self._tiles.T if self._order == "F" else self._tiles @property def rgb(self) -> np.ndarray: - # This attribute is provisional and may be changed at anytime. - return self.tiles_rgb + """An array of this consoles data without the alpha channel. + + The :any:`rgb_graphic` can be used to make arrays + + Example: + >>> con = tcod.console.Console(10, 2) + >>> con.rgb[0, 0] = ord("@"), tcod.yellow, tcod.black + >>> con.rgb[0, 0] + (64, [255, 255, 0], [0, 0, 0]) + >>> con.rgb["bg"] = tcod.blue + >>> con.rgb[0, 0] + (64, [255, 255, 0], [ 0, 0, 255]) + + .. versionadded:: 12.3 + """ + return self.rgba.view(self._DTYPE_RGB) @property def default_bg(self) -> Tuple[int, int, int]: From 3a3d746f4ee6a99ec0ccd0a303e440f68c1b8bf3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 May 2021 17:20:30 -0700 Subject: [PATCH 0514/1101] Prevent equality testing between Scancode and KeySym classes. This would be bad for obvious reasons, but automatically converting them would cause hashing issues as well. --- tcod/event.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tcod/event.py b/tcod/event.py index c2680c7c..1af012c9 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1655,6 +1655,14 @@ def scancode(self) -> "Scancode": """ return self + def __eq__(self, other: Any) -> bool: + if isinstance(other, KeySym): + raise TypeError( + "Scancode and KeySym enums can not be compared directly." + " Convert one or the other to the same type." + ) + return super().__eq__(other) + class KeySym(enum.IntEnum): """Key syms @@ -2182,6 +2190,14 @@ def scancode(self) -> Scancode: """ return Scancode(lib.SDL_GetScancodeFromKey(self.value)) + def __eq__(self, other: Any) -> bool: + if isinstance(other, Scancode): + raise TypeError( + "Scancode and KeySym enums can not be compared directly." + " Convert one or the other to the same type." + ) + return super().__eq__(other) + __all__ = [ # noqa: F405 "Modifier", From d1f37dfd6e5f95cb3396fca8c114de0c4ff2cae3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 May 2021 12:56:11 -0700 Subject: [PATCH 0515/1101] Allow keyboard conversions before video is initialized. Update docs for key enums. --- tcod/event.py | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 1af012c9..77e0f64c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -109,8 +109,23 @@ def _verify_tile_coordinates(xy: Optional[Point]) -> Point: return Point(0, 0) +_is_sdl_video_initialized = False + + +def _init_sdl_video() -> None: + """Keyboard layout stuff needs SDL to be initialized first.""" + global _is_sdl_video_initialized + if _is_sdl_video_initialized: + return + lib.SDL_InitSubSystem(lib.SDL_INIT_VIDEO) + _is_sdl_video_initialized = True + + class Modifier(enum.IntFlag): - """Keyboard modifiers.""" + """Keyboard modifier flags. + + .. versionadded:: 12.3 + """ NONE = 0 LSHIFT = 1 @@ -1131,6 +1146,8 @@ class Scancode(enum.IntEnum): These names are derived from SDL expect for the numbers which are prefixed with ``N`` (since raw numbers can not be a Python name.) + .. versionadded:: 12.3 + ================== === UNKNOWN 0 A 4 @@ -1623,12 +1640,13 @@ class Scancode(enum.IntEnum): @property def label(self) -> str: - """Return a human-readable name of a key based on a scancode. + """Return a human-readable name of a key based on its scancode. + + Be sure not to confuse this with ``.name``, which will return the enum + name rather than the human-readable name. .. seealso:: :any:`KeySym.label` - - .. versionadded:: 12.3 """ return self.keysym.label @@ -1637,9 +1655,8 @@ def keysym(self) -> "KeySym": """Return a :class:`KeySym` from a scancode. Based on the current keyboard layout. - - .. versionadded:: 12.3 """ + _init_sdl_video() return KeySym(lib.SDL_GetKeyFromScancode(self.value)) @property @@ -1650,8 +1667,6 @@ def scancode(self) -> "Scancode": .. seealso:: :any:`KeySym.scancode` - - .. versionadded:: 12.3 """ return self @@ -1665,7 +1680,12 @@ def __eq__(self, other: Any) -> bool: class KeySym(enum.IntEnum): - """Key syms + """Keyboard constants based on their symbol. + + These names are derived from SDL expect for the numbers which are prefixed + with ``N`` (since raw numbers can not be a Python name.) + + .. versionadded:: 12.3 ================== ========== UNKNOWN 0 @@ -2154,14 +2174,15 @@ def label(self) -> str: Returns "" if the keycode doesn't have a name. + Be sure not to confuse this with ``.name``, which will return the enum + name rather than the human-readable name. + Example:: >>> tcod.event.KeySym.F1.label 'F1' >>> tcod.event.KeySym.BACKSPACE.label 'Backspace' - - .. versionadded:: 12.3 """ return str( ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8" @@ -2175,8 +2196,6 @@ def keysym(self) -> "KeySym": .. seealso:: :any:`Scancode.keysym` - - .. versionadded:: 12.3 """ return self @@ -2185,9 +2204,8 @@ def scancode(self) -> Scancode: """Return a scancode from a keycode. Based on the current keyboard layout. - - .. versionadded:: 12.3 """ + _init_sdl_video() return Scancode(lib.SDL_GetScancodeFromKey(self.value)) def __eq__(self, other: Any) -> bool: From c48a09a995cbd35a7b02742199c29d0d64550546 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 May 2021 16:04:19 -0700 Subject: [PATCH 0516/1101] Update libtcod. --- CHANGELOG.rst | 3 +++ libtcod | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5a96fe9..afdfec5f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,9 @@ Added - Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. - Another name for the Console array attributes: `Console.rgb` and `Console.rgba`. +Changed + - Using `libtcod 1.17.0`. + Deprecated - `Console_tiles_rgb` is being renamed to `Console.rgb`. - `Console_tiles` being renamed to `Console.rgba`. diff --git a/libtcod b/libtcod index 1e80845d..4616313d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 1e80845df8e36fa91e27347ebb1dbb9aed7b840c +Subproject commit 4616313df50e35b1b0593101c56f73052f183a8b From 6be33d934cf33c2df04f9c7d28bd7fd99c5caefe Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 May 2021 16:08:35 -0700 Subject: [PATCH 0517/1101] Prepare 12.3.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index afdfec5f..749d1b66 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.3.0 - 2021-05-13 +------------------- Added - New keyboard enums: - `tcod.event.KeySym` From 1a5535dede7c5bf4991bd412ffb07572eaedb6b7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 May 2021 16:45:47 -0700 Subject: [PATCH 0518/1101] Fix Windows deployment. Twine was not being installed in Windows deployment workflows. --- .github/workflows/python-package-macos.yml | 13 +++++-------- .github/workflows/python-package.yml | 9 ++++----- CHANGELOG.rst | 2 ++ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 0afe99d5..36a03334 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -16,12 +16,9 @@ jobs: python-version: ['3.7.9'] steps: - - name: Clone ${{github.repository}} - # actions/checkout breaks `git describe` so a manual clone is needed. - # https://github.com/actions/checkout/issues/272 - run: | - git clone https://github.com/${{github.repository}}.git ${{github.workspace}} - git checkout ${{github.sha}} + # v2 breaks `git describe` so v1 is needed. + # https://github.com/actions/checkout/issues/272 + - uses: actions/checkout@v1 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -44,8 +41,8 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest pytest-cov pytest-benchmark wheel twine - python -m pip install git+https://github.com/HexDecimal/delocate.git@loader_path + pip install pytest pytest-cov pytest-benchmark wheel twine + pip install git+https://github.com/HexDecimal/delocate.git@loader_path if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Build package. run: | diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d3ac1b02..6feaaa84 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -23,10 +23,9 @@ jobs: fail-fast: false steps: - - name: Checkout code - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - uses: actions/checkout@v1 + # v2 breaks `git describe` so v1 is needed. + # https://github.com/actions/checkout/issues/272 + - uses: actions/checkout@v1 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -42,7 +41,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install pytest pytest-cov pytest-benchmark wheel + pip install pytest pytest-cov pytest-benchmark wheel twine if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 749d1b66..f2d48312 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fix Windows deployment. 12.3.0 - 2021-05-13 ------------------- From c99fb9ce3b47e011ba45340b7cf24aabf32616ea Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 May 2021 16:49:28 -0700 Subject: [PATCH 0519/1101] Prepare 12.3.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f2d48312..cb48cc8a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.3.1 - 2021-05-13 +------------------- Fixed - Fix Windows deployment. From 802a666ba7eeff5b892fc6ea4f8b543bfb5b3b5e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 15 May 2021 16:42:29 -0700 Subject: [PATCH 0520/1101] Prepare 12.3.2 release. --- CHANGELOG.rst | 8 ++++++++ libtcod | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cb48cc8a..af05b5ef 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,14 @@ v2.0.0 Unreleased ------------------ +12.3.2 - 2021-05-15 +------------------- +Changed + - Using `libtcod 1.17.1`. + +Fixed + - Fixed regression with loading PNG images. + 12.3.1 - 2021-05-13 ------------------- Fixed diff --git a/libtcod b/libtcod index 4616313d..125e23dd 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 4616313df50e35b1b0593101c56f73052f183a8b +Subproject commit 125e23dde424bd3432b79e4eee62d7ce6e01f7e4 From 3fe86a51277ed396c0155eb8803e40cb364029bc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 17 May 2021 13:15:08 -0700 Subject: [PATCH 0521/1101] Add modern REXPaint load/save functions. --- CHANGELOG.rst | 7 +++ libtcod | 2 +- tcod/console.py | 104 +++++++++++++++++++++++++++++++++++++++++- tcod/libtcodpy.py | 6 ++- tests/test_console.py | 12 +++++ 5 files changed, 127 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index af05b5ef..14fc320a 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,13 @@ v2.0.0 Unreleased ------------------ +Added + - Added modernized REXPaint saving/loading functions. + - `tcod.console.load_xp` + - `tcod.console.save_xp` + +Changed + - Using `libtcod 1.18.0`. 12.3.2 - 2021-05-15 ------------------- diff --git a/libtcod b/libtcod index 125e23dd..d96ec803 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 125e23dde424bd3432b79e4eee62d7ce6e01f7e4 +Subproject commit d96ec80309faac56f0c9d0982c5322fad453a11a diff --git a/tcod/console.py b/tcod/console.py index 3cfa0389..e17e5a5c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -3,9 +3,10 @@ To render a console you need a tileset and a window to render to. See :ref:`getting-started` for info on how to set those up. """ - +import os +import pathlib import warnings -from typing import Any, Optional, Tuple, Union # noqa: F401 +from typing import Any, Iterable, Optional, Tuple, Union import numpy as np from typing_extensions import Literal @@ -1229,3 +1230,102 @@ def recommended_size() -> Tuple[int, int]: w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width) h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height) return w, h + + +def load_xp( + path: Union[str, pathlib.Path], order: Literal["C", "F"] = "C" +) -> Tuple[Console, ...]: + """Load a REXPaint file as a tuple of consoles. + + `path` is the name of the REXPaint file to load. + Usually ending with `.xp`. + + `order` is the memory order of the Console's array buffer, + see :any:`tcod.console.Console`. + + .. versionadded:: 12.4 + + Example:: + import tcod + from numpy import np + + path = "example.xp" # REXPaint file with one layer. + + # Load a REXPaint file with a single layer. + # The comma after console is used to unpack a single item tuple. + console, = tcod.console.load_xp(path, order="F") + + # Convert from REXPaint's encoding to Unicode. + CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) + console.ch[:] = CP437_TO_UNICODE[console.ch] + + # Apply REXPaint's alpha key color. + KEY_COLOR = (255, 0, 255) + is_transparent = console.rgb["bg"] == KEY_COLOR + console.rgba[is_transparent] = (ord(" "), (0,), (0,)) + """ + if not os.path.exists(path): + raise FileNotFoundError(f"File not found:\n\t{os.path.abspath(path)}") + layers = _check( + tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), 0, ffi.NULL) + ) + consoles = ffi.new("TCOD_Console*[]", layers) + _check(tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), layers, consoles)) + return tuple( + Console._from_cdata(console_p, order=order) for console_p in consoles + ) + + +def save_xp( + path: Union[str, pathlib.Path], + consoles: Iterable[Console], + compress_level: int = 9, +) -> None: + """Save tcod Consoles to a REXPaint file. + + `path` is where to save the file. + + `consoles` are the :any:`tcod.console.Console` objects to be saved. + + `compress_level` is the zlib compression level to be used. + + Color alpha will be lost during saving. + + Consoles will be saved as-is as much as possible. You may need to convert + characters from Unicode to CP437 if you want to load the file in REXPaint. + + .. versionadded:: 12.4 + + Example:: + import tcod + from numpy import np + + console = tcod.Console(80, 24) # Example console. + + # Load a REXPaint file with a single layer. + # The comma after console is used to unpack a single item tuple. + console, = tcod.console.load_xp(path, order="F") + + # Convert from Unicode to REXPaint's encoding. + # Required to load this console correctly in the REXPaint tool. + CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) + UNICODE_TO_CP437 = np.full(0x1FFFF, fill_value=ord("?")) + UNICODE_TO_CP437[CP437_TO_UNICODE] = np.arange(len(CP437_TO_UNICODE)) + console.ch[:] = UNICODE_TO_CP437[console.ch] + + # Convert console alpha into REXPaint's alpha key color. + KEY_COLOR = (255, 0, 255) + is_transparent = console.rgba["bg"][:, :, 3] == 0 + console.rgb["bg"][is_transparent] = KEY_COLOR + + tcod.console.save_xp("example.xp", [console]) + """ + consoles_c = ffi.new("TCOD_Console*[]", [c.console_c for c in consoles]) + _check( + tcod.lib.TCOD_save_xp( + len(consoles_c), + consoles_c, + str(path).encode("utf-8"), + compress_level, + ) + ) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 12b908a8..0d83b3ec 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2078,7 +2078,7 @@ def console_save_apf(con: tcod.console.Console, filename: str) -> bool: ) -@deprecate("Use tcod.console_from_xp to load this file.") +@deprecate("Use tcod.console.load_xp to load this file.") def console_load_xp(con: tcod.console.Console, filename: str) -> bool: """Update a console from a REXPaint `.xp` file. @@ -2091,6 +2091,7 @@ def console_load_xp(con: tcod.console.Console, filename: str) -> bool: ) +@deprecate("Use tcod.console.save_xp to save this console.") def console_save_xp( con: tcod.console.Console, filename: str, compress_level: int = 9 ) -> bool: @@ -2102,6 +2103,7 @@ def console_save_xp( ) +@deprecate("Use tcod.console.load_xp to load this file.") def console_from_xp(filename: str) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file.""" if not os.path.exists(filename): @@ -2113,6 +2115,7 @@ def console_from_xp(filename: str) -> tcod.console.Console: ) +@deprecate("Use tcod.console.load_xp to load this file.") def console_list_load_xp( filename: str, ) -> Optional[List[tcod.console.Console]]: @@ -2136,6 +2139,7 @@ def console_list_load_xp( lib.TCOD_list_delete(tcod_list) +@deprecate("Use tcod.console.save_xp to save these consoles.") def console_list_save_xp( console_list: Sequence[tcod.console.Console], filename: str, diff --git a/tests/test_console.py b/tests/test_console.py index 60b85cc1..9605357b 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -129,3 +129,15 @@ def test_console_semigraphics(): console.draw_semigraphics( [[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]], ) + +def test_rexpaint(tmp_path) -> None: + xp_path = tmp_path / "test.xp" + consoles = tcod.Console(80, 24, order="F"), tcod.Console(8, 8, order="F") + tcod.console.save_xp(xp_path, consoles, compress_level=0) + loaded = tcod.console.load_xp(xp_path, order="F") + assert len(consoles) == len(loaded) + assert loaded[0].rgba.flags["F_CONTIGUOUS"] + assert consoles[0].rgb.shape == loaded[0].rgb.shape + assert consoles[1].rgb.shape == loaded[1].rgb.shape + with pytest.raises(FileNotFoundError): + tcod.console.load_xp(tmp_path / "non_existant") From e0cd96db1d5448a434f2542dcc93e147c69352d7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 20 May 2021 21:58:56 -0700 Subject: [PATCH 0522/1101] Fix enum hashing issues. These were always supposed to be hashable, but the equality check to prevent comparing enums of different types caused the hash function to be undefined. --- CHANGELOG.rst | 3 +++ tcod/event.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 14fc320a..e55911c2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,9 @@ Added Changed - Using `libtcod 1.18.0`. +Fixed + - `tcod.event.KeySym` and `tcod.event.Scancode ` can now be hashed. + 12.3.2 - 2021-05-15 ------------------- Changed diff --git a/tcod/event.py b/tcod/event.py index 77e0f64c..03becfa6 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1678,6 +1678,10 @@ def __eq__(self, other: Any) -> bool: ) return super().__eq__(other) + def __hash__(self) -> int: + # __eq__ was defined, so __hash__ must be defined. + return super().__hash__() + class KeySym(enum.IntEnum): """Keyboard constants based on their symbol. @@ -2216,6 +2220,10 @@ def __eq__(self, other: Any) -> bool: ) return super().__eq__(other) + def __hash__(self) -> int: + # __eq__ was defined, so __hash__ must be defined. + return super().__hash__() + __all__ = [ # noqa: F405 "Modifier", From 8ddd70c5e0563e77dfaff3a108c582abb0d760bf Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 07:58:49 -0700 Subject: [PATCH 0523/1101] Prepare 12.4.0 release. --- CHANGELOG.rst | 5 ++++- libtcod | 2 +- tcod/console.py | 6 ++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e55911c2..f019c096 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,13 +8,16 @@ v2.0.0 Unreleased ------------------ + +12.4.0 - 2021-05-21 +------------------- Added - Added modernized REXPaint saving/loading functions. - `tcod.console.load_xp` - `tcod.console.save_xp` Changed - - Using `libtcod 1.18.0`. + - Using `libtcod 1.18.1`. Fixed - `tcod.event.KeySym` and `tcod.event.Scancode ` can now be hashed. diff --git a/libtcod b/libtcod index d96ec803..40cfd7c4 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit d96ec80309faac56f0c9d0982c5322fad453a11a +Subproject commit 40cfd7c47bbd12b5f0845befbe9a156c5514e233 diff --git a/tcod/console.py b/tcod/console.py index e17e5a5c..8bce85e3 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1257,6 +1257,7 @@ def load_xp( # Convert from REXPaint's encoding to Unicode. CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) + console.ch[:] = CP437_TO_UNICODE[console.ch] # Apply REXPaint's alpha key color. @@ -1302,15 +1303,12 @@ def save_xp( console = tcod.Console(80, 24) # Example console. - # Load a REXPaint file with a single layer. - # The comma after console is used to unpack a single item tuple. - console, = tcod.console.load_xp(path, order="F") - # Convert from Unicode to REXPaint's encoding. # Required to load this console correctly in the REXPaint tool. CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) UNICODE_TO_CP437 = np.full(0x1FFFF, fill_value=ord("?")) UNICODE_TO_CP437[CP437_TO_UNICODE] = np.arange(len(CP437_TO_UNICODE)) + console.ch[:] = UNICODE_TO_CP437[console.ch] # Convert console alpha into REXPaint's alpha key color. From 4615008c074d5b8f3fc55203bcd79bc3f0d1d978 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 11:16:29 -0700 Subject: [PATCH 0524/1101] Fix example blocks formatting. I always forget to add the extra blank line here. --- tcod/console.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tcod/console.py b/tcod/console.py index 8bce85e3..00f2953c 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1246,6 +1246,7 @@ def load_xp( .. versionadded:: 12.4 Example:: + import tcod from numpy import np @@ -1298,6 +1299,7 @@ def save_xp( .. versionadded:: 12.4 Example:: + import tcod from numpy import np From 8b9c6f3c2819517335709260f35ce87021bf6a8a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 12:08:03 -0700 Subject: [PATCH 0525/1101] Update console docs. --- tcod/console.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 00f2953c..5a9c60e8 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -43,9 +43,12 @@ class Console: `width` and `height` are the size of the console (in tiles.) - `order` determines how the axes of NumPy array attributes are arraigned. + `order` determines how the axes of NumPy array attributes are arranged. + With the default setting of `"C"` you use [y, x] to index a consoles + arrays such as :any:`Console.rgb`. `order="F"` will swap the first two axes which allows for more intuitive - `[x, y]` indexing. + `[x, y]` indexing. To be consistent you may have to do this for every + NumPy array to create. With `buffer` the console can be initialized from another array. The `buffer` should be compatible with the `width`, `height`, and `order` @@ -297,6 +300,9 @@ def tiles2(self) -> np.ndarray: def rgba(self) -> np.ndarray: """An array of this consoles raw tile data. + The axes of this array is affected by the `order` parameter given to + initialize the console. + Example: >>> con = tcod.console.Console(10, 2) >>> con.rgba[0, 0] = ( @@ -315,7 +321,11 @@ def rgba(self) -> np.ndarray: def rgb(self) -> np.ndarray: """An array of this consoles data without the alpha channel. - The :any:`rgb_graphic` can be used to make arrays + The axes of this array is affected by the `order` parameter given to + initialize the console. + + The :any:`rgb_graphic` can be used to make arrays similar to these + independent of a :any:`Console`. Example: >>> con = tcod.console.Console(10, 2) @@ -1256,9 +1266,10 @@ def load_xp( # The comma after console is used to unpack a single item tuple. console, = tcod.console.load_xp(path, order="F") - # Convert from REXPaint's encoding to Unicode. + # Convert tcod's Code Page 437 character mapping into a NumPy array. CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) + # Convert from REXPaint's CP437 encoding to Unicode in-place. console.ch[:] = CP437_TO_UNICODE[console.ch] # Apply REXPaint's alpha key color. @@ -1307,10 +1318,18 @@ def save_xp( # Convert from Unicode to REXPaint's encoding. # Required to load this console correctly in the REXPaint tool. + + # Convert tcod's Code Page 437 character mapping into a NumPy array. CP437_TO_UNICODE = np.asarray(tcod.tileset.CHARMAP_CP437) - UNICODE_TO_CP437 = np.full(0x1FFFF, fill_value=ord("?")) + + # Initialize a Unicode-to-CP437 array. + # 0x20000 is the current full range of Unicode. + # fill_value=ord("?") means that "?" will be the result of any unknown codepoint. + UNICODE_TO_CP437 = np.full(0x20000, fill_value=ord("?")) + # Assign the CP437 mappings. UNICODE_TO_CP437[CP437_TO_UNICODE] = np.arange(len(CP437_TO_UNICODE)) + # Convert from Unicode to CP437 in-place. console.ch[:] = UNICODE_TO_CP437[console.ch] # Convert console alpha into REXPaint's alpha key color. From 916bfbb443894d590b66fd35ce3bfebcff851820 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 12:26:51 -0700 Subject: [PATCH 0526/1101] Add better VSCode support. Fix several warnings and some documentation. --- .vscode/settings.json | 19 +++++++++++++++++++ examples/cavegen.py | 8 ++++---- examples/framerate.py | 2 +- tcod/console.py | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6463c5e6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "editor.rulers": [ + 79, + 120 + ], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + "python.linting.enabled": true, + "python.linting.flake8Enabled": true, + "python.linting.mypyEnabled": true, + "python.linting.mypyArgs": [ + "--ignore-missing-imports", + "--follow-imports=silent", + "--show-column-numbers" + ], + "python.formatting.provider": "black" +} diff --git a/examples/cavegen.py b/examples/cavegen.py index 43d2e807..6b26ed48 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -6,11 +6,11 @@ This will print the result to the console, so be sure to run this from the command line. """ -import numpy as np # type: ignore -import scipy.signal # type: ignore +import numpy as np +import scipy.signal -def convolve(tiles: np.array, wall_rule: int = 5) -> np.array: +def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: """Return the next step of the cave generation algorithm. `tiles` is the input array. (0: wall, 1: floor) @@ -25,7 +25,7 @@ def convolve(tiles: np.array, wall_rule: int = 5) -> np.array: return neighbors < wall_rule # Apply the wall rule. -def show(tiles: np.array) -> None: +def show(tiles: np.ndarray) -> None: """Print out the tiles of an array.""" for line in tiles: print("".join("# "[int(cell)] for cell in line)) diff --git a/examples/framerate.py b/examples/framerate.py index 1e116e9e..fb08917c 100644 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -8,7 +8,7 @@ import statistics import time from collections import deque -from typing import Deque, List, Optional, Tuple +from typing import Deque, Optional import tcod diff --git a/tcod/console.py b/tcod/console.py index 5a9c60e8..b263dd44 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1257,8 +1257,8 @@ def load_xp( Example:: + import numpy as np import tcod - from numpy import np path = "example.xp" # REXPaint file with one layer. @@ -1311,8 +1311,8 @@ def save_xp( Example:: + import numpy as np import tcod - from numpy import np console = tcod.Console(80, 24) # Example console. From 8771ee07da985ca3f533fb96fd380804e0222d10 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 13:31:18 -0700 Subject: [PATCH 0527/1101] Resolve remaining VSCode/MyPy issues and fix more warnings. --- .vscode/settings.json | 2 +- examples/cavegen.py | 7 ++--- examples/samples_tcod.py | 58 ++++++++++++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6463c5e6..7685f2b6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,7 @@ "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.mypyArgs": [ - "--ignore-missing-imports", + "--python-version 3.8", // Python 3.8 supported by examples. "--follow-imports=silent", "--show-column-numbers" ], diff --git a/examples/cavegen.py b/examples/cavegen.py index 6b26ed48..92544765 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -7,7 +7,7 @@ command line. """ import numpy as np -import scipy.signal +import scipy.signal # type: ignore def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: @@ -19,10 +19,11 @@ def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: walls then the tile will become a wall. """ # Use convolve2d, the 2nd input is a 3x3 ones array. - neighbors = scipy.signal.convolve2d( + neighbors: np.ndarray = scipy.signal.convolve2d( tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same" ) - return neighbors < wall_rule # Apply the wall rule. + next_tiles: np.ndarray = neighbors < wall_rule # Apply the wall rule. + return next_tiles def show(tiles: np.ndarray) -> None: diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index ec37c7d6..92f662fb 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -348,15 +348,51 @@ def on_draw(self) -> None: class NoiseSample(Sample): NOISE_OPTIONS = [ # (name, algorithm, implementation) - ("perlin noise", tcod.NOISE_PERLIN, tcod.noise.SIMPLE), - ("simplex noise", tcod.NOISE_SIMPLEX, tcod.noise.SIMPLE), - ("wavelet noise", tcod.NOISE_WAVELET, tcod.noise.SIMPLE), - ("perlin fbm", tcod.NOISE_PERLIN, tcod.noise.FBM), - ("perlin turbulence", tcod.NOISE_PERLIN, tcod.noise.TURBULENCE), - ("simplex fbm", tcod.NOISE_SIMPLEX, tcod.noise.FBM), - ("simplex turbulence", tcod.NOISE_SIMPLEX, tcod.noise.TURBULENCE), - ("wavelet fbm", tcod.NOISE_WAVELET, tcod.noise.FBM), - ("wavelet turbulence", tcod.NOISE_WAVELET, tcod.noise.TURBULENCE), + ( + "perlin noise", + tcod.noise.Algorithm.PERLIN, + tcod.noise.Implementation.SIMPLE, + ), + ( + "simplex noise", + tcod.noise.Algorithm.SIMPLEX, + tcod.noise.Implementation.SIMPLE, + ), + ( + "wavelet noise", + tcod.noise.Algorithm.WAVELET, + tcod.noise.Implementation.SIMPLE, + ), + ( + "perlin fbm", + tcod.noise.Algorithm.PERLIN, + tcod.noise.Implementation.FBM, + ), + ( + "perlin turbulence", + tcod.noise.Algorithm.PERLIN, + tcod.noise.Implementation.TURBULENCE, + ), + ( + "simplex fbm", + tcod.noise.Algorithm.SIMPLEX, + tcod.noise.Implementation.FBM, + ), + ( + "simplex turbulence", + tcod.noise.Algorithm.SIMPLEX, + tcod.noise.Implementation.TURBULENCE, + ), + ( + "wavelet fbm", + tcod.noise.Algorithm.WAVELET, + tcod.noise.Implementation.FBM, + ), + ( + "wavelet turbulence", + tcod.noise.Algorithm.WAVELET, + tcod.noise.Implementation.TURBULENCE, + ), ] def __init__(self) -> None: @@ -407,7 +443,7 @@ def on_draw(self) -> None: self.img.put_pixel(x, y, (c // 2, c // 2, c)) rectw = 24 recth = 13 - if self.implementation == tcod.noise.SIMPLE: + if self.implementation == tcod.noise.Implementation.SIMPLE: recth = 10 sample_console.draw_semigraphics(self.img) sample_console.draw_rect( @@ -435,7 +471,7 @@ def on_draw(self) -> None: sample_console.print( 2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None ) - if self.implementation != tcod.noise.SIMPLE: + if self.implementation != tcod.noise.Implementation.SIMPLE: sample_console.print( 2, 12, From c9aa1432198a0af1973c53c4c835aa55ef35bc38 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 17:21:14 -0700 Subject: [PATCH 0528/1101] Update keyboard events to use enums. --- CHANGELOG.rst | 6 ++--- tcod/event.py | 71 ++++++++++++++++++++++----------------------------- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f019c096..c69f6780 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums. 12.4.0 - 2021-05-21 ------------------- @@ -18,9 +20,7 @@ Added Changed - Using `libtcod 1.18.1`. - -Fixed - - `tcod.event.KeySym` and `tcod.event.Scancode ` can now be hashed. + - `tcod.event.KeySym` and `tcod.event.Scancode` can now be hashed. 12.3.2 - 2021-05-15 ------------------- diff --git a/tcod/event.py b/tcod/event.py index 03becfa6..5ade3383 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -122,7 +122,26 @@ def _init_sdl_video() -> None: class Modifier(enum.IntFlag): - """Keyboard modifier flags. + """Keyboard modifier flags, a bitfield of held modifier keys. + + Use `bitwise and` to check if a modifier key is held. + + The following example shows some common ways of checking modifiers. + All non-zero return values are considered true. + + Example:: + + >>> mod = tcod.event.Modifier(4098) + >>> mod + + >>> mod & tcod.event.Modifier.SHIFT # Check if any shift key is held. + + >>> mod & tcod.event.Modifier.LSHIFT # Check if left shift key is held. + + >>> not mod & tcod.event.Modifier.LSHIFT # Check if left shift key is NOT held. + True + >>> mod & tcod.event.Modifier.SHIFT and mod & tcod.event.Modifier.CTRL # Check if Shift+Control is held. + .. versionadded:: 12.3 """ @@ -258,58 +277,28 @@ class KeyboardEvent(Event): """ Attributes: type (str): Will be "KEYDOWN" or "KEYUP", depending on the event. - scancode (int): The keyboard scan-code, this is the physical location + scancode (Scancode): The keyboard scan-code, this is the physical location of the key on the keyboard rather than the keys symbol. - sym (int): The keyboard symbol. - mod (int): A bitmask of the currently held modifier keys. - - You can use the following to check if a modifier key is held: - - * `tcod.event.KMOD_LSHIFT` - Left shift bit. - * `tcod.event.KMOD_RSHIFT` - Right shift bit. - * `tcod.event.KMOD_LCTRL` - Left control bit. - * `tcod.event.KMOD_RCTRL` - Right control bit. - * `tcod.event.KMOD_LALT` - Left alt bit. - * `tcod.event.KMOD_RALT` - Right alt bit. - * `tcod.event.KMOD_LGUI` - Left meta key bit. - * `tcod.event.KMOD_RGUI` - Right meta key bit. - * `tcod.event.KMOD_SHIFT` - ``tcod.event.KMOD_LSHIFT | tcod.event.KMOD_RSHIFT`` - * `tcod.event.KMOD_CTRL` - ``tcod.event.KMOD_LCTRL | tcod.event.KMOD_RCTRL`` - * `tcod.event.KMOD_ALT` - ``tcod.event.KMOD_LALT | tcod.event.KMOD_RALT`` - * `tcod.event.KMOD_GUI` - ``tcod.event.KMOD_LGUI | tcod.event.KMOD_RGUI`` - * `tcod.event.KMOD_NUM` - Num lock bit. - * `tcod.event.KMOD_CAPS` - Caps lock bit. - * `tcod.event.KMOD_MODE` - AltGr key bit. + sym (KeySym): The keyboard symbol. + mod (Modifier): A bitmask of the currently held modifier keys. For example, if shift is held then - ``event.mod & tcod.event.KMOD_SHIFT`` will evaluate to a true + ``event.mod & tcod.event.Modifier.SHIFT`` will evaluate to a true value. repeat (bool): True if this event exists because of key repeat. + + .. versionchanged:: 12.5 + `scancode`, `sym`, and `mod` now use their respective enums. """ def __init__( self, scancode: int, sym: int, mod: int, repeat: bool = False ): super().__init__() - self.scancode = scancode - self.sym = sym - self.mod = mod + self.scancode = Scancode(scancode) + self.sym = KeySym(sym) + self.mod = Modifier(mod) self.repeat = repeat @classmethod From 47353dc2ef4565e4f891fac9ee95a2628e933d3a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 21 May 2021 17:30:44 -0700 Subject: [PATCH 0529/1101] Prepare 12.5.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c69f6780..fde155ab 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.5.0 - 2021-05-21 +------------------- Changed - `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums. From 1ad3f8e63d8da9adb155de98f030af0146093f0a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 May 2021 14:49:30 -0700 Subject: [PATCH 0530/1101] Ensure cffi is available for the setup script. The setup script will now crash instead of printing a warning which mostly gets ignored by pip. Add missing build-backend to pyproject.toml. --- CHANGELOG.rst | 2 ++ pyproject.toml | 1 + setup.py | 43 ++++++++++++++++++++++++------------------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fde155ab..6bc74384 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - The setup script should no longer fail silently when cffi is not available. 12.5.0 - 2021-05-21 ------------------- diff --git a/pyproject.toml b/pyproject.toml index d6aa8694..f470203d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ requires = [ "cffi~=1.13", "pycparser>=2.14", ] +build-backend = "setuptools.build-meta" [tool.black] line-length = 79 diff --git a/setup.py b/setup.py index 4a4d946a..1b82df2d 100755 --- a/setup.py +++ b/setup.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 -import sys import os - -from setuptools import setup - -from subprocess import check_output import platform +import sys import warnings +from subprocess import CalledProcessError, check_output +from typing import List + +from setuptools import setup SDL_VERSION_NEEDED = (2, 0, 5) -def get_version(): +def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" try: tag = check_output( @@ -31,27 +31,28 @@ def get_version(): version += ".dev%i" % commits_since_tag # update tcod/version.py - with open("tcod/version.py", "w") as f: - f.write('__version__ = "%s"\n' % version) + with open("tcod/version.py", "w") as version_file: + version_file.write('__version__ = "%s"\n' % version) return version - except: + except CalledProcessError: try: - exec(open("tcod/version.py").read(), globals()) - return __version__ + __version__ = "0.0.0" + with open("tcod/version.py") as version_file: + exec(version_file.read(), globals()) # Update __version__ except FileNotFoundError: warnings.warn( "Unknown version: " "Not in a Git repository and not from a sdist bundle or wheel." ) - return "0.0.0" + return __version__ is_pypy = platform.python_implementation() == "PyPy" -def get_package_data(): +def get_package_data() -> List[str]: """get data files which will be included in the main tcod/ directory""" - BITSIZE, LINKAGE = platform.architecture() + BITSIZE, _ = platform.architecture() files = [ "py.typed", "lib/LIBTCOD-CREDITS.txt", @@ -68,13 +69,13 @@ def get_package_data(): return files -def get_long_description(): +def get_long_description() -> str: """Return this projects description.""" - with open("README.rst", "r") as f: - return f.read() + with open("README.rst", "r") as readme_file: + return readme_file.read() -def check_sdl_version(): +def check_sdl_version() -> None: """Check the local SDL version on Linux distributions.""" if not sys.platform.startswith("linux"): return @@ -127,13 +128,17 @@ def check_sdl_version(): packages=["tcod", "tcod.__pyinstaller"], package_data={"tcod": get_package_data()}, python_requires=">=3.6", + setup_requires=[ + *pytest_runner, + "cffi~=1.13", + "pycparser>=2.14", + ], install_requires=[ "cffi~=1.13", # Also required by pyproject.toml. "numpy~=1.10" if not is_pypy else "", "typing_extensions", ], cffi_modules=["build_libtcod.py:ffi"], - setup_requires=pytest_runner, tests_require=["pytest", "pytest-cov", "pytest-benchmark"], classifiers=[ "Development Status :: 5 - Production/Stable", From 06e231c55f8fa6a9b946f86446fa7d3752103caf Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 May 2021 15:25:25 -0700 Subject: [PATCH 0531/1101] Update required numpy. This to resolve type checking. The install required version is unchanged. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3b43ea13..f458e1e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ cffi~=1.13 -numpy>=1.20.0rc1; python_version >= '3.7' +numpy>=1.20.3; python_version >= '3.7' numpy>=1.19.5; python_version <= '3.6' pycparser>=2.14 setuptools>=36.0.1 From c5e3f72473542cba9f5811e1e13a08f4bff8066d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 May 2021 22:58:05 -0700 Subject: [PATCH 0532/1101] Prepare 12.5.1 release. --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6bc74384..fcb5f275 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,11 @@ v2.0.0 Unreleased ------------------ + +12.5.1 - 2021-05-30 +------------------- Fixed - - The setup script should no longer fail silently when cffi is not available. + - The setup script should no longer fail silently when cffi is unavailable. 12.5.0 - 2021-05-21 ------------------- From 834d12ddf1e6c0c1b7810e8365e2df803c20174b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Jun 2021 10:29:20 -0700 Subject: [PATCH 0533/1101] Use latest version of setuptools and fix module spelling. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f470203d..6d52c0df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [build-system] requires = [ - "setuptools", + "setuptools>=57.0.0", "wheel", "cffi~=1.13", "pycparser>=2.14", ] -build-backend = "setuptools.build-meta" +build-backend = "setuptools.build_meta" [tool.black] line-length = 79 From 740d57c4a03de456bfb9a697b16a957195020e00 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Jun 2021 10:38:18 -0700 Subject: [PATCH 0534/1101] Reorganize build imports. Add build imports to path. --- build_libtcod.py | 22 +++++++++++----------- parse_sdl2.py | 3 +-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 7aa5782d..1a992d9b 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 -import os -import sys - import glob +import os +import platform import re -from typing import List, Tuple, Any, Dict, Iterable, Iterator, Set - -from cffi import FFI # type: ignore - import shutil import subprocess -import platform +import sys import zipfile - -import parse_sdl2 +from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple try: from urllib import urlretrieve # type: ignore except ImportError: from urllib.request import urlretrieve +from cffi import FFI # type: ignore + +sys.path.append(os.path.dirname(__file__)) + +import parse_sdl2 # noqa: E402 + # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") @@ -460,8 +460,8 @@ def generate_enums(prefix: str) -> Iterator[str]: def write_library_constants() -> None: """Write libtcod constants into the tcod.constants module.""" - from tcod._libtcod import lib, ffi import tcod.color + from tcod._libtcod import ffi, lib with open("tcod/constants.py", "w") as f: all_names = [] diff --git a/parse_sdl2.py b/parse_sdl2.py index 237eeb8b..964ba84a 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -1,8 +1,7 @@ import os.path -import sys - import platform import re +import sys from typing import Iterator import cffi # type: ignore From af0806d86b9dd491ea5b2e2b6d9eb95240567e75 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Jun 2021 10:42:11 -0700 Subject: [PATCH 0535/1101] Default to a shallow checkout of the libtcod submodule. --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index fcc1ec6c..4f2f7179 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "libtcod"] path = libtcod url = https://github.com/libtcod/libtcod + shallow = true From f4e0e4a42be913923254563aa8454e2e15627476 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Jun 2021 22:25:41 -0700 Subject: [PATCH 0536/1101] Add PyCharm requirements issue to FAQ. --- docs/faq.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/faq.rst b/docs/faq.rst index 0981c29c..feed1459 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -8,3 +8,25 @@ You'll need to use an external tool to manage the framerate. This can either be your own custom tool or you can copy the Clock class from the `framerate.py `_ example. + +I get ``No module named 'tcod'`` when I try to ``import tcod`` in PyCharm. +-------------------------------------------------------------------------- + +`PyCharm`_ will automatically setup a `Python virtual environment `_ for new or added projects. +By default this virtual environment is isolated and will ignore global Python packages installed from the standard terminal. **In this case you MUST install tcod inside of your per-project virtual environment.** + +The recommended way to work with PyCharm is to add a ``requirements.txt`` file to the root of your PyCharm project with a `requirement specifier `_ for `tcod`. +This file should have the following: + +.. code-block:: python + + # requirements.txt + # https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format + tcod + +Once this file is saved to your projects root directory then PyCharm will detect it and ask if you want these requirements installed. Say yes and `tcod` will be installed to the `virtual environment`. Be sure to add more specifiers for any modules you're using other than `tcod`, such as `numpy`. + +Alternatively you can open the `Terminal` tab in PyCharm and run ``pip install tcod`` there. This will install `tcod` to the currently open project. + + +.. _PyCharm: https://www.jetbrains.com/pycharm/ From 791c76656a95d3028e6d3268ec98cd4755319031 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Jun 2021 15:39:36 -0700 Subject: [PATCH 0537/1101] Raise line length to 120 and reformat all files. Added additional checks for formatting in workflows. --- .github/workflows/python-lint.yml | 8 +- build_libtcod.py | 75 +- examples/.isort.cfg | 2 +- examples/cavegen.py | 4 +- examples/distribution/PyInstaller/main.py | 8 +- examples/distribution/cx_Freeze/main.py | 8 +- examples/eventget.py | 4 +- examples/samples_libtcodpy.py | 1371 +++++++++++---------- examples/samples_tcod.py | 315 ++--- examples/termbox/termbox.py | 256 ++-- examples/termbox/termboxtest.py | 81 +- examples/thread_jobs.py | 36 +- examples/ttf.py | 23 +- parse_sdl2.py | 47 +- pyproject.toml | 4 +- scripts/generate_charmap_table.py | 11 +- scripts/tag_release.py | 28 +- setup.py | 18 +- tcod/.clang-format | 2 +- tcod/__init__.py | 15 +- tcod/_internal.py | 19 +- tcod/bsp.py | 9 +- tcod/cdef.h | 6 +- tcod/color.py | 4 +- tcod/console.py | 92 +- tcod/context.py | 61 +- tcod/event.py | 129 +- tcod/image.py | 20 +- tcod/libtcodpy.py | 509 ++------ tcod/loader.py | 4 +- tcod/map.py | 28 +- tcod/noise.c | 14 +- tcod/noise.h | 6 +- tcod/noise.py | 61 +- tcod/path.c | 170 +-- tcod/path.h | 55 +- tcod/path.py | 171 +-- tcod/random.h | 18 +- tcod/random.py | 8 +- tcod/sdl.py | 17 +- tcod/tcod.c | 4 +- tcod/tdl.c | 65 +- tcod/tdl.h | 12 +- tcod/tileset.py | 29 +- tests/common.py | 3 +- tests/conftest.py | 61 +- tests/test_console.py | 22 +- tests/test_libtcodpy.py | 203 ++- tests/test_noise.py | 8 +- tests/test_parser.py | 137 +- tests/test_random.py | 3 +- tests/test_tcod.py | 37 +- tests/test_testing.py | 4 +- 53 files changed, 1609 insertions(+), 2696 deletions(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index 9fa20f82..ee89c14b 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -32,18 +32,18 @@ jobs: uses: liskin/gh-problem-matcher-wrap@v1 with: linters: flake8 - run: flake8 tcod/ + run: flake8 scripts/ tcod/ - name: MyPy if: always() uses: liskin/gh-problem-matcher-wrap@v1 with: linters: mypy - run: mypy --show-column-numbers tcod/ + run: mypy --show-column-numbers scripts/ tcod/ - name: isort uses: liskin/gh-problem-matcher-wrap@v1 with: linters: isort - run: isort tcod/ tests/ --check + run: isort scripts/ tcod/ tests/ *.py --check - name: isort (examples) uses: liskin/gh-problem-matcher-wrap@v1 with: @@ -51,4 +51,4 @@ jobs: run: isort examples/ --check --thirdparty tcod - name: Black run: | - black --check tcod/ + black --check examples/ scripts/ tcod/ tests/ *.py diff --git a/build_libtcod.py b/build_libtcod.py index 1a992d9b..80143b7f 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -34,21 +34,15 @@ # Regular expressions to parse the headers for cffi. RE_COMMENT = re.compile(r"\s*/\*.*?\*/|\s*//*?$", re.DOTALL | re.MULTILINE) -RE_CPLUSPLUS = re.compile( - r"#ifdef __cplusplus.*?#endif.*?$", re.DOTALL | re.MULTILINE -) -RE_PREPROCESSOR = re.compile( - r"(?!#define\s+\w+\s+\d+$)#.*?(? str: assert sdl2_arc.endswith(".dmg") subprocess.check_call(["hdiutil", "mount", sdl2_arc]) subprocess.check_call(["mkdir", "-p", sdl2_dir]) - subprocess.check_call( - ["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir] - ) + subprocess.check_call(["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir]) subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL2"]) return sdl2_path @@ -234,9 +225,7 @@ def unpack_sdl2(version: str) -> str: else: matches = re.findall( r"-I(\S+)", - subprocess.check_output( - ["sdl2-config", "--cflags"], universal_newlines=True - ), + subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True), ) assert matches @@ -249,9 +238,7 @@ def unpack_sdl2(version: str) -> str: if sys.platform == "win32": include_dirs.append(SDL2_INCLUDE) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = os.path.join( - SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE] - ) + SDL2_LIB_DIR = os.path.join(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) library_dirs.append(SDL2_LIB_DIR) SDL2_LIB_DEST = os.path.join("tcod", ARCH_MAPPING[BITSIZE]) if not os.path.exists(SDL2_LIB_DEST): @@ -287,21 +274,9 @@ def fix_header(filepath: str) -> None: define_macros.append(("HAVE_UNISTD_H", 1)) if sys.platform not in ["win32", "darwin"]: - extra_parse_args += ( - subprocess.check_output( - ["sdl2-config", "--cflags"], universal_newlines=True - ) - .strip() - .split() - ) + extra_parse_args += subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True).strip().split() extra_compile_args += extra_parse_args - extra_link_args += ( - subprocess.check_output( - ["sdl2-config", "--libs"], universal_newlines=True - ) - .strip() - .split() - ) + extra_link_args += subprocess.check_output(["sdl2-config", "--libs"], universal_newlines=True).strip().split() tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() @@ -399,9 +374,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: """ names = [] lookup = [] - for name, value in sorted( - find_sdl_attrs(prefix), key=lambda item: item[1] - ): + for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): all_names.append(name) names.append("%s = %s" % (name, value)) lookup.append('%s: "%s"' % (value, name)) @@ -446,9 +419,7 @@ def update_module_all(filename: str, new_all: str) -> None: def generate_enums(prefix: str) -> Iterator[str]: """Generate attribute assignments suitable for a Python enum.""" prefix_len = len(prefix) - len("SDL_") + 1 - for name, value in sorted( - find_sdl_attrs(prefix), key=lambda item: item[1] - ): + for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): name = name.split("_", 1)[1] if name.isdigit(): name = f"N{name}" @@ -473,9 +444,7 @@ def write_library_constants() -> None: continue if name in EXCLUDE_CONSTANTS: continue - if any( - name.startswith(prefix) for prefix in EXCLUDE_CONSTANT_PREFIXES - ): + if any(name.startswith(prefix) for prefix in EXCLUDE_CONSTANT_PREFIXES): continue value = getattr(lib, name) if name[:5] == "TCOD_": @@ -511,28 +480,16 @@ def write_library_constants() -> None: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) f.write("# --- SDL scancodes ---\n") - f.write( - "%s\n_REVERSE_SCANCODE_TABLE = %s\n" - % parse_sdl_attrs("SDL_SCANCODE", all_names) - ) + f.write("%s\n_REVERSE_SCANCODE_TABLE = %s\n" % parse_sdl_attrs("SDL_SCANCODE", all_names)) f.write("\n# --- SDL keyboard symbols ---\n") - f.write( - "%s\n_REVERSE_SYM_TABLE = %s\n" - % parse_sdl_attrs("SDLK", all_names) - ) + f.write("%s\n_REVERSE_SYM_TABLE = %s\n" % parse_sdl_attrs("SDLK", all_names)) f.write("\n# --- SDL keyboard modifiers ---\n") - f.write( - "%s\n_REVERSE_MOD_TABLE = %s\n" - % parse_sdl_attrs("KMOD", all_names) - ) + f.write("%s\n_REVERSE_MOD_TABLE = %s\n" % parse_sdl_attrs("KMOD", all_names)) f.write("\n# --- SDL wheel ---\n") - f.write( - "%s\n_REVERSE_WHEEL_TABLE = %s\n" - % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names) - ) + f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) all_names = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) diff --git a/examples/.isort.cfg b/examples/.isort.cfg index bb09c95c..9e0911c7 100644 --- a/examples/.isort.cfg +++ b/examples/.isort.cfg @@ -2,4 +2,4 @@ profile= black py_version = 36 skip_gitignore = true -line_length = 79 +line_length = 120 diff --git a/examples/cavegen.py b/examples/cavegen.py index 92544765..aa2bca7c 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -19,9 +19,7 @@ def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: walls then the tile will become a wall. """ # Use convolve2d, the 2nd input is a 3x3 ones array. - neighbors: np.ndarray = scipy.signal.convolve2d( - tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same" - ) + neighbors: np.ndarray = scipy.signal.convolve2d(tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same") next_tiles: np.ndarray = neighbors < wall_rule # Apply the wall rule. return next_tiles diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index 805c4218..0755c212 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -17,12 +17,8 @@ def main(): - tileset = tcod.tileset.load_tilesheet( - FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437 - ) - with tcod.context.new( - columns=WIDTH, rows=HEIGHT, tileset=tileset - ) as context: + tileset = tcod.tileset.load_tilesheet(FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437) + with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: console = tcod.console.Console(WIDTH, HEIGHT) console.print(0, 0, "Hello World") diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index 6cbec00f..cb70f549 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -6,12 +6,8 @@ def main(): - tileset = tcod.tileset.load_tilesheet( - "data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437 - ) - with tcod.context.new( - columns=WIDTH, rows=HEIGHT, tileset=tileset - ) as context: + tileset = tcod.tileset.load_tilesheet("data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437) + with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: console = tcod.console.Console(WIDTH, HEIGHT) console.print(0, 0, "Hello World") diff --git a/examples/eventget.py b/examples/eventget.py index e325763b..1c3c0554 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -19,9 +19,7 @@ def main() -> None: event_log: List[str] = [] motion_desc = "" - with tcod.context.new( - width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS - ) as context: + with tcod.context.new(width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS) as context: console = context.new_console() while True: # Display all event items. diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index cf1fa84c..6b5b05a7 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -15,6 +15,7 @@ try: # Import Psyco if available import psyco + psyco.full() except ImportError: pass @@ -29,13 +30,14 @@ def get_data(path: str) -> str: DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") return os.path.join(DATA_DIR, path) + SAMPLE_SCREEN_WIDTH = 46 SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 font = get_data("fonts/dejavu10x10_gs_tc.png") libtcod.console_set_custom_font(font, libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) -libtcod.console_init_root(80, 50, b'libtcod python sample', False) +libtcod.console_init_root(80, 50, b"libtcod python sample", False) sample_console = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) ############################################# @@ -43,99 +45,75 @@ def get_data(path: str) -> str: ############################################# # parser declaration if True: - print ('***** File Parser test *****') - parser=libtcod.parser_new() - struct=libtcod.parser_new_struct(parser, b'myStruct') - libtcod.struct_add_property(struct, b'bool_field', libtcod.TYPE_BOOL, True) - libtcod.struct_add_property(struct, b'char_field', libtcod.TYPE_CHAR, True) - libtcod.struct_add_property(struct, b'int_field', libtcod.TYPE_INT, True) - libtcod.struct_add_property(struct, b'float_field', libtcod.TYPE_FLOAT, True) - libtcod.struct_add_property(struct, b'color_field', libtcod.TYPE_COLOR, True) - libtcod.struct_add_property(struct, b'dice_field', libtcod.TYPE_DICE, True) - libtcod.struct_add_property(struct, b'string_field', libtcod.TYPE_STRING, - True) - libtcod.struct_add_list_property(struct, b'bool_list', libtcod.TYPE_BOOL, - True) - libtcod.struct_add_list_property(struct, b'char_list', libtcod.TYPE_CHAR, - True) - libtcod.struct_add_list_property(struct, b'integer_list', libtcod.TYPE_INT, - True) - libtcod.struct_add_list_property(struct, b'float_list', libtcod.TYPE_FLOAT, - True) - libtcod.struct_add_list_property(struct, b'string_list', libtcod.TYPE_STRING, - True) - libtcod.struct_add_list_property(struct, b'color_list', libtcod.TYPE_COLOR, - True) -## # dice lists doesn't work yet -## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE, -## True) + print("***** File Parser test *****") + parser = libtcod.parser_new() + struct = libtcod.parser_new_struct(parser, b"myStruct") + libtcod.struct_add_property(struct, b"bool_field", libtcod.TYPE_BOOL, True) + libtcod.struct_add_property(struct, b"char_field", libtcod.TYPE_CHAR, True) + libtcod.struct_add_property(struct, b"int_field", libtcod.TYPE_INT, True) + libtcod.struct_add_property(struct, b"float_field", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_property(struct, b"color_field", libtcod.TYPE_COLOR, True) + libtcod.struct_add_property(struct, b"dice_field", libtcod.TYPE_DICE, True) + libtcod.struct_add_property(struct, b"string_field", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, b"bool_list", libtcod.TYPE_BOOL, True) + libtcod.struct_add_list_property(struct, b"char_list", libtcod.TYPE_CHAR, True) + libtcod.struct_add_list_property(struct, b"integer_list", libtcod.TYPE_INT, True) + libtcod.struct_add_list_property(struct, b"float_list", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_list_property(struct, b"string_list", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, b"color_list", libtcod.TYPE_COLOR, True) + ## # dice lists doesn't work yet + ## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE, + ## True) # default listener - print ('***** Default listener *****') - libtcod.parser_run(parser, get_data('cfg/sample.cfg')) - print ('bool_field : ', \ - libtcod.parser_get_bool_property(parser, b'myStruct.bool_field')) - print ('char_field : ', \ - libtcod.parser_get_char_property(parser, b'myStruct.char_field')) - print ('int_field : ', \ - libtcod.parser_get_int_property(parser, b'myStruct.int_field')) - print ('float_field : ', \ - libtcod.parser_get_float_property(parser, b'myStruct.float_field')) - print ('color_field : ', \ - libtcod.parser_get_color_property(parser, b'myStruct.color_field')) - print ('dice_field : ', \ - libtcod.parser_get_dice_property(parser, b'myStruct.dice_field')) - print ('string_field : ', \ - libtcod.parser_get_string_property(parser, b'myStruct.string_field')) - print ('bool_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.bool_list', - libtcod.TYPE_BOOL)) - print ('char_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.char_list', - libtcod.TYPE_CHAR)) - print ('integer_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.integer_list', - libtcod.TYPE_INT)) - print ('float_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.float_list', - libtcod.TYPE_FLOAT)) - print ('string_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.string_list', - libtcod.TYPE_STRING)) - print ('color_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.color_list', - libtcod.TYPE_COLOR)) -## print ('dice_list : ', \ -## libtcod.parser_get_list_property(parser, b'myStruct.dice_list', -## libtcod.TYPE_DICE)) + print("***** Default listener *****") + libtcod.parser_run(parser, get_data("cfg/sample.cfg")) + print("bool_field : ", libtcod.parser_get_bool_property(parser, b"myStruct.bool_field")) + print("char_field : ", libtcod.parser_get_char_property(parser, b"myStruct.char_field")) + print("int_field : ", libtcod.parser_get_int_property(parser, b"myStruct.int_field")) + print("float_field : ", libtcod.parser_get_float_property(parser, b"myStruct.float_field")) + print("color_field : ", libtcod.parser_get_color_property(parser, b"myStruct.color_field")) + print("dice_field : ", libtcod.parser_get_dice_property(parser, b"myStruct.dice_field")) + print("string_field : ", libtcod.parser_get_string_property(parser, b"myStruct.string_field")) + print("bool_list : ", libtcod.parser_get_list_property(parser, b"myStruct.bool_list", libtcod.TYPE_BOOL)) + print("char_list : ", libtcod.parser_get_list_property(parser, b"myStruct.char_list", libtcod.TYPE_CHAR)) + print("integer_list : ", libtcod.parser_get_list_property(parser, b"myStruct.integer_list", libtcod.TYPE_INT)) + print("float_list : ", libtcod.parser_get_list_property(parser, b"myStruct.float_list", libtcod.TYPE_FLOAT)) + print("string_list : ", libtcod.parser_get_list_property(parser, b"myStruct.string_list", libtcod.TYPE_STRING)) + print("color_list : ", libtcod.parser_get_list_property(parser, b"myStruct.color_list", libtcod.TYPE_COLOR)) + ## print ('dice_list : ', \ + ## libtcod.parser_get_list_property(parser, b'myStruct.dice_list', + ## libtcod.TYPE_DICE)) # custom listener - print ('***** Custom listener *****') + print("***** Custom listener *****") + class MyListener: def new_struct(self, struct, name): - print ('new structure type', libtcod.struct_get_name(struct), \ - ' named ', name ) + print("new structure type", libtcod.struct_get_name(struct), " named ", name) return True + def new_flag(self, name): - print ('new flag named ', name) + print("new flag named ", name) return True - def new_property(self,name, typ, value): - type_names = ['NONE', 'BOOL', 'CHAR', 'INT', 'FLOAT', 'STRING', \ - 'COLOR', 'DICE'] - type_name = type_names[typ & 0xff] + + def new_property(self, name, typ, value): + type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"] + type_name = type_names[typ & 0xFF] if typ & libtcod.TYPE_LIST: - type_name = 'LIST<%s>' % type_name - print ('new property named ', name,' type ',type_name, \ - ' value ', value) + type_name = "LIST<%s>" % type_name + print("new property named ", name, " type ", type_name, " value ", value) return True + def end_struct(self, struct, name): - print ('end structure type', libtcod.struct_get_name(struct), \ - ' named ', name) + print("end structure type", libtcod.struct_get_name(struct), " named ", name) return True - def error(self,msg): - print ('error : ', msg) + + def error(self, msg): + print("error : ", msg) return True - libtcod.parser_run(parser, get_data('cfg/sample.cfg'), MyListener()) + + libtcod.parser_run(parser, get_data("cfg/sample.cfg"), MyListener()) ############################################# # end of parser unit test ############################################# @@ -143,14 +121,17 @@ def error(self,msg): ############################################# # true color sample ############################################# -tc_cols = [libtcod.Color(50, 40, 150), - libtcod.Color(240, 85, 5), - libtcod.Color(50, 35, 240), - libtcod.Color(10, 200, 130), - ] +tc_cols = [ + libtcod.Color(50, 40, 150), + libtcod.Color(240, 85, 5), + libtcod.Color(50, 35, 240), + libtcod.Color(10, 200, 130), +] tc_dirr = [1, -1, 1, 1] tc_dirg = [1, -1, -1, 1] tc_dirb = [1, 1, 1, -1] + + def render_colors(first, key, mouse): global tc_cols, tc_dirr, tc_dirg, tc_dirb, tc_fast @@ -164,7 +145,7 @@ def render_colors(first, key, mouse): tc_fast = False for c in range(4): # move each corner color - component=libtcod.random_get_int(None, 0, 2) + component = libtcod.random_get_int(None, 0, 2) if component == 0: tc_cols[c].r += 5 * tc_dirr[c] if tc_cols[c].r == 255: @@ -189,15 +170,12 @@ def render_colors(first, key, mouse): for x in range(SAMPLE_SCREEN_WIDTH): xcoef = float(x) / (SAMPLE_SCREEN_WIDTH - 1) top = libtcod.color_lerp(tc_cols[TOPLEFT], tc_cols[TOPRIGHT], xcoef) - bottom = libtcod.color_lerp(tc_cols[BOTTOMLEFT], tc_cols[BOTTOMRIGHT], - xcoef) + bottom = libtcod.color_lerp(tc_cols[BOTTOMLEFT], tc_cols[BOTTOMRIGHT], xcoef) for y in range(SAMPLE_SCREEN_HEIGHT): ycoef = float(y) / (SAMPLE_SCREEN_HEIGHT - 1) curColor = libtcod.color_lerp(top, bottom, ycoef) - libtcod.console_set_char_background(sample_console, x, y, curColor, - libtcod.BKGND_SET) - textColor = libtcod.console_get_char_background(sample_console, - SAMPLE_SCREEN_WIDTH // 2, 5) + libtcod.console_set_char_background(sample_console, x, y, curColor, libtcod.BKGND_SET) + textColor = libtcod.console_get_char_background(sample_console, SAMPLE_SCREEN_WIDTH // 2, 5) textColor.r = 255 - textColor.r textColor.g = 255 - textColor.g textColor.b = 255 - textColor.b @@ -206,14 +184,13 @@ def render_colors(first, key, mouse): for y in range(SAMPLE_SCREEN_HEIGHT): col = libtcod.console_get_char_background(sample_console, x, y) col = libtcod.color_lerp(col, libtcod.black, 0.5) - c = libtcod.random_get_int(None, ord('a'), ord('z')) + c = libtcod.random_get_int(None, ord("a"), ord("z")) libtcod.console_set_default_foreground(sample_console, col) - libtcod.console_put_char(sample_console, x, y, c, - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, x, y, c, libtcod.BKGND_NONE) else: # same, but using the ConsoleBuffer class to speed up rendering buffer = libtcod.ConsoleBuffer(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) # initialize buffer - c = libtcod.random_get_int(None, ord('a'), ord('z')) + c = libtcod.random_get_int(None, ord("a"), ord("z")) for x in range(SAMPLE_SCREEN_WIDTH): xcoef = float(x) / (SAMPLE_SCREEN_WIDTH - 1) top = libtcod.color_lerp(tc_cols[TOPLEFT], tc_cols[TOPRIGHT], xcoef) @@ -226,25 +203,32 @@ def render_colors(first, key, mouse): g = int(top.g * ycoef + bottom.g * (1 - ycoef)) b = int(top.b * ycoef + bottom.b * (1 - ycoef)) c += 1 - if c > ord('z'): c = ord('a') + if c > ord("z"): + c = ord("a") # set background, foreground and char with a single function buffer.set(x, y, r, g, b, r // 2, g // 2, b // 2, chr(c)) buffer.blit(sample_console) # update console with the buffer's contents libtcod.console_set_default_foreground(sample_console, libtcod.Color(int(r), int(g), int(b))) libtcod.console_set_default_background(sample_console, libtcod.grey) - libtcod.console_print_rect_ex(sample_console, SAMPLE_SCREEN_WIDTH // 2, - 5, SAMPLE_SCREEN_WIDTH - 2, - SAMPLE_SCREEN_HEIGHT - 1, - libtcod.BKGND_MULTIPLY, libtcod.CENTER, - "The Doryen library uses 24 bits " - "colors, for both background and " - "foreground.") - - if key.c == ord('f'): tc_fast = not tc_fast + libtcod.console_print_rect_ex( + sample_console, + SAMPLE_SCREEN_WIDTH // 2, + 5, + SAMPLE_SCREEN_WIDTH - 2, + SAMPLE_SCREEN_HEIGHT - 1, + libtcod.BKGND_MULTIPLY, + libtcod.CENTER, + "The Doryen library uses 24 bits " "colors, for both background and " "foreground.", + ) + + if key.c == ord("f"): + tc_fast = not tc_fast libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, SAMPLE_SCREEN_HEIGHT - 2, - "F : turn fast rendering %s" % ("off" if tc_fast else "on")) + libtcod.console_print( + sample_console, 1, SAMPLE_SCREEN_HEIGHT - 2, "F : turn fast rendering %s" % ("off" if tc_fast else "on") + ) + ############################################# # offscreen console sample @@ -257,32 +241,43 @@ def render_colors(first, key, mouse): oc_init = False oc_xdir = 1 oc_ydir = 1 + + def render_offscreen(first, key, mouse): global oc_secondary, oc_screenshot global oc_counter, oc_x, oc_y, oc_init, oc_xdir, oc_ydir if not oc_init: oc_init = True - oc_secondary = libtcod.console_new(SAMPLE_SCREEN_WIDTH // 2, - SAMPLE_SCREEN_HEIGHT // 2) - oc_screenshot = libtcod.console_new(SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT) - libtcod.console_print_frame(oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2, - SAMPLE_SCREEN_HEIGHT // 2, False, libtcod.BKGND_NONE, - b'Offscreen console') - libtcod.console_print_rect_ex(oc_secondary, SAMPLE_SCREEN_WIDTH // 4, - 2, SAMPLE_SCREEN_WIDTH // 2 - 2, - SAMPLE_SCREEN_HEIGHT // 2, - libtcod.BKGND_NONE, libtcod.CENTER, - b"You can render to an offscreen " - b"console and blit in on another " - b"one, simulating alpha " - b"transparency.") + oc_secondary = libtcod.console_new(SAMPLE_SCREEN_WIDTH // 2, SAMPLE_SCREEN_HEIGHT // 2) + oc_screenshot = libtcod.console_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + libtcod.console_print_frame( + oc_secondary, + 0, + 0, + SAMPLE_SCREEN_WIDTH // 2, + SAMPLE_SCREEN_HEIGHT // 2, + False, + libtcod.BKGND_NONE, + b"Offscreen console", + ) + libtcod.console_print_rect_ex( + oc_secondary, + SAMPLE_SCREEN_WIDTH // 4, + 2, + SAMPLE_SCREEN_WIDTH // 2 - 2, + SAMPLE_SCREEN_HEIGHT // 2, + libtcod.BKGND_NONE, + libtcod.CENTER, + b"You can render to an offscreen " + b"console and blit in on another " + b"one, simulating alpha " + b"transparency.", + ) if first: libtcod.sys_set_fps(30) # get a "screenshot" of the current sample screen - libtcod.console_blit(sample_console, 0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT, oc_screenshot, 0, 0) + libtcod.console_blit(sample_console, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, oc_screenshot, 0, 0) oc_counter += 1 if oc_counter % 20 == 0: oc_x += oc_xdir @@ -295,11 +290,11 @@ def render_offscreen(first, key, mouse): oc_ydir = -1 elif oc_y == -5: oc_ydir = 1 - libtcod.console_blit(oc_screenshot, 0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0) - libtcod.console_blit(oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2, - SAMPLE_SCREEN_HEIGHT // 2, sample_console, oc_x, oc_y, - 1.0,0.75) + libtcod.console_blit(oc_screenshot, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0) + libtcod.console_blit( + oc_secondary, 0, 0, SAMPLE_SCREEN_WIDTH // 2, SAMPLE_SCREEN_HEIGHT // 2, sample_console, oc_x, oc_y, 1.0, 0.75 + ) + ############################################# # line drawing sample @@ -308,33 +303,35 @@ def render_offscreen(first, key, mouse): line_init = False line_bk_flag = libtcod.BKGND_SET + def render_lines(first, key, mouse): global line_bk, line_init, line_bk_flag - flag_names=['BKGND_NONE', - 'BKGND_SET', - 'BKGND_MULTIPLY', - 'BKGND_LIGHTEN', - 'BKGND_DARKEN', - 'BKGND_SCREEN', - 'BKGND_COLOR_DODGE', - 'BKGND_COLOR_BURN', - 'BKGND_ADD', - 'BKGND_ADDALPHA', - 'BKGND_BURN', - 'BKGND_OVERLAY', - 'BKGND_ALPHA', - ] + flag_names = [ + "BKGND_NONE", + "BKGND_SET", + "BKGND_MULTIPLY", + "BKGND_LIGHTEN", + "BKGND_DARKEN", + "BKGND_SCREEN", + "BKGND_COLOR_DODGE", + "BKGND_COLOR_BURN", + "BKGND_ADD", + "BKGND_ADDALPHA", + "BKGND_BURN", + "BKGND_OVERLAY", + "BKGND_ALPHA", + ] if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER): line_bk_flag += 1 - if (line_bk_flag & 0xff) > libtcod.BKGND_ALPH: - line_bk_flag=libtcod.BKGND_NONE + if (line_bk_flag & 0xFF) > libtcod.BKGND_ALPH: + line_bk_flag = libtcod.BKGND_NONE alpha = 0.0 - if (line_bk_flag & 0xff) == libtcod.BKGND_ALPH: + if (line_bk_flag & 0xFF) == libtcod.BKGND_ALPH: # for the alpha mode, update alpha every frame alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 line_bk_flag = libtcod.BKGND_ALPHA(alpha) - elif (line_bk_flag & 0xff) == libtcod.BKGND_ADDA: + elif (line_bk_flag & 0xFF) == libtcod.BKGND_ADDA: # for the add alpha mode, update alpha every frame alpha = (1.0 + math.cos(libtcod.sys_elapsed_seconds() * 2)) / 2.0 line_bk_flag = libtcod.BKGND_ADDALPHA(alpha) @@ -343,45 +340,39 @@ def render_lines(first, key, mouse): # initialize the colored background for x in range(SAMPLE_SCREEN_WIDTH): for y in range(SAMPLE_SCREEN_HEIGHT): - col = libtcod.Color(x * 255 // (SAMPLE_SCREEN_WIDTH - 1), - (x + y) * 255 // (SAMPLE_SCREEN_WIDTH - 1 + - SAMPLE_SCREEN_HEIGHT - 1), - y * 255 // (SAMPLE_SCREEN_HEIGHT-1)) + col = libtcod.Color( + x * 255 // (SAMPLE_SCREEN_WIDTH - 1), + (x + y) * 255 // (SAMPLE_SCREEN_WIDTH - 1 + SAMPLE_SCREEN_HEIGHT - 1), + y * 255 // (SAMPLE_SCREEN_HEIGHT - 1), + ) libtcod.console_set_char_background(line_bk, x, y, col, libtcod.BKGND_SET) line_init = True if first: libtcod.sys_set_fps(30) libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_blit(line_bk, 0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0) - recty = int((SAMPLE_SCREEN_HEIGHT - 2) * ((1.0 + - math.cos(libtcod.sys_elapsed_seconds())) / 2.0)) + libtcod.console_blit(line_bk, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, sample_console, 0, 0) + recty = int((SAMPLE_SCREEN_HEIGHT - 2) * ((1.0 + math.cos(libtcod.sys_elapsed_seconds())) / 2.0)) for x in range(SAMPLE_SCREEN_WIDTH): - col = libtcod.Color(x * 255 // SAMPLE_SCREEN_WIDTH, - x * 255 // SAMPLE_SCREEN_WIDTH, - x * 255 // SAMPLE_SCREEN_WIDTH) + col = libtcod.Color( + x * 255 // SAMPLE_SCREEN_WIDTH, x * 255 // SAMPLE_SCREEN_WIDTH, x * 255 // SAMPLE_SCREEN_WIDTH + ) libtcod.console_set_char_background(sample_console, x, recty, col, line_bk_flag) - libtcod.console_set_char_background(sample_console, x, recty + 1, col, - line_bk_flag) - libtcod.console_set_char_background(sample_console, x, recty + 2, col, - line_bk_flag) + libtcod.console_set_char_background(sample_console, x, recty + 1, col, line_bk_flag) + libtcod.console_set_char_background(sample_console, x, recty + 2, col, line_bk_flag) angle = libtcod.sys_elapsed_seconds() * 2.0 - cos_angle=math.cos(angle) - sin_angle=math.sin(angle) + cos_angle = math.cos(angle) + sin_angle = math.sin(angle) xo = int(SAMPLE_SCREEN_WIDTH // 2 * (1 + cos_angle)) yo = int(SAMPLE_SCREEN_HEIGHT // 2 + sin_angle * SAMPLE_SCREEN_WIDTH // 2) xd = int(SAMPLE_SCREEN_WIDTH // 2 * (1 - cos_angle)) yd = int(SAMPLE_SCREEN_HEIGHT // 2 - sin_angle * SAMPLE_SCREEN_WIDTH // 2) # draw the line # in python the easiest way is to use the line iterator - for x,y in libtcod.line_iter(xo, yo, xd, yd): - if 0 <= x < SAMPLE_SCREEN_WIDTH and \ - 0 <= y < SAMPLE_SCREEN_HEIGHT: - libtcod.console_set_char_background(sample_console, x, y, - libtcod.light_blue, line_bk_flag) - libtcod.console_print(sample_console, 2, 2, - '%s (ENTER to change)' % - flag_names[line_bk_flag & 0xff]) + for x, y in libtcod.line_iter(xo, yo, xd, yd): + if 0 <= x < SAMPLE_SCREEN_WIDTH and 0 <= y < SAMPLE_SCREEN_HEIGHT: + libtcod.console_set_char_background(sample_console, x, y, libtcod.light_blue, line_bk_flag) + libtcod.console_print(sample_console, 2, 2, "%s (ENTER to change)" % flag_names[line_bk_flag & 0xFF]) + ############################################# # noise sample @@ -394,7 +385,9 @@ def render_lines(first, key, mouse): noise_hurst = libtcod.NOISE_DEFAULT_HURST noise_lacunarity = libtcod.NOISE_DEFAULT_LACUNARITY noise = libtcod.noise_new(2) -noise_img=libtcod.image_new(SAMPLE_SCREEN_WIDTH*2,SAMPLE_SCREEN_HEIGHT*2) +noise_img = libtcod.image_new(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2) + + def render_noise(first, key, mouse): global noise_func, noise_img global noise_dx, noise_dy @@ -409,26 +402,28 @@ def render_noise(first, key, mouse): TURBULENCE_SIMPLEX = 6 FBM_WAVELET = 7 TURBULENCE_WAVELET = 8 - funcName=[ - '1 : perlin noise ', - '2 : simplex noise ', - '3 : wavelet noise ', - '4 : perlin fbm ', - '5 : perlin turbulence ', - '6 : simplex fbm ', - '7 : simplex turbulence ', - '8 : wavelet fbm ', - '9 : wavelet turbulence ', + funcName = [ + "1 : perlin noise ", + "2 : simplex noise ", + "3 : wavelet noise ", + "4 : perlin fbm ", + "5 : perlin turbulence ", + "6 : simplex fbm ", + "7 : simplex turbulence ", + "8 : wavelet fbm ", + "9 : wavelet turbulence ", ] if first: libtcod.sys_set_fps(30) libtcod.console_clear(sample_console) noise_dx += 0.01 noise_dy += 0.01 - for y in range(2*SAMPLE_SCREEN_HEIGHT): - for x in range(2*SAMPLE_SCREEN_WIDTH): - f = [noise_zoom * x / (2*SAMPLE_SCREEN_WIDTH) + noise_dx, - noise_zoom * y / (2*SAMPLE_SCREEN_HEIGHT) + noise_dy] + for y in range(2 * SAMPLE_SCREEN_HEIGHT): + for x in range(2 * SAMPLE_SCREEN_WIDTH): + f = [ + noise_zoom * x / (2 * SAMPLE_SCREEN_WIDTH) + noise_dx, + noise_zoom * y / (2 * SAMPLE_SCREEN_HEIGHT) + noise_dy, + ] value = 0.0 if noise_func == PERLIN: value = libtcod.noise_get(noise, f, libtcod.NOISE_PERLIN) @@ -443,85 +438,75 @@ def render_noise(first, key, mouse): elif noise_func == FBM_SIMPLEX: value = libtcod.noise_get_fbm(noise, f, noise_octaves, libtcod.NOISE_SIMPLEX) elif noise_func == TURBULENCE_SIMPLEX: - value = libtcod.noise_get_turbulence(noise, f, - noise_octaves, libtcod.NOISE_SIMPLEX) + value = libtcod.noise_get_turbulence(noise, f, noise_octaves, libtcod.NOISE_SIMPLEX) elif noise_func == FBM_WAVELET: value = libtcod.noise_get_fbm(noise, f, noise_octaves, libtcod.NOISE_WAVELET) elif noise_func == TURBULENCE_WAVELET: - value = libtcod.noise_get_turbulence(noise, f, - noise_octaves, libtcod.NOISE_WAVELET) + value = libtcod.noise_get_turbulence(noise, f, noise_octaves, libtcod.NOISE_WAVELET) c = int((value + 1.0) / 2.0 * 255) if c < 0: c = 0 elif c > 255: c = 255 col = libtcod.Color(c // 2, c // 2, c) - libtcod.image_put_pixel(noise_img,x,y,col) + libtcod.image_put_pixel(noise_img, x, y, col) libtcod.console_set_default_background(sample_console, libtcod.grey) rectw = 24 recth = 13 if noise_func <= WAVELET: recth = 10 - libtcod.image_blit_2x(noise_img,sample_console,0,0) - libtcod.console_rect(sample_console, 2, 2, rectw, recth, False, - libtcod.BKGND_MULTIPLY) - for y in range(2,2+recth): - for x in range(2,2+rectw): - col=libtcod.console_get_char_foreground(sample_console,x,y) + libtcod.image_blit_2x(noise_img, sample_console, 0, 0) + libtcod.console_rect(sample_console, 2, 2, rectw, recth, False, libtcod.BKGND_MULTIPLY) + for y in range(2, 2 + recth): + for x in range(2, 2 + rectw): + col = libtcod.console_get_char_foreground(sample_console, x, y) col = col * libtcod.grey - libtcod.console_set_char_foreground(sample_console,x,y,col) + libtcod.console_set_char_foreground(sample_console, x, y, col) for curfunc in range(TURBULENCE_WAVELET + 1): if curfunc == noise_func: libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_set_default_background(sample_console, - libtcod.light_blue) - libtcod.console_print_ex(sample_console, 2, 2 + curfunc, - libtcod.BKGND_SET, libtcod.LEFT, funcName[curfunc]) + libtcod.console_set_default_background(sample_console, libtcod.light_blue) + libtcod.console_print_ex(sample_console, 2, 2 + curfunc, libtcod.BKGND_SET, libtcod.LEFT, funcName[curfunc]) else: libtcod.console_set_default_foreground(sample_console, libtcod.grey) - libtcod.console_print(sample_console, 2, 2 + curfunc, - funcName[curfunc]) + libtcod.console_print(sample_console, 2, 2 + curfunc, funcName[curfunc]) libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 2, 11, - 'Y/H : zoom (%2.1f)' % noise_zoom) + libtcod.console_print(sample_console, 2, 11, "Y/H : zoom (%2.1f)" % noise_zoom) if noise_func > WAVELET: - libtcod.console_print(sample_console, 2, 12, - 'E/D : hurst (%2.1f)' % noise_hurst) - libtcod.console_print(sample_console, 2, 13, - 'R/F : lacunarity (%2.1f)' % - noise_lacunarity) - libtcod.console_print(sample_console, 2, 14, - 'T/G : octaves (%2.1f)' % noise_octaves) + libtcod.console_print(sample_console, 2, 12, "E/D : hurst (%2.1f)" % noise_hurst) + libtcod.console_print(sample_console, 2, 13, "R/F : lacunarity (%2.1f)" % noise_lacunarity) + libtcod.console_print(sample_console, 2, 14, "T/G : octaves (%2.1f)" % noise_octaves) if key.vk == libtcod.KEY_NONE: return - if ord('9') >= key.c >= ord('1'): - noise_func = key.c - ord('1') - elif key.c in (ord('E'), ord('e')): + if ord("9") >= key.c >= ord("1"): + noise_func = key.c - ord("1") + elif key.c in (ord("E"), ord("e")): noise_hurst += 0.1 libtcod.noise_delete(noise) - noise = libtcod.noise_new(2,noise_hurst,noise_lacunarity) - elif key.c in (ord('D'), ord('d')): + noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity) + elif key.c in (ord("D"), ord("d")): noise_hurst -= 0.1 libtcod.noise_delete(noise) noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity) - elif key.c in (ord('R'), ord('r')): + elif key.c in (ord("R"), ord("r")): noise_lacunarity += 0.5 libtcod.noise_delete(noise) noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity) - elif key.c in (ord('F'), ord('f')): + elif key.c in (ord("F"), ord("f")): noise_lacunarity -= 0.5 libtcod.noise_delete(noise) noise = libtcod.noise_new(2, noise_hurst, noise_lacunarity) - elif key.c in (ord('T'), ord('t')): + elif key.c in (ord("T"), ord("t")): noise_octaves += 0.5 - elif key.c in (ord('G'), ord('g')): + elif key.c in (ord("G"), ord("g")): noise_octaves -= 0.5 - elif key.c in (ord('Y'), ord('y')): + elif key.c in (ord("Y"), ord("y")): noise_zoom += 0.2 - elif key.c in (ord('H'), ord('h')): + elif key.c in (ord("H"), ord("h")): noise_zoom -= 0.2 + ############################################# # field of view sample ############################################# @@ -539,36 +524,51 @@ def render_noise(first, key, mouse): fov_init = False fov_light_walls = True fov_algo_num = 0 -fov_algo_names = ['BASIC ','DIAMOND ', 'SHADOW ', - 'PERMISSIVE0','PERMISSIVE1','PERMISSIVE2','PERMISSIVE3','PERMISSIVE4', - 'PERMISSIVE5','PERMISSIVE6','PERMISSIVE7','PERMISSIVE8','RESTRICTIVE'] +fov_algo_names = [ + "BASIC ", + "DIAMOND ", + "SHADOW ", + "PERMISSIVE0", + "PERMISSIVE1", + "PERMISSIVE2", + "PERMISSIVE3", + "PERMISSIVE4", + "PERMISSIVE5", + "PERMISSIVE6", + "PERMISSIVE7", + "PERMISSIVE8", + "RESTRICTIVE", +] + + def render_fov(first, key, mouse): global fov_px, fov_py, fov_map, fov_dark_wall, fov_light_wall global fov_dark_ground, fov_light_ground global fov_recompute, fov_torch, fov_noise, fov_torchx, fov_init global fov_light_walls, fov_algo_num, fov_algo_names - smap = ['##############################################', - '####################### #################', - '##################### # ###############', - '###################### ### ###########', - '################## ##### ####', - '################ ######## ###### ####', - '############### #################### ####', - '################ ###### ##', - '######## ####### ###### # # # ##', - '######## ###### ### ##', - '######## ##', - '#### ###### ### # # # ##', - '#### ### ########## #### ##', - '#### ### ########## ###########=##########', - '#### ################## ##### #####', - '#### ### #### ##### #####', - '#### # #### #####', - '######## # #### ##### #####', - '######## ##### ####################', - '##############################################', - ] + smap = [ + "##############################################", + "####################### #################", + "##################### # ###############", + "###################### ### ###########", + "################## ##### ####", + "################ ######## ###### ####", + "############### #################### ####", + "################ ###### ##", + "######## ####### ###### # # # ##", + "######## ###### ### ##", + "######## ##", + "#### ###### ### # # # ##", + "#### ### ########## #### ##", + "#### ### ########## ###########=##########", + "#### ################## ##### #####", + "#### ### #### ##### #####", + "#### # #### #####", + "######## # #### ##### #####", + "######## ##### ####################", + "##############################################", + ] TORCH_RADIUS = 10 SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS dx = 0.0 @@ -579,20 +579,20 @@ def render_fov(first, key, mouse): fov_map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] == ' ': + if smap[y][x] == " ": # ground libtcod.map_set_properties(fov_map, x, y, True, True) - elif smap[y][x] == '=': + elif smap[y][x] == "=": # window libtcod.map_set_properties(fov_map, x, y, True, False) # 1d noise for the torch flickering fov_noise = libtcod.noise_new(1, 1.0, 1.0) - torchs = 'off' - lights = 'off' + torchs = "off" + lights = "off" if fov_torch: - torchs = 'on ' - if fov_light_walls : - lights='on ' + torchs = "on " + if fov_light_walls: + lights = "on " if first: libtcod.sys_set_fps(30) # we draw the foreground only the first time. @@ -601,19 +601,20 @@ def render_fov(first, key, mouse): # draw the help text & player @ libtcod.console_clear(sample_console) libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, 1, - "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" % - (torchs,lights,fov_algo_names[fov_algo_num])) + libtcod.console_print( + sample_console, + 1, + 1, + "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" + % (torchs, lights, fov_algo_names[fov_algo_num]), + ) libtcod.console_set_default_foreground(sample_console, libtcod.black) - libtcod.console_put_char(sample_console, fov_px, fov_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE) # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] == '=': - libtcod.console_put_char(sample_console, x, y, - libtcod.CHAR_DHLINE, - libtcod.BKGND_NONE) + if smap[y][x] == "=": + libtcod.console_put_char(sample_console, x, y, libtcod.CHAR_DHLINE, libtcod.BKGND_NONE) if fov_recompute: fov_recompute = False if fov_torch: @@ -625,32 +626,25 @@ def render_fov(first, key, mouse): fov_torchx += 0.2 # randomize the light position between -1.5 and 1.5 tdx = [fov_torchx + 20.0] - dx = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 + dx = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 tdx[0] += 30.0 - dy = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 + dy = libtcod.noise_get(noise, tdx, libtcod.NOISE_SIMPLEX) * 1.5 di = 0.2 * libtcod.noise_get(noise, [fov_torchx], libtcod.NOISE_SIMPLEX) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): visible = libtcod.map_is_in_fov(fov_map, x, y) - wall = (smap[y][x] == '#') + wall = smap[y][x] == "#" if not visible: if wall: - libtcod.console_set_char_background(sample_console, x, y, - fov_dark_wall, libtcod.BKGND_SET) + libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET) else: - libtcod.console_set_char_background(sample_console, x, y, - fov_dark_ground, - libtcod.BKGND_SET) + libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET) else: if not fov_torch: if wall: - libtcod.console_set_char_background(sample_console, x, y, - fov_light_wall, - libtcod.BKGND_SET ) + libtcod.console_set_char_background(sample_console, x, y, fov_light_wall, libtcod.BKGND_SET) else: - libtcod.console_set_char_background(sample_console, x, y, - fov_light_ground, - libtcod.BKGND_SET ) + libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET) else: if wall: base = fov_dark_wall @@ -659,77 +653,79 @@ def render_fov(first, key, mouse): base = fov_dark_ground light = fov_light_ground # cell distance to torch (squared) - r = float(x - fov_px + dx) * (x - fov_px + dx) + \ - (y - fov_py + dy) * (y - fov_py + dy) + r = float(x - fov_px + dx) * (x - fov_px + dx) + (y - fov_py + dy) * (y - fov_py + dy) if r < SQUARED_TORCH_RADIUS: - l = (SQUARED_TORCH_RADIUS - r) / SQUARED_TORCH_RADIUS \ - + di - if l < 0.0: + l = (SQUARED_TORCH_RADIUS - r) / SQUARED_TORCH_RADIUS + di + if l < 0.0: l = 0.0 - elif l> 1.0: + elif l > 1.0: l = 1.0 base = libtcod.color_lerp(base, light, l) - libtcod.console_set_char_background(sample_console, x, y, base, - libtcod.BKGND_SET) - if key.c in (ord('I'), ord('i')): - if smap[fov_py-1][fov_px] == ' ': - libtcod.console_put_char(sample_console, fov_px, fov_py, ' ', - libtcod.BKGND_NONE) + libtcod.console_set_char_background(sample_console, x, y, base, libtcod.BKGND_SET) + if key.c in (ord("I"), ord("i")): + if smap[fov_py - 1][fov_px] == " ": + libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE) fov_py -= 1 - libtcod.console_put_char(sample_console, fov_px, fov_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE) fov_recompute = True - elif key.c in (ord('K'), ord('k')): - if smap[fov_py+1][fov_px] == ' ': - libtcod.console_put_char(sample_console, fov_px, fov_py, ' ', - libtcod.BKGND_NONE) + elif key.c in (ord("K"), ord("k")): + if smap[fov_py + 1][fov_px] == " ": + libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE) fov_py += 1 - libtcod.console_put_char(sample_console, fov_px, fov_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE) fov_recompute = True - elif key.c in (ord('J'), ord('j')): - if smap[fov_py][fov_px-1] == ' ': - libtcod.console_put_char(sample_console, fov_px, fov_py, ' ', - libtcod.BKGND_NONE) + elif key.c in (ord("J"), ord("j")): + if smap[fov_py][fov_px - 1] == " ": + libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE) fov_px -= 1 - libtcod.console_put_char(sample_console, fov_px, fov_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE) fov_recompute = True - elif key.c in (ord('L'), ord('l')): - if smap[fov_py][fov_px+1] == ' ': - libtcod.console_put_char(sample_console, fov_px, fov_py, ' ', - libtcod.BKGND_NONE) + elif key.c in (ord("L"), ord("l")): + if smap[fov_py][fov_px + 1] == " ": + libtcod.console_put_char(sample_console, fov_px, fov_py, " ", libtcod.BKGND_NONE) fov_px += 1 - libtcod.console_put_char(sample_console, fov_px, fov_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, fov_px, fov_py, "@", libtcod.BKGND_NONE) fov_recompute = True - elif key.c in (ord('T'), ord('t')): + elif key.c in (ord("T"), ord("t")): fov_torch = not fov_torch libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, 1, - "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" % - (torchs,lights,fov_algo_names[fov_algo_num])) + libtcod.console_print( + sample_console, + 1, + 1, + "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" + % (torchs, lights, fov_algo_names[fov_algo_num]), + ) libtcod.console_set_default_foreground(sample_console, libtcod.black) - elif key.c in (ord('W'), ord('w')): + elif key.c in (ord("W"), ord("w")): fov_light_walls = not fov_light_walls libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, 1, - "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" % - (torchs,lights,fov_algo_names[fov_algo_num])) + libtcod.console_print( + sample_console, + 1, + 1, + "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" + % (torchs, lights, fov_algo_names[fov_algo_num]), + ) libtcod.console_set_default_foreground(sample_console, libtcod.black) fov_recompute = True - elif key.c in (ord('+'), ord('-')): - if key.c == ord('+') and fov_algo_num < libtcod.NB_FOV_ALGORITHMS-1: + elif key.c in (ord("+"), ord("-")): + if key.c == ord("+") and fov_algo_num < libtcod.NB_FOV_ALGORITHMS - 1: fov_algo_num = fov_algo_num + 1 - elif fov_algo_num > 0 : + elif fov_algo_num > 0: fov_algo_num = fov_algo_num - 1 libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_print(sample_console, 1, 1, - "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" % - (torchs,lights,fov_algo_names[fov_algo_num])) + libtcod.console_print( + sample_console, + 1, + 1, + "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s" + % (torchs, lights, fov_algo_names[fov_algo_num]), + ) libtcod.console_set_default_foreground(sample_console, libtcod.black) fov_recompute = True + ############################################# # pathfinding sample ############################################# @@ -744,34 +740,37 @@ def render_fov(first, key, mouse): path_dijk = None path_recalculate = False path_busy = 0.0 -path_oldchar = ' ' +path_oldchar = " " path_init = False + + def render_path(first, key, mouse): global path_px, path_py, path_dx, path_dy, path_map, path, path_busy global path_oldchar, path_init, path_recalculate global path_dijk_dist, path_using_astar, path_dijk - smap = ['##############################################', - '####################### #################', - '##################### # ###############', - '###################### ### ###########', - '################## ##### ####', - '################ ######## ###### ####', - '############### #################### ####', - '################ ###### ##', - '######## ####### ###### # # # ##', - '######## ###### ### ##', - '######## ##', - '#### ###### ### # # # ##', - '#### ### ########## #### ##', - '#### ### ########## ###########=##########', - '#### ################## ##### #####', - '#### ### #### ##### #####', - '#### # #### #####', - '######## # #### ##### #####', - '######## ##### ####################', - '##############################################', - ] + smap = [ + "##############################################", + "####################### #################", + "##################### # ###############", + "###################### ### ###########", + "################## ##### ####", + "################ ######## ###### ####", + "############### #################### ####", + "################ ###### ##", + "######## ####### ###### # # # ##", + "######## ###### ### ##", + "######## ##", + "#### ###### ### # # # ##", + "#### ### ########## #### ##", + "#### ### ########## ###########=##########", + "#### ################## ##### #####", + "#### ### #### ##### #####", + "#### # #### #####", + "######## # #### ##### #####", + "######## ##### ####################", + "##############################################", + ] TORCH_RADIUS = 10.0 SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS if not path_init: @@ -779,10 +778,10 @@ def render_path(first, key, mouse): path_map = libtcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] == ' ': + if smap[y][x] == " ": # ground libtcod.map_set_properties(path_map, x, y, True, True) - elif smap[y][x] == '=': + elif smap[y][x] == "=": # window libtcod.map_set_properties(path_map, x, y, True, False) path = libtcod.path_new_using_map(path_map) @@ -795,153 +794,132 @@ def render_path(first, key, mouse): # draw the help text & player @ libtcod.console_clear(sample_console) libtcod.console_set_default_foreground(sample_console, libtcod.white) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - libtcod.console_put_char(sample_console, path_px, path_py, '@', - libtcod.BKGND_NONE) - libtcod.console_print(sample_console, 1, 1, - "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra") - libtcod.console_print(sample_console, 1, 4, - "Using : A*") + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE) + libtcod.console_print(sample_console, 1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra") + libtcod.console_print(sample_console, 1, 4, "Using : A*") # draw windows for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] == '=': - libtcod.console_put_char(sample_console, x, y, - libtcod.CHAR_DHLINE, - libtcod.BKGND_NONE) + if smap[y][x] == "=": + libtcod.console_put_char(sample_console, x, y, libtcod.CHAR_DHLINE, libtcod.BKGND_NONE) path_recalculate = True if path_recalculate: - if path_using_astar : + if path_using_astar: libtcod.path_compute(path, path_px, path_py, path_dx, path_dy) else: path_dijk_dist = 0.0 # compute dijkstra grid (distance from px,py) - libtcod.dijkstra_compute(path_dijk,path_px,path_py) + libtcod.dijkstra_compute(path_dijk, path_px, path_py) # get the maximum distance (needed for rendering) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - d=libtcod.dijkstra_get_distance(path_dijk,x,y) + d = libtcod.dijkstra_get_distance(path_dijk, x, y) if d > path_dijk_dist: - path_dijk_dist=d + path_dijk_dist = d # compute path from px,py to dx,dy - libtcod.dijkstra_path_set(path_dijk,path_dx,path_dy) + libtcod.dijkstra_path_set(path_dijk, path_dx, path_dy) path_recalculate = False path_busy = 0.2 # draw the dungeon for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] == '#': - libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, - libtcod.BKGND_SET) + if smap[y][x] == "#": + libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET) else: - libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, - libtcod.BKGND_SET) + libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET) # draw the path - if path_using_astar : + if path_using_astar: for i in range(libtcod.path_size(path)): - x,y = libtcod.path_get(path, i) - libtcod.console_set_char_background(sample_console, x, y, - fov_light_ground, libtcod.BKGND_SET) + x, y = libtcod.path_get(path, i) + libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET) else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - if smap[y][x] != '#': - libtcod.console_set_char_background(sample_console, x, y, libtcod.color_lerp(fov_light_ground,fov_dark_ground, - 0.9 * libtcod.dijkstra_get_distance(path_dijk,x,y) / path_dijk_dist), libtcod.BKGND_SET) + if smap[y][x] != "#": + libtcod.console_set_char_background( + sample_console, + x, + y, + libtcod.color_lerp( + fov_light_ground, + fov_dark_ground, + 0.9 * libtcod.dijkstra_get_distance(path_dijk, x, y) / path_dijk_dist, + ), + libtcod.BKGND_SET, + ) for i in range(libtcod.dijkstra_size(path_dijk)): - x,y=libtcod.dijkstra_get(path_dijk,i) - libtcod.console_set_char_background(sample_console,x,y,fov_light_ground, libtcod.BKGND_SET ) + x, y = libtcod.dijkstra_get(path_dijk, i) + libtcod.console_set_char_background(sample_console, x, y, fov_light_ground, libtcod.BKGND_SET) # move the creature path_busy -= libtcod.sys_get_last_frame_length() if path_busy <= 0.0: path_busy = 0.2 - if path_using_astar : + if path_using_astar: if not libtcod.path_is_empty(path): - libtcod.console_put_char(sample_console, path_px, path_py, ' ', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_px, path_py, " ", libtcod.BKGND_NONE) path_px, path_py = libtcod.path_walk(path, True) - libtcod.console_put_char(sample_console, path_px, path_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE) else: if not libtcod.dijkstra_is_empty(path_dijk): - libtcod.console_put_char(sample_console, path_px, path_py, ' ', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_px, path_py, " ", libtcod.BKGND_NONE) path_px, path_py = libtcod.dijkstra_path_walk(path_dijk) - libtcod.console_put_char(sample_console, path_px, path_py, '@', - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_px, path_py, "@", libtcod.BKGND_NONE) path_recalculate = True - if key.c in (ord('I'), ord('i')) and path_dy > 0: + if key.c in (ord("I"), ord("i")) and path_dy > 0: # destination move north - libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE) path_dy -= 1 - path_oldchar = libtcod.console_get_char(sample_console, path_dx, - path_dy) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - if smap[path_dy][path_dx] == ' ': + path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy) + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + if smap[path_dy][path_dx] == " ": path_recalculate = True - elif key.c in (ord('K'), ord('k')) and path_dy < SAMPLE_SCREEN_HEIGHT - 1: + elif key.c in (ord("K"), ord("k")) and path_dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE) path_dy += 1 - path_oldchar = libtcod.console_get_char(sample_console, path_dx, - path_dy) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - if smap[path_dy][path_dx] == ' ': + path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy) + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + if smap[path_dy][path_dx] == " ": path_recalculate = True - elif key.c in (ord('J'), ord('j')) and path_dx > 0: + elif key.c in (ord("J"), ord("j")) and path_dx > 0: # destination move west - libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE) path_dx -= 1 - path_oldchar = libtcod.console_get_char(sample_console, path_dx, - path_dy) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - if smap[path_dy][path_dx] == ' ': + path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy) + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + if smap[path_dy][path_dx] == " ": path_recalculate = True - elif key.c in (ord('L'), ord('l')) and path_dx < SAMPLE_SCREEN_WIDTH - 1: + elif key.c in (ord("L"), ord("l")) and path_dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, - libtcod.BKGND_NONE) + libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE) path_dx += 1 - path_oldchar = libtcod.console_get_char(sample_console, path_dx, - path_dy) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - if smap[path_dy][path_dx] == ' ': + path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy) + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + if smap[path_dy][path_dx] == " ": path_recalculate = True elif key.vk == libtcod.KEY_TAB: path_using_astar = not path_using_astar - if path_using_astar : - libtcod.console_print(sample_console, 1, 4, - "Using : A* ") + if path_using_astar: + libtcod.console_print(sample_console, 1, 4, "Using : A* ") else: - libtcod.console_print(sample_console, 1, 4, - "Using : Dijkstra") - path_recalculate=True + libtcod.console_print(sample_console, 1, 4, "Using : Dijkstra") + path_recalculate = True mx = mouse.cx - SAMPLE_SCREEN_X my = mouse.cy - SAMPLE_SCREEN_Y - if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and \ - (path_dx != mx or path_dy != my): - libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, - libtcod.BKGND_NONE) + if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (path_dx != mx or path_dy != my): + libtcod.console_put_char(sample_console, path_dx, path_dy, path_oldchar, libtcod.BKGND_NONE) path_dx = mx path_dy = my - path_oldchar = libtcod.console_get_char(sample_console, path_dx, - path_dy) - libtcod.console_put_char(sample_console, path_dx, path_dy, '+', - libtcod.BKGND_NONE) - if smap[path_dy][path_dx] == ' ': + path_oldchar = libtcod.console_get_char(sample_console, path_dx, path_dy) + libtcod.console_put_char(sample_console, path_dx, path_dy, "+", libtcod.BKGND_NONE) + if smap[path_dy][path_dx] == " ": path_recalculate = True + ############################################# # bsp sample ############################################# @@ -955,41 +933,47 @@ def render_path(first, key, mouse): # draw a vertical line def vline(m, x, y1, y2): if y1 > y2: - y1,y2 = y2,y1 - for y in range(y1,y2+1): + y1, y2 = y2, y1 + for y in range(y1, y2 + 1): m[x][y] = True + # draw a vertical line up until we reach an empty space def vline_up(m, x, y): while y >= 0 and not m[x][y]: m[x][y] = True y -= 1 + # draw a vertical line down until we reach an empty space def vline_down(m, x, y): while y < SAMPLE_SCREEN_HEIGHT and not m[x][y]: m[x][y] = True y += 1 + # draw a horizontal line def hline(m, x1, y, x2): if x1 > x2: - x1,x2 = x2,x1 - for x in range(x1,x2+1): + x1, x2 = x2, x1 + for x in range(x1, x2 + 1): m[x][y] = True + # draw a horizontal line left until we reach an empty space def hline_left(m, x, y): while x >= 0 and not m[x][y]: m[x][y] = True x -= 1 + # draw a horizontal line right until we reach an empty space def hline_right(m, x, y): while x < SAMPLE_SCREEN_WIDTH and not m[x][y]: - m[x][y]=True + m[x][y] = True x += 1 + # the class building the dungeon from the bsp nodes def traverse_node(node, *dat): global bsp_map @@ -1003,7 +987,7 @@ def traverse_node(node, *dat): if minx > 1: minx -= 1 if miny > 1: - miny -=1 + miny -= 1 if maxx == SAMPLE_SCREEN_WIDTH - 1: maxx -= 1 if maxy == SAMPLE_SCREEN_HEIGHT - 1: @@ -1016,8 +1000,8 @@ def traverse_node(node, *dat): # resize the node to fit the room node.x = minx node.y = miny - node.w = maxx-minx + 1 - node.h = maxy-miny + 1 + node.w = maxx - minx + 1 + node.h = maxy - miny + 1 # dig the room for x in range(minx, maxx + 1): for y in range(miny, maxy + 1): @@ -1067,9 +1051,12 @@ def traverse_node(node, *dat): hline_right(bsp_map, right.x, y) return True + bsp = None bsp_generate = True bsp_refresh = False + + def render_bsp(first, key, mouse): global bsp, bsp_generate, bsp_refresh, bsp_map global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size @@ -1077,12 +1064,10 @@ def render_bsp(first, key, mouse): # dungeon generation if bsp is None: # create the bsp - bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT) + bsp = libtcod.bsp_new_with_size(0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) else: # restore the nodes size - libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH, - SAMPLE_SCREEN_HEIGHT) + libtcod.bsp_resize(bsp, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) bsp_map = list() for x in range(SAMPLE_SCREEN_WIDTH): bsp_map.append([False] * SAMPLE_SCREEN_HEIGHT) @@ -1090,69 +1075,66 @@ def render_bsp(first, key, mouse): # build a new random bsp tree libtcod.bsp_remove_sons(bsp) if bsp_room_walls: - libtcod.bsp_split_recursive(bsp, 0, bsp_depth, - bsp_min_room_size + 1, - bsp_min_room_size + 1, 1.5, 1.5) + libtcod.bsp_split_recursive(bsp, 0, bsp_depth, bsp_min_room_size + 1, bsp_min_room_size + 1, 1.5, 1.5) else: - libtcod.bsp_split_recursive(bsp, 0, bsp_depth, - bsp_min_room_size, - bsp_min_room_size, 1.5, 1.5) + libtcod.bsp_split_recursive(bsp, 0, bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5) # create the dungeon from the bsp libtcod.bsp_traverse_inverted_level_order(bsp, traverse_node) bsp_generate = False bsp_refresh = False libtcod.console_clear(sample_console) libtcod.console_set_default_foreground(sample_console, libtcod.white) - rooms = 'OFF' + rooms = "OFF" if bsp_random_room: - rooms = 'ON' - libtcod.console_print(sample_console, 1, 1, - "ENTER : rebuild bsp\n" - "SPACE : rebuild dungeon\n" - "+-: bsp depth %d\n" - "*/: room size %d\n" - "1 : random room size %s" % (bsp_depth, - bsp_min_room_size, rooms)) + rooms = "ON" + libtcod.console_print( + sample_console, + 1, + 1, + "ENTER : rebuild bsp\n" + "SPACE : rebuild dungeon\n" + "+-: bsp depth %d\n" + "*/: room size %d\n" + "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), + ) if bsp_random_room: - walls = 'OFF' + walls = "OFF" if bsp_room_walls: - walls ='ON' - libtcod.console_print(sample_console, 1, 6, - '2 : room walls %s' % walls) + walls = "ON" + libtcod.console_print(sample_console, 1, 6, "2 : room walls %s" % walls) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if not bsp_map[x][y]: - libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, - libtcod.BKGND_SET) + libtcod.console_set_char_background(sample_console, x, y, fov_dark_wall, libtcod.BKGND_SET) else: - libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, - libtcod.BKGND_SET) - if key.vk in (libtcod.KEY_ENTER ,libtcod.KEY_KPENTER): + libtcod.console_set_char_background(sample_console, x, y, fov_dark_ground, libtcod.BKGND_SET) + if key.vk in (libtcod.KEY_ENTER, libtcod.KEY_KPENTER): bsp_generate = True - elif key.c==ord(' '): + elif key.c == ord(" "): bsp_refresh = True - elif key.c == ord('+'): + elif key.c == ord("+"): bsp_depth += 1 bsp_generate = True - elif key.c == ord('-') and bsp_depth > 1: + elif key.c == ord("-") and bsp_depth > 1: bsp_depth -= 1 bsp_generate = True - elif key.c==ord('*'): + elif key.c == ord("*"): bsp_min_room_size += 1 bsp_generate = True - elif key.c == ord('/') and bsp_min_room_size > 2: + elif key.c == ord("/") and bsp_min_room_size > 2: bsp_min_room_size -= 1 bsp_generate = True - elif key.c == ord('1') or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1): + elif key.c == ord("1") or key.vk in (libtcod.KEY_1, libtcod.KEY_KP1): bsp_random_room = not bsp_random_room if not bsp_random_room: bsp_room_walls = True bsp_refresh = True - elif key.c == ord('2') or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2): + elif key.c == ord("2") or key.vk in (libtcod.KEY_2, libtcod.KEY_KP2): bsp_room_walls = not bsp_room_walls bsp_refresh = True + ############################################# # image sample ############################################# @@ -1160,19 +1142,21 @@ def render_bsp(first, key, mouse): img_circle = None img_blue = libtcod.Color(0, 0, 255) img_green = libtcod.Color(0, 255, 0) + + def render_image(first, key, mouse): - global img,img_circle,img_blue,img_green + global img, img_circle, img_blue, img_green if img is None: - img = libtcod.image_load(get_data('img/skull.png')) - libtcod.image_set_key_color(img,libtcod.black) - img_circle = libtcod.image_load(get_data(('img/circle.png'))) + img = libtcod.image_load(get_data("img/skull.png")) + libtcod.image_set_key_color(img, libtcod.black) + img_circle = libtcod.image_load(get_data(("img/circle.png"))) if first: libtcod.sys_set_fps(30) libtcod.console_set_default_background(sample_console, libtcod.black) libtcod.console_clear(sample_console) x = SAMPLE_SCREEN_WIDTH / 2 + math.cos(libtcod.sys_elapsed_seconds()) * 10.0 y = float(SAMPLE_SCREEN_HEIGHT / 2) - scalex=0.2 + 1.8 * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0 + scalex = 0.2 + 1.8 * (1.0 + math.cos(libtcod.sys_elapsed_seconds() / 2)) / 2.0 scaley = scalex angle = libtcod.sys_elapsed_seconds() elapsed = libtcod.sys_elapsed_milli() // 2000 @@ -1180,32 +1164,23 @@ def render_image(first, key, mouse): # split the color channels of circle.png # the red channel libtcod.console_set_default_background(sample_console, libtcod.red) - libtcod.console_rect(sample_console, 0, 3, 15, 15, False, - libtcod.BKGND_SET) - libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, - libtcod.BKGND_MULTIPLY) + libtcod.console_rect(sample_console, 0, 3, 15, 15, False, libtcod.BKGND_SET) + libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, libtcod.BKGND_MULTIPLY) # the green channel libtcod.console_set_default_background(sample_console, img_green) - libtcod.console_rect(sample_console, 15, 3, 15, 15, False, - libtcod.BKGND_SET) - libtcod.image_blit_rect(img_circle,sample_console, 15, 3, -1, -1, - libtcod.BKGND_MULTIPLY) + libtcod.console_rect(sample_console, 15, 3, 15, 15, False, libtcod.BKGND_SET) + libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1, libtcod.BKGND_MULTIPLY) # the blue channel libtcod.console_set_default_background(sample_console, img_blue) - libtcod.console_rect(sample_console, 30, 3, 15, 15, False, - libtcod.BKGND_SET) - libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, - libtcod.BKGND_MULTIPLY) + libtcod.console_rect(sample_console, 30, 3, 15, 15, False, libtcod.BKGND_SET) + libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, libtcod.BKGND_MULTIPLY) else: # render circle.png with normal blitting - libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, - libtcod.BKGND_SET) - libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1, - libtcod.BKGND_SET) - libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, - libtcod.BKGND_SET) - libtcod.image_blit(img, sample_console, x, y, libtcod.BKGND_SET, - scalex, scaley, angle) + libtcod.image_blit_rect(img_circle, sample_console, 0, 3, -1, -1, libtcod.BKGND_SET) + libtcod.image_blit_rect(img_circle, sample_console, 15, 3, -1, -1, libtcod.BKGND_SET) + libtcod.image_blit_rect(img_circle, sample_console, 30, 3, -1, -1, libtcod.BKGND_SET) + libtcod.image_blit(img, sample_console, x, y, libtcod.BKGND_SET, scalex, scaley, angle) + ############################################# # mouse sample @@ -1213,15 +1188,16 @@ def render_image(first, key, mouse): mouse_lbut = 0 mouse_mbut = 0 mouse_rbut = 0 + + def render_mouse(first, key, mouse): global mouse_lbut global mouse_mbut global mouse_rbut - butstatus=('OFF', 'ON') + butstatus = ("OFF", "ON") if first: libtcod.console_set_default_background(sample_console, libtcod.grey) - libtcod.console_set_default_foreground(sample_console, - libtcod.light_yellow) + libtcod.console_set_default_foreground(sample_console, libtcod.light_yellow) libtcod.mouse_move(320, 200) libtcod.mouse_show_cursor(True) libtcod.sys_set_fps(30) @@ -1232,33 +1208,45 @@ def render_mouse(first, key, mouse): mouse_rbut = 1 - mouse_rbut if mouse.mbutton_pressed: mouse_mbut = 1 - mouse_mbut - wheel="" - if mouse.wheel_up : - wheel="UP" - elif mouse.wheel_down : - wheel="DOWN" - libtcod.console_print(sample_console, 1, 1, - "Mouse position : %4dx%4d\n" - "Mouse cell : %4dx%4d\n" - "Mouse movement : %4dx%4d\n" - "Left button : %s (toggle %s)\n" - "Right button : %s (toggle %s)\n" - "Middle button : %s (toggle %s)\n" - "Wheel : %s" % - (mouse.x, mouse.y, - mouse.cx, mouse.cy, - mouse.dx, mouse.dy, - butstatus[mouse.lbutton], butstatus[mouse_lbut], - butstatus[mouse.rbutton], butstatus[mouse_rbut], - butstatus[mouse.mbutton], butstatus[mouse_mbut], - wheel)) - libtcod.console_print(sample_console, 1, 10, - "1 : Hide cursor\n2 : Show cursor") - if key.c == ord('1'): + wheel = "" + if mouse.wheel_up: + wheel = "UP" + elif mouse.wheel_down: + wheel = "DOWN" + libtcod.console_print( + sample_console, + 1, + 1, + "Mouse position : %4dx%4d\n" + "Mouse cell : %4dx%4d\n" + "Mouse movement : %4dx%4d\n" + "Left button : %s (toggle %s)\n" + "Right button : %s (toggle %s)\n" + "Middle button : %s (toggle %s)\n" + "Wheel : %s" + % ( + mouse.x, + mouse.y, + mouse.cx, + mouse.cy, + mouse.dx, + mouse.dy, + butstatus[mouse.lbutton], + butstatus[mouse_lbut], + butstatus[mouse.rbutton], + butstatus[mouse_rbut], + butstatus[mouse.mbutton], + butstatus[mouse_mbut], + wheel, + ), + ) + libtcod.console_print(sample_console, 1, 10, "1 : Hide cursor\n2 : Show cursor") + if key.c == ord("1"): libtcod.mouse_show_cursor(False) - elif key.c == ord('2'): + elif key.c == ord("2"): libtcod.mouse_show_cursor(True) + ############################################# # name generator sample ############################################# @@ -1267,6 +1255,8 @@ def render_mouse(first, key, mouse): ng_delay = 0.0 ng_names = [] ng_sets = None + + def render_name(first, key, mouse): global ng_curset global ng_nbsets @@ -1275,118 +1265,127 @@ def render_name(first, key, mouse): global ng_sets if ng_nbsets == 0: # parse all *.cfg files in data/namegen - for file in os.listdir(get_data('namegen')) : - if file.find(b'.cfg') > 0 : - libtcod.namegen_parse(get_data(os.path.join('namegen',file))) + for file in os.listdir(get_data("namegen")): + if file.find(b".cfg") > 0: + libtcod.namegen_parse(get_data(os.path.join("namegen", file))) # get the sets list - ng_sets=libtcod.namegen_get_sets() - print (ng_sets) - ng_nbsets=len(ng_sets) + ng_sets = libtcod.namegen_get_sets() + print(ng_sets) + ng_nbsets = len(ng_sets) if first: libtcod.sys_set_fps(30) - while len(ng_names)> 15: + while len(ng_names) > 15: ng_names.pop(0) libtcod.console_clear(sample_console) - libtcod.console_set_default_foreground(sample_console,libtcod.white) - libtcod.console_print(sample_console,1,1,"%s\n\n+ : next generator\n- : prev generator" % - ng_sets[ng_curset]) - for i in range(len(ng_names)) : - libtcod.console_print_ex(sample_console,SAMPLE_SCREEN_WIDTH-2,2+i, - libtcod.BKGND_NONE,libtcod.RIGHT,ng_names[i]) + libtcod.console_set_default_foreground(sample_console, libtcod.white) + libtcod.console_print(sample_console, 1, 1, "%s\n\n+ : next generator\n- : prev generator" % ng_sets[ng_curset]) + for i in range(len(ng_names)): + libtcod.console_print_ex( + sample_console, SAMPLE_SCREEN_WIDTH - 2, 2 + i, libtcod.BKGND_NONE, libtcod.RIGHT, ng_names[i] + ) ng_delay += libtcod.sys_get_last_frame_length() - if ng_delay > 0.5 : + if ng_delay > 0.5: ng_delay -= 0.5 ng_names.append(libtcod.namegen_generate(ng_sets[ng_curset])) - if key.c == ord('+'): + if key.c == ord("+"): ng_curset += 1 - if ng_curset == ng_nbsets : - ng_curset=0 + if ng_curset == ng_nbsets: + ng_curset = 0 ng_names.append("======") - elif key.c == ord('-'): + elif key.c == ord("-"): ng_curset -= 1 - if ng_curset < 0 : - ng_curset=ng_nbsets-1 + if ng_curset < 0: + ng_curset = ng_nbsets - 1 ng_names.append("======") + ############################################# # python fast render sample ############################################# -try: #import NumPy +try: # import NumPy import numpy as np + numpy_available = True except ImportError: numpy_available = False -use_numpy = numpy_available #default option +use_numpy = numpy_available # default option SCREEN_W = SAMPLE_SCREEN_WIDTH SCREEN_H = SAMPLE_SCREEN_HEIGHT HALF_W = SCREEN_W // 2 HALF_H = SCREEN_H // 2 -RES_U = 80 #texture resolution +RES_U = 80 # texture resolution RES_V = 80 -TEX_STRETCH = 5 #texture stretching with tunnel depth +TEX_STRETCH = 5 # texture stretching with tunnel depth SPEED = 15 -LIGHT_BRIGHTNESS = 3.5 #brightness multiplier for all lights (changes their radius) -LIGHTS_CHANCE = 0.07 #chance of a light appearing +LIGHT_BRIGHTNESS = 3.5 # brightness multiplier for all lights (changes their radius) +LIGHTS_CHANCE = 0.07 # chance of a light appearing MAX_LIGHTS = 6 MIN_LIGHT_STRENGTH = 0.2 -LIGHT_UPDATE = 0.05 #how much the ambient light changes to reflect current light sources -AMBIENT_LIGHT = 0.8 #brightness of tunnel texture +LIGHT_UPDATE = 0.05 # how much the ambient light changes to reflect current light sources +AMBIENT_LIGHT = 0.8 # brightness of tunnel texture -#the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen) -#xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] -#yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] +# the coordinates of all tiles in the screen, as numpy arrays. example: (4x3 pixels screen) +# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] +# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] if numpy_available: (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) - #translate coordinates of all pixels to center + # translate coordinates of all pixels to center xc = xc - HALF_W yc = yc - HALF_H noise2d = libtcod.noise_new(2, 0.5, 2.0) -if numpy_available: #the texture starts empty +if numpy_available: # the texture starts empty texture = np.zeros((RES_U, RES_V)) -#create lists to work without numpy +# create lists to work without numpy texture2 = [0 for i in range(RES_U * RES_V)] brightness2 = [0 for i in range(SCREEN_W * SCREEN_H)] R2 = [0 for i in range(SCREEN_W * SCREEN_H)] G2 = [0 for i in range(SCREEN_W * SCREEN_H)] B2 = [0 for i in range(SCREEN_W * SCREEN_H)] + class Light: def __init__(self, x, y, z, r, g, b, strength): - self.x, self.y, self.z = x, y, z #pos. - self.r, self.g, self.b = r, g, b #color - self.strength = strength #between 0 and 1, defines brightness + self.x, self.y, self.z = x, y, z # pos. + self.r, self.g, self.b = r, g, b # color + self.strength = strength # between 0 and 1, defines brightness + def render_py(first, key, mouse): global use_numpy, frac_t, abs_t, lights, tex_r, tex_g, tex_b, xc, yc, texture, texture2, brightness2, R2, G2, B2 - if key.c == ord(' ') and numpy_available: #toggle renderer + if key.c == ord(" ") and numpy_available: # toggle renderer use_numpy = not use_numpy first = True - if first: #initialize stuff + if first: # initialize stuff libtcod.sys_set_fps(0) - libtcod.console_clear(sample_console) #render status message - if not numpy_available: text = 'NumPy uninstalled, using default renderer' - elif use_numpy: text = 'Renderer: NumPy \nSpacebar to change' - else: text = 'Renderer: default\nSpacebar to change' - libtcod.console_set_default_foreground(sample_console,libtcod.white) + libtcod.console_clear(sample_console) # render status message + if not numpy_available: + text = "NumPy uninstalled, using default renderer" + elif use_numpy: + text = "Renderer: NumPy \nSpacebar to change" + else: + text = "Renderer: default\nSpacebar to change" + libtcod.console_set_default_foreground(sample_console, libtcod.white) libtcod.console_print(sample_console, 1, SCREEN_H - 3, text) - frac_t = RES_V - 1 #time is represented in number of pixels of the texture, start later in time to initialize texture + frac_t = ( + RES_V - 1 + ) # time is represented in number of pixels of the texture, start later in time to initialize texture abs_t = RES_V - 1 - lights = [] #lights list, and current color of the tunnel texture + lights = [] # lights list, and current color of the tunnel texture tex_r, tex_g, tex_b = 0, 0, 0 - time_delta = libtcod.sys_get_last_frame_length() * SPEED #advance time - frac_t += time_delta #increase fractional (always < 1.0) time - abs_t += time_delta #increase absolute elapsed time - int_t = int(frac_t) #integer time units that passed this frame (number of texture pixels to advance) - frac_t -= int_t #keep this < 1.0 + time_delta = libtcod.sys_get_last_frame_length() * SPEED # advance time + frac_t += time_delta # increase fractional (always < 1.0) time + abs_t += time_delta # increase absolute elapsed time + int_t = int(frac_t) # integer time units that passed this frame (number of texture pixels to advance) + frac_t -= int_t # keep this < 1.0 - #change texture color according to presence of lights (basically, sum them - #to get ambient light and smoothly change the current color into that) + # change texture color according to presence of lights (basically, sum them + # to get ambient light and smoothly change the current color into that) ambient_r = AMBIENT_LIGHT * sum([light.r * light.strength for light in lights]) ambient_g = AMBIENT_LIGHT * sum([light.g * light.strength for light in lights]) ambient_b = AMBIENT_LIGHT * sum([light.b * light.strength for light in lights]) @@ -1395,47 +1394,48 @@ def render_py(first, key, mouse): tex_g = tex_g * (1 - alpha) + ambient_g * alpha tex_b = tex_b * (1 - alpha) + ambient_b * alpha - if int_t >= 1: #roll texture (ie, advance in tunnel) according to int_t - int_t = int_t % RES_V #can't roll more than the texture's size (can happen when time_delta is large) - int_abs_t = int(abs_t) #new pixels are based on absolute elapsed time + if int_t >= 1: # roll texture (ie, advance in tunnel) according to int_t + int_t = int_t % RES_V # can't roll more than the texture's size (can happen when time_delta is large) + int_abs_t = int(abs_t) # new pixels are based on absolute elapsed time if use_numpy: texture = np.roll(texture, -int_t, 1) - #replace new stretch of texture with new values + # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(0, RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u,v] = (libtcod.noise_get_fbm(noise2d, [u/float(RES_U), tex_v], 32.0) + - libtcod.noise_get_fbm(noise2d, [1 - u/float(RES_U), tex_v], 32.0)) + texture[u, v] = libtcod.noise_get_fbm( + noise2d, [u / float(RES_U), tex_v], 32.0 + ) + libtcod.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0) - else: #"roll" texture, without numpy - temp = texture2[0 : RES_U*int_t] - texture2 = texture2[RES_U*int_t : ] + else: # "roll" texture, without numpy + temp = texture2[0 : RES_U * int_t] + texture2 = texture2[RES_U * int_t :] texture2.extend(temp) - #replace new stretch of texture with new values + # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(0, RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture2[u + v*RES_U] = ( - libtcod.noise_get_fbm(noise2d, [u/float(RES_U), tex_v], 32.0) + - libtcod.noise_get_fbm(noise2d, [1 - u/float(RES_U), tex_v], 32.0)) + texture2[u + v * RES_U] = libtcod.noise_get_fbm( + noise2d, [u / float(RES_U), tex_v], 32.0 + ) + libtcod.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0) if use_numpy: - #squared distance from center, clipped to sensible minimum and maximum values - sqr_dist = xc**2 + yc**2 - sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V**2) + # squared distance from center, clipped to sensible minimum and maximum values + sqr_dist = xc ** 2 + yc ** 2 + sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2) - #one coordinate into the texture, represents depth in the tunnel + # one coordinate into the texture, represents depth in the tunnel v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t v = v.clip(0, RES_V - 1) - #another coordinate, represents rotation around the tunnel + # another coordinate, represents rotation around the tunnel u = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U) - #retrieve corresponding pixels from texture + # retrieve corresponding pixels from texture brightness = texture[u.astype(int), v.astype(int)] / 4.0 + 0.5 - #use the brightness map to compose the final color of the tunnel + # use the brightness map to compose the final color of the tunnel R = brightness * tex_r G = brightness * tex_g B = brightness * tex_b @@ -1443,62 +1443,62 @@ def render_py(first, key, mouse): i = 0 for y in range(-HALF_H, HALF_H): for x in range(-HALF_W, HALF_W): - #squared distance from center, clipped to sensible minimum and maximum values - sqr_dist = x**2 + y**2 - sqr_dist = min(max(sqr_dist, 1.0 / RES_V), RES_V**2) + # squared distance from center, clipped to sensible minimum and maximum values + sqr_dist = x ** 2 + y ** 2 + sqr_dist = min(max(sqr_dist, 1.0 / RES_V), RES_V ** 2) - #one coordinate into the texture, represents depth in the tunnel + # one coordinate into the texture, represents depth in the tunnel v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t v = min(v, RES_V - 1) - #another coordinate, represents rotation around the tunnel + # another coordinate, represents rotation around the tunnel u = (RES_U * (math.atan2(y, x) / (2 * math.pi) + 0.5)) % RES_U - #retrieve corresponding pixels from texture - brightness = texture2[int(u) + int(v)*RES_U] / 4.0 + 0.5 + # retrieve corresponding pixels from texture + brightness = texture2[int(u) + int(v) * RES_U] / 4.0 + 0.5 - #use the brightness map to compose the final color of the tunnel + # use the brightness map to compose the final color of the tunnel R2[i] = brightness * tex_r G2[i] = brightness * tex_g B2[i] = brightness * tex_b i += 1 - #create new light source + # create new light source if libtcod.random_get_float(0, 0, 1) <= time_delta * LIGHTS_CHANCE and len(lights) < MAX_LIGHTS: x = libtcod.random_get_float(0, -0.5, 0.5) y = libtcod.random_get_float(0, -0.5, 0.5) strength = libtcod.random_get_float(0, MIN_LIGHT_STRENGTH, 1.0) - color = libtcod.Color(0, 0, 0) #create bright colors with random hue + color = libtcod.Color(0, 0, 0) # create bright colors with random hue hue = libtcod.random_get_float(0, 0, 360) libtcod.color_set_hsv(color, hue, 0.5, strength) lights.append(Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength)) - #eliminate lights that are going to be out of view + # eliminate lights that are going to be out of view lights = [light for light in lights if light.z - time_delta > 1.0 / RES_V] - for light in lights: #render lights - #move light's Z coordinate with time, then project its XYZ coordinates to screen-space + for light in lights: # render lights + # move light's Z coordinate with time, then project its XYZ coordinates to screen-space light.z -= float(time_delta) / TEX_STRETCH xl = light.x / light.z * SCREEN_H yl = light.y / light.z * SCREEN_H if use_numpy: - #calculate brightness of light according to distance from viewer and strength, - #then calculate brightness of each pixel with inverse square distance law + # calculate brightness of light according to distance from viewer and strength, + # then calculate brightness of each pixel with inverse square distance law light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH) - brightness = light_brightness / ((xc - xl)**2 + (yc - yl)**2) + brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2) - #make all pixels shine around this light + # make all pixels shine around this light R += brightness * light.r G += brightness * light.g B += brightness * light.b else: - i = 0 #same, without numpy + i = 0 # same, without numpy for y in range(-HALF_H, HALF_H): for x in range(-HALF_W, HALF_W): light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH) - brightness = light_brightness / ((x - xl)**2 + (y - yl)**2) + brightness = light_brightness / ((x - xl) ** 2 + (y - yl) ** 2) R2[i] += brightness * light.r G2[i] += brightness * light.g @@ -1506,22 +1506,23 @@ def render_py(first, key, mouse): i += 1 if use_numpy: - #truncate values + # truncate values R = R.clip(0, 255) G = G.clip(0, 255) B = B.clip(0, 255) - #fill the screen with these background colors + # fill the screen with these background colors libtcod.console_fill_background(sample_console, R, G, B) else: - #truncate and convert to integer + # truncate and convert to integer R2 = [int(min(r, 255)) for r in R2] G2 = [int(min(g, 255)) for g in G2] B2 = [int(min(b, 255)) for b in B2] - #fill the screen with these background colors + # fill the screen with these background colors libtcod.console_fill_background(sample_console, R2, G2, B2) + ############################################# # main loop ############################################# @@ -1530,32 +1531,35 @@ def __init__(self, name, func): self.name = name self.func = func -samples = (Sample(' True colors ', render_colors), - Sample(' Offscreen console ', render_offscreen), - Sample(' Line drawing ', render_lines), - Sample(' Noise ', render_noise), - Sample(' Field of view ', render_fov), - Sample(' Path finding ', render_path), - Sample(' Bsp toolkit ', render_bsp), - Sample(' Image toolkit ', render_image), - Sample(' Mouse support ', render_mouse), - Sample(' Name generator ', render_name), - Sample(' Python fast render ', render_py)) + +samples = ( + Sample(" True colors ", render_colors), + Sample(" Offscreen console ", render_offscreen), + Sample(" Line drawing ", render_lines), + Sample(" Noise ", render_noise), + Sample(" Field of view ", render_fov), + Sample(" Path finding ", render_path), + Sample(" Bsp toolkit ", render_bsp), + Sample(" Image toolkit ", render_image), + Sample(" Mouse support ", render_mouse), + Sample(" Name generator ", render_name), + Sample(" Python fast render ", render_py), +) cur_sample = 0 credits_end = False first = True cur_renderer = 0 -renderer_name=('F1 GLSL ','F2 OPENGL ','F3 SDL ','F4 SDL2 ','F5 OPENGL2') -key=libtcod.Key() -mouse=libtcod.Mouse() +renderer_name = ("F1 GLSL ", "F2 OPENGL ", "F3 SDL ", "F4 SDL2 ", "F5 OPENGL2") +key = libtcod.Key() +mouse = libtcod.Mouse() while not libtcod.console_is_window_closed(): - libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS|libtcod.EVENT_MOUSE,key,mouse) + libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse) # render the sample samples[cur_sample].func(first, key, mouse) first = False - libtcod.console_blit(sample_console, - 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, - 0, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) + libtcod.console_blit( + sample_console, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, 0, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y + ) # render credits if not credits_end: credits_end = libtcod.console_credits_render(60, 43, 0) @@ -1567,49 +1571,58 @@ def __init__(self, name, func): else: libtcod.console_set_default_foreground(None, libtcod.grey) libtcod.console_set_default_background(None, libtcod.black) - libtcod.console_print_ex(None, 2, 46 - (len(samples) - i), - libtcod.BKGND_SET, libtcod.LEFT, samples[i].name) + libtcod.console_print_ex(None, 2, 46 - (len(samples) - i), libtcod.BKGND_SET, libtcod.LEFT, samples[i].name) # render stats libtcod.console_set_default_foreground(None, libtcod.grey) - libtcod.console_print_ex(None, 79, 46, libtcod.BKGND_NONE, libtcod.RIGHT, - 'last frame : %3d ms (%3d fps)' % - (int(libtcod.sys_get_last_frame_length() * - 1000.0), libtcod.sys_get_fps())) - libtcod.console_print_ex(None, 79, 47, libtcod.BKGND_NONE, libtcod.RIGHT, - 'elapsed : %8d ms %4.2fs' % - (libtcod.sys_elapsed_milli(), - libtcod.sys_elapsed_seconds())) - - cur_renderer=libtcod.sys_get_renderer() - libtcod.console_set_default_foreground(None,libtcod.grey) - libtcod.console_set_default_background(None,libtcod.black) - libtcod.console_print_ex(None,42,46-(libtcod.NB_RENDERERS+1),libtcod.BKGND_SET, libtcod.LEFT, "Renderer :") - for i in range(libtcod.NB_RENDERERS) : - if i==cur_renderer : - libtcod.console_set_default_foreground(None,libtcod.white) - libtcod.console_set_default_background(None,libtcod.light_blue) - else : - libtcod.console_set_default_foreground(None,libtcod.grey) - libtcod.console_set_default_background(None,libtcod.black) - libtcod.console_print_ex(None,42,46-(libtcod.NB_RENDERERS-i),libtcod.BKGND_SET,libtcod.LEFT,renderer_name[i]) + libtcod.console_print_ex( + None, + 79, + 46, + libtcod.BKGND_NONE, + libtcod.RIGHT, + "last frame : %3d ms (%3d fps)" % (int(libtcod.sys_get_last_frame_length() * 1000.0), libtcod.sys_get_fps()), + ) + libtcod.console_print_ex( + None, + 79, + 47, + libtcod.BKGND_NONE, + libtcod.RIGHT, + "elapsed : %8d ms %4.2fs" % (libtcod.sys_elapsed_milli(), libtcod.sys_elapsed_seconds()), + ) + + cur_renderer = libtcod.sys_get_renderer() + libtcod.console_set_default_foreground(None, libtcod.grey) + libtcod.console_set_default_background(None, libtcod.black) + libtcod.console_print_ex(None, 42, 46 - (libtcod.NB_RENDERERS + 1), libtcod.BKGND_SET, libtcod.LEFT, "Renderer :") + for i in range(libtcod.NB_RENDERERS): + if i == cur_renderer: + libtcod.console_set_default_foreground(None, libtcod.white) + libtcod.console_set_default_background(None, libtcod.light_blue) + else: + libtcod.console_set_default_foreground(None, libtcod.grey) + libtcod.console_set_default_background(None, libtcod.black) + libtcod.console_print_ex( + None, 42, 46 - (libtcod.NB_RENDERERS - i), libtcod.BKGND_SET, libtcod.LEFT, renderer_name[i] + ) # key handler if key.vk == libtcod.KEY_DOWN: - cur_sample = (cur_sample+1) % len(samples) + cur_sample = (cur_sample + 1) % len(samples) first = True elif key.vk == libtcod.KEY_UP: - cur_sample = (cur_sample-1) % len(samples) + cur_sample = (cur_sample - 1) % len(samples) first = True elif key.vk == libtcod.KEY_ENTER and key.lalt: libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen()) - elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == 'p': - print ("screenshot") - if key.lalt : - libtcod.console_save_apf(None,"samples.apf") - print ("apf") - else : + elif key.vk == libtcod.KEY_PRINTSCREEN or key.c == "p": + print("screenshot") + if key.lalt: + libtcod.console_save_apf(None, "samples.apf") + print("apf") + else: libtcod.sys_save_screenshot() - print ("png") + print("png") elif key.vk == libtcod.KEY_ESCAPE: break elif key.vk == libtcod.KEY_F1: @@ -1623,5 +1636,3 @@ def __init__(self, name, func): elif key.vk == libtcod.KEY_F5: libtcod.sys_set_renderer(libtcod.RENDERER_OPENGL2) libtcod.console_flush() - - diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 92f662fb..71d2e932 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -28,8 +28,7 @@ def get_data(path: str) -> str: SCRIPT_DIR = os.path.dirname(__file__) DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") assert os.path.exists(DATA_DIR), ( - "Data directory is missing," - " did you forget to run `git submodule update --init`?" + "Data directory is missing," " did you forget to run `git submodule update --init`?" ) return os.path.join(DATA_DIR, path) @@ -50,9 +49,7 @@ def get_data(path: str) -> str: context: tcod.context.Context tileset: tcod.tileset.Tileset root_console = tcod.Console(80, 50, order="F") -sample_console = tcod.console.Console( - SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F" -) +sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F") cur_sample = 0 # Current selected sample. frame_times = [time.perf_counter()] frame_length = [0.0] @@ -78,10 +75,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: cur_sample = (cur_sample - 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() draw_samples_menu() - elif ( - event.sym == tcod.event.K_RETURN - and event.mod & tcod.event.KMOD_LALT - ): + elif event.sym == tcod.event.K_RETURN and event.mod & tcod.event.KMOD_LALT: tcod.console_set_fullscreen(not tcod.console_is_fullscreen()) elif event.sym == tcod.event.K_PRINTSCREEN or event.sym == ord("p"): print("screenshot") @@ -111,9 +105,7 @@ def __init__(self) -> None: dtype=np.int16, ) # color shift direction - self.slide_dir = np.array( - [[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16 - ) + self.slide_dir = np.array([[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16) # corner indexes self.corners = np.array([0, 1, 2, 3]) @@ -129,9 +121,7 @@ def slide_corner_colors(self) -> None: rand_channels = np.random.randint(low=0, high=3, size=4) # shift picked color channels in the direction of slide_dir - self.colors[self.corners, rand_channels] += ( - self.slide_dir[self.corners, rand_channels] * 5 - ) + self.colors[self.corners, rand_channels] += self.slide_dir[self.corners, rand_channels] * 5 # reverse slide_dir values when limits are reached self.slide_dir[self.colors[:] == 255] = -1 @@ -139,12 +129,8 @@ def slide_corner_colors(self) -> None: def interpolate_corner_colors(self) -> None: # interpolate corner colors across the sample console - left = np.linspace( - self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT - ) - right = np.linspace( - self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT - ) + left = np.linspace(self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT) + right = np.linspace(self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT) sample_console.bg[:] = np.linspace(left, right, SAMPLE_SCREEN_WIDTH) def darken_background_characters(self) -> None: @@ -168,8 +154,7 @@ def print_banner(self) -> None: y=5, width=sample_console.width - 2, height=sample_console.height - 1, - string="The Doryen library uses 24 bits colors, for both " - "background and foreground.", + string="The Doryen library uses 24 bits colors, for both " "background and foreground.", fg=WHITE, bg=GREY, bg_blend=tcod.BKGND_MULTIPLY, @@ -180,12 +165,8 @@ def print_banner(self) -> None: class OffscreenConsoleSample(Sample): def __init__(self) -> None: self.name = "Offscreen console" - self.secondary = tcod.console.Console( - sample_console.width // 2, sample_console.height // 2 - ) - self.screenshot = tcod.console.Console( - sample_console.width, sample_console.height - ) + self.secondary = tcod.console.Console(sample_console.width // 2, sample_console.height // 2) + self.screenshot = tcod.console.Console(sample_console.width, sample_console.height) self.counter = 0.0 self.x = 0 self.y = 0 @@ -208,8 +189,7 @@ def __init__(self) -> None: 2, sample_console.width // 2 - 2, sample_console.height // 2, - "You can render to an offscreen console and blit in on another " - "one, simulating alpha transparency.", + "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", fg=WHITE, bg=None, alignment=tcod.CENTER, @@ -270,15 +250,11 @@ def __init__(self) -> None: self.mk_flag = tcod.BKGND_SET self.bk_flag = tcod.BKGND_SET - self.bk = tcod.console.Console( - sample_console.width, sample_console.height, order="F" - ) + self.bk = tcod.console.Console(sample_console.width, sample_console.height, order="F") # initialize the colored background self.bk.bg[:, :, 0] = np.linspace(0, 255, self.bk.width)[:, np.newaxis] self.bk.bg[:, :, 2] = np.linspace(0, 255, self.bk.height) - self.bk.bg[:, :, 1] = ( - self.bk.bg[:, :, 0].astype(int) + self.bk.bg[:, :, 2] - ) / 2 + self.bk.bg[:, :, 1] = (self.bk.bg[:, :, 0].astype(int) + self.bk.bg[:, :, 2]) / 2 self.bk.ch[:] = ord(" ") def ev_keydown(self, event: tcod.event.KeyDown) -> None: @@ -301,42 +277,25 @@ def on_draw(self) -> None: self.bk_flag = tcod.BKGND_ADDALPHA(int(alpha)) self.bk.blit(sample_console) - recty = int( - (sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0) - ) + recty = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0)) for x in range(sample_console.width): value = x * 255 // sample_console.width col = (value, value, value) - tcod.console_set_char_background( - sample_console, x, recty, col, self.bk_flag - ) - tcod.console_set_char_background( - sample_console, x, recty + 1, col, self.bk_flag - ) - tcod.console_set_char_background( - sample_console, x, recty + 2, col, self.bk_flag - ) + tcod.console_set_char_background(sample_console, x, recty, col, self.bk_flag) + tcod.console_set_char_background(sample_console, x, recty + 1, col, self.bk_flag) + tcod.console_set_char_background(sample_console, x, recty + 2, col, self.bk_flag) angle = time.time() * 2.0 cos_angle = math.cos(angle) sin_angle = math.sin(angle) xo = int(sample_console.width // 2 * (1 + cos_angle)) - yo = int( - sample_console.height // 2 + sin_angle * sample_console.width // 2 - ) + yo = int(sample_console.height // 2 + sin_angle * sample_console.width // 2) xd = int(sample_console.width // 2 * (1 - cos_angle)) - yd = int( - sample_console.height // 2 - sin_angle * sample_console.width // 2 - ) + yd = int(sample_console.height // 2 - sin_angle * sample_console.width // 2) # draw the line # in python the easiest way is to use the line iterator for x, y in tcod.line_iter(xo, yo, xd, yd): - if ( - 0 <= x < sample_console.width - and 0 <= y < sample_console.height - ): - tcod.console_set_char_background( - sample_console, x, y, LIGHT_BLUE, self.bk_flag - ) + if 0 <= x < sample_console.width and 0 <= y < sample_console.height: + tcod.console_set_char_background(sample_console, x, y, LIGHT_BLUE, self.bk_flag) sample_console.print( 2, 2, @@ -405,9 +364,7 @@ def __init__(self) -> None: self.hurst = tcod.NOISE_DEFAULT_HURST self.lacunarity = tcod.NOISE_DEFAULT_LACUNARITY self.noise = self.get_noise() - self.img = tcod.image_new( - SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2 - ) + self.img = tcod.image_new(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2) @property def algorithm(self) -> int: @@ -456,21 +413,15 @@ def on_draw(self) -> None: bg=GREY, bg_blend=tcod.BKGND_MULTIPLY, ) - sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = ( - sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * GREY / 255 - ) + sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * GREY / 255 for curfunc in range(len(self.NOISE_OPTIONS)): text = "%i : %s" % (curfunc + 1, self.NOISE_OPTIONS[curfunc][0]) if curfunc == self.func: - sample_console.print( - 2, 2 + curfunc, text, fg=WHITE, bg=LIGHT_BLUE - ) + sample_console.print(2, 2 + curfunc, text, fg=WHITE, bg=LIGHT_BLUE) else: sample_console.print(2, 2 + curfunc, text, fg=GREY, bg=None) - sample_console.print( - 2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None - ) + sample_console.print(2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None) if self.implementation != tcod.noise.Implementation.SIMPLE: sample_console.print( 2, @@ -671,13 +622,9 @@ def on_draw(self) -> None: dark_bg = self.dark_map_bg.astype(np.float16) # Linear interpolation between colors. - sample_console.tiles_rgb["bg"] = ( - dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] - ) + sample_console.tiles_rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] else: - sample_console.bg[...] = np.where( - fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg - ) + sample_console.bg[...] = np.where(fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg) def ev_keydown(self, event: tcod.event.KeyDown) -> None: MOVE_KEYS = { @@ -756,17 +703,13 @@ def on_enter(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] == "=": - tcod.console_put_char( - sample_console, x, y, tcod.CHAR_DHLINE, tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, x, y, tcod.CHAR_DHLINE, tcod.BKGND_NONE) self.recalculate = True def on_draw(self) -> None: if self.recalculate: if self.using_astar: - tcod.path_compute( - self.path, self.px, self.py, self.dx, self.dy - ) + tcod.path_compute(self.path, self.px, self.py, self.dx, self.dy) else: self.dijk_dist = 0.0 # compute dijkstra grid (distance from px,py) @@ -785,20 +728,14 @@ def on_draw(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] == "#": - tcod.console_set_char_background( - sample_console, x, y, DARK_WALL, tcod.BKGND_SET - ) + tcod.console_set_char_background(sample_console, x, y, DARK_WALL, tcod.BKGND_SET) else: - tcod.console_set_char_background( - sample_console, x, y, DARK_GROUND, tcod.BKGND_SET - ) + tcod.console_set_char_background(sample_console, x, y, DARK_GROUND, tcod.BKGND_SET) # draw the path if self.using_astar: for i in range(tcod.path_size(self.path)): x, y = tcod.path_get(self.path, i) - tcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET - ) + tcod.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET) else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -810,17 +747,13 @@ def on_draw(self) -> None: tcod.color_lerp( LIGHT_GROUND, DARK_GROUND, - 0.9 - * tcod.dijkstra_get_distance(self.dijk, x, y) - / self.dijk_dist, + 0.9 * tcod.dijkstra_get_distance(self.dijk, x, y) / self.dijk_dist, ), tcod.BKGND_SET, ) for i in range(tcod.dijkstra_size(self.dijk)): x, y = tcod.dijkstra_get(self.dijk, i) - tcod.console_set_char_background( - sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET - ) + tcod.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET) # move the creature self.busy -= frame_length[-1] @@ -828,71 +761,47 @@ def on_draw(self) -> None: self.busy = 0.2 if self.using_astar: if not tcod.path_is_empty(self.path): - tcod.console_put_char( - sample_console, self.px, self.py, " ", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) self.px, self.py = tcod.path_walk(self.path, True) - tcod.console_put_char( - sample_console, self.px, self.py, "@", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) else: if not tcod.dijkstra_is_empty(self.dijk): - tcod.console_put_char( - sample_console, self.px, self.py, " ", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) self.px, self.py = tcod.dijkstra_path_walk(self.dijk) - tcod.console_put_char( - sample_console, self.px, self.py, "@", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) self.recalculate = True def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == ord("i") and self.dy > 0: # destination move north - tcod.console_put_char( - sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dy -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("k") and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - tcod.console_put_char( - sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dy += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("j") and self.dx > 0: # destination move west - tcod.console_put_char( - sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dx -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == ord("l") and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - tcod.console_put_char( - sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dx += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.K_TAB: @@ -908,20 +817,12 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y - if ( - 0 <= mx < SAMPLE_SCREEN_WIDTH - and 0 <= my < SAMPLE_SCREEN_HEIGHT - and (self.dx != mx or self.dy != my) - ): - tcod.console_put_char( - sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE - ) + if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my): + tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dx = mx self.dy = my self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char( - sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE - ) + tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True @@ -989,19 +890,13 @@ def traverse_node(bsp_map: np.ndarray, node: tcod.bsp.BSP) -> None: node.width -= 1 node.height -= 1 if bsp_random_room: - new_width = random.randint( - min(node.width, bsp_min_room_size), node.width - ) - new_height = random.randint( - min(node.height, bsp_min_room_size), node.height - ) + new_width = random.randint(min(node.width, bsp_min_room_size), node.width) + new_height = random.randint(min(node.height, bsp_min_room_size), node.height) node.x += random.randint(0, node.width - new_width) node.y += random.randint(0, node.height - new_height) node.width, node.height = new_width, new_height # dig the room - bsp_map[ - node.x : node.x + node.width, node.y : node.y + node.height - ] = True + bsp_map[node.x : node.x + node.width, node.y : node.y + node.height] = True else: # resize the node to fit its sons left, right = node.children @@ -1049,12 +944,8 @@ def traverse_node(bsp_map: np.ndarray, node: tcod.bsp.BSP) -> None: class BSPSample(Sample): def __init__(self) -> None: self.name = "Bsp toolkit" - self.bsp = tcod.bsp.BSP( - 1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1 - ) - self.bsp_map = np.zeros( - (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F" - ) + self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1) + self.bsp_map = np.zeros((SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F") self.bsp_generate() def bsp_generate(self) -> None: @@ -1068,9 +959,7 @@ def bsp_generate(self) -> None: 1.5, ) else: - self.bsp.split_recursive( - bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5 - ) + self.bsp.split_recursive(bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5) self.bsp_refresh() def bsp_refresh(self) -> None: @@ -1098,16 +987,12 @@ def on_draw(self) -> None: walls = "OFF" if bsp_room_walls: walls = "ON" - sample_console.print( - 1, 6, "2 : room walls %s" % walls, fg=WHITE, bg=None - ) + sample_console.print(1, 6, "2 : room walls %s" % walls, fg=WHITE, bg=None) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): color = DARK_GROUND if self.bsp_map[x][y] else DARK_WALL - tcod.console_set_char_background( - sample_console, x, y, color, tcod.BKGND_SET - ) + tcod.console_set_char_background(sample_console, x, y, color, tcod.BKGND_SET) def ev_keydown(self, event: tcod.event.KeyDown) -> None: global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size @@ -1158,31 +1043,19 @@ def on_draw(self) -> None: # split the color channels of circle.png # the red channel sample_console.draw_rect(0, 3, 15, 15, 0, None, (255, 0, 0)) - self.circle.blit_rect( - sample_console, 0, 3, -1, -1, tcod.BKGND_MULTIPLY - ) + self.circle.blit_rect(sample_console, 0, 3, -1, -1, tcod.BKGND_MULTIPLY) # the green channel sample_console.draw_rect(15, 3, 15, 15, 0, None, (0, 255, 0)) - self.circle.blit_rect( - sample_console, 15, 3, -1, -1, tcod.BKGND_MULTIPLY - ) + self.circle.blit_rect(sample_console, 15, 3, -1, -1, tcod.BKGND_MULTIPLY) # the blue channel sample_console.draw_rect(30, 3, 15, 15, 0, None, (0, 0, 255)) - self.circle.blit_rect( - sample_console, 30, 3, -1, -1, tcod.BKGND_MULTIPLY - ) + self.circle.blit_rect(sample_console, 30, 3, -1, -1, tcod.BKGND_MULTIPLY) else: # render circle.png with normal blitting self.circle.blit_rect(sample_console, 0, 3, -1, -1, tcod.BKGND_SET) - self.circle.blit_rect( - sample_console, 15, 3, -1, -1, tcod.BKGND_SET - ) - self.circle.blit_rect( - sample_console, 30, 3, -1, -1, tcod.BKGND_SET - ) - self.img.blit( - sample_console, x, y, tcod.BKGND_SET, scalex, scaley, angle - ) + self.circle.blit_rect(sample_console, 15, 3, -1, -1, tcod.BKGND_SET) + self.circle.blit_rect(sample_console, 30, 3, -1, -1, tcod.BKGND_SET) + self.img.blit(sample_console, x, y, tcod.BKGND_SET, scalex, scaley, angle) class MouseSample(Sample): @@ -1284,8 +1157,7 @@ def on_draw(self) -> None: sample_console.print( 1, 1, - "%s\n\n+ : next generator\n- : prev generator" - % self.sets[self.curset], + "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.curset], fg=WHITE, bg=None, ) @@ -1332,15 +1204,11 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: RES_V = 80 TEX_STRETCH = 5 # texture stretching with tunnel depth SPEED = 15 -LIGHT_BRIGHTNESS = ( - 3.5 # brightness multiplier for all lights (changes their radius) -) +LIGHT_BRIGHTNESS = 3.5 # brightness multiplier for all lights (changes their radius) LIGHTS_CHANCE = 0.07 # chance of a light appearing MAX_LIGHTS = 6 MIN_LIGHT_STRENGTH = 0.2 -LIGHT_UPDATE = ( - 0.05 # how much the ambient light changes to reflect current light sources -) +LIGHT_UPDATE = 0.05 # how much the ambient light changes to reflect current light sources AMBIENT_LIGHT = 0.8 # brightness of tunnel texture # the coordinates of all tiles in the screen, as numpy arrays. @@ -1380,9 +1248,7 @@ def __init__(self) -> None: def on_enter(self) -> None: sample_console.clear() # render status message - sample_console.print( - 1, SCREEN_H - 3, "Renderer: NumPy", fg=WHITE, bg=None - ) + sample_console.print(1, SCREEN_H - 3, "Renderer: NumPy", fg=WHITE, bg=None) # time is represented in number of pixels of the texture, start later # in time to initialize texture @@ -1408,15 +1274,9 @@ def on_draw(self) -> None: # change texture color according to presence of lights (basically, sum # them to get ambient light and smoothly change the current color into # that) - ambient_r = AMBIENT_LIGHT * sum( - light.r * light.strength for light in self.lights - ) - ambient_g = AMBIENT_LIGHT * sum( - light.g * light.strength for light in self.lights - ) - ambient_b = AMBIENT_LIGHT * sum( - light.b * light.strength for light in self.lights - ) + ambient_r = AMBIENT_LIGHT * sum(light.r * light.strength for light in self.lights) + ambient_g = AMBIENT_LIGHT * sum(light.g * light.strength for light in self.lights) + ambient_b = AMBIENT_LIGHT * sum(light.b * light.strength for light in self.lights) alpha = LIGHT_UPDATE * time_delta self.tex_r = self.tex_r * (1 - alpha) + ambient_r * alpha self.tex_g = self.tex_g * (1 - alpha) + ambient_g * alpha @@ -1435,9 +1295,7 @@ def on_draw(self) -> None: for v in range(RES_V - int_t, RES_V): for u in range(0, RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u, v] = tcod.noise_get_fbm( - noise2d, [u / float(RES_U), tex_v], 32.0 - ) + tcod.noise_get_fbm( + texture[u, v] = tcod.noise_get_fbm(noise2d, [u / float(RES_U), tex_v], 32.0) + tcod.noise_get_fbm( noise2d, [1 - u / float(RES_U), tex_v], 32.0 ) @@ -1462,10 +1320,7 @@ def on_draw(self) -> None: B = brightness * self.tex_b # create new light source - if ( - random.random() <= time_delta * LIGHTS_CHANCE - and len(self.lights) < MAX_LIGHTS - ): + if random.random() <= time_delta * LIGHTS_CHANCE and len(self.lights) < MAX_LIGHTS: x = random.uniform(-0.5, 0.5) y = random.uniform(-0.5, 0.5) strength = random.uniform(MIN_LIGHT_STRENGTH, 1.0) @@ -1473,16 +1328,10 @@ def on_draw(self) -> None: color = tcod.Color(0, 0, 0) # create bright colors with random hue hue = random.uniform(0, 360) tcod.color_set_hsv(color, hue, 0.5, strength) - self.lights.append( - Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength) - ) + self.lights.append(Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength)) # eliminate lights that are going to be out of view - self.lights = [ - light - for light in self.lights - if light.z - time_delta > 1.0 / RES_V - ] + self.lights = [light for light in self.lights if light.z - time_delta > 1.0 / RES_V] for light in self.lights: # render lights # move light's Z coordinate with time, then project its XYZ @@ -1494,11 +1343,7 @@ def on_draw(self) -> None: # calculate brightness of light according to distance from viewer # and strength, then calculate brightness of each pixel with # inverse square distance law - light_brightness = ( - LIGHT_BRIGHTNESS - * light.strength - * (1.0 - light.z / TEX_STRETCH) - ) + light_brightness = LIGHT_BRIGHTNESS * light.strength * (1.0 - light.z / TEX_STRETCH) brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2) # make all pixels shine around this light @@ -1563,8 +1408,7 @@ def init_context(renderer: int) -> tcod.context.Context: return tcod.context.new( columns=root_console.width, rows=root_console.height, - title=f"python-tcod samples" - f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})", + title=f"python-tcod samples" f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})", renderer=renderer, vsync=False, # VSync turned off since this is for benchmarking. tileset=tileset, @@ -1573,9 +1417,7 @@ def init_context(renderer: int) -> tcod.context.Context: def main() -> None: global context, tileset - tileset = tcod.tileset.load_tilesheet( - FONT, 32, 8, tcod.tileset.CHARMAP_TCOD - ) + tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) context = init_context(tcod.RENDERER_SDL2) try: SAMPLES[cur_sample].on_enter() @@ -1648,8 +1490,7 @@ def draw_stats() -> None: root_console.print( 79, 47, - "elapsed : %8d ms %5.2fs" - % (time.perf_counter() * 1000, time.perf_counter()), + "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), fg=GREY, alignment=tcod.RIGHT, ) diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index db4c9813..d97d9110 100755 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -16,140 +16,145 @@ [ ] not all keys/events are mapped """ + class TermboxException(Exception): def __init__(self, msg): self.msg = msg + def __str__(self): return self.msg + _instance = None # keys ---------------------------------- -KEY_F1 = (0xFFFF-0) -KEY_F2 = (0xFFFF-1) -KEY_F3 = (0xFFFF-2) -KEY_F4 = (0xFFFF-3) -KEY_F5 = (0xFFFF-4) -KEY_F6 = (0xFFFF-5) -KEY_F7 = (0xFFFF-6) -KEY_F8 = (0xFFFF-7) -KEY_F9 = (0xFFFF-8) -KEY_F10 = (0xFFFF-9) -KEY_F11 = (0xFFFF-10) -KEY_F12 = (0xFFFF-11) -KEY_INSERT = (0xFFFF-12) -KEY_DELETE = (0xFFFF-13) - -KEY_PGUP = (0xFFFF-16) -KEY_PGDN = (0xFFFF-17) - -KEY_MOUSE_LEFT = (0xFFFF-22) -KEY_MOUSE_RIGHT = (0xFFFF-23) -KEY_MOUSE_MIDDLE = (0xFFFF-24) -KEY_MOUSE_RELEASE = (0xFFFF-25) -KEY_MOUSE_WHEEL_UP = (0xFFFF-26) -KEY_MOUSE_WHEEL_DOWN = (0xFFFF-27) - -KEY_CTRL_TILDE = 0x00 -KEY_CTRL_2 = 0x00 -KEY_CTRL_A = 0x01 -KEY_CTRL_B = 0x02 -KEY_CTRL_C = 0x03 -KEY_CTRL_D = 0x04 -KEY_CTRL_E = 0x05 -KEY_CTRL_F = 0x06 -KEY_CTRL_G = 0x07 -KEY_BACKSPACE = 0x08 -KEY_CTRL_H = 0x08 -KEY_TAB = 0x09 -KEY_CTRL_I = 0x09 -KEY_CTRL_J = 0x0A -KEY_CTRL_K = 0x0B -KEY_CTRL_L = 0x0C -KEY_ENTER = 0x0D -KEY_CTRL_M = 0x0D -KEY_CTRL_N = 0x0E -KEY_CTRL_O = 0x0F -KEY_CTRL_P = 0x10 -KEY_CTRL_Q = 0x11 -KEY_CTRL_R = 0x12 -KEY_CTRL_S = 0x13 -KEY_CTRL_T = 0x14 -KEY_CTRL_U = 0x15 -KEY_CTRL_V = 0x16 -KEY_CTRL_W = 0x17 -KEY_CTRL_X = 0x18 -KEY_CTRL_Y = 0x19 -KEY_CTRL_Z = 0x1A +KEY_F1 = 0xFFFF - 0 +KEY_F2 = 0xFFFF - 1 +KEY_F3 = 0xFFFF - 2 +KEY_F4 = 0xFFFF - 3 +KEY_F5 = 0xFFFF - 4 +KEY_F6 = 0xFFFF - 5 +KEY_F7 = 0xFFFF - 6 +KEY_F8 = 0xFFFF - 7 +KEY_F9 = 0xFFFF - 8 +KEY_F10 = 0xFFFF - 9 +KEY_F11 = 0xFFFF - 10 +KEY_F12 = 0xFFFF - 11 +KEY_INSERT = 0xFFFF - 12 +KEY_DELETE = 0xFFFF - 13 + +KEY_PGUP = 0xFFFF - 16 +KEY_PGDN = 0xFFFF - 17 + +KEY_MOUSE_LEFT = 0xFFFF - 22 +KEY_MOUSE_RIGHT = 0xFFFF - 23 +KEY_MOUSE_MIDDLE = 0xFFFF - 24 +KEY_MOUSE_RELEASE = 0xFFFF - 25 +KEY_MOUSE_WHEEL_UP = 0xFFFF - 26 +KEY_MOUSE_WHEEL_DOWN = 0xFFFF - 27 + +KEY_CTRL_TILDE = 0x00 +KEY_CTRL_2 = 0x00 +KEY_CTRL_A = 0x01 +KEY_CTRL_B = 0x02 +KEY_CTRL_C = 0x03 +KEY_CTRL_D = 0x04 +KEY_CTRL_E = 0x05 +KEY_CTRL_F = 0x06 +KEY_CTRL_G = 0x07 +KEY_BACKSPACE = 0x08 +KEY_CTRL_H = 0x08 +KEY_TAB = 0x09 +KEY_CTRL_I = 0x09 +KEY_CTRL_J = 0x0A +KEY_CTRL_K = 0x0B +KEY_CTRL_L = 0x0C +KEY_ENTER = 0x0D +KEY_CTRL_M = 0x0D +KEY_CTRL_N = 0x0E +KEY_CTRL_O = 0x0F +KEY_CTRL_P = 0x10 +KEY_CTRL_Q = 0x11 +KEY_CTRL_R = 0x12 +KEY_CTRL_S = 0x13 +KEY_CTRL_T = 0x14 +KEY_CTRL_U = 0x15 +KEY_CTRL_V = 0x16 +KEY_CTRL_W = 0x17 +KEY_CTRL_X = 0x18 +KEY_CTRL_Y = 0x19 +KEY_CTRL_Z = 0x1A # -- mapped to tdl -KEY_HOME = 'HOME' -KEY_END = 'END' -KEY_ARROW_UP = 'UP' -KEY_ARROW_DOWN = 'DOWN' -KEY_ARROW_LEFT = 'LEFT' -KEY_ARROW_RIGHT = 'RIGHT' -KEY_ESC = 'ESCAPE' +KEY_HOME = "HOME" +KEY_END = "END" +KEY_ARROW_UP = "UP" +KEY_ARROW_DOWN = "DOWN" +KEY_ARROW_LEFT = "LEFT" +KEY_ARROW_RIGHT = "RIGHT" +KEY_ESC = "ESCAPE" # /-- KEY_CTRL_LSQ_BRACKET = 0x1B -KEY_CTRL_3 = 0x1B -KEY_CTRL_4 = 0x1C -KEY_CTRL_BACKSLASH = 0x1C -KEY_CTRL_5 = 0x1D +KEY_CTRL_3 = 0x1B +KEY_CTRL_4 = 0x1C +KEY_CTRL_BACKSLASH = 0x1C +KEY_CTRL_5 = 0x1D KEY_CTRL_RSQ_BRACKET = 0x1D -KEY_CTRL_6 = 0x1E -KEY_CTRL_7 = 0x1F -KEY_CTRL_SLASH = 0x1F -KEY_CTRL_UNDERSCORE = 0x1F -KEY_SPACE = 0x20 -KEY_BACKSPACE2 = 0x7F -KEY_CTRL_8 = 0x7F +KEY_CTRL_6 = 0x1E +KEY_CTRL_7 = 0x1F +KEY_CTRL_SLASH = 0x1F +KEY_CTRL_UNDERSCORE = 0x1F +KEY_SPACE = 0x20 +KEY_BACKSPACE2 = 0x7F +KEY_CTRL_8 = 0x7F -MOD_ALT = 0x01 +MOD_ALT = 0x01 # attributes ---------------------- -#-- mapped to tdl -DEFAULT = Ellipsis - -BLACK = 0x000000 -RED = 0xFF0000 -GREEN = 0x00FF00 -YELLOW = 0xFFFF00 -BLUE = 0x0000FF -MAGENTA = 0xFF00FF -CYAN = 0x00FFFF -WHITE = 0xFFFFFF -#/-- +# -- mapped to tdl +DEFAULT = Ellipsis + +BLACK = 0x000000 +RED = 0xFF0000 +GREEN = 0x00FF00 +YELLOW = 0xFFFF00 +BLUE = 0x0000FF +MAGENTA = 0xFF00FF +CYAN = 0x00FFFF +WHITE = 0xFFFFFF +# /-- -BOLD = 0x10 +BOLD = 0x10 UNDERLINE = 0x20 -REVERSE = 0x40 +REVERSE = 0x40 # misc ---------------------------- -HIDE_CURSOR = -1 -INPUT_CURRENT = 0 -INPUT_ESC = 1 -INPUT_ALT = 2 -OUTPUT_CURRENT = 0 -OUTPUT_NORMAL = 1 -OUTPUT_256 = 2 -OUTPUT_216 = 3 +HIDE_CURSOR = -1 +INPUT_CURRENT = 0 +INPUT_ESC = 1 +INPUT_ALT = 2 +OUTPUT_CURRENT = 0 +OUTPUT_NORMAL = 1 +OUTPUT_256 = 2 +OUTPUT_216 = 3 OUTPUT_GRAYSCALE = 4 # -- mapped to tdl -EVENT_KEY = 'KEYDOWN' +EVENT_KEY = "KEYDOWN" # /-- EVENT_RESIZE = 2 EVENT_MOUSE = 3 + class Event: - """ Aggregate for Termbox Event structure """ + """Aggregate for Termbox Event structure""" + type = None ch = None key = None @@ -160,29 +165,28 @@ class Event: mousey = None def gettuple(self): - return (self.type, self.ch, self.key, self.mod, self.width, - self.height, self.mousex, self.mousey) + return (self.type, self.ch, self.key, self.mod, self.width, self.height, self.mousex, self.mousey) + class Termbox: def __init__(self, width=132, height=60): global _instance if _instance: - raise TermboxException( - "It is possible to create only one instance of Termbox") + raise TermboxException("It is possible to create only one instance of Termbox") try: self.console = tdl.init(width, height) except tdl.TDLException as e: raise TermboxException(e) - self.e = Event() # cache for event data + self.e = Event() # cache for event data _instance = self def __del__(self): self.close() - def __exit__(self, *args):#t, value, traceback): + def __exit__(self, *args): # t, value, traceback): self.close() def __enter__(self): @@ -195,63 +199,57 @@ def close(self): # TBD, does nothing def present(self): - """Sync state of the internal cell buffer with the terminal. - """ + """Sync state of the internal cell buffer with the terminal.""" tdl.flush() def change_cell(self, x, y, ch, fg, bg): - """Change cell in position (x;y). - """ + """Change cell in position (x;y).""" self.console.draw_char(x, y, ch, fg, bg) def width(self): - """Returns width of the terminal screen. - """ + """Returns width of the terminal screen.""" return self.console.width def height(self): - """Return height of the terminal screen. - """ + """Return height of the terminal screen.""" return self.console.height def clear(self): - """Clear the internal cell buffer. - """ + """Clear the internal cell buffer.""" self.console.clear() def set_cursor(self, x, y): """Set cursor position to (x;y). - Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to - hide it. + Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to + hide it. """ tb_set_cursor(x, y) def hide_cursor(self): - """Hide cursor. - """ + """Hide cursor.""" tb_set_cursor(-1, -1) def select_input_mode(self, mode): """Select preferred input mode: INPUT_ESC or INPUT_ALT. - INPUT_CURRENT returns the selected mode without changing anything. + INPUT_CURRENT returns the selected mode without changing anything. """ return int(tb_select_input_mode(mode)) def select_output_mode(self, mode): """Select preferred output mode: one of OUTPUT_* constants. - OUTPUT_CURRENT returns the selected mode without changing anything. + OUTPUT_CURRENT returns the selected mode without changing anything. """ return int(tb_select_output_mode(mode)) def peek_event(self, timeout=0): """Wait for an event up to 'timeout' milliseconds and return it. - Returns None if there was no event and timeout is expired. - Returns a tuple otherwise: (type, unicode character, key, mod, - width, height, mousex, mousey). + Returns None if there was no event and timeout is expired. + Returns a tuple otherwise: (type, unicode character, key, mod, + width, height, mousex, mousey). """ """ cdef tb_event e @@ -266,13 +264,13 @@ def peek_event(self, timeout=0): else: uch = None """ - pass #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) + pass # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) def poll_event(self): """Wait for an event and return it. - Returns a tuple: (type, unicode character, key, mod, width, height, - mousex, mousey). + Returns a tuple: (type, unicode character, key, mod, width, height, + mousex, mousey). """ """ cdef tb_event e @@ -288,8 +286,8 @@ def poll_event(self): for e in tdl.event.get(): # [ ] not all events are passed thru self.e.type = e.type - if e.type == 'KEYDOWN': + if e.type == "KEYDOWN": self.e.key = e.key return self.e.gettuple() - #return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) + # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py index 4020e49a..9b43cfae 100755 --- a/examples/termbox/termboxtest.py +++ b/examples/termbox/termboxtest.py @@ -3,7 +3,8 @@ import termbox -spaceord = ord(u" ") +spaceord = ord(" ") + def print_line(t, msg, y, fg, bg): w = t.width() @@ -13,7 +14,8 @@ def print_line(t, msg, y, fg, bg): c = spaceord if i < l: c = ord(msg[i]) - t.change_cell(x+i, y, c, fg, bg) + t.change_cell(x + i, y, c, fg, bg) + class SelectBox(object): def __init__(self, tb, choices, active=-1): @@ -34,7 +36,7 @@ def validate_active(self): if self.active < 0: self.active = 0 if self.active >= len(self.choices): - self.active = len(self.choices)-1 + self.active = len(self.choices) - 1 def set_active(self, i): self.active = i @@ -48,50 +50,61 @@ def move_down(self): self.active += 1 self.validate_active() + choices = [ - u"This instructs Psyco", - u"to compile and run as", - u"much of your application", - u"code as possible. This is the", - u"simplest interface to Psyco.", - u"In good cases you can just add", - u"these two lines and enjoy the speed-up.", - u"If your application does a lot", - u"of initialization stuff before", - u"the real work begins, you can put", - u"the above two lines after this", - u"initialization - e.g. after importing", - u"modules, creating constant global objects, etc.", - u"This instructs Psyco", - u"to compile and run as", - u"much of your application", - u"code as possible. This is the", - u"simplest interface to Psyco.", - u"In good cases you can just add", - u"these two lines and enjoy the speed-up.", - u"If your application does a lot", - u"of initialization stuff before", - u"the real work begins, you can put", - u"the above two lines after this", - u"initialization - e.g. after importing", - u"modules, creating constant global objects, etc." + "This instructs Psyco", + "to compile and run as", + "much of your application", + "code as possible. This is the", + "simplest interface to Psyco.", + "In good cases you can just add", + "these two lines and enjoy the speed-up.", + "If your application does a lot", + "of initialization stuff before", + "the real work begins, you can put", + "the above two lines after this", + "initialization - e.g. after importing", + "modules, creating constant global objects, etc.", + "This instructs Psyco", + "to compile and run as", + "much of your application", + "code as possible. This is the", + "simplest interface to Psyco.", + "In good cases you can just add", + "these two lines and enjoy the speed-up.", + "If your application does a lot", + "of initialization stuff before", + "the real work begins, you can put", + "the above two lines after this", + "initialization - e.g. after importing", + "modules, creating constant global objects, etc.", ] + def draw_bottom_line(t, i): i = i % 8 w = t.width() h = t.height() c = i - palette = [termbox.DEFAULT, termbox.BLACK, termbox.RED, termbox.GREEN, - termbox.YELLOW, termbox.BLUE, termbox.MAGENTA, termbox.CYAN, - termbox.WHITE] + palette = [ + termbox.DEFAULT, + termbox.BLACK, + termbox.RED, + termbox.GREEN, + termbox.YELLOW, + termbox.BLUE, + termbox.MAGENTA, + termbox.CYAN, + termbox.WHITE, + ] for x in range(w): - t.change_cell(x, h-1, ord(u' '), termbox.BLACK, palette[c]) - t.change_cell(x, h-2, ord(u' '), termbox.BLACK, palette[c]) + t.change_cell(x, h - 1, ord(" "), termbox.BLACK, palette[c]) + t.change_cell(x, h - 2, ord(" "), termbox.BLACK, palette[c]) c += 1 if c > 7: c = 0 + with termbox.Termbox() as t: sb = SelectBox(t, choices, 0) t.clear() diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index 8aa2b808..82d88358 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -41,9 +41,7 @@ def test_fov_single(maps: List[tcod.map.Map]) -> None: test_fov(map_) -def test_fov_threads( - executor: concurrent.futures.Executor, maps: List[tcod.map.Map] -) -> None: +def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: for result in executor.map(test_fov, maps): pass @@ -58,9 +56,7 @@ def test_astar_single(maps: List[tcod.map.Map]) -> None: test_astar(map_) -def test_astar_threads( - executor: concurrent.futures.Executor, maps: List[tcod.map.Map] -) -> None: +def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: for result in executor.map(test_astar, maps): pass @@ -68,31 +64,20 @@ def test_astar_threads( def run_test( maps: List[tcod.map.Map], single_func: Callable[[List[tcod.map.Map]], None], - multi_func: Callable[ - [concurrent.futures.Executor, List[tcod.map.Map]], None - ], + multi_func: Callable[[concurrent.futures.Executor, List[tcod.map.Map]], None], ) -> None: """Run a function designed for a single thread and compare it to a threaded version. This prints the results of these tests. """ - single_time = min( - timeit.repeat(lambda: single_func(maps), number=1, repeat=REPEAT) - ) + single_time = min(timeit.repeat(lambda: single_func(maps), number=1, repeat=REPEAT)) print(f"Single threaded: {single_time * 1000:.2f}ms") for i in range(1, THREADS + 1): executor = concurrent.futures.ThreadPoolExecutor(i) - multi_time = min( - timeit.repeat( - lambda: multi_func(executor, maps), number=1, repeat=REPEAT - ) - ) - print( - f"{i} threads: {multi_time * 1000:.2f}ms, " - f"{single_time / (multi_time * i) * 100:.2f}% efficiency" - ) + multi_time = min(timeit.repeat(lambda: multi_func(executor, maps), number=1, repeat=REPEAT)) + print(f"{i} threads: {multi_time * 1000:.2f}ms, " f"{single_time / (multi_time * i) * 100:.2f}% efficiency") def main() -> None: @@ -102,14 +87,9 @@ def main() -> None: map_.walkable[...] = True map_.transparent[...] = True - print( - f"Python {sys.version}\n{platform.platform()}\n{platform.processor()}" - ) + print(f"Python {sys.version}\n{platform.platform()}\n{platform.processor()}") - print( - f"\nComputing field-of-view for " - f"{len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps." - ) + print(f"\nComputing field-of-view for " f"{len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps.") run_test(maps, test_fov_single, test_fov_threads) print( diff --git a/examples/ttf.py b/examples/ttf.py index 638655b0..36434916 100644 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -36,9 +36,7 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: ttf.load_glyph(glyph_index) bitmap = ttf.glyph.bitmap assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY - bitmap_array = np.asarray(bitmap.buffer).reshape( - (bitmap.width, bitmap.rows), order="F" - ) + bitmap_array = np.asarray(bitmap.buffer).reshape((bitmap.width, bitmap.rows), order="F") if bitmap_array.size == 0: continue # Skip blank glyphs. output_image = np.zeros(size, dtype=np.uint8, order="F") @@ -50,9 +48,9 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: # `max` is used because I was too lazy to properly slice the array. out_slice = out_slice[max(0, left) :, max(0, top) :] - out_slice[ - : bitmap_array.shape[0], : bitmap_array.shape[1] - ] = bitmap_array[: out_slice.shape[0], : out_slice.shape[1]] + out_slice[: bitmap_array.shape[0], : bitmap_array.shape[1]] = bitmap_array[ + : out_slice.shape[0], : out_slice.shape[1] + ] tileset.set_tile(codepoint, output_image.transpose()) return tileset @@ -71,18 +69,13 @@ def main() -> None: console.tiles_rgb["bg"][::2, ::2] = 0x20 console.tiles_rgb["bg"][1::2, 1::2] = 0x20 # Print ASCII characters. - console.tiles_rgb["ch"][:16, :6] = np.arange(0x20, 0x80).reshape( - 0x10, -1, order="F" - ) + console.tiles_rgb["ch"][:16, :6] = np.arange(0x20, 0x80).reshape(0x10, -1, order="F") console.print(0, 7, "Example text.") context.present(console, integer_scaling=True) for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): raise SystemExit() - if ( - isinstance(event, tcod.event.WindowResized) - and event.type == "WINDOWSIZECHANGED" - ): + if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWSIZECHANGED": # Resize the Tileset to match the new screen size. context.change_tileset( load_ttf( @@ -96,8 +89,6 @@ def main() -> None: if __name__ == "__main__": - tcod_version = tuple( - int(n) for n in tcod.__version__.split(".") if n.isdigit() - ) + tcod_version = tuple(int(n) for n in tcod.__version__.split(".") if n.isdigit()) assert tcod_version[:2] >= (12, 1), "Must be using tcod 12.1 or later." main() diff --git a/parse_sdl2.py b/parse_sdl2.py index 964ba84a..34d2f931 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -10,27 +10,18 @@ # supported by cffi. RE_COMMENT = re.compile(r" */\*.*?\*/", re.DOTALL) RE_REMOVALS = re.compile( - r"#ifndef DOXYGEN_SHOULD_IGNORE_THIS.*" - r"#endif /\* DOXYGEN_SHOULD_IGNORE_THIS \*/", + r"#ifndef DOXYGEN_SHOULD_IGNORE_THIS.*" r"#endif /\* DOXYGEN_SHOULD_IGNORE_THIS \*/", re.DOTALL, ) RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) RE_TYPEDEF = re.compile(r"^typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) RE_ENUM = re.compile(r"^(?:typedef )?enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) RE_DECL = re.compile(r"^extern[^#(]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) -RE_ENDIAN = re.compile( - r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL -) -RE_ENDIAN2 = re.compile( - r"#if SDL_BYTEORDER == SDL_BIG_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL -) +RE_ENDIAN = re.compile(r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL) +RE_ENDIAN2 = re.compile(r"#if SDL_BYTEORDER == SDL_BIG_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL) RE_DEFINE_TRUNCATE = re.compile(r"(#define\s+\w+\s+).+$", flags=re.DOTALL) -RE_TYPEDEF_TRUNCATE = re.compile( - r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE -) -RE_ENUM_TRUNCATE = re.compile( - r"(\w+\s*=).+?(?=,$|})(?![^(']*\))", re.MULTILINE | re.DOTALL -) +RE_TYPEDEF_TRUNCATE = re.compile(r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE) +RE_ENUM_TRUNCATE = re.compile(r"(\w+\s*=).+?(?=,$|})(?![^(']*\))", re.MULTILINE | re.DOTALL) def get_header(name: str) -> str: @@ -42,12 +33,8 @@ def get_header(name: str) -> str: # Remove comments. header = RE_COMMENT.sub("", header) # Deal with endianness in "SDL_audio.h". - header = RE_ENDIAN.sub( - r"\1" if sys.byteorder == "little" else r"\2", header - ) - header = RE_ENDIAN2.sub( - r"\1" if sys.byteorder != "little" else r"\2", header - ) + header = RE_ENDIAN.sub(r"\1" if sys.byteorder == "little" else r"\2", header) + header = RE_ENDIAN2.sub(r"\1" if sys.byteorder != "little" else r"\2", header) # Ignore bad ARM compiler typedef. header = header.replace("typedef int SDL_bool;", "") @@ -81,9 +68,7 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: for typedef in RE_TYPEDEF.findall(header): # Special case for SDL window flags enum. if "SDL_WINDOW_FULLSCREEN_DESKTOP" in typedef: - typedef = typedef.replace( - "( SDL_WINDOW_FULLSCREEN | 0x00001000 )", "..." - ) + typedef = typedef.replace("( SDL_WINDOW_FULLSCREEN | 0x00001000 )", "...") # Detect array sizes at compile time. typedef = typedef.replace("SDL_TEXTINPUTEVENT_TEXT_SIZE", "...") typedef = typedef.replace("SDL_TEXTEDITINGEVENT_TEXT_SIZE", "...") @@ -154,21 +139,13 @@ def add_to_ffi(ffi: cffi.FFI, path: str) -> None: # cdef_args["pack"] = 4 ffi.cdef( - "\n".join( - RE_TYPEDEF.findall(get_header(os.path.join(path, "SDL_stdinc.h"))) - ).replace("SDLCALL ", ""), - **cdef_args + "\n".join(RE_TYPEDEF.findall(get_header(os.path.join(path, "SDL_stdinc.h")))).replace("SDLCALL ", ""), + **cdef_args, ) for header in HEADERS: try: - for code in parse( - get_header(os.path.join(path, header)), NEEDS_PACK4 - ): - if ( - "typedef struct SDL_AudioCVT" in code - and sys.platform != "win32" - and not NEEDS_PACK4 - ): + for code in parse(get_header(os.path.join(path, header)), NEEDS_PACK4): + if "typedef struct SDL_AudioCVT" in code and sys.platform != "win32" and not NEEDS_PACK4: # This specific struct needs to be packed. ffi.cdef(code, packed=1) continue diff --git a/pyproject.toml b/pyproject.toml index 6d52c0df..0278424c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires = [ build-backend = "setuptools.build_meta" [tool.black] -line-length = 79 +line-length = 120 py36 = true target-version = ["py36"] @@ -16,4 +16,4 @@ target-version = ["py36"] profile= "black" py_version = "36" skip_gitignore = true -line_length = 79 +line_length = 120 diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 205b87f0..6f7c8514 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -7,15 +7,16 @@ import unicodedata from typing import Iterable, Iterator -import tcod.tileset from tabulate import tabulate +import tcod.tileset + def get_charmaps() -> Iterator[str]: """Return an iterator of the current character maps from tcod.tilest.""" for name in dir(tcod.tileset): if name.startswith("CHARMAP_"): - yield name[len("CHARMAP_"):].lower() + yield name[len("CHARMAP_") :].lower() def generate_table(charmap: Iterable[int]) -> str: @@ -32,11 +33,7 @@ def generate_table(charmap: Iterable[int]) -> str: except ValueError: # Skip names rather than guessing, the official names are enough. name = "" - string = ( - f"{chr(ch)!r}".replace("\\", "\\\\") - .replace("*", "\\*") # Escape RST symbols. - .replace("`", "\\`") - ) + string = f"{chr(ch)!r}".replace("\\", "\\\\").replace("*", "\\*").replace("`", "\\`") # Escape RST symbols. table.append((i, f"0x{ch:0{hex_len}X}", string, name)) return tabulate(table, headers, tablefmt="rst") diff --git a/scripts/tag_release.py b/scripts/tag_release.py index e9b55080..3e7225b6 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -1,29 +1,20 @@ #!/usr/bin/env python3 -import sys - import argparse import datetime import re import subprocess +import sys from typing import Any, Tuple -parser = argparse.ArgumentParser( - description="Tags and releases the next version of this project." -) +parser = argparse.ArgumentParser(description="Tags and releases the next version of this project.") parser.add_argument("tag", help="Semantic version number to use as the tag.") -parser.add_argument( - "-e", "--edit", action="store_true", help="Force edits of git commits." -) +parser.add_argument("-e", "--edit", action="store_true", help="Force edits of git commits.") -parser.add_argument( - "-n", "--dry-run", action="store_true", help="Don't modify files." -) +parser.add_argument("-n", "--dry-run", action="store_true", help="Don't modify files.") -parser.add_argument( - "-v", "--verbose", action="store_true", help="Print debug information." -) +parser.add_argument("-v", "--verbose", action="store_true", help="Print debug information.") def parse_changelog(args: Any) -> Tuple[str, str]: @@ -60,13 +51,8 @@ def main() -> None: with open("CHANGELOG.rst", "w") as f: f.write(new_changelog) edit = ["-e"] if args.edit else [] - subprocess.check_call( - ["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit - ) - subprocess.check_call( - ["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] - + edit - ) + subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) + subprocess.check_call(["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] + edit) if __name__ == "__main__": diff --git a/setup.py b/setup.py index 1b82df2d..11c097fe 100755 --- a/setup.py +++ b/setup.py @@ -15,9 +15,7 @@ def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" try: - tag = check_output( - ["git", "describe", "--abbrev=0"], universal_newlines=True - ).strip() + tag = check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() assert not tag.startswith("v") version = tag @@ -40,10 +38,7 @@ def get_version() -> str: with open("tcod/version.py") as version_file: exec(version_file.read(), globals()) # Update __version__ except FileNotFoundError: - warnings.warn( - "Unknown version: " - "Not in a Git repository and not from a sdist bundle or wheel." - ) + warnings.warn("Unknown version: " "Not in a Git repository and not from a sdist bundle or wheel.") return __version__ @@ -81,9 +76,7 @@ def check_sdl_version() -> None: return needed_version = "%i.%i.%i" % SDL_VERSION_NEEDED try: - sdl_version_str = check_output( - ["sdl2-config", "--version"], universal_newlines=True - ).strip() + sdl_version_str = check_output(["sdl2-config", "--version"], universal_newlines=True).strip() except FileNotFoundError: raise RuntimeError( "libsdl2-dev or equivalent must be installed on your system" @@ -93,10 +86,7 @@ def check_sdl_version() -> None: print("Found SDL %s." % (sdl_version_str,)) sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_VERSION_NEEDED: - raise RuntimeError( - "SDL version must be at least %s, (found %s)" - % (needed_version, sdl_version_str) - ) + raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) if not os.path.exists("libtcod/src"): diff --git a/tcod/.clang-format b/tcod/.clang-format index 322668fd..1c1a215d 100644 --- a/tcod/.clang-format +++ b/tcod/.clang-format @@ -5,7 +5,7 @@ Language: Cpp DerivePointerAlignment: false PointerAlignment: Left -ColumnLimit: 79 +ColumnLimit: 120 AlignAfterOpenBracket: AlwaysBreak BinPackArguments: false BinPackParameters: false diff --git a/tcod/__init__.py b/tcod/__init__.py index f7020a16..a0a803bd 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -18,20 +18,7 @@ import sys import warnings -from tcod import ( - bsp, - color, - console, - context, - event, - image, - los, - map, - noise, - path, - random, - tileset, -) +from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset from tcod.console import Console # noqa: F401 from tcod.constants import * # noqa: F4 from tcod.libtcodpy import * # noqa: F4 diff --git a/tcod/_internal.py b/tcod/_internal.py index 64ac6bc0..d785301d 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -13,9 +13,7 @@ F = TypeVar("F", bound=FuncType) -def deprecate( - message: str, category: Any = DeprecationWarning, stacklevel: int = 0 -) -> Callable[[F], F]: +def deprecate(message: str, category: Any = DeprecationWarning, stacklevel: int = 0) -> Callable[[F], F]: """Return a decorator which adds a warning to functions.""" def decorator(func: F) -> F: @@ -104,10 +102,7 @@ def _bytes(string: AnyStr) -> bytes: def _unicode(string: AnyStr, stacklevel: int = 2) -> str: if isinstance(string, bytes): warnings.warn( - ( - "Passing byte strings as parameters to Unicode functions is " - "deprecated." - ), + ("Passing byte strings as parameters to Unicode functions is " "deprecated."), DeprecationWarning, stacklevel=stacklevel + 1, ) @@ -118,10 +113,7 @@ def _unicode(string: AnyStr, stacklevel: int = 2) -> str: def _fmt(string: str, stacklevel: int = 2) -> bytes: if isinstance(string, bytes): warnings.warn( - ( - "Passing byte strings as parameters to Unicode functions is " - "deprecated." - ), + ("Passing byte strings as parameters to Unicode functions is " "deprecated."), DeprecationWarning, stacklevel=stacklevel + 1, ) @@ -234,10 +226,7 @@ def __init__(self, array: Any): self._array = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape if depth != 3: - raise TypeError( - "Array must have RGB channels. Shape is: %r" - % (self._array.shape,) - ) + raise TypeError("Array must have RGB channels. Shape is: %r" % (self._array.shape,)) self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) self._mipmaps = ffi.new( "struct TCOD_mipmap_*", diff --git a/tcod/bsp.py b/tcod/bsp.py index 485676e0..8debd752 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -85,9 +85,7 @@ def h(self, value: int) -> None: def _as_cdata(self) -> Any: cdata = ffi.gc( - lib.TCOD_bsp_new_with_size( - self.x, self.y, self.width, self.height - ), + lib.TCOD_bsp_new_with_size(self.x, self.y, self.width, self.height), lib.TCOD_bsp_delete, ) cdata.level = self.level @@ -252,10 +250,7 @@ def contains(self, x: int, y: int) -> bool: bool: True if this node contains these coordinates. Otherwise False. """ - return ( - self.x <= x < self.x + self.width - and self.y <= y < self.y + self.height - ) + return self.x <= x < self.x + self.width and self.y <= y < self.y + self.height def find_node(self, x: int, y: int) -> Optional["BSP"]: """Return the deepest node which contains these coordinates. diff --git a/tcod/cdef.h b/tcod/cdef.h index f3b75599..70ed74f9 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -3,8 +3,7 @@ extern "Python" { bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char* name); bool _pycall_parser_new_flag(const char* name); -bool _pycall_parser_new_property( - const char* propname, TCOD_value_type_t type, TCOD_value_t value); +bool _pycall_parser_new_property(const char* propname, TCOD_value_type_t type, TCOD_value_t value); bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char* name); void _pycall_parser_error(const char* msg); @@ -12,8 +11,7 @@ bool _pycall_bsp_callback(TCOD_bsp_t* node, void* userData); float _pycall_path_old(int x, int y, int xDest, int yDest, void* user_data); float _pycall_path_simple(int x, int y, int xDest, int yDest, void* user_data); -float _pycall_path_swap_src_dest( - int x1, int y1, int x2, int y2, void* user_data); +float _pycall_path_swap_src_dest(int x1, int y1, int x2, int y2, void* user_data); float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data); void _pycall_sdl_hook(struct SDL_Surface*); diff --git a/tcod/color.py b/tcod/color.py index 35db9b39..b7a5f4db 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -127,9 +127,7 @@ def __mul__(self, other: Any) -> "Color": if isinstance(other, (Color, list, tuple)): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) else: - return Color._new_from_cdata( - lib.TCOD_color_multiply_scalar(self, other) - ) + return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) def __repr__(self) -> str: """Return a printable representation of the current color.""" diff --git a/tcod/console.py b/tcod/console.py index b263dd44..584d2407 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -134,9 +134,7 @@ def __init__( "w": width, "h": height, "elements": width * height, - "tiles": ffi.from_buffer( - "struct TCOD_ConsoleTile*", self._tiles - ), + "tiles": ffi.from_buffer("struct TCOD_ConsoleTile*", self._tiles), "bkgnd_flag": default_bg_blend, "alignment": default_alignment, "fore": (255, 255, 255), @@ -148,9 +146,7 @@ def __init__( self.clear() @classmethod - def _from_cdata( - cls, cdata: Any, order: Literal["C", "F"] = "C" - ) -> "Console": + def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> "Console": """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): return cdata @@ -183,9 +179,7 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: _root_console = self self._console_data = lib.TCOD_ctx.root else: - self._console_data = ffi.cast( - "struct TCOD_Console*", self.console_c - ) + self._console_data = ffi.cast("struct TCOD_Console*", self.console_c) self._tiles = np.frombuffer( ffi.buffer(self._console_data.tiles[0 : self.width * self.height]), @@ -385,8 +379,7 @@ def default_alignment(self, value: int) -> None: def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: """Raise a warning for bad default values during calls to clear.""" warnings.warn( - "Clearing with the console default values is deprecated.\n" - "Add %s=%r to this call." % (name, value), + "Clearing with the console default values is deprecated.\n" "Add %s=%r to this call." % (name, value), DeprecationWarning, stacklevel=3, ) @@ -504,9 +497,7 @@ def __deprecate_defaults( if bg_blend is not None: params.append("bg_blend=%s" % (self.__BG_BLEND_LOOKUP[bg_blend],)) if alignment is not None: - params.append( - "alignment=%s" % (self.__ALIGNMENT_LOOKUP[alignment],) - ) + params.append("alignment=%s" % (self.__ALIGNMENT_LOOKUP[alignment],)) param_str = ", ".join(params) if not param_str: param_str = "." @@ -544,9 +535,7 @@ def print_( """ self.__deprecate_defaults("print", bg_blend, alignment) alignment = self.default_alignment if alignment is None else alignment - lib.TCOD_console_printf_ex( - self.console_c, x, y, bg_blend, alignment, _fmt(string) - ) + lib.TCOD_console_printf_ex(self.console_c, x, y, bg_blend, alignment, _fmt(string)) def print_rect( self, @@ -598,9 +587,7 @@ def print_rect( ) ) - def get_height_rect( - self, x: int, y: int, width: int, height: int, string: str - ) -> int: + def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: """Return the height of this text word-wrapped into this rectangle. Args: @@ -614,11 +601,7 @@ def get_height_rect( int: The number of lines of text once word-wrapped. """ string_ = string.encode("utf-8") - return int( - lib.TCOD_console_get_height_rect_n( - self.console_c, x, y, width, height, len(string_), string_ - ) - ) + return int(lib.TCOD_console_get_height_rect_n(self.console_c, x, y, width, height, len(string_), string_)) def rect( self, @@ -650,9 +633,7 @@ def rect( explicit. """ self.__deprecate_defaults("draw_rect", bg_blend, clear=bool(clear)) - lib.TCOD_console_rect( - self.console_c, x, y, width, height, clear, bg_blend - ) + lib.TCOD_console_rect(self.console_c, x, y, width, height, clear, bg_blend) def hline( self, @@ -748,11 +729,7 @@ def print_frame( """ self.__deprecate_defaults("draw_frame", bg_blend) string = _fmt(string) if string else ffi.NULL - _check( - lib.TCOD_console_printf_frame( - self.console_c, x, y, width, height, clear, bg_blend, string - ) - ) + _check(lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, clear, bg_blend, string)) def blit( self, @@ -844,9 +821,7 @@ def blit( bg_alpha, ) - @deprecate( - "Pass the key color to Console.blit instead of calling this function." - ) + @deprecate("Pass the key color to Console.blit instead of calling this function.") def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: """Set a consoles blit transparent color. @@ -884,9 +859,7 @@ def close(self) -> None: .. versionadded:: 11.11 """ if self.console_c != ffi.NULL: - raise NotImplementedError( - "Only the root console can be used to close libtcod's window." - ) + raise NotImplementedError("Only the root console can be used to close libtcod's window.") lib.TCOD_console_delete(self.console_c) def __exit__(self, *args: Any) -> None: @@ -933,26 +906,21 @@ def __setstate__(self, state: Any) -> None: del state["_bg"] self.__dict__.update(state) - self._console_data["tiles"] = ffi.from_buffer( - "struct TCOD_ConsoleTile*", self._tiles - ) - self._console_data = self.console_c = ffi.new( - "struct TCOD_Console*", self._console_data - ) + self._console_data["tiles"] = ffi.from_buffer("struct TCOD_ConsoleTile*", self._tiles) + self._console_data = self.console_c = ffi.new("struct TCOD_Console*", self._console_data) def __repr__(self) -> str: """Return a string representation of this console.""" - return ( - "tcod.console.Console(width=%i, height=%i, " - "order=%r,buffer=\n%r)" - % (self.width, self.height, self._order, self.tiles) + return "tcod.console.Console(width=%i, height=%i, " "order=%r,buffer=\n%r)" % ( + self.width, + self.height, + self._order, + self.tiles, ) def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" - return "<%s>" % "|\n|".join( - "".join(chr(c) for c in line) for line in self._tiles["ch"] - ) + return "<%s>" % "|\n|".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: if __debug__ and (x < 0 or y < 0): @@ -1189,9 +1157,7 @@ def draw_semigraphics(self, pixels: Any, x: int = 0, y: int = 0) -> None: .. versionadded:: 11.4 """ image = tcod._internal._asimage(pixels) - lib.TCOD_image_blit_2x( - image.image_c, self.console_c, x, y, 0, 0, -1, -1 - ) + lib.TCOD_image_blit_2x(image.image_c, self.console_c, x, y, 0, 0, -1, -1) def get_height_rect(width: int, string: str) -> int: @@ -1204,9 +1170,7 @@ def get_height_rect(width: int, string: str) -> int: .. versionadded:: 9.2 """ string_ = string.encode("utf-8") # type: bytes - return int( - lib.TCOD_console_get_height_rect_wn(width, len(string_), string_) - ) + return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) @deprecate("This function does not support contexts.") @@ -1242,9 +1206,7 @@ def recommended_size() -> Tuple[int, int]: return w, h -def load_xp( - path: Union[str, pathlib.Path], order: Literal["C", "F"] = "C" -) -> Tuple[Console, ...]: +def load_xp(path: Union[str, pathlib.Path], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: """Load a REXPaint file as a tuple of consoles. `path` is the name of the REXPaint file to load. @@ -1279,14 +1241,10 @@ def load_xp( """ if not os.path.exists(path): raise FileNotFoundError(f"File not found:\n\t{os.path.abspath(path)}") - layers = _check( - tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), 0, ffi.NULL) - ) + layers = _check(tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), 0, ffi.NULL)) consoles = ffi.new("TCOD_Console*[]", layers) _check(tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), layers, consoles)) - return tuple( - Console._from_cdata(console_p, order=order) for console_p in consoles - ) + return tuple(Console._from_cdata(console_p, order=order) for console_p in consoles) def save_xp( diff --git a/tcod/context.py b/tcod/context.py index 0f5b4bc5..87005cc1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -213,30 +213,18 @@ def present( "align_y": align[1], }, ) - _check( - lib.TCOD_context_present( - self._context_p, console.console_c, viewport_args - ) - ) + _check(lib.TCOD_context_present(self._context_p, console.console_c, viewport_args)) def pixel_to_tile(self, x: int, y: int) -> Tuple[int, int]: """Convert window pixel coordinates to tile coordinates.""" with ffi.new("int[2]", (x, y)) as xy: - _check( - lib.TCOD_context_screen_pixel_to_tile_i( - self._context_p, xy, xy + 1 - ) - ) + _check(lib.TCOD_context_screen_pixel_to_tile_i(self._context_p, xy, xy + 1)) return xy[0], xy[1] def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: """Convert window pixel coordinates to sub-tile coordinates.""" with ffi.new("double[2]", (x, y)) as xy: - _check( - lib.TCOD_context_screen_pixel_to_tile_d( - self._context_p, xy, xy + 1 - ) - ) + _check(lib.TCOD_context_screen_pixel_to_tile_d(self._context_p, xy, xy + 1)) return xy[0], xy[1] def convert_event(self, event: tcod.event.Event) -> None: @@ -248,9 +236,7 @@ def convert_event(self, event: tcod.event.Event) -> None: event.pixel[0] - event.pixel_motion[0], event.pixel[1] - event.pixel_motion[1], ) - event.tile_motion = tcod.event.Point( - event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1] - ) + event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) def save_screenshot(self, path: Optional[str] = None) -> None: """Save a screen-shot to the given file path.""" @@ -259,11 +245,7 @@ def save_screenshot(self, path: Optional[str] = None) -> None: def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None: """Change the active tileset used by this context.""" - _check( - lib.TCOD_context_change_tileset( - self._context_p, _handle_tileset(tileset) - ) - ) + _check(lib.TCOD_context_change_tileset(self._context_p, _handle_tileset(tileset))) def new_console( self, @@ -301,22 +283,13 @@ def new_console( :any:`tcod.console.Console` """ if magnification < 0: - raise ValueError( - "Magnification must be greater than zero. (Got %f)" - % magnification - ) + raise ValueError("Magnification must be greater than zero. (Got %f)" % magnification) size = ffi.new("int[2]") - _check( - lib.TCOD_context_recommended_console_size( - self._context_p, magnification, size, size + 1 - ) - ) + _check(lib.TCOD_context_recommended_console_size(self._context_p, magnification, size, size + 1)) width, height = max(min_columns, size[0]), max(min_rows, size[1]) return tcod.console.Console(width, height, order=order) - def recommended_console_size( - self, min_columns: int = 1, min_rows: int = 1 - ) -> Tuple[int, int]: + def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> Tuple[int, int]: """Return the recommended (columns, rows) of a console for this context. @@ -326,11 +299,7 @@ def recommended_console_size( call :any:`Context.new_console` instead. """ with ffi.new("int[2]") as size: - _check( - lib.TCOD_context_recommended_console_size( - self._context_p, 1.0, size, size + 1 - ) - ) + _check(lib.TCOD_context_recommended_console_size(self._context_p, 1.0, size, size + 1)) return max(min_columns, size[0]), max(min_rows, size[1]) @property @@ -446,9 +415,7 @@ def new( sdl_window_flags = SDL_WINDOW_RESIZABLE if argv is None: argv = sys.argv - argv_encoded = [ # Needs to be kept alive for argv_c. - ffi.new("char[]", arg.encode("utf-8")) for arg in argv - ] + argv_encoded = [ffi.new("char[]", arg.encode("utf-8")) for arg in argv] # Needs to be kept alive for argv_c. argv_c = ffi.new("char*[]", argv_encoded) catch_msg = [] # type: List[str] @@ -486,9 +453,7 @@ def new( return Context._claim(context_pp[0]) -@pending_deprecate( - "Call tcod.context.new with width and height as keyword parameters." -) +@pending_deprecate("Call tcod.context.new with width and height as keyword parameters.") def new_window( width: int, height: int, @@ -515,9 +480,7 @@ def new_window( ) -@pending_deprecate( - "Call tcod.context.new with columns and rows as keyword parameters." -) +@pending_deprecate("Call tcod.context.new with columns and rows as keyword parameters.") def new_terminal( columns: int, rows: int, diff --git a/tcod/event.py b/tcod/event.py index 5ade3383..81f0339e 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -21,18 +21,7 @@ """ import enum import warnings -from typing import ( - Any, - Callable, - Dict, - Generic, - Iterator, - Mapping, - NamedTuple, - Optional, - Tuple, - TypeVar, -) +from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, TypeVar import numpy as np @@ -58,9 +47,7 @@ def __iter__(self) -> Iterator[int]: return iter(self.constants) -def _describe_bitmask( - bits: int, table: Mapping[int, str], default: str = "0" -) -> str: +def _describe_bitmask(bits: int, table: Mapping[int, str], default: str = "0") -> str: """Return a bitmask in human readable form. This is a private function, used internally. @@ -209,15 +196,9 @@ class Modifier(enum.IntFlag): } _REVERSE_BUTTON_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_TABLE) -_REVERSE_BUTTON_MASK_TABLE_PREFIX = _ConstantsWithPrefix( - _REVERSE_BUTTON_MASK_TABLE -) -_REVERSE_SCANCODE_TABLE_PREFIX = _ConstantsWithPrefix( - tcod.event_constants._REVERSE_SCANCODE_TABLE -) -_REVERSE_SYM_TABLE_PREFIX = _ConstantsWithPrefix( - tcod.event_constants._REVERSE_SYM_TABLE -) +_REVERSE_BUTTON_MASK_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_MASK_TABLE) +_REVERSE_SCANCODE_TABLE_PREFIX = _ConstantsWithPrefix(tcod.event_constants._REVERSE_SCANCODE_TABLE) +_REVERSE_SYM_TABLE_PREFIX = _ConstantsWithPrefix(tcod.event_constants._REVERSE_SYM_TABLE) _REVERSE_MOD_TABLE = tcod.event_constants._REVERSE_MOD_TABLE.copy() @@ -292,9 +273,7 @@ class KeyboardEvent(Event): `scancode`, `sym`, and `mod` now use their respective enums. """ - def __init__( - self, scancode: int, sym: int, mod: int, repeat: bool = False - ): + def __init__(self, scancode: int, sym: int, mod: int, repeat: bool = False): super().__init__() self.scancode = Scancode(scancode) self.sym = KeySym(sym) @@ -304,9 +283,7 @@ def __init__( @classmethod def from_sdl_event(cls, sdl_event: Any) -> Any: keysym = sdl_event.key.keysym - self = cls( - keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat) - ) + self = cls(keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat)) self.sdl_event = sdl_event return self @@ -336,9 +313,7 @@ def __repr__(self) -> str: def __str__(self) -> str: return "<%s, scancode=%s, sym=%s, mod=%s, repeat=%r>" % ( super().__str__().strip("<>"), - self._scancode_constant( - tcod.event_constants._REVERSE_SCANCODE_TABLE - ), + self._scancode_constant(tcod.event_constants._REVERSE_SCANCODE_TABLE), self._sym_constant(tcod.event_constants._REVERSE_SYM_TABLE), _describe_bitmask(self.mod, _REVERSE_MOD_TABLE), self.repeat, @@ -437,9 +412,7 @@ def __init__( ): super().__init__(pixel, tile, state) self.pixel_motion = Point(*pixel_motion) - self.__tile_motion = ( - Point(*tile_motion) if tile_motion is not None else None - ) + self.__tile_motion = Point(*tile_motion) if tile_motion is not None else None @property def tile_motion(self) -> Point: @@ -469,10 +442,7 @@ def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": return self def __repr__(self) -> str: - return ( - "tcod.event.%s(pixel=%r, pixel_motion=%r, " - "tile=%r, tile_motion=%r, state=%s)" - ) % ( + return ("tcod.event.%s(pixel=%r, pixel_motion=%r, " "tile=%r, tile_motion=%r, state=%s)") % ( self.__class__.__name__, tuple(self.pixel), tuple(self.pixel_motion), @@ -482,9 +452,7 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return ( - "<%s, pixel_motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>" - ) % ( + return ("<%s, pixel_motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>") % ( super().__str__().strip("<>"), *self.pixel_motion, *self.tile_motion, @@ -547,14 +515,11 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return ( - " Any: lib.SDL_WINDOWEVENT_RESIZED, lib.SDL_WINDOWEVENT_SIZE_CHANGED, ): - self = WindowResized( - event_type, sdl_event.window.data1, sdl_event.window.data2 - ) + self = WindowResized(event_type, sdl_event.window.data1, sdl_event.window.data2) else: self = cls(event_type) self.sdl_event = sdl_event @@ -959,9 +922,7 @@ def dispatch(self, event: Any) -> Optional[T]: stacklevel=2, ) return None - func = getattr( - self, "ev_%s" % (event.type.lower(),) - ) # type: Callable[[Any], Optional[T]] + func = getattr(self, "ev_%s" % (event.type.lower(),)) # type: Callable[[Any], Optional[T]] return func(event) def event_get(self) -> None: @@ -984,14 +945,10 @@ def ev_keyup(self, event: "tcod.event.KeyUp") -> Optional[T]: def ev_mousemotion(self, event: "tcod.event.MouseMotion") -> Optional[T]: """Called when the mouse is moved.""" - def ev_mousebuttondown( - self, event: "tcod.event.MouseButtonDown" - ) -> Optional[T]: + def ev_mousebuttondown(self, event: "tcod.event.MouseButtonDown") -> Optional[T]: """Called when a mouse button is pressed.""" - def ev_mousebuttonup( - self, event: "tcod.event.MouseButtonUp" - ) -> Optional[T]: + def ev_mousebuttonup(self, event: "tcod.event.MouseButtonUp") -> Optional[T]: """Called when a mouse button is released.""" def ev_mousewheel(self, event: "tcod.event.MouseWheel") -> Optional[T]: @@ -1015,29 +972,19 @@ def ev_windowexposed(self, event: "tcod.event.WindowEvent") -> Optional[T]: def ev_windowmoved(self, event: "tcod.event.WindowMoved") -> Optional[T]: """Called when the window is moved.""" - def ev_windowresized( - self, event: "tcod.event.WindowResized" - ) -> Optional[T]: + def ev_windowresized(self, event: "tcod.event.WindowResized") -> Optional[T]: """Called when the window is resized.""" - def ev_windowsizechanged( - self, event: "tcod.event.WindowResized" - ) -> Optional[T]: + def ev_windowsizechanged(self, event: "tcod.event.WindowResized") -> Optional[T]: """Called when the system or user changes the size of the window.""" - def ev_windowminimized( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowminimized(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is minimized.""" - def ev_windowmaximized( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowmaximized(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is maximized.""" - def ev_windowrestored( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowrestored(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window is restored.""" def ev_windowenter(self, event: "tcod.event.WindowEvent") -> Optional[T]: @@ -1046,22 +993,16 @@ def ev_windowenter(self, event: "tcod.event.WindowEvent") -> Optional[T]: def ev_windowleave(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window loses mouse focus.""" - def ev_windowfocusgained( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowfocusgained(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowfocuslost(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window loses keyboard focus.""" def ev_windowclose(self, event: "tcod.event.WindowEvent") -> Optional[T]: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus( - self, event: "tcod.event.WindowEvent" - ) -> Optional[T]: + def ev_windowtakefocus(self, event: "tcod.event.WindowEvent") -> Optional[T]: pass def ev_windowhittest(self, event: "tcod.event.WindowEvent") -> Optional[T]: @@ -1111,9 +1052,7 @@ def get_keyboard_state() -> np.ndarray: """ numkeys = ffi.new("int[1]") keyboard_state = lib.SDL_GetKeyboardState(numkeys) - out: np.ndarray = np.frombuffer( - ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool - ) + out: np.ndarray = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool) out.flags["WRITEABLE"] = False # This buffer is supposed to be const. return out @@ -1662,8 +1601,7 @@ def scancode(self) -> "Scancode": def __eq__(self, other: Any) -> bool: if isinstance(other, KeySym): raise TypeError( - "Scancode and KeySym enums can not be compared directly." - " Convert one or the other to the same type." + "Scancode and KeySym enums can not be compared directly." " Convert one or the other to the same type." ) return super().__eq__(other) @@ -2177,9 +2115,7 @@ def label(self) -> str: >>> tcod.event.KeySym.BACKSPACE.label 'Backspace' """ - return str( - ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8" - ) + return str(ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8") @property def keysym(self) -> "KeySym": @@ -2204,8 +2140,7 @@ def scancode(self) -> Scancode: def __eq__(self, other: Any) -> bool: if isinstance(other, Scancode): raise TypeError( - "Scancode and KeySym enums can not be compared directly." - " Convert one or the other to the same type." + "Scancode and KeySym enums can not be compared directly." " Convert one or the other to the same type." ) return super().__eq__(other) diff --git a/tcod/image.py b/tcod/image.py index 114e761d..ee32014a 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -29,9 +29,7 @@ class Image(object): def __init__(self, width: int, height: int): self.width, self.height = width, height - self.image_c = ffi.gc( - lib.TCOD_image_new(width, height), lib.TCOD_image_delete - ) + self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete) @classmethod def _from_cdata(cls, cdata: Any) -> "Image": @@ -160,9 +158,7 @@ def get_pixel(self, x: int, y: int) -> Tuple[int, int, int]: color = lib.TCOD_image_get_pixel(self.image_c, x, y) return color.r, color.g, color.b - def get_mipmap_pixel( - self, left: float, top: float, right: float, bottom: float - ) -> Tuple[int, int, int]: + def get_mipmap_pixel(self, left: float, top: float, right: float, bottom: float) -> Tuple[int, int, int]: """Get the average color of a rectangle in this Image. Parameters should stay within the following limits: @@ -180,9 +176,7 @@ def get_mipmap_pixel( An (r, g, b) tuple containing the averaged color value. Values are in a 0 to 255 range. """ - color = lib.TCOD_image_get_mipmap_pixel( - self.image_c, left, top, right, bottom - ) + color = lib.TCOD_image_get_mipmap_pixel(self.image_c, left, top, right, bottom) return (color.r, color.g, color.b) def put_pixel(self, x: int, y: int, color: Tuple[int, int, int]) -> None: @@ -250,9 +244,7 @@ def blit_rect( height (int): Use -1 for Image height. bg_blend (int): Background blending mode to use. """ - lib.TCOD_image_blit_rect( - self.image_c, _console(console), x, y, width, height, bg_blend - ) + lib.TCOD_image_blit_rect(self.image_c, _console(console), x, y, width, height, bg_blend) def blit_2x( self, @@ -346,9 +338,7 @@ def load(filename: str) -> np.ndarray: .. versionadded:: 11.4 """ - image = Image._from_cdata( - ffi.gc(lib.TCOD_image_load(filename.encode()), lib.TCOD_image_delete) - ) + image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(filename.encode()), lib.TCOD_image_delete)) array = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 0d83b3ec..b804abbe 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -5,19 +5,7 @@ import sys import threading import warnings -from typing import ( - Any, - AnyStr, - Callable, - Hashable, - Iterable, - Iterator, - List, - Optional, - Sequence, - Tuple, - Union, -) +from typing import Any, AnyStr, Callable, Hashable, Iterable, Iterator, List, Optional, Sequence, Tuple, Union import numpy as np from typing_extensions import Literal @@ -172,9 +160,7 @@ def copy(self) -> "ConsoleBuffer": other.char = list(self.char) return other - def set_fore( - self, x: int, y: int, r: int, g: int, b: int, char: str - ) -> None: + def set_fore(self, x: int, y: int, r: int, g: int, b: int, char: str) -> None: """Set the character and foreground color of one cell. Args: @@ -258,10 +244,7 @@ def blit( if not dest: dest = tcod.console.Console._from_cdata(ffi.NULL) if dest.width != self.width or dest.height != self.height: - raise ValueError( - "ConsoleBuffer.blit: " - "Destination console has an incorrect size." - ) + raise ValueError("ConsoleBuffer.blit: " "Destination console has an incorrect size.") if fill_back: bg = dest.bg.ravel() @@ -342,11 +325,7 @@ def __repr__(self) -> str: # reverse lookup table for KEY_X attributes, used by Key.__repr__ -_LOOKUP_VK = { - value: "KEY_%s" % key[6:] - for key, value in lib.__dict__.items() - if key.startswith("TCODK") -} +_LOOKUP_VK = {value: "KEY_%s" % key[6:] for key, value in lib.__dict__.items() if key.startswith("TCODK")} class Key(_CDataWrapper): @@ -432,9 +411,7 @@ def __setattr__(self, attr: str, value: Any) -> None: def __repr__(self) -> str: """Return a representation of this Key object.""" params = [] - params.append( - "pressed=%r, vk=tcod.%s" % (self.pressed, _LOOKUP_VK[self.vk]) - ) + params.append("pressed=%r, vk=tcod.%s" % (self.pressed, _LOOKUP_VK[self.vk])) if self.c: params.append("c=ord(%r)" % chr(self.c)) if self.text: @@ -555,9 +532,7 @@ def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: @deprecate("Call node.split_once instead.") -def bsp_split_once( - node: tcod.bsp.BSP, horizontal: bool, position: int -) -> None: +def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: """ .. deprecated:: 2.0 Use :any:`BSP.split_once` instead. @@ -579,9 +554,7 @@ def bsp_split_recursive( .. deprecated:: 2.0 Use :any:`BSP.split_recursive` instead. """ - node.split_recursive( - nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer - ) + node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer) @deprecate("Assign values via attribute instead.") @@ -642,9 +615,7 @@ def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool: @deprecate("Use 'node.find_node' instead.") -def bsp_find_node( - node: tcod.bsp.BSP, cx: int, cy: int -) -> Optional[tcod.bsp.BSP]: +def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> Optional[tcod.bsp.BSP]: """ .. deprecated:: 2.0 Use :any:`BSP.find_node` instead. @@ -720,10 +691,7 @@ def bsp_traverse_level_order( _bsp_traverse(node.level_order(), callback, userData) -@deprecate( - "Iterate over nodes using " - "'for n in node.inverted_level_order():' instead." -) +@deprecate("Iterate over nodes using " "'for n in node.inverted_level_order():' instead.") def bsp_traverse_inverted_level_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], @@ -765,9 +733,7 @@ def bsp_delete(node: tcod.bsp.BSP) -> None: @pending_deprecate() -def color_lerp( - c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float -) -> Color: +def color_lerp(c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float) -> Color: """Return the linear interpolation between two colors. ``a`` is the interpolation value, with 0 returing ``c1``, @@ -840,9 +806,7 @@ def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: @pending_deprecate() -def color_gen_map( - colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int] -) -> List[Color]: +def color_gen_map(colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int]) -> List[Color]: """Return a smoothly defined scale of colors. If ``indexes`` is [0, 3, 9] for example, the first color from ``colors`` @@ -965,11 +929,7 @@ def console_init_root( DeprecationWarning, stacklevel=2, ) - _check_warn( - lib.TCOD_console_init_root_( - w, h, _bytes(title), fullscreen, renderer, vsync - ) - ) + _check_warn(lib.TCOD_console_init_root_(w, h, _bytes(title), fullscreen, renderer, vsync)) console = tcod.console.Console._get_root(order) console.clear() return console @@ -1012,14 +972,8 @@ def console_set_custom_font( See :ref:`getting-started` for more info. """ if not os.path.exists(_unicode(fontFile)): - raise RuntimeError( - "File not found:\n\t%s" % (str(os.path.realpath(fontFile)),) - ) - _check( - lib.TCOD_console_set_custom_font( - _bytes(fontFile), flags, nb_char_horiz, nb_char_vertic - ) - ) + raise RuntimeError("File not found:\n\t%s" % (str(os.path.realpath(fontFile)),)) + _check(lib.TCOD_console_set_custom_font(_bytes(fontFile), flags, nb_char_horiz, nb_char_vertic)) @deprecate("Check `con.width` instead.") @@ -1055,9 +1009,7 @@ def console_get_height(con: tcod.console.Console) -> int: @deprecate("Setup fonts using the tcod.tileset module.") -def console_map_ascii_code_to_font( - asciiCode: int, fontCharX: int, fontCharY: int -) -> None: +def console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int) -> None: """Set a character code to new coordinates on the tile-set. `asciiCode` should be any Unicode codepoint. @@ -1073,15 +1025,11 @@ def console_map_ascii_code_to_font( Setup fonts using the :any:`tcod.tileset` module. :any:`Tileset.remap` replaces this function. """ - lib.TCOD_console_map_ascii_code_to_font( - _int(asciiCode), fontCharX, fontCharY - ) + lib.TCOD_console_map_ascii_code_to_font(_int(asciiCode), fontCharX, fontCharY) @deprecate("Setup fonts using the tcod.tileset module.") -def console_map_ascii_codes_to_font( - firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int -) -> None: +def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int) -> None: """Remap a contiguous set of codes to a contiguous set of tiles. Both the tile-set and character codes must be contiguous to use this @@ -1101,9 +1049,7 @@ def console_map_ascii_codes_to_font( :any:`Tileset.remap` replaces this function. """ - lib.TCOD_console_map_ascii_codes_to_font( - _int(firstAsciiCode), nbCodes, fontCharX, fontCharY - ) + lib.TCOD_console_map_ascii_codes_to_font(_int(firstAsciiCode), nbCodes, fontCharX, fontCharY) @deprecate("Setup fonts using the tcod.tileset module.") @@ -1285,9 +1231,7 @@ def console_flush( # drawing on a console @deprecate("Set the `con.default_bg` attribute instead.") -def console_set_default_background( - con: tcod.console.Console, col: Tuple[int, int, int] -) -> None: +def console_set_default_background(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: """Change the default background color for a console. Args: @@ -1302,9 +1246,7 @@ def console_set_default_background( @deprecate("Set the `con.default_fg` attribute instead.") -def console_set_default_foreground( - con: tcod.console.Console, col: Tuple[int, int, int] -) -> None: +def console_set_default_foreground(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: """Change the default foreground color for a console. Args: @@ -1401,9 +1343,7 @@ def console_set_char_background( @deprecate("Directly access a consoles foreground color with `console.fg`") -def console_set_char_foreground( - con: tcod.console.Console, x: int, y: int, col: Tuple[int, int, int] -) -> None: +def console_set_char_foreground(con: tcod.console.Console, x: int, y: int, col: Tuple[int, int, int]) -> None: """Change the foreground color of x,y to col. Args: @@ -1421,9 +1361,7 @@ def console_set_char_foreground( @deprecate("Directly access a consoles characters with `console.ch`") -def console_set_char( - con: tcod.console.Console, x: int, y: int, c: Union[int, str] -) -> None: +def console_set_char(con: tcod.console.Console, x: int, y: int, c: Union[int, str]) -> None: """Change the character at x,y to c, keeping the current colors. Args: @@ -1536,9 +1474,7 @@ def console_print_ex( @deprecate("Call the `con.print_rect` method instead.") -def console_print_rect( - con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str -) -> int: +def console_print_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str) -> int: """Print a string constrained to a rectangle. If h > 0 and the bottom of the rectangle is reached, @@ -1553,9 +1489,7 @@ def console_print_rect( .. deprecated:: 8.5 Use :any:`Console.print_rect` instead. """ - return int( - lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt)) - ) + return int(lib.TCOD_console_printf_rect(_console(con), x, y, w, h, _fmt(fmt))) @deprecate("Call the `con.print_rect` method instead.") @@ -1577,17 +1511,11 @@ def console_print_rect_ex( .. deprecated:: 8.5 Use :any:`Console.print_rect` instead. """ - return int( - lib.TCOD_console_printf_rect_ex( - _console(con), x, y, w, h, flag, alignment, _fmt(fmt) - ) - ) + return int(lib.TCOD_console_printf_rect_ex(_console(con), x, y, w, h, flag, alignment, _fmt(fmt))) @deprecate("Call the `con.get_height_rect` method instead.") -def console_get_height_rect( - con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str -) -> int: +def console_get_height_rect(con: tcod.console.Console, x: int, y: int, w: int, h: int, fmt: str) -> int: """Return the height of this text once word-wrapped into this rectangle. Returns: @@ -1596,11 +1524,7 @@ def console_get_height_rect( .. deprecated:: 8.5 Use :any:`Console.get_height_rect` instead. """ - return int( - lib.TCOD_console_get_height_rect_fmt( - _console(con), x, y, w, h, _fmt(fmt) - ) - ) + return int(lib.TCOD_console_get_height_rect_fmt(_console(con), x, y, w, h, _fmt(fmt))) @deprecate("Call the `con.rect` method instead.") @@ -1685,17 +1609,11 @@ def console_print_frame( Use :any:`Console.print_frame` instead. """ fmt = _fmt(fmt) if fmt else ffi.NULL - _check( - lib.TCOD_console_printf_frame( - _console(con), x, y, w, h, clear, flag, fmt - ) - ) + _check(lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt)) @pending_deprecate() -def console_set_color_control( - con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int] -) -> None: +def console_set_color_control(con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int]) -> None: """Configure :term:`color controls`. Args: @@ -1715,9 +1633,7 @@ def console_get_default_background(con: tcod.console.Console) -> Color: .. deprecated:: 8.5 Use :any:`Console.default_bg` instead. """ - return Color._new_from_cdata( - lib.TCOD_console_get_default_background(_console(con)) - ) + return Color._new_from_cdata(lib.TCOD_console_get_default_background(_console(con))) @deprecate("Check the `con.default_fg` attribute instead.") @@ -1727,39 +1643,29 @@ def console_get_default_foreground(con: tcod.console.Console) -> Color: .. deprecated:: 8.5 Use :any:`Console.default_fg` instead. """ - return Color._new_from_cdata( - lib.TCOD_console_get_default_foreground(_console(con)) - ) + return Color._new_from_cdata(lib.TCOD_console_get_default_foreground(_console(con))) @deprecate("Directly access a consoles background color with `console.bg`") -def console_get_char_background( - con: tcod.console.Console, x: int, y: int -) -> Color: +def console_get_char_background(con: tcod.console.Console, x: int, y: int) -> Color: """Return the background color at the x,y of this console. .. deprecated:: 8.4 Array access performs significantly faster than using this function. See :any:`Console.bg`. """ - return Color._new_from_cdata( - lib.TCOD_console_get_char_background(_console(con), x, y) - ) + return Color._new_from_cdata(lib.TCOD_console_get_char_background(_console(con), x, y)) @deprecate("Directly access a consoles foreground color with `console.fg`") -def console_get_char_foreground( - con: tcod.console.Console, x: int, y: int -) -> Color: +def console_get_char_foreground(con: tcod.console.Console, x: int, y: int) -> Color: """Return the foreground color at the x,y of this console. .. deprecated:: 8.4 Array access performs significantly faster than using this function. See :any:`Console.fg`. """ - return Color._new_from_cdata( - lib.TCOD_console_get_char_foreground(_console(con), x, y) - ) + return Color._new_from_cdata(lib.TCOD_console_get_char_foreground(_console(con), x, y)) @deprecate("Directly access a consoles characters with `console.ch`") @@ -1860,12 +1766,8 @@ def console_from_file(filename: str) -> tcod.console.Console: Returns: A new :any`Console` instance. """ if not os.path.exists(filename): - raise RuntimeError( - "File not found:\n\t%s" % (os.path.realpath(filename),) - ) - return tcod.console.Console._from_cdata( - _check_p(lib.TCOD_console_from_file(filename.encode("utf-8"))) - ) + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(filename.encode("utf-8")))) @deprecate("Call the `Console.blit` method instead.") @@ -1886,17 +1788,11 @@ def console_blit( .. deprecated:: 8.5 Call the :any:`Console.blit` method instead. """ - lib.TCOD_console_blit( - _console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade - ) + lib.TCOD_console_blit(_console(src), x, y, w, h, _console(dst), xdst, ydst, ffade, bfade) -@deprecate( - "Pass the key color to `Console.blit` instead of calling this function." -) -def console_set_key_color( - con: tcod.console.Console, col: Tuple[int, int, int] -) -> None: +@deprecate("Pass the key color to `Console.blit` instead of calling this function.") +def console_set_key_color(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: """Set a consoles blit transparent color. .. deprecated:: 8.5 @@ -1936,8 +1832,7 @@ def console_delete(con: tcod.console.Console) -> None: ) else: warnings.warn( - "You no longer need to make this call, " - "Console's are deleted when they go out of scope.", + "You no longer need to make this call, " "Console's are deleted when they go out of scope.", DeprecationWarning, stacklevel=2, ) @@ -1963,11 +1858,7 @@ def console_fill_foreground( """ if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") - if ( - isinstance(r, np.ndarray) - and isinstance(g, np.ndarray) - and isinstance(b, np.ndarray) - ): + if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray): # numpy arrays, use numpy's ctypes functions r_ = np.ascontiguousarray(r, dtype=np.intc) g_ = np.ascontiguousarray(g, dtype=np.intc) @@ -2004,11 +1895,7 @@ def console_fill_background( """ if len(r) != len(g) or len(r) != len(b): raise TypeError("R, G and B must all have the same size.") - if ( - isinstance(r, np.ndarray) - and isinstance(g, np.ndarray) - and isinstance(b, np.ndarray) - ): + if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray): # numpy arrays, use numpy's ctypes functions r_ = np.ascontiguousarray(r, dtype=np.intc) g_ = np.ascontiguousarray(g, dtype=np.intc) @@ -2049,33 +1936,25 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: @pending_deprecate() def console_load_asc(con: tcod.console.Console, filename: str) -> bool: """Update a console from a non-delimited ASCII `.asc` file.""" - return bool( - lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8")) - ) + return bool(lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8"))) @pending_deprecate() def console_save_asc(con: tcod.console.Console, filename: str) -> bool: """Save a console to a non-delimited ASCII `.asc` file.""" - return bool( - lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8")) - ) + return bool(lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8"))) @pending_deprecate() def console_load_apf(con: tcod.console.Console, filename: str) -> bool: """Update a console from an ASCII Paint `.apf` file.""" - return bool( - lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8")) - ) + return bool(lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8"))) @pending_deprecate() def console_save_apf(con: tcod.console.Console, filename: str) -> bool: """Save a console to an ASCII Paint `.apf` file.""" - return bool( - lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8")) - ) + return bool(lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8"))) @deprecate("Use tcod.console.load_xp to load this file.") @@ -2086,33 +1965,21 @@ def console_load_xp(con: tcod.console.Console, filename: str) -> bool: Functions modifying console objects in-place are deprecated. Use :any:`tcod.console_from_xp` to load a Console from a file. """ - return bool( - lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8")) - ) + return bool(lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8"))) @deprecate("Use tcod.console.save_xp to save this console.") -def console_save_xp( - con: tcod.console.Console, filename: str, compress_level: int = 9 -) -> bool: +def console_save_xp(con: tcod.console.Console, filename: str, compress_level: int = 9) -> bool: """Save a console to a REXPaint `.xp` file.""" - return bool( - lib.TCOD_console_save_xp( - _console(con), filename.encode("utf-8"), compress_level - ) - ) + return bool(lib.TCOD_console_save_xp(_console(con), filename.encode("utf-8"), compress_level)) @deprecate("Use tcod.console.load_xp to load this file.") def console_from_xp(filename: str) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file.""" if not os.path.exists(filename): - raise RuntimeError( - "File not found:\n\t%s" % (os.path.realpath(filename),) - ) - return tcod.console.Console._from_cdata( - _check_p(lib.TCOD_console_from_xp(filename.encode("utf-8"))) - ) + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(filename.encode("utf-8")))) @deprecate("Use tcod.console.load_xp to load this file.") @@ -2121,9 +1988,7 @@ def console_list_load_xp( ) -> Optional[List[tcod.console.Console]]: """Return a list of consoles from a REXPaint `.xp` file.""" if not os.path.exists(filename): - raise RuntimeError( - "File not found:\n\t%s" % (os.path.realpath(filename),) - ) + raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) if tcod_list == ffi.NULL: return None @@ -2131,9 +1996,7 @@ def console_list_load_xp( python_list = [] lib.TCOD_list_reverse(tcod_list) while not lib.TCOD_list_is_empty(tcod_list): - python_list.append( - tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list)) - ) + python_list.append(tcod.console.Console._from_cdata(lib.TCOD_list_pop(tcod_list))) return python_list finally: lib.TCOD_list_delete(tcod_list) @@ -2150,19 +2013,13 @@ def console_list_save_xp( try: for console in console_list: lib.TCOD_list_push(tcod_list, _console(console)) - return bool( - lib.TCOD_console_list_save_xp( - tcod_list, filename.encode("utf-8"), compress_level - ) - ) + return bool(lib.TCOD_console_list_save_xp(tcod_list, filename.encode("utf-8"), compress_level)) finally: lib.TCOD_list_delete(tcod_list) @pending_deprecate() -def path_new_using_map( - m: tcod.map.Map, dcost: float = 1.41 -) -> tcod.path.AStar: +def path_new_using_map(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.AStar: """Return a new AStar using the given Map. Args: @@ -2195,15 +2052,11 @@ def path_new_using_function( Returns: AStar: A new AStar instance. """ - return tcod.path.AStar( - tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost - ) + return tcod.path.AStar(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost) @pending_deprecate() -def path_compute( - p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int -) -> bool: +def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool: """Find a path from (ox, oy) to (dx, dy). Return True if path is found. Args: @@ -2301,9 +2154,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: @pending_deprecate() -def path_walk( - p: tcod.path.AStar, recompute: bool -) -> Union[Tuple[int, int], Tuple[None, None]]: +def path_walk(p: tcod.path.AStar, recompute: bool) -> Union[Tuple[int, int], Tuple[None, None]]: """Return the next (x, y) point in a path, or (None, None) if it's empty. When ``recompute`` is True and a previously valid path reaches a point @@ -2344,9 +2195,7 @@ def dijkstra_new_using_function( userData: Any = 0, dcost: float = 1.41, ) -> tcod.path.Dijkstra: - return tcod.path.Dijkstra( - tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost - ) + return tcod.path.Dijkstra(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost) @pending_deprecate() @@ -2458,8 +2307,7 @@ def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None: """ if hm.flags["C_CONTIGUOUS"]: warnings.warn( - "Assign to this heightmap with hm[i,j] = value\n" - "consider using order='F'", + "Assign to this heightmap with hm[i,j] = value\n" "consider using order='F'", DeprecationWarning, stacklevel=2, ) @@ -2546,9 +2394,7 @@ def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: @pending_deprecate() -def heightmap_normalize( - hm: np.ndarray, mi: float = 0.0, ma: float = 1.0 -) -> None: +def heightmap_normalize(hm: np.ndarray, mi: float = 0.0, ma: float = 1.0) -> None: """Normalize heightmap values between ``mi`` and ``ma``. Args: @@ -2559,9 +2405,7 @@ def heightmap_normalize( @pending_deprecate() -def heightmap_lerp_hm( - hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: float -) -> None: +def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: float) -> None: """Perform linear interpolation between two heightmaps storing the result in ``hm3``. @@ -2582,9 +2426,7 @@ def heightmap_lerp_hm( @deprecate("Add 2 arrays using `hm3 = hm1 + hm2`") -def heightmap_add_hm( - hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray -) -> None: +def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> None: """Add two heightmaps together and stores the result in ``hm3``. Args: @@ -2599,9 +2441,7 @@ def heightmap_add_hm( @deprecate("Multiply 2 arrays using `hm3 = hm1 * hm2`") -def heightmap_multiply_hm( - hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray -) -> None: +def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> None: """Multiplies two heightmap's together and stores the result in ``hm3``. Args: @@ -2617,9 +2457,7 @@ def heightmap_multiply_hm( @pending_deprecate() -def heightmap_add_hill( - hm: np.ndarray, x: float, y: float, radius: float, height: float -) -> None: +def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float, height: float) -> None: """Add a hill (a half spheroid) at given position. If height == radius or -radius, the hill is a half-sphere. @@ -2635,9 +2473,7 @@ def heightmap_add_hill( @pending_deprecate() -def heightmap_dig_hill( - hm: np.ndarray, x: float, y: float, radius: float, height: float -) -> None: +def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float, height: float) -> None: """ This function takes the highest value (if height > 0) or the lowest @@ -2739,9 +2575,7 @@ def heightmap_kernel_transform( cdx = ffi.new("int[]", dx) cdy = ffi.new("int[]", dy) cweight = ffi.new("float[]", weight) - lib.TCOD_heightmap_kernel_transform( - _heightmap_cdata(hm), kernelsize, cdx, cdy, cweight, minLevel, maxLevel - ) + lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, cdx, cdy, cweight, minLevel, maxLevel) @pending_deprecate() @@ -2910,8 +2744,7 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: """ if hm.flags["C_CONTIGUOUS"]: warnings.warn( - "Get a value from this heightmap with hm[i,j]\n" - "consider using order='F'", + "Get a value from this heightmap with hm[i,j]\n" "consider using order='F'", DeprecationWarning, stacklevel=2, ) @@ -2928,9 +2761,7 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_interpolated_value( - hm: np.ndarray, x: float, y: float -) -> float: +def heightmap_get_interpolated_value(hm: np.ndarray, x: float, y: float) -> float: """Return the interpolated height at non integer coordinates. Args: @@ -2941,9 +2772,7 @@ def heightmap_get_interpolated_value( Returns: float: The value at ``x``, ``y``. """ - return float( - lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), x, y) - ) + return float(lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), x, y)) @pending_deprecate() @@ -2962,9 +2791,7 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_normal( - hm: np.ndarray, x: float, y: float, waterLevel: float -) -> Tuple[float, float, float]: +def heightmap_get_normal(hm: np.ndarray, x: float, y: float, waterLevel: float) -> Tuple[float, float, float]: """Return the map normal at given coordinates. Args: @@ -3011,9 +2838,7 @@ def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: Returns: bool: True if the map edges are below ``waterlevel``, otherwise False. """ - return bool( - lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm), waterlevel) - ) + return bool(lib.TCOD_heightmap_has_land_on_border(_heightmap_cdata(hm), waterlevel)) @deprecate("Use `hm.min()` and `hm.max()` instead.") @@ -3082,9 +2907,7 @@ def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: @pending_deprecate() -def image_set_key_color( - image: tcod.image.Image, col: Tuple[int, int, int] -) -> None: +def image_set_key_color(image: tcod.image.Image, col: Tuple[int, int, int]) -> None: image.set_key_color(col) @@ -3094,9 +2917,7 @@ def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int: @pending_deprecate() -def image_is_pixel_transparent( - image: tcod.image.Image, x: int, y: int -) -> bool: +def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) @@ -3107,9 +2928,7 @@ def image_load(filename: str) -> tcod.image.Image: Args: filename (AnyStr): Path to a .bmp or .png image file. """ - return tcod.image.Image._from_cdata( - ffi.gc(lib.TCOD_image_load(_bytes(filename)), lib.TCOD_image_delete) - ) + return tcod.image.Image._from_cdata(ffi.gc(lib.TCOD_image_load(_bytes(filename)), lib.TCOD_image_delete)) @pending_deprecate() @@ -3130,9 +2949,7 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: @pending_deprecate() -def image_refresh_console( - image: tcod.image.Image, console: tcod.console.Console -) -> None: +def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console) -> None: image.refresh_console(console) @@ -3142,23 +2959,17 @@ def image_get_size(image: tcod.image.Image) -> Tuple[int, int]: @pending_deprecate() -def image_get_pixel( - image: tcod.image.Image, x: int, y: int -) -> Tuple[int, int, int]: +def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> Tuple[int, int, int]: return image.get_pixel(x, y) @pending_deprecate() -def image_get_mipmap_pixel( - image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float -) -> Tuple[int, int, int]: +def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> Tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) @pending_deprecate() -def image_put_pixel( - image: tcod.image.Image, x: int, y: int, col: Tuple[int, int, int] -) -> None: +def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: Tuple[int, int, int]) -> None: image.put_pixel(x, y, col) @@ -3259,9 +3070,7 @@ def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: @deprecate("Use tcod.line_iter instead.") -def line( - xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool] -) -> bool: +def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool]) -> bool: """Iterate over a line using a callback function. Your callback function will take x and y parameters and return True to @@ -3320,9 +3129,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: @deprecate("This function has been replaced by tcod.los.bresenham.") -def line_where( - x1: int, y1: int, x2: int, y2: int, inclusive: bool = True -) -> Tuple[np.ndarray, np.ndarray]: +def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> Tuple[np.ndarray, np.ndarray]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. @@ -3372,16 +3179,12 @@ def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: array attributes manually. """ if source.width != dest.width or source.height != dest.height: - dest.__init__( # type: ignore - source.width, source.height, source._order - ) + dest.__init__(source.width, source.height, source._order) # type: ignore dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore @deprecate("Set properties using the m.transparent and m.walkable arrays.") -def map_set_properties( - m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool -) -> None: +def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: """Set the properties of a single cell. .. note:: @@ -3394,9 +3197,7 @@ def map_set_properties( @deprecate("Clear maps using NumPy broadcast rules instead.") -def map_clear( - m: tcod.map.Map, transparent: bool = False, walkable: bool = False -) -> None: +def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: """Change all map cells to a specific value. .. deprecated:: 4.5 @@ -3510,9 +3311,7 @@ def mouse_get_status() -> Mouse: @pending_deprecate() -def namegen_parse( - filename: str, random: Optional[tcod.random.Random] = None -) -> None: +def namegen_parse(filename: str, random: Optional[tcod.random.Random] = None) -> None: lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) @@ -3523,9 +3322,7 @@ def namegen_generate(name: str) -> str: @pending_deprecate() def namegen_generate_custom(name: str, rule: str) -> str: - return _unpack_char_p( - lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False) - ) + return _unpack_char_p(lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False)) @pending_deprecate() @@ -3534,9 +3331,7 @@ def namegen_get_sets() -> List[str]: try: lst = [] while not lib.TCOD_list_is_empty(sets): - lst.append( - _unpack_char_p(ffi.cast("char *", lib.TCOD_list_pop(sets))) - ) + lst.append(_unpack_char_p(ffi.cast("char *", lib.TCOD_list_pop(sets)))) finally: lib.TCOD_list_delete(sets) return lst @@ -3579,9 +3374,7 @@ def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: @pending_deprecate() -def noise_get( - n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT -) -> float: +def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) -> float: """Return the noise value sampled from the ``f`` coordinate. ``f`` should be a tuple or list with a length matching the `dimensions` @@ -3618,9 +3411,7 @@ def noise_get_fbm( Returns: float: The sampled noise value. """ - return float( - lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ) - ) + return float(lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) @pending_deprecate() @@ -3641,11 +3432,7 @@ def noise_get_turbulence( Returns: float: The sampled noise value. """ - return float( - lib.TCOD_noise_get_turbulence_ex( - n.noise_c, ffi.new("float[4]", f), oc, typ - ) - ) + return float(lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) @deprecate("libtcod objects are deleted automatically.") @@ -3667,10 +3454,7 @@ def _unpack_union(type_: int, union: Any) -> Any: return union.i elif type_ == lib.TCOD_TYPE_FLOAT: return union.f - elif ( - type_ == lib.TCOD_TYPE_STRING - or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00 - ): + elif type_ == lib.TCOD_TYPE_STRING or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00: return _unpack_char_p(union.s) elif type_ == lib.TCOD_TYPE_COLOR: return Color._new_from_cdata(union.col) @@ -3683,10 +3467,7 @@ def _unpack_union(type_: int, union: Any) -> Any: def _convert_TCODList(clist: Any, type_: int) -> Any: - return [ - _unpack_union(type_, lib.TDL_list_get_union(clist, i)) - for i in range(lib.TCOD_list_size(clist)) - ] + return [_unpack_union(type_, lib.TDL_list_get_union(clist, i)) for i in range(lib.TCOD_list_size(clist))] @deprecate("Parser functions have been deprecated.") @@ -3717,9 +3498,7 @@ def _pycall_parser_new_flag(name: str) -> Any: @ffi.def_extern() # type: ignore def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: - return _parser_listener.new_property( - _unpack_char_p(propname), type, _unpack_union(type, value) - ) + return _parser_listener.new_property(_unpack_char_p(propname), type, _unpack_union(type, value)) @ffi.def_extern() # type: ignore @@ -3789,16 +3568,12 @@ def parser_get_float_property(parser: Any, name: str) -> float: @deprecate("Parser functions have been deprecated.") def parser_get_string_property(parser: Any, name: str) -> str: - return _unpack_char_p( - lib.TCOD_parser_get_string_property(parser, _bytes(name)) - ) + return _unpack_char_p(lib.TCOD_parser_get_string_property(parser, _bytes(name))) @deprecate("Parser functions have been deprecated.") def parser_get_color_property(parser: Any, name: str) -> Color: - return Color._new_from_cdata( - lib.TCOD_parser_get_color_property(parser, _bytes(name)) - ) + return Color._new_from_cdata(lib.TCOD_parser_get_color_property(parser, _bytes(name))) @deprecate("Parser functions have been deprecated.") @@ -3831,9 +3606,7 @@ def random_get_instance() -> tcod.random.Random: Returns: Random: A Random instance using the default random number generator. """ - return tcod.random.Random._new_from_cdata( - ffi.cast("mersenne_data_t*", lib.TCOD_random_get_instance()) - ) + return tcod.random.Random._new_from_cdata(ffi.cast("mersenne_data_t*", lib.TCOD_random_get_instance())) @pending_deprecate() @@ -3850,9 +3623,7 @@ def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: @pending_deprecate() -def random_new_from_seed( - seed: Hashable, algo: int = RNG_CMWC -) -> tcod.random.Random: +def random_new_from_seed(seed: Hashable, algo: int = RNG_CMWC) -> tcod.random.Random: """Return a new Random instance. Using the given ``seed`` and ``algo``. Args: @@ -3867,9 +3638,7 @@ def random_new_from_seed( @pending_deprecate() -def random_set_distribution( - rnd: Optional[tcod.random.Random], dist: int -) -> None: +def random_set_distribution(rnd: Optional[tcod.random.Random], dist: int) -> None: """Change the distribution mode of a random number generator. Args: @@ -3893,15 +3662,11 @@ def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: Returns: int: A random integer in the range ``mi`` <= n <= ``ma``. """ - return int( - lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma) - ) + return int(lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma)) @pending_deprecate() -def random_get_float( - rnd: Optional[tcod.random.Random], mi: float, ma: float -) -> float: +def random_get_float(rnd: Optional[tcod.random.Random], mi: float, ma: float) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. The result is affected by calls to :any:`random_set_distribution`. @@ -3915,30 +3680,22 @@ def random_get_float( float: A random double precision float in the range ``mi`` <= n <= ``ma``. """ - return float( - lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma) - ) + return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma)) @deprecate("Call tcod.random_get_float instead.") -def random_get_double( - rnd: Optional[tcod.random.Random], mi: float, ma: float -) -> float: +def random_get_double(rnd: Optional[tcod.random.Random], mi: float, ma: float) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 Use :any:`random_get_float` instead. Both funtions return a double precision float. """ - return float( - lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma) - ) + return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma)) @pending_deprecate() -def random_get_int_mean( - rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int -) -> int: +def random_get_int_mean(rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int) -> int: """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3952,17 +3709,11 @@ def random_get_int_mean( Returns: int: A random weighted integer in the range ``mi`` <= n <= ``ma``. """ - return int( - lib.TCOD_random_get_int_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean - ) - ) + return int(lib.TCOD_random_get_int_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean)) @pending_deprecate() -def random_get_float_mean( - rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float -) -> float: +def random_get_float_mean(rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. The result is affacted by calls to :any:`random_set_distribution`. @@ -3977,28 +3728,18 @@ def random_get_float_mean( float: A random weighted double precision float in the range ``mi`` <= n <= ``ma``. """ - return float( - lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean - ) - ) + return float(lib.TCOD_random_get_double_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean)) @deprecate("Call tcod.random_get_float_mean instead.") -def random_get_double_mean( - rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float -) -> float: +def random_get_double_mean(rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 Use :any:`random_get_float_mean` instead. Both funtions return a double precision float. """ - return float( - lib.TCOD_random_get_double_mean( - rnd.random_c if rnd else ffi.NULL, mi, ma, mean - ) - ) + return float(lib.TCOD_random_get_double_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean)) @deprecate("Use the standard library 'copy' module instead.") @@ -4021,9 +3762,7 @@ def random_save(rnd: Optional[tcod.random.Random]) -> tcod.random.Random: @deprecate("This function is deprecated.") -def random_restore( - rnd: Optional[tcod.random.Random], backup: tcod.random.Random -) -> None: +def random_restore(rnd: Optional[tcod.random.Random], backup: tcod.random.Random) -> None: """Restore a random number generator from a backed up copy. Args: @@ -4057,9 +3796,7 @@ def struct_add_property(struct, name, typ, mandatory): # type: ignore @deprecate("This function is deprecated.") def struct_add_value_list(struct, name, value_list, mandatory): # type: ignore - c_strings = [ - ffi.new("char[]", value.encode("utf-8")) for value in value_list - ] + c_strings = [ffi.new("char[]", value.encode("utf-8")) for value in value_list] c_value_list = ffi.new("char*[]", c_strings) lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory) @@ -4218,9 +3955,7 @@ def sys_save_screenshot(name: Optional[str] = None) -> None: This function is not supported by contexts. Use :any:`Context.save_screenshot` instead. """ - lib.TCOD_sys_save_screenshot( - _bytes(name) if name is not None else ffi.NULL - ) + lib.TCOD_sys_save_screenshot(_bytes(name) if name is not None else ffi.NULL) # custom fullscreen resolution @@ -4245,9 +3980,7 @@ def sys_force_fullscreen_resolution(width: int, height: int) -> None: lib.TCOD_sys_force_fullscreen_resolution(width, height) -@deprecate( - "This function is deprecated, which monitor is detected is ambiguous." -) +@deprecate("This function is deprecated, which monitor is detected is ambiguous.") def sys_get_current_resolution() -> Tuple[int, int]: """Return a monitors pixel resolution as (width, height). @@ -4337,9 +4070,7 @@ def _pycall_sdl_hook(sdl_surface: Any) -> None: @deprecate("Use tcod.event.get to check for events.") -def sys_check_for_event( - mask: int, k: Optional[Key], m: Optional[Mouse] -) -> int: +def sys_check_for_event(mask: int, k: Optional[Key], m: Optional[Mouse]) -> int: """Check for and return an event. Args: @@ -4352,17 +4083,11 @@ def sys_check_for_event( .. deprecated:: 9.3 Use the :any:`tcod.event.get` function to check for events. """ - return int( - lib.TCOD_sys_check_for_event( - mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL - ) - ) + return int(lib.TCOD_sys_check_for_event(mask, k.key_p if k else ffi.NULL, m.mouse_p if m else ffi.NULL)) @deprecate("Use tcod.event.wait to wait for events.") -def sys_wait_for_event( - mask: int, k: Optional[Key], m: Optional[Mouse], flush: bool -) -> int: +def sys_wait_for_event(mask: int, k: Optional[Key], m: Optional[Mouse], flush: bool) -> int: """Wait for an event then return. If flush is True then the buffer will be cleared before waiting. Otherwise diff --git a/tcod/loader.py b/tcod/loader.py index 74b8e2c1..b122cbe9 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -34,9 +34,7 @@ def verify_dependencies() -> None: lib_test.SDL_GetVersion(version) # Need to check this version. version = version.major, version.minor, version.patch if version < (2, 0, 5): - raise RuntimeError( - "Tried to load an old version of SDL %r" % (version,) - ) + raise RuntimeError("Tried to load an old version of SDL %r" % (version,)) def get_architecture() -> str: diff --git a/tcod/map.py b/tcod/map.py index fc90bfda..918f57b2 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -138,22 +138,17 @@ def compute_fov( if not (0 <= x < self.width and 0 <= y < self.height): warnings.warn( "Index (%r, %r) is outside of this maps shape (%r, %r)." - "\nThis will raise an error in future versions." - % (x, y, self.width, self.height), + "\nThis will raise an error in future versions." % (x, y, self.width, self.height), RuntimeWarning, stacklevel=2, ) - lib.TCOD_map_compute_fov( - self.map_c, x, y, radius, light_walls, algorithm - ) + lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm) def __setstate__(self, state: Any) -> None: if "_Map__buffer" not in state: # deprecated # remove this check on major version update - self.__buffer = np.zeros( - (state["height"], state["width"], 3), dtype=np.bool_ - ) + self.__buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_) self.__buffer[:, :, 0] = state["buffer"] & 0x01 self.__buffer[:, :, 1] = state["buffer"] & 0x02 self.__buffer[:, :, 2] = state["buffer"] & 0x04 @@ -240,23 +235,16 @@ def compute_fov( """ transparency = np.asarray(transparency) if len(transparency.shape) != 2: - raise TypeError( - "transparency must be an array of 2 dimensions" - " (shape is %r)" % transparency.shape - ) + raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape) if isinstance(pov, int): raise TypeError( "The tcod.map.compute_fov function has changed. The `x` and `y`" " parameters should now be given as a single tuple." ) - if not ( - 0 <= pov[0] < transparency.shape[0] - and 0 <= pov[1] < transparency.shape[1] - ): + if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]): warnings.warn( "Given pov index %r is outside the array of shape %r." - "\nThis will raise an error in future versions." - % (pov, transparency.shape), + "\nThis will raise an error in future versions." % (pov, transparency.shape), RuntimeWarning, stacklevel=2, ) @@ -274,7 +262,5 @@ def compute_fov( ), ) map_buffer["transparent"] = transparency - lib.TCOD_map_compute_fov( - map_cdata, pov[1], pov[0], radius, light_walls, algorithm - ) + lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm) return map_buffer["fov"] # type: ignore diff --git a/tcod/noise.c b/tcod/noise.c index 38424406..17a90a74 100644 --- a/tcod/noise.c +++ b/tcod/noise.c @@ -15,19 +15,13 @@ float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw) { return TCOD_noise_get_turbulence(noise->noise, xyzw, noise->octaves); } } -void NoiseSampleMeshGrid( - TDLNoise* noise, - const long len, - const float* __restrict in, - float* __restrict out) { +void NoiseSampleMeshGrid(TDLNoise* noise, const long len, const float* __restrict in, float* __restrict out) { { long i; for (i = 0; i < len; ++i) { int axis; float xyzw[TCOD_NOISE_MAX_DIMENSIONS]; - for (axis = 0; axis < noise->dimensions; ++axis) { - xyzw[axis] = in[axis * len + i]; - } + for (axis = 0; axis < noise->dimensions; ++axis) { xyzw[axis] = in[axis * len + i]; } out[i] = NoiseGetSample(noise, xyzw); } } @@ -65,8 +59,6 @@ void NoiseSampleOpenMeshGrid( { long i; long len = GetSizeFromShape(ndim_in, shape); - for (i = 0; i < len; ++i) { - out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i); - } + for (i = 0; i < len; ++i) { out[i] = GetOpenMeshGridValue(noise, ndim_in, shape, ogrid_in, i); } } } diff --git a/tcod/noise.h b/tcod/noise.h index a63a7a10..393c18e2 100644 --- a/tcod/noise.h +++ b/tcod/noise.h @@ -22,11 +22,7 @@ typedef struct TDLNoise { float NoiseGetSample(TDLNoise* noise, float* __restrict xyzw); /* Fill `out` with samples derived from the mesh-grid `in`. */ -void NoiseSampleMeshGrid( - TDLNoise* noise, - const long len, - const float* __restrict in, - float* __restrict out); +void NoiseSampleMeshGrid(TDLNoise* noise, const long len, const float* __restrict in, float* __restrict out); /* Fill `out` with samples derived from the open mesh-grid `in`. */ void NoiseSampleOpenMeshGrid( diff --git a/tcod/noise.py b/tcod/noise.py index bf83e421..fff29e48 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -96,8 +96,7 @@ def __repr__(self) -> str: def __getattr__(name: str) -> Implementation: if name in Implementation.__members__: warnings.warn( - f"'tcod.noise.{name}' is deprecated," - f" use 'tcod.noise.Implementation.{name}' instead.", + f"'tcod.noise.{name}' is deprecated," f" use 'tcod.noise.Implementation.{name}' instead.", DeprecationWarning, stacklevel=2, ) @@ -142,10 +141,7 @@ def __init__( seed: Optional[Union[int, tcod.random.Random]] = None, ): if not 0 < dimensions <= 4: - raise ValueError( - "dimensions must be in range 0 < n <= 4, got %r" - % (dimensions,) - ) + raise ValueError("dimensions must be in range 0 < n <= 4, got %r" % (dimensions,)) self._seed = seed self._random = self.__rng_from_seed(seed) _random_c = self._random.random_c @@ -156,20 +152,14 @@ def __init__( ), lib.TCOD_noise_delete, ) - self._tdl_noise_c = ffi.new( - "TDLNoise*", (self.noise_c, dimensions, 0, octaves) - ) + self._tdl_noise_c = ffi.new("TDLNoise*", (self.noise_c, dimensions, 0, octaves)) self.algorithm = algorithm self.implementation = implementation # sanity check @staticmethod - def __rng_from_seed( - seed: Union[None, int, tcod.random.Random] - ) -> tcod.random.Random: + def __rng_from_seed(seed: Union[None, int, tcod.random.Random]) -> tcod.random.Random: if seed is None or isinstance(seed, int): - return tcod.random.Random( - seed=seed, algorithm=tcod.random.MERSENNE_TWISTER - ) + return tcod.random.Random(seed=seed, algorithm=tcod.random.MERSENNE_TWISTER) return seed def __repr__(self) -> str: @@ -232,9 +222,7 @@ def octaves(self) -> float: def octaves(self, value: float) -> None: self._tdl_noise_c.octaves = value - def get_point( - self, x: float = 0, y: float = 0, z: float = 0, w: float = 0 - ) -> float: + def get_point(self, x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> float: """Return the noise value at the (x, y, z, w) point. Args: @@ -257,8 +245,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: indexes = (indexes,) if len(indexes) > self.dimensions: raise IndexError( - "This noise generator has %i dimensions, but was indexed with %i." - % (self.dimensions, len(indexes)) + "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) ) indexes = np.broadcast_arrays(*indexes) c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] @@ -319,15 +306,11 @@ def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: mgrid = np.ascontiguousarray(mgrid, np.float32) if mgrid.shape[0] != self.dimensions: raise ValueError( - "mgrid.shape[0] must equal self.dimensions, " - "%r[0] != %r" % (mgrid.shape, self.dimensions) + "mgrid.shape[0] must equal self.dimensions, " "%r[0] != %r" % (mgrid.shape, self.dimensions) ) out = np.ndarray(mgrid.shape[1:], np.float32) if mgrid.shape[1:] != out.shape: - raise ValueError( - "mgrid.shape[1:] must equal out.shape, " - "%r[1:] != %r" % (mgrid.shape, out.shape) - ) + raise ValueError("mgrid.shape[1:] must equal out.shape, " "%r[1:] != %r" % (mgrid.shape, out.shape)) lib.NoiseSampleMeshGrid( self._tdl_noise_c, out.size, @@ -350,10 +333,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> np.ndarray: The ``dtype`` is `numpy.float32`. """ if len(ogrid) != self.dimensions: - raise ValueError( - "len(ogrid) must equal self.dimensions, " - "%r != %r" % (len(ogrid), self.dimensions) - ) + raise ValueError("len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions)) ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid] out = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( @@ -376,9 +356,7 @@ def __getstate__(self) -> Any: waveletTileData = None if self.noise_c.waveletTileData != ffi.NULL: - waveletTileData = list( - self.noise_c.waveletTileData[0 : 32 * 32 * 32] - ) + waveletTileData = list(self.noise_c.waveletTileData[0 : 32 * 32 * 32]) state["_waveletTileData"] = waveletTileData state["noise_c"] = { @@ -403,9 +381,7 @@ def __setstate__(self, state: Any) -> None: return self._setstate_old(state) # unpack wavelet tile data if it exists if "_waveletTileData" in state: - state["_waveletTileData"] = ffi.new( - "float[]", state["_waveletTileData"] - ) + state["_waveletTileData"] = ffi.new("float[]", state["_waveletTileData"]) state["noise_c"]["waveletTileData"] = state["_waveletTileData"] else: state["noise_c"]["waveletTileData"] = ffi.NULL @@ -433,9 +409,7 @@ def _setstate_old(self, state: Any) -> None: self.__waveletTileData = ffi.new("float[]", 32 * 32 * 32) ffi.buffer(self.__waveletTileData)[:] = state[9] self.noise_c.noise_type = state[10] - self._tdl_noise_c = ffi.new( - "TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2]) - ) + self._tdl_noise_c = ffi.new("TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2])) def grid( @@ -488,10 +462,5 @@ def grid( raise TypeError("shape must have the same length as scale") if len(shape) != len(origin): raise TypeError("shape must have the same length as origin") - indexes = ( - np.arange(i_shape) * i_scale + i_origin - for i_shape, i_scale, i_origin in zip(shape, scale, origin) - ) - return tuple( - np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing) - ) + indexes = (np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin)) + return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) diff --git a/tcod/path.c b/tcod/path.c index 13fd8c6a..600b78ee 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -9,36 +9,28 @@ #include "../libtcod/src/libtcod/pathfinder_frontier.h" #include "../libtcod/src/libtcod/utility.h" -static void* pick_array_pointer( - const struct PathCostArray* map, int i, int j) { +static void* pick_array_pointer(const struct PathCostArray* map, int i, int j) { return (void*)(map->array + map->strides[0] * i + map->strides[1] * j); } -float PathCostArrayFloat32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(float*)pick_array_pointer(map, x2, y2); } -float PathCostArrayInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(int8_t*)pick_array_pointer(map, x2, y2); } -float PathCostArrayInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(int16_t*)pick_array_pointer(map, x2, y2); } -float PathCostArrayInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return (float)(*(int32_t*)pick_array_pointer(map, x2, y2)); } -float PathCostArrayUInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(uint8_t*)pick_array_pointer(map, x2, y2); } -float PathCostArrayUInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return *(uint16_t*)pick_array_pointer(map, x2, y2); } -float PathCostArrayUInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map) { +float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map) { return (float)(*(uint32_t*)pick_array_pointer(map, x2, y2)); } @@ -56,8 +48,7 @@ static void* get_array_ptr(const struct NArray* arr, int n, const int* index) { for (int i = 0; i < n; ++i) { ptr += arr->strides[i] * index[i]; } return (void*)ptr; } -static int64_t get_array_int64( - const struct NArray* arr, int n, const int* index) { +static int64_t get_array_int64(const struct NArray* arr, int n, const int* index) { const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -83,11 +74,7 @@ static int64_t get_array_int64( static int get_array_int(const struct NArray* arr, int n, const int* index) { return (int)get_array_int64(arr, n, index); } -static void set_array_int64( - struct NArray* __restrict arr, - int n, - const int* __restrict index, - int64_t value) { +static void set_array_int64(struct NArray* __restrict arr, int n, const int* __restrict index, int64_t value) { void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -118,21 +105,17 @@ static void set_array_int64( return; } } -static void set_array2d_int64( - struct NArray* __restrict arr, int i, int j, int64_t value) { +static void set_array2d_int64(struct NArray* __restrict arr, int i, int j, int64_t value) { int index[2] = {i, j}; set_array_int64(arr, 2, index, value); } -static void set_array_int( - struct NArray* __restrict arr, int n, const int* index, int value) { +static void set_array_int(struct NArray* __restrict arr, int n, const int* index, int value) { set_array_int64(arr, n, index, value); } -static void set_array2d_int( - struct NArray* __restrict arr, int i, int j, int value) { +static void set_array2d_int(struct NArray* __restrict arr, int i, int j, int value) { set_array2d_int64(arr, i, j, value); } -static int64_t get_array_is_max( - const struct NArray* arr, int n, const int* index) { +static int64_t get_array_is_max(const struct NArray* arr, int n, const int* index) { const void* ptr = get_array_ptr(arr, n, index); switch (arr->type) { case np_int8: @@ -166,8 +149,7 @@ static void dijkstra2d_add_edge( int edge_cost, const int* __restrict dir // dir[2] ) { - const int index[2] = { - frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1]}; + const int index[2] = {frontier->active_index[0] + dir[0], frontier->active_index[1] + dir[1]}; if (!array_in_range(dist_array, 2, index)) { return; } edge_cost *= get_array_int(cost, 2, index); if (edge_cost <= 0) { return; } @@ -197,18 +179,14 @@ int dijkstra2d( int distance_here = get_array_int(dist_array, 2, frontier->active_index); if (frontier->active_dist != distance_here) { continue; } for (int i = 0; i < edges_2d_n; ++i) { - dijkstra2d_add_edge( - frontier, dist_array, cost, edges_2d[i * 3 + 2], &edges_2d[i * 3]); + dijkstra2d_add_edge(frontier, dist_array, cost, edges_2d[i * 3 + 2], &edges_2d[i * 3]); } } return TCOD_E_OK; } int dijkstra2d_basic( - struct NArray* __restrict dist_array, - const struct NArray* __restrict cost, - int cardinal, - int diagonal) { + struct NArray* __restrict dist_array, const struct NArray* __restrict cost, int cardinal, int diagonal) { struct TCOD_Frontier* frontier = TCOD_frontier_new(2); if (!frontier) { return TCOD_E_ERROR; } for (int i = 0; i < dist_array->shape[0]; ++i) { @@ -274,8 +252,7 @@ int hillclimb2d( } const int origin[2] = {next[0], next[1]}; for (int i = 0; i < edges_2d_n; ++i) { - hillclimb2d_check_edge( - dist_array, &new_dist, origin, &edges_2d[i * 2], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, &edges_2d[i * 2], next); } if (old_dist == new_dist) { return length; } old_dist = new_dist; @@ -301,33 +278,22 @@ int hillclimb2d_basic( } const int origin[2] = {next[0], next[1]}; if (cardinal) { - hillclimb2d_check_edge( - dist_array, &new_dist, origin, CARDINAL_[0], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, CARDINAL_[1], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, CARDINAL_[2], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, CARDINAL_[3], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[0], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[1], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[2], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, CARDINAL_[3], next); } if (diagonal) { - hillclimb2d_check_edge( - dist_array, &new_dist, origin, DIAGONAL_[0], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, DIAGONAL_[1], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, DIAGONAL_[2], next); - hillclimb2d_check_edge( - dist_array, &new_dist, origin, DIAGONAL_[3], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[0], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[1], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[2], next); + hillclimb2d_check_edge(dist_array, &new_dist, origin, DIAGONAL_[3], next); } if (old_dist == new_dist) { return length; } old_dist = new_dist; } } -int compute_heuristic( - const struct PathfinderHeuristic* __restrict heuristic, - int ndim, - const int* __restrict index) { +int compute_heuristic(const struct PathfinderHeuristic* __restrict heuristic, int ndim, const int* __restrict index) { if (!heuristic) { return 0; } int x = 0; int y = 0; @@ -351,9 +317,7 @@ int compute_heuristic( } int diagonal = heuristic->diagonal != 0 ? MIN(x, y) : 0; int straight = MAX(x, y) - diagonal; - return ( - straight * heuristic->cardinal + diagonal * heuristic->diagonal + - w * heuristic->w + z * heuristic->z); + return (straight * heuristic->cardinal + diagonal * heuristic->diagonal + w * heuristic->w + z * heuristic->z); } void path_compute_add_edge( struct TCOD_Frontier* __restrict frontier, @@ -363,9 +327,7 @@ void path_compute_add_edge( const int* __restrict edge_rule, const struct PathfinderHeuristic* __restrict heuristic) { int dest[TCOD_PATHFINDER_MAX_DIMENSIONS]; - for (int i = 0; i < frontier->ndim; ++i) { - dest[i] = frontier->active_index[i] + edge_rule[i]; - } + for (int i = 0; i < frontier->ndim; ++i) { dest[i] = frontier->active_index[i] + edge_rule[i]; } if (!array_in_range(dist_map, frontier->ndim, dest)) { return; } int edge_cost = edge_rule[frontier->ndim]; edge_cost *= get_array_int(cost_map, frontier->ndim, dest); @@ -374,9 +336,7 @@ void path_compute_add_edge( if (get_array_int(dist_map, frontier->ndim, dest) <= distance) { return; } set_array_int(dist_map, frontier->ndim, dest, distance); int* path = get_array_ptr(travel_map, frontier->ndim, dest); - for (int i = 0; i < frontier->ndim; ++i) { - path[i] = frontier->active_index[i]; - } + for (int i = 0; i < frontier->ndim; ++i) { path[i] = frontier->active_index[i]; } int priority = distance + compute_heuristic(heuristic, frontier->ndim, dest); TCOD_frontier_push(frontier, dest, distance, priority); } @@ -384,8 +344,7 @@ void path_compute_add_edge( Returns true if the heuristic target has been reached by the active_node. */ static bool path_compute_at_goal( - const struct TCOD_Frontier* __restrict frontier, - const struct PathfinderHeuristic* __restrict heuristic) { + const struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic) { if (!heuristic) { return 0; } for (int i = 0; i < frontier->ndim; ++i) { if (frontier->active_index[i] != heuristic->target[i]) { return 0; } @@ -404,19 +363,12 @@ int path_compute_step( return TCOD_set_errorv("Invalid frontier->ndim."); } if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); } - if (frontier->ndim != dist_map->ndim) { - return TCOD_set_errorv("Invalid or corrupt input."); - } - if (travel_map && frontier->ndim + 1 != travel_map->ndim) { - return TCOD_set_errorv("Invalid or corrupt input."); - } + if (frontier->ndim != dist_map->ndim) { return TCOD_set_errorv("Invalid or corrupt input."); } + if (travel_map && frontier->ndim + 1 != travel_map->ndim) { return TCOD_set_errorv("Invalid or corrupt input."); } TCOD_frontier_pop(frontier); for (int i = 0; i < n; ++i) { if (rules[i].condition.type) { - if (!get_array_int( - &rules[i].condition, frontier->ndim, frontier->active_index)) { - continue; - } + if (!get_array_int(&rules[i].condition, frontier->ndim, frontier->active_index)) { continue; } } for (int edge_i = 0; edge_i < rules[i].edge_count; ++edge_i) { path_compute_add_edge( @@ -442,25 +394,17 @@ int path_compute( const struct PathfinderHeuristic* __restrict heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } while (TCOD_frontier_size(frontier)) { - int err = - path_compute_step(frontier, dist_map, travel_map, n, rules, heuristic); + int err = path_compute_step(frontier, dist_map, travel_map, n, rules, heuristic); if (err != 0) { return err; } } return 0; } ptrdiff_t get_travel_path( - int8_t ndim, - const struct NArray* __restrict travel_map, - const int* __restrict start, - int* __restrict out) { - if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { - return TCOD_set_errorv("Invalid ndim."); - } + int8_t ndim, const struct NArray* __restrict travel_map, const int* __restrict start, int* __restrict out) { + if (ndim <= 0 || ndim > TCOD_PATHFINDER_MAX_DIMENSIONS) { return TCOD_set_errorv("Invalid ndim."); } if (!travel_map) { return TCOD_set_errorv("Missing travel_map."); } if (!start) { return TCOD_set_errorv("Missing start."); } - if (ndim != travel_map->ndim - 1) { - return TCOD_set_errorv("Invalid or corrupt input."); - } + if (ndim != travel_map->ndim - 1) { return TCOD_set_errorv("Invalid or corrupt input."); } const int* next = get_array_ptr(travel_map, ndim, start); const int* current = start; ptrdiff_t max_loops = 1; @@ -478,51 +422,33 @@ ptrdiff_t get_travel_path( case 1: return TCOD_set_errorvf("Index (%i) is out of range.", next[0]); case 2: - return TCOD_set_errorvf( - "Index (%i, %i) is out of range.", next[0], next[1]); + return TCOD_set_errorvf("Index (%i, %i) is out of range.", next[0], next[1]); case 3: - return TCOD_set_errorvf( - "Index (%i, %i, %i) is out of range.", - next[0], - next[1], - next[2]); + return TCOD_set_errorvf("Index (%i, %i, %i) is out of range.", next[0], next[1], next[2]); case 4: - return TCOD_set_errorvf( - "Index (%i, %i, %i, %i) is out of range.", - next[0], - next[1], - next[2], - next[3]); + return TCOD_set_errorvf("Index (%i, %i, %i, %i) is out of range.", next[0], next[1], next[2], next[3]); } } next = get_array_ptr(travel_map, ndim, next); - if (!out && length == max_loops) { - return TCOD_set_errorv("Possible cyclic loop detected."); - } + if (!out && length == max_loops) { return TCOD_set_errorv("Possible cyclic loop detected."); } } return length; } int update_frontier_heuristic( - struct TCOD_Frontier* __restrict frontier, - const struct PathfinderHeuristic* __restrict heuristic) { + struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } for (int i = 0; i < frontier->heap.size; ++i) { unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; struct TCOD_HeapNode* heap_node = (void*)heap_ptr; struct FrontierNode* fnode = (struct FrontierNode*)heap_node->data; - heap_node->priority = - (fnode->distance + - compute_heuristic(heuristic, frontier->ndim, fnode->index)); + heap_node->priority = (fnode->distance + compute_heuristic(heuristic, frontier->ndim, fnode->index)); } TCOD_minheap_heapify(&frontier->heap); return 0; } static int update_frontier_from_distance_iterator( - struct TCOD_Frontier* __restrict frontier, - const struct NArray* __restrict dist_map, - int dimension, - int* index) { + struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map, int dimension, int* index) { if (dimension == frontier->ndim) { if (get_array_is_max(dist_map, dimension, index)) { return 0; } int dist = get_array_int(dist_map, dimension, index); @@ -530,15 +456,13 @@ static int update_frontier_from_distance_iterator( } for (int i = 0; i < dist_map->shape[dimension];) { index[dimension] = i; - int err = update_frontier_from_distance_iterator( - frontier, dist_map, dimension + 1, index); + int err = update_frontier_from_distance_iterator(frontier, dist_map, dimension + 1, index); if (err) { return err; } } return 0; } int rebuild_frontier_from_distance( - struct TCOD_Frontier* __restrict frontier, - const struct NArray* __restrict dist_map) { + struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map) { if (!frontier) { return TCOD_set_errorv("Missing frontier."); } if (!dist_map) { return TCOD_set_errorv("Missing dist_map."); } TCOD_frontier_clear(frontier); diff --git a/tcod/path.h b/tcod/path.h index 17c281aa..dc87cae3 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -67,26 +67,19 @@ struct PathCostArray { long long strides[2]; }; -float PathCostArrayFloat32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayUInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayUInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayUInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayInt8( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayInt16( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray* map); -float PathCostArrayInt32( - int x1, int y1, int x2, int y2, const struct PathCostArray* map); +float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray* map); /** Return the value to add to the distance to sort nodes by A*. @@ -95,21 +88,14 @@ float PathCostArrayInt32( `index[ndim]` must not be NULL. */ -int compute_heuristic( - const struct PathfinderHeuristic* __restrict heuristic, - int ndim, - const int* __restrict index); +int compute_heuristic(const struct PathfinderHeuristic* __restrict heuristic, int ndim, const int* __restrict index); int dijkstra2d( struct NArray* __restrict dist, const struct NArray* __restrict cost, int edges_2d_n, const int* __restrict edges_2d); -int dijkstra2d_basic( - struct NArray* __restrict dist, - const struct NArray* __restrict cost, - int cardinal, - int diagonal); +int dijkstra2d_basic(struct NArray* __restrict dist, const struct NArray* __restrict cost, int cardinal, int diagonal); int hillclimb2d( const struct NArray* __restrict dist_array, @@ -120,12 +106,7 @@ int hillclimb2d( int* __restrict out); int hillclimb2d_basic( - const struct NArray* __restrict dist, - int x, - int y, - bool cardinal, - bool diagonal, - int* __restrict out); + const struct NArray* __restrict dist, int x, int y, bool cardinal, bool diagonal, int* __restrict out); int path_compute_step( struct TCOD_Frontier* __restrict frontier, @@ -150,30 +131,22 @@ int path_compute( parameters. */ ptrdiff_t get_travel_path( - int8_t ndim, - const struct NArray* __restrict travel_map, - const int* __restrict start, - int* __restrict out); + int8_t ndim, const struct NArray* __restrict travel_map, const int* __restrict start, int* __restrict out); /** Update the priority of nodes on the frontier and sort them. */ int update_frontier_heuristic( - struct TCOD_Frontier* __restrict frontier, - const struct PathfinderHeuristic* __restrict heuristic); + struct TCOD_Frontier* __restrict frontier, const struct PathfinderHeuristic* __restrict heuristic); /** Update a frontier from a distance array. Assumes no heuristic is active. */ -int rebuild_frontier_from_distance( - struct TCOD_Frontier* __restrict frontier, - const struct NArray* __restrict dist_map); +int rebuild_frontier_from_distance(struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map); /** Return true if `index[frontier->ndim]` is a node in `frontier`. */ -int frontier_has_index( - const struct TCOD_Frontier* __restrict frontier, - const int* __restrict index); +int frontier_has_index(const struct TCOD_Frontier* __restrict frontier, const int* __restrict index); #ifdef __cplusplus } #endif diff --git a/tcod/path.py b/tcod/path.py index b689ff36..437420c2 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -41,25 +41,19 @@ def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: @ffi.def_extern() # type: ignore -def _pycall_path_simple( - x1: int, y1: int, x2: int, y2: int, handle: Any -) -> float: +def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: """Does less and should run faster, just calls the handle function.""" return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore @ffi.def_extern() # type: ignore -def _pycall_path_swap_src_dest( - x1: int, y1: int, x2: int, y2: int, handle: Any -) -> float: +def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: """A TDL function dest comes first to match up with a dest only call.""" return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore @ffi.def_extern() # type: ignore -def _pycall_path_dest_only( - x1: int, y1: int, x2: int, y2: int, handle: Any -) -> float: +def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: """A TDL function which samples the dest coordinate only.""" return ffi.from_handle(handle)(x2, y2) # type: ignore @@ -70,9 +64,7 @@ def _get_pathcost_func( """Return a properly cast PathCostArray callback.""" if not ffi: return lambda x1, y1, x2, y2, _: 0 - return ffi.cast( # type: ignore - "TCOD_path_func_t", ffi.addressof(lib, name) - ) + return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) # type: ignore class _EdgeCostFunc(object): @@ -156,14 +148,9 @@ def __repr__(self) -> str: def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: if len(self.shape) != 2: - raise ValueError( - "Array must have a 2d shape, shape is %r" % (self.shape,) - ) + raise ValueError("Array must have a 2d shape, shape is %r" % (self.shape,)) if self.dtype.type not in self._C_ARRAY_CALLBACKS: - raise ValueError( - "dtype must be one of %r, dtype is %r" - % (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type) - ) + raise ValueError("dtype must be one of %r, dtype is %r" % (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type)) array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type] userdata = ffi.new( @@ -192,8 +179,7 @@ def __init__(self, cost: Any, diagonal: float = 1.41): if not hasattr(self.cost, "get_tcod_path_ffi"): assert not callable(self.cost), ( - "Any callback alone is missing shape information. " - "Wrap your callback in tcod.path.EdgeCostCallback" + "Any callback alone is missing shape information. " "Wrap your callback in tcod.path.EdgeCostCallback" ) self.cost = NodeCostArray(self.cost) @@ -245,9 +231,7 @@ class AStar(_PathFinder): A value of 0 will disable diagonal movement entirely. """ - def get_path( - self, start_x: int, start_y: int, goal_x: int, goal_y: int - ) -> List[Tuple[int, int]]: + def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> List[Tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible. Args: @@ -335,10 +319,7 @@ def maxarray( def _export_dict(array: np.ndarray) -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" if array.dtype.type not in _INT_TYPES: - raise TypeError( - "dtype was %s, but must be one of %s." - % (array.dtype.type, tuple(_INT_TYPES.keys())) - ) + raise TypeError("dtype was %s, but must be one of %s." % (array.dtype.type, tuple(_INT_TYPES.keys()))) return { "type": _INT_TYPES[array.dtype.type], "ndim": array.ndim, @@ -357,9 +338,7 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: """Return an edge_cost array using an integer map.""" edge_map = np.copy(edge_map) if edge_map.ndim != 2: - raise ValueError( - "edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim - ) + raise ValueError("edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 edge_map[edge_center] = 0 edge_map[edge_map < 0] = 0 @@ -367,9 +346,7 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: edge_array = np.transpose(edge_nz) edge_array -= edge_center c_edges = ffi.new("int[]", len(edge_array) * 3) - edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape( - len(edge_array), 3 - ) + edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3) edges[:, :2] = edge_array edges[:, 2] = edge_map[edge_nz] return c_edges, len(edge_array) @@ -516,23 +493,14 @@ def dijkstra2d( out[...] = dist if dist.shape != out.shape: - raise TypeError( - "distance and output must have the same shape %r != %r" - % (dist.shape, out.shape) - ) + raise TypeError("distance and output must have the same shape %r != %r" % (dist.shape, out.shape)) cost = np.asarray(cost) if dist.shape != cost.shape: - raise TypeError( - "output and cost must have the same shape %r != %r" - % (out.shape, cost.shape) - ) + raise TypeError("output and cost must have the same shape %r != %r" % (out.shape, cost.shape)) c_dist = _export(out) if edge_map is not None: if cardinal is not None or diagonal is not None: - raise TypeError( - "`edge_map` can not be set at the same time as" - " `cardinal` or `diagonal`." - ) + raise TypeError("`edge_map` can not be set at the same time as" " `cardinal` or `diagonal`.") c_edges, n_edges = _compile_cost_edges(edge_map) _check(lib.dijkstra2d(c_dist, _export(cost), n_edges, c_edges)) else: @@ -596,24 +564,15 @@ def hillclimb2d( x, y = start dist = np.asarray(distance) if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): - raise IndexError( - "Starting point %r not in shape %r" % (start, dist.shape) - ) + raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) c_dist = _export(dist) if edge_map is not None: if cardinal is not None or diagonal is not None: - raise TypeError( - "`edge_map` can not be set at the same time as" - " `cardinal` or `diagonal`." - ) + raise TypeError("`edge_map` can not be set at the same time as" " `cardinal` or `diagonal`.") c_edges, n_edges = _compile_bool_edges(edge_map) - func = functools.partial( - lib.hillclimb2d, c_dist, x, y, n_edges, c_edges - ) + func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges, c_edges) else: - func = functools.partial( - lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal - ) + func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal) length = _check(func(ffi.NULL)) path = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.from_buffer("int*", path) @@ -790,26 +749,15 @@ def add_edge( edge_dir = tuple(edge_dir) cost = np.asarray(cost) if len(edge_dir) != self._ndim: - raise TypeError( - "edge_dir must have exactly %i items, got %r" - % (self._ndim, edge_dir) - ) + raise TypeError("edge_dir must have exactly %i items, got %r" % (self._ndim, edge_dir)) if edge_cost <= 0: - raise ValueError( - "edge_cost must be greater than zero, got %r" % (edge_cost,) - ) + raise ValueError("edge_cost must be greater than zero, got %r" % (edge_cost,)) if cost.shape != self._shape: - raise TypeError( - "cost array must be shape %r, got %r" - % (self._shape, cost.shape) - ) + raise TypeError("cost array must be shape %r, got %r" % (self._shape, cost.shape)) if condition is not None: condition = np.asarray(condition) if condition.shape != self._shape: - raise TypeError( - "condition array must be shape %r, got %r" - % (self._shape, condition.shape) - ) + raise TypeError("condition array must be shape %r, got %r" % (self._shape, condition.shape)) if self._order == "F": # Inputs need to be converted to C. edge_dir = edge_dir[::-1] @@ -932,10 +880,7 @@ def add_edges( if edge_map.ndim < self._ndim: edge_map = edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)] if edge_map.ndim != self._ndim: - raise TypeError( - "edge_map must must match graph dimensions (%i). (Got %i)" - % (self.ndim, edge_map.ndim) - ) + raise TypeError("edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim)) if self._order == "F": # edge_map needs to be converted into C. # The other parameters are converted by the add_edge method. @@ -951,9 +896,7 @@ def add_edges( edge = tuple(edge) self.add_edge(edge, edge_cost, cost=cost, condition=condition) - def set_heuristic( - self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0 - ) -> None: + def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0) -> None: """Sets a pathfinder heuristic so that pathfinding can done with A*. `cardinal`, `diagonal`, `z, and `w` are the lower-bound cost of @@ -1006,9 +949,7 @@ def set_heuristic( if 0 == cardinal == diagonal == z == w: self._heuristic = None if diagonal and cardinal > diagonal: - raise ValueError( - "Diagonal parameter can not be lower than cardinal." - ) + raise ValueError("Diagonal parameter can not be lower than cardinal.") if cardinal < 0 or diagonal < 0 or z < 0 or w < 0: raise ValueError("Parameters can not be set to negative values..") self._heuristic = (cardinal, diagonal, z, w) @@ -1022,12 +963,8 @@ def _compile_rules(self) -> Any: rule = rule_.copy() rule["edge_count"] = len(rule["edge_list"]) # Edge rule format: [i, j, cost, ...] etc. - edge_obj = ffi.new( - "int[]", len(rule["edge_list"]) * (self._ndim + 1) - ) - edge_obj[0 : len(edge_obj)] = itertools.chain( - *rule["edge_list"] - ) + edge_obj = ffi.new("int[]", len(rule["edge_list"]) * (self._ndim + 1)) + edge_obj[0 : len(edge_obj)] = itertools.chain(*rule["edge_list"]) self._edge_rules_keep_alive.append(edge_obj) rule["edge_array"] = edge_obj self._edge_rules_keep_alive.append(rule["cost"]) @@ -1087,19 +1024,12 @@ class SimpleGraph: .. versionadded:: 11.15 """ - def __init__( - self, *, cost: np.ndarray, cardinal: int, diagonal: int, greed: int = 1 - ): + def __init__(self, *, cost: np.ndarray, cardinal: int, diagonal: int, greed: int = 1): cost = np.asarray(cost) if cost.ndim != 2: - raise TypeError( - "The cost array must e 2 dimensional, array of shape %r given." - % (cost.shape,) - ) + raise TypeError("The cost array must e 2 dimensional, array of shape %r given." % (cost.shape,)) if greed <= 0: - raise ValueError( - "Greed must be greater than zero, got %r" % (greed,) - ) + raise ValueError("Greed must be greater than zero, got %r" % (greed,)) edge_map = ( (diagonal, cardinal, diagonal), (cardinal, 0, cardinal), @@ -1111,9 +1041,7 @@ def __init__( self._shape = self._subgraph._shape[0], self._subgraph._shape[1] self._shape_c = self._subgraph._shape_c self._subgraph.add_edges(edge_map=edge_map, cost=cost) - self.set_heuristic( - cardinal=cardinal * greed, diagonal=diagonal * greed - ) + self.set_heuristic(cardinal=cardinal * greed, diagonal=diagonal * greed) @property def ndim(self) -> int: @@ -1156,16 +1084,12 @@ class Pathfinder: def __init__(self, graph: Union[CustomGraph, SimpleGraph]): self._graph = graph self._order = graph._order - self._frontier_p = ffi.gc( - lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete - ) + self._frontier_p = ffi.gc(lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete) self._distance = maxarray(self._graph._shape_c) self._travel = _world_array(self._graph._shape_c) self._distance_p = _export(self._distance) self._travel_p = _export(self._travel) - self._heuristic = ( - None - ) # type: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] + self._heuristic = None # type: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] self._heuristic_p = ffi.NULL # type: Any @property @@ -1252,9 +1176,7 @@ def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: if self._order == "F": # Convert to ij indexing order. index = index[::-1] if len(index) != self._distance.ndim: - raise TypeError( - "Index must be %i items, got %r" % (self._distance.ndim, index) - ) + raise TypeError("Index must be %i items, got %r" % (self._distance.ndim, index)) self._distance[index] = value self._update_heuristic(None) lib.TCOD_frontier_push(self._frontier_p, index, value, value) @@ -1273,9 +1195,7 @@ def _update_heuristic(self, goal_ij: Optional[Tuple[int, ...]]) -> bool: if heuristic is None: self._heuristic_p = ffi.NULL else: - self._heuristic_p = ffi.new( - "struct PathfinderHeuristic*", heuristic - ) + self._heuristic_p = ffi.new("struct PathfinderHeuristic*", heuristic) lib.update_frontier_heuristic(self._frontier_p, self._heuristic_p) return True # Frontier was updated. @@ -1292,11 +1212,7 @@ def rebuild_frontier(self) -> None: """ lib.TCOD_frontier_clear(self._frontier_p) self._update_heuristic(None) - _check( - lib.rebuild_frontier_from_distance( - self._frontier_p, self._distance_p - ) - ) + _check(lib.rebuild_frontier_from_distance(self._frontier_p, self._distance_p)) def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: """Manually run the pathfinder algorithm. @@ -1346,10 +1262,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: if goal is not None: goal = tuple(goal) # Check for bad input. if len(goal) != self._distance.ndim: - raise TypeError( - "Goal must be %i items, got %r" - % (self._distance.ndim, goal) - ) + raise TypeError("Goal must be %i items, got %r" % (self._distance.ndim, goal)) if self._order == "F": # Goal is now ij indexed for the rest of this function. goal = goal[::-1] @@ -1395,17 +1308,11 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: """ # noqa: E501 index = tuple(index) # Check for bad input. if len(index) != self._graph._ndim: - raise TypeError( - "Index must be %i items, got %r" % (self._distance.ndim, index) - ) + raise TypeError("Index must be %i items, got %r" % (self._distance.ndim, index)) self.resolve(index) if self._order == "F": # Convert to ij indexing order. index = index[::-1] - length = _check( - lib.get_travel_path( - self._graph._ndim, self._travel_p, index, ffi.NULL - ) - ) + length = _check(lib.get_travel_path(self._graph._ndim, self._travel_p, index, ffi.NULL)) path = np.ndarray((length, self._graph._ndim), dtype=np.intc) _check( lib.get_travel_path( diff --git a/tcod/random.h b/tcod/random.h index 0219b44d..cc7109db 100644 --- a/tcod/random.h +++ b/tcod/random.h @@ -6,17 +6,11 @@ int TCOD_random_get_i(TCOD_random_t mersenne, int min, int max); double TCOD_random_get_d(TCOD_random_t mersenne, double min, double max); -double TCOD_random_get_gaussian_double( - TCOD_random_t mersenne, double mean, double std_deviation); -double TCOD_random_get_gaussian_double_range( - TCOD_random_t mersenne, double min, double max); -double TCOD_random_get_gaussian_double_range_custom( - TCOD_random_t mersenne, double min, double max, double mean); -double TCOD_random_get_gaussian_double_inv( - TCOD_random_t mersenne, double mean, double std_deviation); -double TCOD_random_get_gaussian_double_range_inv( - TCOD_random_t mersenne, double min, double max); -double TCOD_random_get_gaussian_double_range_custom_inv( - TCOD_random_t mersenne, double min, double max, double mean); +double TCOD_random_get_gaussian_double(TCOD_random_t mersenne, double mean, double std_deviation); +double TCOD_random_get_gaussian_double_range(TCOD_random_t mersenne, double min, double max); +double TCOD_random_get_gaussian_double_range_custom(TCOD_random_t mersenne, double min, double max, double mean); +double TCOD_random_get_gaussian_double_inv(TCOD_random_t mersenne, double mean, double std_deviation); +double TCOD_random_get_gaussian_double_range_inv(TCOD_random_t mersenne, double min, double max); +double TCOD_random_get_gaussian_double_range_custom_inv(TCOD_random_t mersenne, double min, double max, double mean); #endif /* PYTHON_TCOD_RANDOM_H_ */ diff --git a/tcod/random.py b/tcod/random.py index 144f9825..d54a7036 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -120,9 +120,7 @@ def guass(self, mu: float, sigma: float) -> float: Returns: float: A random float. """ - return float( - lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma) - ) + return float(lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma)) def inverse_guass(self, mu: float, sigma: float) -> float: """Return a random Gaussian number using the Box-Muller transform. @@ -134,9 +132,7 @@ def inverse_guass(self, mu: float, sigma: float) -> float: Returns: float: A random float. """ - return float( - lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma) - ) + return float(lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma)) def __getstate__(self) -> Any: """Pack the self.random_c attribute into a portable state.""" diff --git a/tcod/sdl.py b/tcod/sdl.py index 3ead35ff..3ccd243f 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -18,15 +18,9 @@ class _TempSurface: def __init__(self, pixels: np.ndarray) -> None: self._array = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: - raise TypeError( - "NumPy shape must be 3D [y, x, ch] (got %r)" - % (self._array.shape,) - ) + raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape,)) if 3 <= self._array.shape[2] <= 4: - raise TypeError( - "NumPy array must have RGB or RGBA channels. (got %r)" - % (self._array.shape,) - ) + raise TypeError("NumPy array must have RGB or RGBA channels. (got %r)" % (self._array.shape,)) self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( ffi.from_buffer("void*", self._array), @@ -49,8 +43,7 @@ class Window: def __init__(self, sdl_window_p: Any) -> None: if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): raise TypeError( - "sdl_window_p must be %r type (was %r)." - % (ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)) + "sdl_window_p must be %r type (was %r)." % (ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)) ) if not sdl_window_p: raise ValueError("sdl_window_p can not be a null pointer.") @@ -114,9 +107,7 @@ def size(self) -> Tuple[int, int]: @size.setter def size(self, xy: Tuple[int, int]) -> None: if any(i <= 0 for i in xy): - raise ValueError( - "Window size must be greater than zero, not %r" % (xy,) - ) + raise ValueError("Window size must be greater than zero, not %r" % (xy,)) x, y = xy lib.SDL_SetWindowSize(self.p, x, y) diff --git a/tcod/tcod.c b/tcod/tcod.c index 1f8abffb..75994b85 100644 --- a/tcod/tcod.c +++ b/tcod/tcod.c @@ -20,9 +20,7 @@ int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out) { // Bresenham length is Chebyshev distance. int length = MAX(abs(x1 - x2), abs(y1 - y2)) + 1; if (!out) { return length; } - if (n < length) { - return TCOD_set_errorv("Bresenham output length mismatched."); - } + if (n < length) { return TCOD_set_errorv("Bresenham output length mismatched."); } TCOD_bresenham_data_t bresenham; out[0] = x1; out[1] = y1; diff --git a/tcod/tdl.c b/tcod/tdl.c index 149095dd..2e9f5b76 100644 --- a/tcod/tdl.c +++ b/tcod/tdl.c @@ -11,44 +11,27 @@ TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx) { return item; } -bool TDL_list_get_bool(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).b; -} +bool TDL_list_get_bool(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).b; } -char TDL_list_get_char(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).c; -} +char TDL_list_get_char(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).c; } -int TDL_list_get_int(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).i; -} +int TDL_list_get_int(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).i; } -float TDL_list_get_float(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).f; -} +float TDL_list_get_float(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).f; } -char* TDL_list_get_string(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).s; -} +char* TDL_list_get_string(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).s; } -TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).col; -} +TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).col; } -TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx) { - return TDL_list_get_union(l, idx).dice; -} +TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).dice; } /* get a TCOD color type from a 0xRRGGBB formatted integer */ TCOD_color_t TDL_color_from_int(int color) { - TCOD_color_t tcod_color = { - (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; + TCOD_color_t tcod_color = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; return tcod_color; } -int TDL_color_to_int(TCOD_color_t* color) { - return (color->r << 16) | (color->g << 8) | color->b; -} +int TDL_color_to_int(TCOD_color_t* color) { return (color->r << 16) | (color->g << 8) | color->b; } int* TDL_color_int_to_array(int color) { static int array[3]; @@ -58,9 +41,7 @@ int* TDL_color_int_to_array(int color) { return array; } -int TDL_color_RGB(int r, int g, int b) { - return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); -} +int TDL_color_RGB(int r, int g, int b) { return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } int TDL_color_HSV(float h, float s, float v) { TCOD_color_t tcod_color = TCOD_color_HSV(h, s, v); @@ -154,12 +135,7 @@ void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer) { for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { int i = y * width + x; - TCOD_map_set_properties( - map, - x, - y, - (buffer[i] & TRANSPARENT_BIT) != 0, - (buffer[i] & WALKABLE_BIT) != 0); + TCOD_map_set_properties(map, x, y, (buffer[i] & TRANSPARENT_BIT) != 0, (buffer[i] & WALKABLE_BIT) != 0); } } } @@ -183,21 +159,12 @@ void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative) { colors are converted to TCOD_color_t types in C and is much faster than in Python. Also Python indexing is used, negative x/y will index to (width-x, etc.) */ -int TDL_console_put_char_ex( - TCOD_console_t console, - int x, - int y, - int ch, - int fg, - int bg, - TCOD_bkgnd_flag_t blend) { +int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t blend) { int width = TCOD_console_get_width(console); int height = TCOD_console_get_height(console); TCOD_color_t color; - if (x < -width || x >= width || y < -height || y >= height) { - return -1; /* outside of console */ - } + if (x < -width || x >= width || y < -height || y >= height) { return -1; /* outside of console */ } /* normalize x, y */ if (x < 0) { x += width; }; @@ -225,10 +192,8 @@ int TDL_console_get_fg(TCOD_console_t console, int x, int y) { return TDL_color_to_int(&tcod_color); } -void TDL_console_set_bg( - TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag) { - TCOD_console_set_char_background( - console, x, y, TDL_color_from_int(color), flag); +void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag) { + TCOD_console_set_char_background(console, x, y, TDL_color_from_int(color), flag); } void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color) { diff --git a/tcod/tdl.h b/tcod/tdl.h index f3fc61f1..a68785b5 100644 --- a/tcod/tdl.h +++ b/tcod/tdl.h @@ -45,18 +45,10 @@ int TDL_color_scale_HSV(int color, float scoef, float vcoef); void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer); void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative); -int TDL_console_put_char_ex( - TCOD_console_t console, - int x, - int y, - int ch, - int fg, - int bg, - TCOD_bkgnd_flag_t flag); +int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t flag); int TDL_console_get_bg(TCOD_console_t console, int x, int y); int TDL_console_get_fg(TCOD_console_t console, int x, int y); -void TDL_console_set_bg( - TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag); +void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag); void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color); #endif /* PYTHON_TCOD_TDL_H_ */ diff --git a/tcod/tileset.py b/tcod/tileset.py index c3e3a661..b9249cc8 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -59,10 +59,7 @@ def tile_shape(self) -> Tuple[int, int]: def __contains__(self, codepoint: int) -> bool: """Test if a tileset has a codepoint with ``n in tileset``.""" - return bool( - lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) - == 0 - ) + return bool(lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) == 0) def get_tile(self, codepoint: int) -> np.ndarray: """Return a copy of a tile for the given codepoint. @@ -98,10 +95,7 @@ def set_tile(self, codepoint: int, tile: np.ndarray) -> None: return self.set_tile(codepoint, full_tile) required = self.tile_shape + (4,) if tile.shape != required: - raise ValueError( - "Tile shape must be %r or %r, got %r." - % (required, self.tile_shape, tile.shape) - ) + raise ValueError("Tile shape must be %r or %r, got %r." % (required, self.tile_shape, tile.shape)) lib.TCOD_tileset_set_tile_( self._tileset_p, codepoint, @@ -167,8 +161,7 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: if not (0 <= tile_i < self._tileset_p.tiles_count): raise IndexError( "Tile %i is non-existent and can't be assigned." - " (Tileset has %i tiles.)" - % (tile_i, self._tileset_p.tiles_count) + " (Tileset has %i tiles.)" % (tile_i, self._tileset_p.tiles_count) ) _check( lib.TCOD_tileset_assign_tile( @@ -207,9 +200,7 @@ def set_default(tileset: Tileset) -> None: lib.TCOD_set_default_tileset(tileset._tileset_p) -def load_truetype_font( - path: str, tile_width: int, tile_height: int -) -> Tileset: +def load_truetype_font(path: str, tile_width: int, tile_height: int) -> Tileset: """Return a new Tileset from a `.ttf` or `.otf` file. Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead. @@ -219,9 +210,7 @@ def load_truetype_font( """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_truetype_font_( - path.encode(), tile_width, tile_height - ) + cdata = lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) return Tileset._claim(cdata) @@ -275,9 +264,7 @@ def load_bdf(path: str) -> Tileset: return Tileset._claim(cdata) -def load_tilesheet( - path: str, columns: int, rows: int, charmap: Optional[Iterable[int]] -) -> Tileset: +def load_tilesheet(path: str, columns: int, rows: int, charmap: Optional[Iterable[int]]) -> Tileset: """Return a new Tileset from a simple tilesheet image. `path` is the file path to a PNG file with the tileset. @@ -302,9 +289,7 @@ def load_tilesheet( mapping = [] if charmap is not None: mapping = list(itertools.islice(charmap, columns * rows)) - cdata = lib.TCOD_tileset_load( - path.encode(), columns, rows, len(mapping), mapping - ) + cdata = lib.TCOD_tileset_load(path.encode(), columns, rows, len(mapping), mapping) if not cdata: _raise_tcod_error() return Tileset._claim(cdata) diff --git a/tests/common.py b/tests/common.py index 051b6c44..950049f7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,8 +1,7 @@ - import pytest import tcod def raise_Exception(*args): - raise Exception('testing exception') + raise Exception("testing exception") diff --git a/tests/conftest.py b/tests/conftest.py index 25de2125..1411d326 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ - import random import warnings @@ -8,25 +7,25 @@ def pytest_addoption(parser): - parser.addoption("--no-window", action="store_true", - help="Skip tests which need a rendering context.") + parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") + -@pytest.fixture(scope="session", params=['SDL', 'SDL2']) +@pytest.fixture(scope="session", params=["SDL", "SDL2"]) def session_console(request): - if(request.config.getoption("--no-window")): + if request.config.getoption("--no-window"): pytest.skip("This test needs a rendering context.") - FONT_FILE = 'libtcod/terminal.png' + FONT_FILE = "libtcod/terminal.png" WIDTH = 12 HEIGHT = 10 - TITLE = 'libtcod-cffi tests' + TITLE = "libtcod-cffi tests" FULLSCREEN = False - RENDERER = getattr(tcod, 'RENDERER_' + request.param) + RENDERER = getattr(tcod, "RENDERER_" + request.param) tcod.console_set_custom_font(FONT_FILE) - with tcod.console_init_root( - WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: + with tcod.console_init_root(WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: yield con + @pytest.fixture(scope="function") def console(session_console): console = session_console @@ -40,55 +39,71 @@ def console(session_console): console.clear() return console + @pytest.fixture() def offscreen(console): """Return an off-screen console with the same size as the root console.""" return tcod.console.Console(console.width, console.height) + @pytest.fixture() def fg(): - return tcod.Color(random.randint(0, 255), random.randint(0, 255), - random.randint(0, 255)) + return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + @pytest.fixture() def bg(): - return tcod.Color(random.randint(0, 255), random.randint(0, 255), - random.randint(0, 255)) + return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + try: unichr except NameError: unichr = chr + def ch_ascii_int(): - return random.randint(0x21, 0x7f) + return random.randint(0x21, 0x7F) + def ch_ascii_str(): return chr(ch_ascii_int()) + def ch_latin1_int(): - return random.randint(0x80, 0xff) + return random.randint(0x80, 0xFF) + def ch_latin1_str(): return chr(ch_latin1_int()) + def ch_bmp_int(): # Basic Multilingual Plane, before surrogates - return random.randint(0x100, 0xd7ff) + return random.randint(0x100, 0xD7FF) + def ch_bmp_str(): return unichr(ch_bmp_int()) + def ch_smp_int(): - return random.randint(0x10000, 0x1f9ff) + return random.randint(0x10000, 0x1F9FF) + def ch_smp_str(): return unichr(ch_bmp_int()) -@pytest.fixture(params=['ascii_int', 'ascii_str', - 'latin1_int', 'latin1_str', - #'bmp_int', 'bmp_str', # causes crashes - ]) + +@pytest.fixture( + params=[ + "ascii_int", + "ascii_str", + "latin1_int", + "latin1_str", + #'bmp_int', 'bmp_str', # causes crashes + ] +) def ch(request): """Test with multiple types of ascii/latin1 characters""" - return globals()['ch_%s' % request.param]() + return globals()["ch_%s" % request.param]() diff --git a/tests/test_console.py b/tests/test_console.py index 9605357b..b25f7477 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -13,7 +13,7 @@ def test_array_read_write(): console = tcod.console.Console(width=12, height=10) FG = (255, 254, 253) BG = (1, 2, 3) - CH = ord('&') + CH = ord("&") tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) assert console.ch[0, 0] == CH assert tuple(console.fg[0, 0]) == FG @@ -25,7 +25,7 @@ def test_array_read_write(): assert tuple(console.bg[2, 1]) == BG console.clear() - assert console.ch[1, 1] == ord(' ') + assert console.ch[1, 1] == ord(" ") assert tuple(console.fg[1, 1]) == (255, 255, 255) assert tuple(console.bg[1, 1]) == (0, 0, 0) @@ -61,14 +61,14 @@ def test_console_defaults(): @pytest.mark.filterwarnings("ignore:.*default values have been deprecated") def test_console_methods(): console = tcod.console.Console(width=12, height=10) - console.put_char(0, 0, ord('@')) - console.print_(0, 0, 'Test') - console.print_rect(0, 0, 2, 8, 'a b c d e f') - console.get_height_rect(0, 0, 2, 8, 'a b c d e f') + console.put_char(0, 0, ord("@")) + console.print_(0, 0, "Test") + console.print_rect(0, 0, 2, 8, "a b c d e f") + console.get_height_rect(0, 0, 2, 8, "a b c d e f") console.rect(0, 0, 2, 2, True) console.hline(0, 1, 10) console.vline(1, 0, 10) - console.print_frame(0, 0, 8, 8, 'Frame') + console.print_frame(0, 0, 8, 8, "Frame") console.blit(0, 0, 0, 0, console, 0, 0) console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) console.set_key_color((254, 0, 254)) @@ -76,7 +76,7 @@ def test_console_methods(): def test_console_pickle(): console = tcod.console.Console(width=12, height=10) - console.ch[...] = ord('.') + console.ch[...] = ord(".") console.fg[...] = (10, 20, 30) console.bg[...] = (1, 2, 3) console2 = pickle.loads(pickle.dumps(console)) @@ -86,7 +86,7 @@ def test_console_pickle(): def test_console_pickle_fortran(): - console = tcod.console.Console(2, 3, order='F') + console = tcod.console.Console(2, 3, order="F") console2 = pickle.loads(pickle.dumps(console)) assert console.ch.strides == console2.ch.strides assert console.fg.strides == console2.fg.strides @@ -102,8 +102,7 @@ def test_console_repr(): def test_console_str(): console = tcod.console.Console(10, 2) console.print_(0, 0, "Test") - assert str(console) == ("") + assert str(console) == ("") def test_console_fortran_buffer(): @@ -130,6 +129,7 @@ def test_console_semigraphics(): [[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]], ) + def test_rexpaint(tmp_path) -> None: xp_path = tmp_path / "test.xp" consoles = tcod.Console(80, 24, order="F"), tcod.Console(8, 8, order="F") diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 5bf053a3..c3440bd4 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -17,7 +17,7 @@ def test_console_behaviour(console): assert not console -@pytest.mark.skip('takes too long') +@pytest.mark.skip("takes too long") @pytest.mark.filterwarnings("ignore") def test_credits_long(console): libtcodpy.console_credits() @@ -81,29 +81,20 @@ def console_put_char_ex(console, ch, fg, bg): @pytest.mark.filterwarnings("ignore") def test_console_printing(console, fg, bg): - libtcodpy.console_set_background_flag(console, - libtcodpy.BKGND_SET) - assert (libtcodpy.console_get_background_flag(console) == - libtcodpy.BKGND_SET) + libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET) + assert libtcodpy.console_get_background_flag(console) == libtcodpy.BKGND_SET libtcodpy.console_set_alignment(console, libtcodpy.LEFT) - assert (libtcodpy.console_get_alignment(console) == - libtcodpy.LEFT) - - libtcodpy.console_print(console, 0, 0, 'print') - libtcodpy.console_print_ex(console, 0, 0, libtcodpy.BKGND_SET, - libtcodpy.LEFT, 'print ex') - - assert (libtcodpy.console_print_rect( - console, 0, 0, 8, 8, 'print rect') > 0 - ) - assert (libtcodpy.console_print_rect_ex( - console, 0, 0, 8, 8, libtcodpy.BKGND_SET, libtcodpy.LEFT, - 'print rect ex') > 0 - ) - assert (libtcodpy.console_get_height_rect( - console, 0, 0, 8, 8, 'get height') > 0 - ) + assert libtcodpy.console_get_alignment(console) == libtcodpy.LEFT + + libtcodpy.console_print(console, 0, 0, "print") + libtcodpy.console_print_ex(console, 0, 0, libtcodpy.BKGND_SET, libtcodpy.LEFT, "print ex") + + assert libtcodpy.console_print_rect(console, 0, 0, 8, 8, "print rect") > 0 + assert ( + libtcodpy.console_print_rect_ex(console, 0, 0, 8, 8, libtcodpy.BKGND_SET, libtcodpy.LEFT, "print rect ex") > 0 + ) + assert libtcodpy.console_get_height_rect(console, 0, 0, 8, 8, "get height") > 0 libtcodpy.console_set_color_control(libtcodpy.COLCTRL_1, fg, bg) @@ -132,14 +123,12 @@ def test_console_fade(console): def assertConsolesEqual(a, b): - return ((a.fg[:] == b.fg[:]).all() and - (a.bg[:] == b.bg[:]).all() and - (a.ch[:] == b.ch[:]).all()) + return (a.fg[:] == b.fg[:]).all() and (a.bg[:] == b.bg[:]).all() and (a.ch[:] == b.ch[:]).all() @pytest.mark.filterwarnings("ignore") def test_console_blit(console, offscreen): - libtcodpy.console_print(offscreen, 0, 0, 'test') + libtcodpy.console_print(offscreen, 0, 0, "test") libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1) assertConsolesEqual(console, offscreen) libtcodpy.console_set_key_color(offscreen, libtcodpy.black) @@ -147,9 +136,9 @@ def test_console_blit(console, offscreen): @pytest.mark.filterwarnings("ignore") def test_console_asc_read_write(console, offscreen, tmpdir): - libtcodpy.console_print(console, 0, 0, 'test') + libtcodpy.console_print(console, 0, 0, "test") - asc_file = tmpdir.join('test.asc').strpath + asc_file = tmpdir.join("test.asc").strpath assert libtcodpy.console_save_asc(console, asc_file) assert libtcodpy.console_load_asc(offscreen, asc_file) assertConsolesEqual(console, offscreen) @@ -157,9 +146,9 @@ def test_console_asc_read_write(console, offscreen, tmpdir): @pytest.mark.filterwarnings("ignore") def test_console_apf_read_write(console, offscreen, tmpdir): - libtcodpy.console_print(console, 0, 0, 'test') + libtcodpy.console_print(console, 0, 0, "test") - apf_file = tmpdir.join('test.apf').strpath + apf_file = tmpdir.join("test.apf").strpath assert libtcodpy.console_save_apf(console, apf_file) assert libtcodpy.console_load_apf(offscreen, apf_file) assertConsolesEqual(console, offscreen) @@ -167,23 +156,20 @@ def test_console_apf_read_write(console, offscreen, tmpdir): @pytest.mark.filterwarnings("ignore") def test_console_rexpaint_load_test_file(console): - xp_console = libtcodpy.console_from_xp('libtcod/data/rexpaint/test.xp') + xp_console = libtcodpy.console_from_xp("libtcod/data/rexpaint/test.xp") assert xp_console - assert libtcodpy.console_get_char(xp_console, 0, 0) == ord('T') - assert libtcodpy.console_get_char(xp_console, 1, 0) == ord('e') - assert (libtcodpy.console_get_char_background(xp_console, 0, 1) == - libtcodpy.Color(255, 0, 0)) - assert (libtcodpy.console_get_char_background(xp_console, 1, 1) == - libtcodpy.Color(0, 255, 0)) - assert (libtcodpy.console_get_char_background(xp_console, 2, 1) == - libtcodpy.Color(0, 0, 255)) + assert libtcodpy.console_get_char(xp_console, 0, 0) == ord("T") + assert libtcodpy.console_get_char(xp_console, 1, 0) == ord("e") + assert libtcodpy.console_get_char_background(xp_console, 0, 1) == libtcodpy.Color(255, 0, 0) + assert libtcodpy.console_get_char_background(xp_console, 1, 1) == libtcodpy.Color(0, 255, 0) + assert libtcodpy.console_get_char_background(xp_console, 2, 1) == libtcodpy.Color(0, 0, 255) @pytest.mark.filterwarnings("ignore") def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): - libtcodpy.console_print(console, 0, 0, 'test') + libtcodpy.console_print(console, 0, 0, "test") libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) - xp_file = tmpdir.join('test.xp').strpath + xp_file = tmpdir.join("test.xp").strpath assert libtcodpy.console_save_xp(console, xp_file, 1) xp_console = libtcodpy.console_from_xp(xp_file) assert xp_console @@ -196,9 +182,9 @@ def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): def test_console_rexpaint_list_save_load(console, tmpdir): con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) - libtcodpy.console_print(con1, 0, 0, 'hello') - libtcodpy.console_print(con2, 0, 0, 'world') - xp_file = tmpdir.join('test.xp').strpath + libtcodpy.console_print(con1, 0, 0, "hello") + libtcodpy.console_print(con2, 0, 0, "world") + xp_file = tmpdir.join("test.xp").strpath assert libtcodpy.console_list_save_xp([con1, con2], xp_file, 1) for a, b in zip([con1, con2], libtcodpy.console_list_load_xp(xp_file)): assertConsolesEqual(a, b) @@ -224,6 +210,7 @@ def test_console_fill_errors(console): with pytest.raises(TypeError): libtcodpy.console_fill_foreground(console, [0], [], []) + @pytest.mark.filterwarnings("ignore") def test_console_fill(console): width = libtcodpy.console_get_width(console) @@ -271,6 +258,7 @@ def test_console_fill_numpy(console): assert fill == fg.tolist() assert fill == ch.tolist() + @pytest.mark.filterwarnings("ignore") def test_console_buffer(console): buffer = libtcodpy.ConsoleBuffer( @@ -278,22 +266,25 @@ def test_console_buffer(console): libtcodpy.console_get_height(console), ) buffer = buffer.copy() - buffer.set_fore(0, 0, 0, 0, 0, '@') + buffer.set_fore(0, 0, 0, 0, 0, "@") buffer.set_back(0, 0, 0, 0, 0) - buffer.set(0, 0, 0, 0, 0, 0, 0, 0, '@') + buffer.set(0, 0, 0, 0, 0, 0, 0, 0, "@") buffer.blit(console) + @pytest.mark.filterwarnings("ignore:Console array attributes perform better") def test_console_buffer_error(console): buffer = libtcodpy.ConsoleBuffer(0, 0) with pytest.raises(ValueError): buffer.blit(console) + @pytest.mark.filterwarnings("ignore") def test_console_font_mapping(console): - libtcodpy.console_map_ascii_code_to_font('@', 1, 1) - libtcodpy.console_map_ascii_codes_to_font('@', 1, 0, 0) - libtcodpy.console_map_string_to_font('@', 0, 0) + libtcodpy.console_map_ascii_code_to_font("@", 1, 1) + libtcodpy.console_map_ascii_codes_to_font("@", 1, 0, 0) + libtcodpy.console_map_string_to_font("@", 0, 0) + @pytest.mark.filterwarnings("ignore") def test_mouse(console): @@ -303,6 +294,7 @@ def test_mouse(console): repr(mouse) libtcodpy.mouse_move(0, 0) + @pytest.mark.filterwarnings("ignore") def test_sys_time(console): libtcodpy.sys_set_fps(0) @@ -312,22 +304,27 @@ def test_sys_time(console): libtcodpy.sys_elapsed_milli() libtcodpy.sys_elapsed_seconds() + @pytest.mark.filterwarnings("ignore") def test_sys_screenshot(console, tmpdir): - libtcodpy.sys_save_screenshot(tmpdir.join('test.png').strpath) + libtcodpy.sys_save_screenshot(tmpdir.join("test.png").strpath) + @pytest.mark.filterwarnings("ignore") def test_sys_custom_render(console): if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL: - pytest.xfail(reason='Only supports SDL') + pytest.xfail(reason="Only supports SDL") escape = [] + def sdl_callback(sdl_surface): escape.append(True) libtcodpy.console_set_dirty(0, 0, 0, 0) + libtcodpy.sys_register_SDL_renderer(sdl_callback) libtcodpy.console_flush() - assert escape, 'proof that sdl_callback was called' + assert escape, "proof that sdl_callback was called" + @pytest.mark.filterwarnings("ignore") def test_image(console, tmpdir): @@ -345,22 +342,21 @@ def test_image(console, tmpdir): libtcodpy.image_get_pixel(img, 0, 0) libtcodpy.image_get_mipmap_pixel(img, 0, 0, 1, 1) libtcodpy.image_put_pixel(img, 0, 0, libtcodpy.Color(255, 255, 255)) - libtcodpy.image_blit(img, console, 0, 0, - libtcodpy.BKGND_SET, 1, 1, 0) - libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16, - libtcodpy.BKGND_SET) + libtcodpy.image_blit(img, console, 0, 0, libtcodpy.BKGND_SET, 1, 1, 0) + libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16, libtcodpy.BKGND_SET) libtcodpy.image_blit_2x(img, console, 0, 0) - libtcodpy.image_save(img, tmpdir.join('test.png').strpath) + libtcodpy.image_save(img, tmpdir.join("test.png").strpath) libtcodpy.image_delete(img) img = libtcodpy.image_from_console(console) libtcodpy.image_refresh_console(img, console) libtcodpy.image_delete(img) - libtcodpy.image_delete(libtcodpy.image_load('libtcod/data/img/circle.png')) + libtcodpy.image_delete(libtcodpy.image_load("libtcod/data/img/circle.png")) + -@pytest.mark.parametrize('sample', ['@', u'\u2603']) # Unicode snowman -@pytest.mark.xfail(reason='Unreliable') +@pytest.mark.parametrize("sample", ["@", "\u2603"]) # Unicode snowman +@pytest.mark.xfail(reason="Unreliable") @pytest.mark.filterwarnings("ignore") def test_clipboard(console, sample): saved = libtcodpy.sys_clipboard_get() @@ -373,8 +369,7 @@ def test_clipboard(console, sample): # arguments to test with and the results expected from these arguments LINE_ARGS = (-5, 0, 5, 10) -EXCLUSIVE_RESULTS = [(-4, 1), (-3, 2), (-2, 3), (-1, 4), (0, 5), (1, 6), - (2, 7), (3, 8), (4, 9), (5, 10)] +EXCLUSIVE_RESULTS = [(-4, 1), (-3, 2), (-2, 3), (-1, 4), (0, 5), (1, 6), (2, 7), (3, 8), (4, 9), (5, 10)] INCLUSIVE_RESULTS = [(-5, 0)] + EXCLUSIVE_RESULTS @@ -396,17 +391,21 @@ def test_line(): """ # test normal results test_result = [] + def line_test(*test_xy): test_result.append(test_xy) return 1 + assert libtcodpy.line(*LINE_ARGS, py_callback=line_test) == 1 assert test_result == INCLUSIVE_RESULTS # test lazy evaluation test_result = [] + def return_false(*test_xy): test_result.append(test_xy) return False + assert libtcodpy.line(*LINE_ARGS, py_callback=return_false) == 0 assert test_result == INCLUSIVE_RESULTS[:1] @@ -425,7 +424,7 @@ def test_bsp(): commented out statements work in libtcod-cffi """ bsp = libtcodpy.bsp_new_with_size(0, 0, 64, 64) - repr(bsp) # test __repr__ on leaf + repr(bsp) # test __repr__ on leaf libtcodpy.bsp_resize(bsp, 0, 0, 32, 32) assert bsp != None @@ -439,28 +438,28 @@ def test_bsp(): bsp.level = bsp.level # cover functions on leaf - #self.assertFalse(libtcodpy.bsp_left(bsp)) - #self.assertFalse(libtcodpy.bsp_right(bsp)) - #self.assertFalse(libtcodpy.bsp_father(bsp)) + # self.assertFalse(libtcodpy.bsp_left(bsp)) + # self.assertFalse(libtcodpy.bsp_right(bsp)) + # self.assertFalse(libtcodpy.bsp_father(bsp)) assert libtcodpy.bsp_is_leaf(bsp) assert libtcodpy.bsp_contains(bsp, 1, 1) - #self.assertFalse(libtcodpy.bsp_contains(bsp, -1, -1)) - #self.assertEqual(libtcodpy.bsp_find_node(bsp, 1, 1), bsp) - #self.assertFalse(libtcodpy.bsp_find_node(bsp, -1, -1)) + # self.assertFalse(libtcodpy.bsp_contains(bsp, -1, -1)) + # self.assertEqual(libtcodpy.bsp_find_node(bsp, 1, 1), bsp) + # self.assertFalse(libtcodpy.bsp_find_node(bsp, -1, -1)) libtcodpy.bsp_split_once(bsp, False, 4) - repr(bsp) # test __repr__ with parent + repr(bsp) # test __repr__ with parent libtcodpy.bsp_split_once(bsp, True, 4) repr(bsp) # cover functions on parent assert libtcodpy.bsp_left(bsp) assert libtcodpy.bsp_right(bsp) - #self.assertFalse(libtcodpy.bsp_father(bsp)) + # self.assertFalse(libtcodpy.bsp_father(bsp)) assert not libtcodpy.bsp_is_leaf(bsp) - #self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)), bsp) - #self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)), bsp) + # self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)), bsp) + # self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)), bsp) libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0) @@ -505,7 +504,7 @@ def test_color(): assert color_a[2] == color_a.b color_a[1] = 3 - color_a['b'] = color_a['b'] + color_a["b"] = color_a["b"] assert list(color_a) == [0, 3, 2] assert color_a == color_a @@ -545,8 +544,8 @@ def test_color_gen_map(): @pytest.mark.filterwarnings("ignore") def test_namegen_parse(): - libtcodpy.namegen_parse('libtcod/data/namegen/jice_celtic.cfg') - assert libtcodpy.namegen_generate('Celtic female') + libtcodpy.namegen_parse("libtcod/data/namegen/jice_celtic.cfg") + assert libtcodpy.namegen_generate("Celtic female") assert libtcodpy.namegen_get_sets() libtcodpy.namegen_destroy() @@ -604,13 +603,11 @@ def test_heightmap(): libtcodpy.heightmap_add_hill(hmap, 0, 0, 4, 1) libtcodpy.heightmap_dig_hill(hmap, 0, 0, 4, 1) libtcodpy.heightmap_rain_erosion(hmap, 1, 1, 1) - libtcodpy.heightmap_kernel_transform(hmap, 3, [-1, 1, 0], [0, 0, 0], - [.33, .33, .33], 0, 1) - libtcodpy.heightmap_add_voronoi(hmap, 10, 3, [1,3,5]) + libtcodpy.heightmap_kernel_transform(hmap, 3, [-1, 1, 0], [0, 0, 0], [0.33, 0.33, 0.33], 0, 1) + libtcodpy.heightmap_add_voronoi(hmap, 10, 3, [1, 3, 5]) libtcodpy.heightmap_add_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) libtcodpy.heightmap_scale_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) - libtcodpy.heightmap_dig_bezier(hmap, [0, 16, 16, 0], [0, 0, 16, 16], - 1, 1, 1, 1) + libtcodpy.heightmap_dig_bezier(hmap, [0, 16, 16, 0], [0, 0, 16, 16], 1, 1, 1, 1) # read data libtcodpy.heightmap_get_value(hmap, 0, 0) @@ -625,14 +622,15 @@ def test_heightmap(): libtcodpy.noise_delete(noise) libtcodpy.heightmap_delete(hmap) + MAP = ( - '############', - '# ### #', - '# ### #', - '# ### ####', - '## #### # ##', - '## ####', - '############', + "############", + "# ### #", + "# ### #", + "# ### ####", + "## #### # ##", + "## ####", + "############", ) MAP = np.array([list(line) for line in MAP]) @@ -643,14 +641,14 @@ def test_heightmap(): POINT_B = (9, 2) POINT_C = (9, 4) -POINTS_AB = POINT_A + POINT_B # valid path -POINTS_AC = POINT_A + POINT_C # invalid path +POINTS_AB = POINT_A + POINT_B # valid path +POINTS_AC = POINT_A + POINT_C # invalid path @pytest.fixture() def map_(): map_ = tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) - map_.walkable[...] = map_.transparent[...] = MAP[...] == ' ' + map_.walkable[...] = map_.transparent[...] = MAP[...] == " " yield map_ libtcodpy.map_delete(map_) @@ -661,6 +659,7 @@ def callback(ox, oy, dx, dy, user_data): if map_.walkable[dy, dx]: return 1 return 0 + return callback @@ -673,11 +672,11 @@ def test_map_fov(map_): def test_astar(map_): astar = libtcodpy.path_new_using_map(map_) - assert not libtcodpy.path_compute(astar, *POINTS_AC) - assert libtcodpy.path_size(astar) == 0 - assert libtcodpy.path_compute(astar, *POINTS_AB) - assert libtcodpy.path_get_origin(astar) == POINT_A - assert libtcodpy.path_get_destination(astar) == POINT_B + assert not libtcodpy.path_compute(astar, *POINTS_AC) + assert libtcodpy.path_size(astar) == 0 + assert libtcodpy.path_compute(astar, *POINTS_AB) + assert libtcodpy.path_get_origin(astar) == POINT_A + assert libtcodpy.path_get_destination(astar) == POINT_B libtcodpy.path_reverse(astar) assert libtcodpy.path_get_origin(astar) == POINT_B assert libtcodpy.path_get_destination(astar) == POINT_A @@ -701,7 +700,7 @@ def test_astar_callback(map_, path_callback): libtcodpy.map_get_width(map_), libtcodpy.map_get_height(map_), path_callback, - ) + ) libtcodpy.path_compute(astar, *POINTS_AB) libtcodpy.path_delete(astar) @@ -736,7 +735,7 @@ def test_dijkstra_callback(map_, path_callback): libtcodpy.map_get_width(map_), libtcodpy.map_get_height(map_), path_callback, - ) + ) libtcodpy.dijkstra_compute(path, *POINT_A) libtcodpy.dijkstra_delete(path) @@ -744,11 +743,9 @@ def test_dijkstra_callback(map_, path_callback): @pytest.mark.filterwarnings("ignore") def test_alpha_blend(console): for i in range(256): - libtcodpy.console_put_char(console, 0, 0, 'x', - libtcodpy.BKGND_ALPHA(i)) - libtcodpy.console_put_char(console, 0, 0, 'x', - libtcodpy.BKGND_ADDALPHA(i)) + libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ALPHA(i)) + libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ADDALPHA(i)) -if __name__ == '__main__': +if __name__ == "__main__": pytest.main() diff --git a/tests/test_noise.py b/tests/test_noise.py index ce1c22c7..2067b852 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -44,16 +44,12 @@ def test_noise_class( noise.sample_mgrid(np.mgrid[:2, :3]), noise.sample_ogrid(np.ogrid[:2, :3]), ) - np.testing.assert_equal( - noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])] - ) + np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])]) repr(noise) def test_noise_samples() -> None: - noise = tcod.noise.Noise( - 2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE - ) + noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE) np.testing.assert_equal( noise.sample_mgrid(np.mgrid[:32, :24]), noise.sample_ogrid(np.ogrid[:32, :24]), diff --git a/tests/test_parser.py b/tests/test_parser.py index 3bb62ac7..0973312a 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -9,99 +9,76 @@ @pytest.mark.filterwarnings("ignore") def test_parser(): - print ('***** File Parser test *****') - parser=libtcod.parser_new() - struct=libtcod.parser_new_struct(parser, b'myStruct') - libtcod.struct_add_property(struct, b'bool_field', libtcod.TYPE_BOOL, True) - libtcod.struct_add_property(struct, b'char_field', libtcod.TYPE_CHAR, True) - libtcod.struct_add_property(struct, b'int_field', libtcod.TYPE_INT, True) - libtcod.struct_add_property(struct, b'float_field', libtcod.TYPE_FLOAT, True) - libtcod.struct_add_property(struct, b'color_field', libtcod.TYPE_COLOR, True) - libtcod.struct_add_property(struct, b'dice_field', libtcod.TYPE_DICE, True) - libtcod.struct_add_property(struct, b'string_field', libtcod.TYPE_STRING, - True) - libtcod.struct_add_list_property(struct, b'bool_list', libtcod.TYPE_BOOL, - True) - libtcod.struct_add_list_property(struct, b'char_list', libtcod.TYPE_CHAR, - True) - libtcod.struct_add_list_property(struct, b'integer_list', libtcod.TYPE_INT, - True) - libtcod.struct_add_list_property(struct, b'float_list', libtcod.TYPE_FLOAT, - True) - libtcod.struct_add_list_property(struct, b'string_list', libtcod.TYPE_STRING, - True) - libtcod.struct_add_list_property(struct, b'color_list', libtcod.TYPE_COLOR, - True) -## # dice lists doesn't work yet -## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE, -## True) + print("***** File Parser test *****") + parser = libtcod.parser_new() + struct = libtcod.parser_new_struct(parser, b"myStruct") + libtcod.struct_add_property(struct, b"bool_field", libtcod.TYPE_BOOL, True) + libtcod.struct_add_property(struct, b"char_field", libtcod.TYPE_CHAR, True) + libtcod.struct_add_property(struct, b"int_field", libtcod.TYPE_INT, True) + libtcod.struct_add_property(struct, b"float_field", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_property(struct, b"color_field", libtcod.TYPE_COLOR, True) + libtcod.struct_add_property(struct, b"dice_field", libtcod.TYPE_DICE, True) + libtcod.struct_add_property(struct, b"string_field", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, b"bool_list", libtcod.TYPE_BOOL, True) + libtcod.struct_add_list_property(struct, b"char_list", libtcod.TYPE_CHAR, True) + libtcod.struct_add_list_property(struct, b"integer_list", libtcod.TYPE_INT, True) + libtcod.struct_add_list_property(struct, b"float_list", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_list_property(struct, b"string_list", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, b"color_list", libtcod.TYPE_COLOR, True) + ## # dice lists doesn't work yet + ## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE, + ## True) # default listener - print ('***** Default listener *****') - libtcod.parser_run(parser, os.path.join('libtcod', 'data', 'cfg', 'sample.cfg')) - print ('bool_field : ', \ - libtcod.parser_get_bool_property(parser, b'myStruct.bool_field')) - print ('char_field : ', \ - libtcod.parser_get_char_property(parser, b'myStruct.char_field')) - print ('int_field : ', \ - libtcod.parser_get_int_property(parser, b'myStruct.int_field')) - print ('float_field : ', \ - libtcod.parser_get_float_property(parser, b'myStruct.float_field')) - print ('color_field : ', \ - libtcod.parser_get_color_property(parser, b'myStruct.color_field')) - print ('dice_field : ', \ - libtcod.parser_get_dice_property(parser, b'myStruct.dice_field')) - print ('string_field : ', \ - libtcod.parser_get_string_property(parser, b'myStruct.string_field')) - print ('bool_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.bool_list', - libtcod.TYPE_BOOL)) - print ('char_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.char_list', - libtcod.TYPE_CHAR)) - print ('integer_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.integer_list', - libtcod.TYPE_INT)) - print ('float_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.float_list', - libtcod.TYPE_FLOAT)) - print ('string_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.string_list', - libtcod.TYPE_STRING)) - print ('color_list : ', \ - libtcod.parser_get_list_property(parser, b'myStruct.color_list', - libtcod.TYPE_COLOR)) -## print ('dice_list : ', \ -## libtcod.parser_get_list_property(parser, b'myStruct.dice_list', -## libtcod.TYPE_DICE)) + print("***** Default listener *****") + libtcod.parser_run(parser, os.path.join("libtcod", "data", "cfg", "sample.cfg")) + print("bool_field : ", libtcod.parser_get_bool_property(parser, b"myStruct.bool_field")) + print("char_field : ", libtcod.parser_get_char_property(parser, b"myStruct.char_field")) + print("int_field : ", libtcod.parser_get_int_property(parser, b"myStruct.int_field")) + print("float_field : ", libtcod.parser_get_float_property(parser, b"myStruct.float_field")) + print("color_field : ", libtcod.parser_get_color_property(parser, b"myStruct.color_field")) + print("dice_field : ", libtcod.parser_get_dice_property(parser, b"myStruct.dice_field")) + print("string_field : ", libtcod.parser_get_string_property(parser, b"myStruct.string_field")) + print("bool_list : ", libtcod.parser_get_list_property(parser, b"myStruct.bool_list", libtcod.TYPE_BOOL)) + print("char_list : ", libtcod.parser_get_list_property(parser, b"myStruct.char_list", libtcod.TYPE_CHAR)) + print("integer_list : ", libtcod.parser_get_list_property(parser, b"myStruct.integer_list", libtcod.TYPE_INT)) + print("float_list : ", libtcod.parser_get_list_property(parser, b"myStruct.float_list", libtcod.TYPE_FLOAT)) + print("string_list : ", libtcod.parser_get_list_property(parser, b"myStruct.string_list", libtcod.TYPE_STRING)) + print("color_list : ", libtcod.parser_get_list_property(parser, b"myStruct.color_list", libtcod.TYPE_COLOR)) + ## print ('dice_list : ', \ + ## libtcod.parser_get_list_property(parser, b'myStruct.dice_list', + ## libtcod.TYPE_DICE)) # custom listener - print ('***** Custom listener *****') + print("***** Custom listener *****") + class MyListener: def new_struct(self, struct, name): - print ('new structure type', libtcod.struct_get_name(struct), \ - ' named ', name ) + print("new structure type", libtcod.struct_get_name(struct), " named ", name) return True + def new_flag(self, name): - print ('new flag named ', name) + print("new flag named ", name) return True - def new_property(self,name, typ, value): - type_names = ['NONE', 'BOOL', 'CHAR', 'INT', 'FLOAT', 'STRING', \ - 'COLOR', 'DICE'] - type_name = type_names[typ & 0xff] + + def new_property(self, name, typ, value): + type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"] + type_name = type_names[typ & 0xFF] if typ & libtcod.TYPE_LIST: - type_name = 'LIST<%s>' % type_name - print ('new property named ', name,' type ',type_name, \ - ' value ', value) + type_name = "LIST<%s>" % type_name + print("new property named ", name, " type ", type_name, " value ", value) return True + def end_struct(self, struct, name): - print ('end structure type', libtcod.struct_get_name(struct), \ - ' named ', name) + print("end structure type", libtcod.struct_get_name(struct), " named ", name) return True - def error(self,msg): - print ('error : ', msg) + + def error(self, msg): + print("error : ", msg) return True - libtcod.parser_run(parser, os.path.join('libtcod','data','cfg','sample.cfg'), MyListener()) -if __name__ == '__main__': + libtcod.parser_run(parser, os.path.join("libtcod", "data", "cfg", "sample.cfg"), MyListener()) + + +if __name__ == "__main__": test_parser() diff --git a/tests/test_random.py b/tests/test_random.py index f423289f..4e889e7d 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,4 +1,3 @@ - import copy import pickle @@ -12,6 +11,7 @@ def test_tcod_random(): rand.guass(0, 1) rand.inverse_guass(0, 1) + def test_tcod_random_copy(): rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER) rand2 = copy.copy(rand) @@ -19,6 +19,7 @@ def test_tcod_random_copy(): assert rand.uniform(0, 1) == rand2.uniform(0, 1) assert rand.uniform(0, 1) == rand2.uniform(0, 1) + def test_tcod_random_pickle(): rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER) rand2 = pickle.loads(pickle.dumps(rand)) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 66db56a2..8365053c 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -38,7 +38,7 @@ def test_tcod_bsp(): for node in bsp.walk(): assert isinstance(node, tcod.bsp.BSP) - assert bsp != 'asd' + assert bsp != "asd" # test that operations on deep BSP nodes preserve depth sub_bsp = bsp.children[0] @@ -56,7 +56,7 @@ def test_tcod_bsp(): @pytest.mark.filterwarnings("ignore:Use map.+ to check for this") @pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_set_bits(): - map_ = tcod.map.Map(2,2) + map_ = tcod.map.Map(2, 2) assert map_.transparent[:].any() == False assert map_.walkable[:].any() == False @@ -72,7 +72,7 @@ def test_tcod_map_set_bits(): @pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_get_bits(): - map_ = tcod.map.Map(2,2) + map_ = tcod.map.Map(2, 2) map_.transparent[0] @@ -80,7 +80,7 @@ def test_tcod_map_get_bits(): def test_tcod_map_copy(): map_ = tcod.map.Map(3, 3) map_.transparent[:] = True - assert (map_.transparent.tolist() == copy.copy(map_).transparent.tolist()) + assert map_.transparent.tolist() == copy.copy(map_).transparent.tolist() @pytest.mark.filterwarnings("ignore:This class may perform poorly") @@ -88,12 +88,12 @@ def test_tcod_map_pickle(): map_ = tcod.map.Map(3, 3) map_.transparent[:] = True map2 = pickle.loads(pickle.dumps(copy.copy(map_))) - assert (map_.transparent[:].tolist() == map2.transparent[:].tolist()) + assert map_.transparent[:].tolist() == map2.transparent[:].tolist() @pytest.mark.filterwarnings("ignore:This class may perform poorly") def test_tcod_map_pickle_fortran(): - map_ = tcod.map.Map(2, 3, order='F') + map_ = tcod.map.Map(2, 3, order="F") map2 = pickle.loads(pickle.dumps(copy.copy(map_))) assert map_._Map__buffer.strides == map2._Map__buffer.strides assert map_.transparent.strides == map2.transparent.strides @@ -120,21 +120,20 @@ def test_color_class(): assert color == (1, 2, 3) -@pytest.mark.parametrize('dtype', [np.int8, np.int16, np.int32, - np.uint8, np.uint16, np.uint32, np.float32]) +@pytest.mark.parametrize("dtype", [np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32, np.float32]) def test_path_numpy(dtype): map_np = np.ones((6, 6), dtype=dtype) map_np[1:4, 1:4] = 0 astar = tcod.path.AStar(map_np, 0) - astar = pickle.loads(pickle.dumps(astar)) # test pickle - astar = tcod.path.AStar(astar.cost, 0) # use existing cost attribute + astar = pickle.loads(pickle.dumps(astar)) # test pickle + astar = tcod.path.AStar(astar.cost, 0) # use existing cost attribute assert len(astar.get_path(0, 0, 5, 5)) == 10 dijkstra = tcod.path.Dijkstra(map_np, 0) dijkstra.set_goal(0, 0) assert len(dijkstra.get_path(5, 5)) == 10 - repr(dijkstra) # cover __repr__ methods + repr(dijkstra) # cover __repr__ methods # cover errors with pytest.raises(ValueError): @@ -144,16 +143,14 @@ def test_path_numpy(dtype): def path_cost(this_x, this_y, dest_x, dest_y): - return 1 + return 1 + def test_path_callback(): - astar = tcod.path.AStar( - tcod.path.EdgeCostCallback(path_cost, (10, 10)) - ) + astar = tcod.path.AStar(tcod.path.EdgeCostCallback(path_cost, (10, 10))) astar = pickle.loads(pickle.dumps(astar)) - assert astar.get_path(0, 0, 5, 0) == \ - [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] - repr(astar) # cover __repr__ methods + assert astar.get_path(0, 0, 5, 0) == [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] + repr(astar) # cover __repr__ methods def test_key_repr(): @@ -192,9 +189,7 @@ def test_context(): with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): pass WIDTH, HEIGHT = 16, 4 - with tcod.context.new_terminal( - columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2 - ) as context: + with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2) as context: console = tcod.Console(*context.recommended_console_size()) context.present(console) context.sdl_window_p diff --git a/tests/test_testing.py b/tests/test_testing.py index 151d81fe..727ad863 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -6,9 +6,9 @@ curdir = os.path.dirname(__file__) -FONT_FILE = os.path.join(curdir, 'data/fonts/consolas10x10_gs_tc.png') +FONT_FILE = os.path.join(curdir, "data/fonts/consolas10x10_gs_tc.png") -#def test_console(): +# def test_console(): # libtcod.console_set_custom_font(FONT_FILE, libtcod.FONT_LAYOUT_TCOD) # libtcod.console_init_root(40, 30, 'test', False, libtcod.RENDERER_SDL) # libtcod.console_flush() From 6df1b1fefb9ea289de8d000aca76289a744b3dde Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Jun 2021 17:30:15 -0700 Subject: [PATCH 0538/1101] Clean up and enable linting for tests and scripts. Add type hints to deprecated parser functions. These functions can now take str. Fix Python version mypy setting. For now this will be set to Python 3.8 globally and other tests will be used to check earlier versions. Remove dev scripts, these were old experiments for tdl. Associate PyInstaller spec files with Python. --- .github/workflows/python-lint.yml | 10 +- .vscode/settings.json | 6 +- build_libtcod.py | 27 ++- dev/benchmark.py | 117 ----------- dev/noise_gen.py | 36 ---- dev/samples.py | 101 ---------- dev/sdl_hook_test.py | 41 ---- dev/stress_test.py | 167 ---------------- examples/distribution/PyInstaller/main.py | 2 +- examples/distribution/cx_Freeze/main.py | 2 +- examples/distribution/cx_Freeze/setup.py | 2 +- examples/samples_tcod.py | 6 +- parse_sdl2.py | 6 +- setup.cfg | 3 +- setup.py | 2 +- tcod/bsp.py | 10 +- tcod/libtcodpy.py | 32 +-- tests/common.py | 7 - tests/conftest.py | 57 ++---- tests/test_console.py | 35 ++-- tests/test_libtcodpy.py | 225 ++++++++++++---------- tests/test_parser.py | 73 ++++--- tests/test_random.py | 6 +- tests/test_tcod.py | 79 ++++---- tests/test_testing.py | 2 - 25 files changed, 294 insertions(+), 760 deletions(-) delete mode 100644 dev/benchmark.py delete mode 100755 dev/noise_gen.py delete mode 100755 dev/samples.py delete mode 100644 dev/sdl_hook_test.py delete mode 100755 dev/stress_test.py delete mode 100644 tests/common.py diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index ee89c14b..6f3a6de6 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -23,7 +23,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 mypy black isort + python -m pip install flake8 mypy black isort pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Fake initialize package run: | @@ -32,18 +32,18 @@ jobs: uses: liskin/gh-problem-matcher-wrap@v1 with: linters: flake8 - run: flake8 scripts/ tcod/ + run: flake8 scripts/ tcod/ tests/ - name: MyPy if: always() uses: liskin/gh-problem-matcher-wrap@v1 with: linters: mypy - run: mypy --show-column-numbers scripts/ tcod/ + run: mypy --show-column-numbers . - name: isort uses: liskin/gh-problem-matcher-wrap@v1 with: linters: isort - run: isort scripts/ tcod/ tests/ *.py --check + run: isort scripts/ tcod/ tests/ --check - name: isort (examples) uses: liskin/gh-problem-matcher-wrap@v1 with: @@ -51,4 +51,4 @@ jobs: run: isort examples/ --check --thirdparty tcod - name: Black run: | - black --check examples/ scripts/ tcod/ tests/ *.py + black --check examples/ scripts/ tcod/ tests/ *.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 7685f2b6..88fa964d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,9 +11,11 @@ "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.mypyArgs": [ - "--python-version 3.8", // Python 3.8 supported by examples. "--follow-imports=silent", "--show-column-numbers" ], - "python.formatting.provider": "black" + "python.formatting.provider": "black", + "files.associations": { + "*.spec": "python", + } } diff --git a/build_libtcod.py b/build_libtcod.py index 80143b7f..4241a544 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -8,7 +8,7 @@ import subprocess import sys import zipfile -from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple +from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union try: from urllib import urlretrieve # type: ignore @@ -165,7 +165,7 @@ def unpack_sdl2(version: str) -> str: if sdl2_arc.endswith(".zip"): with zipfile.ZipFile(sdl2_arc) as zf: zf.extractall("dependencies/") - else: + elif sys.platform == "darwin": assert sdl2_arc.endswith(".dmg") subprocess.check_call(["hdiutil", "mount", sdl2_arc]) subprocess.check_call(["mkdir", "-p", sdl2_dir]) @@ -190,7 +190,7 @@ def unpack_sdl2(version: str) -> str: sources = [] # type: List[str] libraries = [] -library_dirs = [] +library_dirs: List[str] = [] define_macros = [("Py_LIMITED_API", 0x03060000)] # type: List[Tuple[str, Any]] sources += walk_sources("tcod/") @@ -281,7 +281,7 @@ def fix_header(filepath: str) -> None: tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]} -MSVC_LDFLAGS = {"DEBUG": [], "RELEASE": ["/LTCG"]} +MSVC_LDFLAGS: Dict[str, List[str]] = {"DEBUG": [], "RELEASE": ["/LTCG"]} GCC_CFLAGS = { "DEBUG": ["-std=c99", "-Og", "-g", "-fPIC"], "RELEASE": [ @@ -348,7 +348,7 @@ def fix_header(filepath: str) -> None: ''' -def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Any]]: +def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Union[int, str, Any]]]: """Return names and values from `tcod.lib`. `prefix` is used to filter out which names to copy. @@ -378,9 +378,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: all_names.append(name) names.append("%s = %s" % (name, value)) lookup.append('%s: "%s"' % (value, name)) - names = "\n".join(names) - lookup = "{\n %s,\n}" % (",\n ".join(lookup),) - return names, lookup + return "\n".join(names), "{\n %s,\n}" % (",\n ".join(lookup),) EXCLUDE_CONSTANTS = [ @@ -418,7 +416,6 @@ def update_module_all(filename: str, new_all: str) -> None: def generate_enums(prefix: str) -> Iterator[str]: """Generate attribute assignments suitable for a Python enum.""" - prefix_len = len(prefix) - len("SDL_") + 1 for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): name = name.split("_", 1)[1] if name.isdigit(): @@ -471,10 +468,10 @@ def write_library_constants() -> None: f.write("%s = %r\n" % (name[5:], color)) all_names.append(name[5:]) - all_names = ",\n ".join('"%s"' % name for name in all_names) - f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) - update_module_all("tcod/__init__.py", all_names) - update_module_all("tcod/libtcodpy.py", all_names) + all_names_merged = ",\n ".join('"%s"' % name for name in all_names) + f.write("\n__all__ = [\n %s,\n]\n" % (all_names_merged,)) + update_module_all("tcod/__init__.py", all_names_merged) + update_module_all("tcod/libtcodpy.py", all_names_merged) with open("tcod/event_constants.py", "w") as f: all_names = [] @@ -490,8 +487,8 @@ def write_library_constants() -> None: f.write("\n# --- SDL wheel ---\n") f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) - all_names = ",\n ".join('"%s"' % name for name in all_names) - f.write("\n__all__ = [\n %s,\n]\n" % (all_names,)) + all_names_merged = ",\n ".join('"%s"' % name for name in all_names) + f.write("\n__all__ = [\n %s,\n]\n" % (all_names_merged,)) with open("tcod/event.py", "r") as f: event_py = f.read() diff --git a/dev/benchmark.py b/dev/benchmark.py deleted file mode 100644 index 01570661..00000000 --- a/dev/benchmark.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import time -import platform - -import tdl - -WIDTH = 80 # must be divisible by 16 -HEIGHT = 48 - -RENDERER = 'OpenGL' - -log = None - -def print_result(string): - print(string) - print(string, file=log) - -class Benchmark: - default_frames = 100 - - def run(self, console, frames=None, times=4): - if times > 1: - print_result('Running %s' % self.__class__.__name__) - while times > 0: - self.run(console, frames, times=1) - times -= 1 - print_result('') - return - if frames is None: - frames = self.default_frames - self.total_frames = 0 - self.tiles = 0 - console.clear() - self.start_time = time.clock() - while self.total_frames < frames: - self.total_frames += 1 - self.test(console) - for event in tdl.event.get(): - if event.type == 'QUIT': - raise SystemExit('Benchmark Canceled') - self.total_time = time.clock() - self.start_time - self.tiles_per_second = self.tiles / self.total_time - print_result( - '%i tiles drawn in %.2f seconds, %.2f characters/ms, %.2f FPS' % - (self.tiles, self.total_time,self.tiles_per_second / 1000, - self.total_frames / self.total_time)) - - def test(self, console): - for x,y in console: - console.draw_char(x, y, '.') - tiles += 1 - tdl.flush() - - -class Benchmark_DrawChar_DefaultColor(Benchmark): - - def test(self, console): - for x,y in console: - console.draw_char(x, y, 'A') - self.tiles += 1 - tdl.flush() - - -class Benchmark_DrawChar_NoColor(Benchmark): - - def test(self, console): - for x,y in console: - console.draw_char(x, y, 'B', None, None) - self.tiles += 1 - tdl.flush() - - -class Benchmark_DrawStr16_DefaultColor(Benchmark): - default_frames = 100 - - def test(self, console): - for y in range(HEIGHT): - for x in range(0, WIDTH, 16): - console.draw_str(x, y, '0123456789ABCDEF') - self.tiles += 16 - tdl.flush() - - -class Benchmark_DrawStr16_NoColor(Benchmark): - default_frames = 100 - - def test(self, console): - for y in range(HEIGHT): - for x in range(0, WIDTH, 16): - console.draw_str(x, y, '0123456789ABCDEF', None, None) - self.tiles += 16 - tdl.flush() - -def run_benchmark(): - global log - log = open('results.log', 'a') - print('', file=log) - console = tdl.init(WIDTH, HEIGHT, renderer=RENDERER) - - print_result('Benchmark run on %s' % time.ctime()) - print_result('Running under %s %s' % (platform.python_implementation(), - platform.python_version())) - print_result('In %s mode' % (['release', 'debug'][__debug__])) - print_result('%i characters/frame' % (WIDTH * HEIGHT)) - print_result('Opened console in %s mode' % RENDERER) - Benchmark_DrawChar_DefaultColor().run(console) - Benchmark_DrawChar_NoColor().run(console) - #Benchmark_DrawStr16_DefaultColor().run(console) - #Benchmark_DrawStr16_NoColor().run(console) - log.close() - print('results written to results.log') - -if __name__ == '__main__': - run_benchmark() diff --git a/dev/noise_gen.py b/dev/noise_gen.py deleted file mode 100755 index 5eeddba3..00000000 --- a/dev/noise_gen.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python - -# allow a noise test -# give a directory and this will save simple bitmaps of each noise example -import sys, os - -sys.path.insert(0, '..') -from tdl.noise import Noise - -IMGSIZE = 128 # width and height of the saved image -NOISE_SCALE = 1 / 12 -saveDir = '' -kargs = {} -if len(sys.argv) >= 2: - saveDir = sys.argv[1] # get save directory - kargs = dict((param.split('=') for param in sys.argv[2:])) # get parameters -if not saveDir: - raise SystemExit('Provide a directory to save the noise examples.') -for algo in ['PERLIN', 'SIMPLEX', 'WAVELET']: - for mode in ['FLAT', 'FBM', 'TURBULENCE']: - noise = Noise(algo, mode) - noiseFile = open(os.path.join(saveDir, '%s_%s.pgm' % (algo, mode)), 'wb') - print('Generating %s' % noiseFile.name) - # make a greyscale Netpbm file - noiseFile.write(b'P5\n') - noiseFile.write(('%i %i\n' % (IMGSIZE, IMGSIZE)).encode('ascii')) - noiseFile.write(b'255\n') - for y in range(IMGSIZE): - noiseY = y * NOISE_SCALE - for x in range(IMGSIZE): - noiseX = x * NOISE_SCALE - if x == 0 or x == IMGSIZE - 1 or y == 0 or y == IMGSIZE - 1: - val = 0 # use black border - else: - val = int(noise.getPoint(noiseX, noiseY) * 255) - noiseFile.write(bytes((val,))) diff --git a/dev/samples.py b/dev/samples.py deleted file mode 100755 index 8044de75..00000000 --- a/dev/samples.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -""" - Sample pack showcasing the abilities of TDL - - This example is not finished quite yet -""" - -import sys - -import random - -sys.path.insert(0, '../') -import tdl - -sampleIndex = 0 - -class SampleApp(tdl.event.App): - name = '' - - def key_UP(self, event): - global sampleIndex - sampleIndex = (sampleIndex - 1) % len(samples) - - def key_DOWN(self, event): - global sampleIndex - sampleIndex = (sampleIndex - 1) % len(samples) - -class TrueColorSample(SampleApp): - name = 'True Colors' - - def update(self, deltaTime): - width, height = samplewin.getSize() - for x in range(width): - for y in range(height): - char = random.getrandbits(8) - samplewin.drawChar(x, y, char, (255, 255, 255), (0, 0, 0)) - -class NoiseSample(SampleApp): - name = 'Noise' - SPEED = 3 - - NOISE_KEYS = {'1': 'PERLIN', '2': 'SIMPLEX', '3': 'WAVELET'} - MODE_KEYS = {'4': 'FLAT', '5': 'FBM', '6': 'TURBULENCE'} - - def __init__(self): - self.noiseType = 'PERLIN' - self.noiseMode = 'FLAT' - self.x = 0 - self.y = 0 - self.z = 0 - self.zoom = 4 - self.generateNoise() - - def generateNoise(self): - self.noise = tdl.noise.Noise(self.noiseType, self.noiseMode, seed=42) - - def ev_KEYDOWN(self, event): - if event.char in self.NOISE_KEYS: - self.noiseType = self.NOISE_KEYS[event.char] - print('noise set to %s' % self.noiseType) - if event.char in self.MODE_KEYS: - self.noiseMode = self.MODE_KEYS[event.char] - print('mode set to %s' % self.noiseMode) - self.generateNoise() - - def update(self, deltaTime): - self.x += self.SPEED * deltaTime# * self.zoom - self.y += self.SPEED * deltaTime# * self.zoom - self.z += deltaTime / 4 - - width, height = samplewin.getSize() - for x in range(width): - for y in range(height): - val = self.noise.getPoint((x + self.x) / width * self.zoom, - (y + self.y) / height * self.zoom, - self.z) - bgcolor = (int(val * 255),) * 2 + (min(255, int(val * 2 * 255)),) - samplewin.drawChar(x, y, ' ', (255, 255, 255), bgcolor) - -WIDTH, HEIGHT = 80, 40 -SAMPLE_WINDOW_RECT = (20, 10, 46, 20) - -FONT = '../fonts/X11/8x13.png' - -if __name__ == '__main__': - tdl.setFont(FONT) - console = tdl.init(WIDTH, HEIGHT, renderer='opengl') - samplewin = tdl.Window(console, *SAMPLE_WINDOW_RECT) - - samples = [cls() for cls in [TrueColorSample, NoiseSample]] - - while 1: - console.clear() - samples[sampleIndex].runOnce() - for i, sample in enumerate(samples): - bgcolor = (0, 0, 0) - if sampleIndex == i: - bgcolor = (0, 0, 192) - console.drawStr(0, -5 + i, '%s' % sample.name, (255, 255, 255), bgcolor) - console.drawStr(0, -1, '%i FPS' % tdl.getFPS()) - tdl.flush() diff --git a/dev/sdl_hook_test.py b/dev/sdl_hook_test.py deleted file mode 100644 index 454ad642..00000000 --- a/dev/sdl_hook_test.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -""" - example of libtcod's SDL hook - - draws a simple white square. -""" - -import tcod -import tdl - -import numpy as np - -# generate a callback for libtcod -@tcod.ffi.callback('SDL_renderer_t') -def sdl_hook(surface): - tcod.lib.SDL_UpperBlit(my_surface, tcod.ffi.NULL, surface, [{'x':0, 'y':0}]) - -pixels = np.zeros((100, 150, 4), dtype=np.uint8) -my_surface = tcod.lib.SDL_CreateRGBSurfaceWithFormatFrom( - tcod.ffi.cast('void*', pixels.ctypes.data), - pixels.shape[1], pixels.shape[0], 32, - pixels.strides[0], - tcod.lib.SDL_PIXELFORMAT_RGBA32, -) - - -if __name__ == '__main__': - # hook callback to libtcod - tcod.sys_register_SDL_renderer(sdl_hook) - - con = tdl.init(32, 32, renderer='SDL') # MUST BE SDL RENDERER - - pixels[:] = 255 - - tick = 0 - while(True): - tick += 1 - for event in tdl.event.get(): - if event.type == 'QUIT': - raise SystemExit() - tdl.flush() # will call sdl_hook diff --git a/dev/stress_test.py b/dev/stress_test.py deleted file mode 100755 index 87c2f92a..00000000 --- a/dev/stress_test.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python -""" - Stress test designed to test tdl under harsh conditions - - Note that one of the slower parts is converting colors over ctypes so you - can get a bit of speed avoiding the fgcolor and bgcolor parameters. - Another thing slowing things down are mass calls to ctypes functions. - Eventually these will be optimized to work better. -""" -import sys - -import itertools -import random -import time - -sys.path.insert(0, '../') -import tdl - -class StopWatch: - "Simple tool used to count time within a block using the with statement" - MAXSNAPSHOTS = 10 - - def __init__(self): - self.enterTime = None - self.snapshots = [] - - def __enter__(self): - self.enterTime = time.clock() - - def __exit__(self, *exc): - self.snapshots.append(time.clock() - self.enterTime) - if len(self.snapshots) > self.MAXSNAPSHOTS: - self.snapshots.pop(0) - - def getMeanTime(self): - if not self.snapshots: - return 0 - return sum(self.snapshots) / len(self.snapshots) - - -class TestApp(tdl.event.App): - - def __init__(self, console): - self.console = console - self.writer = self.console - self.console.setMode('scroll') - self.width, self.height = self.console.getSize() - self.total = self.width * self.height - self.cells = list(itertools.product(range(self.width), range(self.height))) - self.tick = 0 - self.init() - - def init(self): - pass - - def ev_MOUSEDOWN(self, event): - self.suspend() - - def update(self, deltaTime): - self.updateTest(deltaTime) - self.tick += 1 - #if self.tick % 50 == 0: - tdl.setTitle('%s: %i FPS' % (self.__class__.__name__, tdl.getFPS())) - tdl.flush() - -class FullDrawCharTest(TestApp): - - def updateTest(self, deltaTime): - # getrandbits is around 5x faster than using randint - bgcolors = [(random.getrandbits(7), random.getrandbits(7), random.getrandbits(7)) for _ in range(self.total)] - char = [random.getrandbits(8) for _ in range(self.total)] - fgcolor = (255, 255, 255) - for (x,y), bgcolor, char in zip(self.cells, bgcolors, char): - self.console.draw_char(x, y, char, fgcolor, bgcolor) - -class PreCompiledColorTest(TestApp): - """ - This test was to see if the cast to the ctypes Color object was a big - impact on performance, turns out there's no hit with this class. - """ - - def init(self): - self.bgcolors = [tdl._Color(random.getrandbits(7), - random.getrandbits(7), - random.getrandbits(7)) - for _ in range(256)] - self.fgcolor = tdl._Color(255, 255, 255) - - def updateTest(self, deltaTime): - # getrandbits is around 5x faster than using randint - char = [random.getrandbits(8) for _ in range(self.total)] - for (x,y), char in zip(self.cells, char): - self.console.draw_char(x, y, char, - self.fgcolor, random.choice(self.bgcolors)) - - -class CharOnlyTest(TestApp): - - def updateTest(self, deltaTime): - self.console.clear((255, 255, 255), (0, 0, 0)) - char = [random.getrandbits(8) for _ in range(self.total)] - for (x,y), char in zip(self.cells, char): - self.console.draw_char(x, y, char) - -class TypewriterCharOnlyTest(TestApp): - - def updateTest(self, deltaTime): - self.writer.move(0, 0) - char = [random.getrandbits(8) for _ in range(self.total)] - for (x,y), char in zip(self.cells, char): - self.writer.move(x, y) - self.writer.print_str(chr(char)) - -class ColorOnlyTest(TestApp): - - def updateTest(self, deltaTime): - # getrandbits is around 5x faster than using randint - bgcolors = [(random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) for _ in range(self.total)] - for (x,y), bgcolor in zip(self.cells, bgcolors): - self.console.draw_char(x, y, None, None, bgcolor) - -class GetCharTest(TestApp): - - def init(self): - for (x,y) in self.cells: - bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) - ch = random.getrandbits(8) - self.console.get_char(x, y, ch, bgcolor=bgcolor) - - def updateTest(self, deltaTime): - for (x,y) in self.cells: - self.console.draw_char(x, y, *self.console.get_char(x, y)) - -class SingleRectTest(TestApp): - - def updateTest(self, deltaTime): - bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) - self.console.draw_rect(0, 0, None, None, ' ', (255, 255, 255), bgcolor) - -class DrawStrTest(TestApp): - - def updateTest(self, deltaTime): - for y in range(self.height): - bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) - string = [random.getrandbits(8) for x in range(self.width)] - self.console.draw_str(0, y, string, (255, 255, 255), bgcolor) - -class BlitScrollTest(TestApp): - def updateTest(self, deltaTime): - self.console.scroll(0, 1) - for x in range(self.width): - bgcolor = (random.getrandbits(6), random.getrandbits(6), random.getrandbits(6)) - ch = random.getrandbits(8) - self.console.draw_char(x, 0, ch, bg=bgcolor) - -# match libtcod sample screen -WIDTH = 46 -HEIGHT = 20 -def main(): - console = tdl.init(46, 20, renderer='OPENGL') - for Test in [FullDrawCharTest, CharOnlyTest, TypewriterCharOnlyTest, ColorOnlyTest, GetCharTest, - SingleRectTest, DrawStrTest, BlitScrollTest]: - Test(console).run() - console.clear() - -if __name__ == '__main__': - main() diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index 0755c212..b396d536 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -16,7 +16,7 @@ FONT_PATH = os.path.join(BASE_DIR, "data/terminal8x8_gs_ro.png") -def main(): +def main() -> None: tileset = tcod.tileset.load_tilesheet(FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437) with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index cb70f549..6323407a 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -5,7 +5,7 @@ console = None -def main(): +def main() -> None: tileset = tcod.tileset.load_tilesheet("data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437) with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py index ce66eae0..446a0f42 100755 --- a/examples/distribution/cx_Freeze/setup.py +++ b/examples/distribution/cx_Freeze/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -from cx_Freeze import Executable, setup +from cx_Freeze import Executable, setup # type: ignore # cx_Freeze options, see documentation: # https://cx-freeze.readthedocs.io/en/latest/distutils.html#build-exe diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 71d2e932..6dd94758 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -744,7 +744,7 @@ def on_draw(self) -> None: sample_console, x, y, - tcod.color_lerp( + tcod.color_lerp( # type: ignore LIGHT_GROUND, DARK_GROUND, 0.9 * tcod.dijkstra_get_distance(self.dijk, x, y) / self.dijk_dist, @@ -762,12 +762,12 @@ def on_draw(self) -> None: if self.using_astar: if not tcod.path_is_empty(self.path): tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) - self.px, self.py = tcod.path_walk(self.path, True) + self.px, self.py = tcod.path_walk(self.path, True) # type: ignore tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) else: if not tcod.dijkstra_is_empty(self.dijk): tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) - self.px, self.py = tcod.dijkstra_path_walk(self.dijk) + self.px, self.py = tcod.dijkstra_path_walk(self.dijk) # type: ignore tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) self.recalculate = True diff --git a/parse_sdl2.py b/parse_sdl2.py index 34d2f931..f56ecd24 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -2,7 +2,7 @@ import platform import re import sys -from typing import Iterator +from typing import Any, Dict, Iterator import cffi # type: ignore @@ -130,7 +130,7 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: def add_to_ffi(ffi: cffi.FFI, path: str) -> None: BITS, _ = platform.architecture() - cdef_args = {} + cdef_args: Dict[str, Any] = {} NEEDS_PACK4 = False if sys.platform == "win32" and BITS == "32bit": NEEDS_PACK4 = True @@ -150,7 +150,7 @@ def add_to_ffi(ffi: cffi.FFI, path: str) -> None: ffi.cdef(code, packed=1) continue ffi.cdef(code, **cdef_args) - except: + except Exception: print("Error parsing %r code:\n%s" % (header, code)) raise diff --git a/setup.cfg b/setup.cfg index 935985bd..7ca32566 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,7 @@ ignore = E203 W503 max-line-length = 120 [mypy] -python_version = 3.6 +python_version = 3.8 warn_unused_configs = True disallow_subclassing_any = True disallow_any_generics = True @@ -38,6 +38,7 @@ warn_unused_ignores = True warn_return_any = True implicit_reexport = False strict_equality = True +exclude = (build/|venv/|libtcod/|docs/|distribution/|termbox/|samples_libtcodpy.py) [mypy-numpy] ignore_missing_imports = True diff --git a/setup.py b/setup.py index 11c097fe..bb9617ed 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from subprocess import CalledProcessError, check_output from typing import List -from setuptools import setup +from setuptools import setup # type: ignore SDL_VERSION_NEEDED = (2, 0, 5) diff --git a/tcod/bsp.py b/tcod/bsp.py index 8debd752..ee69b907 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -60,12 +60,12 @@ def __init__(self, x: int, y: int, width: int, height: int): self.width = width self.height = height - self.level = 0 # type: int - self.position = 0 # type: int - self.horizontal = False # type: bool + self.level = 0 + self.position = 0 + self.horizontal = False - self.parent = None # type: Optional['BSP'] - self.children = () # type: Union[Tuple[()], Tuple['BSP', 'BSP']] + self.parent: Optional["BSP"] = None + self.children: Union[Tuple[()], Tuple["BSP", "BSP"]] = () @property def w(self) -> int: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b804abbe..b2310f67 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -547,8 +547,8 @@ def bsp_split_recursive( nb: int, minHSize: int, minVSize: int, - maxHRatio: int, - maxVRatio: int, + maxHRatio: float, + maxVRatio: float, ) -> None: """ .. deprecated:: 2.0 @@ -3477,7 +3477,7 @@ def parser_new() -> Any: @deprecate("Parser functions have been deprecated.") def parser_new_struct(parser: Any, name: str) -> Any: - return lib.TCOD_parser_new_struct(parser, name) + return lib.TCOD_parser_new_struct(parser, _bytes(name)) # prevent multiple threads from messing with def_extern callbacks @@ -3785,45 +3785,45 @@ def random_delete(rnd: tcod.random.Random) -> None: @deprecate("This function is deprecated.") -def struct_add_flag(struct, name): # type: ignore - lib.TCOD_struct_add_flag(struct, name) +def struct_add_flag(struct: Any, name: str) -> None: + lib.TCOD_struct_add_flag(struct, _bytes(name)) @deprecate("This function is deprecated.") -def struct_add_property(struct, name, typ, mandatory): # type: ignore - lib.TCOD_struct_add_property(struct, name, typ, mandatory) +def struct_add_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: + lib.TCOD_struct_add_property(struct, _bytes(name), typ, mandatory) @deprecate("This function is deprecated.") -def struct_add_value_list(struct, name, value_list, mandatory): # type: ignore +def struct_add_value_list(struct: Any, name: str, value_list: Iterable[str], mandatory: bool) -> None: c_strings = [ffi.new("char[]", value.encode("utf-8")) for value in value_list] c_value_list = ffi.new("char*[]", c_strings) lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory) @deprecate("This function is deprecated.") -def struct_add_list_property(struct, name, typ, mandatory): # type: ignore - lib.TCOD_struct_add_list_property(struct, name, typ, mandatory) +def struct_add_list_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: + lib.TCOD_struct_add_list_property(struct, _bytes(name), typ, mandatory) @deprecate("This function is deprecated.") -def struct_add_structure(struct, sub_struct): # type: ignore +def struct_add_structure(struct: Any, sub_struct: Any) -> None: lib.TCOD_struct_add_structure(struct, sub_struct) @deprecate("This function is deprecated.") -def struct_get_name(struct): # type: ignore +def struct_get_name(struct: Any) -> str: return _unpack_char_p(lib.TCOD_struct_get_name(struct)) @deprecate("This function is deprecated.") -def struct_is_mandatory(struct, name): # type: ignore - return lib.TCOD_struct_is_mandatory(struct, name) +def struct_is_mandatory(struct: Any, name: str) -> bool: + return bool(lib.TCOD_struct_is_mandatory(struct, _bytes(name))) @deprecate("This function is deprecated.") -def struct_get_type(struct, name): # type: ignore - return lib.TCOD_struct_get_type(struct, name) +def struct_get_type(struct: Any, name: str) -> int: + return int(lib.TCOD_struct_get_type(struct, _bytes(name))) # high precision time functions diff --git a/tests/common.py b/tests/common.py deleted file mode 100644 index 950049f7..00000000 --- a/tests/common.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - -import tcod - - -def raise_Exception(*args): - raise Exception("testing exception") diff --git a/tests/conftest.py b/tests/conftest.py index 1411d326..da22cd44 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,18 @@ import random import warnings +from typing import Any, Callable, Iterator, Union import pytest import tcod -def pytest_addoption(parser): +def pytest_addoption(parser: Any) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") @pytest.fixture(scope="session", params=["SDL", "SDL2"]) -def session_console(request): +def session_console(request: Any) -> Iterator[tcod.console.Console]: if request.config.getoption("--no-window"): pytest.skip("This test needs a rendering context.") FONT_FILE = "libtcod/terminal.png" @@ -27,83 +28,59 @@ def session_console(request): @pytest.fixture(scope="function") -def console(session_console): +def console(session_console: tcod.console.Console) -> tcod.console.Console: console = session_console tcod.console_flush() with warnings.catch_warnings(): warnings.simplefilter("ignore") - console.default_fg = (255, 255, 255) - console.default_bg = (0, 0, 0) - console.default_blend = tcod.BKGND_SET - console.default_alignment = tcod.LEFT + console.default_fg = (255, 255, 255) # type: ignore + console.default_bg = (0, 0, 0) # type: ignore + console.default_bg_blend = tcod.BKGND_SET # type: ignore + console.default_alignment = tcod.LEFT # type: ignore console.clear() return console @pytest.fixture() -def offscreen(console): +def offscreen(console: tcod.console.Console) -> tcod.console.Console: """Return an off-screen console with the same size as the root console.""" return tcod.console.Console(console.width, console.height) @pytest.fixture() -def fg(): +def fg() -> tcod.Color: return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) @pytest.fixture() -def bg(): +def bg() -> tcod.Color: return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) -try: - unichr -except NameError: - unichr = chr - - -def ch_ascii_int(): +def ch_ascii_int() -> int: return random.randint(0x21, 0x7F) -def ch_ascii_str(): +def ch_ascii_str() -> str: return chr(ch_ascii_int()) -def ch_latin1_int(): +def ch_latin1_int() -> int: return random.randint(0x80, 0xFF) -def ch_latin1_str(): +def ch_latin1_str() -> str: return chr(ch_latin1_int()) -def ch_bmp_int(): - # Basic Multilingual Plane, before surrogates - return random.randint(0x100, 0xD7FF) - - -def ch_bmp_str(): - return unichr(ch_bmp_int()) - - -def ch_smp_int(): - return random.randint(0x10000, 0x1F9FF) - - -def ch_smp_str(): - return unichr(ch_bmp_int()) - - @pytest.fixture( params=[ "ascii_int", "ascii_str", "latin1_int", "latin1_str", - #'bmp_int', 'bmp_str', # causes crashes ] ) -def ch(request): +def ch(request: Any) -> Callable[[], Union[int, str]]: """Test with multiple types of ascii/latin1 characters""" - return globals()["ch_%s" % request.param]() + return globals()["ch_%s" % request.param]() # type: ignore diff --git a/tests/test_console.py b/tests/test_console.py index b25f7477..52ea20d2 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -1,4 +1,5 @@ import pickle +from pathlib import Path import numpy as np import pytest @@ -9,7 +10,7 @@ @pytest.mark.filterwarnings("ignore:Directly access a consoles") @pytest.mark.filterwarnings("ignore:This function may be deprecated in the fu") -def test_array_read_write(): +def test_array_read_write() -> None: console = tcod.console.Console(width=12, height=10) FG = (255, 254, 253) BG = (1, 2, 3) @@ -40,26 +41,26 @@ def test_array_read_write(): @pytest.mark.filterwarnings("ignore") -def test_console_defaults(): +def test_console_defaults() -> None: console = tcod.console.Console(width=12, height=10) - console.default_bg = [2, 3, 4] + console.default_bg = [2, 3, 4] # type: ignore assert console.default_bg == (2, 3, 4) - console.default_fg = (4, 5, 6) + console.default_fg = (4, 5, 6) # type: ignore assert console.default_fg == (4, 5, 6) - console.default_bg_blend = tcod.BKGND_ADD + console.default_bg_blend = tcod.BKGND_ADD # type: ignore assert console.default_bg_blend == tcod.BKGND_ADD - console.default_alignment = tcod.RIGHT + console.default_alignment = tcod.RIGHT # type: ignore assert console.default_alignment == tcod.RIGHT @pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") @pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instea") @pytest.mark.filterwarnings("ignore:.*default values have been deprecated") -def test_console_methods(): +def test_console_methods() -> None: console = tcod.console.Console(width=12, height=10) console.put_char(0, 0, ord("@")) console.print_(0, 0, "Test") @@ -69,12 +70,12 @@ def test_console_methods(): console.hline(0, 1, 10) console.vline(1, 0, 10) console.print_frame(0, 0, 8, 8, "Frame") - console.blit(0, 0, 0, 0, console, 0, 0) - console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) + console.blit(0, 0, 0, 0, console, 0, 0) # type: ignore + console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) # type: ignore console.set_key_color((254, 0, 254)) -def test_console_pickle(): +def test_console_pickle() -> None: console = tcod.console.Console(width=12, height=10) console.ch[...] = ord(".") console.fg[...] = (10, 20, 30) @@ -85,7 +86,7 @@ def test_console_pickle(): assert (console.bg == console2.bg).all() -def test_console_pickle_fortran(): +def test_console_pickle_fortran() -> None: console = tcod.console.Console(2, 3, order="F") console2 = pickle.loads(pickle.dumps(console)) assert console.ch.strides == console2.ch.strides @@ -93,19 +94,19 @@ def test_console_pickle_fortran(): assert console.bg.strides == console2.bg.strides -def test_console_repr(): +def test_console_repr() -> None: array # Needed for eval. eval(repr(tcod.console.Console(10, 2))) @pytest.mark.filterwarnings("ignore") -def test_console_str(): +def test_console_str() -> None: console = tcod.console.Console(10, 2) console.print_(0, 0, "Test") assert str(console) == ("") -def test_console_fortran_buffer(): +def test_console_fortran_buffer() -> None: tcod.console.Console( width=1, height=2, @@ -114,7 +115,7 @@ def test_console_fortran_buffer(): ) -def test_console_clear(): +def test_console_clear() -> None: console = tcod.console.Console(1, 1) assert console.fg[0, 0].tolist() == [255, 255, 255] assert console.bg[0, 0].tolist() == [0, 0, 0] @@ -123,14 +124,14 @@ def test_console_clear(): assert console.bg[0, 0].tolist() == [10, 11, 12] -def test_console_semigraphics(): +def test_console_semigraphics() -> None: console = tcod.console.Console(1, 1) console.draw_semigraphics( [[[255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]], ) -def test_rexpaint(tmp_path) -> None: +def test_rexpaint(tmp_path: Path) -> None: xp_path = tmp_path / "test.xp" consoles = tcod.Console(80, 24, order="F"), tcod.Console(8, 8, order="F") tcod.console.save_xp(xp_path, consoles, compress_level=0) diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index c3440bd4..a514bce4 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +from typing import Any, Callable, Iterator, List, Optional, Tuple, Union + import numpy import numpy as np import pytest @@ -13,27 +15,32 @@ ] -def test_console_behaviour(console): +def test_console_behaviour(console: tcod.Console) -> None: assert not console @pytest.mark.skip("takes too long") @pytest.mark.filterwarnings("ignore") -def test_credits_long(console): +def test_credits_long(console: tcod.console.Console) -> None: libtcodpy.console_credits() -def test_credits(console): +def test_credits(console: tcod.console.Console) -> None: libtcodpy.console_credits_render(0, 0, True) libtcodpy.console_credits_reset() -def assert_char(console, x, y, ch=None, fg=None, bg=None): +def assert_char( + console: tcod.console.Console, + x: int, + y: int, + ch: Optional[Union[str, int]] = None, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None, +) -> None: if ch is not None: - try: + if isinstance(ch, str): ch = ord(ch) - except TypeError: - pass assert console.ch[y, x] == ch if fg is not None: assert (console.fg[y, x] == fg).all() @@ -42,7 +49,7 @@ def assert_char(console, x, y, ch=None, fg=None, bg=None): @pytest.mark.filterwarnings("ignore") -def test_console_defaults(console, fg, bg): +def test_console_defaults(console: tcod.console.Console, fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> None: libtcodpy.console_set_default_foreground(console, fg) libtcodpy.console_set_default_background(console, bg) libtcodpy.console_clear(console) @@ -50,37 +57,39 @@ def test_console_defaults(console, fg, bg): @pytest.mark.filterwarnings("ignore") -def test_console_set_char_background(console, bg): +def test_console_set_char_background(console: tcod.console.Console, bg: Tuple[int, int, int]) -> None: libtcodpy.console_set_char_background(console, 0, 0, bg, libtcodpy.BKGND_SET) assert_char(console, 0, 0, bg=bg) @pytest.mark.filterwarnings("ignore") -def test_console_set_char_foreground(console, fg): +def test_console_set_char_foreground(console: tcod.console.Console, fg: Tuple[int, int, int]) -> None: libtcodpy.console_set_char_foreground(console, 0, 0, fg) assert_char(console, 0, 0, fg=fg) @pytest.mark.filterwarnings("ignore") -def test_console_set_char(console, ch): +def test_console_set_char(console: tcod.console.Console, ch: int) -> None: libtcodpy.console_set_char(console, 0, 0, ch) assert_char(console, 0, 0, ch=ch) @pytest.mark.filterwarnings("ignore") -def test_console_put_char(console, ch): +def test_console_put_char(console: tcod.console.Console, ch: int) -> None: libtcodpy.console_put_char(console, 0, 0, ch, libtcodpy.BKGND_SET) assert_char(console, 0, 0, ch=ch) @pytest.mark.filterwarnings("ignore") -def console_put_char_ex(console, ch, fg, bg): +def console_put_char_ex( + console: tcod.console.Console, ch: int, fg: Tuple[int, int, int], bg: Tuple[int, int, int] +) -> None: libtcodpy.console_put_char_ex(console, 0, 0, ch, fg, bg) assert_char(console, 0, 0, ch=ch, fg=fg, bg=bg) @pytest.mark.filterwarnings("ignore") -def test_console_printing(console, fg, bg): +def test_console_printing(console: tcod.console.Console, fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> None: libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET) assert libtcodpy.console_get_background_flag(console) == libtcodpy.BKGND_SET @@ -100,42 +109,42 @@ def test_console_printing(console, fg, bg): @pytest.mark.filterwarnings("ignore") -def test_console_rect(console): +def test_console_rect(console: tcod.console.Console) -> None: libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) @pytest.mark.filterwarnings("ignore") -def test_console_lines(console): +def test_console_lines(console: tcod.console.Console) -> None: libtcodpy.console_hline(console, 0, 0, 4) libtcodpy.console_vline(console, 0, 0, 4) @pytest.mark.filterwarnings("ignore") -def test_console_print_frame(console): +def test_console_print_frame(console: tcod.console.Console) -> None: libtcodpy.console_print_frame(console, 0, 0, 9, 9) @pytest.mark.filterwarnings("ignore") -def test_console_fade(console): - libtcodpy.console_set_fade(0, libtcodpy.Color(0, 0, 0)) +def test_console_fade(console: tcod.console.Console) -> None: + libtcodpy.console_set_fade(0, (0, 0, 0)) libtcodpy.console_get_fade() libtcodpy.console_get_fading_color() -def assertConsolesEqual(a, b): - return (a.fg[:] == b.fg[:]).all() and (a.bg[:] == b.bg[:]).all() and (a.ch[:] == b.ch[:]).all() +def assertConsolesEqual(a: tcod.console.Console, b: tcod.console.Console) -> bool: + return bool((a.fg[:] == b.fg[:]).all() and (a.bg[:] == b.bg[:]).all() and (a.ch[:] == b.ch[:]).all()) @pytest.mark.filterwarnings("ignore") -def test_console_blit(console, offscreen): +def test_console_blit(console: tcod.console.Console, offscreen: tcod.console.Console) -> None: libtcodpy.console_print(offscreen, 0, 0, "test") libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1) assertConsolesEqual(console, offscreen) - libtcodpy.console_set_key_color(offscreen, libtcodpy.black) + libtcodpy.console_set_key_color(offscreen, (0, 0, 0)) @pytest.mark.filterwarnings("ignore") -def test_console_asc_read_write(console, offscreen, tmpdir): +def test_console_asc_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmpdir: Any) -> None: libtcodpy.console_print(console, 0, 0, "test") asc_file = tmpdir.join("test.asc").strpath @@ -145,7 +154,7 @@ def test_console_asc_read_write(console, offscreen, tmpdir): @pytest.mark.filterwarnings("ignore") -def test_console_apf_read_write(console, offscreen, tmpdir): +def test_console_apf_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmpdir: Any) -> None: libtcodpy.console_print(console, 0, 0, "test") apf_file = tmpdir.join("test.apf").strpath @@ -155,7 +164,7 @@ def test_console_apf_read_write(console, offscreen, tmpdir): @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_load_test_file(console): +def test_console_rexpaint_load_test_file(console: tcod.console.Console) -> None: xp_console = libtcodpy.console_from_xp("libtcod/data/rexpaint/test.xp") assert xp_console assert libtcodpy.console_get_char(xp_console, 0, 0) == ord("T") @@ -166,7 +175,13 @@ def test_console_rexpaint_load_test_file(console): @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): +def test_console_rexpaint_save_load( + console: tcod.console.Console, + tmpdir: Any, + ch: int, + fg: Tuple[int, int, int], + bg: Tuple[int, int, int], +) -> None: libtcodpy.console_print(console, 0, 0, "test") libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) xp_file = tmpdir.join("test.xp").strpath @@ -174,37 +189,39 @@ def test_console_rexpaint_save_load(console, tmpdir, ch, fg, bg): xp_console = libtcodpy.console_from_xp(xp_file) assert xp_console assertConsolesEqual(console, xp_console) - assert libtcodpy.console_load_xp(None, xp_file) + assert libtcodpy.console_load_xp(None, xp_file) # type: ignore assertConsolesEqual(console, xp_console) @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_list_save_load(console, tmpdir): +def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmpdir: Any) -> None: con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) libtcodpy.console_print(con1, 0, 0, "hello") libtcodpy.console_print(con2, 0, 0, "world") xp_file = tmpdir.join("test.xp").strpath assert libtcodpy.console_list_save_xp([con1, con2], xp_file, 1) - for a, b in zip([con1, con2], libtcodpy.console_list_load_xp(xp_file)): + loaded_consoles = libtcodpy.console_list_load_xp(xp_file) + assert loaded_consoles + for a, b in zip([con1, con2], loaded_consoles): assertConsolesEqual(a, b) libtcodpy.console_delete(a) libtcodpy.console_delete(b) @pytest.mark.filterwarnings("ignore") -def test_console_fullscreen(console): +def test_console_fullscreen(console: tcod.console.Console) -> None: libtcodpy.console_set_fullscreen(False) @pytest.mark.filterwarnings("ignore") -def test_console_key_input(console): +def test_console_key_input(console: tcod.console.Console) -> None: libtcodpy.console_check_for_keypress() libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER) @pytest.mark.filterwarnings("ignore") -def test_console_fill_errors(console): +def test_console_fill_errors(console: tcod.console.Console) -> None: with pytest.raises(TypeError): libtcodpy.console_fill_background(console, [0], [], []) with pytest.raises(TypeError): @@ -212,7 +229,7 @@ def test_console_fill_errors(console): @pytest.mark.filterwarnings("ignore") -def test_console_fill(console): +def test_console_fill(console: tcod.console.Console) -> None: width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) fill = [i % 256 for i in range(width * height)] @@ -233,16 +250,16 @@ def test_console_fill(console): @pytest.mark.filterwarnings("ignore") -def test_console_fill_numpy(console): +def test_console_fill_numpy(console: tcod.console.Console) -> None: width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) fill = numpy.zeros((height, width), dtype=numpy.intc) for y in range(height): fill[y, :] = y % 256 - libtcodpy.console_fill_background(console, fill, fill, fill) - libtcodpy.console_fill_foreground(console, fill, fill, fill) - libtcodpy.console_fill_char(console, fill) + libtcodpy.console_fill_background(console, fill, fill, fill) # type: ignore + libtcodpy.console_fill_foreground(console, fill, fill, fill) # type: ignore + libtcodpy.console_fill_char(console, fill) # type: ignore # verify fill bg = numpy.zeros((height, width), dtype=numpy.intc) @@ -260,7 +277,7 @@ def test_console_fill_numpy(console): @pytest.mark.filterwarnings("ignore") -def test_console_buffer(console): +def test_console_buffer(console: tcod.console.Console) -> None: buffer = libtcodpy.ConsoleBuffer( libtcodpy.console_get_width(console), libtcodpy.console_get_height(console), @@ -273,21 +290,21 @@ def test_console_buffer(console): @pytest.mark.filterwarnings("ignore:Console array attributes perform better") -def test_console_buffer_error(console): +def test_console_buffer_error(console: tcod.console.Console) -> None: buffer = libtcodpy.ConsoleBuffer(0, 0) with pytest.raises(ValueError): buffer.blit(console) @pytest.mark.filterwarnings("ignore") -def test_console_font_mapping(console): - libtcodpy.console_map_ascii_code_to_font("@", 1, 1) - libtcodpy.console_map_ascii_codes_to_font("@", 1, 0, 0) +def test_console_font_mapping(console: tcod.console.Console) -> None: + libtcodpy.console_map_ascii_code_to_font(ord("@"), 1, 1) + libtcodpy.console_map_ascii_codes_to_font(ord("@"), 1, 0, 0) libtcodpy.console_map_string_to_font("@", 0, 0) @pytest.mark.filterwarnings("ignore") -def test_mouse(console): +def test_mouse(console: tcod.console.Console) -> None: libtcodpy.mouse_show_cursor(True) libtcodpy.mouse_is_cursor_visible() mouse = libtcodpy.mouse_get_status() @@ -296,7 +313,7 @@ def test_mouse(console): @pytest.mark.filterwarnings("ignore") -def test_sys_time(console): +def test_sys_time(console: tcod.console.Console) -> None: libtcodpy.sys_set_fps(0) libtcodpy.sys_get_fps() libtcodpy.sys_get_last_frame_length() @@ -306,20 +323,19 @@ def test_sys_time(console): @pytest.mark.filterwarnings("ignore") -def test_sys_screenshot(console, tmpdir): +def test_sys_screenshot(console: tcod.console.Console, tmpdir: Any) -> None: libtcodpy.sys_save_screenshot(tmpdir.join("test.png").strpath) @pytest.mark.filterwarnings("ignore") -def test_sys_custom_render(console): +def test_sys_custom_render(console: tcod.console.Console) -> None: if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL: pytest.xfail(reason="Only supports SDL") escape = [] - def sdl_callback(sdl_surface): + def sdl_callback(sdl_surface: Any) -> None: escape.append(True) - libtcodpy.console_set_dirty(0, 0, 0, 0) libtcodpy.sys_register_SDL_renderer(sdl_callback) libtcodpy.console_flush() @@ -327,21 +343,21 @@ def sdl_callback(sdl_surface): @pytest.mark.filterwarnings("ignore") -def test_image(console, tmpdir): +def test_image(console: tcod.console.Console, tmpdir: Any) -> None: img = libtcodpy.image_new(16, 16) - libtcodpy.image_clear(img, libtcodpy.Color(0, 0, 0)) + libtcodpy.image_clear(img, (0, 0, 0)) libtcodpy.image_invert(img) libtcodpy.image_hflip(img) libtcodpy.image_rotate90(img) libtcodpy.image_vflip(img) libtcodpy.image_scale(img, 24, 24) - libtcodpy.image_set_key_color(img, libtcodpy.Color(255, 255, 255)) + libtcodpy.image_set_key_color(img, (255, 255, 255)) libtcodpy.image_get_alpha(img, 0, 0) libtcodpy.image_is_pixel_transparent(img, 0, 0) libtcodpy.image_get_size(img) libtcodpy.image_get_pixel(img, 0, 0) libtcodpy.image_get_mipmap_pixel(img, 0, 0, 1, 1) - libtcodpy.image_put_pixel(img, 0, 0, libtcodpy.Color(255, 255, 255)) + libtcodpy.image_put_pixel(img, 0, 0, (255, 255, 255)) libtcodpy.image_blit(img, console, 0, 0, libtcodpy.BKGND_SET, 1, 1, 0) libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16, libtcodpy.BKGND_SET) libtcodpy.image_blit_2x(img, console, 0, 0) @@ -358,7 +374,7 @@ def test_image(console, tmpdir): @pytest.mark.parametrize("sample", ["@", "\u2603"]) # Unicode snowman @pytest.mark.xfail(reason="Unreliable") @pytest.mark.filterwarnings("ignore") -def test_clipboard(console, sample): +def test_clipboard(console: tcod.console.Console, sample: str) -> None: saved = libtcodpy.sys_clipboard_get() try: libtcodpy.sys_clipboard_set(sample) @@ -374,7 +390,7 @@ def test_clipboard(console, sample): @pytest.mark.filterwarnings("ignore") -def test_line_step(): +def test_line_step() -> None: """ libtcodpy.line_init and libtcodpy.line_step """ @@ -385,16 +401,16 @@ def test_line_step(): @pytest.mark.filterwarnings("ignore") -def test_line(): +def test_line() -> None: """ tests normal use, lazy evaluation, and error propagation """ # test normal results - test_result = [] + test_result: List[Tuple[int, int]] = [] - def line_test(*test_xy): - test_result.append(test_xy) - return 1 + def line_test(x: int, y: int) -> bool: + test_result.append((x, y)) + return True assert libtcodpy.line(*LINE_ARGS, py_callback=line_test) == 1 assert test_result == INCLUSIVE_RESULTS @@ -402,8 +418,8 @@ def line_test(*test_xy): # test lazy evaluation test_result = [] - def return_false(*test_xy): - test_result.append(test_xy) + def return_false(x: int, y: int) -> bool: + test_result.append((x, y)) return False assert libtcodpy.line(*LINE_ARGS, py_callback=return_false) == 0 @@ -411,7 +427,7 @@ def return_false(*test_xy): @pytest.mark.filterwarnings("ignore") -def test_line_iter(): +def test_line_iter() -> None: """ libtcodpy.line_iter """ @@ -419,14 +435,14 @@ def test_line_iter(): @pytest.mark.filterwarnings("ignore") -def test_bsp(): +def test_bsp() -> None: """ commented out statements work in libtcod-cffi """ bsp = libtcodpy.bsp_new_with_size(0, 0, 64, 64) repr(bsp) # test __repr__ on leaf libtcodpy.bsp_resize(bsp, 0, 0, 32, 32) - assert bsp != None + assert bsp is not None # test getter/setters bsp.x = bsp.x @@ -464,8 +480,8 @@ def test_bsp(): libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0) # cover bsp_traverse - def traverse(node, user_data): - return True + def traverse(node: tcod.bsp.BSP, user_data: Any) -> None: + return None libtcodpy.bsp_traverse_pre_order(bsp, traverse) libtcodpy.bsp_traverse_in_order(bsp, traverse) @@ -482,7 +498,7 @@ def traverse(node, user_data): @pytest.mark.filterwarnings("ignore") -def test_map(): +def test_map() -> None: map = libtcodpy.map_new(16, 16) assert libtcodpy.map_get_width(map) == 16 assert libtcodpy.map_get_height(map) == 16 @@ -496,7 +512,7 @@ def test_map(): @pytest.mark.filterwarnings("ignore") -def test_color(): +def test_color() -> None: color_a = libtcodpy.Color(0, 1, 2) assert list(color_a) == [0, 1, 2] assert color_a[0] == color_a.r @@ -512,20 +528,20 @@ def test_color(): color_b = libtcodpy.Color(255, 255, 255) assert color_a != color_b - color = libtcodpy.color_lerp(color_a, color_b, 0.5) + color = libtcodpy.color_lerp(color_a, color_b, 0.5) # type: ignore libtcodpy.color_set_hsv(color, 0, 0, 0) - libtcodpy.color_get_hsv(color) + libtcodpy.color_get_hsv(color) # type: ignore libtcodpy.color_scale_HSV(color, 0, 0) -def test_color_repr(): +def test_color_repr() -> None: Color = libtcodpy.Color col = Color(0, 1, 2) assert eval(repr(col)) == col @pytest.mark.filterwarnings("ignore") -def test_color_math(): +def test_color_math() -> None: color_a = libtcodpy.Color(0, 1, 2) color_b = libtcodpy.Color(0, 10, 20) @@ -536,14 +552,14 @@ def test_color_math(): @pytest.mark.filterwarnings("ignore") -def test_color_gen_map(): +def test_color_gen_map() -> None: colors = libtcodpy.color_gen_map([(0, 0, 0), (255, 255, 255)], [0, 8]) assert colors[0] == libtcodpy.Color(0, 0, 0) assert colors[-1] == libtcodpy.Color(255, 255, 255) @pytest.mark.filterwarnings("ignore") -def test_namegen_parse(): +def test_namegen_parse() -> None: libtcodpy.namegen_parse("libtcod/data/namegen/jice_celtic.cfg") assert libtcodpy.namegen_generate("Celtic female") assert libtcodpy.namegen_get_sets() @@ -551,7 +567,7 @@ def test_namegen_parse(): @pytest.mark.filterwarnings("ignore") -def test_noise(): +def test_noise() -> None: noise = libtcodpy.noise_new(1) libtcodpy.noise_set_type(noise, libtcodpy.NOISE_SIMPLEX) libtcodpy.noise_get(noise, [0]) @@ -561,7 +577,7 @@ def test_noise(): @pytest.mark.filterwarnings("ignore") -def test_random(): +def test_random() -> None: rand = libtcodpy.random_get_instance() rand = libtcodpy.random_new() libtcodpy.random_delete(rand) @@ -582,7 +598,7 @@ def test_random(): @pytest.mark.filterwarnings("ignore") -def test_heightmap(): +def test_heightmap() -> None: hmap = libtcodpy.heightmap_new(16, 16) repr(hmap) noise = libtcodpy.noise_new(2) @@ -607,7 +623,7 @@ def test_heightmap(): libtcodpy.heightmap_add_voronoi(hmap, 10, 3, [1, 3, 5]) libtcodpy.heightmap_add_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) libtcodpy.heightmap_scale_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) - libtcodpy.heightmap_dig_bezier(hmap, [0, 16, 16, 0], [0, 0, 16, 16], 1, 1, 1, 1) + libtcodpy.heightmap_dig_bezier(hmap, (0, 16, 16, 0), (0, 0, 16, 16), 1, 1, 1, 1) # read data libtcodpy.heightmap_get_value(hmap, 0, 0) @@ -623,18 +639,21 @@ def test_heightmap(): libtcodpy.heightmap_delete(hmap) -MAP = ( - "############", - "# ### #", - "# ### #", - "# ### ####", - "## #### # ##", - "## ####", - "############", +MAP = np.array( + [ + list(line) + for line in ( + "############", + "# ### #", + "# ### #", + "# ### ####", + "## #### # ##", + "## ####", + "############", + ) + ] ) -MAP = np.array([list(line) for line in MAP]) - MAP_HEIGHT, MAP_WIDTH = MAP.shape POINT_A = (2, 2) @@ -646,7 +665,7 @@ def test_heightmap(): @pytest.fixture() -def map_(): +def map_() -> Iterator[tcod.map.Map]: map_ = tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) map_.walkable[...] = map_.transparent[...] = MAP[...] == " " yield map_ @@ -654,22 +673,22 @@ def map_(): @pytest.fixture() -def path_callback(map_): - def callback(ox, oy, dx, dy, user_data): +def path_callback(map_: tcod.map.Map) -> Callable[[int, int, int, int, None], bool]: + def callback(ox: int, oy: int, dx: int, dy: int, user_data: None) -> bool: if map_.walkable[dy, dx]: - return 1 - return 0 + return True + return False return callback @pytest.mark.filterwarnings("ignore") -def test_map_fov(map_): +def test_map_fov(map_: tcod.map.Map) -> None: libtcodpy.map_compute_fov(map_, *POINT_A) @pytest.mark.filterwarnings("ignore") -def test_astar(map_): +def test_astar(map_: tcod.map.Map) -> None: astar = libtcodpy.path_new_using_map(map_) assert not libtcodpy.path_compute(astar, *POINTS_AC) @@ -685,6 +704,9 @@ def test_astar(map_): assert libtcodpy.path_size(astar) > 0 assert not libtcodpy.path_is_empty(astar) + x: Optional[int] + y: Optional[int] + for i in range(libtcodpy.path_size(astar)): x, y = libtcodpy.path_get(astar, i) @@ -695,7 +717,7 @@ def test_astar(map_): @pytest.mark.filterwarnings("ignore") -def test_astar_callback(map_, path_callback): +def test_astar_callback(map_: tcod.map.Map, path_callback: Callable[[int, int, int, int, Any], bool]) -> None: astar = libtcodpy.path_new_using_function( libtcodpy.map_get_width(map_), libtcodpy.map_get_height(map_), @@ -706,7 +728,7 @@ def test_astar_callback(map_, path_callback): @pytest.mark.filterwarnings("ignore") -def test_dijkstra(map_): +def test_dijkstra(map_: tcod.map.Map) -> None: path = libtcodpy.dijkstra_new(map_) libtcodpy.dijkstra_compute(path, *POINT_A) @@ -720,6 +742,9 @@ def test_dijkstra(map_): libtcodpy.dijkstra_reverse(path) + x: Optional[int] + y: Optional[int] + for i in range(libtcodpy.dijkstra_size(path)): x, y = libtcodpy.dijkstra_get(path, i) @@ -730,7 +755,7 @@ def test_dijkstra(map_): @pytest.mark.filterwarnings("ignore") -def test_dijkstra_callback(map_, path_callback): +def test_dijkstra_callback(map_: tcod.map.Map, path_callback: Callable[[int, int, int, int, Any], bool]) -> None: path = libtcodpy.dijkstra_new_using_function( libtcodpy.map_get_width(map_), libtcodpy.map_get_height(map_), @@ -741,7 +766,7 @@ def test_dijkstra_callback(map_, path_callback): @pytest.mark.filterwarnings("ignore") -def test_alpha_blend(console): +def test_alpha_blend(console: tcod.console.Console) -> None: for i in range(256): libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ALPHA(i)) libtcodpy.console_put_char(console, 0, 0, "x", libtcodpy.BKGND_ADDALPHA(i)) diff --git a/tests/test_parser.py b/tests/test_parser.py index 0973312a..2e0ef5ed 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os +from typing import Any import pytest @@ -8,60 +9,54 @@ @pytest.mark.filterwarnings("ignore") -def test_parser(): +def test_parser() -> None: print("***** File Parser test *****") parser = libtcod.parser_new() - struct = libtcod.parser_new_struct(parser, b"myStruct") - libtcod.struct_add_property(struct, b"bool_field", libtcod.TYPE_BOOL, True) - libtcod.struct_add_property(struct, b"char_field", libtcod.TYPE_CHAR, True) - libtcod.struct_add_property(struct, b"int_field", libtcod.TYPE_INT, True) - libtcod.struct_add_property(struct, b"float_field", libtcod.TYPE_FLOAT, True) - libtcod.struct_add_property(struct, b"color_field", libtcod.TYPE_COLOR, True) - libtcod.struct_add_property(struct, b"dice_field", libtcod.TYPE_DICE, True) - libtcod.struct_add_property(struct, b"string_field", libtcod.TYPE_STRING, True) - libtcod.struct_add_list_property(struct, b"bool_list", libtcod.TYPE_BOOL, True) - libtcod.struct_add_list_property(struct, b"char_list", libtcod.TYPE_CHAR, True) - libtcod.struct_add_list_property(struct, b"integer_list", libtcod.TYPE_INT, True) - libtcod.struct_add_list_property(struct, b"float_list", libtcod.TYPE_FLOAT, True) - libtcod.struct_add_list_property(struct, b"string_list", libtcod.TYPE_STRING, True) - libtcod.struct_add_list_property(struct, b"color_list", libtcod.TYPE_COLOR, True) - ## # dice lists doesn't work yet - ## libtcod.struct_add_list_property(struct, b'dice_list', libtcod.TYPE_DICE, - ## True) + struct = libtcod.parser_new_struct(parser, "myStruct") + libtcod.struct_add_property(struct, "bool_field", libtcod.TYPE_BOOL, True) + libtcod.struct_add_property(struct, "char_field", libtcod.TYPE_CHAR, True) + libtcod.struct_add_property(struct, "int_field", libtcod.TYPE_INT, True) + libtcod.struct_add_property(struct, "float_field", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_property(struct, "color_field", libtcod.TYPE_COLOR, True) + libtcod.struct_add_property(struct, "dice_field", libtcod.TYPE_DICE, True) + libtcod.struct_add_property(struct, "string_field", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, "bool_list", libtcod.TYPE_BOOL, True) + libtcod.struct_add_list_property(struct, "char_list", libtcod.TYPE_CHAR, True) + libtcod.struct_add_list_property(struct, "integer_list", libtcod.TYPE_INT, True) + libtcod.struct_add_list_property(struct, "float_list", libtcod.TYPE_FLOAT, True) + libtcod.struct_add_list_property(struct, "string_list", libtcod.TYPE_STRING, True) + libtcod.struct_add_list_property(struct, "color_list", libtcod.TYPE_COLOR, True) # default listener print("***** Default listener *****") libtcod.parser_run(parser, os.path.join("libtcod", "data", "cfg", "sample.cfg")) - print("bool_field : ", libtcod.parser_get_bool_property(parser, b"myStruct.bool_field")) - print("char_field : ", libtcod.parser_get_char_property(parser, b"myStruct.char_field")) - print("int_field : ", libtcod.parser_get_int_property(parser, b"myStruct.int_field")) - print("float_field : ", libtcod.parser_get_float_property(parser, b"myStruct.float_field")) - print("color_field : ", libtcod.parser_get_color_property(parser, b"myStruct.color_field")) - print("dice_field : ", libtcod.parser_get_dice_property(parser, b"myStruct.dice_field")) - print("string_field : ", libtcod.parser_get_string_property(parser, b"myStruct.string_field")) - print("bool_list : ", libtcod.parser_get_list_property(parser, b"myStruct.bool_list", libtcod.TYPE_BOOL)) - print("char_list : ", libtcod.parser_get_list_property(parser, b"myStruct.char_list", libtcod.TYPE_CHAR)) - print("integer_list : ", libtcod.parser_get_list_property(parser, b"myStruct.integer_list", libtcod.TYPE_INT)) - print("float_list : ", libtcod.parser_get_list_property(parser, b"myStruct.float_list", libtcod.TYPE_FLOAT)) - print("string_list : ", libtcod.parser_get_list_property(parser, b"myStruct.string_list", libtcod.TYPE_STRING)) - print("color_list : ", libtcod.parser_get_list_property(parser, b"myStruct.color_list", libtcod.TYPE_COLOR)) - ## print ('dice_list : ', \ - ## libtcod.parser_get_list_property(parser, b'myStruct.dice_list', - ## libtcod.TYPE_DICE)) + print("bool_field : ", libtcod.parser_get_bool_property(parser, "myStruct.bool_field")) + print("char_field : ", libtcod.parser_get_char_property(parser, "myStruct.char_field")) + print("int_field : ", libtcod.parser_get_int_property(parser, "myStruct.int_field")) + print("float_field : ", libtcod.parser_get_float_property(parser, "myStruct.float_field")) + print("color_field : ", libtcod.parser_get_color_property(parser, "myStruct.color_field")) + print("dice_field : ", libtcod.parser_get_dice_property(parser, "myStruct.dice_field")) + print("string_field : ", libtcod.parser_get_string_property(parser, "myStruct.string_field")) + print("bool_list : ", libtcod.parser_get_list_property(parser, "myStruct.bool_list", libtcod.TYPE_BOOL)) + print("char_list : ", libtcod.parser_get_list_property(parser, "myStruct.char_list", libtcod.TYPE_CHAR)) + print("integer_list : ", libtcod.parser_get_list_property(parser, "myStruct.integer_list", libtcod.TYPE_INT)) + print("float_list : ", libtcod.parser_get_list_property(parser, "myStruct.float_list", libtcod.TYPE_FLOAT)) + print("string_list : ", libtcod.parser_get_list_property(parser, "myStruct.string_list", libtcod.TYPE_STRING)) + print("color_list : ", libtcod.parser_get_list_property(parser, "myStruct.color_list", libtcod.TYPE_COLOR)) # custom listener print("***** Custom listener *****") class MyListener: - def new_struct(self, struct, name): + def new_struct(self, struct: Any, name: str) -> bool: print("new structure type", libtcod.struct_get_name(struct), " named ", name) return True - def new_flag(self, name): + def new_flag(self, name: str) -> bool: print("new flag named ", name) return True - def new_property(self, name, typ, value): + def new_property(self, name: str, typ: int, value: Any) -> bool: type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"] type_name = type_names[typ & 0xFF] if typ & libtcod.TYPE_LIST: @@ -69,11 +64,11 @@ def new_property(self, name, typ, value): print("new property named ", name, " type ", type_name, " value ", value) return True - def end_struct(self, struct, name): + def end_struct(self, struct: Any, name: str) -> bool: print("end structure type", libtcod.struct_get_name(struct), " named ", name) return True - def error(self, msg): + def error(self, msg: str) -> bool: print("error : ", msg) return True diff --git a/tests/test_random.py b/tests/test_random.py index 4e889e7d..e0a33a3a 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -4,7 +4,7 @@ import tcod -def test_tcod_random(): +def test_tcod_random() -> None: rand = tcod.random.Random(tcod.random.COMPLEMENTARY_MULTIPLY_WITH_CARRY) assert 0 <= rand.randint(0, 100) <= 100 assert 0 <= rand.uniform(0, 100) <= 100 @@ -12,7 +12,7 @@ def test_tcod_random(): rand.inverse_guass(0, 1) -def test_tcod_random_copy(): +def test_tcod_random_copy() -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER) rand2 = copy.copy(rand) assert rand.uniform(0, 1) == rand2.uniform(0, 1) @@ -20,7 +20,7 @@ def test_tcod_random_copy(): assert rand.uniform(0, 1) == rand2.uniform(0, 1) -def test_tcod_random_pickle(): +def test_tcod_random_pickle() -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER) rand2 = pickle.loads(pickle.dumps(rand)) assert rand.uniform(0, 1) == rand2.uniform(0, 1) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 8365053c..7780f5d5 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -2,26 +2,34 @@ import copy import pickle +from typing import Any, NoReturn import numpy as np import pytest -from common import raise_Exception, tcod -import tcod.noise -import tcod.path +import tcod +try: + from numpy.typing import DTypeLike +except ImportError: # Python < 3.7 + from typing import Any as DTypeLike -def test_line_error(): + +def raise_Exception(*args: Any) -> NoReturn: + raise RuntimeError("testing exception") + + +def test_line_error() -> None: """ test exception propagation """ - with pytest.raises(Exception): - tcod.line(*LINE_ARGS, py_callback=raise_Exception) + with pytest.raises(RuntimeError): + tcod.line(0, 0, 10, 10, py_callback=raise_Exception) @pytest.mark.filterwarnings("ignore:Iterate over nodes using") @pytest.mark.filterwarnings("ignore:Use pre_order method instead of walk.") -def test_tcod_bsp(): +def test_tcod_bsp() -> None: """ test tcod additions to BSP """ @@ -31,14 +39,14 @@ def test_tcod_bsp(): assert not bsp.horizontal assert not bsp.children - with pytest.raises(Exception): + with pytest.raises(RuntimeError): tcod.bsp_traverse_pre_order(bsp, raise_Exception) bsp.split_recursive(3, 4, 4, 1, 1) for node in bsp.walk(): assert isinstance(node, tcod.bsp.BSP) - assert bsp != "asd" + assert bsp.children # test that operations on deep BSP nodes preserve depth sub_bsp = bsp.children[0] @@ -55,36 +63,36 @@ def test_tcod_bsp(): @pytest.mark.filterwarnings("ignore:Use map.+ to check for this") @pytest.mark.filterwarnings("ignore:This class may perform poorly") -def test_tcod_map_set_bits(): +def test_tcod_map_set_bits() -> None: map_ = tcod.map.Map(2, 2) - assert map_.transparent[:].any() == False - assert map_.walkable[:].any() == False - assert map_.fov[:].any() == False + assert not map_.transparent[:].any() + assert not map_.walkable[:].any() + assert not map_.fov[:].any() map_.transparent[1, 0] = True - assert tcod.map_is_transparent(map_, 0, 1) == True + assert tcod.map_is_transparent(map_, 0, 1) map_.walkable[1, 0] = True - assert tcod.map_is_walkable(map_, 0, 1) == True + assert tcod.map_is_walkable(map_, 0, 1) map_.fov[1, 0] = True - assert tcod.map_is_in_fov(map_, 0, 1) == True + assert tcod.map_is_in_fov(map_, 0, 1) @pytest.mark.filterwarnings("ignore:This class may perform poorly") -def test_tcod_map_get_bits(): +def test_tcod_map_get_bits() -> None: map_ = tcod.map.Map(2, 2) map_.transparent[0] @pytest.mark.filterwarnings("ignore:This class may perform poorly") -def test_tcod_map_copy(): +def test_tcod_map_copy() -> None: map_ = tcod.map.Map(3, 3) map_.transparent[:] = True assert map_.transparent.tolist() == copy.copy(map_).transparent.tolist() @pytest.mark.filterwarnings("ignore:This class may perform poorly") -def test_tcod_map_pickle(): +def test_tcod_map_pickle() -> None: map_ = tcod.map.Map(3, 3) map_.transparent[:] = True map2 = pickle.loads(pickle.dumps(copy.copy(map_))) @@ -92,17 +100,17 @@ def test_tcod_map_pickle(): @pytest.mark.filterwarnings("ignore:This class may perform poorly") -def test_tcod_map_pickle_fortran(): +def test_tcod_map_pickle_fortran() -> None: map_ = tcod.map.Map(2, 3, order="F") - map2 = pickle.loads(pickle.dumps(copy.copy(map_))) - assert map_._Map__buffer.strides == map2._Map__buffer.strides + map2: tcod.map.Map = pickle.loads(pickle.dumps(copy.copy(map_))) + assert map_._Map__buffer.strides == map2._Map__buffer.strides # type: ignore assert map_.transparent.strides == map2.transparent.strides assert map_.walkable.strides == map2.walkable.strides assert map_.fov.strides == map2.fov.strides @pytest.mark.filterwarnings("ignore") -def test_color_class(): +def test_color_class() -> None: assert tcod.black == tcod.black assert tcod.black == (0, 0, 0) assert tcod.black == [0, 0, 0] @@ -111,17 +119,16 @@ def test_color_class(): assert tcod.white * tcod.black == tcod.black assert tcod.white - tcod.white == tcod.black assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) - assert not tcod.black == None color = tcod.Color() - color.r = 1 - color.g = 2 - color.b = 3 + color.r = 1 # type: ignore + color.g = 2 # type: ignore + color.b = 3 # type: ignore assert color == (1, 2, 3) @pytest.mark.parametrize("dtype", [np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32, np.float32]) -def test_path_numpy(dtype): +def test_path_numpy(dtype: DTypeLike) -> None: map_np = np.ones((6, 6), dtype=dtype) map_np[1:4, 1:4] = 0 @@ -142,18 +149,18 @@ def test_path_numpy(dtype): tcod.path.AStar(np.ones((2, 2), dtype=np.float64)) -def path_cost(this_x, this_y, dest_x, dest_y): - return 1 +def path_cost(this_x: int, this_y: int, dest_x: int, dest_y: int) -> bool: + return True -def test_path_callback(): +def test_path_callback() -> None: astar = tcod.path.AStar(tcod.path.EdgeCostCallback(path_cost, (10, 10))) astar = pickle.loads(pickle.dumps(astar)) assert astar.get_path(0, 0, 5, 0) == [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] repr(astar) # cover __repr__ methods -def test_key_repr(): +def test_key_repr() -> None: Key = tcod.Key key = Key(vk=1, c=2, shift=True) assert key.vk == 1 @@ -165,7 +172,7 @@ def test_key_repr(): assert key.shift == key_copy.shift -def test_mouse_repr(): +def test_mouse_repr() -> None: Mouse = tcod.Mouse mouse = Mouse(x=1, lbutton=True) mouse_copy = eval(repr(mouse)) @@ -173,19 +180,19 @@ def test_mouse_repr(): assert mouse.lbutton == mouse_copy.lbutton -def test_cffi_structs(): +def test_cffi_structs() -> None: # Make sure cffi structures are the correct size. tcod.ffi.new("SDL_Event*") tcod.ffi.new("SDL_AudioCVT*") @pytest.mark.filterwarnings("ignore") -def test_recommended_size(console): +def test_recommended_size(console: tcod.console.Console) -> None: tcod.console.recommended_size() @pytest.mark.filterwarnings("ignore") -def test_context(): +def test_context() -> None: with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): pass WIDTH, HEIGHT = 16, 4 diff --git a/tests/test_testing.py b/tests/test_testing.py index 727ad863..027da622 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -2,8 +2,6 @@ import os -import tcod as libtcod - curdir = os.path.dirname(__file__) FONT_FILE = os.path.join(curdir, "data/fonts/consolas10x10_gs_tc.png") From 0bee02484c68152ea6a00a221d422e32b370845e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Jun 2021 23:21:17 -0700 Subject: [PATCH 0539/1101] Add frame decoration options and deprecate older handling of indexes. --- CHANGELOG.rst | 7 +++++ tcod/console.py | 74 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fcb5f275..1092f13d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,13 @@ v2.0.0 Unreleased ------------------ +Added + - Added the *decoration* parameter to *Console.draw_frame*. + You may use this parameter to designate custom glyphs as the frame border. + +Deprecated + - The handling of negative indexes given to console drawing and printing + functions will be changed to be used as absolute coordinates in the future. 12.5.1 - 2021-05-30 ------------------- diff --git a/tcod/console.py b/tcod/console.py index 584d2407..e5625aa0 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -6,7 +6,7 @@ import os import pathlib import warnings -from typing import Any, Iterable, Optional, Tuple, Union +from typing import Any, Iterable, Optional, Sequence, Tuple, Union import numpy as np from typing_extensions import Literal @@ -925,8 +925,9 @@ def __str__(self) -> str: def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: if __debug__ and (x < 0 or y < 0): warnings.warn( - "How negative indexes are handled my change in the future.", - PendingDeprecationWarning, + "Negative indexes will be treated as absolute coordinates instead of relative in the future." + " The current behavior of indexing from the end is deprecated.", + FutureWarning, stacklevel=3, ) if x < 0: @@ -948,9 +949,7 @@ def print( """Print a string on a console with manual line breaks. `x` and `y` are the starting tile, with ``0,0`` as the upper-left - corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner, but this behavior - may change in future versions. + corner of the console. `string` is a Unicode string which may include color control characters. Strings which are too long will be truncated until the @@ -999,9 +998,7 @@ def print_box( """Print a string constrained to a rectangle and return the height. `x` and `y` are the starting tile, with ``0,0`` as the upper-left - corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner, but this behavior - may change in future versions. + corner of the console. `width` and `height` determine the bounds of the rectangle, the text will automatically be word-wrapped to fit within these bounds. @@ -1054,13 +1051,13 @@ def draw_frame( fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None, bg_blend: int = tcod.constants.BKGND_SET, + *, + decoration: Union[str, Tuple[int, int, int, int, int, int, int, int, int]] = "┌─┐│ │└─┘", ) -> None: """Draw a framed rectangle with an optional title. `x` and `y` are the starting tile, with ``0,0`` as the upper-left - corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner, but this behavior - may change in future versions. + corner of the console. `width` and `height` determine the size of the frame. @@ -1076,21 +1073,60 @@ def draw_frame( `bg_blend` is the blend type used by libtcod. + `decoration` is a sequence of glyphs to use for rendering the borders. + This a str or tuple of int's with 9 items with the items arranged in + row-major order. + If a `decoration` is given then `title` can not be used because the + style for `title` is hard-coded. You can easily print along the upper + or lower border of the frame manually. + .. versionadded:: 8.5 .. versionchanged:: 9.0 `fg` and `bg` now default to `None` instead of white-on-black. + + .. versionchanged:: 12.6 + Added `decoration` parameter. """ x, y = self._pythonic_index(x, y) - title_ = title.encode("utf-8") # type: bytes - lib.TCOD_console_printn_frame( - self.console_c, + if title and decoration != "┌─┐│ │└─┘": + raise TypeError( + "The title and decoration parameters are mutually exclusive. You should print the title manually." + ) + if title: + warnings.warn( + "The title parameter will be removed in the future since the style is hard-coded.", + PendingDeprecationWarning, + stacklevel=2, + ) + title_ = title.encode("utf-8") # type: bytes + lib.TCOD_console_printn_frame( + self.console_c, + x, + y, + width, + height, + len(title_), + title_, + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + clear, + ) + return + decoration_: Sequence[int] + if isinstance(decoration, str): + decoration_ = [ord(c) for c in decoration] + else: + decoration_ = decoration + if len(decoration_) != 9: + raise TypeError(f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)") + lib.TCOD_console_draw_frame_rgb( x, y, width, height, - len(title_), - title_, + decoration_, (fg,) if fg is not None else ffi.NULL, (bg,) if bg is not None else ffi.NULL, bg_blend, @@ -1111,9 +1147,7 @@ def draw_rect( """Draw characters and colors over a rectangular region. `x` and `y` are the starting tile, with ``0,0`` as the upper-left - corner of the console. You can use negative numbers if you want to - start printing relative to the bottom-right corner, but this behavior - may change in future versions. + corner of the console. `width` and `height` determine the size of the rectangle. From dc1f29d938e045e1a2f49e9642d76b1c53937b9a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 7 Jun 2021 15:58:43 -0700 Subject: [PATCH 0540/1101] Fix draw frame, update console __str__ and add tests. Tests using print(console) will now replace the space character instead of leaving blank spaces which will mess up doc tests. --- tcod/console.py | 53 +++++++++++++++++++++++++++++++------------ tests/test_console.py | 15 +++++++++++- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index e5625aa0..1ed8b4d5 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -79,13 +79,13 @@ class Console: ... dtype=tcod.console.Console.DTYPE, ... order="F", ... ) - >>> buffer["ch"] = ord(' ') + >>> buffer["ch"] = ord('_') >>> buffer["ch"][:, 1] = ord('x') >>> c = tcod.console.Console(20, 3, order="F", buffer=buffer) >>> print(c) - < | - |xxxxxxxxxxxxxxxxxxxx| - | > + <____________________ + xxxxxxxxxxxxxxxxxxxx + ____________________> .. versionadded:: 8.5 @@ -920,7 +920,7 @@ def __repr__(self) -> str: def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" - return "<%s>" % "|\n|".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) + return "<%s>" % "\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: if __debug__ and (x < 0 or y < 0): @@ -1087,6 +1087,26 @@ def draw_frame( .. versionchanged:: 12.6 Added `decoration` parameter. + + Example:: + + >>> console = tcod.Console(12, 6) + >>> console.draw_frame(x=0, y=0, width=3, height=3) + >>> console.draw_frame(x=3, y=0, width=3, height=3, decoration="╔═╗║ ║╚═╝") + >>> console.draw_frame(x=6, y=0, width=3, height=3, decoration="123456789") + >>> console.draw_frame(x=9, y=0, width=3, height=3, decoration="/-\\| |\\-/") + >>> console.draw_frame(x=0, y=3, width=12, height=3) + >>> console.print_box(x=0, y=3, width=12, height=1, string=" Title ", alignment=tcod.CENTER) + 1 + >>> console.print_box(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=tcod.CENTER) + 1 + >>> print(console) + <┌─┐╔═╗123/-\\ + │ │║ ║456| | + └─┘╚═╝789\\-/ + ┌─ Title ──┐ + │ │ + └─┤Lower├──┘> """ x, y = self._pythonic_index(x, y) if title and decoration != "┌─┐│ │└─┘": @@ -1121,16 +1141,19 @@ def draw_frame( decoration_ = decoration if len(decoration_) != 9: raise TypeError(f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)") - lib.TCOD_console_draw_frame_rgb( - x, - y, - width, - height, - decoration_, - (fg,) if fg is not None else ffi.NULL, - (bg,) if bg is not None else ffi.NULL, - bg_blend, - clear, + _check( + lib.TCOD_console_draw_frame_rgb( + self.console_c, + x, + y, + width, + height, + decoration_, + (fg,) if fg is not None else ffi.NULL, + (bg,) if bg is not None else ffi.NULL, + bg_blend, + clear, + ) ) def draw_rect( diff --git a/tests/test_console.py b/tests/test_console.py index 52ea20d2..e4f15ef7 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -102,8 +102,9 @@ def test_console_repr() -> None: @pytest.mark.filterwarnings("ignore") def test_console_str() -> None: console = tcod.console.Console(10, 2) + console.ch[:] = ord(".") console.print_(0, 0, "Test") - assert str(console) == ("") + assert str(console) == ("") def test_console_fortran_buffer() -> None: @@ -142,3 +143,15 @@ def test_rexpaint(tmp_path: Path) -> None: assert consoles[1].rgb.shape == loaded[1].rgb.shape with pytest.raises(FileNotFoundError): tcod.console.load_xp(tmp_path / "non_existant") + + +def test_draw_frame() -> None: + console = tcod.Console(3, 3, order="C") + with pytest.raises(TypeError): + console.draw_frame(0, 0, 3, 3, title="test", decoration="123456789") + with pytest.raises(TypeError): + console.draw_frame(0, 0, 3, 3, decoration="0123456789") + + console.draw_frame(0, 0, 3, 3, decoration=(49, 50, 51, 52, 53, 54, 55, 56, 57)) + assert console.ch.tolist() == [[49, 50, 51], [52, 53, 54], [55, 56, 57]] + console.draw_frame(0, 0, 3, 3, title="T") From b63368bd934f8cf10d56d2c61a83fc1c299325dc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 7 Jun 2021 18:36:56 -0700 Subject: [PATCH 0541/1101] Recommend using a real image library over tcod image loading functions. --- tcod/image.py | 7 ++++++- tcod/libtcodpy.py | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tcod/image.py b/tcod/image.py index ee32014a..1bee2198 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -12,7 +12,7 @@ import numpy as np import tcod.console -from tcod._internal import _console +from tcod._internal import _console, deprecate from tcod.loader import ffi, lib @@ -329,6 +329,11 @@ def _get_format_name(format: int) -> str: return str(format) +@deprecate( + "This function may be removed in the future." + " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", + category=PendingDeprecationWarning, +) def load(filename: str) -> np.ndarray: """Load a PNG file as an RGBA array. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b2310f67..d385d9d1 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2921,7 +2921,10 @@ def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) -@pending_deprecate() +@pending_deprecate( + "This function may be removed in the future." + " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio." +) def image_load(filename: str) -> tcod.image.Image: """Load an image file into an Image instance and return it. From f7060b6128f33f10163d030c16e7051af8bf297a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 15:12:03 -0700 Subject: [PATCH 0542/1101] Fix source build issues. Paths need to be tweaked for pyproject.toml build process. --- .github/workflows/python-package.yml | 36 ++++++++++++++++++++++++++++ setup.py | 11 +++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6feaaa84..9c273f16 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -65,3 +65,39 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | twine upload --skip-existing dist/* + - uses: actions/upload-artifact@v2 + if: runner.os == 'Linux' + with: + name: sdist + path: dist/tcod-*.tar.gz + retention-days: 3 + + isolated: # Test installing the package from source. + needs: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ['ubuntu-20.04', 'windows-2019'] + steps: + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install wheel + - name: Install APT dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libsdl2-dev + - uses: actions/download-artifact@v2 + with: + name: sdist + - name: Build package in isolation + run: | + pip install -v tcod-*.tar.gz + - name: Confirm package import + run: | + python -c "import tcod" diff --git a/setup.py b/setup.py index bb9617ed..3d234916 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import pathlib import platform import sys import warnings @@ -11,6 +12,8 @@ SDL_VERSION_NEEDED = (2, 0, 5) +PATH = pathlib.Path(__file__).parent # setup.py current directory + def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" @@ -29,13 +32,13 @@ def get_version() -> str: version += ".dev%i" % commits_since_tag # update tcod/version.py - with open("tcod/version.py", "w") as version_file: + with open(PATH / "tcod/version.py", "w") as version_file: version_file.write('__version__ = "%s"\n' % version) return version except CalledProcessError: try: __version__ = "0.0.0" - with open("tcod/version.py") as version_file: + with open(PATH / "tcod/version.py") as version_file: exec(version_file.read(), globals()) # Update __version__ except FileNotFoundError: warnings.warn("Unknown version: " "Not in a Git repository and not from a sdist bundle or wheel.") @@ -66,7 +69,7 @@ def get_package_data() -> List[str]: def get_long_description() -> str: """Return this projects description.""" - with open("README.rst", "r") as readme_file: + with open(PATH / "README.rst", "r") as readme_file: return readme_file.read() @@ -89,7 +92,7 @@ def check_sdl_version() -> None: raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) -if not os.path.exists("libtcod/src"): +if not os.path.exists(PATH / "libtcod/src"): print("Libtcod submodule is uninitialized.") print("Did you forget to run 'git submodule update --init'?") sys.exit(1) From 96f679f17fd24e620a3b78c810acad1759692817 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 15:29:54 -0700 Subject: [PATCH 0543/1101] Add MyPy types packages. These packages are needed for the newest versions of MyPy. --- .github/workflows/python-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index 6f3a6de6..df80d385 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -23,7 +23,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 mypy black isort pytest + python -m pip install flake8 mypy black isort pytest types-tabulate if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Fake initialize package run: | From ad98cf969e281c8be0b877a06bd786a2cb21657c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 15:44:00 -0700 Subject: [PATCH 0544/1101] Prepare 12.6.0 release. --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1092f13d..acf81adc 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.6.0 - 2021-06-09 +------------------- Added - Added the *decoration* parameter to *Console.draw_frame*. You may use this parameter to designate custom glyphs as the frame border. @@ -16,6 +19,9 @@ Deprecated - The handling of negative indexes given to console drawing and printing functions will be changed to be used as absolute coordinates in the future. +Fixed + - Fixed version mismatch when building from sources. + 12.5.1 - 2021-05-30 ------------------- Fixed From 19e08a6a77948a0e796348dc1e8544951e8a1858 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 17:20:09 -0700 Subject: [PATCH 0545/1101] Add pyproject.toml to manifest. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9f7781d1..54b46459 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include *.py *.cfg *.txt *.rst +include *.py *.cfg *.txt *.rst *.toml recursive-include tcod *.py *.c *.h From 0d615dc833e41366fa212820884f3df356682428 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 17:32:48 -0700 Subject: [PATCH 0546/1101] Fix version detection. The previous code was adding `__version__` to the globals which was then ignored as there was a local version of the same name. The new code is a lot more strict and no longer uses exec. The verbose pip install wasn't needed. --- .github/workflows/python-package.yml | 2 +- CHANGELOG.rst | 5 ++--- setup.py | 10 ++++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9c273f16..7034ab70 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -97,7 +97,7 @@ jobs: name: sdist - name: Build package in isolation run: | - pip install -v tcod-*.tar.gz + pip install tcod-*.tar.gz - name: Confirm package import run: | python -c "import tcod" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index acf81adc..190e51c3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Fixed version mismatch when building from sources. 12.6.0 - 2021-06-09 ------------------- @@ -19,9 +21,6 @@ Deprecated - The handling of negative indexes given to console drawing and printing functions will be changed to be used as absolute coordinates in the future. -Fixed - - Fixed version mismatch when building from sources. - 12.5.1 - 2021-05-30 ------------------- Fixed diff --git a/setup.py b/setup.py index 3d234916..9f10b161 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import os import pathlib import platform +import re import sys import warnings from subprocess import CalledProcessError, check_output @@ -37,12 +38,13 @@ def get_version() -> str: return version except CalledProcessError: try: - __version__ = "0.0.0" with open(PATH / "tcod/version.py") as version_file: - exec(version_file.read(), globals()) # Update __version__ + match = re.match(r'__version__ = "(\S+)"', version_file.read()) + assert match + return match.groups()[0] except FileNotFoundError: - warnings.warn("Unknown version: " "Not in a Git repository and not from a sdist bundle or wheel.") - return __version__ + warnings.warn("Unknown version: Not in a Git repository and not from a sdist bundle or wheel.") + return "0.0.0" is_pypy = platform.python_implementation() == "PyPy" From 08934cdb4b7f640e5b78e00967820ad182a252b7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Jun 2021 18:12:43 -0700 Subject: [PATCH 0547/1101] Prepare 12.6.1 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 190e51c3..f24b7314 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.6.1 - 2021-06-09 +------------------- Fixed - Fixed version mismatch when building from sources. From e1e2012c4e918724a029f9e42bb053704deda264 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 15 Jun 2021 12:11:46 -0700 Subject: [PATCH 0548/1101] Use the existence of .git to determine if git-describe is used. Fixes installation errors if Git is missing. Switch some string formatting to use f-strings. --- CHANGELOG.rst | 2 ++ setup.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f24b7314..cb6a212b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Git is no longer required to install from source. 12.6.1 - 2021-06-09 ------------------- diff --git a/setup.py b/setup.py index 9f10b161..aa3ca8cf 100755 --- a/setup.py +++ b/setup.py @@ -4,9 +4,9 @@ import pathlib import platform import re +import subprocess import sys import warnings -from subprocess import CalledProcessError, check_output from typing import List from setuptools import setup # type: ignore @@ -18,25 +18,22 @@ def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" - try: - tag = check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() + if (PATH / ".git").exists(): + tag = subprocess.check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() assert not tag.startswith("v") version = tag # add .devNN if needed - log = check_output( - ["git", "log", "%s..HEAD" % tag, "--oneline"], - universal_newlines=True, - ) + log = subprocess.check_output(["git", "log", f"{tag}..HEAD", "--oneline"], universal_newlines=True) commits_since_tag = log.count("\n") if commits_since_tag: version += ".dev%i" % commits_since_tag # update tcod/version.py with open(PATH / "tcod/version.py", "w") as version_file: - version_file.write('__version__ = "%s"\n' % version) + version_file.write(f'__version__ = "{version}"\n') return version - except CalledProcessError: + else: # Not a Git respotitory. try: with open(PATH / "tcod/version.py") as version_file: match = re.match(r'__version__ = "(\S+)"', version_file.read()) @@ -81,7 +78,7 @@ def check_sdl_version() -> None: return needed_version = "%i.%i.%i" % SDL_VERSION_NEEDED try: - sdl_version_str = check_output(["sdl2-config", "--version"], universal_newlines=True).strip() + sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() except FileNotFoundError: raise RuntimeError( "libsdl2-dev or equivalent must be installed on your system" From 8be15d3b3397ed5b49288c8f84d913d9cd08407c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 15 Jun 2021 18:32:55 -0700 Subject: [PATCH 0549/1101] Prepare 12.6.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cb6a212b..20a105a5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.6.2 - 2021-06-15 +------------------- Fixed - Git is no longer required to install from source. From 2f2cf354cb7dd8d72928202e201d403b0040ae80 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 17 Jun 2021 18:46:52 -0700 Subject: [PATCH 0550/1101] Auto format YAML files. --- .github/workflows/python-lint.yml | 84 ++++++------ .github/workflows/python-package-macos.yml | 141 ++++++++++---------- .github/workflows/python-package.yml | 148 ++++++++++----------- .github/workflows/release-on-tag.yml | 4 +- 4 files changed, 188 insertions(+), 189 deletions(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index df80d385..83ed26ff 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -9,46 +9,46 @@ jobs: lint: runs-on: ubuntu-20.04 env: - python-version: '3.9' + python-version: "3.9" steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python ${{ env.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ env.python-version }} - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 mypy black isort pytest types-tabulate - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Fake initialize package - run: | - echo '__version__ = ""' > tcod/version.py - - name: Flake8 - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: flake8 - run: flake8 scripts/ tcod/ tests/ - - name: MyPy - if: always() - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: mypy - run: mypy --show-column-numbers . - - name: isort - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: isort - run: isort scripts/ tcod/ tests/ --check - - name: isort (examples) - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: isort - run: isort examples/ --check --thirdparty tcod - - name: Black - run: | - black --check examples/ scripts/ tcod/ tests/ *.py + - name: Checkout code + uses: actions/checkout@v2 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ env.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.python-version }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 mypy black isort pytest types-tabulate + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Fake initialize package + run: | + echo '__version__ = ""' > tcod/version.py + - name: Flake8 + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: flake8 + run: flake8 scripts/ tcod/ tests/ + - name: MyPy + if: always() + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: mypy + run: mypy --show-column-numbers . + - name: isort + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort scripts/ tcod/ tests/ --check + - name: isort (examples) + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort examples/ --check --thirdparty tcod + - name: Black + run: | + black --check examples/ scripts/ tcod/ tests/ *.py diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 36a03334..b0b6c6d4 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -8,66 +8,65 @@ on: jobs: build: - runs-on: ${{ matrix.os }} strategy: matrix: - os: ['macos-latest'] - python-version: ['3.7.9'] + os: ["macos-latest"] + python-version: ["3.7.9"] steps: - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - - uses: actions/checkout@v1 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Print git describe - run: | - git describe - - name: Set up Mac Python ${{ matrix.python-version }} - # actions/setup-python can't be used as it builds less portable extensions. - env: - MB_PYTHON_VERSION: ${{ matrix.python-version }} - run: | - source .travis/install_python.sh - install_python - $PYTHON_EXE -m venv venv - source venv/bin/activate - echo "$PATH" >> $GITHUB_PATH - - name: Print Python version - run: | - python -VV - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark wheel twine - pip install git+https://github.com/HexDecimal/delocate.git@loader_path - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Build package. - run: | - python setup.py sdist develop bdist_wheel --py-limited-api=cp36 - - name: Test with pytest - run: | - pytest --no-window --cov-report=xml - - uses: codecov/codecov-action@v1 - - name: Package binary files - run: | - delocate-wheel -v dist/*.whl - delocate-listdeps --all dist/*.whl - - name: Archive wheel - uses: actions/upload-artifact@v2 - with: - name: wheel-macos - path: dist/*.whl - retention-days: 1 - - name: Upload to PyPI - if: github.event_name == 'release' - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing dist/* + # v2 breaks `git describe` so v1 is needed. + # https://github.com/actions/checkout/issues/272 + - uses: actions/checkout@v1 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Print git describe + run: | + git describe + - name: Set up Mac Python ${{ matrix.python-version }} + # actions/setup-python can't be used as it builds less portable extensions. + env: + MB_PYTHON_VERSION: ${{ matrix.python-version }} + run: | + source .travis/install_python.sh + install_python + $PYTHON_EXE -m venv venv + source venv/bin/activate + echo "$PATH" >> $GITHUB_PATH + - name: Print Python version + run: | + python -VV + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pytest-benchmark wheel twine + pip install git+https://github.com/HexDecimal/delocate.git@loader_path + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Build package. + run: | + python setup.py sdist develop bdist_wheel --py-limited-api=cp36 + - name: Test with pytest + run: | + pytest --no-window --cov-report=xml + - uses: codecov/codecov-action@v1 + - name: Package binary files + run: | + delocate-wheel -v dist/*.whl + delocate-listdeps --all dist/*.whl + - name: Archive wheel + uses: actions/upload-artifact@v2 + with: + name: wheel-macos + path: dist/*.whl + retention-days: 1 + - name: Upload to PyPI + if: github.event_name == 'release' + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing dist/* isolated_test: name: Verify wheel dependencies @@ -75,20 +74,20 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['macos-latest'] - python-version: ['3.x'] + os: ["macos-latest"] + python-version: ["3.x"] steps: - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - uses: actions/download-artifact@v2 - with: - name: wheel-macos - path: dist - - name: Install tcod from wheel - run: | - python -m pip install dist/*.whl - - name: Verify dependency correctness - run: | - python -c "import tcod" + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/download-artifact@v2 + with: + name: wheel-macos + path: dist + - name: Install tcod from wheel + run: | + python -m pip install dist/*.whl + - name: Verify dependency correctness + run: | + python -c "import tcod" diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 7034ab70..5bd0d24e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -18,86 +18,86 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-20.04', 'windows-2019'] - python-version: ['3.6', '3.7', '3.8', '3.9', 'pypy-3.6', 'pypy-3.7'] + os: ["ubuntu-20.04", "windows-2019"] + python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.6", "pypy-3.7"] fail-fast: false steps: - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - - uses: actions/checkout@v1 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install APT dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install libsdl2-dev xvfb - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark wheel twine - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Initialize package - run: | - python setup.py check # Creates tcod/version.py. - - name: Build package. - run: | - python setup.py build sdist develop bdist_wheel --py-limited-api cp36 # Install the package in-place. - - name: Test with pytest - if: runner.os == 'Windows' - run: | - pytest --cov-report=xml - - name: Test with pytest - if: runner.os != 'Windows' - run: | - xvfb-run --auto-servernum pytest --cov-report=xml - - uses: codecov/codecov-action@v1 - - name: Upload to PyPI - if: github.event_name == 'release' && runner.os != 'Linux' - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing dist/* - - uses: actions/upload-artifact@v2 - if: runner.os == 'Linux' - with: - name: sdist - path: dist/tcod-*.tar.gz - retention-days: 3 + # v2 breaks `git describe` so v1 is needed. + # https://github.com/actions/checkout/issues/272 + - uses: actions/checkout@v1 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install APT dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libsdl2-dev xvfb + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pytest-benchmark wheel twine + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Initialize package + run: | + python setup.py check # Creates tcod/version.py. + - name: Build package. + run: | + python setup.py build sdist develop bdist_wheel --py-limited-api cp36 # Install the package in-place. + - name: Test with pytest + if: runner.os == 'Windows' + run: | + pytest --cov-report=xml + - name: Test with pytest + if: runner.os != 'Windows' + run: | + xvfb-run --auto-servernum pytest --cov-report=xml + - uses: codecov/codecov-action@v1 + - name: Upload to PyPI + if: github.event_name == 'release' && runner.os != 'Linux' + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing dist/* + - uses: actions/upload-artifact@v2 + if: runner.os == 'Linux' + with: + name: sdist + path: dist/tcod-*.tar.gz + retention-days: 3 - isolated: # Test installing the package from source. + isolated: # Test installing the package from source. needs: build runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-20.04', 'windows-2019'] + os: ["ubuntu-20.04", "windows-2019"] steps: - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.x - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install wheel - - name: Install APT dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install libsdl2-dev - - uses: actions/download-artifact@v2 - with: - name: sdist - - name: Build package in isolation - run: | - pip install tcod-*.tar.gz - - name: Confirm package import - run: | - python -c "import tcod" + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install wheel + - name: Install APT dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libsdl2-dev + - uses: actions/download-artifact@v2 + with: + name: sdist + - name: Build package in isolation + run: | + pip install tcod-*.tar.gz + - name: Confirm package import + run: | + python -c "import tcod" diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index dac3b19b..fd5e5377 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -1,7 +1,7 @@ on: push: tags: - - '*.*.*' + - "*.*.*" name: Create Release @@ -22,7 +22,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} with: tag_name: ${{ github.ref }} - release_name: '' + release_name: "" body_path: release_body.md draft: false prerelease: false From 381136238105880e26b41d34aae0cf8a58828a8e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 18 Jun 2021 19:45:21 -0700 Subject: [PATCH 0551/1101] Add recommended extensions file for VSCode. --- .editorconfig | 4 ++++ .vscode/extensions.json | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.editorconfig b/.editorconfig index cd57b23f..bd4f19f2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,3 +22,7 @@ indent_size = 2 [*.{h,c,cpp}] indent_style = space indent_size = 2 + +[*.json] +indent_style = space +indent_size = 4 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..8d2b7da1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,17 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "austin.code-gnu-global", + "editorconfig.editorconfig", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-vscode.cpptools", + "redhat.vscode-yaml", + "tamasfe.even-better-toml", + "xaver.clang-format", + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} From 9b22d77b64c00f19ff7a61d1e37782004b057828 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 18 Jun 2021 19:47:25 -0700 Subject: [PATCH 0552/1101] Exclude final newline from JSON code. --- .editorconfig | 1 + .vscode/extensions.json | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 2 +- .vscode/tasks.json | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index bd4f19f2..2cd81a2a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -26,3 +26,4 @@ indent_size = 2 [*.json] indent_style = space indent_size = 4 +insert_final_newline = false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8d2b7da1..cf388f9c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -14,4 +14,4 @@ ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] -} +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 52963e4e..b1e2764f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,4 +24,4 @@ "preLaunchTask": "develop python-tcod", } ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 88fa964d..320eade3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,4 +18,4 @@ "files.associations": { "*.spec": "python", } -} +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 08f9ed85..854b959f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,4 +10,4 @@ "command": "python setup.py develop" } ] -} +} \ No newline at end of file From c34d037f9c2f783ec1448aad6b806e56c5bb089e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 22 Jun 2021 17:12:12 -0700 Subject: [PATCH 0553/1101] Update docstrings. --- tcod/__init__.py | 19 +++++-------------- tcod/console.py | 3 +++ tcod/constants.py | 3 +-- tcod/random.py | 3 ++- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index a0a803bd..7f772cff 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -1,19 +1,10 @@ -""" - This module provides a simple CFFI API to libtcod. - - This port has large partial support for libtcod's C functions. - Use tcod/libtcod_cdef.h in the source distribution to see specially what - functions were exported and what new functions have been added by TDL. - - The ffi and lib variables should be familiar to anyone that has used CFFI - before, otherwise it's time to read up on how they work: - https://cffi.readthedocs.org/en/latest/using.html +"""The fast Python port of libtcod. - Otherwise this module can be used as a drop in replacement for the official - libtcod.py module. +This module can be used as a drop in replacement for the official libtcodpy +module. - Bring any issues or requests to GitHub: - https://github.com/HexDecimal/libtcod-cffi +Bring any issues or feature requests to GitHub: +https://github.com/libtcod/python-tcod """ import sys import warnings diff --git a/tcod/console.py b/tcod/console.py index 1ed8b4d5..417fb628 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1063,6 +1063,9 @@ def draw_frame( `title` is a Unicode string. The title is drawn with `bg` as the text color and `fg` as the background. + Using the `title` parameter is discouraged since the style it uses is + hard-coded into libtcod. You should print over the top or bottom + border with :any:`Console.print_box` using your own style. If `clear` is True than the region inside of the frame will be cleared. diff --git a/tcod/constants.py b/tcod/constants.py index 681f8792..41afe966 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -1,5 +1,4 @@ -""" -Constants from the libtcod C API. +"""Constants from the libtcod C API. This module is auto-generated by `build_libtcod.py`. """ diff --git a/tcod/random.py b/tcod/random.py index d54a7036..4fdfbb93 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -1,4 +1,5 @@ -""" +"""Ports of the libtcod random number generator. + Usually it's recommend to the Python's standard library `random` module instead of this one. From f48766788a3dd76de187b05dc3f62c5f5b6eab3a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 24 Jun 2021 20:08:31 -0700 Subject: [PATCH 0554/1101] Link to the online docs from the tcod package docstring. --- tcod/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index 7f772cff..cf0a9038 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -1,10 +1,10 @@ """The fast Python port of libtcod. -This module can be used as a drop in replacement for the official libtcodpy -module. +This module can be used as a drop in replacement for the official libtcodpy module. -Bring any issues or feature requests to GitHub: -https://github.com/libtcod/python-tcod +Bring any issues or feature requests to GitHub: https://github.com/libtcod/python-tcod + +Read the documentation online: https://python-tcod.readthedocs.io/en/latest/ """ import sys import warnings From 3e3400032d58857ae9538d8ae1ca2f1c6b8b8e49 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 28 Jun 2021 00:13:47 -0700 Subject: [PATCH 0555/1101] Add pathlib support to tcod.tileset and tcod.image. --- CHANGELOG.rst | 2 ++ tcod/console.py | 6 +++--- tcod/image.py | 9 ++++----- tcod/tileset.py | 19 ++++++++++--------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 20a105a5..b82b9c74 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added + - *tcod.image* and *tcod.tileset* now support *pathlib*. 12.6.2 - 2021-06-15 ------------------- diff --git a/tcod/console.py b/tcod/console.py index 417fb628..12142e64 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -4,8 +4,8 @@ See :ref:`getting-started` for info on how to set those up. """ import os -import pathlib import warnings +from pathlib import Path from typing import Any, Iterable, Optional, Sequence, Tuple, Union import numpy as np @@ -1266,7 +1266,7 @@ def recommended_size() -> Tuple[int, int]: return w, h -def load_xp(path: Union[str, pathlib.Path], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: +def load_xp(path: Union[str, Path], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: """Load a REXPaint file as a tuple of consoles. `path` is the name of the REXPaint file to load. @@ -1308,7 +1308,7 @@ def load_xp(path: Union[str, pathlib.Path], order: Literal["C", "F"] = "C") -> T def save_xp( - path: Union[str, pathlib.Path], + path: Union[str, Path], consoles: Iterable[Console], compress_level: int = 9, ) -> None: diff --git a/tcod/image.py b/tcod/image.py index 1bee2198..5eaa219b 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -5,9 +5,8 @@ `an alternative library for graphics rendering `_. """ - - -from typing import Any, Dict, Tuple +from pathlib import Path +from typing import Any, Dict, Tuple, Union import numpy as np @@ -334,7 +333,7 @@ def _get_format_name(format: int) -> str: " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", category=PendingDeprecationWarning, ) -def load(filename: str) -> np.ndarray: +def load(filename: Union[str, Path]) -> np.ndarray: """Load a PNG file as an RGBA array. `filename` is the name of the file to load. @@ -343,7 +342,7 @@ def load(filename: str) -> np.ndarray: .. versionadded:: 11.4 """ - image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(filename.encode()), lib.TCOD_image_delete)) + image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(str(filename).encode()), lib.TCOD_image_delete)) array = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: diff --git a/tcod/tileset.py b/tcod/tileset.py index b9249cc8..407f2b32 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -12,7 +12,8 @@ """ import itertools import os -from typing import Any, Iterable, Optional, Tuple +from pathlib import Path +from typing import Any, Iterable, Optional, Tuple, Union import numpy as np @@ -200,7 +201,7 @@ def set_default(tileset: Tileset) -> None: lib.TCOD_set_default_tileset(tileset._tileset_p) -def load_truetype_font(path: str, tile_width: int, tile_height: int) -> Tileset: +def load_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) -> Tileset: """Return a new Tileset from a `.ttf` or `.otf` file. Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead. @@ -210,14 +211,14 @@ def load_truetype_font(path: str, tile_width: int, tile_height: int) -> Tileset: """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_truetype_font_(path.encode(), tile_width, tile_height) + cdata = lib.TCOD_load_truetype_font_(str(path).encode(), tile_width, tile_height) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) return Tileset._claim(cdata) @deprecate("Accessing the default tileset is deprecated.") -def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: +def set_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) -> None: """Set the default tileset from a `.ttf` or `.otf` file. `path` is the file path for the font file. @@ -239,11 +240,11 @@ def set_truetype_font(path: str, tile_width: int, tile_height: int) -> None: """ if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - if lib.TCOD_tileset_load_truetype_(path.encode(), tile_width, tile_height): + if lib.TCOD_tileset_load_truetype_(str(path).encode(), tile_width, tile_height): raise RuntimeError(ffi.string(lib.TCOD_get_error())) -def load_bdf(path: str) -> Tileset: +def load_bdf(path: Union[str, Path]) -> Tileset: """Return a new Tileset from a `.bdf` file. For the best results the font should be monospace, cell-based, and @@ -258,13 +259,13 @@ def load_bdf(path: str) -> Tileset: """ # noqa: E501 if not os.path.exists(path): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_bdf(path.encode()) + cdata = lib.TCOD_load_bdf(str(path).encode()) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) return Tileset._claim(cdata) -def load_tilesheet(path: str, columns: int, rows: int, charmap: Optional[Iterable[int]]) -> Tileset: +def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Optional[Iterable[int]]) -> Tileset: """Return a new Tileset from a simple tilesheet image. `path` is the file path to a PNG file with the tileset. @@ -289,7 +290,7 @@ def load_tilesheet(path: str, columns: int, rows: int, charmap: Optional[Iterabl mapping = [] if charmap is not None: mapping = list(itertools.islice(charmap, columns * rows)) - cdata = lib.TCOD_tileset_load(path.encode(), columns, rows, len(mapping), mapping) + cdata = lib.TCOD_tileset_load(str(path).encode(), columns, rows, len(mapping), mapping) if not cdata: _raise_tcod_error() return Tileset._claim(cdata) From 03676bb05429223b3e7ee878db8ab154e64bcd62 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Jun 2021 18:17:17 -0700 Subject: [PATCH 0556/1101] Build 32-bit wheels on Windows. --- .github/workflows/python-package.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5bd0d24e..3fe12aa7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,7 +19,15 @@ jobs: strategy: matrix: os: ["ubuntu-20.04", "windows-2019"] - python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.6", "pypy-3.7"] + python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.7"] + architecture: ["x64"] + include: + - os: "windows-2019" + python-version: "3.6" + architecture: "x86" + - os: "windows-2019" + python-version: "pypy-3.6" + architecture: "x86" fail-fast: false steps: @@ -33,6 +41,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.architecture }} - name: Install APT dependencies if: runner.os == 'Linux' run: | From 4d9f28f5e3e28291ac3129bc1c18b2a50d93feaa Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Jun 2021 19:35:20 -0700 Subject: [PATCH 0557/1101] Update NumPy type hints. --- examples/cavegen.py | 13 ++++--- examples/samples_tcod.py | 17 +++++---- tcod/console.py | 30 +++++++-------- tcod/event.py | 4 +- tcod/image.py | 4 +- tcod/libtcodpy.py | 79 +++++++++++++++++++++++++--------------- tcod/los.py | 6 +-- tcod/map.py | 16 ++++---- tcod/noise.py | 16 ++++---- tcod/path.py | 56 ++++++++++++++-------------- tcod/sdl.py | 4 +- tcod/tileset.py | 11 ++++-- tests/test_noise.py | 10 ++--- 13 files changed, 148 insertions(+), 118 deletions(-) diff --git a/examples/cavegen.py b/examples/cavegen.py index aa2bca7c..eedf0575 100644 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -6,11 +6,14 @@ This will print the result to the console, so be sure to run this from the command line. """ +from typing import Any + import numpy as np import scipy.signal # type: ignore +from numpy.typing import NDArray -def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: +def convolve(tiles: NDArray[Any], wall_rule: int = 5) -> NDArray[np.bool_]: """Return the next step of the cave generation algorithm. `tiles` is the input array. (0: wall, 1: floor) @@ -19,12 +22,12 @@ def convolve(tiles: np.ndarray, wall_rule: int = 5) -> np.ndarray: walls then the tile will become a wall. """ # Use convolve2d, the 2nd input is a 3x3 ones array. - neighbors: np.ndarray = scipy.signal.convolve2d(tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same") - next_tiles: np.ndarray = neighbors < wall_rule # Apply the wall rule. + neighbors: NDArray[Any] = scipy.signal.convolve2d(tiles == 0, [[1, 1, 1], [1, 1, 1], [1, 1, 1]], "same") + next_tiles: NDArray[np.bool_] = neighbors < wall_rule # Apply the wall rule. return next_tiles -def show(tiles: np.ndarray) -> None: +def show(tiles: NDArray[Any]) -> None: """Print out the tiles of an array.""" for line in tiles: print("".join("# "[int(cell)] for cell in line)) @@ -35,7 +38,7 @@ def show(tiles: np.ndarray) -> None: INITIAL_CHANCE = 0.45 # Initial wall chance. CONVOLVE_STEPS = 4 # 0: wall, 1: floor - tiles = np.random.random((HEIGHT, WIDTH)) > INITIAL_CHANCE + tiles: NDArray[np.bool_] = np.random.random((HEIGHT, WIDTH)) > INITIAL_CHANCE for _ in range(CONVOLVE_STEPS): tiles = convolve(tiles) tiles[[0, -1], :] = 0 # Ensure surrounding wall. diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 6dd94758..53e990d4 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -18,6 +18,7 @@ import numpy as np import tcod +from numpy.typing import NDArray if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. @@ -839,7 +840,7 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: # draw a vertical line -def vline(m: np.ndarray, x: int, y1: int, y2: int) -> None: +def vline(m: NDArray[np.bool_], x: int, y1: int, y2: int) -> None: if y1 > y2: y1, y2 = y2, y1 for y in range(y1, y2 + 1): @@ -847,21 +848,21 @@ def vline(m: np.ndarray, x: int, y1: int, y2: int) -> None: # draw a vertical line up until we reach an empty space -def vline_up(m: np.ndarray, x: int, y: int) -> None: +def vline_up(m: NDArray[np.bool_], x: int, y: int) -> None: while y >= 0 and not m[x, y]: m[x, y] = True y -= 1 # draw a vertical line down until we reach an empty space -def vline_down(m: np.ndarray, x: int, y: int) -> None: +def vline_down(m: NDArray[np.bool_], x: int, y: int) -> None: while y < SAMPLE_SCREEN_HEIGHT and not m[x, y]: m[x, y] = True y += 1 # draw a horizontal line -def hline(m: np.ndarray, x1: int, y: int, x2: int) -> None: +def hline(m: NDArray[np.bool_], x1: int, y: int, x2: int) -> None: if x1 > x2: x1, x2 = x2, x1 for x in range(x1, x2 + 1): @@ -869,21 +870,21 @@ def hline(m: np.ndarray, x1: int, y: int, x2: int) -> None: # draw a horizontal line left until we reach an empty space -def hline_left(m: np.ndarray, x: int, y: int) -> None: +def hline_left(m: NDArray[np.bool_], x: int, y: int) -> None: while x >= 0 and not m[x, y]: m[x, y] = True x -= 1 # draw a horizontal line right until we reach an empty space -def hline_right(m: np.ndarray, x: int, y: int) -> None: +def hline_right(m: NDArray[np.bool_], x: int, y: int) -> None: while x < SAMPLE_SCREEN_WIDTH and not m[x, y]: m[x, y] = True x += 1 # the class building the dungeon from the bsp nodes -def traverse_node(bsp_map: np.ndarray, node: tcod.bsp.BSP) -> None: +def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: if not node.children: # calculate the room size if bsp_room_walls: @@ -1216,7 +1217,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: # xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] if numpy_available: - (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) + (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) # type: ignore # translate coordinates of all pixels to center xc = xc - HALF_W yc = yc - HALF_H diff --git a/tcod/console.py b/tcod/console.py index 12142e64..3ed7f030 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -110,7 +110,7 @@ def __init__( width: int, height: int, order: Literal["C", "F"] = "C", - buffer: Optional[np.ndarray] = None, + buffer: "Optional[np.ndarray[Any, Any]]" = None, ): self._key_color = None # type: Optional[Tuple[int, int, int]] self._order = tcod._internal.verify_order(order) @@ -181,7 +181,7 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: else: self._console_data = ffi.cast("struct TCOD_Console*", self.console_c) - self._tiles = np.frombuffer( + self._tiles = np.frombuffer( # type: ignore ffi.buffer(self._console_data.tiles[0 : self.width * self.height]), dtype=self.DTYPE, ).reshape((self.height, self.width)) @@ -199,7 +199,7 @@ def height(self) -> int: return lib.TCOD_console_get_height(self.console_c) # type: ignore @property - def bg(self) -> np.ndarray: + def bg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": """A uint8 array with the shape (height, width, 3). You can change the consoles background colors by using this array. @@ -208,13 +208,13 @@ def bg(self) -> np.ndarray: ``console.bg[x, y, channel] # order='F'``. """ - bg = self._tiles["bg"][..., :3] # type: np.ndarray + bg: np.ndarray[Any, np.dtype[np.uint8]] = self._tiles["bg"][..., :3] if self._order == "F": bg = bg.transpose(1, 0, 2) return bg @property - def fg(self) -> np.ndarray: + def fg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": """A uint8 array with the shape (height, width, 3). You can change the consoles foreground colors by using this array. @@ -222,13 +222,13 @@ def fg(self) -> np.ndarray: Index this array with ``console.fg[i, j, channel] # order='C'`` or ``console.fg[x, y, channel] # order='F'``. """ - fg = self._tiles["fg"][..., :3] # type: np.ndarray + fg: np.ndarray[Any, np.dtype[np.uint8]] = self._tiles["fg"][..., :3] if self._order == "F": fg = fg.transpose(1, 0, 2) return fg @property - def ch(self) -> np.ndarray: + def ch(self) -> "np.ndarray[Any, np.dtype[np.intc]]": """An integer array with the shape (height, width). You can change the consoles character codes by using this array. @@ -240,7 +240,7 @@ def ch(self) -> np.ndarray: @property # type: ignore @deprecate("This attribute has been renamed to `rgba`.") - def tiles(self) -> np.ndarray: + def tiles(self) -> "np.ndarray[Any, Any]": """An array of this consoles raw tile data. This acts as a combination of the `ch`, `fg`, and `bg` attributes. @@ -256,7 +256,7 @@ def tiles(self) -> np.ndarray: @property # type: ignore @deprecate("This attribute has been renamed to `rgba`.") - def buffer(self) -> np.ndarray: + def buffer(self) -> "np.ndarray[Any, Any]": """An array of this consoles raw tile data. .. versionadded:: 11.4 @@ -268,7 +268,7 @@ def buffer(self) -> np.ndarray: @property # type: ignore @deprecate("This attribute has been renamed to `rgb`.") - def tiles_rgb(self) -> np.ndarray: + def tiles_rgb(self) -> "np.ndarray[Any, Any]": """An array of this consoles data without the alpha channel. .. versionadded:: 11.8 @@ -280,7 +280,7 @@ def tiles_rgb(self) -> np.ndarray: @property # type: ignore @deprecate("This attribute has been renamed to `rgb`.") - def tiles2(self) -> np.ndarray: + def tiles2(self) -> "np.ndarray[Any, Any]": """This name is deprecated in favour of :any:`rgb`. .. versionadded:: 11.3 @@ -291,7 +291,7 @@ def tiles2(self) -> np.ndarray: return self.rgb @property - def rgba(self) -> np.ndarray: + def rgba(self) -> "np.ndarray[Any, Any]": """An array of this consoles raw tile data. The axes of this array is affected by the `order` parameter given to @@ -312,7 +312,7 @@ def rgba(self) -> np.ndarray: return self._tiles.T if self._order == "F" else self._tiles @property - def rgb(self) -> np.ndarray: + def rgb(self) -> "np.ndarray[Any, Any]": """An array of this consoles data without the alpha channel. The axes of this array is affected by the `order` parameter given to @@ -888,13 +888,13 @@ def __getstate__(self) -> Any: "back": self.default_bg, } if self.console_c == ffi.NULL: - state["_tiles"] = np.copy(self._tiles) + state["_tiles"] = np.array(self._tiles, copy=True) return state def __setstate__(self, state: Any) -> None: self._key_color = None if "_tiles" not in state: - tiles = np.ndarray((self.height, self.width), dtype=self.DTYPE) + tiles: np.ndarray[Any, Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE) tiles["ch"] = state["_ch"] tiles["fg"][..., :3] = state["_fg"] tiles["fg"][..., 3] = 255 diff --git a/tcod/event.py b/tcod/event.py index 81f0339e..2020480c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1030,7 +1030,7 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: return 0 -def get_keyboard_state() -> np.ndarray: +def get_keyboard_state() -> "np.ndarray[Any, np.dtype[np.bool_]]": """Return a boolean array with the current keyboard state. Index this array with a scancode. The value will be True if the key is @@ -1052,7 +1052,7 @@ def get_keyboard_state() -> np.ndarray: """ numkeys = ffi.new("int[1]") keyboard_state = lib.SDL_GetKeyboardState(numkeys) - out: np.ndarray = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool) + out: np.ndarray[Any, Any] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool) # type: ignore out.flags["WRITEABLE"] = False # This buffer is supposed to be const. return out diff --git a/tcod/image.py b/tcod/image.py index 5eaa219b..30a1c4ec 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -333,7 +333,7 @@ def _get_format_name(format: int) -> str: " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", category=PendingDeprecationWarning, ) -def load(filename: Union[str, Path]) -> np.ndarray: +def load(filename: Union[str, Path]) -> "np.ndarray[Any, np.dtype[np.uint8]]": """Load a PNG file as an RGBA array. `filename` is the name of the file to load. @@ -346,7 +346,7 @@ def load(filename: Union[str, Path]) -> np.ndarray: array = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: - array = np.concatenate( + array = np.concatenate( # type: ignore ( array, np.full((height, width, 1), fill_value=255, dtype=np.uint8), diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d385d9d1..99eb8810 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2255,7 +2255,7 @@ def dijkstra_delete(p: tcod.path.Dijkstra) -> None: """ -def _heightmap_cdata(array: np.ndarray) -> ffi.CData: +def _heightmap_cdata(array: "np.ndarray[Any, np.dtype[np.float32]]") -> ffi.CData: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. @@ -2264,7 +2264,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: raise ValueError("array must be a contiguous segment.") - if array.dtype != np.float32: + if array.dtype != np.float32: # type: ignore raise ValueError("array dtype must be float32, not %r" % array.dtype) height, width = array.shape pointer = ffi.from_buffer("float *", array) @@ -2272,7 +2272,7 @@ def _heightmap_cdata(array: np.ndarray) -> ffi.CData: @pending_deprecate() -def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: +def heightmap_new(w: int, h: int, order: str = "C") -> "np.ndarray[Any, np.dtype[np.float32]]": """Return a new numpy.ndarray formatted for use with heightmap functions. `w` and `h` are the width and height of the array. @@ -2299,7 +2299,7 @@ def heightmap_new(w: int, h: int, order: str = "C") -> np.ndarray: @deprecate("Assign to heightmaps as a NumPy array instead.") -def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None: +def heightmap_set_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int, value: float) -> None: """Set the value of a point on a heightmap. .. deprecated:: 2.0 @@ -2324,7 +2324,7 @@ def heightmap_set_value(hm: np.ndarray, x: int, y: int, value: float) -> None: @deprecate("Add a scalar to an array using `hm[:] += value`") -def heightmap_add(hm: np.ndarray, value: float) -> None: +def heightmap_add(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) -> None: """Add value to all values on this heightmap. Args: @@ -2338,7 +2338,7 @@ def heightmap_add(hm: np.ndarray, value: float) -> None: @deprecate("Multiply an array with a scaler using `hm[:] *= value`") -def heightmap_scale(hm: np.ndarray, value: float) -> None: +def heightmap_scale(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) -> None: """Multiply all items on this heightmap by value. Args: @@ -2352,7 +2352,7 @@ def heightmap_scale(hm: np.ndarray, value: float) -> None: @deprecate("Clear an array with`hm[:] = 0`") -def heightmap_clear(hm: np.ndarray) -> None: +def heightmap_clear(hm: "np.ndarray[Any, np.dtype[np.float32]]") -> None: """Add value to all values on this heightmap. Args: @@ -2365,7 +2365,7 @@ def heightmap_clear(hm: np.ndarray) -> None: @deprecate("Clamp array values using `hm.clip(mi, ma)`") -def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None: +def heightmap_clamp(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float, ma: float) -> None: """Clamp all values on this heightmap between ``mi`` and ``ma`` Args: @@ -2380,7 +2380,7 @@ def heightmap_clamp(hm: np.ndarray, mi: float, ma: float) -> None: @deprecate("Copy an array using `hm2[:] = hm1[:]`, or `hm1.copy()`") -def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: +def heightmap_copy(hm1: "np.ndarray[Any, np.dtype[np.float32]]", hm2: "np.ndarray[Any, np.dtype[np.float32]]") -> None: """Copy the heightmap ``hm1`` to ``hm2``. Args: @@ -2394,7 +2394,7 @@ def heightmap_copy(hm1: np.ndarray, hm2: np.ndarray) -> None: @pending_deprecate() -def heightmap_normalize(hm: np.ndarray, mi: float = 0.0, ma: float = 1.0) -> None: +def heightmap_normalize(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float = 0.0, ma: float = 1.0) -> None: """Normalize heightmap values between ``mi`` and ``ma``. Args: @@ -2405,7 +2405,12 @@ def heightmap_normalize(hm: np.ndarray, mi: float = 0.0, ma: float = 1.0) -> Non @pending_deprecate() -def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: float) -> None: +def heightmap_lerp_hm( + hm1: "np.ndarray[Any, np.dtype[np.float32]]", + hm2: "np.ndarray[Any, np.dtype[np.float32]]", + hm3: "np.ndarray[Any, np.dtype[np.float32]]", + coef: float, +) -> None: """Perform linear interpolation between two heightmaps storing the result in ``hm3``. @@ -2426,7 +2431,11 @@ def heightmap_lerp_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray, coef: f @deprecate("Add 2 arrays using `hm3 = hm1 + hm2`") -def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> None: +def heightmap_add_hm( + hm1: "np.ndarray[Any, np.dtype[np.float32]]", + hm2: "np.ndarray[Any, np.dtype[np.float32]]", + hm3: "np.ndarray[Any, np.dtype[np.float32]]", +) -> None: """Add two heightmaps together and stores the result in ``hm3``. Args: @@ -2441,7 +2450,11 @@ def heightmap_add_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> None: @deprecate("Multiply 2 arrays using `hm3 = hm1 * hm2`") -def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> None: +def heightmap_multiply_hm( + hm1: "np.ndarray[Any, np.dtype[np.float32]]", + hm2: "np.ndarray[Any, np.dtype[np.float32]]", + hm3: "np.ndarray[Any, np.dtype[np.float32]]", +) -> None: """Multiplies two heightmap's together and stores the result in ``hm3``. Args: @@ -2457,7 +2470,9 @@ def heightmap_multiply_hm(hm1: np.ndarray, hm2: np.ndarray, hm3: np.ndarray) -> @pending_deprecate() -def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float, height: float) -> None: +def heightmap_add_hill( + hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, radius: float, height: float +) -> None: """Add a hill (a half spheroid) at given position. If height == radius or -radius, the hill is a half-sphere. @@ -2473,7 +2488,9 @@ def heightmap_add_hill(hm: np.ndarray, x: float, y: float, radius: float, height @pending_deprecate() -def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float, height: float) -> None: +def heightmap_dig_hill( + hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, radius: float, height: float +) -> None: """ This function takes the highest value (if height > 0) or the lowest @@ -2494,7 +2511,7 @@ def heightmap_dig_hill(hm: np.ndarray, x: float, y: float, radius: float, height @pending_deprecate() def heightmap_rain_erosion( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", nbDrops: int, erosionCoef: float, sedimentationCoef: float, @@ -2523,7 +2540,7 @@ def heightmap_rain_erosion( @pending_deprecate() def heightmap_kernel_transform( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", kernelsize: int, dx: Sequence[int], dy: Sequence[int], @@ -2580,7 +2597,7 @@ def heightmap_kernel_transform( @pending_deprecate() def heightmap_add_voronoi( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", nbPoints: Any, nbCoef: int, coef: Sequence[float], @@ -2612,7 +2629,7 @@ def heightmap_add_voronoi( @deprecate("Arrays of noise should be sampled using the tcod.noise module.") def heightmap_add_fbm( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", noise: tcod.noise.Noise, mulx: float, muly: float, @@ -2660,7 +2677,7 @@ def heightmap_add_fbm( @deprecate("Arrays of noise should be sampled using the tcod.noise module.") def heightmap_scale_fbm( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", noise: tcod.noise.Noise, mulx: float, muly: float, @@ -2703,7 +2720,7 @@ def heightmap_scale_fbm( @pending_deprecate() def heightmap_dig_bezier( - hm: np.ndarray, + hm: "np.ndarray[Any, np.dtype[np.float32]]", px: Tuple[int, int, int, int], py: Tuple[int, int, int, int], startRadius: float, @@ -2736,7 +2753,7 @@ def heightmap_dig_bezier( @deprecate("This object can be accessed as a NumPy array.") -def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: +def heightmap_get_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int) -> float: """Return the value at ``x``, ``y`` in a heightmap. .. deprecated:: 2.0 @@ -2761,7 +2778,7 @@ def heightmap_get_value(hm: np.ndarray, x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_interpolated_value(hm: np.ndarray, x: float, y: float) -> float: +def heightmap_get_interpolated_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float) -> float: """Return the interpolated height at non integer coordinates. Args: @@ -2776,7 +2793,7 @@ def heightmap_get_interpolated_value(hm: np.ndarray, x: float, y: float) -> floa @pending_deprecate() -def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: +def heightmap_get_slope(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int) -> float: """Return the slope between 0 and (pi / 2) at given coordinates. Args: @@ -2791,7 +2808,9 @@ def heightmap_get_slope(hm: np.ndarray, x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_normal(hm: np.ndarray, x: float, y: float, waterLevel: float) -> Tuple[float, float, float]: +def heightmap_get_normal( + hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, waterLevel: float +) -> Tuple[float, float, float]: """Return the map normal at given coordinates. Args: @@ -2809,7 +2828,7 @@ def heightmap_get_normal(hm: np.ndarray, x: float, y: float, waterLevel: float) @deprecate("This function is deprecated, see documentation.") -def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: +def heightmap_count_cells(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float, ma: float) -> int: """Return the number of map cells which value is between ``mi`` and ``ma``. Args: @@ -2828,7 +2847,7 @@ def heightmap_count_cells(hm: np.ndarray, mi: float, ma: float) -> int: @pending_deprecate() -def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: +def heightmap_has_land_on_border(hm: "np.ndarray[Any, np.dtype[np.float32]]", waterlevel: float) -> bool: """Returns True if the map edges are below ``waterlevel``, otherwise False. Args: @@ -2842,7 +2861,7 @@ def heightmap_has_land_on_border(hm: np.ndarray, waterlevel: float) -> bool: @deprecate("Use `hm.min()` and `hm.max()` instead.") -def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]: +def heightmap_get_minmax(hm: "np.ndarray[Any, np.dtype[np.float32]]") -> Tuple[float, float]: """Return the min and max values of this heightmap. Args: @@ -3132,7 +3151,9 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: @deprecate("This function has been replaced by tcod.los.bresenham.") -def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> Tuple[np.ndarray, np.ndarray]: +def line_where( + x1: int, y1: int, x2: int, y2: int, inclusive: bool = True +) -> "Tuple[np.ndarray[Any, np.dtype[np.intc]], np.ndarray[Any, np.dtype[np.intc]]]": """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. diff --git a/tcod/los.py b/tcod/los.py index 011d416c..5d9ff25b 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -1,13 +1,13 @@ """This modules holds functions for NumPy-based line of sight algorithms. """ -from typing import Tuple +from typing import Any, Tuple import numpy as np from tcod.loader import ffi, lib -def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> np.ndarray: +def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> "np.ndarray[Any, np.dtype[np.intc]]": """Return a thin Bresenham line as a NumPy array of shape (length, 2). `start` and `end` are the endpoints of the line. @@ -56,6 +56,6 @@ def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> np.ndarray: x1, y1 = start x2, y2 = end length = lib.bresenham(x1, y1, x2, y2, 0, ffi.NULL) - array = np.ndarray((length, 2), dtype=np.intc) + array: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, 2), dtype=np.intc) lib.bresenham(x1, y1, x2, y2, length, ffi.from_buffer("int*", array)) return array diff --git a/tcod/map.py b/tcod/map.py index 918f57b2..76cc293c 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -99,18 +99,18 @@ def __as_cdata(self) -> Any: ) @property - def transparent(self) -> np.ndarray: - buffer = self.__buffer[:, :, 0] # type: np.ndarray + def transparent(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property - def walkable(self) -> np.ndarray: - buffer = self.__buffer[:, :, 1] # type: np.ndarray + def walkable(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property - def fov(self) -> np.ndarray: - buffer = self.__buffer[:, :, 2] # type: np.ndarray + def fov(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer def compute_fov( @@ -166,12 +166,12 @@ def __getstate__(self) -> Any: def compute_fov( - transparency: np.ndarray, + transparency: "np.ndarray[Any, Any]", pov: Tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, -) -> np.ndarray: +) -> "np.ndarray[Any, np.dtype[np.bool_]]": """Return a boolean mask of the area covered by a field-of-view. `transparency` is a 2 dimensional array where all non-zero values are diff --git a/tcod/noise.py b/tcod/noise.py index fff29e48..9cb01f2d 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -233,7 +233,7 @@ def get_point(self, x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> f """ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) - def __getitem__(self, indexes: Any) -> np.ndarray: + def __getitem__(self, indexes: Any) -> "np.ndarray[Any, np.dtype[np.float32]]": """Sample a noise map through NumPy indexing. This follows NumPy's advanced indexing rules, but allows for floating @@ -247,7 +247,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: raise IndexError( "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) ) - indexes = np.broadcast_arrays(*indexes) + indexes = np.broadcast_arrays(*indexes) # type: ignore c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): if index.dtype.type == np.object_: @@ -287,7 +287,7 @@ def __getitem__(self, indexes: Any) -> np.ndarray: return out - def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: + def sample_mgrid(self, mgrid: ArrayLike) -> "np.ndarray[Any, np.dtype[np.float32]]": """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -308,7 +308,7 @@ def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: raise ValueError( "mgrid.shape[0] must equal self.dimensions, " "%r[0] != %r" % (mgrid.shape, self.dimensions) ) - out = np.ndarray(mgrid.shape[1:], np.float32) + out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray(mgrid.shape[1:], np.float32) if mgrid.shape[1:] != out.shape: raise ValueError("mgrid.shape[1:] must equal out.shape, " "%r[1:] != %r" % (mgrid.shape, out.shape)) lib.NoiseSampleMeshGrid( @@ -319,7 +319,7 @@ def sample_mgrid(self, mgrid: ArrayLike) -> np.ndarray: ) return out - def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> np.ndarray: + def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> "np.ndarray[Any, np.dtype[np.float32]]": """Sample an open mesh-grid array and return the result. Args @@ -335,7 +335,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> np.ndarray: if len(ogrid) != self.dimensions: raise ValueError("len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions)) ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid] - out = np.ndarray([array.size for array in ogrids], np.float32) + out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( self._tdl_noise_c, len(ogrids), @@ -417,7 +417,7 @@ def grid( scale: Union[Tuple[float, ...], float], origin: Optional[Tuple[int, ...]] = None, indexing: Literal["ij", "xy"] = "xy", -) -> Tuple[np.ndarray, ...]: +) -> "Tuple[np.ndarray[Any, Any], ...]": """A helper function for generating a grid of noise samples. `shape` is the shape of the returned mesh grid. This can be any number of @@ -463,4 +463,4 @@ def grid( if len(shape) != len(origin): raise TypeError("shape must have the same length as origin") indexes = (np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin)) - return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) + return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) # type: ignore diff --git a/tcod/path.py b/tcod/path.py index 437420c2..b58cd228 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -117,7 +117,7 @@ def __init__( super(EdgeCostCallback, self).__init__(callback, shape) -class NodeCostArray(np.ndarray): +class NodeCostArray(np.ndarray): # type: ignore """Calculate cost from a numpy array of nodes. `array` is a NumPy array holding the path-cost of each node. @@ -297,7 +297,7 @@ def maxarray( shape: Tuple[int, ...], dtype: Any = np.int32, order: Literal["C", "F"] = "C", -) -> np.ndarray: +) -> "np.ndarray[Any, Any]": """Return a new array filled with the maximum finite value for `dtype`. `shape` is of the new array. Same as other NumPy array initializers. @@ -316,7 +316,7 @@ def maxarray( return np.full(shape, np.iinfo(dtype).max, dtype, order) -def _export_dict(array: np.ndarray) -> Dict[str, Any]: +def _export_dict(array: "np.ndarray[Any, Any]") -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" if array.dtype.type not in _INT_TYPES: raise TypeError("dtype was %s, but must be one of %s." % (array.dtype.type, tuple(_INT_TYPES.keys()))) @@ -329,14 +329,14 @@ def _export_dict(array: np.ndarray) -> Dict[str, Any]: } -def _export(array: np.ndarray) -> Any: +def _export(array: "np.ndarray[Any, Any]") -> Any: """Convert a NumPy array into a ctype object.""" return ffi.new("struct NArray*", _export_dict(array)) def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: """Return an edge_cost array using an integer map.""" - edge_map = np.copy(edge_map) + edge_map = np.array(edge_map, copy=True) if edge_map.ndim != 2: raise ValueError("edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 @@ -346,7 +346,7 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: edge_array = np.transpose(edge_nz) edge_array -= edge_center c_edges = ffi.new("int[]", len(edge_array) * 3) - edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3) + edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3) # type: ignore edges[:, :2] = edge_array edges[:, 2] = edge_map[edge_nz] return c_edges, len(edge_array) @@ -360,7 +360,7 @@ def dijkstra2d( *, edge_map: Any = None, out: Optional[np.ndarray] = ..., # type: ignore -) -> np.ndarray: +) -> "np.ndarray[Any, Any]": """Return the computed distance of all nodes on a 2D Dijkstra grid. `distance` is an input array of node distances. Is this often an @@ -488,7 +488,7 @@ def dijkstra2d( stacklevel=2, ) elif out is None: - out = np.copy(distance) + out = np.array(distance, copy=True) else: out[...] = dist @@ -512,9 +512,9 @@ def dijkstra2d( return out -def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: +def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: """Return an edge array using a boolean map.""" - edge_map = np.copy(edge_map) + edge_map = np.array(edge_map, copy=True) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 edge_map[edge_center] = 0 edge_array = np.transpose(edge_map.nonzero()) @@ -529,7 +529,7 @@ def hillclimb2d( diagonal: Optional[bool] = None, *, edge_map: Any = None, -) -> np.ndarray: +) -> "np.ndarray[Any, Any]": """Return a path on a grid from `start` to the lowest point. `distance` should be a fully computed distance array. This kind of array @@ -574,17 +574,17 @@ def hillclimb2d( else: func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal) length = _check(func(ffi.NULL)) - path = np.ndarray((length, 2), dtype=np.intc) + path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.from_buffer("int*", path) _check(func(c_path)) return path -def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: +def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> "np.ndarray[Any, Any]": """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( - np.meshgrid( + np.meshgrid( # type: ignore *(np.arange(i, dtype=dtype) for i in shape), indexing="ij", copy=False, @@ -594,7 +594,7 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> np.ndarray: ) -def _as_hashable(obj: Optional[np.ndarray]) -> Optional[Any]: +def _as_hashable(obj: "Optional[np.ndarray[Any, Any]]") -> Optional[Any]: """Return NumPy arrays as a more hashable form.""" if obj is None: return obj @@ -691,8 +691,8 @@ def add_edge( edge_dir: Tuple[int, ...], edge_cost: int = 1, *, - cost: np.ndarray, - condition: Optional[np.ndarray] = None, + cost: "np.ndarray[Any, Any]", + condition: "Optional[np.ndarray[Any, Any]]" = None, ) -> None: """Add a single edge rule. @@ -781,9 +781,9 @@ def add_edge( def add_edges( self, *, - edge_map: Any, - cost: np.ndarray, - condition: Optional[np.ndarray] = None, + edge_map: ArrayLike, + cost: "np.ndarray[Any, Any]", + condition: "Optional[np.ndarray[Any, Any]]" = None, ) -> None: """Add a rule with multiple edges. @@ -876,9 +876,9 @@ def add_edges( ], ] """ - edge_map = np.copy(edge_map) + edge_map = np.array(edge_map, copy=True) if edge_map.ndim < self._ndim: - edge_map = edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)] + edge_map = np.asarray(edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)]) if edge_map.ndim != self._ndim: raise TypeError("edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim)) if self._order == "F": @@ -1024,7 +1024,7 @@ class SimpleGraph: .. versionadded:: 11.15 """ - def __init__(self, *, cost: np.ndarray, cardinal: int, diagonal: int, greed: int = 1): + def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1): cost = np.asarray(cost) if cost.ndim != 2: raise TypeError("The cost array must e 2 dimensional, array of shape %r given." % (cost.shape,)) @@ -1093,7 +1093,7 @@ def __init__(self, graph: Union[CustomGraph, SimpleGraph]): self._heuristic_p = ffi.NULL # type: Any @property - def distance(self) -> np.ndarray: + def distance(self) -> "np.ndarray[Any, Any]": """The distance values of the pathfinder. The array returned from this property maintains the graphs `order`. @@ -1113,7 +1113,7 @@ def distance(self) -> np.ndarray: return self._distance.T if self._order == "F" else self._distance @property - def traversal(self) -> np.ndarray: + def traversal(self) -> "np.ndarray[Any, Any]": """An array used to generate paths from any point to the nearest root. The array returned from this property maintains the graphs `order`. @@ -1272,7 +1272,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: self._update_heuristic(goal) self._graph._resolve(self) - def path_from(self, index: Tuple[int, ...]) -> np.ndarray: + def path_from(self, index: Tuple[int, ...]) -> "np.ndarray[Any, Any]": """Return the shortest path from `index` to the nearest root. The returned array is of shape `(length, ndim)` where `length` is the @@ -1313,7 +1313,7 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: if self._order == "F": # Convert to ij indexing order. index = index[::-1] length = _check(lib.get_travel_path(self._graph._ndim, self._travel_p, index, ffi.NULL)) - path = np.ndarray((length, self._graph._ndim), dtype=np.intc) + path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, self._graph._ndim), dtype=np.intc) _check( lib.get_travel_path( self._graph._ndim, @@ -1324,7 +1324,7 @@ def path_from(self, index: Tuple[int, ...]) -> np.ndarray: ) return path[:, ::-1] if self._order == "F" else path # type: ignore - def path_to(self, index: Tuple[int, ...]) -> np.ndarray: + def path_to(self, index: Tuple[int, ...]) -> "np.ndarray[Any, Any]": """Return the shortest path from the nearest root to `index`. See :any:`path_from`. diff --git a/tcod/sdl.py b/tcod/sdl.py index 3ccd243f..be31e161 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -15,7 +15,7 @@ class _TempSurface: """Holds a temporary surface derived from a NumPy array.""" - def __init__(self, pixels: np.ndarray) -> None: + def __init__(self, pixels: "np.ndarray[Any, np.dtype[np.uint8]]") -> None: self._array = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape,)) @@ -52,7 +52,7 @@ def __init__(self, sdl_window_p: Any) -> None: def __eq__(self, other: Any) -> bool: return bool(self.p == other.p) - def set_icon(self, image: np.ndarray) -> None: + def set_icon(self, image: "np.ndarray[Any, np.dtype[np.uint8]]") -> None: """Set the window icon from an image. `image` is a C memory order RGB or RGBA NumPy array. diff --git a/tcod/tileset.py b/tcod/tileset.py index 407f2b32..7a199f7e 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -21,6 +21,11 @@ from tcod._internal import _check, _console, _raise_tcod_error, deprecate from tcod.loader import ffi, lib +try: + from numpy.typing import ArrayLike +except ImportError: # Python < 3.7, Numpy < 1.20 + from typing import Any as ArrayLike + class Tileset: """A collection of graphical tiles. @@ -62,7 +67,7 @@ def __contains__(self, codepoint: int) -> bool: """Test if a tileset has a codepoint with ``n in tileset``.""" return bool(lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) == 0) - def get_tile(self, codepoint: int) -> np.ndarray: + def get_tile(self, codepoint: int) -> "np.ndarray[Any, np.dtype[np.uint8]]": """Return a copy of a tile for the given codepoint. If the tile does not exist yet then a blank array will be returned. @@ -79,7 +84,7 @@ def get_tile(self, codepoint: int) -> np.ndarray: ) return tile - def set_tile(self, codepoint: int, tile: np.ndarray) -> None: + def set_tile(self, codepoint: int, tile: ArrayLike) -> None: """Upload a tile into this array. The tile can be in 32-bit color (height, width, rgba), or grey-scale @@ -103,7 +108,7 @@ def set_tile(self, codepoint: int, tile: np.ndarray) -> None: ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) - def render(self, console: tcod.console.Console) -> np.ndarray: + def render(self, console: tcod.console.Console) -> "np.ndarray[Any, np.dtype[np.uint8]]": """Render an RGBA array, using console with this tileset. `console` is the Console object to render, this can not be the root diff --git a/tests/test_noise.py b/tests/test_noise.py index 2067b852..ed77dce6 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -40,17 +40,17 @@ def test_noise_class( noise.sample_mgrid(np.mgrid[:2, :3]) noise.sample_ogrid(np.ogrid[:2, :3]) - np.testing.assert_equal( + np.testing.assert_equal( # type: ignore noise.sample_mgrid(np.mgrid[:2, :3]), noise.sample_ogrid(np.ogrid[:2, :3]), ) - np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])]) + np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])]) # type: ignore repr(noise) def test_noise_samples() -> None: noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE) - np.testing.assert_equal( + np.testing.assert_equal( # type: ignore noise.sample_mgrid(np.mgrid[:32, :24]), noise.sample_ogrid(np.ogrid[:32, :24]), ) @@ -77,7 +77,7 @@ def test_noise_pickle(implementation: tcod.noise.Implementation) -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) noise = tcod.noise.Noise(2, implementation, seed=rand) noise2 = copy.copy(noise) - np.testing.assert_equal( + np.testing.assert_equal( # type: ignore noise.sample_ogrid(np.ogrid[:3, :1]), noise2.sample_ogrid(np.ogrid[:3, :1]), ) @@ -87,7 +87,7 @@ def test_noise_copy() -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) noise = tcod.noise.Noise(2, seed=rand) noise2 = pickle.loads(pickle.dumps(noise)) - np.testing.assert_equal( + np.testing.assert_equal( # type: ignore noise.sample_ogrid(np.ogrid[:3, :1]), noise2.sample_ogrid(np.ogrid[:3, :1]), ) From 37bc8e21e74985d74ac9fb16ba3eba2bc998b2ce Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Jun 2021 20:07:03 -0700 Subject: [PATCH 0558/1101] Prepare 12.7.0 release. --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b82b9c74..fea597b5 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,9 +8,15 @@ v2.0.0 Unreleased ------------------ + +12.7.0 - 2021-06-29 +------------------- Added - *tcod.image* and *tcod.tileset* now support *pathlib*. +Fixed + - Wheels for 32-bit Windows now deploy again. + 12.6.2 - 2021-06-15 ------------------- Fixed From b09945b9b4e069f0a6480c5d7c25911c87bff14b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Jun 2021 07:52:58 -0700 Subject: [PATCH 0559/1101] Deploy universal2 and arm64 wheels on OSX. Automatically generate ABI wheels on all platforms. --- .github/workflows/python-package-macos.yml | 72 ++++++---------------- setup.cfg | 3 + 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index b0b6c6d4..4de60275 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: ["macos-latest"] - python-version: ["3.7.9"] + python-version: ["3.x"] steps: # v2 breaks `git describe` so v1 is needed. @@ -24,41 +24,33 @@ jobs: - name: Print git describe run: | git describe - - name: Set up Mac Python ${{ matrix.python-version }} - # actions/setup-python can't be used as it builds less portable extensions. - env: - MB_PYTHON_VERSION: ${{ matrix.python-version }} - run: | - source .travis/install_python.sh - install_python - $PYTHON_EXE -m venv venv - source venv/bin/activate - echo "$PATH" >> $GITHUB_PATH - - name: Print Python version - run: | - python -VV + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark wheel twine - pip install git+https://github.com/HexDecimal/delocate.git@loader_path + pip install wheel twine if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Build package. - run: | - python setup.py sdist develop bdist_wheel --py-limited-api=cp36 - - name: Test with pytest - run: | - pytest --no-window --cov-report=xml - - uses: codecov/codecov-action@v1 - - name: Package binary files + - name: Prepare package. + # Downloads SDL2 for the later step. run: | - delocate-wheel -v dist/*.whl - delocate-listdeps --all dist/*.whl + python setup.py check + - name: Build wheels + uses: pypa/cibuildwheel@v2.0.0a4 + env: + CIBW_BUILD: cp38-* pp* + CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/HexDecimal/delocate.git@loader_path + CIBW_BEFORE_TEST: pip install numpy + CIBW_TEST_COMMAND: python -c "import tcod" + CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" - name: Archive wheel uses: actions/upload-artifact@v2 with: name: wheel-macos - path: dist/*.whl + path: wheelhouse/*.whl retention-days: 1 - name: Upload to PyPI if: github.event_name == 'release' @@ -66,28 +58,4 @@ jobs: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - twine upload --skip-existing dist/* - - isolated_test: - name: Verify wheel dependencies - needs: build - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ["macos-latest"] - python-version: ["3.x"] - steps: - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - uses: actions/download-artifact@v2 - with: - name: wheel-macos - path: dist - - name: Install tcod from wheel - run: | - python -m pip install dist/*.whl - - name: Verify dependency correctness - run: | - python -c "import tcod" + twine upload --skip-existing wheelhouse/* diff --git a/setup.cfg b/setup.cfg index 7ca32566..5741e285 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,9 @@ pyinstaller40 = hook-dirs = tcod.__pyinstaller:get_hook_dirs +[bdist_wheel] +py-limited-api = cp36 + [aliases] test=pytest From c5514a7031a38c3f0a23a672010d581efa90b043 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Jun 2021 11:24:54 -0700 Subject: [PATCH 0560/1101] Prepare 12.7.1 release. --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fea597b5..75c0ffd6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,11 @@ v2.0.0 Unreleased ------------------ +12.7.1 - 2021-06-30 +------------------- +Added + - Started uploading wheels for ARM64 macOS. + 12.7.0 - 2021-06-29 ------------------- Added From f63fb7b094de1286890289f95694218d62d68774 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Jul 2021 10:50:08 -0700 Subject: [PATCH 0561/1101] Accept unknown values in Scancode and KeySym enums. Fixes a crash with the event handling. --- CHANGELOG.rst | 2 ++ tcod/event.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 75c0ffd6..a0217c2f 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - *Scancode* and *KeySym* enums no longer crash when SDL returns an unexpected value. 12.7.1 - 2021-06-30 ------------------- diff --git a/tcod/event.py b/tcod/event.py index 2020480c..a356cae9 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1598,6 +1598,14 @@ def scancode(self) -> "Scancode": """ return self + @classmethod + def _missing_(cls, value: object) -> "Optional[Scancode]": + if not isinstance(value, int): + return None + result = cls(0) + result._value_ = value + return result + def __eq__(self, other: Any) -> bool: if isinstance(other, KeySym): raise TypeError( @@ -2137,6 +2145,14 @@ def scancode(self) -> Scancode: _init_sdl_video() return Scancode(lib.SDL_GetScancodeFromKey(self.value)) + @classmethod + def _missing_(cls, value: object) -> "Optional[KeySym]": + if not isinstance(value, int): + return None + result = cls(0) + result._value_ = value + return result + def __eq__(self, other: Any) -> bool: if isinstance(other, Scancode): raise TypeError( From 55f8cf4352e22903a3a89dadcc5192a8ddf3b043 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Jul 2021 11:02:04 -0700 Subject: [PATCH 0562/1101] Prepare 12.7.2 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0217c2f..40e61f9d 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.7.2 - 2021-07-01 +------------------- Fixed - *Scancode* and *KeySym* enums no longer crash when SDL returns an unexpected value. From e79ee640f902e4c63810b2c44c6536bcf6ed4656 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Jul 2021 11:09:20 -0700 Subject: [PATCH 0563/1101] Ignore releases and upload on tags instead. This skips a redundant job when new versions are released. --- .github/workflows/python-package-macos.yml | 4 +--- .github/workflows/python-package.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 4de60275..63517c77 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -3,8 +3,6 @@ name: Python package (MacOS) on: push: pull_request: - release: - types: [created] jobs: build: @@ -53,7 +51,7 @@ jobs: path: wheelhouse/*.whl retention-days: 1 - name: Upload to PyPI - if: github.event_name == 'release' + if: startsWith(github.ref, 'refs/tags/') env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3fe12aa7..745e7895 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -6,8 +6,6 @@ name: Package on: push: pull_request: - release: - types: [created] defaults: run: @@ -68,7 +66,7 @@ jobs: xvfb-run --auto-servernum pytest --cov-report=xml - uses: codecov/codecov-action@v1 - name: Upload to PyPI - if: github.event_name == 'release' && runner.os != 'Linux' + if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} From f7cf2fb4ea6afcb7531f7e835205eee76bcb04e9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 Jul 2021 07:17:28 -0700 Subject: [PATCH 0564/1101] Clean up Console dtypes. --- tcod/console.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 3ed7f030..3fdda330 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -24,14 +24,18 @@ def _fmt(string: str) -> bytes: _root_console = None -rgba_graphic = np.dtype([("ch", np.intc), ("fg", "4u1"), ("bg", "4u1")]) -"""A NumPy :any:`dtype` compatible with :any:`Console.buffer`. +rgba_graphic = np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")]) +"""A NumPy :any:`dtype` compatible with :any:`Console.rgba`. + +This dtype is: ``np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")])`` .. versionadded:: 12.3 """ -rgb_graphic = np.dtype([("ch", np.intc), ("fg", "3u1"), ("bg", "3u1")]) -"""A NumPy :any:`dtype` compatible with :any:`Console.tiles_rgb`. +rgb_graphic = np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")]) +"""A NumPy :any:`dtype` compatible with :any:`Console.rgb`. + +This dtype is: ``np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")])`` .. versionadded:: 12.3 """ @@ -93,9 +97,9 @@ class Console: Added an alpha channel to the color types. """ - DTYPE = np.dtype([("ch", np.intc), ("fg", "4u1"), ("bg", "4u1")]) + DTYPE = rgba_graphic - # A structured arrays type with the added "fg_rgb" and "bg_rgb" fields. + # A structured array type with the added "fg_rgb" and "bg_rgb" fields. _DTYPE_RGB = np.dtype( { "names": ["ch", "fg", "bg"], @@ -318,8 +322,8 @@ def rgb(self) -> "np.ndarray[Any, Any]": The axes of this array is affected by the `order` parameter given to initialize the console. - The :any:`rgb_graphic` can be used to make arrays similar to these - independent of a :any:`Console`. + The :any:`rgb_graphic` dtype can be used to make arrays compatiable + with this attribute that are independent of a :any:`Console`. Example: >>> con = tcod.console.Console(10, 2) From db9cdd2753ee32a271ef74f5baf5c1b4a4ffe687 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 Jul 2021 09:40:30 -0700 Subject: [PATCH 0565/1101] Fix docs typo. --- tcod/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index a356cae9..8228af38 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1071,7 +1071,7 @@ class Scancode(enum.IntEnum): For example the scan codes for WASD remain in the same physical location regardless of the actual keyboard layout. - These names are derived from SDL expect for the numbers which are prefixed + These names are derived from SDL except for the numbers which are prefixed with ``N`` (since raw numbers can not be a Python name.) .. versionadded:: 12.3 @@ -1621,7 +1621,7 @@ def __hash__(self) -> int: class KeySym(enum.IntEnum): """Keyboard constants based on their symbol. - These names are derived from SDL expect for the numbers which are prefixed + These names are derived from SDL except for the numbers which are prefixed with ``N`` (since raw numbers can not be a Python name.) .. versionadded:: 12.3 From 04901bfec99862662e0a4e94699e72d1985c9f47 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Jul 2021 14:24:54 -0700 Subject: [PATCH 0566/1101] Update context docs. --- tcod/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 87005cc1..e05be5a4 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -144,8 +144,7 @@ def _handle_title(title: Optional[str]) -> Any: class Context: """Context manager for libtcod context objects. - You should use :any:`tcod.context.new_terminal` or - :any:`tcod.context.new_window` to create a new context. + Use :any:`tcod.context.new` to create a new context. """ def __init__(self, context_p: Any): From ccb5d49b8625f5e8dbb13a45ab86c2308e2e90f7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 17 Jul 2021 06:24:54 -0700 Subject: [PATCH 0567/1101] Deprecate more libtcodpy functions. --- CHANGELOG.rst | 4 ++++ tcod/libtcodpy.py | 56 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 40e61f9d..2160d995 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,10 @@ v2.0.0 Unreleased ------------------ +Deprecated + - `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`. + - `tcod.console_from_file` is deprecated. + - The `.asc` and `.apf` formats are no longer actively supported. 12.7.2 - 2021-07-01 ------------------- diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 99eb8810..a62e5a78 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1720,6 +1720,12 @@ def console_wait_for_keypress(flush: bool) -> Key: .. deprecated:: 9.3 Use the :any:`tcod.event.wait` function to wait for events. + + Example:: + + for event in tcod.event.wait(): + if isinstance(event, tcod.event.KeyDown): + ... """ key = Key() lib.TCOD_console_wait_for_keypress_wrapper(key.key_p, flush) @@ -1731,14 +1737,24 @@ def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: """ .. deprecated:: 9.3 Use the :any:`tcod.event.get` function to check for events. + + Example:: + + for event in tcod.event.get(): + if isinstance(event, tcod.event.KeyDown): + ... """ key = Key() lib.TCOD_console_check_for_keypress_wrapper(key.key_p, flags) return key -@pending_deprecate() +@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.") def console_is_key_pressed(key: int) -> bool: + """ + .. deprecated:: 12.7 + Use :any:`tcod.event.get_keyboard_state` to check if a key is held. + """ return bool(lib.TCOD_console_is_key_pressed(key)) @@ -1754,6 +1770,7 @@ def console_new(w: int, h: int) -> tcod.console.Console: return tcod.console.Console(w, h) +@deprecate("This loading method is no longer supported, use tcod.console_load_xp instead.") def console_from_file(filename: str) -> tcod.console.Console: """Return a new console object from a filename. @@ -1764,6 +1781,11 @@ def console_from_file(filename: str) -> tcod.console.Console: filename (Text): The path to the file, as a string. Returns: A new :any`Console` instance. + + .. deprecated:: 12.7 + Use :any:`tcod.console_load_xp` to load REXPaint consoles. + + Other formats are not actively supported. """ if not os.path.exists(filename): raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) @@ -1933,27 +1955,43 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: lib.TCOD_console_fill_char(_console(con), carr) -@pending_deprecate() +@deprecate("This format is not actively supported") def console_load_asc(con: tcod.console.Console, filename: str) -> bool: - """Update a console from a non-delimited ASCII `.asc` file.""" + """Update a console from a non-delimited ASCII `.asc` file. + + .. deprecated:: 12.7 + This format is no longer supported. + """ return bool(lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8"))) -@pending_deprecate() +@deprecate("This format is not actively supported") def console_save_asc(con: tcod.console.Console, filename: str) -> bool: - """Save a console to a non-delimited ASCII `.asc` file.""" + """Save a console to a non-delimited ASCII `.asc` file. + + .. deprecated:: 12.7 + This format is no longer supported. + """ return bool(lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8"))) -@pending_deprecate() +@deprecate("This format is not actively supported") def console_load_apf(con: tcod.console.Console, filename: str) -> bool: - """Update a console from an ASCII Paint `.apf` file.""" + """Update a console from an ASCII Paint `.apf` file. + + .. deprecated:: 12.7 + This format is no longer supported. + """ return bool(lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8"))) -@pending_deprecate() +@deprecate("This format is not actively supported") def console_save_apf(con: tcod.console.Console, filename: str) -> bool: - """Save a console to an ASCII Paint `.apf` file.""" + """Save a console to an ASCII Paint `.apf` file. + + .. deprecated:: 12.7 + This format is no longer supported. + """ return bool(lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8"))) From 58bae5f6f6e844d05a1f84583e246e16623d409c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 17 Jul 2021 06:30:31 -0700 Subject: [PATCH 0568/1101] Update delocate Git reference. --- .github/workflows/python-package-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml index 63517c77..ec264af9 100644 --- a/.github/workflows/python-package-macos.yml +++ b/.github/workflows/python-package-macos.yml @@ -40,7 +40,7 @@ jobs: env: CIBW_BUILD: cp38-* pp* CIBW_ARCHS_MACOS: x86_64 arm64 universal2 - CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/HexDecimal/delocate.git@loader_path + CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/matthew-brett/delocate.git@1c07b50 CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" From 6d85d074760b6c6a2b09307f4bf998503a10ca27 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jul 2021 11:57:47 -0700 Subject: [PATCH 0569/1101] Setup manylinux wheels. --- .github/workflows/python-package.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 745e7895..c5ae906b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -108,3 +108,43 @@ jobs: - name: Confirm package import run: | python -c "import tcod" + + linux_wheels: + runs-on: "ubuntu-20.04" + steps: + - uses: actions/checkout@v1 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install twine cibuildwheel==2.0.0 + - name: Build wheels + run: | + python -m cibuildwheel --output-dir wheelhouse + env: + CIBW_BUILD: cp36-* pp* + CIBW_ARCHS_LINUX: "x86_64" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014 + CIBW_BEFORE_ALL_LINUX: yum install -y SDL2-devel + CIBW_BEFORE_TEST: pip install numpy + CIBW_TEST_COMMAND: python -c "import tcod" + - name: Archive wheel + uses: actions/upload-artifact@v2 + with: + name: wheel-linux + path: wheelhouse/*.whl + retention-days: 1 + - name: Upload to PyPI + if: startsWith(github.ref, 'refs/tags/') + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing wheelhouse/* From 0715349184112862a9874f210b582cbef4281c50 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jul 2021 19:17:44 -0700 Subject: [PATCH 0570/1101] Add FAQ about adding custom tiles. --- docs/faq.rst | 20 ++++++++++++++++++++ tcod/tileset.py | 5 ++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index feed1459..109ac981 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -9,6 +9,7 @@ This can either be your own custom tool or you can copy the Clock class from the `framerate.py `_ example. + I get ``No module named 'tcod'`` when I try to ``import tcod`` in PyCharm. -------------------------------------------------------------------------- @@ -29,4 +30,23 @@ Once this file is saved to your projects root directory then PyCharm will detect Alternatively you can open the `Terminal` tab in PyCharm and run ``pip install tcod`` there. This will install `tcod` to the currently open project. +How do I add custom tiles? +-------------------------- + +Libtcod uses Unicode to identify tiles. +To prevent conflicts with real glyphs you should decide on codepoints from a `Private Use Area `_ before continuing. +If you're unsure, then use the codepoints from ``0x100000`` to ``0x10FFFD`` for your custom tiles. + +Normally you load a font with :func:`tcod.tileset.load_tilesheet` which will return a :any:`Tileset` that gets passed to :func:`tcod.context.new`'s `tileset` parameter. +:func:`tcod.tileset.load_tilesheet` assigns the codepoints from `charmap` to the tilesheet in row-major order. + +There are two ways to extend a tileset like the above: + +- Increase the tilesheet size vertically and update the `rows` parameter in :func:`tcod.tileset.load_tilesheet` to match the new image size, then modify the `charmap` parameter to map the new tiles to codepoints. + If you edited a CP437 tileset this way then you'd add your new codepoints to the end of :any:`tcod.tileset.CHARMAP_CP437` before using the result as the `charmap` parameter. + You can also use :any:`Tileset.remap` if you want to reassign tiles based on their position rather than editing `charmap`. +- Or do not modify the original tilesheet. + Load the tileset normally, then add new tiles with :any:`Tileset.set_tile` with manually loaded images. + + .. _PyCharm: https://www.jetbrains.com/pycharm/ diff --git a/tcod/tileset.py b/tcod/tileset.py index 7a199f7e..af61f49d 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -278,9 +278,8 @@ def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Opt `columns` and `rows` is the shape of the tileset. Tiles are assumed to take up the entire space of the image. - `charmap` is the character mapping to use. This is a list or generator - of codepoints which map the tiles like this: - ``charmap[tile_index] = codepoint``. + `charmap` is a sequence of codepoints to map the tilesheet to in row-major order. + This is a list or generator of codepoints which map the tiles like this: ``charmap[tile_index] = codepoint``. For common tilesets `charmap` should be :any:`tcod.tileset.CHARMAP_CP437`. Generators will be sliced so :any:`itertools.count` can be used which will give all tiles the same codepoint as their index, but this will not map From 7053a993de235a01d5490a732e405134614419c5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jul 2021 19:20:43 -0700 Subject: [PATCH 0571/1101] Prevent the ArrayLike type from expanding into a mess in the documentation. This type will need to be quoted until future annotations are enabled. --- tcod/noise.py | 4 ++-- tcod/path.py | 14 +++++++------- tcod/tileset.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tcod/noise.py b/tcod/noise.py index 9cb01f2d..5297911e 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -287,7 +287,7 @@ def __getitem__(self, indexes: Any) -> "np.ndarray[Any, np.dtype[np.float32]]": return out - def sample_mgrid(self, mgrid: ArrayLike) -> "np.ndarray[Any, np.dtype[np.float32]]": + def sample_mgrid(self, mgrid: "ArrayLike") -> "np.ndarray[Any, np.dtype[np.float32]]": """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -319,7 +319,7 @@ def sample_mgrid(self, mgrid: ArrayLike) -> "np.ndarray[Any, np.dtype[np.float32 ) return out - def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> "np.ndarray[Any, np.dtype[np.float32]]": + def sample_ogrid(self, ogrid: "Sequence[ArrayLike]") -> "np.ndarray[Any, np.dtype[np.float32]]": """Sample an open mesh-grid array and return the result. Args diff --git a/tcod/path.py b/tcod/path.py index b58cd228..2bb3439d 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -135,7 +135,7 @@ class NodeCostArray(np.ndarray): # type: ignore np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), } - def __new__(cls, array: ArrayLike) -> "NodeCostArray": + def __new__(cls, array: "ArrayLike") -> "NodeCostArray": """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) return self @@ -353,8 +353,8 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: def dijkstra2d( - distance: ArrayLike, - cost: ArrayLike, + distance: "ArrayLike", + cost: "ArrayLike", cardinal: Optional[int] = None, diagonal: Optional[int] = None, *, @@ -512,7 +512,7 @@ def dijkstra2d( return out -def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: +def _compile_bool_edges(edge_map: "ArrayLike") -> Tuple[Any, int]: """Return an edge array using a boolean map.""" edge_map = np.array(edge_map, copy=True) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 @@ -523,7 +523,7 @@ def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: def hillclimb2d( - distance: ArrayLike, + distance: "ArrayLike", start: Tuple[int, int], cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, @@ -781,7 +781,7 @@ def add_edge( def add_edges( self, *, - edge_map: ArrayLike, + edge_map: "ArrayLike", cost: "np.ndarray[Any, Any]", condition: "Optional[np.ndarray[Any, Any]]" = None, ) -> None: @@ -1024,7 +1024,7 @@ class SimpleGraph: .. versionadded:: 11.15 """ - def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1): + def __init__(self, *, cost: "ArrayLike", cardinal: int, diagonal: int, greed: int = 1): cost = np.asarray(cost) if cost.ndim != 2: raise TypeError("The cost array must e 2 dimensional, array of shape %r given." % (cost.shape,)) diff --git a/tcod/tileset.py b/tcod/tileset.py index af61f49d..c360f651 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -84,7 +84,7 @@ def get_tile(self, codepoint: int) -> "np.ndarray[Any, np.dtype[np.uint8]]": ) return tile - def set_tile(self, codepoint: int, tile: ArrayLike) -> None: + def set_tile(self, codepoint: int, tile: "ArrayLike") -> None: """Upload a tile into this array. The tile can be in 32-bit color (height, width, rgba), or grey-scale From 689e870ac12fe74d8aa7eab49c1c2e46ab58df57 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 6 Aug 2021 04:08:54 -0700 Subject: [PATCH 0572/1101] Fix REXPaint loading example. The boolean array was the wrong shape with an extra dimension along the color channels. --- tcod/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index 3fdda330..3377ece8 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1300,7 +1300,7 @@ def load_xp(path: Union[str, Path], order: Literal["C", "F"] = "C") -> Tuple[Con # Apply REXPaint's alpha key color. KEY_COLOR = (255, 0, 255) - is_transparent = console.rgb["bg"] == KEY_COLOR + is_transparent = (console.rgb["bg"] == KEY_COLOR).all(axis=-1) console.rgba[is_transparent] = (ord(" "), (0,), (0,)) """ if not os.path.exists(path): From a0fca3af243861d4191e75d93a99f68fb4b8e22c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Aug 2021 12:30:13 -0700 Subject: [PATCH 0573/1101] Add tests for parsing SDL headers. --- .github/workflows/parse-sdl.yml | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/parse-sdl.yml diff --git a/.github/workflows/parse-sdl.yml b/.github/workflows/parse-sdl.yml new file mode 100644 index 00000000..91cff756 --- /dev/null +++ b/.github/workflows/parse-sdl.yml @@ -0,0 +1,39 @@ +# This makes sure that the latest versions of the SDL headers parse correctly. + +name: Parse SDL + +on: + push: + pull_request: + +defaults: + run: + shell: bash + +jobs: + parse_sdl: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["windows-2019", "macos-latest"] + python-version: ["3.x"] + sdl-version: ["2.0.14", "2.0.16"] + fail-fast: true + + steps: + - uses: actions/checkout@v1 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + - name: Build package. + run: | + python setup.py build + env: + SDL_VERSION: ${{ matrix.sdl-version }} From f8103220c14f022ff2c073d7a5bbbc39e9465d85 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Aug 2021 12:56:25 -0700 Subject: [PATCH 0574/1101] Fix the parsing of SDL 2.0.16 headers. SDL event padding is now derived from the compiler. SDL_DEPRECATED is now ignored correctly. Fixes #110 --- parse_sdl2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/parse_sdl2.py b/parse_sdl2.py index f56ecd24..486a30c4 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -22,6 +22,7 @@ RE_DEFINE_TRUNCATE = re.compile(r"(#define\s+\w+\s+).+$", flags=re.DOTALL) RE_TYPEDEF_TRUNCATE = re.compile(r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE) RE_ENUM_TRUNCATE = re.compile(r"(\w+\s*=).+?(?=,$|})(?![^(']*\))", re.MULTILINE | re.DOTALL) +RE_EVENT_PADDING = re.compile(r"Uint8 padding\[[^]]+\];", re.MULTILINE | re.DOTALL) def get_header(name: str) -> str: @@ -76,6 +77,7 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: typedef = typedef.replace("SDLCALL", " ") typedef = typedef.replace("SDL_AUDIOCVT_PACKED ", "") + typedef = RE_EVENT_PADDING.sub("Uint8 padding[...];", typedef) if NEEDS_PACK4 and "typedef struct SDL_AudioCVT" in typedef: typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) @@ -93,8 +95,9 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: if "va_list" in decl: continue decl = re.sub(r"SDL_PRINTF_VARARG_FUNC\(\w*\)", "", decl) - decl = decl.replace("extern DECLSPEC ", "") - decl = decl.replace("SDLCALL", " ") + decl = decl.replace("SDL_DEPRECATED", "") + decl = decl.replace("SDLCALL", "") + decl = re.sub(r"extern\s+DECLSPEC", "", decl) yield decl.replace("SDL_PRINTF_FORMAT_STRING ", "") From 5c8e43bb0f99c9a6b6b961c11f3b19be94b883f6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Aug 2021 13:10:10 -0700 Subject: [PATCH 0575/1101] Update changelog. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2160d995..1e528411 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,9 @@ Deprecated - `tcod.console_from_file` is deprecated. - The `.asc` and `.apf` formats are no longer actively supported. +Fixed + - Fixed the parsing of SDL 2.0.16 headers. + 12.7.2 - 2021-07-01 ------------------- Fixed From 2521db3647fcdedc4991c0cb0850bc6ed0869bd8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Aug 2021 13:28:32 -0700 Subject: [PATCH 0576/1101] Prepare 12.7.3 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1e528411..a12cc2f8 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +12.7.3 - 2021-08-13 +------------------- Deprecated - `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`. - `tcod.console_from_file` is deprecated. From 221813dc8fd9cc0ade1512612a3048a3ceb3e44f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 22 Aug 2021 07:13:53 -0700 Subject: [PATCH 0577/1101] Disable smart quotes in Sphinx for character tables. --- docs/tcod/charmap-reference.rst | 848 +++++++++++++++--------------- scripts/generate_charmap_table.py | 14 +- 2 files changed, 437 insertions(+), 425 deletions(-) diff --git a/docs/tcod/charmap-reference.rst b/docs/tcod/charmap-reference.rst index 9576b122..0fa8150a 100644 --- a/docs/tcod/charmap-reference.rst +++ b/docs/tcod/charmap-reference.rst @@ -43,266 +43,266 @@ https://dwarffortresswiki.org/index.php/Tileset_repository Wikipedia also has a good reference for this character mapping: https://en.wikipedia.org/wiki/Code_page_437 -============ ========= ======== ================================================== - Tile Index Unicode String Name -============ ========= ======== ================================================== - 0 0x00 '\\x00' - 1 0x263A '☺' WHITE SMILING FACE - 2 0x263B '☻' BLACK SMILING FACE - 3 0x2665 '♥' BLACK HEART SUIT - 4 0x2666 '♦' BLACK DIAMOND SUIT - 5 0x2663 '♣' BLACK CLUB SUIT - 6 0x2660 '♠' BLACK SPADE SUIT - 7 0x2022 '•' BULLET - 8 0x25D8 '◘' INVERSE BULLET - 9 0x25CB '○' WHITE CIRCLE - 10 0x25D9 '◙' INVERSE WHITE CIRCLE - 11 0x2642 '♂' MALE SIGN - 12 0x2640 '♀' FEMALE SIGN - 13 0x266A '♪' EIGHTH NOTE - 14 0x266B '♫' BEAMED EIGHTH NOTES - 15 0x263C '☼' WHITE SUN WITH RAYS - 16 0x25BA '►' BLACK RIGHT-POINTING POINTER - 17 0x25C4 '◄' BLACK LEFT-POINTING POINTER - 18 0x2195 '↕' UP DOWN ARROW - 19 0x203C '‼' DOUBLE EXCLAMATION MARK - 20 0xB6 '¶' PILCROW SIGN - 21 0xA7 '§' SECTION SIGN - 22 0x25AC '▬' BLACK RECTANGLE - 23 0x21A8 '↨' UP DOWN ARROW WITH BASE - 24 0x2191 '↑' UPWARDS ARROW - 25 0x2193 '↓' DOWNWARDS ARROW - 26 0x2192 '→' RIGHTWARDS ARROW - 27 0x2190 '←' LEFTWARDS ARROW - 28 0x221F '∟' RIGHT ANGLE - 29 0x2194 '↔' LEFT RIGHT ARROW - 30 0x25B2 '▲' BLACK UP-POINTING TRIANGLE - 31 0x25BC '▼' BLACK DOWN-POINTING TRIANGLE - 32 0x20 ' ' SPACE - 33 0x21 '!' EXCLAMATION MARK - 34 0x22 '"' QUOTATION MARK - 35 0x23 '#' NUMBER SIGN - 36 0x24 '$' DOLLAR SIGN - 37 0x25 '%' PERCENT SIGN - 38 0x26 '&' AMPERSAND - 39 0x27 "'" APOSTROPHE - 40 0x28 '(' LEFT PARENTHESIS - 41 0x29 ')' RIGHT PARENTHESIS - 42 0x2A '\*' ASTERISK - 43 0x2B '+' PLUS SIGN - 44 0x2C ',' COMMA - 45 0x2D '-' HYPHEN-MINUS - 46 0x2E '.' FULL STOP - 47 0x2F '/' SOLIDUS - 48 0x30 '0' DIGIT ZERO - 49 0x31 '1' DIGIT ONE - 50 0x32 '2' DIGIT TWO - 51 0x33 '3' DIGIT THREE - 52 0x34 '4' DIGIT FOUR - 53 0x35 '5' DIGIT FIVE - 54 0x36 '6' DIGIT SIX - 55 0x37 '7' DIGIT SEVEN - 56 0x38 '8' DIGIT EIGHT - 57 0x39 '9' DIGIT NINE - 58 0x3A ':' COLON - 59 0x3B ';' SEMICOLON - 60 0x3C '<' LESS-THAN SIGN - 61 0x3D '=' EQUALS SIGN - 62 0x3E '>' GREATER-THAN SIGN - 63 0x3F '?' QUESTION MARK - 64 0x40 '@' COMMERCIAL AT - 65 0x41 'A' LATIN CAPITAL LETTER A - 66 0x42 'B' LATIN CAPITAL LETTER B - 67 0x43 'C' LATIN CAPITAL LETTER C - 68 0x44 'D' LATIN CAPITAL LETTER D - 69 0x45 'E' LATIN CAPITAL LETTER E - 70 0x46 'F' LATIN CAPITAL LETTER F - 71 0x47 'G' LATIN CAPITAL LETTER G - 72 0x48 'H' LATIN CAPITAL LETTER H - 73 0x49 'I' LATIN CAPITAL LETTER I - 74 0x4A 'J' LATIN CAPITAL LETTER J - 75 0x4B 'K' LATIN CAPITAL LETTER K - 76 0x4C 'L' LATIN CAPITAL LETTER L - 77 0x4D 'M' LATIN CAPITAL LETTER M - 78 0x4E 'N' LATIN CAPITAL LETTER N - 79 0x4F 'O' LATIN CAPITAL LETTER O - 80 0x50 'P' LATIN CAPITAL LETTER P - 81 0x51 'Q' LATIN CAPITAL LETTER Q - 82 0x52 'R' LATIN CAPITAL LETTER R - 83 0x53 'S' LATIN CAPITAL LETTER S - 84 0x54 'T' LATIN CAPITAL LETTER T - 85 0x55 'U' LATIN CAPITAL LETTER U - 86 0x56 'V' LATIN CAPITAL LETTER V - 87 0x57 'W' LATIN CAPITAL LETTER W - 88 0x58 'X' LATIN CAPITAL LETTER X - 89 0x59 'Y' LATIN CAPITAL LETTER Y - 90 0x5A 'Z' LATIN CAPITAL LETTER Z - 91 0x5B '[' LEFT SQUARE BRACKET - 92 0x5C '\\\\' REVERSE SOLIDUS - 93 0x5D ']' RIGHT SQUARE BRACKET - 94 0x5E '^' CIRCUMFLEX ACCENT - 95 0x5F '_' LOW LINE - 96 0x60 '\`' GRAVE ACCENT - 97 0x61 'a' LATIN SMALL LETTER A - 98 0x62 'b' LATIN SMALL LETTER B - 99 0x63 'c' LATIN SMALL LETTER C - 100 0x64 'd' LATIN SMALL LETTER D - 101 0x65 'e' LATIN SMALL LETTER E - 102 0x66 'f' LATIN SMALL LETTER F - 103 0x67 'g' LATIN SMALL LETTER G - 104 0x68 'h' LATIN SMALL LETTER H - 105 0x69 'i' LATIN SMALL LETTER I - 106 0x6A 'j' LATIN SMALL LETTER J - 107 0x6B 'k' LATIN SMALL LETTER K - 108 0x6C 'l' LATIN SMALL LETTER L - 109 0x6D 'm' LATIN SMALL LETTER M - 110 0x6E 'n' LATIN SMALL LETTER N - 111 0x6F 'o' LATIN SMALL LETTER O - 112 0x70 'p' LATIN SMALL LETTER P - 113 0x71 'q' LATIN SMALL LETTER Q - 114 0x72 'r' LATIN SMALL LETTER R - 115 0x73 's' LATIN SMALL LETTER S - 116 0x74 't' LATIN SMALL LETTER T - 117 0x75 'u' LATIN SMALL LETTER U - 118 0x76 'v' LATIN SMALL LETTER V - 119 0x77 'w' LATIN SMALL LETTER W - 120 0x78 'x' LATIN SMALL LETTER X - 121 0x79 'y' LATIN SMALL LETTER Y - 122 0x7A 'z' LATIN SMALL LETTER Z - 123 0x7B '{' LEFT CURLY BRACKET - 124 0x7C '|' VERTICAL LINE - 125 0x7D '}' RIGHT CURLY BRACKET - 126 0x7E '~' TILDE - 127 0x7F '\\x7f' - 128 0xC7 'Ç' LATIN CAPITAL LETTER C WITH CEDILLA - 129 0xFC 'ü' LATIN SMALL LETTER U WITH DIAERESIS - 130 0xE9 'é' LATIN SMALL LETTER E WITH ACUTE - 131 0xE2 'â' LATIN SMALL LETTER A WITH CIRCUMFLEX - 132 0xE4 'ä' LATIN SMALL LETTER A WITH DIAERESIS - 133 0xE0 'à' LATIN SMALL LETTER A WITH GRAVE - 134 0xE5 'å' LATIN SMALL LETTER A WITH RING ABOVE - 135 0xE7 'ç' LATIN SMALL LETTER C WITH CEDILLA - 136 0xEA 'ê' LATIN SMALL LETTER E WITH CIRCUMFLEX - 137 0xEB 'ë' LATIN SMALL LETTER E WITH DIAERESIS - 138 0xE8 'è' LATIN SMALL LETTER E WITH GRAVE - 139 0xEF 'ï' LATIN SMALL LETTER I WITH DIAERESIS - 140 0xEE 'î' LATIN SMALL LETTER I WITH CIRCUMFLEX - 141 0xEC 'ì' LATIN SMALL LETTER I WITH GRAVE - 142 0xC4 'Ä' LATIN CAPITAL LETTER A WITH DIAERESIS - 143 0xC5 'Å' LATIN CAPITAL LETTER A WITH RING ABOVE - 144 0xC9 'É' LATIN CAPITAL LETTER E WITH ACUTE - 145 0xE6 'æ' LATIN SMALL LETTER AE - 146 0xC6 'Æ' LATIN CAPITAL LETTER AE - 147 0xF4 'ô' LATIN SMALL LETTER O WITH CIRCUMFLEX - 148 0xF6 'ö' LATIN SMALL LETTER O WITH DIAERESIS - 149 0xF2 'ò' LATIN SMALL LETTER O WITH GRAVE - 150 0xFB 'û' LATIN SMALL LETTER U WITH CIRCUMFLEX - 151 0xF9 'ù' LATIN SMALL LETTER U WITH GRAVE - 152 0xFF 'ÿ' LATIN SMALL LETTER Y WITH DIAERESIS - 153 0xD6 'Ö' LATIN CAPITAL LETTER O WITH DIAERESIS - 154 0xDC 'Ü' LATIN CAPITAL LETTER U WITH DIAERESIS - 155 0xA2 '¢' CENT SIGN - 156 0xA3 '£' POUND SIGN - 157 0xA5 '¥' YEN SIGN - 158 0x20A7 '₧' PESETA SIGN - 159 0x0192 'ƒ' LATIN SMALL LETTER F WITH HOOK - 160 0xE1 'á' LATIN SMALL LETTER A WITH ACUTE - 161 0xED 'í' LATIN SMALL LETTER I WITH ACUTE - 162 0xF3 'ó' LATIN SMALL LETTER O WITH ACUTE - 163 0xFA 'ú' LATIN SMALL LETTER U WITH ACUTE - 164 0xF1 'ñ' LATIN SMALL LETTER N WITH TILDE - 165 0xD1 'Ñ' LATIN CAPITAL LETTER N WITH TILDE - 166 0xAA 'ª' FEMININE ORDINAL INDICATOR - 167 0xBA 'º' MASCULINE ORDINAL INDICATOR - 168 0xBF '¿' INVERTED QUESTION MARK - 169 0x2310 '⌐' REVERSED NOT SIGN - 170 0xAC '¬' NOT SIGN - 171 0xBD '½' VULGAR FRACTION ONE HALF - 172 0xBC '¼' VULGAR FRACTION ONE QUARTER - 173 0xA1 '¡' INVERTED EXCLAMATION MARK - 174 0xAB '«' LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - 175 0xBB '»' RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - 176 0x2591 '░' LIGHT SHADE - 177 0x2592 '▒' MEDIUM SHADE - 178 0x2593 '▓' DARK SHADE - 179 0x2502 '│' BOX DRAWINGS LIGHT VERTICAL - 180 0x2524 '┤' BOX DRAWINGS LIGHT VERTICAL AND LEFT - 181 0x2561 '╡' BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE - 182 0x2562 '╢' BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE - 183 0x2556 '╖' BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE - 184 0x2555 '╕' BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE - 185 0x2563 '╣' BOX DRAWINGS DOUBLE VERTICAL AND LEFT - 186 0x2551 '║' BOX DRAWINGS DOUBLE VERTICAL - 187 0x2557 '╗' BOX DRAWINGS DOUBLE DOWN AND LEFT - 188 0x255D '╝' BOX DRAWINGS DOUBLE UP AND LEFT - 189 0x255C '╜' BOX DRAWINGS UP DOUBLE AND LEFT SINGLE - 190 0x255B '╛' BOX DRAWINGS UP SINGLE AND LEFT DOUBLE - 191 0x2510 '┐' BOX DRAWINGS LIGHT DOWN AND LEFT - 192 0x2514 '└' BOX DRAWINGS LIGHT UP AND RIGHT - 193 0x2534 '┴' BOX DRAWINGS LIGHT UP AND HORIZONTAL - 194 0x252C '┬' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL - 195 0x251C '├' BOX DRAWINGS LIGHT VERTICAL AND RIGHT - 196 0x2500 '─' BOX DRAWINGS LIGHT HORIZONTAL - 197 0x253C '┼' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL - 198 0x255E '╞' BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE - 199 0x255F '╟' BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE - 200 0x255A '╚' BOX DRAWINGS DOUBLE UP AND RIGHT - 201 0x2554 '╔' BOX DRAWINGS DOUBLE DOWN AND RIGHT - 202 0x2569 '╩' BOX DRAWINGS DOUBLE UP AND HORIZONTAL - 203 0x2566 '╦' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL - 204 0x2560 '╠' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT - 205 0x2550 '═' BOX DRAWINGS DOUBLE HORIZONTAL - 206 0x256C '╬' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL - 207 0x2567 '╧' BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE - 208 0x2568 '╨' BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE - 209 0x2564 '╤' BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE - 210 0x2565 '╥' BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE - 211 0x2559 '╙' BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE - 212 0x2558 '╘' BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE - 213 0x2552 '╒' BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE - 214 0x2553 '╓' BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE - 215 0x256B '╫' BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE - 216 0x256A '╪' BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE - 217 0x2518 '┘' BOX DRAWINGS LIGHT UP AND LEFT - 218 0x250C '┌' BOX DRAWINGS LIGHT DOWN AND RIGHT - 219 0x2588 '█' FULL BLOCK - 220 0x2584 '▄' LOWER HALF BLOCK - 221 0x258C '▌' LEFT HALF BLOCK - 222 0x2590 '▐' RIGHT HALF BLOCK - 223 0x2580 '▀' UPPER HALF BLOCK - 224 0x03B1 'α' GREEK SMALL LETTER ALPHA - 225 0xDF 'ß' LATIN SMALL LETTER SHARP S - 226 0x0393 'Γ' GREEK CAPITAL LETTER GAMMA - 227 0x03C0 'π' GREEK SMALL LETTER PI - 228 0x03A3 'Σ' GREEK CAPITAL LETTER SIGMA - 229 0x03C3 'σ' GREEK SMALL LETTER SIGMA - 230 0xB5 'µ' MICRO SIGN - 231 0x03C4 'τ' GREEK SMALL LETTER TAU - 232 0x03A6 'Φ' GREEK CAPITAL LETTER PHI - 233 0x0398 'Θ' GREEK CAPITAL LETTER THETA - 234 0x03A9 'Ω' GREEK CAPITAL LETTER OMEGA - 235 0x03B4 'δ' GREEK SMALL LETTER DELTA - 236 0x221E '∞' INFINITY - 237 0x03C6 'φ' GREEK SMALL LETTER PHI - 238 0x03B5 'ε' GREEK SMALL LETTER EPSILON - 239 0x2229 '∩' INTERSECTION - 240 0x2261 '≡' IDENTICAL TO - 241 0xB1 '±' PLUS-MINUS SIGN - 242 0x2265 '≥' GREATER-THAN OR EQUAL TO - 243 0x2264 '≤' LESS-THAN OR EQUAL TO - 244 0x2320 '⌠' TOP HALF INTEGRAL - 245 0x2321 '⌡' BOTTOM HALF INTEGRAL - 246 0xF7 '÷' DIVISION SIGN - 247 0x2248 '≈' ALMOST EQUAL TO - 248 0xB0 '°' DEGREE SIGN - 249 0x2219 '∙' BULLET OPERATOR - 250 0xB7 '·' MIDDLE DOT - 251 0x221A '√' SQUARE ROOT - 252 0x207F 'ⁿ' SUPERSCRIPT LATIN SMALL LETTER N - 253 0xB2 '²' SUPERSCRIPT TWO - 254 0x25A0 '■' BLACK SQUARE - 255 0xA0 '\\xa0' NO-BREAK SPACE -============ ========= ======== ================================================== +============ ========= ========= ================================================== + Tile Index Unicode String Name +============ ========= ========= ================================================== + 0 0x00 \'\\x00\' + 1 0x263A \'☺\' WHITE SMILING FACE + 2 0x263B \'☻\' BLACK SMILING FACE + 3 0x2665 \'♥\' BLACK HEART SUIT + 4 0x2666 \'♦\' BLACK DIAMOND SUIT + 5 0x2663 \'♣\' BLACK CLUB SUIT + 6 0x2660 \'♠\' BLACK SPADE SUIT + 7 0x2022 \'•\' BULLET + 8 0x25D8 \'◘\' INVERSE BULLET + 9 0x25CB \'○\' WHITE CIRCLE + 10 0x25D9 \'◙\' INVERSE WHITE CIRCLE + 11 0x2642 \'♂\' MALE SIGN + 12 0x2640 \'♀\' FEMALE SIGN + 13 0x266A \'♪\' EIGHTH NOTE + 14 0x266B \'♫\' BEAMED EIGHTH NOTES + 15 0x263C \'☼\' WHITE SUN WITH RAYS + 16 0x25BA \'►\' BLACK RIGHT-POINTING POINTER + 17 0x25C4 \'◄\' BLACK LEFT-POINTING POINTER + 18 0x2195 \'↕\' UP DOWN ARROW + 19 0x203C \'‼\' DOUBLE EXCLAMATION MARK + 20 0xB6 \'¶\' PILCROW SIGN + 21 0xA7 \'§\' SECTION SIGN + 22 0x25AC \'▬\' BLACK RECTANGLE + 23 0x21A8 \'↨\' UP DOWN ARROW WITH BASE + 24 0x2191 \'↑\' UPWARDS ARROW + 25 0x2193 \'↓\' DOWNWARDS ARROW + 26 0x2192 \'→\' RIGHTWARDS ARROW + 27 0x2190 \'←\' LEFTWARDS ARROW + 28 0x221F \'∟\' RIGHT ANGLE + 29 0x2194 \'↔\' LEFT RIGHT ARROW + 30 0x25B2 \'▲\' BLACK UP-POINTING TRIANGLE + 31 0x25BC \'▼\' BLACK DOWN-POINTING TRIANGLE + 32 0x20 \' \' SPACE + 33 0x21 \'!\' EXCLAMATION MARK + 34 0x22 \'\"\' QUOTATION MARK + 35 0x23 \'#\' NUMBER SIGN + 36 0x24 \'$\' DOLLAR SIGN + 37 0x25 \'%\' PERCENT SIGN + 38 0x26 \'&\' AMPERSAND + 39 0x27 \"\'\" APOSTROPHE + 40 0x28 \'(\' LEFT PARENTHESIS + 41 0x29 \')\' RIGHT PARENTHESIS + 42 0x2A \'\*\' ASTERISK + 43 0x2B \'+\' PLUS SIGN + 44 0x2C \',\' COMMA + 45 0x2D \'-\' HYPHEN-MINUS + 46 0x2E \'.\' FULL STOP + 47 0x2F \'/\' SOLIDUS + 48 0x30 \'0\' DIGIT ZERO + 49 0x31 \'1\' DIGIT ONE + 50 0x32 \'2\' DIGIT TWO + 51 0x33 \'3\' DIGIT THREE + 52 0x34 \'4\' DIGIT FOUR + 53 0x35 \'5\' DIGIT FIVE + 54 0x36 \'6\' DIGIT SIX + 55 0x37 \'7\' DIGIT SEVEN + 56 0x38 \'8\' DIGIT EIGHT + 57 0x39 \'9\' DIGIT NINE + 58 0x3A \':\' COLON + 59 0x3B \';\' SEMICOLON + 60 0x3C \'<\' LESS-THAN SIGN + 61 0x3D \'=\' EQUALS SIGN + 62 0x3E \'>\' GREATER-THAN SIGN + 63 0x3F \'?\' QUESTION MARK + 64 0x40 \'@\' COMMERCIAL AT + 65 0x41 \'A\' LATIN CAPITAL LETTER A + 66 0x42 \'B\' LATIN CAPITAL LETTER B + 67 0x43 \'C\' LATIN CAPITAL LETTER C + 68 0x44 \'D\' LATIN CAPITAL LETTER D + 69 0x45 \'E\' LATIN CAPITAL LETTER E + 70 0x46 \'F\' LATIN CAPITAL LETTER F + 71 0x47 \'G\' LATIN CAPITAL LETTER G + 72 0x48 \'H\' LATIN CAPITAL LETTER H + 73 0x49 \'I\' LATIN CAPITAL LETTER I + 74 0x4A \'J\' LATIN CAPITAL LETTER J + 75 0x4B \'K\' LATIN CAPITAL LETTER K + 76 0x4C \'L\' LATIN CAPITAL LETTER L + 77 0x4D \'M\' LATIN CAPITAL LETTER M + 78 0x4E \'N\' LATIN CAPITAL LETTER N + 79 0x4F \'O\' LATIN CAPITAL LETTER O + 80 0x50 \'P\' LATIN CAPITAL LETTER P + 81 0x51 \'Q\' LATIN CAPITAL LETTER Q + 82 0x52 \'R\' LATIN CAPITAL LETTER R + 83 0x53 \'S\' LATIN CAPITAL LETTER S + 84 0x54 \'T\' LATIN CAPITAL LETTER T + 85 0x55 \'U\' LATIN CAPITAL LETTER U + 86 0x56 \'V\' LATIN CAPITAL LETTER V + 87 0x57 \'W\' LATIN CAPITAL LETTER W + 88 0x58 \'X\' LATIN CAPITAL LETTER X + 89 0x59 \'Y\' LATIN CAPITAL LETTER Y + 90 0x5A \'Z\' LATIN CAPITAL LETTER Z + 91 0x5B \'[\' LEFT SQUARE BRACKET + 92 0x5C \'\\\\\' REVERSE SOLIDUS + 93 0x5D \']\' RIGHT SQUARE BRACKET + 94 0x5E \'^\' CIRCUMFLEX ACCENT + 95 0x5F \'_\' LOW LINE + 96 0x60 \'\`\' GRAVE ACCENT + 97 0x61 \'a\' LATIN SMALL LETTER A + 98 0x62 \'b\' LATIN SMALL LETTER B + 99 0x63 \'c\' LATIN SMALL LETTER C + 100 0x64 \'d\' LATIN SMALL LETTER D + 101 0x65 \'e\' LATIN SMALL LETTER E + 102 0x66 \'f\' LATIN SMALL LETTER F + 103 0x67 \'g\' LATIN SMALL LETTER G + 104 0x68 \'h\' LATIN SMALL LETTER H + 105 0x69 \'i\' LATIN SMALL LETTER I + 106 0x6A \'j\' LATIN SMALL LETTER J + 107 0x6B \'k\' LATIN SMALL LETTER K + 108 0x6C \'l\' LATIN SMALL LETTER L + 109 0x6D \'m\' LATIN SMALL LETTER M + 110 0x6E \'n\' LATIN SMALL LETTER N + 111 0x6F \'o\' LATIN SMALL LETTER O + 112 0x70 \'p\' LATIN SMALL LETTER P + 113 0x71 \'q\' LATIN SMALL LETTER Q + 114 0x72 \'r\' LATIN SMALL LETTER R + 115 0x73 \'s\' LATIN SMALL LETTER S + 116 0x74 \'t\' LATIN SMALL LETTER T + 117 0x75 \'u\' LATIN SMALL LETTER U + 118 0x76 \'v\' LATIN SMALL LETTER V + 119 0x77 \'w\' LATIN SMALL LETTER W + 120 0x78 \'x\' LATIN SMALL LETTER X + 121 0x79 \'y\' LATIN SMALL LETTER Y + 122 0x7A \'z\' LATIN SMALL LETTER Z + 123 0x7B \'{\' LEFT CURLY BRACKET + 124 0x7C \'\|\' VERTICAL LINE + 125 0x7D \'}\' RIGHT CURLY BRACKET + 126 0x7E \'~\' TILDE + 127 0x7F \'\\x7f\' + 128 0xC7 \'Ç\' LATIN CAPITAL LETTER C WITH CEDILLA + 129 0xFC \'ü\' LATIN SMALL LETTER U WITH DIAERESIS + 130 0xE9 \'é\' LATIN SMALL LETTER E WITH ACUTE + 131 0xE2 \'â\' LATIN SMALL LETTER A WITH CIRCUMFLEX + 132 0xE4 \'ä\' LATIN SMALL LETTER A WITH DIAERESIS + 133 0xE0 \'à\' LATIN SMALL LETTER A WITH GRAVE + 134 0xE5 \'å\' LATIN SMALL LETTER A WITH RING ABOVE + 135 0xE7 \'ç\' LATIN SMALL LETTER C WITH CEDILLA + 136 0xEA \'ê\' LATIN SMALL LETTER E WITH CIRCUMFLEX + 137 0xEB \'ë\' LATIN SMALL LETTER E WITH DIAERESIS + 138 0xE8 \'è\' LATIN SMALL LETTER E WITH GRAVE + 139 0xEF \'ï\' LATIN SMALL LETTER I WITH DIAERESIS + 140 0xEE \'î\' LATIN SMALL LETTER I WITH CIRCUMFLEX + 141 0xEC \'ì\' LATIN SMALL LETTER I WITH GRAVE + 142 0xC4 \'Ä\' LATIN CAPITAL LETTER A WITH DIAERESIS + 143 0xC5 \'Å\' LATIN CAPITAL LETTER A WITH RING ABOVE + 144 0xC9 \'É\' LATIN CAPITAL LETTER E WITH ACUTE + 145 0xE6 \'æ\' LATIN SMALL LETTER AE + 146 0xC6 \'Æ\' LATIN CAPITAL LETTER AE + 147 0xF4 \'ô\' LATIN SMALL LETTER O WITH CIRCUMFLEX + 148 0xF6 \'ö\' LATIN SMALL LETTER O WITH DIAERESIS + 149 0xF2 \'ò\' LATIN SMALL LETTER O WITH GRAVE + 150 0xFB \'û\' LATIN SMALL LETTER U WITH CIRCUMFLEX + 151 0xF9 \'ù\' LATIN SMALL LETTER U WITH GRAVE + 152 0xFF \'ÿ\' LATIN SMALL LETTER Y WITH DIAERESIS + 153 0xD6 \'Ö\' LATIN CAPITAL LETTER O WITH DIAERESIS + 154 0xDC \'Ü\' LATIN CAPITAL LETTER U WITH DIAERESIS + 155 0xA2 \'¢\' CENT SIGN + 156 0xA3 \'£\' POUND SIGN + 157 0xA5 \'¥\' YEN SIGN + 158 0x20A7 \'₧\' PESETA SIGN + 159 0x0192 \'ƒ\' LATIN SMALL LETTER F WITH HOOK + 160 0xE1 \'á\' LATIN SMALL LETTER A WITH ACUTE + 161 0xED \'í\' LATIN SMALL LETTER I WITH ACUTE + 162 0xF3 \'ó\' LATIN SMALL LETTER O WITH ACUTE + 163 0xFA \'ú\' LATIN SMALL LETTER U WITH ACUTE + 164 0xF1 \'ñ\' LATIN SMALL LETTER N WITH TILDE + 165 0xD1 \'Ñ\' LATIN CAPITAL LETTER N WITH TILDE + 166 0xAA \'ª\' FEMININE ORDINAL INDICATOR + 167 0xBA \'º\' MASCULINE ORDINAL INDICATOR + 168 0xBF \'¿\' INVERTED QUESTION MARK + 169 0x2310 \'⌐\' REVERSED NOT SIGN + 170 0xAC \'¬\' NOT SIGN + 171 0xBD \'½\' VULGAR FRACTION ONE HALF + 172 0xBC \'¼\' VULGAR FRACTION ONE QUARTER + 173 0xA1 \'¡\' INVERTED EXCLAMATION MARK + 174 0xAB \'«\' LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 175 0xBB \'»\' RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 176 0x2591 \'░\' LIGHT SHADE + 177 0x2592 \'▒\' MEDIUM SHADE + 178 0x2593 \'▓\' DARK SHADE + 179 0x2502 \'│\' BOX DRAWINGS LIGHT VERTICAL + 180 0x2524 \'┤\' BOX DRAWINGS LIGHT VERTICAL AND LEFT + 181 0x2561 \'╡\' BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + 182 0x2562 \'╢\' BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + 183 0x2556 \'╖\' BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + 184 0x2555 \'╕\' BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + 185 0x2563 \'╣\' BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 186 0x2551 \'║\' BOX DRAWINGS DOUBLE VERTICAL + 187 0x2557 \'╗\' BOX DRAWINGS DOUBLE DOWN AND LEFT + 188 0x255D \'╝\' BOX DRAWINGS DOUBLE UP AND LEFT + 189 0x255C \'╜\' BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + 190 0x255B \'╛\' BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + 191 0x2510 \'┐\' BOX DRAWINGS LIGHT DOWN AND LEFT + 192 0x2514 \'└\' BOX DRAWINGS LIGHT UP AND RIGHT + 193 0x2534 \'┴\' BOX DRAWINGS LIGHT UP AND HORIZONTAL + 194 0x252C \'┬\' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 195 0x251C \'├\' BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 196 0x2500 \'─\' BOX DRAWINGS LIGHT HORIZONTAL + 197 0x253C \'┼\' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 198 0x255E \'╞\' BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + 199 0x255F \'╟\' BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + 200 0x255A \'╚\' BOX DRAWINGS DOUBLE UP AND RIGHT + 201 0x2554 \'╔\' BOX DRAWINGS DOUBLE DOWN AND RIGHT + 202 0x2569 \'╩\' BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 203 0x2566 \'╦\' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 204 0x2560 \'╠\' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 205 0x2550 \'═\' BOX DRAWINGS DOUBLE HORIZONTAL + 206 0x256C \'╬\' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 207 0x2567 \'╧\' BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + 208 0x2568 \'╨\' BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + 209 0x2564 \'╤\' BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + 210 0x2565 \'╥\' BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + 211 0x2559 \'╙\' BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + 212 0x2558 \'╘\' BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + 213 0x2552 \'╒\' BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + 214 0x2553 \'╓\' BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + 215 0x256B \'╫\' BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + 216 0x256A \'╪\' BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + 217 0x2518 \'┘\' BOX DRAWINGS LIGHT UP AND LEFT + 218 0x250C \'┌\' BOX DRAWINGS LIGHT DOWN AND RIGHT + 219 0x2588 \'█\' FULL BLOCK + 220 0x2584 \'▄\' LOWER HALF BLOCK + 221 0x258C \'▌\' LEFT HALF BLOCK + 222 0x2590 \'▐\' RIGHT HALF BLOCK + 223 0x2580 \'▀\' UPPER HALF BLOCK + 224 0x03B1 \'α\' GREEK SMALL LETTER ALPHA + 225 0xDF \'ß\' LATIN SMALL LETTER SHARP S + 226 0x0393 \'Γ\' GREEK CAPITAL LETTER GAMMA + 227 0x03C0 \'π\' GREEK SMALL LETTER PI + 228 0x03A3 \'Σ\' GREEK CAPITAL LETTER SIGMA + 229 0x03C3 \'σ\' GREEK SMALL LETTER SIGMA + 230 0xB5 \'µ\' MICRO SIGN + 231 0x03C4 \'τ\' GREEK SMALL LETTER TAU + 232 0x03A6 \'Φ\' GREEK CAPITAL LETTER PHI + 233 0x0398 \'Θ\' GREEK CAPITAL LETTER THETA + 234 0x03A9 \'Ω\' GREEK CAPITAL LETTER OMEGA + 235 0x03B4 \'δ\' GREEK SMALL LETTER DELTA + 236 0x221E \'∞\' INFINITY + 237 0x03C6 \'φ\' GREEK SMALL LETTER PHI + 238 0x03B5 \'ε\' GREEK SMALL LETTER EPSILON + 239 0x2229 \'∩\' INTERSECTION + 240 0x2261 \'≡\' IDENTICAL TO + 241 0xB1 \'±\' PLUS-MINUS SIGN + 242 0x2265 \'≥\' GREATER-THAN OR EQUAL TO + 243 0x2264 \'≤\' LESS-THAN OR EQUAL TO + 244 0x2320 \'⌠\' TOP HALF INTEGRAL + 245 0x2321 \'⌡\' BOTTOM HALF INTEGRAL + 246 0xF7 \'÷\' DIVISION SIGN + 247 0x2248 \'≈\' ALMOST EQUAL TO + 248 0xB0 \'°\' DEGREE SIGN + 249 0x2219 \'∙\' BULLET OPERATOR + 250 0xB7 \'·\' MIDDLE DOT + 251 0x221A \'√\' SQUARE ROOT + 252 0x207F \'ⁿ\' SUPERSCRIPT LATIN SMALL LETTER N + 253 0xB2 \'²\' SUPERSCRIPT TWO + 254 0x25A0 \'■\' BLACK SQUARE + 255 0xA0 \'\\xa0\' NO-BREAK SPACE +============ ========= ========= ================================================== .. _deprecated-tcod-layout: @@ -311,167 +311,167 @@ Deprecated TCOD Layout The layout for tilesets loaded with: :any:`tcod.tileset.CHARMAP_TCOD` -============ ========= ======== =========================================== - Tile Index Unicode String Name -============ ========= ======== =========================================== - 0 0x20 ' ' SPACE - 1 0x21 '!' EXCLAMATION MARK - 2 0x22 '"' QUOTATION MARK - 3 0x23 '#' NUMBER SIGN - 4 0x24 '$' DOLLAR SIGN - 5 0x25 '%' PERCENT SIGN - 6 0x26 '&' AMPERSAND - 7 0x27 "'" APOSTROPHE - 8 0x28 '(' LEFT PARENTHESIS - 9 0x29 ')' RIGHT PARENTHESIS - 10 0x2A '\*' ASTERISK - 11 0x2B '+' PLUS SIGN - 12 0x2C ',' COMMA - 13 0x2D '-' HYPHEN-MINUS - 14 0x2E '.' FULL STOP - 15 0x2F '/' SOLIDUS - 16 0x30 '0' DIGIT ZERO - 17 0x31 '1' DIGIT ONE - 18 0x32 '2' DIGIT TWO - 19 0x33 '3' DIGIT THREE - 20 0x34 '4' DIGIT FOUR - 21 0x35 '5' DIGIT FIVE - 22 0x36 '6' DIGIT SIX - 23 0x37 '7' DIGIT SEVEN - 24 0x38 '8' DIGIT EIGHT - 25 0x39 '9' DIGIT NINE - 26 0x3A ':' COLON - 27 0x3B ';' SEMICOLON - 28 0x3C '<' LESS-THAN SIGN - 29 0x3D '=' EQUALS SIGN - 30 0x3E '>' GREATER-THAN SIGN - 31 0x3F '?' QUESTION MARK - 32 0x40 '@' COMMERCIAL AT - 33 0x5B '[' LEFT SQUARE BRACKET - 34 0x5C '\\' REVERSE SOLIDUS - 35 0x5D ']' RIGHT SQUARE BRACKET - 36 0x5E '^' CIRCUMFLEX ACCENT - 37 0x5F '_' LOW LINE - 38 0x60 '\`' GRAVE ACCENT - 39 0x7B '{' LEFT CURLY BRACKET - 40 0x7C '|' VERTICAL LINE - 41 0x7D '}' RIGHT CURLY BRACKET - 42 0x7E '~' TILDE - 43 0x2591 '░' LIGHT SHADE - 44 0x2592 '▒' MEDIUM SHADE - 45 0x2593 '▓' DARK SHADE - 46 0x2502 '│' BOX DRAWINGS LIGHT VERTICAL - 47 0x2500 '─' BOX DRAWINGS LIGHT HORIZONTAL - 48 0x253C '┼' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL - 49 0x2524 '┤' BOX DRAWINGS LIGHT VERTICAL AND LEFT - 50 0x2534 '┴' BOX DRAWINGS LIGHT UP AND HORIZONTAL - 51 0x251C '├' BOX DRAWINGS LIGHT VERTICAL AND RIGHT - 52 0x252C '┬' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL - 53 0x2514 '└' BOX DRAWINGS LIGHT UP AND RIGHT - 54 0x250C '┌' BOX DRAWINGS LIGHT DOWN AND RIGHT - 55 0x2510 '┐' BOX DRAWINGS LIGHT DOWN AND LEFT - 56 0x2518 '┘' BOX DRAWINGS LIGHT UP AND LEFT - 57 0x2598 '▘' QUADRANT UPPER LEFT - 58 0x259D '▝' QUADRANT UPPER RIGHT - 59 0x2580 '▀' UPPER HALF BLOCK - 60 0x2596 '▖' QUADRANT LOWER LEFT - 61 0x259A '▚' QUADRANT UPPER LEFT AND LOWER RIGHT - 62 0x2590 '▐' RIGHT HALF BLOCK - 63 0x2597 '▗' QUADRANT LOWER RIGHT - 64 0x2191 '↑' UPWARDS ARROW - 65 0x2193 '↓' DOWNWARDS ARROW - 66 0x2190 '←' LEFTWARDS ARROW - 67 0x2192 '→' RIGHTWARDS ARROW - 68 0x25B2 '▲' BLACK UP-POINTING TRIANGLE - 69 0x25BC '▼' BLACK DOWN-POINTING TRIANGLE - 70 0x25C4 '◄' BLACK LEFT-POINTING POINTER - 71 0x25BA '►' BLACK RIGHT-POINTING POINTER - 72 0x2195 '↕' UP DOWN ARROW - 73 0x2194 '↔' LEFT RIGHT ARROW - 74 0x2610 '☐' BALLOT BOX - 75 0x2611 '☑' BALLOT BOX WITH CHECK - 76 0x25CB '○' WHITE CIRCLE - 77 0x25C9 '◉' FISHEYE - 78 0x2551 '║' BOX DRAWINGS DOUBLE VERTICAL - 79 0x2550 '═' BOX DRAWINGS DOUBLE HORIZONTAL - 80 0x256C '╬' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL - 81 0x2563 '╣' BOX DRAWINGS DOUBLE VERTICAL AND LEFT - 82 0x2569 '╩' BOX DRAWINGS DOUBLE UP AND HORIZONTAL - 83 0x2560 '╠' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT - 84 0x2566 '╦' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL - 85 0x255A '╚' BOX DRAWINGS DOUBLE UP AND RIGHT - 86 0x2554 '╔' BOX DRAWINGS DOUBLE DOWN AND RIGHT - 87 0x2557 '╗' BOX DRAWINGS DOUBLE DOWN AND LEFT - 88 0x255D '╝' BOX DRAWINGS DOUBLE UP AND LEFT - 89 0x00 '\\x00' - 90 0x00 '\\x00' - 91 0x00 '\\x00' - 92 0x00 '\\x00' - 93 0x00 '\\x00' - 94 0x00 '\\x00' - 95 0x00 '\\x00' - 96 0x41 'A' LATIN CAPITAL LETTER A - 97 0x42 'B' LATIN CAPITAL LETTER B - 98 0x43 'C' LATIN CAPITAL LETTER C - 99 0x44 'D' LATIN CAPITAL LETTER D - 100 0x45 'E' LATIN CAPITAL LETTER E - 101 0x46 'F' LATIN CAPITAL LETTER F - 102 0x47 'G' LATIN CAPITAL LETTER G - 103 0x48 'H' LATIN CAPITAL LETTER H - 104 0x49 'I' LATIN CAPITAL LETTER I - 105 0x4A 'J' LATIN CAPITAL LETTER J - 106 0x4B 'K' LATIN CAPITAL LETTER K - 107 0x4C 'L' LATIN CAPITAL LETTER L - 108 0x4D 'M' LATIN CAPITAL LETTER M - 109 0x4E 'N' LATIN CAPITAL LETTER N - 110 0x4F 'O' LATIN CAPITAL LETTER O - 111 0x50 'P' LATIN CAPITAL LETTER P - 112 0x51 'Q' LATIN CAPITAL LETTER Q - 113 0x52 'R' LATIN CAPITAL LETTER R - 114 0x53 'S' LATIN CAPITAL LETTER S - 115 0x54 'T' LATIN CAPITAL LETTER T - 116 0x55 'U' LATIN CAPITAL LETTER U - 117 0x56 'V' LATIN CAPITAL LETTER V - 118 0x57 'W' LATIN CAPITAL LETTER W - 119 0x58 'X' LATIN CAPITAL LETTER X - 120 0x59 'Y' LATIN CAPITAL LETTER Y - 121 0x5A 'Z' LATIN CAPITAL LETTER Z - 122 0x00 '\\x00' - 123 0x00 '\\x00' - 124 0x00 '\\x00' - 125 0x00 '\\x00' - 126 0x00 '\\x00' - 127 0x00 '\\x00' - 128 0x61 'a' LATIN SMALL LETTER A - 129 0x62 'b' LATIN SMALL LETTER B - 130 0x63 'c' LATIN SMALL LETTER C - 131 0x64 'd' LATIN SMALL LETTER D - 132 0x65 'e' LATIN SMALL LETTER E - 133 0x66 'f' LATIN SMALL LETTER F - 134 0x67 'g' LATIN SMALL LETTER G - 135 0x68 'h' LATIN SMALL LETTER H - 136 0x69 'i' LATIN SMALL LETTER I - 137 0x6A 'j' LATIN SMALL LETTER J - 138 0x6B 'k' LATIN SMALL LETTER K - 139 0x6C 'l' LATIN SMALL LETTER L - 140 0x6D 'm' LATIN SMALL LETTER M - 141 0x6E 'n' LATIN SMALL LETTER N - 142 0x6F 'o' LATIN SMALL LETTER O - 143 0x70 'p' LATIN SMALL LETTER P - 144 0x71 'q' LATIN SMALL LETTER Q - 145 0x72 'r' LATIN SMALL LETTER R - 146 0x73 's' LATIN SMALL LETTER S - 147 0x74 't' LATIN SMALL LETTER T - 148 0x75 'u' LATIN SMALL LETTER U - 149 0x76 'v' LATIN SMALL LETTER V - 150 0x77 'w' LATIN SMALL LETTER W - 151 0x78 'x' LATIN SMALL LETTER X - 152 0x79 'y' LATIN SMALL LETTER Y - 153 0x7A 'z' LATIN SMALL LETTER Z - 154 0x00 '\\x00' - 155 0x00 '\\x00' - 156 0x00 '\\x00' - 157 0x00 '\\x00' - 158 0x00 '\\x00' - 159 0x00 '\\x00' -============ ========= ======== =========================================== +============ ========= ========= =========================================== + Tile Index Unicode String Name +============ ========= ========= =========================================== + 0 0x20 \' \' SPACE + 1 0x21 \'!\' EXCLAMATION MARK + 2 0x22 \'\"\' QUOTATION MARK + 3 0x23 \'#\' NUMBER SIGN + 4 0x24 \'$\' DOLLAR SIGN + 5 0x25 \'%\' PERCENT SIGN + 6 0x26 \'&\' AMPERSAND + 7 0x27 \"\'\" APOSTROPHE + 8 0x28 \'(\' LEFT PARENTHESIS + 9 0x29 \')\' RIGHT PARENTHESIS + 10 0x2A \'\*\' ASTERISK + 11 0x2B \'+\' PLUS SIGN + 12 0x2C \',\' COMMA + 13 0x2D \'-\' HYPHEN-MINUS + 14 0x2E \'.\' FULL STOP + 15 0x2F \'/\' SOLIDUS + 16 0x30 \'0\' DIGIT ZERO + 17 0x31 \'1\' DIGIT ONE + 18 0x32 \'2\' DIGIT TWO + 19 0x33 \'3\' DIGIT THREE + 20 0x34 \'4\' DIGIT FOUR + 21 0x35 \'5\' DIGIT FIVE + 22 0x36 \'6\' DIGIT SIX + 23 0x37 \'7\' DIGIT SEVEN + 24 0x38 \'8\' DIGIT EIGHT + 25 0x39 \'9\' DIGIT NINE + 26 0x3A \':\' COLON + 27 0x3B \';\' SEMICOLON + 28 0x3C \'<\' LESS-THAN SIGN + 29 0x3D \'=\' EQUALS SIGN + 30 0x3E \'>\' GREATER-THAN SIGN + 31 0x3F \'?\' QUESTION MARK + 32 0x40 \'@\' COMMERCIAL AT + 33 0x5B \'[\' LEFT SQUARE BRACKET + 34 0x5C \'\\\\\' REVERSE SOLIDUS + 35 0x5D \']\' RIGHT SQUARE BRACKET + 36 0x5E \'^\' CIRCUMFLEX ACCENT + 37 0x5F \'_\' LOW LINE + 38 0x60 \'\`\' GRAVE ACCENT + 39 0x7B \'{\' LEFT CURLY BRACKET + 40 0x7C \'\|\' VERTICAL LINE + 41 0x7D \'}\' RIGHT CURLY BRACKET + 42 0x7E \'~\' TILDE + 43 0x2591 \'░\' LIGHT SHADE + 44 0x2592 \'▒\' MEDIUM SHADE + 45 0x2593 \'▓\' DARK SHADE + 46 0x2502 \'│\' BOX DRAWINGS LIGHT VERTICAL + 47 0x2500 \'─\' BOX DRAWINGS LIGHT HORIZONTAL + 48 0x253C \'┼\' BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + 49 0x2524 \'┤\' BOX DRAWINGS LIGHT VERTICAL AND LEFT + 50 0x2534 \'┴\' BOX DRAWINGS LIGHT UP AND HORIZONTAL + 51 0x251C \'├\' BOX DRAWINGS LIGHT VERTICAL AND RIGHT + 52 0x252C \'┬\' BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + 53 0x2514 \'└\' BOX DRAWINGS LIGHT UP AND RIGHT + 54 0x250C \'┌\' BOX DRAWINGS LIGHT DOWN AND RIGHT + 55 0x2510 \'┐\' BOX DRAWINGS LIGHT DOWN AND LEFT + 56 0x2518 \'┘\' BOX DRAWINGS LIGHT UP AND LEFT + 57 0x2598 \'▘\' QUADRANT UPPER LEFT + 58 0x259D \'▝\' QUADRANT UPPER RIGHT + 59 0x2580 \'▀\' UPPER HALF BLOCK + 60 0x2596 \'▖\' QUADRANT LOWER LEFT + 61 0x259A \'▚\' QUADRANT UPPER LEFT AND LOWER RIGHT + 62 0x2590 \'▐\' RIGHT HALF BLOCK + 63 0x2597 \'▗\' QUADRANT LOWER RIGHT + 64 0x2191 \'↑\' UPWARDS ARROW + 65 0x2193 \'↓\' DOWNWARDS ARROW + 66 0x2190 \'←\' LEFTWARDS ARROW + 67 0x2192 \'→\' RIGHTWARDS ARROW + 68 0x25B2 \'▲\' BLACK UP-POINTING TRIANGLE + 69 0x25BC \'▼\' BLACK DOWN-POINTING TRIANGLE + 70 0x25C4 \'◄\' BLACK LEFT-POINTING POINTER + 71 0x25BA \'►\' BLACK RIGHT-POINTING POINTER + 72 0x2195 \'↕\' UP DOWN ARROW + 73 0x2194 \'↔\' LEFT RIGHT ARROW + 74 0x2610 \'☐\' BALLOT BOX + 75 0x2611 \'☑\' BALLOT BOX WITH CHECK + 76 0x25CB \'○\' WHITE CIRCLE + 77 0x25C9 \'◉\' FISHEYE + 78 0x2551 \'║\' BOX DRAWINGS DOUBLE VERTICAL + 79 0x2550 \'═\' BOX DRAWINGS DOUBLE HORIZONTAL + 80 0x256C \'╬\' BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + 81 0x2563 \'╣\' BOX DRAWINGS DOUBLE VERTICAL AND LEFT + 82 0x2569 \'╩\' BOX DRAWINGS DOUBLE UP AND HORIZONTAL + 83 0x2560 \'╠\' BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + 84 0x2566 \'╦\' BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + 85 0x255A \'╚\' BOX DRAWINGS DOUBLE UP AND RIGHT + 86 0x2554 \'╔\' BOX DRAWINGS DOUBLE DOWN AND RIGHT + 87 0x2557 \'╗\' BOX DRAWINGS DOUBLE DOWN AND LEFT + 88 0x255D \'╝\' BOX DRAWINGS DOUBLE UP AND LEFT + 89 0x00 \'\\x00\' + 90 0x00 \'\\x00\' + 91 0x00 \'\\x00\' + 92 0x00 \'\\x00\' + 93 0x00 \'\\x00\' + 94 0x00 \'\\x00\' + 95 0x00 \'\\x00\' + 96 0x41 \'A\' LATIN CAPITAL LETTER A + 97 0x42 \'B\' LATIN CAPITAL LETTER B + 98 0x43 \'C\' LATIN CAPITAL LETTER C + 99 0x44 \'D\' LATIN CAPITAL LETTER D + 100 0x45 \'E\' LATIN CAPITAL LETTER E + 101 0x46 \'F\' LATIN CAPITAL LETTER F + 102 0x47 \'G\' LATIN CAPITAL LETTER G + 103 0x48 \'H\' LATIN CAPITAL LETTER H + 104 0x49 \'I\' LATIN CAPITAL LETTER I + 105 0x4A \'J\' LATIN CAPITAL LETTER J + 106 0x4B \'K\' LATIN CAPITAL LETTER K + 107 0x4C \'L\' LATIN CAPITAL LETTER L + 108 0x4D \'M\' LATIN CAPITAL LETTER M + 109 0x4E \'N\' LATIN CAPITAL LETTER N + 110 0x4F \'O\' LATIN CAPITAL LETTER O + 111 0x50 \'P\' LATIN CAPITAL LETTER P + 112 0x51 \'Q\' LATIN CAPITAL LETTER Q + 113 0x52 \'R\' LATIN CAPITAL LETTER R + 114 0x53 \'S\' LATIN CAPITAL LETTER S + 115 0x54 \'T\' LATIN CAPITAL LETTER T + 116 0x55 \'U\' LATIN CAPITAL LETTER U + 117 0x56 \'V\' LATIN CAPITAL LETTER V + 118 0x57 \'W\' LATIN CAPITAL LETTER W + 119 0x58 \'X\' LATIN CAPITAL LETTER X + 120 0x59 \'Y\' LATIN CAPITAL LETTER Y + 121 0x5A \'Z\' LATIN CAPITAL LETTER Z + 122 0x00 \'\\x00\' + 123 0x00 \'\\x00\' + 124 0x00 \'\\x00\' + 125 0x00 \'\\x00\' + 126 0x00 \'\\x00\' + 127 0x00 \'\\x00\' + 128 0x61 \'a\' LATIN SMALL LETTER A + 129 0x62 \'b\' LATIN SMALL LETTER B + 130 0x63 \'c\' LATIN SMALL LETTER C + 131 0x64 \'d\' LATIN SMALL LETTER D + 132 0x65 \'e\' LATIN SMALL LETTER E + 133 0x66 \'f\' LATIN SMALL LETTER F + 134 0x67 \'g\' LATIN SMALL LETTER G + 135 0x68 \'h\' LATIN SMALL LETTER H + 136 0x69 \'i\' LATIN SMALL LETTER I + 137 0x6A \'j\' LATIN SMALL LETTER J + 138 0x6B \'k\' LATIN SMALL LETTER K + 139 0x6C \'l\' LATIN SMALL LETTER L + 140 0x6D \'m\' LATIN SMALL LETTER M + 141 0x6E \'n\' LATIN SMALL LETTER N + 142 0x6F \'o\' LATIN SMALL LETTER O + 143 0x70 \'p\' LATIN SMALL LETTER P + 144 0x71 \'q\' LATIN SMALL LETTER Q + 145 0x72 \'r\' LATIN SMALL LETTER R + 146 0x73 \'s\' LATIN SMALL LETTER S + 147 0x74 \'t\' LATIN SMALL LETTER T + 148 0x75 \'u\' LATIN SMALL LETTER U + 149 0x76 \'v\' LATIN SMALL LETTER V + 150 0x77 \'w\' LATIN SMALL LETTER W + 151 0x78 \'x\' LATIN SMALL LETTER X + 152 0x79 \'y\' LATIN SMALL LETTER Y + 153 0x7A \'z\' LATIN SMALL LETTER Z + 154 0x00 \'\\x00\' + 155 0x00 \'\\x00\' + 156 0x00 \'\\x00\' + 157 0x00 \'\\x00\' + 158 0x00 \'\\x00\' + 159 0x00 \'\\x00\' +============ ========= ========= =========================================== diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 6f7c8514..d4bd0b77 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -19,6 +19,18 @@ def get_charmaps() -> Iterator[str]: yield name[len("CHARMAP_") :].lower() +def escape_rst(string: str) -> str: + """Escape RST symbols and disable Sphinx smart quotes.""" + return ( + string.replace("\\", "\\\\") + .replace("*", "\\*") + .replace("|", "\\|") + .replace("`", "\\`") + .replace("'", "\\'") + .replace('"', '\\"') + ) + + def generate_table(charmap: Iterable[int]) -> str: """Generate and RST table for `charmap`.""" headers = ("Tile Index", "Unicode", "String", "Name") @@ -33,7 +45,7 @@ def generate_table(charmap: Iterable[int]) -> str: except ValueError: # Skip names rather than guessing, the official names are enough. name = "" - string = f"{chr(ch)!r}".replace("\\", "\\\\").replace("*", "\\*").replace("`", "\\`") # Escape RST symbols. + string = escape_rst(f"{chr(ch)!r}") table.append((i, f"0x{ch:0{hex_len}X}", string, name)) return tabulate(table, headers, tablefmt="rst") From c7f70d205df3a7c69c7c27213c06fa92b4e64a97 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 8 Sep 2021 13:58:27 -0700 Subject: [PATCH 0578/1101] Document color controls, allow doctests in the docs directory. --- docs/glossary.rst | 3 +-- docs/libtcodpy.rst | 30 ++++++++++++++++++++++++++++-- setup.cfg | 2 ++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 7cd9359a..5380de58 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -60,5 +60,4 @@ Glossary color control color controls Libtcod's old system which assigns colors to specific codepoints. - This is deprecated in favor of the codes which set the foreground and - background colors directly. + See :any:`tcod.COLCTRL_STOP`, :any:`tcod.COLCTRL_FORE_RGB`, and :any:`tcod.COLCTRL_BACK_RGB` for examples. diff --git a/docs/libtcodpy.rst b/docs/libtcodpy.rst index 1783b5e1..1b6073d0 100644 --- a/docs/libtcodpy.rst +++ b/docs/libtcodpy.rst @@ -47,19 +47,45 @@ color color controls ~~~~~~~~~~~~~~ -Configurable color control constants which can be set up with -:any:`tcod.console_set_color_control`. +Libtcod color control constants. +These can be inserted into Python strings with the ``%c`` format specifier as shown below. .. data:: tcod.COLCTRL_1 + + These can be configured with :any:`tcod.console_set_color_control`. + However, it is recommended to use :any:`tcod.COLCTRL_FORE_RGB` and :any:`tcod.COLCTRL_BACK_RGB` instead. + .. data:: tcod.COLCTRL_2 .. data:: tcod.COLCTRL_3 .. data:: tcod.COLCTRL_4 .. data:: tcod.COLCTRL_5 .. data:: tcod.COLCTRL_STOP + + When this control character is inserted into a string the foreground and background colors will be reset for the + remaining characters of the string. + + >>> import tcod + >>> reset_color = f"{tcod.COLCTRL_STOP:c}" + .. data:: tcod.COLCTRL_FORE_RGB + + Sets the foreground color to the next 3 Unicode characters for the remaining characters. + + >>> fg = (255, 255, 255) + >>> change_fg = f"{tcod.COLCTRL_FORE_RGB:c}{fg[0]:c}{fg[1]:c}{fg[2]:c}" + >>> string = f"Old color {change_fg}new color{tcod.COLCTRL_STOP:c} old color." + .. data:: tcod.COLCTRL_BACK_RGB + Sets the background color to the next 3 Unicode characters for the remaining characters. + + >>> from typing import Tuple + >>> def change_colors(fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> str: + ... """Return the control codes to change the foreground and background colors.""" + ... return "%c%c%c%c%c%c%c%c" % (tcod.COLCTRL_FORE_RGB, *fg, tcod.COLCTRL_BACK_RGB, *bg) + >>> string = f"Old {change_colors(fg=(255, 255, 255), bg=(0, 0, 255))}new" + console ------- diff --git a/setup.cfg b/setup.cfg index 5741e285..16c31f1b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,9 @@ test=pytest addopts= tcod/ tests/ + docs/ --doctest-modules + --doctest-glob="*.rst" --cov=tcod --capture=sys --ignore=tcod/__pyinstaller From fd466bd30ca7a3f4e5196797412fd477a1150bad Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 20 Sep 2021 14:24:46 -0700 Subject: [PATCH 0579/1101] Switch to using negative positions as absolute coordinates. --- CHANGELOG.rst | 2 ++ tcod/console.py | 30 ++++++++++++------------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a12cc2f8..fc7d0d86 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Changed + - Console print and drawing functions now always use absolute coordinates for negative numbers. 12.7.3 - 2021-08-13 ------------------- diff --git a/tcod/console.py b/tcod/console.py index 3377ece8..e197c28b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -926,20 +926,6 @@ def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" return "<%s>" % "\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) - def _pythonic_index(self, x: int, y: int) -> Tuple[int, int]: - if __debug__ and (x < 0 or y < 0): - warnings.warn( - "Negative indexes will be treated as absolute coordinates instead of relative in the future." - " The current behavior of indexing from the end is deprecated.", - FutureWarning, - stacklevel=3, - ) - if x < 0: - x += self.width - if y < 0: - y += self.height - return x, y - def print( self, x: int, @@ -972,8 +958,10 @@ def print( .. versionchanged:: 9.0 `fg` and `bg` now default to `None` instead of white-on-black. + + .. versionchanged:: 13.0 + `x` and `y` are now always used as an absolute position for negative values. """ - x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes lib.TCOD_console_printn( self.console_c, @@ -1025,8 +1013,10 @@ def print_box( .. versionchanged:: 9.0 `fg` and `bg` now default to `None` instead of white-on-black. + + .. versionchanged:: 13.0 + `x` and `y` are now always used as an absolute position for negative values. """ - x, y = self._pythonic_index(x, y) string_ = string.encode("utf-8") # type: bytes return int( lib.TCOD_console_printn_rect( @@ -1095,6 +1085,9 @@ def draw_frame( .. versionchanged:: 12.6 Added `decoration` parameter. + .. versionchanged:: 13.0 + `x` and `y` are now always used as an absolute position for negative values. + Example:: >>> console = tcod.Console(12, 6) @@ -1115,7 +1108,6 @@ def draw_frame( │ │ └─┤Lower├──┘> """ - x, y = self._pythonic_index(x, y) if title and decoration != "┌─┐│ │└─┘": raise TypeError( "The title and decoration parameters are mutually exclusive. You should print the title manually." @@ -1195,8 +1187,10 @@ def draw_rect( .. versionchanged:: 9.0 `fg` and `bg` now default to `None` instead of white-on-black. + + .. versionchanged:: 13.0 + `x` and `y` are now always used as an absolute position for negative values. """ - x, y = self._pythonic_index(x, y) lib.TCOD_console_draw_rect_rgb( self.console_c, x, From adb3999595c02a693f38123a82edcd542167c301 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 20 Sep 2021 15:03:23 -0700 Subject: [PATCH 0580/1101] Prepare 13.0.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc7d0d86..daf2e183 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +13.0.0 - 2021-09-20 +------------------- Changed - Console print and drawing functions now always use absolute coordinates for negative numbers. From 418e3e5fb8cbcfb5f71935d2c8f69eee00105f34 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 11:59:50 -0700 Subject: [PATCH 0581/1101] Show diffs of failed formatter checks in logs. --- .github/workflows/python-lint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index 83ed26ff..e7b775bd 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -43,12 +43,12 @@ jobs: uses: liskin/gh-problem-matcher-wrap@v1 with: linters: isort - run: isort scripts/ tcod/ tests/ --check + run: isort scripts/ tcod/ tests/ --check --diff - name: isort (examples) uses: liskin/gh-problem-matcher-wrap@v1 with: linters: isort - run: isort examples/ --check --thirdparty tcod + run: isort examples/ --check --diff --thirdparty tcod - name: Black run: | - black --check examples/ scripts/ tcod/ tests/ *.py + black --check --diff examples/ scripts/ tcod/ tests/ *.py From 147987079385c3b4cdeece3ba112f425aa1b8905 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 12:18:53 -0700 Subject: [PATCH 0582/1101] Drop supoprt for Python 3.6 and older CI's. --- .appveyor/install_python.ps1 | 27 --- .github/workflows/python-lint.yml | 2 +- .github/workflows/python-package.yml | 6 +- .travis.yml | 68 -------- .travis/before_install.sh | 19 --- .travis/install_python.sh | 239 --------------------------- .travis/launchpad.key.enc | Bin 2512 -> 0 bytes .travis/upload_ppa.sh | 19 --- CHANGELOG.rst | 2 + README.rst | 2 +- appveyor.yml | 52 ------ pyproject.toml | 14 +- requirements.txt | 3 +- setup.py | 5 +- tcod/noise.py | 6 +- tcod/path.py | 6 +- tcod/tileset.py | 6 +- tests/test_tcod.py | 6 +- 18 files changed, 18 insertions(+), 464 deletions(-) delete mode 100644 .appveyor/install_python.ps1 delete mode 100644 .travis.yml delete mode 100644 .travis/before_install.sh delete mode 100755 .travis/install_python.sh delete mode 100644 .travis/launchpad.key.enc delete mode 100755 .travis/upload_ppa.sh delete mode 100644 appveyor.yml diff --git a/.appveyor/install_python.ps1 b/.appveyor/install_python.ps1 deleted file mode 100644 index c1213cc8..00000000 --- a/.appveyor/install_python.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -$env:ACTIVATE_VENV='venv\Scripts\activate.bat' - -if ($env:PYPY -or $env:PYPY3) { - if($env:PYPY3){ - $env:PYPY_EXE='pypy3.exe' - $env:PYPY=$env:PYPY3 - } else { - $env:PYPY_EXE='pypy.exe' - } - $env:PYPY = $env:PYPY + '-win32' - $env:PYTHON = 'C:\' + $env:PYPY + '\' + $env:PYPY_EXE - $env:PATH += ';' + 'C:\' + $env:PYPY + '\' - $PYPY_DOWNLOAD = 'https://downloads.python.org/pypy/' + $env:PYPY + '.zip' - Invoke-WebRequest $PYPY_DOWNLOAD -OutFile C:\pypy.zip - & '7z' x C:\pypy.zip -oC:\ - & $env:PYTHON -m ensurepip -} -if ($env:WEB_PYTHON) { - $PYTHON_INSTALLER = 'C:\python-webinstall.exe' - Start-FileDownload $env:WEB_PYTHON -FileName $PYTHON_INSTALLER - Start-Process $PYTHON_INSTALLER -Wait -ArgumentList "/quiet InstallAllUsers=1 TargetDir=C:\UserPython Include_doc=0 Include_launcher=0 Include_test=0 Shortcuts=0" - $env:PYTHON = 'C:\UserPython\python.exe' -} -& $env:PYTHON -m pip install --disable-pip-version-check --no-warn-script-location "virtualenv>=20" -& $env:PYTHON -m virtualenv venv - -if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml index e7b775bd..834ee439 100644 --- a/.github/workflows/python-lint.yml +++ b/.github/workflows/python-lint.yml @@ -9,7 +9,7 @@ jobs: lint: runs-on: ubuntu-20.04 env: - python-version: "3.9" + python-version: "3.x" steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c5ae906b..bcb0953c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -17,14 +17,14 @@ jobs: strategy: matrix: os: ["ubuntu-20.04", "windows-2019"] - python-version: ["3.6", "3.7", "3.8", "3.9", "pypy-3.7"] + python-version: ["3.7", "3.8", "3.9", "pypy-3.7"] architecture: ["x64"] include: - os: "windows-2019" - python-version: "3.6" + python-version: "3.7" architecture: "x86" - os: "windows-2019" - python-version: "pypy-3.6" + python-version: "pypy-3.7" architecture: "x86" fail-fast: false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b800adfd..00000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -language: python -jobs: - include: - - os: linux - python: 3.5 - - os: linux - python: 3.6 - - os: linux - python: 3.7 - - os: linux - python: 3.8 - - os: linux - python: 3.9 - - os: linux - python: nightly - - os: osx - language: generic - env: MB_PYTHON_VERSION=3.7.1 - - os: osx - language: generic - env: PYPY3_VERSION=pypy3.6-v7.3.2 CUSTOM_REQUIREMENTS="numpy==1.18" - - os: osx - language: generic - env: PYPY3_VERSION=pypy3.7-v7.3.2 CUSTOM_REQUIREMENTS="numpy==1.18" - allow_failures: - - python: nightly - - python: pypy3.5-7.0 - - fast_finish: true - -os: linux -dist: bionic -cache: pip - -addons: - apt: - packages: - - libsdl2-dev -services: - - xvfb - -before_install: -- source .travis/before_install.sh -- source .travis/install_python.sh -- 'wget https://bootstrap.pypa.io/get-pip.py' -- python get-pip.py -- pip install --upgrade setuptools -- pip install --pre --upgrade wheel -- if [[ -n "$CUSTOM_REQUIREMENTS" ]]; then pip install $CUSTOM_REQUIREMENTS; fi -- pip install --requirement requirements.txt -install: -- if [[ -z "$TRAVIS_TAG" ]]; then export BUILD_FLAGS="-g"; fi -- python setup.py build $BUILD_FLAGS sdist develop bdist_wheel --py-limited-api=cp35 || (sleep 5; exit 1) -- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then python -m pip install delocate; fi' -- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-wheel -v dist/*.whl; fi' -- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then delocate-listdeps --all dist/*.whl; fi' -before_script: -- pip install --upgrade pytest pytest-cov -script: -- pytest -v -after_success: -- if [[ -n "$TRAVIS_TAG" ]]; then pip install --upgrade twine; fi -- if [[ -n "$TRAVIS_TAG" && -z "$PYPY3_VERSION" ]]; then pip install --upgrade pyOpenSSL; fi -- if [[ -n "$TRAVIS_TAG" && "$TRAVIS_OS_NAME" == "osx" ]]; then twine upload --skip-existing dist/*; fi -- pip install codacy-coverage codecov -- codecov -- coverage xml -- if [[ -n "$CODACY_PROJECT_TOKEN" ]]; then python-codacy-coverage -r coverage.xml || true; fi diff --git a/.travis/before_install.sh b/.travis/before_install.sh deleted file mode 100644 index 0617766b..00000000 --- a/.travis/before_install.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - SDL_MAJOR=2 - SDL_MINOR=5 - UPDATE_SDL=true - - if [[ $(sdl2-config --version) =~ ([0-9]*).([0-9]*).([0-9]*) ]]; then - if (( ${BASH_REMATCH[1]} >= $SDL_MAJOR && ${BASH_REMATCH[3]} >= $SDL_MINOR )); then - UPDATE_SDL=false - fi - fi - - if [[ $UPDATE_SDL == "true" ]]; then - # Update SDL2 to a recent version. - wget -O - https://www.libsdl.org/release/SDL2-2.0.8.tar.gz | tar xz - (cd SDL2-* && ./configure --prefix=$HOME/.local && make -j 3 install) - PATH=~/.local/bin:$PATH - fi -fi diff --git a/.travis/install_python.sh b/.travis/install_python.sh deleted file mode 100755 index f20a7f8a..00000000 --- a/.travis/install_python.sh +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/bash -# ********************* -# Copyright and License -# ********************* - -# The multibuild package, including all examples, code snippets and attached -# documentation is covered by the 2-clause BSD license. - - # Copyright (c) 2013-2016, Matt Terry and Matthew Brett; all rights - # reserved. - - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are - # met: - - # 1. Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - - # 2. Redistributions in binary form must reproduce the above copyright - # notice, this list of conditions and the following disclaimer in the - # documentation and/or other materials provided with the distribution. - - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -set -e - -# Work round bug in travis xcode image described at -# https://github.com/direnv/direnv/issues/210 -shell_session_update() { :; } - -MACPYTHON_URL=https://www.python.org/ftp/python -PYPY_URL=https://downloads.python.org/pypy -MACPYTHON_PY_PREFIX=/Library/Frameworks/Python.framework/Versions -WORKING_SDIR=working -DOWNLOADS_SDIR=/tmp - -function untar { - local in_fname=$1 - if [ -z "$in_fname" ];then echo "in_fname not defined"; exit 1; fi - local extension=${in_fname##*.} - case $extension in - tar) tar -xf $in_fname ;; - gz|tgz) tar -zxf $in_fname ;; - bz2) tar -jxf $in_fname ;; - zip) unzip $in_fname ;; - xz) unxz -c $in_fname | tar -xf ;; - *) echo Did not recognize extension $extension; exit 1 ;; - esac -} - -function lex_ver { - # Echoes dot-separated version string padded with zeros - # Thus: - # 3.2.1 -> 003002001 - # 3 -> 003000000 - echo $1 | awk -F "." '{printf "%03d%03d%03d", $1, $2, $3}' -} - -function unlex_ver { - # Reverses lex_ver to produce major.minor.micro - # Thus: - # 003002001 -> 3.2.1 - # 003000000 -> 3.0.0 - echo "$((10#${1:0:3}+0)).$((10#${1:3:3}+0)).$((10#${1:6:3}+0))" -} - -function strip_ver_suffix { - echo $(unlex_ver $(lex_ver $1)) -} - -function check_var { - if [ -z "$1" ]; then - echo "required variable not defined" - exit 1 - fi -} - -function pyinst_ext_for_version { - # echo "pkg" or "dmg" depending on the passed Python version - # Parameters - # $py_version (python version in major.minor.extra format) - # - # Earlier Python installers are .dmg, later are .pkg. - local py_version=$1 - check_var $py_version - local py_0=${py_version:0:1} - if [ $py_0 -eq 2 ]; then - if [ "$(lex_ver $py_version)" -ge "$(lex_ver 2.7.9)" ]; then - echo "pkg" - else - echo "dmg" - fi - elif [ $py_0 -ge 3 ]; then - if [ "$(lex_ver $py_version)" -ge "$(lex_ver 3.4.2)" ]; then - echo "pkg" - else - echo "dmg" - fi - fi -} - -function get_pypy_build_prefix { - # gets the file prefix of the pypy.org PyPy2 - # - # Parameters: - # $version : pypy2 version number - local version=$1 - if [[ $version =~ ([0-9]+)\.([0-9]+) ]]; then - local major=${BASH_REMATCH[1]} - local minor=${BASH_REMATCH[2]} - if (( $major > 5 || ($major == 5 && $minor >= 3) )); then - echo "pypy2-v" - else - echo "pypy-" - fi - else - echo "error: expected version number, got $1" 1>&2 - exit 1 - fi -} - -function get_pypy3_build_prefix { - # gets the file prefix of the pypy.org PyPy3 - # - # Parameters: - # $version : pypy3 version number - local version=$1 - if [[ $version =~ ([0-9]+)\.([0-9]+) ]]; then - local major=${BASH_REMATCH[1]} - local minor=${BASH_REMATCH[2]} - if (( $major == 5 && $minor <= 5 )); then - echo "pypy3.3-v" - elif (( $major < 5 )); then - echo "pypy3-" - else - echo "pypy3-v" - fi - else - echo "error: expected version number, got $1" 1>&2 - exit 1 - fi -} - -function install_python { - # Picks an implementation of Python determined by the current enviroment - # variables then installs it - # Sub-function will set $PYTHON_EXE variable to the python executable - if [ -n "$MB_PYTHON_VERSION" ]; then - install_macpython $MB_PYTHON_VERSION - elif [ -n "$PYPY_VERSION" ]; then - install_mac_pypy $PYPY_VERSION - elif [ -n "$PYPY3_VERSION" ]; then - install_mac_pypy3 $PYPY3_VERSION - elif [ -z "$BREW_PYTHON3" ]; then - # Default Python install is missing virtualenv. - python -m ensurepip - python -m pip install virtualenv - fi -} - -function install_macpython { - # Installs Python.org Python - # Parameter $version - # Version given in major or major.minor or major.minor.micro e.g - # "3" or "3.4" or "3.4.1". - # sets $PYTHON_EXE variable to python executable - local py_version=$1 - local py_stripped=$(strip_ver_suffix $py_version) - local inst_ext=$(pyinst_ext_for_version $py_version) - local py_inst=python-$py_version-macosx10.9.$inst_ext - local inst_path=$DOWNLOADS_SDIR/$py_inst - mkdir -p $DOWNLOADS_SDIR - wget -nv $MACPYTHON_URL/$py_stripped/${py_inst} -P $DOWNLOADS_SDIR - if [ "$inst_ext" == "dmg" ]; then - hdiutil attach $inst_path -mountpoint /Volumes/Python - inst_path=/Volumes/Python/Python.mpkg - fi - sudo installer -pkg $inst_path -target / - local py_mm=${py_version:0:3} - PYTHON_EXE=$MACPYTHON_PY_PREFIX/$py_mm/bin/python$py_mm - # Install certificates for Python 3.6 - local inst_cmd="/Applications/Python ${py_mm}/Install Certificates.command" - if [ -e "$inst_cmd" ]; then - sh "$inst_cmd" - fi -} - -function install_mac_pypy { - # Installs pypy.org PyPy - # Parameter $version - # Version given in full major.minor.micro e.g "3.4.1". - # sets $PYTHON_EXE variable to python executable - local py_version=$1 - local py_build=$(get_pypy_build_prefix $py_version)$py_version-osx64 - local py_zip=$py_build.tar.bz2 - local zip_path=$DOWNLOADS_SDIR/$py_zip - mkdir -p $DOWNLOADS_SDIR - wget -nv $PYPY_URL/${py_zip} -P $DOWNLOADS_SDIR - untar $zip_path - PYTHON_EXE=$(realpath $py_build/bin/pypy) -} - -function install_mac_pypy3 { - # Installs pypy.org PyPy3 - # Parameter $version - # Version given in full major.minor.micro e.g "3.4.1". - # sets $PYTHON_EXE variable to python executable - local py_version=$1 - local py_build=$py_version-osx64 - local py_zip=$py_build.tar.bz2 - local zip_path=$DOWNLOADS_SDIR/$py_zip - mkdir -p $DOWNLOADS_SDIR - wget -nv $PYPY_URL/${py_zip} -P $DOWNLOADS_SDIR - mkdir -p $py_build - tar -xjf $zip_path -C $py_build --strip-components=1 - PYTHON_EXE=$(realpath $py_build/bin/pypy3) -} - -if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - install_python - if [[ -n "$PYTHON_EXE" ]]; then - # Default Python install is missing virtualenv. - $PYTHON_EXE -m ensurepip - $PYTHON_EXE -m pip install --upgrade pip virtualenv - $PYTHON_EXE -m virtualenv venv -p $PYTHON_EXE - else - virtualenv venv - fi - source venv/bin/activate -fi diff --git a/.travis/launchpad.key.enc b/.travis/launchpad.key.enc deleted file mode 100644 index 95ffbd8c555b3f2c94e21c6cf4ac8bbc308d951c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2512 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 a%CZx+6kNb4Q*le@e4o^v%-LH^j#zY7w(Y3^ diff --git a/.travis/upload_ppa.sh b/.travis/upload_ppa.sh deleted file mode 100755 index 39222cd1..00000000 --- a/.travis/upload_ppa.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -function deb_install { - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - pip install stdeb - pip3 install --upgrade pip --user - pip3 install --requirement requirements.txt --user - ./setup.py sdist - ./setup.py --command-packages=stdeb.command sdist_dsc --use-premade-distfile dist/tdl-*.tar.gz --with-python2=True --with-python3=True --debian-version artful1 --suite artful - ./setup.py --command-packages=stdeb.command sdist_dsc --use-premade-distfile dist/tdl-*.tar.gz --with-python2=True --with-python3=True --debian-version bionic1 --suite bionic - export DEB_READY='yes' - fi -} -function deb_upload { - if [[ -n "$TRAVIS_TAG" && -n "$DEB_READY" ]]; then - openssl aes-256-cbc -K $encrypted_765c87af1f2f_key -iv $encrypted_765c87af1f2f_iv -in .travis/launchpad.key.enc -d | gpg --import - debsign -k5B69F065 deb_dist/*.changes - dput ppa:4b796c65/ppa deb_dist/*.changes - fi -} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index daf2e183..0ed91cc0 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Removed + - Python 3.6 is no longer supported. 13.0.0 - 2021-09-20 ------------------- diff --git a/README.rst b/README.rst index b2f41a3e..53b4ec4a 100755 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ For the most part it's just:: ============== Requirements ============== -* Python 3.6+ +* Python 3.7+ * Windows, Linux, or MacOS X 10.9+. * On Linux, requires libsdl2 (2.0.5+). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 71f56068..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,52 +0,0 @@ -image: Visual Studio 2019 - -environment: - CODACY_PROJECT_TOKEN: - secure: xprpiCGL823NKrs/K2Cps1UVBEmpezXReLxcfLyU1M43ZBBOK91xvjdIJamYKi8D - DEPLOY_ONLY: false - EXTRA_SETUP_ARGS: "" - matrix: - - PYTHON: C:\Python36-x64\python.exe - platform: x64 - EXTRA_SETUP_ARGS: "--py-limited-api cp36" - - PYTHON: C:\Python36\python.exe - platform: Any CPU - EXTRA_SETUP_ARGS: "--py-limited-api cp36" - - PYTHON: C:\Python37-x64\python.exe - platform: x64 - NO_DEPLOY: true - - PYTHON: C:\Python38-x64\python.exe - platform: x64 - NO_DEPLOY: true - - PYTHON: C:\Python39-x64\python.exe - platform: x64 - NO_DEPLOY: true - -matrix: - allow_failures: - - DEPLOY_ONLY: true -clone_depth: 50 - -init: -- cmd: "if %APPVEYOR_REPO_TAG%==false if %DEPLOY_ONLY%==true exit /b 1" - -install: -- cmd: "git submodule update --init --recursive" -- ps: ". .appveyor/install_python.ps1" -- cmd: "%ACTIVATE_VENV%" -- cmd: "set PATH=%APPVEYOR_BUILD_FOLDER%\\venv\\bin;%PATH%" -- cmd: "set PATH=%PATH%;C:\\MinGW\\bin" -- cmd: "python --version" -- cmd: "python -m pip install --upgrade pip" -- cmd: "pip install --requirement requirements.txt" -- cmd: "python -O setup.py build sdist develop bdist_wheel %EXTRA_SETUP_ARGS%" -build: off -before_test: -- cmd: "pip install pytest pytest-cov" -test_script: -- ps: "pytest -v" - -on_success: -- pip install codacy-coverage -- coverage xml -- if defined CODACY_PROJECT_TOKEN python-codacy-coverage -r coverage.xml || cd . diff --git a/pyproject.toml b/pyproject.toml index 0278424c..d4450626 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,13 @@ [build-system] -requires = [ - "setuptools>=57.0.0", - "wheel", - "cffi~=1.13", - "pycparser>=2.14", -] +requires = ["setuptools>=57.0.0", "wheel", "cffi~=1.13", "pycparser>=2.14"] build-backend = "setuptools.build_meta" [tool.black] line-length = 120 -py36 = true -target-version = ["py36"] +target-version = ["py37"] [tool.isort] -profile= "black" -py_version = "36" +profile = "black" +py_version = "37" skip_gitignore = true line_length = 120 diff --git a/requirements.txt b/requirements.txt index f458e1e8..056e1c9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ cffi~=1.13 -numpy>=1.20.3; python_version >= '3.7' -numpy>=1.19.5; python_version <= '3.6' +numpy>=1.20.3 pycparser>=2.14 setuptools>=36.0.1 typing_extensions diff --git a/setup.py b/setup.py index aa3ca8cf..bca8a935 100755 --- a/setup.py +++ b/setup.py @@ -119,7 +119,7 @@ def check_sdl_version() -> None: py_modules=["libtcodpy"], packages=["tcod", "tcod.__pyinstaller"], package_data={"tcod": get_package_data()}, - python_requires=">=3.6", + python_requires=">=3.7", setup_requires=[ *pytest_runner, "cffi~=1.13", @@ -127,7 +127,7 @@ def check_sdl_version() -> None: ], install_requires=[ "cffi~=1.13", # Also required by pyproject.toml. - "numpy~=1.10" if not is_pypy else "", + "numpy>=1.20.3" if not is_pypy else "", "typing_extensions", ], cffi_modules=["build_libtcod.py:ffi"], @@ -144,7 +144,6 @@ def check_sdl_version() -> None: "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tcod/noise.py b/tcod/noise.py index 5297911e..f4623302 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -39,6 +39,7 @@ from typing import Any, Optional, Sequence, Tuple, Union import numpy as np +from numpy.typing import ArrayLike from typing_extensions import Literal import tcod.constants @@ -46,11 +47,6 @@ from tcod._internal import deprecate from tcod.loader import ffi, lib -try: - from numpy.typing import ArrayLike -except ImportError: # Python < 3.7, Numpy < 1.20 - from typing import Any as ArrayLike - class Algorithm(enum.IntEnum): """Libtcod noise algorithms. diff --git a/tcod/path.py b/tcod/path.py index 2bb3439d..4953ec98 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,17 +21,13 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np +from numpy.typing import ArrayLike from typing_extensions import Literal import tcod.map # noqa: F401 from tcod._internal import _check from tcod.loader import ffi, lib -try: - from numpy.typing import ArrayLike -except ImportError: # Python < 3.7, Numpy < 1.20 - from typing import Any as ArrayLike - @ffi.def_extern() # type: ignore def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: diff --git a/tcod/tileset.py b/tcod/tileset.py index c360f651..5a054f2e 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -16,16 +16,12 @@ from typing import Any, Iterable, Optional, Tuple, Union import numpy as np +from numpy.typing import ArrayLike import tcod.console from tcod._internal import _check, _console, _raise_tcod_error, deprecate from tcod.loader import ffi, lib -try: - from numpy.typing import ArrayLike -except ImportError: # Python < 3.7, Numpy < 1.20 - from typing import Any as ArrayLike - class Tileset: """A collection of graphical tiles. diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 7780f5d5..bfb6a36e 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -6,14 +6,10 @@ import numpy as np import pytest +from numpy.typing import DTypeLike import tcod -try: - from numpy.typing import DTypeLike -except ImportError: # Python < 3.7 - from typing import Any as DTypeLike - def raise_Exception(*args: Any) -> NoReturn: raise RuntimeError("testing exception") From 0444ca5749b2d455820f8c22fa4a526ad5db8224 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 12:21:34 -0700 Subject: [PATCH 0583/1101] Remove TravisCI and AppVeyor badges. --- README.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 53b4ec4a..c5df64d6 100755 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ ======== |VersionsBadge| |ImplementationBadge| |LicenseBadge| -|PyPI| |RTD| |Appveyor| |Travis| |Codecov| |Codacy| +|PyPI| |RTD| |Codecov| |Codacy| |Requires| |Pyup| @@ -93,12 +93,6 @@ python-tcod is distributed under the `Simplified 2-clause FreeBSD license :target: http://python-tcod.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/bb04bpankj0h1cpa/branch/master?svg=true - :target: https://ci.appveyor.com/project/HexDecimal/python-tdl/branch/master - -.. |Travis| image:: https://travis-ci.org/libtcod/python-tcod.svg?branch=master - :target: https://travis-ci.org/libtcod/python-tcod - .. |Codecov| image:: https://codecov.io/gh/libtcod/python-tcod/branch/master/graph/badge.svg :target: https://codecov.io/gh/libtcod/python-tcod From 2ed28deb3903372132c1c3536289b522380beca9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 12:26:02 -0700 Subject: [PATCH 0584/1101] Combine package workflows. --- .github/workflows/python-package-macos.yml | 59 ---------------------- .github/workflows/python-package.yml | 54 +++++++++++++++++++- 2 files changed, 53 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/python-package-macos.yml diff --git a/.github/workflows/python-package-macos.yml b/.github/workflows/python-package-macos.yml deleted file mode 100644 index ec264af9..00000000 --- a/.github/workflows/python-package-macos.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Python package (MacOS) - -on: - push: - pull_request: - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ["macos-latest"] - python-version: ["3.x"] - - steps: - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - - uses: actions/checkout@v1 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Print git describe - run: | - git describe - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install wheel twine - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Prepare package. - # Downloads SDL2 for the later step. - run: | - python setup.py check - - name: Build wheels - uses: pypa/cibuildwheel@v2.0.0a4 - env: - CIBW_BUILD: cp38-* pp* - CIBW_ARCHS_MACOS: x86_64 arm64 universal2 - CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/matthew-brett/delocate.git@1c07b50 - CIBW_BEFORE_TEST: pip install numpy - CIBW_TEST_COMMAND: python -c "import tcod" - CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" - - name: Archive wheel - uses: actions/upload-artifact@v2 - with: - name: wheel-macos - path: wheelhouse/*.whl - retention-days: 1 - - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing wheelhouse/* diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bcb0953c..b462f3bb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -109,7 +109,7 @@ jobs: run: | python -c "import tcod" - linux_wheels: + linux-wheels: runs-on: "ubuntu-20.04" steps: - uses: actions/checkout@v1 @@ -148,3 +148,55 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | twine upload --skip-existing wheelhouse/* + + build-macos: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["macos-latest"] + python-version: ["3.x"] + steps: + # v2 breaks `git describe` so v1 is needed. + # https://github.com/actions/checkout/issues/272 + - uses: actions/checkout@v1 + - name: Checkout submodules + run: | + git submodule update --init --recursive --depth 1 + - name: Print git describe + run: | + git describe + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install wheel twine + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Prepare package. + # Downloads SDL2 for the later step. + run: | + python setup.py check + - name: Build wheels + uses: pypa/cibuildwheel@v2.0.0a4 + env: + CIBW_BUILD: cp38-* pp* + CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/matthew-brett/delocate.git@1c07b50 + CIBW_BEFORE_TEST: pip install numpy + CIBW_TEST_COMMAND: python -c "import tcod" + CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" + - name: Archive wheel + uses: actions/upload-artifact@v2 + with: + name: wheel-macos + path: wheelhouse/*.whl + retention-days: 1 + - name: Upload to PyPI + if: startsWith(github.ref, 'refs/tags/') + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + twine upload --skip-existing wheelhouse/* From f1135c3e709bbe3dc9c3f12ca7d3c57a40e573d0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 12:31:00 -0700 Subject: [PATCH 0585/1101] Pin macOS version on package workflow. --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b462f3bb..f3b33abe 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -153,7 +153,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["macos-latest"] + os: ["macos-10.15"] python-version: ["3.x"] steps: # v2 breaks `git describe` so v1 is needed. From 00b287cc74741635ee57c05428853fa00746a616 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Sep 2021 13:31:14 -0700 Subject: [PATCH 0586/1101] Clean up type annotations and use future annotations. --- tcod/__init__.py | 2 ++ tcod/_internal.py | 2 ++ tcod/bsp.py | 32 +++++++++--------- tcod/color.py | 10 +++--- tcod/console.py | 53 +++++++++++++++-------------- tcod/context.py | 10 +++--- tcod/event.py | 75 +++++++++++++++++++++-------------------- tcod/image.py | 13 +++++--- tcod/libtcodpy.py | 85 ++++++++++++++++++++++------------------------- tcod/loader.py | 6 ++-- tcod/los.py | 5 ++- tcod/map.py | 13 +++++--- tcod/noise.py | 12 ++++--- tcod/path.py | 66 ++++++++++++++++++------------------ tcod/random.py | 6 ++-- tcod/sdl.py | 7 ++-- tcod/tileset.py | 14 ++++---- 17 files changed, 222 insertions(+), 189 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index cf0a9038..98c489b1 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -6,6 +6,8 @@ Read the documentation online: https://python-tcod.readthedocs.io/en/latest/ """ +from __future__ import annotations + import sys import warnings diff --git a/tcod/_internal.py b/tcod/_internal.py index d785301d..9704d3f3 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,5 +1,7 @@ """This module internal helper functions used by the rest of the library. """ +from __future__ import annotations + import functools import warnings from typing import Any, AnyStr, Callable, TypeVar, cast diff --git a/tcod/bsp.py b/tcod/bsp.py index ee69b907..42f82460 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -23,6 +23,8 @@ else: print('Dig a room for %s.' % node) """ +from __future__ import annotations + from typing import Any, Iterator, List, Optional, Tuple, Union # noqa: F401 import tcod.random @@ -64,8 +66,8 @@ def __init__(self, x: int, y: int, width: int, height: int): self.position = 0 self.horizontal = False - self.parent: Optional["BSP"] = None - self.children: Union[Tuple[()], Tuple["BSP", "BSP"]] = () + self.parent: Optional[BSP] = None + self.children: Union[Tuple[()], Tuple[BSP, BSP]] = () @property def w(self) -> int: @@ -172,7 +174,7 @@ def split_recursive( self._unpack_bsp_tree(cdata) @deprecate("Use pre_order method instead of walk.") - def walk(self) -> Iterator["BSP"]: + def walk(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in pre order. .. deprecated:: 2.3 @@ -180,7 +182,7 @@ def walk(self) -> Iterator["BSP"]: """ return self.post_order() - def pre_order(self) -> Iterator["BSP"]: + def pre_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in pre order. .. versionadded:: 8.3 @@ -189,7 +191,7 @@ def pre_order(self) -> Iterator["BSP"]: for child in self.children: yield from child.pre_order() - def in_order(self) -> Iterator["BSP"]: + def in_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in order. .. versionadded:: 8.3 @@ -201,7 +203,7 @@ def in_order(self) -> Iterator["BSP"]: else: yield self - def post_order(self) -> Iterator["BSP"]: + def post_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in post order. .. versionadded:: 8.3 @@ -210,29 +212,29 @@ def post_order(self) -> Iterator["BSP"]: yield from child.post_order() yield self - def level_order(self) -> Iterator["BSP"]: + def level_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in level order. .. versionadded:: 8.3 """ - next = [self] # type: List['BSP'] + next = [self] while next: - level = next # type: List['BSP'] + level = next next = [] yield from level for node in level: next.extend(node.children) - def inverted_level_order(self) -> Iterator["BSP"]: + def inverted_level_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in inverse level order. .. versionadded:: 8.3 """ - levels = [] # type: List[List['BSP']] - next = [self] # type: List['BSP'] + levels: List[List[BSP]] = [] + next = [self] while next: levels.append(next) - level = next # type: List['BSP'] + level = next next = [] for node in level: next.extend(node.children) @@ -252,7 +254,7 @@ def contains(self, x: int, y: int) -> bool: """ return self.x <= x < self.x + self.width and self.y <= y < self.y + self.height - def find_node(self, x: int, y: int) -> Optional["BSP"]: + def find_node(self, x: int, y: int) -> Optional[BSP]: """Return the deepest node which contains these coordinates. Returns: @@ -261,7 +263,7 @@ def find_node(self, x: int, y: int) -> Optional["BSP"]: if not self.contains(x, y): return None for child in self.children: - found = child.find_node(x, y) # type: Optional["BSP"] + found: Optional[BSP] = child.find_node(x, y) if found: return found return self diff --git a/tcod/color.py b/tcod/color.py index b7a5f4db..f1bcf570 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,6 +1,8 @@ """ """ +from __future__ import annotations + import warnings from typing import Any, List @@ -63,7 +65,7 @@ def b(self, value: int) -> None: self[2] = value & 0xFF @classmethod - def _new_from_cdata(cls, cdata: Any) -> "Color": + def _new_from_cdata(cls, cdata: Any) -> Color: """new in libtcod-cffi""" return cls(cdata.r, cdata.g, cdata.b) @@ -100,7 +102,7 @@ def __eq__(self, other: Any) -> bool: return False @deprecate("Use NumPy instead for color math operations.") - def __add__(self, other: Any) -> "Color": + def __add__(self, other: Any) -> Color: """Add two colors together. .. deprecated:: 9.2 @@ -109,7 +111,7 @@ def __add__(self, other: Any) -> "Color": return Color._new_from_cdata(lib.TCOD_color_add(self, other)) @deprecate("Use NumPy instead for color math operations.") - def __sub__(self, other: Any) -> "Color": + def __sub__(self, other: Any) -> Color: """Subtract one color from another. .. deprecated:: 9.2 @@ -118,7 +120,7 @@ def __sub__(self, other: Any) -> "Color": return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) @deprecate("Use NumPy instead for color math operations.") - def __mul__(self, other: Any) -> "Color": + def __mul__(self, other: Any) -> Color: """Multiply with a scaler or another color. .. deprecated:: 9.2 diff --git a/tcod/console.py b/tcod/console.py index e197c28b..f8216417 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -3,12 +3,15 @@ To render a console you need a tileset and a window to render to. See :ref:`getting-started` for info on how to set those up. """ +from __future__ import annotations + import os import warnings from pathlib import Path from typing import Any, Iterable, Optional, Sequence, Tuple, Union import numpy as np +from numpy.typing import NDArray from typing_extensions import Literal import tcod._internal @@ -114,9 +117,9 @@ def __init__( width: int, height: int, order: Literal["C", "F"] = "C", - buffer: "Optional[np.ndarray[Any, Any]]" = None, + buffer: Optional[NDArray[Any]] = None, ): - self._key_color = None # type: Optional[Tuple[int, int, int]] + self._key_color: Optional[Tuple[int, int, int]] = None self._order = tcod._internal.verify_order(order) if buffer is not None: if self._order == "F": @@ -150,17 +153,17 @@ def __init__( self.clear() @classmethod - def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> "Console": + def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> Console: """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): return cdata - self = object.__new__(cls) # type: Console + self: Console = object.__new__(cls) self.console_c = cdata self._init_setup_console_data(order) return self @classmethod - def _get_root(cls, order: Optional[Literal["C", "F"]] = None) -> "Console": + def _get_root(cls, order: Optional[Literal["C", "F"]] = None) -> Console: """Return a root console singleton with valid buffers. This function will also update an already active root console. @@ -168,7 +171,7 @@ def _get_root(cls, order: Optional[Literal["C", "F"]] = None) -> "Console": global _root_console if _root_console is None: _root_console = object.__new__(cls) - self = _root_console # type: Console + self: Console = _root_console if order is not None: self._order = order self.console_c = ffi.NULL @@ -185,7 +188,7 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: else: self._console_data = ffi.cast("struct TCOD_Console*", self.console_c) - self._tiles = np.frombuffer( # type: ignore + self._tiles: NDArray[Any] = np.frombuffer( # type: ignore ffi.buffer(self._console_data.tiles[0 : self.width * self.height]), dtype=self.DTYPE, ).reshape((self.height, self.width)) @@ -203,7 +206,7 @@ def height(self) -> int: return lib.TCOD_console_get_height(self.console_c) # type: ignore @property - def bg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": + def bg(self) -> NDArray[np.uint8]: """A uint8 array with the shape (height, width, 3). You can change the consoles background colors by using this array. @@ -218,7 +221,7 @@ def bg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": return bg @property - def fg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": + def fg(self) -> NDArray[np.uint8]: """A uint8 array with the shape (height, width, 3). You can change the consoles foreground colors by using this array. @@ -232,7 +235,7 @@ def fg(self) -> "np.ndarray[Any, np.dtype[np.uint8]]": return fg @property - def ch(self) -> "np.ndarray[Any, np.dtype[np.intc]]": + def ch(self) -> NDArray[np.intc]: """An integer array with the shape (height, width). You can change the consoles character codes by using this array. @@ -244,7 +247,7 @@ def ch(self) -> "np.ndarray[Any, np.dtype[np.intc]]": @property # type: ignore @deprecate("This attribute has been renamed to `rgba`.") - def tiles(self) -> "np.ndarray[Any, Any]": + def tiles(self) -> NDArray[Any]: """An array of this consoles raw tile data. This acts as a combination of the `ch`, `fg`, and `bg` attributes. @@ -260,7 +263,7 @@ def tiles(self) -> "np.ndarray[Any, Any]": @property # type: ignore @deprecate("This attribute has been renamed to `rgba`.") - def buffer(self) -> "np.ndarray[Any, Any]": + def buffer(self) -> NDArray[Any]: """An array of this consoles raw tile data. .. versionadded:: 11.4 @@ -272,7 +275,7 @@ def buffer(self) -> "np.ndarray[Any, Any]": @property # type: ignore @deprecate("This attribute has been renamed to `rgb`.") - def tiles_rgb(self) -> "np.ndarray[Any, Any]": + def tiles_rgb(self) -> NDArray[Any]: """An array of this consoles data without the alpha channel. .. versionadded:: 11.8 @@ -284,7 +287,7 @@ def tiles_rgb(self) -> "np.ndarray[Any, Any]": @property # type: ignore @deprecate("This attribute has been renamed to `rgb`.") - def tiles2(self) -> "np.ndarray[Any, Any]": + def tiles2(self) -> NDArray[Any]: """This name is deprecated in favour of :any:`rgb`. .. versionadded:: 11.3 @@ -295,7 +298,7 @@ def tiles2(self) -> "np.ndarray[Any, Any]": return self.rgb @property - def rgba(self) -> "np.ndarray[Any, Any]": + def rgba(self) -> NDArray[Any]: """An array of this consoles raw tile data. The axes of this array is affected by the `order` parameter given to @@ -316,7 +319,7 @@ def rgba(self) -> "np.ndarray[Any, Any]": return self._tiles.T if self._order == "F" else self._tiles @property - def rgb(self) -> "np.ndarray[Any, Any]": + def rgb(self) -> NDArray[Any]: """An array of this consoles data without the alpha channel. The axes of this array is affected by the `order` parameter given to @@ -468,8 +471,8 @@ def __deprecate_defaults( if not __debug__: return - fg = self.default_fg # type: Any - bg = self.default_bg # type: Any + fg: Optional[Tuple[int, int, int]] = self.default_fg + bg: Optional[Tuple[int, int, int]] = self.default_bg if bg_blend == tcod.constants.BKGND_NONE: bg = None if bg_blend == tcod.constants.BKGND_DEFAULT: @@ -737,7 +740,7 @@ def print_frame( def blit( self, - dest: "Console", + dest: Console, dest_x: int = 0, dest_y: int = 0, src_x: int = 0, @@ -837,7 +840,7 @@ def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: """ self._key_color = color - def __enter__(self) -> "Console": + def __enter__(self) -> Console: """Returns this console in a managed context. When the root console is used as a context, the graphical window will @@ -898,7 +901,7 @@ def __getstate__(self) -> Any: def __setstate__(self, state: Any) -> None: self._key_color = None if "_tiles" not in state: - tiles: np.ndarray[Any, Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE) + tiles: NDArray[Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE) tiles["ch"] = state["_ch"] tiles["fg"][..., :3] = state["_fg"] tiles["fg"][..., 3] = 255 @@ -962,7 +965,7 @@ def print( .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. """ - string_ = string.encode("utf-8") # type: bytes + string_ = string.encode("utf-8") lib.TCOD_console_printn( self.console_c, x, @@ -1017,7 +1020,7 @@ def print_box( .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. """ - string_ = string.encode("utf-8") # type: bytes + string_ = string.encode("utf-8") return int( lib.TCOD_console_printn_rect( self.console_c, @@ -1118,7 +1121,7 @@ def draw_frame( PendingDeprecationWarning, stacklevel=2, ) - title_ = title.encode("utf-8") # type: bytes + title_ = title.encode("utf-8") lib.TCOD_console_printn_frame( self.console_c, x, @@ -1227,7 +1230,7 @@ def get_height_rect(width: int, string: str) -> int: .. versionadded:: 9.2 """ - string_ = string.encode("utf-8") # type: bytes + string_ = string.encode("utf-8") return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) diff --git a/tcod/context.py b/tcod/context.py index e05be5a4..e2e69f12 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -47,6 +47,8 @@ .. versionadded:: 11.12 """ # noqa: E501 +from __future__ import annotations + import os import pickle import sys @@ -152,10 +154,10 @@ def __init__(self, context_p: Any): self._context_p = context_p @classmethod - def _claim(cls, context_p: Any) -> "Context": + def _claim(cls, context_p: Any) -> Context: return cls(ffi.gc(context_p, lib.TCOD_context_delete)) - def __enter__(self) -> "Context": + def __enter__(self) -> Context: """This context can be used as a context manager.""" return self @@ -345,7 +347,7 @@ def __reduce__(self) -> NoReturn: @ffi.def_extern() # type: ignore def _pycall_cli_output(catch_reference: Any, output: Any) -> None: """Callback for the libtcod context CLI. Catches the CLI output.""" - catch = ffi.from_handle(catch_reference) # type: List[str] + catch: List[str] = ffi.from_handle(catch_reference) catch.append(ffi.string(output).decode("utf-8")) @@ -417,7 +419,7 @@ def new( argv_encoded = [ffi.new("char[]", arg.encode("utf-8")) for arg in argv] # Needs to be kept alive for argv_c. argv_c = ffi.new("char*[]", argv_encoded) - catch_msg = [] # type: List[str] + catch_msg: List[str] = [] catch_handle = ffi.new_handle(catch_msg) # Keep alive. title_p = _handle_title(title) # Keep alive. diff --git a/tcod/event.py b/tcod/event.py index 8228af38..2fff26e6 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -19,11 +19,14 @@ .. versionadded:: 8.4 """ +from __future__ import annotations + import enum import warnings from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, TypeVar import numpy as np +from numpy.typing import NDArray import tcod.event_constants from tcod.event_constants import * # noqa: F4 @@ -245,7 +248,7 @@ class Quit(Event): """ @classmethod - def from_sdl_event(cls, sdl_event: Any) -> "Quit": + def from_sdl_event(cls, sdl_event: Any) -> Quit: self = cls() self.sdl_event = sdl_event return self @@ -423,7 +426,7 @@ def tile_motion(self, xy: Tuple[int, int]) -> None: self.__tile_motion = Point(*xy) @classmethod - def from_sdl_event(cls, sdl_event: Any) -> "MouseMotion": + def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: motion = sdl_event.motion pixel = motion.x, motion.y @@ -550,7 +553,7 @@ def __init__(self, x: int, y: int, flipped: bool = False): self.flipped = flipped @classmethod - def from_sdl_event(cls, sdl_event: Any) -> "MouseWheel": + def from_sdl_event(cls, sdl_event: Any) -> MouseWheel: wheel = sdl_event.wheel self = cls(wheel.x, wheel.y, bool(wheel.direction)) self.sdl_event = sdl_event @@ -585,7 +588,7 @@ def __init__(self, text: str): self.text = text @classmethod - def from_sdl_event(cls, sdl_event: Any) -> "TextInput": + def from_sdl_event(cls, sdl_event: Any) -> TextInput: self = cls(ffi.string(sdl_event.text.text, 32).decode("utf8")) self.sdl_event = sdl_event return self @@ -711,7 +714,7 @@ def __init__(self) -> None: super().__init__("") @classmethod - def from_sdl_event(cls, sdl_event: Any) -> "Undefined": + def from_sdl_event(cls, sdl_event: Any) -> Undefined: self = cls() self.sdl_event = sdl_event return self @@ -722,7 +725,7 @@ def __str__(self) -> str: return "" -_SDL_TO_CLASS_TABLE = { +_SDL_TO_CLASS_TABLE: Dict[int, Any] = { lib.SDL_QUIT: Quit, lib.SDL_KEYDOWN: KeyDown, lib.SDL_KEYUP: KeyUp, @@ -732,7 +735,7 @@ def __str__(self) -> str: lib.SDL_MOUSEWHEEL: MouseWheel, lib.SDL_TEXTINPUT: TextInput, lib.SDL_WINDOWEVENT: WindowEvent, -} # type: Dict[int, Any] +} def get() -> Iterator[Any]: @@ -922,7 +925,7 @@ def dispatch(self, event: Any) -> Optional[T]: stacklevel=2, ) return None - func = getattr(self, "ev_%s" % (event.type.lower(),)) # type: Callable[[Any], Optional[T]] + func: Callable[[Any], Optional[T]] = getattr(self, "ev_%s" % (event.type.lower(),)) return func(event) def event_get(self) -> None: @@ -933,37 +936,37 @@ def event_wait(self, timeout: Optional[float]) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: "tcod.event.Quit") -> Optional[T]: + def ev_quit(self, event: tcod.event.Quit) -> Optional[T]: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: "tcod.event.KeyDown") -> Optional[T]: + def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[T]: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: "tcod.event.KeyUp") -> Optional[T]: + def ev_keyup(self, event: tcod.event.KeyUp) -> Optional[T]: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: "tcod.event.MouseMotion") -> Optional[T]: + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> Optional[T]: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: "tcod.event.MouseButtonDown") -> Optional[T]: + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> Optional[T]: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: "tcod.event.MouseButtonUp") -> Optional[T]: + def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> Optional[T]: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: "tcod.event.MouseWheel") -> Optional[T]: + def ev_mousewheel(self, event: tcod.event.MouseWheel) -> Optional[T]: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: "tcod.event.TextInput") -> Optional[T]: + def ev_textinput(self, event: tcod.event.TextInput) -> Optional[T]: """Called to handle Unicode input.""" - def ev_windowshown(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowshown(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window is shown.""" - def ev_windowhidden(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowhidden(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window is hidden.""" - def ev_windowexposed(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowexposed(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`tcod.console_flush` is necessary. @@ -978,34 +981,34 @@ def ev_windowresized(self, event: "tcod.event.WindowResized") -> Optional[T]: def ev_windowsizechanged(self, event: "tcod.event.WindowResized") -> Optional[T]: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowminimized(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowmaximized(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window is maximized.""" - def ev_windowrestored(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowrestored(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window is restored.""" - def ev_windowenter(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowenter(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowleave(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowfocusgained(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowfocuslost(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowclose(self, event: tcod.event.WindowEvent) -> Optional[T]: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowtakefocus(self, event: tcod.event.WindowEvent) -> Optional[T]: pass - def ev_windowhittest(self, event: "tcod.event.WindowEvent") -> Optional[T]: + def ev_windowhittest(self, event: tcod.event.WindowEvent) -> Optional[T]: pass def ev_(self, event: Any) -> Optional[T]: @@ -1030,7 +1033,7 @@ def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: return 0 -def get_keyboard_state() -> "np.ndarray[Any, np.dtype[np.bool_]]": +def get_keyboard_state() -> NDArray[np.bool_]: """Return a boolean array with the current keyboard state. Index this array with a scancode. The value will be True if the key is @@ -1052,7 +1055,7 @@ def get_keyboard_state() -> "np.ndarray[Any, np.dtype[np.bool_]]": """ numkeys = ffi.new("int[1]") keyboard_state = lib.SDL_GetKeyboardState(numkeys) - out: np.ndarray[Any, Any] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=bool) # type: ignore + out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=np.bool_) # type: ignore out.flags["WRITEABLE"] = False # This buffer is supposed to be const. return out @@ -1579,7 +1582,7 @@ def label(self) -> str: return self.keysym.label @property - def keysym(self) -> "KeySym": + def keysym(self) -> KeySym: """Return a :class:`KeySym` from a scancode. Based on the current keyboard layout. @@ -1588,7 +1591,7 @@ def keysym(self) -> "KeySym": return KeySym(lib.SDL_GetKeyFromScancode(self.value)) @property - def scancode(self) -> "Scancode": + def scancode(self) -> Scancode: """Return a scancode from a keycode. Returns itself since it is already a :class:`Scancode`. @@ -1599,7 +1602,7 @@ def scancode(self) -> "Scancode": return self @classmethod - def _missing_(cls, value: object) -> "Optional[Scancode]": + def _missing_(cls, value: object) -> Optional[Scancode]: if not isinstance(value, int): return None result = cls(0) @@ -2126,7 +2129,7 @@ def label(self) -> str: return str(ffi.string(lib.SDL_GetKeyName(self.value)), encoding="utf-8") @property - def keysym(self) -> "KeySym": + def keysym(self) -> KeySym: """Return a keycode from a scancode. Returns itself since it is already a :class:`KeySym`. @@ -2146,7 +2149,7 @@ def scancode(self) -> Scancode: return Scancode(lib.SDL_GetScancodeFromKey(self.value)) @classmethod - def _missing_(cls, value: object) -> "Optional[KeySym]": + def _missing_(cls, value: object) -> Optional[KeySym]: if not isinstance(value, int): return None result = cls(0) diff --git a/tcod/image.py b/tcod/image.py index 30a1c4ec..82d489dc 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -5,10 +5,13 @@ `an alternative library for graphics rendering `_. """ +from __future__ import annotations + from pathlib import Path from typing import Any, Dict, Tuple, Union import numpy as np +from numpy.typing import NDArray import tcod.console from tcod._internal import _console, deprecate @@ -31,14 +34,14 @@ def __init__(self, width: int, height: int): self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete) @classmethod - def _from_cdata(cls, cdata: Any) -> "Image": - self = object.__new__(cls) # type: "Image" + def _from_cdata(cls, cdata: Any) -> Image: + self: Image = object.__new__(cls) self.image_c = cdata self.width, self.height = self._get_size() return self @classmethod - def from_array(cls, array: Any) -> "Image": + def from_array(cls, array: Any) -> Image: """Create a new Image from a copy of an array-like object. Example: @@ -333,7 +336,7 @@ def _get_format_name(format: int) -> str: " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", category=PendingDeprecationWarning, ) -def load(filename: Union[str, Path]) -> "np.ndarray[Any, np.dtype[np.uint8]]": +def load(filename: Union[str, Path]) -> NDArray[np.uint8]: """Load a PNG file as an RGBA array. `filename` is the name of the file to load. @@ -343,7 +346,7 @@ def load(filename: Union[str, Path]) -> "np.ndarray[Any, np.dtype[np.uint8]]": .. versionadded:: 11.4 """ image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(str(filename).encode()), lib.TCOD_image_delete)) - array = np.asarray(image, dtype=np.uint8) + array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: array = np.concatenate( # type: ignore diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a62e5a78..91a907eb 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1,5 +1,7 @@ """This module handles backward compatibility with the ctypes libtcodpy module. """ +from __future__ import annotations + import atexit import os import sys @@ -8,6 +10,7 @@ from typing import Any, AnyStr, Callable, Hashable, Iterable, Iterator, List, Optional, Sequence, Tuple, Union import numpy as np +from numpy.typing import NDArray from typing_extensions import Literal import tcod.bsp @@ -142,13 +145,13 @@ def clear( self.fore_b = [fore_b] * n self.char = [ord(char)] * n - def copy(self) -> "ConsoleBuffer": + def copy(self) -> ConsoleBuffer: """Returns a copy of this ConsoleBuffer. Returns: ConsoleBuffer: A new ConsoleBuffer copy. """ - other = ConsoleBuffer(0, 0) # type: "ConsoleBuffer" + other = ConsoleBuffer(0, 0) other.width = self.width other.height = self.height other.back_r = list(self.back_r) # make explicit copies of all lists @@ -2293,7 +2296,7 @@ def dijkstra_delete(p: tcod.path.Dijkstra) -> None: """ -def _heightmap_cdata(array: "np.ndarray[Any, np.dtype[np.float32]]") -> ffi.CData: +def _heightmap_cdata(array: NDArray[np.float32]) -> ffi.CData: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. @@ -2310,7 +2313,7 @@ def _heightmap_cdata(array: "np.ndarray[Any, np.dtype[np.float32]]") -> ffi.CDat @pending_deprecate() -def heightmap_new(w: int, h: int, order: str = "C") -> "np.ndarray[Any, np.dtype[np.float32]]": +def heightmap_new(w: int, h: int, order: str = "C") -> NDArray[np.float32]: """Return a new numpy.ndarray formatted for use with heightmap functions. `w` and `h` are the width and height of the array. @@ -2337,7 +2340,7 @@ def heightmap_new(w: int, h: int, order: str = "C") -> "np.ndarray[Any, np.dtype @deprecate("Assign to heightmaps as a NumPy array instead.") -def heightmap_set_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int, value: float) -> None: +def heightmap_set_value(hm: NDArray[np.float32], x: int, y: int, value: float) -> None: """Set the value of a point on a heightmap. .. deprecated:: 2.0 @@ -2362,7 +2365,7 @@ def heightmap_set_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: @deprecate("Add a scalar to an array using `hm[:] += value`") -def heightmap_add(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) -> None: +def heightmap_add(hm: NDArray[np.float32], value: float) -> None: """Add value to all values on this heightmap. Args: @@ -2376,7 +2379,7 @@ def heightmap_add(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) -> @deprecate("Multiply an array with a scaler using `hm[:] *= value`") -def heightmap_scale(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) -> None: +def heightmap_scale(hm: NDArray[np.float32], value: float) -> None: """Multiply all items on this heightmap by value. Args: @@ -2390,7 +2393,7 @@ def heightmap_scale(hm: "np.ndarray[Any, np.dtype[np.float32]]", value: float) - @deprecate("Clear an array with`hm[:] = 0`") -def heightmap_clear(hm: "np.ndarray[Any, np.dtype[np.float32]]") -> None: +def heightmap_clear(hm: NDArray[np.float32]) -> None: """Add value to all values on this heightmap. Args: @@ -2403,7 +2406,7 @@ def heightmap_clear(hm: "np.ndarray[Any, np.dtype[np.float32]]") -> None: @deprecate("Clamp array values using `hm.clip(mi, ma)`") -def heightmap_clamp(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float, ma: float) -> None: +def heightmap_clamp(hm: NDArray[np.float32], mi: float, ma: float) -> None: """Clamp all values on this heightmap between ``mi`` and ``ma`` Args: @@ -2418,7 +2421,7 @@ def heightmap_clamp(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float, ma: @deprecate("Copy an array using `hm2[:] = hm1[:]`, or `hm1.copy()`") -def heightmap_copy(hm1: "np.ndarray[Any, np.dtype[np.float32]]", hm2: "np.ndarray[Any, np.dtype[np.float32]]") -> None: +def heightmap_copy(hm1: NDArray[np.float32], hm2: NDArray[np.float32]) -> None: """Copy the heightmap ``hm1`` to ``hm2``. Args: @@ -2432,7 +2435,7 @@ def heightmap_copy(hm1: "np.ndarray[Any, np.dtype[np.float32]]", hm2: "np.ndarra @pending_deprecate() -def heightmap_normalize(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float = 0.0, ma: float = 1.0) -> None: +def heightmap_normalize(hm: NDArray[np.float32], mi: float = 0.0, ma: float = 1.0) -> None: """Normalize heightmap values between ``mi`` and ``ma``. Args: @@ -2444,9 +2447,9 @@ def heightmap_normalize(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float = @pending_deprecate() def heightmap_lerp_hm( - hm1: "np.ndarray[Any, np.dtype[np.float32]]", - hm2: "np.ndarray[Any, np.dtype[np.float32]]", - hm3: "np.ndarray[Any, np.dtype[np.float32]]", + hm1: NDArray[np.float32], + hm2: NDArray[np.float32], + hm3: NDArray[np.float32], coef: float, ) -> None: """Perform linear interpolation between two heightmaps storing the result @@ -2470,9 +2473,9 @@ def heightmap_lerp_hm( @deprecate("Add 2 arrays using `hm3 = hm1 + hm2`") def heightmap_add_hm( - hm1: "np.ndarray[Any, np.dtype[np.float32]]", - hm2: "np.ndarray[Any, np.dtype[np.float32]]", - hm3: "np.ndarray[Any, np.dtype[np.float32]]", + hm1: NDArray[np.float32], + hm2: NDArray[np.float32], + hm3: NDArray[np.float32], ) -> None: """Add two heightmaps together and stores the result in ``hm3``. @@ -2489,9 +2492,9 @@ def heightmap_add_hm( @deprecate("Multiply 2 arrays using `hm3 = hm1 * hm2`") def heightmap_multiply_hm( - hm1: "np.ndarray[Any, np.dtype[np.float32]]", - hm2: "np.ndarray[Any, np.dtype[np.float32]]", - hm3: "np.ndarray[Any, np.dtype[np.float32]]", + hm1: NDArray[np.float32], + hm2: NDArray[np.float32], + hm3: NDArray[np.float32], ) -> None: """Multiplies two heightmap's together and stores the result in ``hm3``. @@ -2508,9 +2511,7 @@ def heightmap_multiply_hm( @pending_deprecate() -def heightmap_add_hill( - hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, radius: float, height: float -) -> None: +def heightmap_add_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None: """Add a hill (a half spheroid) at given position. If height == radius or -radius, the hill is a half-sphere. @@ -2526,9 +2527,7 @@ def heightmap_add_hill( @pending_deprecate() -def heightmap_dig_hill( - hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, radius: float, height: float -) -> None: +def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None: """ This function takes the highest value (if height > 0) or the lowest @@ -2549,7 +2548,7 @@ def heightmap_dig_hill( @pending_deprecate() def heightmap_rain_erosion( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], nbDrops: int, erosionCoef: float, sedimentationCoef: float, @@ -2578,7 +2577,7 @@ def heightmap_rain_erosion( @pending_deprecate() def heightmap_kernel_transform( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], kernelsize: int, dx: Sequence[int], dy: Sequence[int], @@ -2635,7 +2634,7 @@ def heightmap_kernel_transform( @pending_deprecate() def heightmap_add_voronoi( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], nbPoints: Any, nbCoef: int, coef: Sequence[float], @@ -2667,7 +2666,7 @@ def heightmap_add_voronoi( @deprecate("Arrays of noise should be sampled using the tcod.noise module.") def heightmap_add_fbm( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], noise: tcod.noise.Noise, mulx: float, muly: float, @@ -2715,7 +2714,7 @@ def heightmap_add_fbm( @deprecate("Arrays of noise should be sampled using the tcod.noise module.") def heightmap_scale_fbm( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], noise: tcod.noise.Noise, mulx: float, muly: float, @@ -2758,7 +2757,7 @@ def heightmap_scale_fbm( @pending_deprecate() def heightmap_dig_bezier( - hm: "np.ndarray[Any, np.dtype[np.float32]]", + hm: NDArray[np.float32], px: Tuple[int, int, int, int], py: Tuple[int, int, int, int], startRadius: float, @@ -2791,7 +2790,7 @@ def heightmap_dig_bezier( @deprecate("This object can be accessed as a NumPy array.") -def heightmap_get_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int) -> float: +def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: """Return the value at ``x``, ``y`` in a heightmap. .. deprecated:: 2.0 @@ -2816,7 +2815,7 @@ def heightmap_get_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: @pending_deprecate() -def heightmap_get_interpolated_value(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float) -> float: +def heightmap_get_interpolated_value(hm: NDArray[np.float32], x: float, y: float) -> float: """Return the interpolated height at non integer coordinates. Args: @@ -2831,7 +2830,7 @@ def heightmap_get_interpolated_value(hm: "np.ndarray[Any, np.dtype[np.float32]]" @pending_deprecate() -def heightmap_get_slope(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: int) -> float: +def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float: """Return the slope between 0 and (pi / 2) at given coordinates. Args: @@ -2846,9 +2845,7 @@ def heightmap_get_slope(hm: "np.ndarray[Any, np.dtype[np.float32]]", x: int, y: @pending_deprecate() -def heightmap_get_normal( - hm: "np.ndarray[Any, np.dtype[np.float32]]", x: float, y: float, waterLevel: float -) -> Tuple[float, float, float]: +def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> Tuple[float, float, float]: """Return the map normal at given coordinates. Args: @@ -2866,7 +2863,7 @@ def heightmap_get_normal( @deprecate("This function is deprecated, see documentation.") -def heightmap_count_cells(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float, ma: float) -> int: +def heightmap_count_cells(hm: NDArray[np.float32], mi: float, ma: float) -> int: """Return the number of map cells which value is between ``mi`` and ``ma``. Args: @@ -2885,7 +2882,7 @@ def heightmap_count_cells(hm: "np.ndarray[Any, np.dtype[np.float32]]", mi: float @pending_deprecate() -def heightmap_has_land_on_border(hm: "np.ndarray[Any, np.dtype[np.float32]]", waterlevel: float) -> bool: +def heightmap_has_land_on_border(hm: NDArray[np.float32], waterlevel: float) -> bool: """Returns True if the map edges are below ``waterlevel``, otherwise False. Args: @@ -2899,7 +2896,7 @@ def heightmap_has_land_on_border(hm: "np.ndarray[Any, np.dtype[np.float32]]", wa @deprecate("Use `hm.min()` and `hm.max()` instead.") -def heightmap_get_minmax(hm: "np.ndarray[Any, np.dtype[np.float32]]") -> Tuple[float, float]: +def heightmap_get_minmax(hm: NDArray[np.float32]) -> Tuple[float, float]: """Return the min and max values of this heightmap. Args: @@ -3189,9 +3186,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: @deprecate("This function has been replaced by tcod.los.bresenham.") -def line_where( - x1: int, y1: int, x2: int, y2: int, inclusive: bool = True -) -> "Tuple[np.ndarray[Any, np.dtype[np.intc]], np.ndarray[Any, np.dtype[np.intc]]]": +def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> Tuple[NDArray[np.intc], NDArray[np.intc]]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. @@ -3545,7 +3540,7 @@ def parser_new_struct(parser: Any, name: str) -> Any: # prevent multiple threads from messing with def_extern callbacks _parser_callback_lock = threading.Lock() # temporary global pointer to a listener instance -_parser_listener = None # type: Any +_parser_listener: Any = None @ffi.def_extern() # type: ignore diff --git a/tcod/loader.py b/tcod/loader.py index b122cbe9..2469d8ce 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -1,5 +1,7 @@ """This module handles loading of the libtcod cffi API. """ +from __future__ import annotations + import os import platform import sys @@ -78,8 +80,8 @@ def __bool__(self) -> bool: return False -lib = None # type: Any -ffi = None # type: Any +lib: Any = None +ffi: Any = None if os.environ.get("READTHEDOCS"): # Mock the lib and ffi objects needed to compile docs for readthedocs.io diff --git a/tcod/los.py b/tcod/los.py index 5d9ff25b..ce48d23b 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -1,13 +1,16 @@ """This modules holds functions for NumPy-based line of sight algorithms. """ +from __future__ import annotations + from typing import Any, Tuple import numpy as np +from numpy.typing import NDArray from tcod.loader import ffi, lib -def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> "np.ndarray[Any, np.dtype[np.intc]]": +def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> NDArray[np.intc]: """Return a thin Bresenham line as a NumPy array of shape (length, 2). `start` and `end` are the endpoints of the line. diff --git a/tcod/map.py b/tcod/map.py index 76cc293c..48ce9f9a 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -2,10 +2,13 @@ """ +from __future__ import annotations + import warnings from typing import Any, Tuple import numpy as np +from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod._internal @@ -99,17 +102,17 @@ def __as_cdata(self) -> Any: ) @property - def transparent(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + def transparent(self) -> NDArray[np.bool_]: buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property - def walkable(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + def walkable(self) -> NDArray[np.bool_]: buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property - def fov(self) -> "np.ndarray[Any, np.dtype[np.bool_]]": + def fov(self) -> NDArray[np.bool_]: buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer @@ -166,12 +169,12 @@ def __getstate__(self) -> Any: def compute_fov( - transparency: "np.ndarray[Any, Any]", + transparency: ArrayLike, pov: Tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, -) -> "np.ndarray[Any, np.dtype[np.bool_]]": +) -> NDArray[np.bool_]: """Return a boolean mask of the area covered by a field-of-view. `transparency` is a 2 dimensional array where all non-zero values are diff --git a/tcod/noise.py b/tcod/noise.py index f4623302..bf8a80e8 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -34,12 +34,14 @@ [ 63, 94, 159, 209, 203], [ 44, 111, 196, 230, 195]], dtype=uint8) """ # noqa: E501 +from __future__ import annotations + import enum import warnings from typing import Any, Optional, Sequence, Tuple, Union import numpy as np -from numpy.typing import ArrayLike +from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod.constants @@ -229,7 +231,7 @@ def get_point(self, x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> f """ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) - def __getitem__(self, indexes: Any) -> "np.ndarray[Any, np.dtype[np.float32]]": + def __getitem__(self, indexes: Any) -> NDArray[np.float32]: """Sample a noise map through NumPy indexing. This follows NumPy's advanced indexing rules, but allows for floating @@ -283,7 +285,7 @@ def __getitem__(self, indexes: Any) -> "np.ndarray[Any, np.dtype[np.float32]]": return out - def sample_mgrid(self, mgrid: "ArrayLike") -> "np.ndarray[Any, np.dtype[np.float32]]": + def sample_mgrid(self, mgrid: ArrayLike) -> NDArray[np.float32]: """Sample a mesh-grid array and return the result. The :any:`sample_ogrid` method performs better as there is a lot of @@ -315,7 +317,7 @@ def sample_mgrid(self, mgrid: "ArrayLike") -> "np.ndarray[Any, np.dtype[np.float ) return out - def sample_ogrid(self, ogrid: "Sequence[ArrayLike]") -> "np.ndarray[Any, np.dtype[np.float32]]": + def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: """Sample an open mesh-grid array and return the result. Args @@ -413,7 +415,7 @@ def grid( scale: Union[Tuple[float, ...], float], origin: Optional[Tuple[int, ...]] = None, indexing: Literal["ij", "xy"] = "xy", -) -> "Tuple[np.ndarray[Any, Any], ...]": +) -> Tuple[NDArray[Any], ...]: """A helper function for generating a grid of noise samples. `shape` is the shape of the returned mesh grid. This can be any number of diff --git a/tcod/path.py b/tcod/path.py index 4953ec98..6c29aa8e 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -15,13 +15,15 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ +from __future__ import annotations + import functools import itertools import warnings from typing import Any, Callable, Dict, List, Optional, Tuple, Union import numpy as np -from numpy.typing import ArrayLike +from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod.map # noqa: F401 @@ -131,7 +133,7 @@ class NodeCostArray(np.ndarray): # type: ignore np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), } - def __new__(cls, array: "ArrayLike") -> "NodeCostArray": + def __new__(cls, array: ArrayLike) -> NodeCostArray: """Validate a numpy array and setup a C callback.""" self = np.asarray(array).view(cls) return self @@ -162,7 +164,7 @@ class _PathFinder(object): def __init__(self, cost: Any, diagonal: float = 1.41): self.cost = cost self.diagonal = diagonal - self._path_c = None # type: Any + self._path_c: Any = None self._callback = self._userdata = None if hasattr(self.cost, "map_c"): @@ -293,7 +295,7 @@ def maxarray( shape: Tuple[int, ...], dtype: Any = np.int32, order: Literal["C", "F"] = "C", -) -> "np.ndarray[Any, Any]": +) -> NDArray[Any]: """Return a new array filled with the maximum finite value for `dtype`. `shape` is of the new array. Same as other NumPy array initializers. @@ -312,7 +314,7 @@ def maxarray( return np.full(shape, np.iinfo(dtype).max, dtype, order) -def _export_dict(array: "np.ndarray[Any, Any]") -> Dict[str, Any]: +def _export_dict(array: NDArray[Any]) -> Dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" if array.dtype.type not in _INT_TYPES: raise TypeError("dtype was %s, but must be one of %s." % (array.dtype.type, tuple(_INT_TYPES.keys()))) @@ -325,7 +327,7 @@ def _export_dict(array: "np.ndarray[Any, Any]") -> Dict[str, Any]: } -def _export(array: "np.ndarray[Any, Any]") -> Any: +def _export(array: NDArray[Any]) -> Any: """Convert a NumPy array into a ctype object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -349,14 +351,14 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: def dijkstra2d( - distance: "ArrayLike", - cost: "ArrayLike", + distance: ArrayLike, + cost: ArrayLike, cardinal: Optional[int] = None, diagonal: Optional[int] = None, *, edge_map: Any = None, out: Optional[np.ndarray] = ..., # type: ignore -) -> "np.ndarray[Any, Any]": +) -> NDArray[Any]: """Return the computed distance of all nodes on a 2D Dijkstra grid. `distance` is an input array of node distances. Is this often an @@ -508,7 +510,7 @@ def dijkstra2d( return out -def _compile_bool_edges(edge_map: "ArrayLike") -> Tuple[Any, int]: +def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: """Return an edge array using a boolean map.""" edge_map = np.array(edge_map, copy=True) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 @@ -519,13 +521,13 @@ def _compile_bool_edges(edge_map: "ArrayLike") -> Tuple[Any, int]: def hillclimb2d( - distance: "ArrayLike", + distance: ArrayLike, start: Tuple[int, int], cardinal: Optional[bool] = None, diagonal: Optional[bool] = None, *, edge_map: Any = None, -) -> "np.ndarray[Any, Any]": +) -> NDArray[Any]: """Return a path on a grid from `start` to the lowest point. `distance` should be a fully computed distance array. This kind of array @@ -576,7 +578,7 @@ def hillclimb2d( return path -def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> "np.ndarray[Any, Any]": +def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( @@ -590,7 +592,7 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> "np.ndarray[A ) -def _as_hashable(obj: "Optional[np.ndarray[Any, Any]]") -> Optional[Any]: +def _as_hashable(obj: Optional[np.ndarray[Any, Any]]) -> Optional[Any]: """Return NumPy arrays as a more hashable form.""" if obj is None: return obj @@ -667,10 +669,10 @@ def __init__(self, shape: Tuple[int, ...], *, order: str = "C"): self._shape_c = self._shape_c[::-1] if not 0 < self._ndim <= 4: raise TypeError("Graph dimensions must be 1 <= n <= 4.") - self._graph = {} # type: Dict[Tuple[Any, ...], Dict[str, Any]] - self._edge_rules_keep_alive = [] # type: List[Any] - self._edge_rules_p = None # type: Any - self._heuristic = None # type: Optional[Tuple[int, int, int, int]] + self._graph: Dict[Tuple[Any, ...], Dict[str, Any]] = {} + self._edge_rules_keep_alive: List[Any] = [] + self._edge_rules_p: Any = None + self._heuristic: Optional[Tuple[int, int, int, int]] = None @property def ndim(self) -> int: @@ -687,8 +689,8 @@ def add_edge( edge_dir: Tuple[int, ...], edge_cost: int = 1, *, - cost: "np.ndarray[Any, Any]", - condition: "Optional[np.ndarray[Any, Any]]" = None, + cost: NDArray[Any], + condition: Optional[ArrayLike] = None, ) -> None: """Add a single edge rule. @@ -777,9 +779,9 @@ def add_edge( def add_edges( self, *, - edge_map: "ArrayLike", - cost: "np.ndarray[Any, Any]", - condition: "Optional[np.ndarray[Any, Any]]" = None, + edge_map: ArrayLike, + cost: NDArray[Any], + condition: Optional[ArrayLike] = None, ) -> None: """Add a rule with multiple edges. @@ -973,7 +975,7 @@ def _compile_rules(self) -> Any: self._edge_rules_p = ffi.new("struct PathfinderRule[]", rules) return self._edge_rules_p, self._edge_rules_keep_alive - def _resolve(self, pathfinder: "Pathfinder") -> None: + def _resolve(self, pathfinder: Pathfinder) -> None: """Run the pathfinding algorithm for this graph.""" rules, keep_alive = self._compile_rules() _check( @@ -1020,7 +1022,7 @@ class SimpleGraph: .. versionadded:: 11.15 """ - def __init__(self, *, cost: "ArrayLike", cardinal: int, diagonal: int, greed: int = 1): + def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1): cost = np.asarray(cost) if cost.ndim != 2: raise TypeError("The cost array must e 2 dimensional, array of shape %r given." % (cost.shape,)) @@ -1064,7 +1066,7 @@ def set_heuristic(self, *, cardinal: int, diagonal: int) -> None: """ self._subgraph.set_heuristic(cardinal=cardinal, diagonal=diagonal) - def _resolve(self, pathfinder: "Pathfinder") -> None: + def _resolve(self, pathfinder: Pathfinder) -> None: self._subgraph._resolve(pathfinder) @@ -1085,11 +1087,11 @@ def __init__(self, graph: Union[CustomGraph, SimpleGraph]): self._travel = _world_array(self._graph._shape_c) self._distance_p = _export(self._distance) self._travel_p = _export(self._travel) - self._heuristic = None # type: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] - self._heuristic_p = ffi.NULL # type: Any + self._heuristic: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] = None + self._heuristic_p: Any = ffi.NULL @property - def distance(self) -> "np.ndarray[Any, Any]": + def distance(self) -> NDArray[Any]: """The distance values of the pathfinder. The array returned from this property maintains the graphs `order`. @@ -1109,7 +1111,7 @@ def distance(self) -> "np.ndarray[Any, Any]": return self._distance.T if self._order == "F" else self._distance @property - def traversal(self) -> "np.ndarray[Any, Any]": + def traversal(self) -> NDArray[Any]: """An array used to generate paths from any point to the nearest root. The array returned from this property maintains the graphs `order`. @@ -1268,7 +1270,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: self._update_heuristic(goal) self._graph._resolve(self) - def path_from(self, index: Tuple[int, ...]) -> "np.ndarray[Any, Any]": + def path_from(self, index: Tuple[int, ...]) -> NDArray[Any]: """Return the shortest path from `index` to the nearest root. The returned array is of shape `(length, ndim)` where `length` is the @@ -1320,7 +1322,7 @@ def path_from(self, index: Tuple[int, ...]) -> "np.ndarray[Any, Any]": ) return path[:, ::-1] if self._order == "F" else path # type: ignore - def path_to(self, index: Tuple[int, ...]) -> "np.ndarray[Any, Any]": + def path_to(self, index: Tuple[int, ...]) -> NDArray[Any]: """Return the shortest path from the nearest root to `index`. See :any:`path_from`. diff --git a/tcod/random.py b/tcod/random.py index 4fdfbb93..eee4815c 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -6,6 +6,8 @@ However, you will need to use these generators to get deterministic results from the :any:`Noise` and :any:`BSP` classes. """ +from __future__ import annotations + import os import random import warnings @@ -81,9 +83,9 @@ def __init__( ) @classmethod - def _new_from_cdata(cls, cdata: Any) -> "Random": + def _new_from_cdata(cls, cdata: Any) -> Random: """Return a new instance encapsulating this cdata.""" - self = object.__new__(cls) # type: "Random" + self: Random = object.__new__(cls) self.random_c = cdata return self diff --git a/tcod/sdl.py b/tcod/sdl.py index be31e161..8aa38ecb 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -3,9 +3,12 @@ Add the line ``import tcod.sdl`` to include this module, as importing this module is not implied by ``import tcod``. """ +from __future__ import annotations + from typing import Any, Tuple import numpy as np +from numpy.typing import ArrayLike from tcod.loader import ffi, lib @@ -15,7 +18,7 @@ class _TempSurface: """Holds a temporary surface derived from a NumPy array.""" - def __init__(self, pixels: "np.ndarray[Any, np.dtype[np.uint8]]") -> None: + def __init__(self, pixels: ArrayLike) -> None: self._array = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape,)) @@ -52,7 +55,7 @@ def __init__(self, sdl_window_p: Any) -> None: def __eq__(self, other: Any) -> bool: return bool(self.p == other.p) - def set_icon(self, image: "np.ndarray[Any, np.dtype[np.uint8]]") -> None: + def set_icon(self, image: ArrayLike) -> None: """Set the window icon from an image. `image` is a C memory order RGB or RGBA NumPy array. diff --git a/tcod/tileset.py b/tcod/tileset.py index 5a054f2e..3726f18a 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -10,13 +10,15 @@ `_ while continuing to use python-tcod's pathfinding and field-of-view algorithms. """ +from __future__ import annotations + import itertools import os from pathlib import Path from typing import Any, Iterable, Optional, Tuple, Union import numpy as np -from numpy.typing import ArrayLike +from numpy.typing import ArrayLike, NDArray import tcod.console from tcod._internal import _check, _console, _raise_tcod_error, deprecate @@ -36,9 +38,9 @@ def __init__(self, tile_width: int, tile_height: int) -> None: ) @classmethod - def _claim(cls, cdata: Any) -> "Tileset": + def _claim(cls, cdata: Any) -> Tileset: """Return a new Tileset that owns the provided TCOD_Tileset* object.""" - self = object.__new__(cls) # type: Tileset + self: Tileset = object.__new__(cls) if cdata == ffi.NULL: raise RuntimeError("Tileset initialized with nullptr.") self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete) @@ -63,7 +65,7 @@ def __contains__(self, codepoint: int) -> bool: """Test if a tileset has a codepoint with ``n in tileset``.""" return bool(lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) == 0) - def get_tile(self, codepoint: int) -> "np.ndarray[Any, np.dtype[np.uint8]]": + def get_tile(self, codepoint: int) -> NDArray[np.uint8]: """Return a copy of a tile for the given codepoint. If the tile does not exist yet then a blank array will be returned. @@ -80,7 +82,7 @@ def get_tile(self, codepoint: int) -> "np.ndarray[Any, np.dtype[np.uint8]]": ) return tile - def set_tile(self, codepoint: int, tile: "ArrayLike") -> None: + def set_tile(self, codepoint: int, tile: ArrayLike) -> None: """Upload a tile into this array. The tile can be in 32-bit color (height, width, rgba), or grey-scale @@ -104,7 +106,7 @@ def set_tile(self, codepoint: int, tile: "ArrayLike") -> None: ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) - def render(self, console: tcod.console.Console) -> "np.ndarray[Any, np.dtype[np.uint8]]": + def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: """Render an RGBA array, using console with this tileset. `console` is the Console object to render, this can not be the root From aa5e69f63a6e88348de950fa02c8e6e5be3156f6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 4 Oct 2021 17:26:10 -0700 Subject: [PATCH 0587/1101] Set encoding in Python scripts. --- build_libtcod.py | 16 ++++++++-------- parse_sdl2.py | 2 +- scripts/get_release_description.py | 2 +- scripts/tag_release.py | 4 ++-- setup.py | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 4241a544..f3ca5321 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -58,7 +58,7 @@ def __init__(self, path: str) -> None: self.path = path = os.path.normpath(path) directory = os.path.dirname(path) depends = set() - with open(self.path, "r") as f: + with open(self.path, "r", encoding="utf-8") as f: header = f.read() header = RE_COMMENT.sub("", header) header = RE_CPLUSPLUS.sub("", header) @@ -251,7 +251,7 @@ def fix_header(filepath: str) -> None: This whitespace is causing issues with directives on some platforms. """ - with open(filepath, "r+") as f: + with open(filepath, "r+", encoding="utf-8") as f: current = f.read() fixed = "\n".join(line.strip() for line in current.split("\n")) if current == fixed: @@ -406,11 +406,11 @@ def update_module_all(filename: str, new_all: str) -> None: r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)", re.DOTALL, ) - with open(filename, "r") as f: + with open(filename, "r", encoding="utf-8") as f: match = RE_CONSTANTS_ALL.match(f.read()) assert match, "Can't determine __all__ subsection in %s!" % (filename,) header, footer = match.groups() - with open(filename, "w") as f: + with open(filename, "w", encoding="utf-8") as f: f.write("%s\n %s,\n %s" % (header, new_all, footer)) @@ -431,7 +431,7 @@ def write_library_constants() -> None: import tcod.color from tcod._libtcod import ffi, lib - with open("tcod/constants.py", "w") as f: + with open("tcod/constants.py", "w", encoding="utf-8") as f: all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): @@ -473,7 +473,7 @@ def write_library_constants() -> None: update_module_all("tcod/__init__.py", all_names_merged) update_module_all("tcod/libtcodpy.py", all_names_merged) - with open("tcod/event_constants.py", "w") as f: + with open("tcod/event_constants.py", "w", encoding="utf-8") as f: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) f.write("# --- SDL scancodes ---\n") @@ -490,7 +490,7 @@ def write_library_constants() -> None: all_names_merged = ",\n ".join('"%s"' % name for name in all_names) f.write("\n__all__ = [\n %s,\n]\n" % (all_names_merged,)) - with open("tcod/event.py", "r") as f: + with open("tcod/event.py", "r", encoding="utf-8") as f: event_py = f.read() event_py = re.sub( @@ -506,7 +506,7 @@ def write_library_constants() -> None: flags=re.DOTALL, ) - with open("tcod/event.py", "w") as f: + with open("tcod/event.py", "w", encoding="utf-8") as f: f.write(event_py) diff --git a/parse_sdl2.py b/parse_sdl2.py index 486a30c4..bfdc0125 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -27,7 +27,7 @@ def get_header(name: str) -> str: """Return the source of a header in a partially preprocessed state.""" - with open(name, "r") as f: + with open(name, "r", encoding="utf-8") as f: header = f.read() # Remove Doxygen code. header = RE_REMOVALS.sub("", header) diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 6c3b0f02..0cd97f66 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -10,7 +10,7 @@ def main() -> None: # Get the most recent tag. - with open("CHANGELOG.rst", "r") as f: + with open("CHANGELOG.rst", "r", encoding="utf-8") as f: match = RE_BODY.match(f.read()) assert match body = match.groups()[0].strip() diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 3e7225b6..68dd07f4 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -19,7 +19,7 @@ def parse_changelog(args: Any) -> Tuple[str, str]: """Return an updated changelog and and the list of changes.""" - with open("CHANGELOG.rst", "r") as file: + with open("CHANGELOG.rst", "r", encoding="utf-8") as file: match = re.match( pattern=r"(.*?Unreleased\n---+\n)(.+?)(\n*[^\n]+\n---+\n.*)", string=file.read(), @@ -48,7 +48,7 @@ def main() -> None: new_changelog, changes = parse_changelog(args) if not args.dry_run: - with open("CHANGELOG.rst", "w") as f: + with open("CHANGELOG.rst", "w", encoding="utf-8") as f: f.write(new_changelog) edit = ["-e"] if args.edit else [] subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) diff --git a/setup.py b/setup.py index bca8a935..ef9b1ef2 100755 --- a/setup.py +++ b/setup.py @@ -30,12 +30,12 @@ def get_version() -> str: version += ".dev%i" % commits_since_tag # update tcod/version.py - with open(PATH / "tcod/version.py", "w") as version_file: + with open(PATH / "tcod/version.py", "w", encoding="utf-8") as version_file: version_file.write(f'__version__ = "{version}"\n') return version else: # Not a Git respotitory. try: - with open(PATH / "tcod/version.py") as version_file: + with open(PATH / "tcod/version.py", encoding="utf-8") as version_file: match = re.match(r'__version__ = "(\S+)"', version_file.read()) assert match return match.groups()[0] @@ -68,7 +68,7 @@ def get_package_data() -> List[str]: def get_long_description() -> str: """Return this projects description.""" - with open(PATH / "README.rst", "r") as readme_file: + with open(PATH / "README.rst", "r", encoding="utf-8") as readme_file: return readme_file.read() From 3cd26ffe8c9540e66a8c3d41e3223b19fe503743 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 4 Oct 2021 20:03:15 -0700 Subject: [PATCH 0588/1101] Clean up some event typing. Make Event.type a Final type. --- examples/eventget.py | 9 ++++----- tcod/event.py | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/examples/eventget.py b/examples/eventget.py index 1c3c0554..55d6a58b 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -10,7 +10,6 @@ import tcod WIDTH, HEIGHT = 720, 480 -FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED def main() -> None: @@ -19,7 +18,7 @@ def main() -> None: event_log: List[str] = [] motion_desc = "" - with tcod.context.new(width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS) as context: + with tcod.context.new(width=WIDTH, height=HEIGHT) as context: console = context.new_console() while True: # Display all event items. @@ -36,11 +35,11 @@ def main() -> None: for event in tcod.event.wait(): context.convert_event(event) # Set tile coordinates for event. print(repr(event)) - if event.type == "QUIT": + if isinstance(event, tcod.event.Quit): raise SystemExit() - if event.type == "WINDOWRESIZED": + if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED": console = context.new_console() - if event.type == "MOUSEMOTION": + if isinstance(event, tcod.event.MouseMotion): motion_desc = str(event) else: event_log.append(str(event)) diff --git a/tcod/event.py b/tcod/event.py index 2fff26e6..6b658a90 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -23,10 +23,11 @@ import enum import warnings -from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, TypeVar +from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, TypeVar, Union import numpy as np from numpy.typing import NDArray +from typing_extensions import Final, Literal import tcod.event_constants from tcod.event_constants import * # noqa: F4 @@ -225,7 +226,7 @@ class Event: def __init__(self, type: Optional[str] = None): if type is None: type = self.__class__.__name__.upper() - self.type = type + self.type: Final = type self.sdl_event = None @classmethod @@ -606,12 +607,33 @@ class WindowEvent(Event): type (str): A window event could mean various event types. """ + type: Final[ # type: ignore[misc] # Narrowing contant type. + Literal[ + "WindowShown", + "WindowHidden", + "WindowExposed", + "WindowMoved", + "WindowResized", + "WindowSizeChanged", + "WindowMinimized", + "WindowMaximized", + "WindowRestored", + "WindowEnter", + "WindowLeave", + "WindowFocusGained", + "WindowFocusLost", + "WindowClose", + "WindowTakeFocus", + "WindowHitTest", + ] + ] + @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Any: + def from_sdl_event(cls, sdl_event: Any) -> Union[WindowEvent, Undefined]: if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) - event_type = cls.__WINDOW_TYPES[sdl_event.window.event].upper() - self = None # type: Any + event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event].upper() + self: WindowEvent if sdl_event.window.event == lib.SDL_WINDOWEVENT_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) elif sdl_event.window.event in ( @@ -655,6 +677,8 @@ class WindowMoved(WindowEvent): y (int): Movement on the y-axis. """ + type: Literal["WINDOWMOVED"] # type: ignore[assignment,misc] + def __init__(self, x: int, y: int) -> None: super().__init__(None) self.x = x @@ -684,6 +708,8 @@ class WindowResized(WindowEvent): height (int): The current height of the window. """ + type: Literal["WINDOWRESIZED", "WINDOWSIZECHANGED"] # type: ignore[assignment,misc] + def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) self.width = width From 125faed7539bd26c2a725644f419cb47efcf6220 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 6 Oct 2021 17:28:05 -0700 Subject: [PATCH 0589/1101] Create and apply spell-checking configuration. Fixed bad variable names where possible. Docs script has been auto-formatted by Black. Remove mentions of libomp as a required dependency. --- .vscode/extensions.json | 1 + .vscode/settings.json | 269 ++++++++++++++++++++++++++++++++++++++- CHANGELOG.rst | 10 +- CONTRIBUTING.md | 4 +- build_libtcod.py | 4 +- debian/changelog | 2 +- debian/control | 2 +- debian/copyright | 12 +- docs/conf.py | 95 +++++++------- docs/installation.rst | 4 +- examples/framerate.py | 2 +- examples/samples_tcod.py | 115 ++++++++--------- setup.py | 2 +- stdeb.cfg | 2 +- tcod/_internal.py | 14 +- tcod/console.py | 10 +- tcod/libtcodpy.py | 78 ++++++------ tcod/path.c | 8 +- tcod/path.py | 2 +- tcod/tdl.c | 4 +- tcod/tdl.h | 4 +- tcod/tileset.py | 2 +- tests/test_libtcodpy.py | 58 ++++----- 23 files changed, 483 insertions(+), 221 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index cf388f9c..59e34caf 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -9,6 +9,7 @@ "ms-python.vscode-pylance", "ms-vscode.cpptools", "redhat.vscode-yaml", + "streetsidesoftware.code-spell-checker", "tamasfe.even-better-toml", "xaver.clang-format", ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 320eade3..c3aad008 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,272 @@ "python.formatting.provider": "black", "files.associations": { "*.spec": "python", - } + }, + "cSpell.words": [ + "ADDA", + "addressof", + "addsub", + "addx", + "addy", + "algo", + "ALPH", + "arange", + "asarray", + "ascontiguousarray", + "astar", + "astype", + "atexit", + "AUDIOCVT", + "AUDIOPREV", + "autoclass", + "autofunction", + "autogenerated", + "automodule", + "backlinks", + "Benesch", + "bezier", + "bfade", + "bgcolor", + "BKGND", + "Blit", + "blits", + "blitting", + "bresenham", + "Bresenham", + "BRIGHTNESSUP", + "bysource", + "caeldera", + "ccoef", + "cdef", + "cffi", + "cflags", + "CFLAGS", + "CHARMAP", + "Chebyshev", + "CMWC", + "Codacy", + "Codecov", + "codepoint", + "codepoints", + "coef", + "Coef", + "COLCTRL", + "consolas", + "cplusplus", + "CPLUSPLUS", + "CRSEL", + "ctypes", + "currentmodule", + "datas", + "dcost", + "DCROSS", + "dejavu", + "deque", + "desaturated", + "DESATURATED", + "devel", + "DHLINE", + "dlopen", + "DTEEE", + "DTEEN", + "DTEES", + "DTEEW", + "dtype", + "dtypes", + "DVLINE", + "endianness", + "epub", + "EQUALSAS", + "errorvf", + "EXSEL", + "favicon", + "ffade", + "fgcolor", + "fheight", + "flto", + "fmean", + "fontx", + "fonty", + "fullscreen", + "fwidth", + "genindex", + "GFORCE", + "GLES", + "globaltoc", + "GLSL", + "greyscale", + "guass", + "heapify", + "heightmap", + "hflip", + "hillclimb", + "hline", + "horiz", + "howto", + "htbp", + "htmlzip", + "ifdef", + "ifndef", + "iinfo", + "IJKL", + "imageio", + "INCOL", + "INROW", + "itleref", + "ivar", + "jice", + "jieba", + "Kaczor", + "KBDILLUMDOWN", + "KBDILLUMTOGGLE", + "KBDILLUMUP", + "keychar", + "keyname", + "keypress", + "KEYUP", + "KMOD", + "KPADD", + "KPDEC", + "KPDIV", + "KPENTER", + "KPMUL", + "KPSUB", + "LACUNARITY", + "LALT", + "lbutton", + "LCTRL", + "LDFLAGS", + "LEFTPAREN", + "lerp", + "LGUI", + "libsdl", + "libtcod", + "libtcodpy", + "LMASK", + "lmeta", + "lodepng", + "LTCG", + "lucida", + "LWIN", + "maxarray", + "maxdepth", + "mbutton", + "MEIPASS", + "mersenne", + "mgrid", + "milli", + "minmax", + "mipmap", + "mipmaps", + "MMASK", + "modindex", + "MOUSEBUTTONUP", + "msilib", + "MSVC", + "msvcr", + "mulx", + "muly", + "namegen", + "ndarray", + "ndim", + "newh", + "neww", + "noarchive", + "NODISCARD", + "Nonrepresentable", + "NONUSBACKSLASH", + "NONUSHASH", + "nullptr", + "NUMLOCK", + "NUMLOCKCLEAR", + "Numpad", + "numpy", + "ogrid", + "ogrids", + "onefile", + "OPENGL", + "OPER", + "PAGEUP", + "pathfinding", + "pathlib", + "PILCROW", + "PRINTF", + "printn", + "pycall", + "pycparser", + "pyinstaller", + "pypiwin", + "PYTHONOPTIMIZE", + "Pyup", + "quickstart", + "RALT", + "randomizer", + "rbutton", + "RCTRL", + "redist", + "Redistributable", + "redistributables", + "repr", + "rgba", + "RGUI", + "RIGHTPAREN", + "RMASK", + "rmeta", + "roguelike", + "rpath", + "RRGGBB", + "rtype", + "RWIN", + "scalex", + "scaley", + "Scancode", + "scipy", + "scoef", + "sdist", + "SDL's", + "SDLCALL", + "sdlevent", + "SDLK", + "setuptools", + "SHADOWCAST", + "SMILIE", + "stdeb", + "struct", + "structs", + "SUBP", + "SYSREQ", + "tablefmt", + "tcod", + "tcoddoc", + "TCODK", + "TCODLIB", + "TEEE", + "TEEW", + "Tileset", + "tilesets", + "tilesheet", + "timeit", + "toctree", + "todos", + "tolist", + "tris", + "truetype", + "undoc", + "Unifont", + "upscaling", + "VAFUNC", + "vcoef", + "venv", + "vertic", + "vflip", + "vline", + "VOLUMEUP", + "voronoi", + "vsync", + "WASD", + "xdst", + "xrel", + "ydst", + "yrel" + ] } \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0ed91cc0..a6690327 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1097,7 +1097,7 @@ Changed - tcod.console: `Console.blit` parameters have been rearranged. Most of the parameters are now optional. - tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`. - - tdl: Changed `set_fps` paramter name to `fps`. + - tdl: Changed `set_fps` parameter name to `fps`. Fixed - tcod.bsp: Corrected spelling of max_vertical_ratio. @@ -1140,7 +1140,7 @@ Fixed Changed - `KeyEvent`'s with `text` now have all their modifier keys set to False. Fixed - - Undefined behaviour in text events caused crashes on 32-bit builds. + - Undefined behavior in text events caused crashes on 32-bit builds. 3.0.0 - 2017-03-21 ------------------ @@ -1166,7 +1166,7 @@ Fixed ------------------ Changed - Dependencies updated, tdl now requires libtcod-cffi 2.x.x - - Some event behaviours have changed with SDL2, event keys might be different + - Some event behaviors have changed with SDL2, event keys might be different than what you expect. Removed - Key repeat functions were removed from SDL2. @@ -1203,7 +1203,7 @@ Removed https://github.com/HexDecimal/libtcod-cffi You can use this library to have some raw access to libtcod if you want. Plus it can be used alongside TDL. -- The libtocd console objects in Console instances have been made public. +- The libtcod console objects in Console instances have been made public. - Added tdl.event.wait function. This function can called with a timeout and can automatically call tdl.flush. @@ -1262,7 +1262,7 @@ Removed ------------------ - Some of the setFont parameters were incorrectly labeled and documented. - setFont can auto-detect tilesets if the font sizes are in the filenames. -- Added some X11 unicode tilesets, including unifont. +- Added some X11 unicode tilesets, including Unifont. 1.1.2 - 2012-12-13 ------------------ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a65636eb..58da55b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,8 +36,8 @@ use with pycparser. - Open a command prompt in the cloned git directory. - Assuming a Debian based distribution of Linux. - Install tcod's dependancies with this command: - `sudo apt install gcc python-dev libsdl2-dev libffi-dev libomp-dev` + Install tcod's dependencies with this command: + `sudo apt install gcc python-dev libsdl2-dev libffi-dev` - Make sure the libtcod submodule is downloaded with this command: `git submodule update --init` - Install an editable version of tdl with this command: diff --git a/build_libtcod.py b/build_libtcod.py index f3ca5321..cf7c8c08 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -62,8 +62,8 @@ def __init__(self, path: str) -> None: header = f.read() header = RE_COMMENT.sub("", header) header = RE_CPLUSPLUS.sub("", header) - for dependancy in RE_INCLUDE.findall(header): - depends.add(os.path.normpath(os.path.join(directory, dependancy))) + for dependency in RE_INCLUDE.findall(header): + depends.add(os.path.normpath(os.path.join(directory, dependency))) header = RE_PREPROCESSOR.sub("", header) header = RE_TAGS.sub("", header) header = RE_VAFUNC.sub("", header) diff --git a/debian/changelog b/debian/changelog index c2de2136..7dea5b63 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,4 +2,4 @@ tdl (4.3.2-1) unstable; urgency=low * source package automatically created by stdeb 0.8.5 - -- Kyle Stewart <4b796c65+tdl@gmail.com> Mon, 02 Apr 2018 16:36:32 -0700 + -- Kyle Benesch <4b796c65+tdl@gmail.com> Mon, 02 Apr 2018 16:36:32 -0700 diff --git a/debian/control b/debian/control index 9bb4c85a..de1c960f 100644 --- a/debian/control +++ b/debian/control @@ -1,5 +1,5 @@ Source: tdl -Maintainer: Kyle Stewart <4b796c65+tdl@gmail.com> +Maintainer: Kyle Benesch <4b796c65+tdl@gmail.com> Section: python Priority: optional Build-Depends: dh-python, python-setuptools (>= 0.6b3), python3-setuptools, python-all-dev (>= 2.6.6-3), python3-all-dev, debhelper (>= 9), libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy diff --git a/debian/copyright b/debian/copyright index 5817ca46..d0445f3d 100755 --- a/debian/copyright +++ b/debian/copyright @@ -1,14 +1,14 @@ -Copyright (c) 2017, Kyle Stewart +Copyright (c) 2017, Kyle Benesch All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -22,5 +22,5 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. \ No newline at end of file +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. diff --git a/docs/conf.py b/docs/conf.py index bf2acdd7..540bce58 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -30,34 +31,34 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.ifconfig", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'python-tcod' -copyright = u'2009-2021, Kyle Benesch' -author = u'Kyle Benesch' +project = "python-tcod" +copyright = "2009-2021, Kyle Benesch" +author = "Kyle Benesch" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -65,12 +66,13 @@ # # The full version, including alpha/beta/rc tags. from subprocess import check_output -release = check_output(['git', 'describe', '--abbrev=0'], - universal_newlines=True).strip() -print('release version: %r' % release) + +release = check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() +print("release version: %r" % release) # The short X.Y version. import re -version = re.match(r'([0-9]+\.[0-9]+).*?', release).group() + +version = re.match(r"([0-9]+\.[0-9]+).*?", release).group() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -91,7 +93,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '/epilog.rst'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "/epilog.rst"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -113,7 +115,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -125,7 +127,7 @@ todo_include_todos = False -autodoc_member_order = 'groupwise' +autodoc_member_order = "groupwise" # -- Options for HTML output ---------------------------------------------- @@ -188,7 +190,7 @@ # Custom sidebar templates, maps document names to template names. # -#html_sidebars = {'**': ['globaltoc.html', 'searchbox.html']} +# html_sidebars = {'**': ['globaltoc.html', 'searchbox.html']} # Additional templates that should be rendered to pages, maps page names to # template names. @@ -247,34 +249,26 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'tcoddoc' +htmlhelp_basename = "tcoddoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'python-tcod.tex', u'python-tcod Documentation', - u'Kyle Stewart', 'manual'), + (master_doc, "python-tcod.tex", "python-tcod Documentation", "Kyle Benesch", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -314,10 +308,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-tcod', u'python-tcod Documentation', - [author], 1) -] +man_pages = [(master_doc, "python-tcod", "python-tcod Documentation", [author], 1)] # If true, show URL addresses after external links. # @@ -330,9 +321,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'python-tcod', u'python-tcod Documentation', - author, 'python-tcod', 'libtcod for Python.', - 'Miscellaneous'), + ( + master_doc, + "python-tcod", + "python-tcod Documentation", + author, + "python-tcod", + "libtcod for Python.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. @@ -367,8 +364,8 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'https://docs.python.org/3/': None, - 'https://numpy.org/doc/stable/': None, + "https://docs.python.org/3/": None, + "https://numpy.org/doc/stable/": None, } -os.environ['READTHEDOCS'] = 'True' +os.environ["READTHEDOCS"] = "True" diff --git a/docs/installation.rst b/docs/installation.rst index 6bd1215b..d64c30e2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -48,12 +48,12 @@ Linux (Debian-based) On Linux python-tcod will need to be built from source. You can run this command to download python-tcod's dependencies with apt:: - sudo apt install build-essential python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev libomp5 + sudo apt install build-essential python3-dev python3-pip python3-numpy libsdl2-dev libffi-dev If your GCC version is less than 6.1, or your SDL version is less than 2.0.5, then you will need to perform a distribution upgrade before continuing. -Once dependences are resolved you can build and install python-tcod using pip +Once dependencies are resolved you can build and install python-tcod using pip in a user environment:: python3 -m pip install --user tcod diff --git a/examples/framerate.py b/examples/framerate.py index fb08917c..39ef6ee6 100644 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -122,7 +122,7 @@ def main() -> None: 1, f"Current time:{time.perf_counter() * 1000:8.2f}ms" f"\nDelta time:{delta_time * 1000:8.2f}ms" - f"\nDesired FPS:{desired_fps:3d} (use scroll whell to adjust)" + f"\nDesired FPS:{desired_fps:3d} (use scroll wheel to adjust)" f"\n last:{clock.last_fps:.2f} fps" f"\n mean:{clock.mean_fps:.2f} fps" f"\nmedian:{clock.median_fps:.2f} fps" diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 53e990d4..3ca8b1a5 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -171,8 +171,8 @@ def __init__(self) -> None: self.counter = 0.0 self.x = 0 self.y = 0 - self.xdir = 1 - self.ydir = 1 + self.x_dir = 1 + self.y_dir = 1 self.secondary.draw_frame( 0, @@ -204,16 +204,16 @@ def on_enter(self) -> None: def on_draw(self) -> None: if time.perf_counter() - self.counter >= 1: self.counter = time.perf_counter() - self.x += self.xdir - self.y += self.ydir + self.x += self.x_dir + self.y += self.y_dir if self.x == sample_console.width / 2 + 5: - self.xdir = -1 + self.x_dir = -1 elif self.x == -5: - self.xdir = 1 + self.x_dir = 1 if self.y == sample_console.height / 2 + 5: - self.ydir = -1 + self.y_dir = -1 elif self.y == -5: - self.ydir = 1 + self.y_dir = 1 self.screenshot.blit(sample_console) self.secondary.blit( sample_console, @@ -278,13 +278,13 @@ def on_draw(self) -> None: self.bk_flag = tcod.BKGND_ADDALPHA(int(alpha)) self.bk.blit(sample_console) - recty = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0)) + rect_y = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0)) for x in range(sample_console.width): value = x * 255 // sample_console.width col = (value, value, value) - tcod.console_set_char_background(sample_console, x, recty, col, self.bk_flag) - tcod.console_set_char_background(sample_console, x, recty + 1, col, self.bk_flag) - tcod.console_set_char_background(sample_console, x, recty + 2, col, self.bk_flag) + tcod.console_set_char_background(sample_console, x, rect_y, col, self.bk_flag) + tcod.console_set_char_background(sample_console, x, rect_y + 1, col, self.bk_flag) + tcod.console_set_char_background(sample_console, x, rect_y + 2, col, self.bk_flag) angle = time.time() * 2.0 cos_angle = math.cos(angle) sin_angle = math.sin(angle) @@ -399,29 +399,31 @@ def on_draw(self) -> None: c = int((value + 1.0) / 2.0 * 255) c = max(0, min(c, 255)) self.img.put_pixel(x, y, (c // 2, c // 2, c)) - rectw = 24 - recth = 13 + rect_w = 24 + rect_h = 13 if self.implementation == tcod.noise.Implementation.SIMPLE: - recth = 10 + rect_h = 10 sample_console.draw_semigraphics(self.img) sample_console.draw_rect( 2, 2, - rectw, - recth, + rect_w, + rect_h, ch=0, fg=None, bg=GREY, bg_blend=tcod.BKGND_MULTIPLY, ) - sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] = sample_console.fg[2 : 2 + rectw, 2 : 2 + recth] * GREY / 255 + sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] = ( + sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] * GREY / 255 + ) - for curfunc in range(len(self.NOISE_OPTIONS)): - text = "%i : %s" % (curfunc + 1, self.NOISE_OPTIONS[curfunc][0]) - if curfunc == self.func: - sample_console.print(2, 2 + curfunc, text, fg=WHITE, bg=LIGHT_BLUE) + for cur_func in range(len(self.NOISE_OPTIONS)): + text = "%i : %s" % (cur_func + 1, self.NOISE_OPTIONS[cur_func][0]) + if cur_func == self.func: + sample_console.print(2, 2 + cur_func, text, fg=WHITE, bg=LIGHT_BLUE) else: - sample_console.print(2, 2 + curfunc, text, fg=GREY, bg=None) + sample_console.print(2, 2 + cur_func, text, fg=GREY, bg=None) sample_console.print(2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None) if self.implementation != tcod.noise.Implementation.SIMPLE: sample_console.print( @@ -664,7 +666,7 @@ def __init__(self) -> None: self.py = 10 self.dx = 24 self.dy = 1 - self.dijk_dist = 0.0 + self.dijkstra_dist = 0.0 self.using_astar = True self.recalculate = False self.busy = 0.0 @@ -680,7 +682,7 @@ def __init__(self) -> None: # window tcod.map_set_properties(self.map, x, y, True, False) self.path = tcod.path_new_using_map(self.map) - self.dijk = tcod.dijkstra_new(self.map) + self.dijkstra = tcod.dijkstra_new(self.map) def on_enter(self) -> None: # we draw the foreground only the first time. @@ -712,17 +714,17 @@ def on_draw(self) -> None: if self.using_astar: tcod.path_compute(self.path, self.px, self.py, self.dx, self.dy) else: - self.dijk_dist = 0.0 + self.dijkstra_dist = 0.0 # compute dijkstra grid (distance from px,py) - tcod.dijkstra_compute(self.dijk, self.px, self.py) + tcod.dijkstra_compute(self.dijkstra, self.px, self.py) # get the maximum distance (needed for rendering) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - d = tcod.dijkstra_get_distance(self.dijk, x, y) - if d > self.dijk_dist: - self.dijk_dist = d + d = tcod.dijkstra_get_distance(self.dijkstra, x, y) + if d > self.dijkstra_dist: + self.dijkstra_dist = d # compute path from px,py to dx,dy - tcod.dijkstra_path_set(self.dijk, self.dx, self.dy) + tcod.dijkstra_path_set(self.dijkstra, self.dx, self.dy) self.recalculate = False self.busy = 0.2 # draw the dungeon @@ -748,12 +750,12 @@ def on_draw(self) -> None: tcod.color_lerp( # type: ignore LIGHT_GROUND, DARK_GROUND, - 0.9 * tcod.dijkstra_get_distance(self.dijk, x, y) / self.dijk_dist, + 0.9 * tcod.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, ), tcod.BKGND_SET, ) - for i in range(tcod.dijkstra_size(self.dijk)): - x, y = tcod.dijkstra_get(self.dijk, i) + for i in range(tcod.dijkstra_size(self.dijkstra)): + x, y = tcod.dijkstra_get(self.dijkstra, i) tcod.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET) # move the creature @@ -766,9 +768,9 @@ def on_draw(self) -> None: self.px, self.py = tcod.path_walk(self.path, True) # type: ignore tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) else: - if not tcod.dijkstra_is_empty(self.dijk): + if not tcod.dijkstra_is_empty(self.dijkstra): tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) - self.px, self.py = tcod.dijkstra_path_walk(self.dijk) # type: ignore + self.px, self.py = tcod.dijkstra_path_walk(self.dijkstra) # type: ignore tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) self.recalculate = True @@ -918,9 +920,9 @@ def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: vline_down(bsp_map, x2, y + 1) else: # straight vertical corridor - minx = max(left.x, right.x) - maxx = min(left.x + left.w - 1, right.x + right.w - 1) - x = random.randint(minx, maxx) + min_x = max(left.x, right.x) + max_x = min(left.x + left.w - 1, right.x + right.w - 1) + x = random.randint(min_x, max_x) vline_down(bsp_map, x, right.y) vline_up(bsp_map, x, right.y - 1) else: @@ -935,9 +937,9 @@ def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: hline_right(bsp_map, x + 1, y2) else: # straight horizontal corridor - miny = max(left.y, right.y) - maxy = min(left.y + left.h - 1, right.y + right.h - 1) - y = random.randint(miny, maxy) + min_y = max(left.y, right.y) + max_y = min(left.y + left.h - 1, right.y + right.h - 1) + y = random.randint(min_y, max_y) hline_left(bsp_map, right.x - 1, y) hline_right(bsp_map, right.x, y) @@ -1064,7 +1066,7 @@ def __init__(self) -> None: self.name = "Mouse support" self.motion = tcod.event.MouseMotion() - self.lbut = self.mbut = self.rbut = 0 + self.mouse_left = self.mouse_middle = self.mouse_right = 0 self.log: List[str] = [] def on_enter(self) -> None: @@ -1076,19 +1078,19 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: if event.button == tcod.event.BUTTON_LEFT: - self.lbut = True + self.mouse_left = True elif event.button == tcod.event.BUTTON_MIDDLE: - self.mbut = True + self.mouse_middle = True elif event.button == tcod.event.BUTTON_RIGHT: - self.rbut = True + self.mouse_right = True def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> None: if event.button == tcod.event.BUTTON_LEFT: - self.lbut = False + self.mouse_left = False elif event.button == tcod.event.BUTTON_MIDDLE: - self.mbut = False + self.mouse_middle = False elif event.button == tcod.event.BUTTON_RIGHT: - self.rbut = False + self.mouse_right = False def on_draw(self) -> None: sample_console.clear(bg=GREY) @@ -1108,9 +1110,9 @@ def on_draw(self) -> None: self.motion.tile.y, self.motion.tile_motion.x, self.motion.tile_motion.y, - ("OFF", "ON")[self.lbut], - ("OFF", "ON")[self.rbut], - ("OFF", "ON")[self.mbut], + ("OFF", "ON")[self.mouse_left], + ("OFF", "ON")[self.mouse_right], + ("OFF", "ON")[self.mouse_middle], ), fg=LIGHT_YELLOW, bg=None, @@ -1137,13 +1139,12 @@ def __init__(self) -> None: self.name = "Name generator" self.curset = 0 - self.nbsets = 0 self.delay = 0.0 self.names: List[str] = [] self.sets: List[str] = [] def on_draw(self) -> None: - if self.nbsets == 0: + if not self.sets: # parse all *.cfg files in data/namegen for file in os.listdir(get_data("namegen")): if file.find(".cfg") > 0: @@ -1151,7 +1152,6 @@ def on_draw(self) -> None: # get the sets list self.sets = tcod.namegen_get_sets() print(self.sets) - self.nbsets = len(self.sets) while len(self.names) > 15: self.names.pop(0) sample_console.clear(bg=GREY) @@ -1179,16 +1179,13 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == ord("="): self.curset += 1 - if self.curset == self.nbsets: - self.curset = 0 self.names.append("======") elif event.sym == ord("-"): self.curset -= 1 - if self.curset < 0: - self.curset = self.nbsets - 1 self.names.append("======") else: super().ev_keydown(event) + self.curset %= len(self.sets) ############################################# diff --git a/setup.py b/setup.py index ef9b1ef2..0c1b3e0b 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def get_version() -> str: with open(PATH / "tcod/version.py", "w", encoding="utf-8") as version_file: version_file.write(f'__version__ = "{version}"\n') return version - else: # Not a Git respotitory. + else: # Not a Git repository. try: with open(PATH / "tcod/version.py", encoding="utf-8") as version_file: match = re.match(r'__version__ = "(\S+)"', version_file.read()) diff --git a/stdeb.cfg b/stdeb.cfg index 8b9fefd9..be082b63 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,5 +1,5 @@ [DEFAULT] -Depends: libsdl2-2.0-0, libomp5 +Depends: libsdl2-2.0-0 Build-Depends: libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy XS-Python-Version: >= 2.7 X-Python3-Version: >= 3.4 diff --git a/tcod/_internal.py b/tcod/_internal.py index 9704d3f3..e7dc033b 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -23,9 +23,9 @@ def decorator(func: F) -> F: return func @functools.wraps(func) - def wrapper(*args, **kargs): # type: ignore + def wrapper(*args, **kwargs): # type: ignore warnings.warn(message, category, stacklevel=stacklevel + 2) - return func(*args, **kargs) + return func(*args, **kwargs) return cast(F, wrapper) @@ -170,15 +170,15 @@ def __exit__(self, type: Any, value: Any, traceback: Any) -> None: class _CDataWrapper(object): - def __init__(self, *args: Any, **kargs: Any): - self.cdata = self._get_cdata_from_args(*args, **kargs) + def __init__(self, *args: Any, **kwargs: Any): + self.cdata = self._get_cdata_from_args(*args, **kwargs) if self.cdata is None: self.cdata = ffi.NULL super(_CDataWrapper, self).__init__() @staticmethod - def _get_cdata_from_args(*args: Any, **kargs: Any) -> Any: - if len(args) == 1 and isinstance(args[0], ffi.CData) and not kargs: + def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: + if len(args) == 1 and isinstance(args[0], ffi.CData) and not kwargs: return args[0] else: return None @@ -251,7 +251,7 @@ def __init__(self, array: Any): ) -def _asimage(image: Any) -> TempImage: +def _as_image(image: Any) -> TempImage: """Convert this input into an Image-like object.""" if hasattr(image, "image_c"): return image # type: ignore diff --git a/tcod/console.py b/tcod/console.py index f8216417..b6e6862a 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -325,7 +325,7 @@ def rgb(self) -> NDArray[Any]: The axes of this array is affected by the `order` parameter given to initialize the console. - The :any:`rgb_graphic` dtype can be used to make arrays compatiable + The :any:`rgb_graphic` dtype can be used to make arrays compatible with this attribute that are independent of a :any:`Console`. Example: @@ -683,7 +683,7 @@ def vline( Args: x (int): The x coordinate from the left. y (int): The y coordinate from the top. - height (int): The horozontal length of this line. + height (int): The horizontal length of this line. bg_blend (int): The background blending flag. .. deprecated:: 8.5 @@ -765,8 +765,8 @@ def blit( height (int): The height of the region to blit. If this is 0 the maximum possible height will be used. - fg_alpha (float): Foreground color alpha vaule. - bg_alpha (float): Background color alpha vaule. + fg_alpha (float): Foreground color alpha value. + bg_alpha (float): Background color alpha value. key_color (Optional[Tuple[int, int, int]]): None, or a (red, green, blue) tuple with values of 0-255. @@ -1217,7 +1217,7 @@ def draw_semigraphics(self, pixels: Any, x: int = 0, y: int = 0) -> None: .. versionadded:: 11.4 """ - image = tcod._internal._asimage(pixels) + image = tcod._internal._as_image(pixels) lib.TCOD_image_blit_2x(image.image_c, self.console_c, x, y, 0, 0, -1, -1) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 91a907eb..f4b607d8 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -277,15 +277,15 @@ class Dice(_CDataWrapper): which is tied to a CData object. """ - def __init__(self, *args: Any, **kargs: Any) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: warnings.warn( "Using this class is not recommended.", DeprecationWarning, stacklevel=2, ) - super(Dice, self).__init__(*args, **kargs) + super(Dice, self).__init__(*args, **kwargs) if self.cdata == ffi.NULL: - self._init(*args, **kargs) + self._init(*args, **kwargs) def _init( self, @@ -472,7 +472,7 @@ def __init__( cy: int = 0, dcx: int = 0, dcy: int = 0, - **kargs: Any, + **kwargs: Any, ): if isinstance(x, ffi.CData): self.cdata = x @@ -486,7 +486,7 @@ def __init__( self.cy = cy self.dcx = dcx self.dcy = dcy - for attr, value in kargs.items(): + for attr, value in kwargs.items(): setattr(self, attr, value) def __repr__(self) -> str: @@ -739,8 +739,8 @@ def bsp_delete(node: tcod.bsp.BSP) -> None: def color_lerp(c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float) -> Color: """Return the linear interpolation between two colors. - ``a`` is the interpolation value, with 0 returing ``c1``, - 1 returning ``c2``, and 0.5 returing a color halfway between both. + ``a`` is the interpolation value, with 0 returning ``c1``, + 1 returning ``c2``, and 0.5 returning a color halfway between both. Args: c1 (Union[Tuple[int, int, int], Sequence[int]]): @@ -759,7 +759,7 @@ def color_lerp(c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float) -> def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: """Set a color using: hue, saturation, and value parameters. - Does not return a new Color. ``c`` is modified inplace. + Does not return a new Color. ``c`` is modified in-place. Args: c (Union[Color, List[Any]]): A Color instance, or a list of any kind. @@ -793,7 +793,7 @@ def color_get_hsv(c: Tuple[int, int, int]) -> Tuple[float, float, float]: def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: """Scale a color's saturation and value. - Does not return a new Color. ``c`` is modified inplace. + Does not return a new Color. ``c`` is modified in-place. Args: c (Union[Color, List[int]]): A Color instance, or an [r, g, b] list. @@ -829,11 +829,11 @@ def color_gen_map(colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int] [Color(0, 0, 0), Color(51, 25, 0), Color(102, 51, 0), \ Color(153, 76, 0), Color(204, 102, 0), Color(255, 128, 0)] """ - ccolors = ffi.new("TCOD_color_t[]", colors) - cindexes = ffi.new("int[]", indexes) - cres = ffi.new("TCOD_color_t[]", max(indexes) + 1) - lib.TCOD_color_gen_map(cres, len(ccolors), ccolors, cindexes) - return [Color._new_from_cdata(cdata) for cdata in cres] + c_colors = ffi.new("TCOD_color_t[]", colors) + c_indexes = ffi.new("int[]", indexes) + c_out = ffi.new("TCOD_color_t[]", max(indexes) + 1) + lib.TCOD_color_gen_map(c_out, len(c_colors), c_colors, c_indexes) + return [Color._new_from_cdata(cdata) for cdata in c_out] @deprecate( @@ -1446,7 +1446,7 @@ def console_print(con: tcod.console.Console, x: int, y: int, fmt: str) -> None: con (Console): Any Console instance. x (int): Character x position from the left. y (int): Character y position from the top. - fmt (AnyStr): A unicode or bytes string optionaly using color codes. + fmt (AnyStr): A unicode or bytes string optionally using color codes. .. deprecated:: 8.5 Use :any:`Console.print_` instead. @@ -1597,7 +1597,7 @@ def console_print_frame( flag: int = BKGND_DEFAULT, fmt: str = "", ) -> None: - """Draw a framed rectangle with optinal text. + """Draw a framed rectangle with optional text. This uses the default background color and blend mode to fill the rectangle and the default foreground to draw the outline. @@ -1777,7 +1777,7 @@ def console_new(w: int, h: int) -> tcod.console.Console: def console_from_file(filename: str) -> tcod.console.Console: """Return a new console object from a filename. - The file format is automactially determined. This can load REXPaint `.xp`, + The file format is automatically determined. This can load REXPaint `.xp`, ASCII Paint `.apf`, or Non-delimited ASCII `.asc` files. Args: @@ -1870,7 +1870,7 @@ def console_fill_foreground( g: Sequence[int], b: Sequence[int], ) -> None: - """Fill the foregound of a console with r,g,b. + """Fill the foreground of a console with r,g,b. Args: con (Console): Any Console instance. @@ -1907,7 +1907,7 @@ def console_fill_background( g: Sequence[int], b: Sequence[int], ) -> None: - """Fill the backgound of a console with r,g,b. + """Fill the background of a console with r,g,b. Args: con (Console): Any Console instance. @@ -2586,7 +2586,7 @@ def heightmap_kernel_transform( maxLevel: float, ) -> None: """Apply a generic transformation on the map, so that each resulting cell - value is the weighted sum of several neighbour cells. + value is the weighted sum of several neighbor cells. This can be used to smooth/sharpen the map. @@ -2594,10 +2594,10 @@ def heightmap_kernel_transform( hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. kernelsize (int): Should be set to the length of the parameters:: dx, dy, and weight. - dx (Sequence[int]): A sequence of x coorinates. - dy (Sequence[int]): A sequence of y coorinates. + dx (Sequence[int]): A sequence of x coordinates. + dy (Sequence[int]): A sequence of y coordinates. weight (Sequence[float]): A sequence of kernelSize cells weight. - The value of each neighbour cell is scaled by + The value of each neighbor cell is scaled by its corresponding weight minLevel (float): No transformation will apply to cells below this value. @@ -2626,10 +2626,10 @@ def heightmap_kernel_transform( >>> tcod.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, ... 0.0, 1.0) """ - cdx = ffi.new("int[]", dx) - cdy = ffi.new("int[]", dy) - cweight = ffi.new("float[]", weight) - lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, cdx, cdy, cweight, minLevel, maxLevel) + c_dx = ffi.new("int[]", dx) + c_dy = ffi.new("int[]", dy) + c_weight = ffi.new("float[]", weight) + lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, c_dx, c_dy, c_weight, minLevel, maxLevel) @pending_deprecate() @@ -3086,7 +3086,7 @@ def image_delete(image: tcod.image.Image) -> None: @deprecate("Use tcod.line_iter instead.") def line_init(xo: int, yo: int, xd: int, yd: int) -> None: - """Initilize a line whose points will be returned by `line_step`. + """Initialize a line whose points will be returned by `line_step`. This function does not return anything on its own. @@ -3523,8 +3523,8 @@ def _unpack_union(type_: int, union: Any) -> Any: raise RuntimeError("Unknown libtcod type: %i" % type_) -def _convert_TCODList(clist: Any, type_: int) -> Any: - return [_unpack_union(type_, lib.TDL_list_get_union(clist, i)) for i in range(lib.TCOD_list_size(clist))] +def _convert_TCODList(c_list: Any, type_: int) -> Any: + return [_unpack_union(type_, lib.TDL_list_get_union(c_list, i)) for i in range(lib.TCOD_list_size(c_list))] @deprecate("Parser functions have been deprecated.") @@ -3577,7 +3577,7 @@ def parser_run(parser: Any, filename: str, listener: Any = None) -> None: propagate_manager = _PropagateException() - clistener = ffi.new( + c_listener = ffi.new( "TCOD_parser_listener_t *", { "new_struct": lib._pycall_parser_new_struct, @@ -3591,7 +3591,7 @@ def parser_run(parser: Any, filename: str, listener: Any = None) -> None: with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, _bytes(filename), clistener) + lib.TCOD_parser_run(parser, _bytes(filename), c_listener) @deprecate("libtcod objects are deleted automatically.") @@ -3642,8 +3642,8 @@ def parser_get_dice_property(parser: Any, name: str) -> Dice: @deprecate("Parser functions have been deprecated.") def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: - clist = lib.TCOD_parser_get_list_property(parser, _bytes(name), type) - return _convert_TCODList(clist, type) + c_list = lib.TCOD_parser_get_list_property(parser, _bytes(name), type) + return _convert_TCODList(c_list, type) RNG_MT = 0 @@ -3746,7 +3746,7 @@ def random_get_double(rnd: Optional[tcod.random.Random], mi: float, ma: float) - .. deprecated:: 2.0 Use :any:`random_get_float` instead. - Both funtions return a double precision float. + Both functions return a double precision float. """ return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma)) @@ -3755,7 +3755,7 @@ def random_get_double(rnd: Optional[tcod.random.Random], mi: float, ma: float) - def random_get_int_mean(rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int) -> int: """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. - The result is affacted by calls to :any:`random_set_distribution`. + The result is affected by calls to :any:`random_set_distribution`. Args: rnd (Optional[Random]): A Random instance, or None to use the default. @@ -3773,7 +3773,7 @@ def random_get_int_mean(rnd: Optional[tcod.random.Random], mi: int, ma: int, mea def random_get_float_mean(rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. - The result is affacted by calls to :any:`random_set_distribution`. + The result is affected by calls to :any:`random_set_distribution`. Args: rnd (Optional[Random]): A Random instance, or None to use the default. @@ -3794,7 +3794,7 @@ def random_get_double_mean(rnd: Optional[tcod.random.Random], mi: float, ma: flo .. deprecated:: 2.0 Use :any:`random_get_float_mean` instead. - Both funtions return a double precision float. + Both functions return a double precision float. """ return float(lib.TCOD_random_get_double_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean)) @@ -4148,7 +4148,7 @@ def sys_wait_for_event(mask: int, k: Optional[Key], m: Optional[Mouse], flush: b """Wait for an event then return. If flush is True then the buffer will be cleared before waiting. Otherwise - each available event will be returned in the order they're recieved. + each available event will be returned in the order they're received. Args: mask (int): :any:`Event types` to wait for. diff --git a/tcod/path.c b/tcod/path.c index 600b78ee..dac89091 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -441,8 +441,8 @@ int update_frontier_heuristic( unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; struct TCOD_HeapNode* heap_node = (void*)heap_ptr; - struct FrontierNode* fnode = (struct FrontierNode*)heap_node->data; - heap_node->priority = (fnode->distance + compute_heuristic(heuristic, frontier->ndim, fnode->index)); + struct FrontierNode* f_node = (struct FrontierNode*)heap_node->data; + heap_node->priority = (f_node->distance + compute_heuristic(heuristic, frontier->ndim, f_node->index)); } TCOD_minheap_heapify(&frontier->heap); return 0; @@ -479,10 +479,10 @@ int frontier_has_index( const unsigned char* heap_ptr = (const unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; const struct TCOD_HeapNode* heap_node = (const void*)heap_ptr; - const struct FrontierNode* fnode = (const void*)heap_node->data; + const struct FrontierNode* f_node = (const void*)heap_node->data; bool found = 1; for (int j = 0; j < frontier->ndim; ++j) { - if (index[j] != fnode->index[j]) { + if (index[j] != f_node->index[j]) { found = 0; break; } diff --git a/tcod/path.py b/tcod/path.py index 6c29aa8e..59b9b301 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -328,7 +328,7 @@ def _export_dict(array: NDArray[Any]) -> Dict[str, Any]: def _export(array: NDArray[Any]) -> Any: - """Convert a NumPy array into a ctype object.""" + """Convert a NumPy array into a cffi object.""" return ffi.new("struct NArray*", _export_dict(array)) diff --git a/tcod/tdl.c b/tcod/tdl.c index 2e9f5b76..c293428f 100644 --- a/tcod/tdl.c +++ b/tcod/tdl.c @@ -111,9 +111,9 @@ int TDL_color_set_value(int color, float h) { TCOD_color_set_value(&tcod_color, h); return TDL_color_to_int(&tcod_color); } -int TDL_color_shift_hue(int color, float hshift) { +int TDL_color_shift_hue(int color, float hue_shift) { TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_shift_hue(&tcod_color, hshift); + TCOD_color_shift_hue(&tcod_color, hue_shift); return TDL_color_to_int(&tcod_color); } int TDL_color_scale_HSV(int color, float scoef, float vcoef) { diff --git a/tcod/tdl.h b/tcod/tdl.h index a68785b5..71a7492e 100644 --- a/tcod/tdl.h +++ b/tcod/tdl.h @@ -3,7 +3,7 @@ #include "../libtcod/src/libtcod/libtcod.h" -/* TDL FUNCTONS ----------------------------------------------------------- */ +/* TDL FUNCTIONS ---------------------------------------------------------- */ TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx); bool TDL_list_get_bool(TCOD_list_t l, int idx); @@ -34,7 +34,7 @@ float TDL_color_get_value(int color); int TDL_color_set_hue(int color, float h); int TDL_color_set_saturation(int color, float h); int TDL_color_set_value(int color, float h); -int TDL_color_shift_hue(int color, float hshift); +int TDL_color_shift_hue(int color, float hue_shift); int TDL_color_scale_HSV(int color, float scoef, float vcoef); /* map data functions using a bitmap of: diff --git a/tcod/tileset.py b/tcod/tileset.py index 3726f18a..0cd9b746 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -231,7 +231,7 @@ def set_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) `tile_width`. This function must be called before :any:`tcod.console_init_root`. Once - the root console is setup you may call this funtion again to change the + the root console is setup you may call this function again to change the font. The tileset can be changed but the window will not be resized automatically. diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index a514bce4..5db89034 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -15,7 +15,7 @@ ] -def test_console_behaviour(console: tcod.Console) -> None: +def test_console_behavior(console: tcod.Console) -> None: assert not console @@ -599,44 +599,44 @@ def test_random() -> None: @pytest.mark.filterwarnings("ignore") def test_heightmap() -> None: - hmap = libtcodpy.heightmap_new(16, 16) - repr(hmap) + h_map = libtcodpy.heightmap_new(16, 16) + repr(h_map) noise = libtcodpy.noise_new(2) # basic operations - libtcodpy.heightmap_set_value(hmap, 0, 0, 1) - libtcodpy.heightmap_add(hmap, 1) - libtcodpy.heightmap_scale(hmap, 1) - libtcodpy.heightmap_clear(hmap) - libtcodpy.heightmap_clamp(hmap, 0, 0) - libtcodpy.heightmap_copy(hmap, hmap) - libtcodpy.heightmap_normalize(hmap) - libtcodpy.heightmap_lerp_hm(hmap, hmap, hmap, 0) - libtcodpy.heightmap_add_hm(hmap, hmap, hmap) - libtcodpy.heightmap_multiply_hm(hmap, hmap, hmap) + libtcodpy.heightmap_set_value(h_map, 0, 0, 1) + libtcodpy.heightmap_add(h_map, 1) + libtcodpy.heightmap_scale(h_map, 1) + libtcodpy.heightmap_clear(h_map) + libtcodpy.heightmap_clamp(h_map, 0, 0) + libtcodpy.heightmap_copy(h_map, h_map) + libtcodpy.heightmap_normalize(h_map) + libtcodpy.heightmap_lerp_hm(h_map, h_map, h_map, 0) + libtcodpy.heightmap_add_hm(h_map, h_map, h_map) + libtcodpy.heightmap_multiply_hm(h_map, h_map, h_map) # modifying the heightmap - libtcodpy.heightmap_add_hill(hmap, 0, 0, 4, 1) - libtcodpy.heightmap_dig_hill(hmap, 0, 0, 4, 1) - libtcodpy.heightmap_rain_erosion(hmap, 1, 1, 1) - libtcodpy.heightmap_kernel_transform(hmap, 3, [-1, 1, 0], [0, 0, 0], [0.33, 0.33, 0.33], 0, 1) - libtcodpy.heightmap_add_voronoi(hmap, 10, 3, [1, 3, 5]) - libtcodpy.heightmap_add_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) - libtcodpy.heightmap_scale_fbm(hmap, noise, 1, 1, 1, 1, 4, 1, 1) - libtcodpy.heightmap_dig_bezier(hmap, (0, 16, 16, 0), (0, 0, 16, 16), 1, 1, 1, 1) + libtcodpy.heightmap_add_hill(h_map, 0, 0, 4, 1) + libtcodpy.heightmap_dig_hill(h_map, 0, 0, 4, 1) + libtcodpy.heightmap_rain_erosion(h_map, 1, 1, 1) + libtcodpy.heightmap_kernel_transform(h_map, 3, [-1, 1, 0], [0, 0, 0], [0.33, 0.33, 0.33], 0, 1) + libtcodpy.heightmap_add_voronoi(h_map, 10, 3, [1, 3, 5]) + libtcodpy.heightmap_add_fbm(h_map, noise, 1, 1, 1, 1, 4, 1, 1) + libtcodpy.heightmap_scale_fbm(h_map, noise, 1, 1, 1, 1, 4, 1, 1) + libtcodpy.heightmap_dig_bezier(h_map, (0, 16, 16, 0), (0, 0, 16, 16), 1, 1, 1, 1) # read data - libtcodpy.heightmap_get_value(hmap, 0, 0) - libtcodpy.heightmap_get_interpolated_value(hmap, 0, 0) + libtcodpy.heightmap_get_value(h_map, 0, 0) + libtcodpy.heightmap_get_interpolated_value(h_map, 0, 0) - libtcodpy.heightmap_get_slope(hmap, 0, 0) - libtcodpy.heightmap_get_normal(hmap, 0, 0, 0) - libtcodpy.heightmap_count_cells(hmap, 0, 0) - libtcodpy.heightmap_has_land_on_border(hmap, 0) - libtcodpy.heightmap_get_minmax(hmap) + libtcodpy.heightmap_get_slope(h_map, 0, 0) + libtcodpy.heightmap_get_normal(h_map, 0, 0, 0) + libtcodpy.heightmap_count_cells(h_map, 0, 0) + libtcodpy.heightmap_has_land_on_border(h_map, 0) + libtcodpy.heightmap_get_minmax(h_map) libtcodpy.noise_delete(noise) - libtcodpy.heightmap_delete(hmap) + libtcodpy.heightmap_delete(h_map) MAP = np.array( From 399de2f1a602f096ecea40a1855efd97a32ac8c0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 9 Oct 2021 21:13:02 -0700 Subject: [PATCH 0590/1101] Update event polling documentation. Add examples for Python 3.10 match statement. --- .vscode/settings.json | 1 + docs/tcod/event.rst | 14 +++++++- tcod/event.py | 76 ++++++++++++++++++++++++++++++------------- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c3aad008..c97367c8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -128,6 +128,7 @@ "imageio", "INCOL", "INROW", + "isinstance", "itleref", "ivar", "jice", diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 7aa9be4c..88776e5b 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -4,9 +4,21 @@ tcod.event - SDL2 Event Handling .. automodule:: tcod.event :members: :member-order: bysource - :exclude-members: KeySym, Scancode, Modifier + :exclude-members: + KeySym, Scancode, Modifier, get, wait +Getting events +-------------- + +The primary way to capture events is with the :any:`tcod.event.get` and :any:`tcod.event.wait` functions. +These functions return events in a loop until the internal event queue is empty. +Use :func:`isinstance`, :any:`tcod.event.EventDispatch`, or `match statements `_ +(introduced in Python 3.10) to determine which event was returned. + +.. autofunction:: tcod.event.get +.. autofunction:: tcod.event.wait + Keyboard Enums -------------- diff --git a/tcod/event.py b/tcod/event.py index 6b658a90..5b0682ad 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -80,7 +80,17 @@ def _pixel_to_tile(x: float, y: float) -> Optional[Tuple[float, float]]: return xy[0], xy[1] -Point = NamedTuple("Point", [("x", int), ("y", int)]) +class Point(NamedTuple): + """A 2D position used for events with mouse coordinates. + + .. seealso:: + :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp` + """ + + x: int + """A pixel or tile coordinate starting with zero as the left-most position.""" + y: int + """A pixel or tile coordinate starting with zero as the top-most position.""" def _verify_tile_coordinates(xy: Optional[Point]) -> Point: @@ -774,19 +784,41 @@ def get() -> Iterator[Any]: Example:: + context: tcod.context.Context # Context object initialized earlier. for event in tcod.event.get(): - if event.type == "QUIT": + context.convert_event(event) # Add tile coordinates to mouse events. + if isinstance(event, tcod.event.Quit): print(event) raise SystemExit() - elif event.type == "KEYDOWN": - print(event) - elif event.type == "MOUSEBUTTONDOWN": - print(event) - elif event.type == "MOUSEMOTION": - print(event) + elif isinstance(event, tcod.event.KeyDown): + print(event) # Prints the Scancode and KeySym enums for this event. + elif isinstance(event, tcod.event.MouseButtonDown): + print(event) # Prints the mouse button constant names for this event. + elif isinstance(event, tcod.event.MouseMotion): + print(event) # Prints the mouse button mask bits in a readable format. else: - print(event) + print(event) # Print any unhandled events. # For loop exits after all current events are processed. + + Python 3.10 introduced `match statements `_ + which can be used to dispatch events more gracefully: + + Example:: + + context: tcod.context.Context # Context object initialized earlier. + for event in tcod.event.get(): + context.convert_event(event) # Add tile coordinates to mouse events. + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym, scancode, mod, repeat): + print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") + case tcod.event.MouseButtonDown(button, pixel, tile): + print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}") + case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion): + print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}") + case tcod.event.Event() as event: + print(event) # Show any unhandled events. """ sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): @@ -804,21 +836,21 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: Returns the same iterator as a call to :any:`tcod.event.get`. + This function is useful for simple games with little to no animations. + The following example sleeps whenever no events are queued: + Example:: - for event in tcod.event.wait(): - if event.type == "QUIT": - print(event) - raise SystemExit() - elif event.type == "KEYDOWN": - print(event) - elif event.type == "MOUSEBUTTONDOWN": - print(event) - elif event.type == "MOUSEMOTION": - print(event) - else: - print(event) - # For loop exits on timeout or after at least one event is processed. + context: tcod.context.Context # Context object initialized earlier. + while True: # Main game-loop. + console: tcod.Console # Console used for rendering. + ... # Render the frame to `console` and then: + context.present(console) # Show the console to the display. + # The ordering to draw first before waiting for events is important. + for event in tcod.event.wait(): # Sleeps until the next events exist. + ... # All events are handled at once before the next frame. + + See :any:`tcod.event.get` examples for how different events are handled. """ if timeout is not None: lib.SDL_WaitEventTimeout(ffi.NULL, int(timeout * 1000)) From 6f6e3f00062008d5fcc2c2d91dae1288a5f3f33a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 9 Oct 2021 22:28:14 -0700 Subject: [PATCH 0591/1101] Update getting started guide. Refactor key event enums. Some lookup tables are no longer needed now that enums are being used. Scancode and KeySym repr strings are now fully qualified. --- .vscode/settings.json | 2 + build_libtcod.py | 12 +- docs/tcod/getting-started.rst | 12 +- tcod/event.py | 38 +-- tcod/event_constants.py | 486 +--------------------------------- 5 files changed, 27 insertions(+), 523 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c97367c8..76275af8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -90,6 +90,7 @@ "dtype", "dtypes", "DVLINE", + "elif", "endianness", "epub", "EQUALSAS", @@ -281,6 +282,7 @@ "voronoi", "vsync", "WASD", + "WINDOWRESIZED", "xdst", "xrel", "ydst", diff --git a/build_libtcod.py b/build_libtcod.py index cf7c8c08..a9de366b 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -331,8 +331,7 @@ def fix_header(filepath: str) -> None: py_limited_api=True, ) -CONSTANT_MODULE_HEADER = '''""" -Constants from the libtcod C API. +CONSTANT_MODULE_HEADER = '''"""Constants from the libtcod C API. This module is auto-generated by `build_libtcod.py`. """ @@ -340,8 +339,7 @@ def fix_header(filepath: str) -> None: ''' -EVENT_CONSTANT_MODULE_HEADER = '''""" -Event constants from SDL's C API. +EVENT_CONSTANT_MODULE_HEADER = '''"""Event constants from SDL's C API. This module is auto-generated by `build_libtcod.py`. """ @@ -476,11 +474,11 @@ def write_library_constants() -> None: with open("tcod/event_constants.py", "w", encoding="utf-8") as f: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) - f.write("# --- SDL scancodes ---\n") - f.write("%s\n_REVERSE_SCANCODE_TABLE = %s\n" % parse_sdl_attrs("SDL_SCANCODE", all_names)) + f.write("\n# --- SDL scancodes ---\n") + f.write(f"""{parse_sdl_attrs("SDL_SCANCODE", all_names)[0]}\n""") f.write("\n# --- SDL keyboard symbols ---\n") - f.write("%s\n_REVERSE_SYM_TABLE = %s\n" % parse_sdl_attrs("SDLK", all_names)) + f.write(f"""{parse_sdl_attrs("SDLK", all_names)[0]}\n""") f.write("\n# --- SDL keyboard modifiers ---\n") f.write("%s\n_REVERSE_MOD_TABLE = %s\n" % parse_sdl_attrs("KMOD", all_names)) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 7c958b7a..ddca121c 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -47,8 +47,8 @@ Example:: for event in tcod.event.wait(): context.convert_event(event) # Sets tile coordinates for mouse events. - print(event) # Print event information to stdout. - if event.type == "QUIT": + print(event) # Print event names and attributes. + if isinstance(event, tcod.event.Quit): raise SystemExit() # The window will be closed after the above with-block exits. @@ -97,16 +97,16 @@ Example:: width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: while True: - console = context.new_console(order="F") + console = context.new_console(order="F") # Console size based on window resolution and tile size. console.print(0, 0, "Hello World") context.present(console, integer_scaling=True) for event in tcod.event.wait(): context.convert_event(event) # Sets tile coordinates for mouse events. - print(event) - if event.type == "QUIT": + print(event) # Print event names and attributes. + if isinstance(event, tcod.event.Quit): raise SystemExit() - if event.type == "WINDOWRESIZED": + elif isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED": pass # The next call to context.new_console may return a different size. diff --git a/tcod/event.py b/tcod/event.py index 5b0682ad..e430ae49 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -211,8 +211,6 @@ class Modifier(enum.IntFlag): _REVERSE_BUTTON_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_TABLE) _REVERSE_BUTTON_MASK_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_MASK_TABLE) -_REVERSE_SCANCODE_TABLE_PREFIX = _ConstantsWithPrefix(tcod.event_constants._REVERSE_SCANCODE_TABLE) -_REVERSE_SYM_TABLE_PREFIX = _ConstantsWithPrefix(tcod.event_constants._REVERSE_SYM_TABLE) _REVERSE_MOD_TABLE = tcod.event_constants._REVERSE_MOD_TABLE.copy() @@ -301,37 +299,17 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: self.sdl_event = sdl_event return self - def _scancode_constant(self, table: Mapping[int, str]) -> str: - """Return the constant name for this scan-code from a table.""" - try: - return table[self.scancode] - except KeyError: - return str(self.scancode) - - def _sym_constant(self, table: Mapping[int, str]) -> str: - """Return the constant name for this symbol from a table.""" - try: - return table[self.sym] - except KeyError: - return str(self.sym) - def __repr__(self) -> str: - return "tcod.event.%s(scancode=%s, sym=%s, mod=%s%s)" % ( + return "tcod.event.%s(scancode=%r, sym=%r, mod=%s%s)" % ( self.__class__.__name__, - self._scancode_constant(_REVERSE_SCANCODE_TABLE_PREFIX), - self._sym_constant(_REVERSE_SYM_TABLE_PREFIX), + self.scancode, + self.sym, _describe_bitmask(self.mod, _REVERSE_MOD_TABLE_PREFIX), ", repeat=True" if self.repeat else "", ) def __str__(self) -> str: - return "<%s, scancode=%s, sym=%s, mod=%s, repeat=%r>" % ( - super().__str__().strip("<>"), - self._scancode_constant(tcod.event_constants._REVERSE_SCANCODE_TABLE), - self._sym_constant(tcod.event_constants._REVERSE_SYM_TABLE), - _describe_bitmask(self.mod, _REVERSE_MOD_TABLE), - self.repeat, - ) + return self.__repr__().replace("tcod.event.", "") class KeyDown(KeyboardEvent): @@ -1678,6 +1656,10 @@ def __hash__(self) -> int: # __eq__ was defined, so __hash__ must be defined. return super().__hash__() + def __repr__(self) -> str: + """Return the fully qualified name of this enum.""" + return f"tcod.event.{self.__class__.__name__}.{self.name}" + class KeySym(enum.IntEnum): """Keyboard constants based on their symbol. @@ -2225,6 +2207,10 @@ def __hash__(self) -> int: # __eq__ was defined, so __hash__ must be defined. return super().__hash__() + def __repr__(self) -> str: + """Return the fully qualified name of this enum.""" + return f"tcod.event.{self.__class__.__name__}.{self.name}" + __all__ = [ # noqa: F405 "Modifier", diff --git a/tcod/event_constants.py b/tcod/event_constants.py index 82633eff..6d6069be 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -1,8 +1,8 @@ -""" -Event constants from SDL's C API. +"""Event constants from SDL's C API. This module is auto-generated by `build_libtcod.py`. """ + # --- SDL scancodes --- SCANCODE_UNKNOWN = 0 SCANCODE_A = 4 @@ -245,249 +245,6 @@ SCANCODE_SLEEP = 282 SCANCODE_APP1 = 283 SCANCODE_APP2 = 284 -_REVERSE_SCANCODE_TABLE = { - 0: "SCANCODE_UNKNOWN", - 4: "SCANCODE_A", - 5: "SCANCODE_B", - 6: "SCANCODE_C", - 7: "SCANCODE_D", - 8: "SCANCODE_E", - 9: "SCANCODE_F", - 10: "SCANCODE_G", - 11: "SCANCODE_H", - 12: "SCANCODE_I", - 13: "SCANCODE_J", - 14: "SCANCODE_K", - 15: "SCANCODE_L", - 16: "SCANCODE_M", - 17: "SCANCODE_N", - 18: "SCANCODE_O", - 19: "SCANCODE_P", - 20: "SCANCODE_Q", - 21: "SCANCODE_R", - 22: "SCANCODE_S", - 23: "SCANCODE_T", - 24: "SCANCODE_U", - 25: "SCANCODE_V", - 26: "SCANCODE_W", - 27: "SCANCODE_X", - 28: "SCANCODE_Y", - 29: "SCANCODE_Z", - 30: "SCANCODE_1", - 31: "SCANCODE_2", - 32: "SCANCODE_3", - 33: "SCANCODE_4", - 34: "SCANCODE_5", - 35: "SCANCODE_6", - 36: "SCANCODE_7", - 37: "SCANCODE_8", - 38: "SCANCODE_9", - 39: "SCANCODE_0", - 40: "SCANCODE_RETURN", - 41: "SCANCODE_ESCAPE", - 42: "SCANCODE_BACKSPACE", - 43: "SCANCODE_TAB", - 44: "SCANCODE_SPACE", - 45: "SCANCODE_MINUS", - 46: "SCANCODE_EQUALS", - 47: "SCANCODE_LEFTBRACKET", - 48: "SCANCODE_RIGHTBRACKET", - 49: "SCANCODE_BACKSLASH", - 50: "SCANCODE_NONUSHASH", - 51: "SCANCODE_SEMICOLON", - 52: "SCANCODE_APOSTROPHE", - 53: "SCANCODE_GRAVE", - 54: "SCANCODE_COMMA", - 55: "SCANCODE_PERIOD", - 56: "SCANCODE_SLASH", - 57: "SCANCODE_CAPSLOCK", - 58: "SCANCODE_F1", - 59: "SCANCODE_F2", - 60: "SCANCODE_F3", - 61: "SCANCODE_F4", - 62: "SCANCODE_F5", - 63: "SCANCODE_F6", - 64: "SCANCODE_F7", - 65: "SCANCODE_F8", - 66: "SCANCODE_F9", - 67: "SCANCODE_F10", - 68: "SCANCODE_F11", - 69: "SCANCODE_F12", - 70: "SCANCODE_PRINTSCREEN", - 71: "SCANCODE_SCROLLLOCK", - 72: "SCANCODE_PAUSE", - 73: "SCANCODE_INSERT", - 74: "SCANCODE_HOME", - 75: "SCANCODE_PAGEUP", - 76: "SCANCODE_DELETE", - 77: "SCANCODE_END", - 78: "SCANCODE_PAGEDOWN", - 79: "SCANCODE_RIGHT", - 80: "SCANCODE_LEFT", - 81: "SCANCODE_DOWN", - 82: "SCANCODE_UP", - 83: "SCANCODE_NUMLOCKCLEAR", - 84: "SCANCODE_KP_DIVIDE", - 85: "SCANCODE_KP_MULTIPLY", - 86: "SCANCODE_KP_MINUS", - 87: "SCANCODE_KP_PLUS", - 88: "SCANCODE_KP_ENTER", - 89: "SCANCODE_KP_1", - 90: "SCANCODE_KP_2", - 91: "SCANCODE_KP_3", - 92: "SCANCODE_KP_4", - 93: "SCANCODE_KP_5", - 94: "SCANCODE_KP_6", - 95: "SCANCODE_KP_7", - 96: "SCANCODE_KP_8", - 97: "SCANCODE_KP_9", - 98: "SCANCODE_KP_0", - 99: "SCANCODE_KP_PERIOD", - 100: "SCANCODE_NONUSBACKSLASH", - 101: "SCANCODE_APPLICATION", - 102: "SCANCODE_POWER", - 103: "SCANCODE_KP_EQUALS", - 104: "SCANCODE_F13", - 105: "SCANCODE_F14", - 106: "SCANCODE_F15", - 107: "SCANCODE_F16", - 108: "SCANCODE_F17", - 109: "SCANCODE_F18", - 110: "SCANCODE_F19", - 111: "SCANCODE_F20", - 112: "SCANCODE_F21", - 113: "SCANCODE_F22", - 114: "SCANCODE_F23", - 115: "SCANCODE_F24", - 116: "SCANCODE_EXECUTE", - 117: "SCANCODE_HELP", - 118: "SCANCODE_MENU", - 119: "SCANCODE_SELECT", - 120: "SCANCODE_STOP", - 121: "SCANCODE_AGAIN", - 122: "SCANCODE_UNDO", - 123: "SCANCODE_CUT", - 124: "SCANCODE_COPY", - 125: "SCANCODE_PASTE", - 126: "SCANCODE_FIND", - 127: "SCANCODE_MUTE", - 128: "SCANCODE_VOLUMEUP", - 129: "SCANCODE_VOLUMEDOWN", - 133: "SCANCODE_KP_COMMA", - 134: "SCANCODE_KP_EQUALSAS400", - 135: "SCANCODE_INTERNATIONAL1", - 136: "SCANCODE_INTERNATIONAL2", - 137: "SCANCODE_INTERNATIONAL3", - 138: "SCANCODE_INTERNATIONAL4", - 139: "SCANCODE_INTERNATIONAL5", - 140: "SCANCODE_INTERNATIONAL6", - 141: "SCANCODE_INTERNATIONAL7", - 142: "SCANCODE_INTERNATIONAL8", - 143: "SCANCODE_INTERNATIONAL9", - 144: "SCANCODE_LANG1", - 145: "SCANCODE_LANG2", - 146: "SCANCODE_LANG3", - 147: "SCANCODE_LANG4", - 148: "SCANCODE_LANG5", - 149: "SCANCODE_LANG6", - 150: "SCANCODE_LANG7", - 151: "SCANCODE_LANG8", - 152: "SCANCODE_LANG9", - 153: "SCANCODE_ALTERASE", - 154: "SCANCODE_SYSREQ", - 155: "SCANCODE_CANCEL", - 156: "SCANCODE_CLEAR", - 157: "SCANCODE_PRIOR", - 158: "SCANCODE_RETURN2", - 159: "SCANCODE_SEPARATOR", - 160: "SCANCODE_OUT", - 161: "SCANCODE_OPER", - 162: "SCANCODE_CLEARAGAIN", - 163: "SCANCODE_CRSEL", - 164: "SCANCODE_EXSEL", - 176: "SCANCODE_KP_00", - 177: "SCANCODE_KP_000", - 178: "SCANCODE_THOUSANDSSEPARATOR", - 179: "SCANCODE_DECIMALSEPARATOR", - 180: "SCANCODE_CURRENCYUNIT", - 181: "SCANCODE_CURRENCYSUBUNIT", - 182: "SCANCODE_KP_LEFTPAREN", - 183: "SCANCODE_KP_RIGHTPAREN", - 184: "SCANCODE_KP_LEFTBRACE", - 185: "SCANCODE_KP_RIGHTBRACE", - 186: "SCANCODE_KP_TAB", - 187: "SCANCODE_KP_BACKSPACE", - 188: "SCANCODE_KP_A", - 189: "SCANCODE_KP_B", - 190: "SCANCODE_KP_C", - 191: "SCANCODE_KP_D", - 192: "SCANCODE_KP_E", - 193: "SCANCODE_KP_F", - 194: "SCANCODE_KP_XOR", - 195: "SCANCODE_KP_POWER", - 196: "SCANCODE_KP_PERCENT", - 197: "SCANCODE_KP_LESS", - 198: "SCANCODE_KP_GREATER", - 199: "SCANCODE_KP_AMPERSAND", - 200: "SCANCODE_KP_DBLAMPERSAND", - 201: "SCANCODE_KP_VERTICALBAR", - 202: "SCANCODE_KP_DBLVERTICALBAR", - 203: "SCANCODE_KP_COLON", - 204: "SCANCODE_KP_HASH", - 205: "SCANCODE_KP_SPACE", - 206: "SCANCODE_KP_AT", - 207: "SCANCODE_KP_EXCLAM", - 208: "SCANCODE_KP_MEMSTORE", - 209: "SCANCODE_KP_MEMRECALL", - 210: "SCANCODE_KP_MEMCLEAR", - 211: "SCANCODE_KP_MEMADD", - 212: "SCANCODE_KP_MEMSUBTRACT", - 213: "SCANCODE_KP_MEMMULTIPLY", - 214: "SCANCODE_KP_MEMDIVIDE", - 215: "SCANCODE_KP_PLUSMINUS", - 216: "SCANCODE_KP_CLEAR", - 217: "SCANCODE_KP_CLEARENTRY", - 218: "SCANCODE_KP_BINARY", - 219: "SCANCODE_KP_OCTAL", - 220: "SCANCODE_KP_DECIMAL", - 221: "SCANCODE_KP_HEXADECIMAL", - 224: "SCANCODE_LCTRL", - 225: "SCANCODE_LSHIFT", - 226: "SCANCODE_LALT", - 227: "SCANCODE_LGUI", - 228: "SCANCODE_RCTRL", - 229: "SCANCODE_RSHIFT", - 230: "SCANCODE_RALT", - 231: "SCANCODE_RGUI", - 257: "SCANCODE_MODE", - 258: "SCANCODE_AUDIONEXT", - 259: "SCANCODE_AUDIOPREV", - 260: "SCANCODE_AUDIOSTOP", - 261: "SCANCODE_AUDIOPLAY", - 262: "SCANCODE_AUDIOMUTE", - 263: "SCANCODE_MEDIASELECT", - 264: "SCANCODE_WWW", - 265: "SCANCODE_MAIL", - 266: "SCANCODE_CALCULATOR", - 267: "SCANCODE_COMPUTER", - 268: "SCANCODE_AC_SEARCH", - 269: "SCANCODE_AC_HOME", - 270: "SCANCODE_AC_BACK", - 271: "SCANCODE_AC_FORWARD", - 272: "SCANCODE_AC_STOP", - 273: "SCANCODE_AC_REFRESH", - 274: "SCANCODE_AC_BOOKMARKS", - 275: "SCANCODE_BRIGHTNESSDOWN", - 276: "SCANCODE_BRIGHTNESSUP", - 277: "SCANCODE_DISPLAYSWITCH", - 278: "SCANCODE_KBDILLUMTOGGLE", - 279: "SCANCODE_KBDILLUMDOWN", - 280: "SCANCODE_KBDILLUMUP", - 281: "SCANCODE_EJECT", - 282: "SCANCODE_SLEEP", - 283: "SCANCODE_APP1", - 284: "SCANCODE_APP2", -} # --- SDL keyboard symbols --- K_UNKNOWN = 0 @@ -727,245 +484,6 @@ K_KBDILLUMUP = 1073742104 K_EJECT = 1073742105 K_SLEEP = 1073742106 -_REVERSE_SYM_TABLE = { - 0: "K_UNKNOWN", - 8: "K_BACKSPACE", - 9: "K_TAB", - 13: "K_RETURN", - 27: "K_ESCAPE", - 32: "K_SPACE", - 33: "K_EXCLAIM", - 34: "K_QUOTEDBL", - 35: "K_HASH", - 36: "K_DOLLAR", - 37: "K_PERCENT", - 38: "K_AMPERSAND", - 39: "K_QUOTE", - 40: "K_LEFTPAREN", - 41: "K_RIGHTPAREN", - 42: "K_ASTERISK", - 43: "K_PLUS", - 44: "K_COMMA", - 45: "K_MINUS", - 46: "K_PERIOD", - 47: "K_SLASH", - 48: "K_0", - 49: "K_1", - 50: "K_2", - 51: "K_3", - 52: "K_4", - 53: "K_5", - 54: "K_6", - 55: "K_7", - 56: "K_8", - 57: "K_9", - 58: "K_COLON", - 59: "K_SEMICOLON", - 60: "K_LESS", - 61: "K_EQUALS", - 62: "K_GREATER", - 63: "K_QUESTION", - 64: "K_AT", - 91: "K_LEFTBRACKET", - 92: "K_BACKSLASH", - 93: "K_RIGHTBRACKET", - 94: "K_CARET", - 95: "K_UNDERSCORE", - 96: "K_BACKQUOTE", - 97: "K_a", - 98: "K_b", - 99: "K_c", - 100: "K_d", - 101: "K_e", - 102: "K_f", - 103: "K_g", - 104: "K_h", - 105: "K_i", - 106: "K_j", - 107: "K_k", - 108: "K_l", - 109: "K_m", - 110: "K_n", - 111: "K_o", - 112: "K_p", - 113: "K_q", - 114: "K_r", - 115: "K_s", - 116: "K_t", - 117: "K_u", - 118: "K_v", - 119: "K_w", - 120: "K_x", - 121: "K_y", - 122: "K_z", - 127: "K_DELETE", - 1073741824: "K_SCANCODE_MASK", - 1073741881: "K_CAPSLOCK", - 1073741882: "K_F1", - 1073741883: "K_F2", - 1073741884: "K_F3", - 1073741885: "K_F4", - 1073741886: "K_F5", - 1073741887: "K_F6", - 1073741888: "K_F7", - 1073741889: "K_F8", - 1073741890: "K_F9", - 1073741891: "K_F10", - 1073741892: "K_F11", - 1073741893: "K_F12", - 1073741894: "K_PRINTSCREEN", - 1073741895: "K_SCROLLLOCK", - 1073741896: "K_PAUSE", - 1073741897: "K_INSERT", - 1073741898: "K_HOME", - 1073741899: "K_PAGEUP", - 1073741901: "K_END", - 1073741902: "K_PAGEDOWN", - 1073741903: "K_RIGHT", - 1073741904: "K_LEFT", - 1073741905: "K_DOWN", - 1073741906: "K_UP", - 1073741907: "K_NUMLOCKCLEAR", - 1073741908: "K_KP_DIVIDE", - 1073741909: "K_KP_MULTIPLY", - 1073741910: "K_KP_MINUS", - 1073741911: "K_KP_PLUS", - 1073741912: "K_KP_ENTER", - 1073741913: "K_KP_1", - 1073741914: "K_KP_2", - 1073741915: "K_KP_3", - 1073741916: "K_KP_4", - 1073741917: "K_KP_5", - 1073741918: "K_KP_6", - 1073741919: "K_KP_7", - 1073741920: "K_KP_8", - 1073741921: "K_KP_9", - 1073741922: "K_KP_0", - 1073741923: "K_KP_PERIOD", - 1073741925: "K_APPLICATION", - 1073741926: "K_POWER", - 1073741927: "K_KP_EQUALS", - 1073741928: "K_F13", - 1073741929: "K_F14", - 1073741930: "K_F15", - 1073741931: "K_F16", - 1073741932: "K_F17", - 1073741933: "K_F18", - 1073741934: "K_F19", - 1073741935: "K_F20", - 1073741936: "K_F21", - 1073741937: "K_F22", - 1073741938: "K_F23", - 1073741939: "K_F24", - 1073741940: "K_EXECUTE", - 1073741941: "K_HELP", - 1073741942: "K_MENU", - 1073741943: "K_SELECT", - 1073741944: "K_STOP", - 1073741945: "K_AGAIN", - 1073741946: "K_UNDO", - 1073741947: "K_CUT", - 1073741948: "K_COPY", - 1073741949: "K_PASTE", - 1073741950: "K_FIND", - 1073741951: "K_MUTE", - 1073741952: "K_VOLUMEUP", - 1073741953: "K_VOLUMEDOWN", - 1073741957: "K_KP_COMMA", - 1073741958: "K_KP_EQUALSAS400", - 1073741977: "K_ALTERASE", - 1073741978: "K_SYSREQ", - 1073741979: "K_CANCEL", - 1073741980: "K_CLEAR", - 1073741981: "K_PRIOR", - 1073741982: "K_RETURN2", - 1073741983: "K_SEPARATOR", - 1073741984: "K_OUT", - 1073741985: "K_OPER", - 1073741986: "K_CLEARAGAIN", - 1073741987: "K_CRSEL", - 1073741988: "K_EXSEL", - 1073742000: "K_KP_00", - 1073742001: "K_KP_000", - 1073742002: "K_THOUSANDSSEPARATOR", - 1073742003: "K_DECIMALSEPARATOR", - 1073742004: "K_CURRENCYUNIT", - 1073742005: "K_CURRENCYSUBUNIT", - 1073742006: "K_KP_LEFTPAREN", - 1073742007: "K_KP_RIGHTPAREN", - 1073742008: "K_KP_LEFTBRACE", - 1073742009: "K_KP_RIGHTBRACE", - 1073742010: "K_KP_TAB", - 1073742011: "K_KP_BACKSPACE", - 1073742012: "K_KP_A", - 1073742013: "K_KP_B", - 1073742014: "K_KP_C", - 1073742015: "K_KP_D", - 1073742016: "K_KP_E", - 1073742017: "K_KP_F", - 1073742018: "K_KP_XOR", - 1073742019: "K_KP_POWER", - 1073742020: "K_KP_PERCENT", - 1073742021: "K_KP_LESS", - 1073742022: "K_KP_GREATER", - 1073742023: "K_KP_AMPERSAND", - 1073742024: "K_KP_DBLAMPERSAND", - 1073742025: "K_KP_VERTICALBAR", - 1073742026: "K_KP_DBLVERTICALBAR", - 1073742027: "K_KP_COLON", - 1073742028: "K_KP_HASH", - 1073742029: "K_KP_SPACE", - 1073742030: "K_KP_AT", - 1073742031: "K_KP_EXCLAM", - 1073742032: "K_KP_MEMSTORE", - 1073742033: "K_KP_MEMRECALL", - 1073742034: "K_KP_MEMCLEAR", - 1073742035: "K_KP_MEMADD", - 1073742036: "K_KP_MEMSUBTRACT", - 1073742037: "K_KP_MEMMULTIPLY", - 1073742038: "K_KP_MEMDIVIDE", - 1073742039: "K_KP_PLUSMINUS", - 1073742040: "K_KP_CLEAR", - 1073742041: "K_KP_CLEARENTRY", - 1073742042: "K_KP_BINARY", - 1073742043: "K_KP_OCTAL", - 1073742044: "K_KP_DECIMAL", - 1073742045: "K_KP_HEXADECIMAL", - 1073742048: "K_LCTRL", - 1073742049: "K_LSHIFT", - 1073742050: "K_LALT", - 1073742051: "K_LGUI", - 1073742052: "K_RCTRL", - 1073742053: "K_RSHIFT", - 1073742054: "K_RALT", - 1073742055: "K_RGUI", - 1073742081: "K_MODE", - 1073742082: "K_AUDIONEXT", - 1073742083: "K_AUDIOPREV", - 1073742084: "K_AUDIOSTOP", - 1073742085: "K_AUDIOPLAY", - 1073742086: "K_AUDIOMUTE", - 1073742087: "K_MEDIASELECT", - 1073742088: "K_WWW", - 1073742089: "K_MAIL", - 1073742090: "K_CALCULATOR", - 1073742091: "K_COMPUTER", - 1073742092: "K_AC_SEARCH", - 1073742093: "K_AC_HOME", - 1073742094: "K_AC_BACK", - 1073742095: "K_AC_FORWARD", - 1073742096: "K_AC_STOP", - 1073742097: "K_AC_REFRESH", - 1073742098: "K_AC_BOOKMARKS", - 1073742099: "K_BRIGHTNESSDOWN", - 1073742100: "K_BRIGHTNESSUP", - 1073742101: "K_DISPLAYSWITCH", - 1073742102: "K_KBDILLUMTOGGLE", - 1073742103: "K_KBDILLUMDOWN", - 1073742104: "K_KBDILLUMUP", - 1073742105: "K_EJECT", - 1073742106: "K_SLEEP", -} # --- SDL keyboard modifiers --- KMOD_NONE = 0 From c8bbe810c56eab1b4c914ad8a957cb2fad28868e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 9 Oct 2021 23:17:09 -0700 Subject: [PATCH 0592/1101] Update installation guide. --- docs/installation.rst | 58 +++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index d64c30e2..109601cc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -2,20 +2,21 @@ Installation ============ -Once installed, you'll be able to import the `tcod` and `libtcodpy` modules, -as well as the deprecated `tdl` module. +Once python-tcod is installed, you'll be able to import the `tcod` module. -Python 3.5 or above is required for a normal install. +The latest version of Python 3 is recommended for a normal install. +Python 2 can not be used with the latest versions of python-tcod. These instructions include installing Python if you don't have it yet. -There are known issues in very old versions of pip. -If pip fails to install python-tcod then try updating it first. - Windows ------- -`First install a recent version of Python 3. +`First install the latest recent version of Python 3. `_ -Make sure Python is added to the Windows `PATH`. + +.. important:: + Make sure ``Add Python to environment variables`` is checked in the installer. + Otherwise Python will not be added to the Windows ``PATH``. + If you forgot to do this then you can reopen the installer and *modify* your installation. If you don't already have it, then install the latest `Microsoft Visual C++ Redistributable @@ -24,13 +25,37 @@ If you don't already have it, then install the latest for a 64-bit install. You'll need to keep this in mind when distributing any libtcod program to end-users. -Then to install python-tcod run the following from a Windows command line:: +You should verify your Python install with your terminal. +The terminal you use can be the Windows Command Prompt, PowerShell, GitBash, or similar. +It can not be the Python interpreter (indicated with a ``>>>`` prompt.) +Run the following commands (excluding the ``>``) to verify your Python installation:: + + >python -V + Python 3.10.0 - py -m pip install tcod + >pip -V + pip 21.2.4 from ...\Python310\lib\site-packages\pip (python 3.10) + +The above outputs would be the result of Python 3.10 being installed. +**Make sure the mentioned Python versions you get are not different than the latest version you just installed.** + +To install python-tcod run the following from a Windows command line:: + + >pip install tcod If Python was installed for all users then you may need to add the ``--user`` flag to pip. +You can then verify that ``tcod`` is importable from the Python interpreter:: + + >python + + >>> import tcod + +If ``import tcod`` doesn't throw an ``ImportError`` then ``tcod`` has been installed correctly to your system libraries. + +Some IDE's such as PyCharm will create a virtual environment which will ignore your system libraries and require tcod to be installed again in that new environment. + MacOS ----- The latest version of python-tcod only supports MacOS 10.9 (Mavericks) or @@ -68,22 +93,13 @@ By default the bottom bar of PyCharm will have a tab labeled `terminal`. Open this tab and you should see a prompt with ``(venv)`` on it. This means your commands will run in the virtual environment of your project. -With a new project and virtual environment you should upgrade pip before -installing python-tcod. You can do this by running the command:: - - python -m pip install --upgrade pip - -If this for some reason failed, you may fall back on using `easy_install`:: - - easy_install --upgrade pip - -After pip is upgraded you can install tcod with the following command:: +From this terminal you can install ``tcod`` to the virtual environment with the following command:: pip install tcod You can now use ``import tcod``. -If you are working with multiple people or computers then it's recommend to pin +If you are working with multiple people or computers or are using a Git repository then it is recommend to pin the tcod version in a `requirements.txt file `_. PyCharm will automatically update the virtual environment from these files. From db687ee7d404200caf304a2c75de08065d05b456 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 10 Oct 2021 00:29:44 -0700 Subject: [PATCH 0593/1101] Expand document width to better fit 120 character examples. --- docs/_static/css/custom.css | 3 +++ docs/conf.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/_static/css/custom.css diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..1af4c40b --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,3 @@ +.wy-nav-content { + max-width: 60em +} diff --git a/docs/conf.py b/docs/conf.py index 540bce58..595c3324 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -169,7 +169,9 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +html_static_path = ["_static"] + +html_css_files = ["css/custom.css"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied From cc37a76ee15d256fb86f2cea0336ef262e490edc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 16:48:22 -0700 Subject: [PATCH 0594/1101] Move pytest config to pyproject.toml. --- pyproject.toml | 19 +++++++++++++++++++ setup.cfg | 15 --------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4450626..df1a4a46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,3 +11,22 @@ profile = "black" py_version = "37" skip_gitignore = true line_length = 120 + +[tool.pytest.ini_options] +minversion = "6.0" +required_plugins = ["pytest-cov"] +testpaths = ["tcod/", "tests/", "docs/"] +addopts = [ + "--doctest-modules", + "--doctest-glob='*.rst'", + "--cov=tcod", + "--capture=sys", + "--ignore=tcod/__pyinstaller", +] +log_file_level = "DEBUG" +faulthandler_timeout = 5 +filterwarnings = [ + "ignore::DeprecationWarning:tcod.libtcodpy", + "ignore::PendingDeprecationWarning:tcod.libtcodpy", + "ignore:This class may perform poorly and is no longer needed.::tcod.map", +] diff --git a/setup.cfg b/setup.cfg index 16c31f1b..4b26be03 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,21 +8,6 @@ py-limited-api = cp36 [aliases] test=pytest -[tool:pytest] -addopts= - tcod/ - tests/ - docs/ - --doctest-modules - --doctest-glob="*.rst" - --cov=tcod - --capture=sys - --ignore=tcod/__pyinstaller -filterwarnings = - ignore::DeprecationWarning:tcod.libtcodpy - ignore::PendingDeprecationWarning:tcod.libtcodpy - ignore:This class may perform poorly and is no longer needed.::tcod.map - [flake8] ignore = E203 W503 max-line-length = 120 From cc5fecd618deeb225245692e63cd27790caa7bd7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 17:05:10 -0700 Subject: [PATCH 0595/1101] Remove old unused files. --- .codeclimate.yml | 27 ------------------- README.rst | 11 +------- debian/changelog | 5 ---- debian/compat | 1 - debian/control | 62 -------------------------------------------- debian/copyright | 26 ------------------- debian/rules | 12 --------- debian/source/format | 1 - debian/watch | 4 --- stdeb.cfg | 6 ----- unittest.cfg | 11 -------- 11 files changed, 1 insertion(+), 165 deletions(-) delete mode 100644 .codeclimate.yml delete mode 100644 debian/changelog delete mode 100644 debian/compat delete mode 100644 debian/control delete mode 100755 debian/copyright delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/watch delete mode 100644 stdeb.cfg delete mode 100644 unittest.cfg diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 834a6236..00000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -engines: - duplication: - enabled: true - config: - languages: - - ruby - - javascript - - python - - php - exclude_paths: - - examples/ - fixme: - enabled: true - radon: - enabled: true -ratings: - paths: - - "**.inc" - - "**.js" - - "**.jsx" - - "**.module" - - "**.php" - - "**.py" - - "**.rb" -exclude_paths: -- tests/ diff --git a/README.rst b/README.rst index c5df64d6..3c38bea3 100755 --- a/README.rst +++ b/README.rst @@ -6,9 +6,7 @@ ======== |VersionsBadge| |ImplementationBadge| |LicenseBadge| -|PyPI| |RTD| |Codecov| |Codacy| - -|Requires| |Pyup| +|PyPI| |RTD| |Codecov| |Pyup| ======= About @@ -99,13 +97,6 @@ python-tcod is distributed under the `Simplified 2-clause FreeBSD license .. |Issues| image:: https://img.shields.io/github/issues/libtcod/python-tcod.svg?maxAge=3600 :target: https://github.com/libtcod/python-tcod/issues -.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/b9df9aff87fb4968a0508a72aeb74a72 - :target: https://www.codacy.com/app/4b796c65-github/python-tcod?utm_source=github.com&utm_medium=referral&utm_content=libtcod/python-tcod&utm_campaign=Badge_Grade - -.. |Requires| image:: https://requires.io/github/libtcod/python-tcod/requirements.svg?branch=master - :target: https://requires.io/github/libtcod/python-tcod/requirements/?branch=master - :alt: Requirements Status - .. |Pyup| image:: https://pyup.io/repos/github/libtcod/python-tcod/shield.svg :target: https://pyup.io/repos/github/libtcod/python-tcod/ :alt: Updates diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 7dea5b63..00000000 --- a/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -tdl (4.3.2-1) unstable; urgency=low - - * source package automatically created by stdeb 0.8.5 - - -- Kyle Benesch <4b796c65+tdl@gmail.com> Mon, 02 Apr 2018 16:36:32 -0700 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index ec635144..00000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/debian/control b/debian/control deleted file mode 100644 index de1c960f..00000000 --- a/debian/control +++ /dev/null @@ -1,62 +0,0 @@ -Source: tdl -Maintainer: Kyle Benesch <4b796c65+tdl@gmail.com> -Section: python -Priority: optional -Build-Depends: dh-python, python-setuptools (>= 0.6b3), python3-setuptools, python-all-dev (>= 2.6.6-3), python3-all-dev, debhelper (>= 9), libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy -Standards-Version: 3.9.6 -X-Python-Version: >= 2.7 -X-Python3-Version: >= 3.4 -Homepage: https://github.com/HexDecimal/python-tdl - -Package: python-tdl -Provides: python-libtcod -Conflicts: python-libtcod -Architecture: any -Depends: ${misc:Depends}, ${python:Depends}, ${shlibs:Depends}, libsdl2-2.0-0 -Description: Pythonic cffi port of libtcod. - .. contents:: - :backlinks: top - . - ======== - Status - ======== - |VersionsBadge| |ImplementationBadge| |LicenseBadge| - . - |PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy| |Scrutinizer| |Landscape| - . - |Requires| |Pyup| - . - ======= - About - ======= - This is a Python cffi_ port of libtcod_. - . - This library is `hosted on GitHub `_. - . - Any issues you have with this module can be reported at the - -Package: python3-tdl -Architecture: any -Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends} -Description: Pythonic cffi port of libtcod. - .. contents:: - :backlinks: top - . - ======== - Status - ======== - |VersionsBadge| |ImplementationBadge| |LicenseBadge| - . - |PyPI| |RTD| |Appveyor| |Travis| |Coveralls| |Codecov| |Codacy| |Scrutinizer| |Landscape| - . - |Requires| |Pyup| - . - ======= - About - ======= - This is a Python cffi_ port of libtcod_. - . - This library is `hosted on GitHub `_. - . - Any issues you have with this module can be reported at the - diff --git a/debian/copyright b/debian/copyright deleted file mode 100755 index d0445f3d..00000000 --- a/debian/copyright +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2017, Kyle Benesch -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 18fea380..00000000 --- a/debian/rules +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/make -f - -# This file was automatically generated by stdeb 0.8.5 at -# Mon, 02 Apr 2018 16:36:32 -0700 -export PYBUILD_NAME=tdl -%: - dh $@ --with python2,python3 --buildsystem=pybuild - -# Ignore possible dependency issues with libGL -# Can be removed if GL is no longer linked. -override_dh_shlibdeps: - dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8d..00000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/watch b/debian/watch deleted file mode 100644 index afee809b..00000000 --- a/debian/watch +++ /dev/null @@ -1,4 +0,0 @@ -# please also check http://pypi.debian.net/tdl/watch -version=3 -opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ -http://pypi.debian.net/tdl/tdl-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) \ No newline at end of file diff --git a/stdeb.cfg b/stdeb.cfg deleted file mode 100644 index be082b63..00000000 --- a/stdeb.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[DEFAULT] -Depends: libsdl2-2.0-0 -Build-Depends: libsdl2-dev, python-pycparser, python3-pycparser, python-cffi, python3-cffi, python-numpy, python3-numpy -XS-Python-Version: >= 2.7 -X-Python3-Version: >= 3.4 -Copyright-File: LICENSE.txt diff --git a/unittest.cfg b/unittest.cfg deleted file mode 100644 index 8b014a48..00000000 --- a/unittest.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[unittest] -start-dir = tests - -[coverage] -always-on = True -coverage = tdl/ tcod/ - -[output-buffer] -always-on = True -stderr = False -stdout = True From d1263729bd127d64544d5f413a77db471c7fb7fb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 17:20:59 -0700 Subject: [PATCH 0596/1101] Split up jobs and merge them into a single workflow. --- .github/workflows/parse-sdl.yml | 39 --------------- .github/workflows/python-lint.yml | 54 --------------------- .github/workflows/python-package.yml | 72 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 93 deletions(-) delete mode 100644 .github/workflows/parse-sdl.yml delete mode 100644 .github/workflows/python-lint.yml diff --git a/.github/workflows/parse-sdl.yml b/.github/workflows/parse-sdl.yml deleted file mode 100644 index 91cff756..00000000 --- a/.github/workflows/parse-sdl.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This makes sure that the latest versions of the SDL headers parse correctly. - -name: Parse SDL - -on: - push: - pull_request: - -defaults: - run: - shell: bash - -jobs: - parse_sdl: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: ["windows-2019", "macos-latest"] - python-version: ["3.x"] - sdl-version: ["2.0.14", "2.0.16"] - fail-fast: true - - steps: - - uses: actions/checkout@v1 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - - name: Build package. - run: | - python setup.py build - env: - SDL_VERSION: ${{ matrix.sdl-version }} diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml deleted file mode 100644 index 834ee439..00000000 --- a/.github/workflows/python-lint.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Lint - -on: [push, pull_request] - -jobs: - lint: - runs-on: ubuntu-20.04 - env: - python-version: "3.x" - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python ${{ env.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ env.python-version }} - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 mypy black isort pytest types-tabulate - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Fake initialize package - run: | - echo '__version__ = ""' > tcod/version.py - - name: Flake8 - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: flake8 - run: flake8 scripts/ tcod/ tests/ - - name: MyPy - if: always() - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: mypy - run: mypy --show-column-numbers . - - name: isort - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: isort - run: isort scripts/ tcod/ tests/ --check --diff - - name: isort (examples) - uses: liskin/gh-problem-matcher-wrap@v1 - with: - linters: isort - run: isort examples/ --check --diff --thirdparty tcod - - name: Black - run: | - black --check --diff examples/ scripts/ tcod/ tests/ *.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f3b33abe..2f564126 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,6 +12,78 @@ defaults: shell: bash jobs: + black: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install Black + run: pip install black + - name: Run Black + run: black --check --diff examples/ scripts/ tcod/ tests/ *.py + + isort: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install isort + run: pip install isort + - name: isort + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort scripts/ tcod/ tests/ --check --diff + - name: isort (examples) + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: isort + run: isort examples/ --check --diff --thirdparty tcod + + flake8: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Install Flake8 + run: pip install Flake8 + - name: Flake8 + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: flake8 + run: flake8 scripts/ tcod/ tests/ + + mypy: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - name: Install Python dependencies + run: pip install mypy pytest types-tabulate -r requirements.txt + - name: Fake initialize package + run: | + echo '__version__ = ""' > tcod/version.py + - name: Mypy + uses: liskin/gh-problem-matcher-wrap@v1 + with: + linters: mypy + run: mypy --show-column-numbers . + + # This makes sure that the latest versions of the SDL headers parse correctly. + parse_sdl: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["windows-2019", "macos-11"] + sdl-version: ["2.0.14", "2.0.16"] + fail-fast: true + steps: + - uses: actions/checkout@v1 # v1 required to build package. + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - name: Build package + run: ./setup.py build + env: + SDL_VERSION: ${{ matrix.sdl-version }} + build: runs-on: ${{ matrix.os }} strategy: From ea1e33710f9b7128f15329e927570c91cc8074a0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 18:00:31 -0700 Subject: [PATCH 0597/1101] Try to reduce xvfb-run failures in tests. The internal handling of auto-servernum might have a race condition. Start with a random number to reduce collisions with the server number. --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2f564126..b9061a97 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -132,10 +132,10 @@ jobs: if: runner.os == 'Windows' run: | pytest --cov-report=xml - - name: Test with pytest + - name: Test with pytest (Xvfb) if: runner.os != 'Windows' run: | - xvfb-run --auto-servernum pytest --cov-report=xml + xvfb-run --server-num=$RANDOM --auto-servernum pytest --cov-report=xml - uses: codecov/codecov-action@v1 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' From c8360b8e575d4a909eedd1234a50032340dec692 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 18:37:14 -0700 Subject: [PATCH 0598/1101] Split macOS build into a matrix of jobs. --- .github/workflows/python-package.yml | 33 ++++++++++------------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b9061a97..3f268c48 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -222,40 +222,30 @@ jobs: twine upload --skip-existing wheelhouse/* build-macos: - runs-on: ${{ matrix.os }} + runs-on: "macos-10.15" strategy: + fail-fast: true matrix: - os: ["macos-10.15"] - python-version: ["3.x"] + python: ["cp38-*_universal2", "cp38-*_x86_64", "cp38-*_arm64", "pp37-*"] steps: # v2 breaks `git describe` so v1 is needed. # https://github.com/actions/checkout/issues/272 - uses: actions/checkout@v1 - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 + run: git submodule update --init --recursive --depth 1 - name: Print git describe - run: | - git describe - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} + run: git describe - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install wheel twine - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Prepare package. + run: pip3 install wheel twine -r requirements.txt + - name: Prepare package # Downloads SDL2 for the later step. - run: | - python setup.py check + run: python3 setup.py check - name: Build wheels uses: pypa/cibuildwheel@v2.0.0a4 env: - CIBW_BUILD: cp38-* pp* + CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 - CIBW_BEFORE_BUILD_MACOS: pip install --force-reinstall git+https://github.com/matthew-brett/delocate.git@1c07b50 + CIBW_BEFORE_BUILD_MACOS: pip install --upgrade delocate CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" @@ -270,5 +260,4 @@ jobs: env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing wheelhouse/* + run: twine upload --skip-existing wheelhouse/* From 5e946635ff0c89dd9f6fe6f62f21229ba91e171d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Oct 2021 18:54:30 -0700 Subject: [PATCH 0599/1101] Show Xvfb error logs. --- .github/workflows/python-package.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3f268c48..c2eb3b73 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -133,9 +133,12 @@ jobs: run: | pytest --cov-report=xml - name: Test with pytest (Xvfb) - if: runner.os != 'Windows' + if: always() && runner.os != 'Windows' run: | - xvfb-run --server-num=$RANDOM --auto-servernum pytest --cov-report=xml + xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml + - name: Xvfb logs + if: runner.os != 'Windows' + run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v1 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' From d7815bbbd2e0a325239ec31b2f6257c38655b611 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 14 Oct 2021 03:49:45 -0700 Subject: [PATCH 0600/1101] Update codecov action version. --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c2eb3b73..489316c7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -139,7 +139,7 @@ jobs: - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' env: From 0ab6a34d6f4a90be337224fe07c1d695bcffb133 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 16 Oct 2021 19:57:22 -0700 Subject: [PATCH 0601/1101] Update set_tile docs and add procedural_block_elements. `procedural_block_elements` was an example that got so large that it became an actual function. --- .vscode/settings.json | 2 + CHANGELOG.rst | 2 + tcod/tileset.py | 157 ++++++++++++++++++++++++++++++++++++++++-- tests/test_tileset.py | 8 +++ 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 tests/test_tileset.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 76275af8..e121c032 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -208,6 +208,7 @@ "pathfinding", "pathlib", "PILCROW", + "pilmode", "PRINTF", "printn", "pycall", @@ -280,6 +281,7 @@ "vline", "VOLUMEUP", "voronoi", + "VRAM", "vsync", "WASD", "WINDOWRESIZED", diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a6690327..0221ca41 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added + - Added the `tcod.tileset.procedural_block_elements` function. Removed - Python 3.6 is no longer supported. diff --git a/tcod/tileset.py b/tcod/tileset.py index 0cd9b746..6a51ca18 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -82,14 +82,54 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]: ) return tile - def set_tile(self, codepoint: int, tile: ArrayLike) -> None: + def set_tile(self, codepoint: int, tile: Union[ArrayLike, NDArray[np.uint8]]) -> None: """Upload a tile into this array. - The tile can be in 32-bit color (height, width, rgba), or grey-scale - (height, width). The tile should have a dtype of ``np.uint8``. + Args: + codepoint (int): The Unicode codepoint you are assigning to. + If the tile is a sprite rather than a common glyph then consider assigning it to a + `Private Use Area `_. + tile (Union[ArrayLike, NDArray[np.uint8]]): + The pixels to use for this tile in row-major order and must be in the same shape as :any:`tile_shape`. + `tile` can be an RGBA array with the shape of ``(height, width, rgba)``, or a grey-scale array with the + shape ``(height, width)``. + The `tile` array will be converted to a dtype of ``np.uint8``. + + An RGB array as an input is too ambiguous and an alpha channel must be added, for example if an image has a key + color than the key color pixels must have their alpha channel set to zero. + + This data may be immediately sent to VRAM, which can be a slow operation. + + Example:: + + # Examples use imageio for image loading, see https://imageio.readthedocs.io + tileset: tcod.tileset.Tileset # This example assumes you are modifying an existing tileset. + + # Normal usage when a tile already has its own alpha channel. + # The loaded tile must be the correct shape for the tileset you assign it to. + # The tile is assigned to a private use area and will not conflict with any exiting codepoint. + tileset.set_tile(0x100000, imageio.load("rgba_tile.png")) + + # Load a greyscale tile. + tileset.set_tile(0x100001, imageio.load("greyscale_tile.png"), pilmode="L") + # If you are stuck with an RGB array then you can use the red channel as the input: `rgb[:, :, 0]` + + # Loads an RGB sprite without a background. + tileset.set_tile(0x100002, imageio.load("rgb_no_background.png", pilmode="RGBA")) + # If you're stuck with an RGB array then you can pad the channel axis with an alpha of 255: + # rgba = np.pad(rgb, pad_width=((0, 0), (0, 0), (0, 1)), constant_values=255) + + # Loads an RGB sprite with a key color background. + KEY_COLOR = np.asarray((255, 0, 255), dtype=np.uint8) + sprite_rgb = imageio.load("rgb_tile.png") + # Compare the RGB colors to KEY_COLOR, compress full matches to a 2D mask. + sprite_mask = (sprite_rgb != KEY_COLOR).all(axis=2) + # Generate the alpha array, with 255 as the foreground and 0 as the background. + sprite_alpha = sprite_mask.astype(np.uint8) * 255 + # Combine the RGB and alpha arrays into an RGBA array. + sprite_rgba = np.append(sprite_rgb, sprite_alpha, axis=2) + tileset.set_tile(0x100003, sprite_rgba) - This data may need to be sent to graphics card memory, this is a slow - operation. """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: @@ -99,7 +139,13 @@ def set_tile(self, codepoint: int, tile: ArrayLike) -> None: return self.set_tile(codepoint, full_tile) required = self.tile_shape + (4,) if tile.shape != required: - raise ValueError("Tile shape must be %r or %r, got %r." % (required, self.tile_shape, tile.shape)) + note = "" + if len(tile.shape) == 3 and tile.shape[2] == 3: + note = ( + "\nNote: An RGB array is too ambiguous," + " an alpha channel must be added to this array to divide the background/foreground areas." + ) + raise ValueError(f"Tile shape must be {required} or {self.tile_shape}, got {tile.shape}.{note}") lib.TCOD_tileset_set_tile_( self._tileset_p, codepoint, @@ -298,6 +344,105 @@ def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Opt return Tileset._claim(cdata) +def procedural_block_elements(tileset: Tileset) -> None: + """Overwrites the block element codepoints in `tileset` with prodecually generated glyphs. + + Args: + tileset (Tileset): A :any:`Tileset` with tiles of any shape. + + This will overwrite all of the codepoints `listed here `_ + except for the shade glyphs. + + This function is useful for other functions such as :any:`Console.draw_semigraphics` which use more types of block + elements than are found in Code Page 437. + + .. versionadded:: 13.1 + + Example:: + + >>> tileset = tcod.tileset.Tileset(8, 8) + >>> tcod.tileset.procedural_block_elements(tileset) + >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left. + array([[ 0, 0, 0, 0, 255, 255, 255, 255], + [ 0, 0, 0, 0, 255, 255, 255, 255], + [ 0, 0, 0, 0, 255, 255, 255, 255], + [ 0, 0, 0, 0, 255, 255, 255, 255], + [255, 255, 255, 255, 0, 0, 0, 0], + [255, 255, 255, 255, 0, 0, 0, 0], + [255, 255, 255, 255, 0, 0, 0, 0], + [255, 255, 255, 255, 0, 0, 0, 0]], dtype=uint8) + >>> tileset.get_tile(0x2581)[:, :, 3] # "▁" Lower one eighth block. + array([[ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0, 0, 0, 0], + [255, 255, 255, 255, 255, 255, 255, 255]], dtype=uint8) + >>> tileset.get_tile(0x258D)[:, :, 3] # "▍" Left three eighths block. + array([[255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0], + [255, 255, 255, 0, 0, 0, 0, 0]], dtype=uint8) + """ + quadrants: NDArray[np.uint8] = np.zeros(tileset.tile_shape, dtype=np.uint8) + half_height = tileset.tile_height // 2 + half_width = tileset.tile_width // 2 + quadrants[:half_height, :half_width] = 0b1000 # Top-left. + quadrants[:half_height, half_width:] = 0b0100 # Top-right. + quadrants[half_height:, :half_width] = 0b0010 # Bottom-left. + quadrants[half_height:, half_width:] = 0b0001 # Bottom-right. + + for codepoint, quad_mask in ( + (0x2580, 0b1100), # "▀" Upper half block. + (0x2584, 0b0011), # "▄" Lower half block. + (0x2588, 0b1111), # "█" Full block. + (0x258C, 0b1010), # "▌" Left half block. + (0x2590, 0b0101), # "▐" Right half block. + (0x2596, 0b0010), # "▖" Quadrant lower left. + (0x2597, 0b0001), # "▗" Quadrant lower right. + (0x2598, 0b1000), # "▘" Quadrant upper left. + (0x2599, 0b1011), # "▙" Quadrant upper left and lower left and lower right. + (0x259A, 0b1001), # "▚" Quadrant upper left and lower right. + (0x259B, 0b1110), # "▛" Quadrant upper left and upper right and lower left. + (0x259C, 0b1101), # "▜" Quadrant upper left and upper right and lower right. + (0x259D, 0b0100), # "▝" Quadrant upper right. + (0x259E, 0b0110), # "▞" Quadrant upper right and lower left. + (0x259F, 0b0111), # "▟" Quadrant upper right and lower left and lower right. + ): + alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) * 255 + tileset.set_tile(codepoint, alpha) + + for codepoint, axis, fraction, negative in ( + (0x2581, 0, 7, True), # "▁" Lower one eighth block. + (0x2582, 0, 6, True), # "▂" Lower one quarter block. + (0x2583, 0, 5, True), # "▃" Lower three eighths block. + (0x2585, 0, 3, True), # "▅" Lower five eighths block. + (0x2586, 0, 2, True), # "▆" Lower three quarters block. + (0x2587, 0, 1, True), # "▇" Lower seven eighths block. + (0x2589, 1, 7, False), # "▉" Left seven eighths block. + (0x258A, 1, 6, False), # "▊" Left three quarters block. + (0x258B, 1, 5, False), # "▋" Left five eighths block. + (0x258D, 1, 3, False), # "▍" Left three eighths block. + (0x258E, 1, 2, False), # "▎" Left one quarter block. + (0x258F, 1, 1, False), # "▏" Left one eighth block. + (0x2594, 0, 1, False), # "▔" Upper one eighth block. + (0x2595, 1, 7, True), # "▕" Right one eighth block . + ): + indexes = [slice(None), slice(None)] + divide = tileset.tile_shape[axis] * fraction // 8 + # If negative then shade from the far corner, otherwise shade from the near corner. + indexes[axis] = slice(divide, None) if negative else slice(None, divide) + alpha = np.zeros(tileset.tile_shape, dtype=np.uint8) + alpha[tuple(indexes)] = 255 + tileset.set_tile(codepoint, alpha) + + CHARMAP_CP437 = [ 0x0000, 0x263A, diff --git a/tests/test_tileset.py b/tests/test_tileset.py new file mode 100644 index 00000000..192c2a36 --- /dev/null +++ b/tests/test_tileset.py @@ -0,0 +1,8 @@ +import tcod + + +def test_proc_block_elements() -> None: + tileset = tcod.tileset.Tileset(8, 8) + tcod.tileset.procedural_block_elements(tileset) + tileset = tcod.tileset.Tileset(0, 0) + tcod.tileset.procedural_block_elements(tileset) From 75329b3014714bdaf0e33f6db9496201087dbd9c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 16 Oct 2021 20:04:27 -0700 Subject: [PATCH 0602/1101] Update setup script. Type ignore is no longer needed with types packages. --- .github/workflows/python-package.yml | 2 +- requirements.txt | 2 ++ setup.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 489316c7..1127e239 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -57,7 +57,7 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install Python dependencies - run: pip install mypy pytest types-tabulate -r requirements.txt + run: pip install mypy pytest -r requirements.txt - name: Fake initialize package run: | echo '__version__ = ""' > tcod/version.py diff --git a/requirements.txt b/requirements.txt index 056e1c9f..3ce930b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ cffi~=1.13 numpy>=1.20.3 pycparser>=2.14 setuptools>=36.0.1 +types-setuptools +types-tabulate typing_extensions diff --git a/setup.py b/setup.py index 0c1b3e0b..f01db6f2 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ import warnings from typing import List -from setuptools import setup # type: ignore +from setuptools import setup SDL_VERSION_NEEDED = (2, 0, 5) @@ -147,6 +147,7 @@ def check_sdl_version() -> None: "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", From 0e7824f59f8d9b767e85e15b8efddd5f50baaa2d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 18 Oct 2021 01:33:53 -0700 Subject: [PATCH 0603/1101] Make procedural_block_elements tileset parameter keyword only. This is to make changing how the function works easier in the future. --- tcod/tileset.py | 4 ++-- tests/test_tileset.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/tileset.py b/tcod/tileset.py index 6a51ca18..98ea4520 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -344,7 +344,7 @@ def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Opt return Tileset._claim(cdata) -def procedural_block_elements(tileset: Tileset) -> None: +def procedural_block_elements(*, tileset: Tileset) -> None: """Overwrites the block element codepoints in `tileset` with prodecually generated glyphs. Args: @@ -361,7 +361,7 @@ def procedural_block_elements(tileset: Tileset) -> None: Example:: >>> tileset = tcod.tileset.Tileset(8, 8) - >>> tcod.tileset.procedural_block_elements(tileset) + >>> tcod.tileset.procedural_block_elements(tileset=tileset) >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left. array([[ 0, 0, 0, 0, 255, 255, 255, 255], [ 0, 0, 0, 0, 255, 255, 255, 255], diff --git a/tests/test_tileset.py b/tests/test_tileset.py index 192c2a36..36a3de58 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -3,6 +3,6 @@ def test_proc_block_elements() -> None: tileset = tcod.tileset.Tileset(8, 8) - tcod.tileset.procedural_block_elements(tileset) + tcod.tileset.procedural_block_elements(tileset=tileset) tileset = tcod.tileset.Tileset(0, 0) - tcod.tileset.procedural_block_elements(tileset) + tcod.tileset.procedural_block_elements(tileset=tileset) From d3cc851967ccdb54eeb8064bae246a264217b480 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 18 Oct 2021 01:48:33 -0700 Subject: [PATCH 0604/1101] Add launch settings in VSCode for pytest and documentation generation. --- .vscode/launch.json | 23 +++++++++++++++++++++++ .vscode/tasks.json | 31 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index b1e2764f..4bd84cbe 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,29 @@ "cwd": "${workspaceFolder}/examples", "console": "integratedTerminal", "preLaunchTask": "develop python-tcod", + }, + { + "name": "Python: Run tests", + "type": "python", + "request": "launch", + "module": "pytest", + "preLaunchTask": "develop python-tcod", + }, + { + "name": "Documentation: Launch Chrome", + "request": "launch", + "type": "pwa-chrome", + "url": "file://${workspaceFolder}/docs/_build/html/index.html", + "webRoot": "${workspaceFolder}", + "preLaunchTask": "build documentation", + }, + { + "name": "Documentation: Launch Edge", + "request": "launch", + "type": "pwa-msedge", + "url": "file://${workspaceFolder}/docs/_build/html/index.html", + "webRoot": "${workspaceFolder}", + "preLaunchTask": "build documentation", } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 854b959f..ba6b05d3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,6 +8,37 @@ "label": "develop python-tcod", "type": "shell", "command": "python setup.py develop" + }, + { + "label": "install documentation requirements", + "type": "process", + "command": "python", + "args": [ + "-m", + "pip", + "install", + "--requirement", + "docs/requirements.txt" + ], + "problemMatcher": [] + }, + { + "label": "build documentation", + "type": "shell", + "command": "make", + "args": [ + "html" + ], + "options": { + "cwd": "${workspaceFolder}/docs" + }, + "problemMatcher": [], + "dependsOn": [ + "install documentation requirements" + ], + "windows": { + "command": "./make.bat" + } } ] } \ No newline at end of file From 51495cd4f84e7ee7a821c9a7d17550f254ff0145 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 18 Oct 2021 02:05:37 -0700 Subject: [PATCH 0605/1101] Check spelling, add words from workflows. --- .vscode/settings.json | 16 ++++++++++++++++ tcod/color.py | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e121c032..a0b7657b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,7 @@ "algo", "ALPH", "arange", + "ARCHS", "asarray", "ascontiguousarray", "astar", @@ -39,6 +40,7 @@ "autogenerated", "automodule", "backlinks", + "bdist", "Benesch", "bezier", "bfade", @@ -59,6 +61,8 @@ "CFLAGS", "CHARMAP", "Chebyshev", + "cibuildwheel", + "CIBW", "CMWC", "Codacy", "Codecov", @@ -77,6 +81,7 @@ "dcost", "DCROSS", "dejavu", + "delocate", "deque", "desaturated", "DESATURATED", @@ -130,6 +135,7 @@ "INCOL", "INROW", "isinstance", + "isort", "itleref", "ivar", "jice", @@ -160,12 +166,14 @@ "libsdl", "libtcod", "libtcodpy", + "liskin", "LMASK", "lmeta", "lodepng", "LTCG", "lucida", "LWIN", + "manylinux", "maxarray", "maxdepth", "mbutton", @@ -184,6 +192,7 @@ "msvcr", "mulx", "muly", + "mypy", "namegen", "ndarray", "ndim", @@ -214,7 +223,11 @@ "pycall", "pycparser", "pyinstaller", + "pypa", + "PYPI", "pypiwin", + "pypy", + "pytest", "PYTHONOPTIMIZE", "Pyup", "quickstart", @@ -246,6 +259,7 @@ "SDLCALL", "sdlevent", "SDLK", + "servernum", "setuptools", "SHADOWCAST", "SMILIE", @@ -261,6 +275,7 @@ "TCODLIB", "TEEE", "TEEW", + "thirdparty", "Tileset", "tilesets", "tilesheet", @@ -287,6 +302,7 @@ "WINDOWRESIZED", "xdst", "xrel", + "xvfb", "ydst", "yrel" ] diff --git a/tcod/color.py b/tcod/color.py index f1bcf570..43fe0aab 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -24,7 +24,7 @@ def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> None: @property def r(self) -> int: - """int: Red value, always normalised to 0-255. + """int: Red value, always normalized to 0-255. .. deprecated:: 9.2 Color attributes will not be mutable in the future. @@ -38,7 +38,7 @@ def r(self, value: int) -> None: @property def g(self) -> int: - """int: Green value, always normalised to 0-255. + """int: Green value, always normalized to 0-255. .. deprecated:: 9.2 Color attributes will not be mutable in the future. @@ -52,7 +52,7 @@ def g(self, value: int) -> None: @property def b(self) -> int: - """int: Blue value, always normalised to 0-255. + """int: Blue value, always normalized to 0-255. .. deprecated:: 9.2 Color attributes will not be mutable in the future. From 25b13cc41c1041ef4173456db43c303e2cc3b73b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 19 Oct 2021 20:23:49 -0700 Subject: [PATCH 0606/1101] Fix NumPy type aliases in docs. Special options were added to Sphinx to prevent them from being expanded. --- docs/conf.py | 6 ++++++ tcod/noise.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 595c3324..cd7ee22e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -129,6 +129,12 @@ autodoc_member_order = "groupwise" +# Prevent type aliases from expanding +autodoc_type_aliases = { + "ArrayLike": "numpy.typing.ArrayLike", + "NDArray": "numpy.typing.NDArray", +} + # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/tcod/noise.py b/tcod/noise.py index bf8a80e8..2ee91c96 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -321,7 +321,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: """Sample an open mesh-grid array and return the result. Args - ogrid (Sequence[Sequence[float]]): An open mesh-grid. + ogrid (Sequence[ArrayLike]): An open mesh-grid. Returns: numpy.ndarray: An array of sampled points. From d9dbc1473f17f26641c9bcb4021be078721832ba Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 19 Oct 2021 20:31:14 -0700 Subject: [PATCH 0607/1101] Fix warnings in Sphinx configuration. --- docs/conf.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cd7ee22e..45d78195 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,10 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os +import re +import subprocess import sys +from typing import Dict sys.path.insert(0, os.path.abspath("..")) @@ -65,14 +68,17 @@ # built documents. # # The full version, including alpha/beta/rc tags. -from subprocess import check_output - -release = check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() +git_describe = subprocess.run( + ["git", "describe", "--abbrev=0"], stdout=subprocess.PIPE, universal_newlines=True, check=True +) +release = git_describe.stdout.strip() +assert release print("release version: %r" % release) -# The short X.Y version. -import re -version = re.match(r"([0-9]+\.[0-9]+).*?", release).group() +# The short X.Y version. +match_version = re.match(r"([0-9]+\.[0-9]+).*?", release) +assert match_version +version = match_version.group() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -261,7 +267,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = { +latex_elements: Dict[str, str] = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). From 5934413481d46ca1e4daeb62964518ebd333f40e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 19 Oct 2021 21:21:39 -0700 Subject: [PATCH 0608/1101] Require the latest versions of Sphinx. So that RTD uses a version later than Sphinx 1. --- .readthedocs.yml | 6 +++--- docs/requirements.txt | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 51084f18..2521036c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -18,7 +18,7 @@ formats: - epub python: - version: 3.8 + version: "3.8" install: - - requirements: requirements.txt - - requirements: docs/requirements.txt + - requirements: requirements.txt + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt index 483a4e96..6cdabebb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ +sphinx>=4.2 sphinx_rtd_theme From 742153c270750150894eced72b2f33cb529a581f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Oct 2021 17:02:03 -0700 Subject: [PATCH 0609/1101] Prepare 13.1.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0221ca41..6c2b3cbc 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +13.1.0 - 2021-10-22 +------------------- Added - Added the `tcod.tileset.procedural_block_elements` function. Removed From d9834fb1f5a4ae8505e22b34658ec09db1cacd91 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 6 Nov 2021 20:20:02 -0700 Subject: [PATCH 0610/1101] Add more support for tests from VSCode. --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a0b7657b..6e892bc5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -305,5 +305,8 @@ "xvfb", "ydst", "yrel" - ] + ], + "python.testing.pytestArgs": [], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } \ No newline at end of file From 3f318bf6d58a9746af0dae37ba7441681716bf2e Mon Sep 17 00:00:00 2001 From: odidev Date: Wed, 17 Nov 2021 15:34:35 +0500 Subject: [PATCH 0611/1101] Add Linux AArch64 wheel build support Signed-off-by: odidev --- .github/workflows/python-package.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1127e239..41d4c886 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -186,8 +186,14 @@ jobs: linux-wheels: runs-on: "ubuntu-20.04" + strategy: + matrix: + arch: ["x86_64", "aarch64"] steps: - uses: actions/checkout@v1 + - name: Set up QEMU + if: ${{ matrix.arch == 'aarch64' }} + uses: docker/setup-qemu-action@v1 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -204,10 +210,14 @@ jobs: python -m cibuildwheel --output-dir wheelhouse env: CIBW_BUILD: cp36-* pp* - CIBW_ARCHS_LINUX: "x86_64" - CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + CIBW_ARCHS_LINUX: ${{ matrix.arch }} + CIBW_MANYLINUX_*_IMAGE: manylinux2014 CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014 - CIBW_BEFORE_ALL_LINUX: yum install -y SDL2-devel + CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014 + CIBW_BEFORE_ALL_LINUX: > + yum install -y epel-release && + yum-config-manager --enable epel && + yum install -y SDL2-devel CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod" - name: Archive wheel From d11f17d0cbf6a2fb839e2f08f68b2d6289f1c5b9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 Nov 2021 09:38:46 -0800 Subject: [PATCH 0612/1101] Add pickle test for v13 Random. The internal C type will have a slight change, so the old format needs to stay supported. --- MANIFEST.in | 2 +- tests/data/README.md | 3 +++ tests/data/random_v13.pkl | Bin 0 -> 12069 bytes tests/test_random.py | 8 ++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/data/README.md create mode 100644 tests/data/random_v13.pkl diff --git a/MANIFEST.in b/MANIFEST.in index 54b46459..dd211a1d 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,6 @@ include *.py *.cfg *.txt *.rst *.toml recursive-include tcod *.py *.c *.h recursive-include libtcod/src *.glsl* *.c *.h -include libtcod/*.txt libtcod/*.md +include libtcod/*.txt libtcod/*.md tests/data/*.pkl exclude tcod/*/SDL2.dll diff --git a/tests/data/README.md b/tests/data/README.md new file mode 100644 index 00000000..8a7a10d7 --- /dev/null +++ b/tests/data/README.md @@ -0,0 +1,3 @@ +Data files for tests, such as old pickle streams. + +Remember to add new file types to `MANIFEST.in`. diff --git a/tests/data/random_v13.pkl b/tests/data/random_v13.pkl new file mode 100644 index 0000000000000000000000000000000000000000..03fb7bf2e07c3c6951b3ecb7332a510a01a4cadd GIT binary patch literal 12069 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 None: assert rand.uniform(0, 1) == rand2.uniform(0, 1) assert rand.uniform(0, 1) == rand2.uniform(0, 1) assert rand.uniform(0, 1) == rand2.uniform(0, 1) + + +def test_load_rng_v13() -> None: + with open(pathlib.Path(__file__).parent / "data/random_v13.pkl", "rb") as f: + rand: tcod.random.Random = pickle.load(f) + assert rand.randint(0, 0xFFFF) == 56422 + assert rand.randint(0, 0xFFFF) == 15795 From 514cc496e1e5ae0c58bbc1b9c8d58fcb0bc5ebd2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 16 Dec 2021 19:26:19 -0800 Subject: [PATCH 0613/1101] Fix breaking changes from libtcod. Private types in libtcod heaps and random were changed. --- build_libtcod.py | 4 ++-- libtcod | 2 +- tcod/cffi.h | 1 + tcod/libtcodpy.py | 7 ++----- tcod/path.c | 9 ++++----- tcod/random.py | 33 ++++++++++++++++----------------- tests/test_random.py | 2 +- 7 files changed, 27 insertions(+), 31 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index a9de366b..0b670586 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -38,10 +38,10 @@ RE_PREPROCESSOR = re.compile(r"(?!#define\s+\w+\s+\d+$)#.*?(? tcod.random.Random: Returns: Random: A Random instance using the default random number generator. """ - return tcod.random.Random._new_from_cdata(ffi.cast("mersenne_data_t*", lib.TCOD_random_get_instance())) + return tcod.random.Random._new_from_cdata(lib.TCOD_random_get_instance()) @pending_deprecate() @@ -3809,10 +3809,7 @@ def random_save(rnd: Optional[tcod.random.Random]) -> tcod.random.Random: """ return tcod.random.Random._new_from_cdata( ffi.gc( - ffi.cast( - "mersenne_data_t*", - lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL), - ), + lib.TCOD_random_save(rnd.random_c if rnd else ffi.NULL), lib.TCOD_random_delete, ) ) diff --git a/tcod/path.c b/tcod/path.c index dac89091..873e2dae 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -440,9 +440,9 @@ int update_frontier_heuristic( for (int i = 0; i < frontier->heap.size; ++i) { unsigned char* heap_ptr = (unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; - struct TCOD_HeapNode* heap_node = (void*)heap_ptr; - struct FrontierNode* f_node = (struct FrontierNode*)heap_node->data; - heap_node->priority = (f_node->distance + compute_heuristic(heuristic, frontier->ndim, f_node->index)); + int* priority = (int*)heap_ptr; + struct FrontierNode* f_node = (struct FrontierNode*)(heap_ptr + frontier->heap.data_offset); + *priority = (f_node->distance + compute_heuristic(heuristic, frontier->ndim, f_node->index)); } TCOD_minheap_heapify(&frontier->heap); return 0; @@ -478,8 +478,7 @@ int frontier_has_index( for (int i = 0; i < frontier->heap.size; ++i) { const unsigned char* heap_ptr = (const unsigned char*)frontier->heap.heap; heap_ptr += frontier->heap.node_size * i; - const struct TCOD_HeapNode* heap_node = (const void*)heap_ptr; - const struct FrontierNode* f_node = (const void*)heap_node->data; + const struct FrontierNode* f_node = (const void*)(heap_ptr + frontier->heap.data_offset); bool found = 1; for (int j = 0; j < frontier->ndim; ++j) { if (index[j] != f_node->index[j]) { diff --git a/tcod/random.py b/tcod/random.py index eee4815c..ef64df2c 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -75,10 +75,7 @@ def __init__( seed = hash(seed) self.random_c = ffi.gc( - ffi.cast( - "mersenne_data_t*", - lib.TCOD_random_new_from_seed(algorithm, seed & 0xFFFFFFFF), - ), + lib.TCOD_random_new_from_seed(algorithm, seed & 0xFFFFFFFF), lib.TCOD_random_delete, ) @@ -141,22 +138,24 @@ def __getstate__(self) -> Any: """Pack the self.random_c attribute into a portable state.""" state = self.__dict__.copy() state["random_c"] = { - "algo": self.random_c.algo, - "distribution": self.random_c.distribution, - "mt": list(self.random_c.mt), - "cur_mt": self.random_c.cur_mt, - "Q": list(self.random_c.Q), - "c": self.random_c.c, - "cur": self.random_c.cur, + "mt_cmwc": { + "algorithm": self.random_c.mt_cmwc.algorithm, + "distribution": self.random_c.mt_cmwc.distribution, + "mt": list(self.random_c.mt_cmwc.mt), + "cur_mt": self.random_c.mt_cmwc.cur_mt, + "Q": list(self.random_c.mt_cmwc.Q), + "c": self.random_c.mt_cmwc.c, + "cur": self.random_c.mt_cmwc.cur, + } } return state def __setstate__(self, state: Any) -> None: """Create a new cdata object with the stored paramaters.""" - try: - cdata = state["random_c"] - except KeyError: # old/deprecated format - cdata = state["cdata"] - del state["cdata"] - state["random_c"] = ffi.new("mersenne_data_t*", cdata) + if "algo" in state["random_c"]: + # Handle old/deprecated format. Covert to libtcod's new union type. + state["random_c"]["algorithm"] = state["random_c"]["algo"] + del state["random_c"]["algo"] + state["random_c"] = {"mt_cmwc": state["random_c"]} + state["random_c"] = ffi.new("TCOD_Random*", state["random_c"]) self.__dict__.update(state) diff --git a/tests/test_random.py b/tests/test_random.py index 5984900e..ce5875e3 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -29,7 +29,7 @@ def test_tcod_random_pickle() -> None: assert rand.uniform(0, 1) == rand2.uniform(0, 1) -def test_load_rng_v13() -> None: +def test_load_rng_v13_1() -> None: with open(pathlib.Path(__file__).parent / "data/random_v13.pkl", "rb") as f: rand: tcod.random.Random = pickle.load(f) assert rand.randint(0, 0xFFFF) == 56422 From b3ff5fb69ccd1c5871ad1a33adee89d4702c0be1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 16 Dec 2021 19:44:27 -0800 Subject: [PATCH 0614/1101] Update type hints. --- examples/samples_tcod.py | 4 ++-- tcod/loader.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 3ca8b1a5..7a41f50b 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -621,8 +621,8 @@ def on_draw(self) -> None: light[~visible] = 0 # Set non-visible areas to darkness. # Setup background colors for floating point math. - light_bg = self.light_map_bg.astype(np.float16) - dark_bg = self.dark_map_bg.astype(np.float16) + light_bg: NDArray[np.float16] = self.light_map_bg.astype(np.float16) + dark_bg: NDArray[np.float16] = self.dark_map_bg.astype(np.float16) # Linear interpolation between colors. sample_console.tiles_rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] diff --git a/tcod/loader.py b/tcod/loader.py index 2469d8ce..645246ba 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -9,7 +9,7 @@ import cffi # type: ignore -from tcod import __path__ # type: ignore +from tcod import __path__ __sdl_version__ = "" From 32668013613e13f9d446aed062796a7caa0c0085 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 18 Dec 2021 16:30:52 -0800 Subject: [PATCH 0615/1101] Add console parameter to tcod.context.new. --- CHANGELOG.rst | 2 ++ tcod/context.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6c2b3cbc..64213b55 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Added + - New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. 13.1.0 - 2021-10-22 ------------------- diff --git a/tcod/context.py b/tcod/context.py index e2e69f12..b05c75b8 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -365,6 +365,7 @@ def new( sdl_window_flags: Optional[int] = None, title: Optional[str] = None, argv: Optional[Iterable[str]] = None, + console: Optional[tcod.Console] = None, ) -> Context: """Create a new context with the desired pixel size. @@ -376,6 +377,9 @@ def new( `columns` and `rows` is the desired size of the console. Can be left as `None` when you're setting a context by a window size instead of a console. + `console` automatically fills in the `columns` and `rows` parameters from an existing :any:`tcod.console.Console` + instance. + Providing no size information at all is also acceptable. `renderer` is the desired libtcod renderer to use. @@ -409,6 +413,9 @@ def new( the console which should be used. .. versionadded:: 11.16 + + .. versionchanged:: 13.2 + Added the `console` parameter. """ if renderer is None: renderer = RENDERER_OPENGL2 @@ -416,6 +423,9 @@ def new( sdl_window_flags = SDL_WINDOW_RESIZABLE if argv is None: argv = sys.argv + if console is not None: + columns = columns or console.width + rows = rows or console.height argv_encoded = [ffi.new("char[]", arg.encode("utf-8")) for arg in argv] # Needs to be kept alive for argv_c. argv_c = ffi.new("char*[]", argv_encoded) From ac6d8374f853b42befe00b2633caeee9ca6b031d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 24 Dec 2021 12:07:57 -0800 Subject: [PATCH 0616/1101] Update libtcod and note fixes. --- CHANGELOG.rst | 10 ++++++++++ libtcod | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 64213b55..c50d6440 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,10 +11,20 @@ Unreleased Added - New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. +Changed + - Using `libtcod 1.20.0`. + +Fixed + - Fixed segfault when an OpenGL2 context fails to load. + - Gaussian number generation no longer affects the results of unrelated RNG's. + - Gaussian number generation is now reentrant and thread-safe. + - Fixed potential crash in PNG image loading. + 13.1.0 - 2021-10-22 ------------------- Added - Added the `tcod.tileset.procedural_block_elements` function. + Removed - Python 3.6 is no longer supported. diff --git a/libtcod b/libtcod index ec5c9168..4ff19abe 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ec5c91683f73e3dd5d2fab75501f98e92383dfe8 +Subproject commit 4ff19abea52c34eb07f2945b9657648a87820963 From 033c8621d27ab77c64854f2b3c745be007f4362d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 24 Dec 2021 13:30:28 -0800 Subject: [PATCH 0617/1101] Prepare 13.2.0 release. --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c50d6440..2f2bf05b 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ + +13.2.0 - 2021-12-24 +------------------- Added - New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. From 14f454906d2c9cc1ad620cf3ba38eb667f8012c1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 31 Dec 2021 18:05:10 -0800 Subject: [PATCH 0618/1101] Use the more correct os.PathLike type hint. --- CHANGELOG.rst | 2 ++ tcod/console.py | 6 +++--- tcod/image.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f2bf05b..89ebd6b6 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ v2.0.0 Unreleased ------------------ +Fixed + - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. 13.2.0 - 2021-12-24 ------------------- diff --git a/tcod/console.py b/tcod/console.py index b6e6862a..e1f3daee 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -7,7 +7,7 @@ import os import warnings -from pathlib import Path +from os import PathLike from typing import Any, Iterable, Optional, Sequence, Tuple, Union import numpy as np @@ -1267,7 +1267,7 @@ def recommended_size() -> Tuple[int, int]: return w, h -def load_xp(path: Union[str, Path], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: +def load_xp(path: Union[str, PathLike[str]], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: """Load a REXPaint file as a tuple of consoles. `path` is the name of the REXPaint file to load. @@ -1309,7 +1309,7 @@ def load_xp(path: Union[str, Path], order: Literal["C", "F"] = "C") -> Tuple[Con def save_xp( - path: Union[str, Path], + path: Union[str, PathLike[str]], consoles: Iterable[Console], compress_level: int = 9, ) -> None: diff --git a/tcod/image.py b/tcod/image.py index 82d489dc..e47ddc39 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -7,7 +7,7 @@ """ from __future__ import annotations -from pathlib import Path +from os import PathLike from typing import Any, Dict, Tuple, Union import numpy as np @@ -336,7 +336,7 @@ def _get_format_name(format: int) -> str: " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", category=PendingDeprecationWarning, ) -def load(filename: Union[str, Path]) -> NDArray[np.uint8]: +def load(filename: Union[str, PathLike[str]]) -> NDArray[np.uint8]: """Load a PNG file as an RGBA array. `filename` is the name of the file to load. From c811aa8c4cddac97ed8d6b6a6d91fc456d5521f8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 1 Jan 2022 01:36:56 -0800 Subject: [PATCH 0619/1101] Add XTERM renderer. --- CHANGELOG.rst | 3 +++ build_libtcod.py | 2 ++ tcod/__init__.py | 1 + tcod/constants.py | 4 +++- tcod/context.py | 14 ++++++++++++++ tcod/libtcodpy.py | 1 + 6 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 89ebd6b6..8bb930a3 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,9 @@ v2.0.0 Unreleased ------------------ +Added + - New experimental renderer `tcod.context.RENDERER_XTERM`. + Fixed - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. diff --git a/build_libtcod.py b/build_libtcod.py index 0b670586..a69933c6 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -388,6 +388,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: "TCOD_KEY_TEXT_SIZE", "TCOD_NOISE_MAX_DIMENSIONS", "TCOD_NOISE_MAX_OCTAVES", + "TCOD_FALLBACK_FONT_SIZE", ] EXCLUDE_CONSTANT_PREFIXES = [ @@ -395,6 +396,7 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: "TCOD_HEAP_", "TCOD_LEX_", "TCOD_CHARMAP_", + "TCOD_LOG_", ] diff --git a/tcod/__init__.py b/tcod/__init__.py index 98c489b1..91f41cc6 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -556,6 +556,7 @@ "RENDERER_OPENGL2", "RENDERER_SDL", "RENDERER_SDL2", + "RENDERER_XTERM", "RIGHT", "RNG_CMWC", "RNG_MT", diff --git a/tcod/constants.py b/tcod/constants.py index 41afe966..4cb84313 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -247,7 +247,7 @@ KEY_PRESSED = 1 KEY_RELEASED = 2 LEFT = 0 -NB_RENDERERS = 5 +NB_RENDERERS = 6 NOISE_DEFAULT = 0 NOISE_PERLIN = 1 NOISE_SIMPLEX = 2 @@ -257,6 +257,7 @@ RENDERER_OPENGL2 = 4 RENDERER_SDL = 2 RENDERER_SDL2 = 3 +RENDERER_XTERM = 5 RIGHT = 1 RNG_CMWC = 1 RNG_MT = 0 @@ -755,6 +756,7 @@ "RENDERER_OPENGL2", "RENDERER_SDL", "RENDERER_SDL2", + "RENDERER_XTERM", "RIGHT", "RNG_CMWC", "RNG_MT", diff --git a/tcod/context.py b/tcod/context.py index b05c75b8..e62ac042 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -80,6 +80,7 @@ "RENDERER_OPENGL2", "RENDERER_SDL", "RENDERER_SDL2", + "RENDERER_XTERM", ) SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN @@ -126,6 +127,19 @@ Rendering is decided by SDL2 and can be changed by using an SDL2 hint: https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER """ +RENDERER_XTERM = lib.TCOD_RENDERER_XTERM +"""A renderer targeting modern terminals with 24-bit color support. + +This is an experimental renderer with partial support for XTerm and SSH. +This will work best on those terminals. + +Terminal inputs and events will be passed to SDL's event system. + +There is poor support for ANSI escapes on Windows 10. +It is not recommended to use this renderer on Windows. + +.. versionadded:: 13.3 +""" def _handle_tileset(tileset: Optional[tcod.tileset.Tileset]) -> Any: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d5b37ea2..ad5fee77 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4713,6 +4713,7 @@ def _atexit_verify() -> None: "RENDERER_OPENGL2", "RENDERER_SDL", "RENDERER_SDL2", + "RENDERER_XTERM", "RIGHT", "RNG_CMWC", "RNG_MT", From 3a261cafc438c9aad36b8b909db44e76bcfbfaa5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 1 Jan 2022 07:31:01 -0800 Subject: [PATCH 0620/1101] Update Numpy type hints. --- examples/samples_tcod.py | 22 +++++++++++----------- examples/ttf.py | 5 +++-- tcod/_internal.py | 3 ++- tcod/console.py | 2 +- tcod/event.py | 4 ++-- tcod/image.py | 10 +++++----- tcod/libtcodpy.py | 2 +- tcod/map.py | 4 ++-- tcod/noise.py | 10 +++++----- tcod/path.py | 6 +++--- tcod/sdl.py | 4 ++-- tcod/tileset.py | 9 +++++---- tests/test_libtcodpy.py | 11 ++++++----- tests/test_noise.py | 10 +++++----- tests/test_tcod.py | 4 ++-- 15 files changed, 55 insertions(+), 51 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 7a41f50b..2d95b315 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -14,7 +14,7 @@ import sys import time import warnings -from typing import List +from typing import Any, List import numpy as np import tcod @@ -101,14 +101,14 @@ class TrueColorSample(Sample): def __init__(self) -> None: self.name = "True colors" # corner colors - self.colors = np.array( + self.colors: NDArray[np.int16] = np.array( [(50, 40, 150), (240, 85, 5), (50, 35, 240), (10, 200, 130)], dtype=np.int16, ) # color shift direction - self.slide_dir = np.array([[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16) + self.slide_dir: NDArray[np.int16] = np.array([[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16) # corner indexes - self.corners = np.array([0, 1, 2, 3]) + self.corners: NDArray[np.int16] = np.array([0, 1, 2, 3], dtype=np.int16) def on_draw(self) -> None: self.slide_corner_colors() @@ -509,7 +509,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: "##############################################", ] -SAMPLE_MAP = np.array([list(line) for line in SAMPLE_MAP_]).transpose() +SAMPLE_MAP: NDArray[Any] = np.array([list(line) for line in SAMPLE_MAP_]).transpose() FOV_ALGO_NAMES = [ "BASIC ", @@ -545,17 +545,17 @@ def __init__(self) -> None: map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) - self.walkable = np.zeros(map_shape, dtype=bool, order="F") + self.walkable: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") self.walkable[:] = SAMPLE_MAP[:] == " " - self.transparent = np.zeros(map_shape, dtype=bool, order="F") + self.transparent: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") self.transparent[:] = self.walkable[:] | (SAMPLE_MAP == "=") # Lit background colors for the map. - self.light_map_bg = np.full(SAMPLE_MAP.shape, LIGHT_GROUND, dtype="3B") + self.light_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, LIGHT_GROUND, dtype="3B") self.light_map_bg[SAMPLE_MAP[:] == "#"] = LIGHT_WALL # Dark background colors for the map. - self.dark_map_bg = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B") + self.dark_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B") self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL def draw_ui(self) -> None: @@ -948,7 +948,7 @@ class BSPSample(Sample): def __init__(self) -> None: self.name = "Bsp toolkit" self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1) - self.bsp_map = np.zeros((SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F") + self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F") self.bsp_generate() def bsp_generate(self) -> None: @@ -1214,7 +1214,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: # xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] if numpy_available: - (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) # type: ignore + (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) # translate coordinates of all pixels to center xc = xc - HALF_W yc = yc - HALF_H diff --git a/examples/ttf.py b/examples/ttf.py index 36434916..249c771a 100644 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -13,6 +13,7 @@ import freetype # type: ignore # pip install freetype-py import numpy as np import tcod +from numpy.typing import NDArray FONT = "VeraMono.ttf" @@ -36,10 +37,10 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: ttf.load_glyph(glyph_index) bitmap = ttf.glyph.bitmap assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY - bitmap_array = np.asarray(bitmap.buffer).reshape((bitmap.width, bitmap.rows), order="F") + bitmap_array: NDArray[np.uint8] = np.asarray(bitmap.buffer).reshape((bitmap.width, bitmap.rows), order="F") if bitmap_array.size == 0: continue # Skip blank glyphs. - output_image = np.zeros(size, dtype=np.uint8, order="F") + output_image: NDArray[np.uint8] = np.zeros(size, dtype=np.uint8, order="F") out_slice = output_image # Adjust the position to center this glyph on the tile. diff --git a/tcod/_internal.py b/tcod/_internal.py index e7dc033b..bde088e9 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -7,6 +7,7 @@ from typing import Any, AnyStr, Callable, TypeVar, cast import numpy as np +from numpy.typing import NDArray from typing_extensions import Literal, NoReturn from tcod.loader import ffi, lib @@ -225,7 +226,7 @@ class TempImage(object): """An Image-like container for NumPy arrays.""" def __init__(self, array: Any): - self._array = np.ascontiguousarray(array, dtype=np.uint8) + self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape if depth != 3: raise TypeError("Array must have RGB channels. Shape is: %r" % (self._array.shape,)) diff --git a/tcod/console.py b/tcod/console.py index e1f3daee..895c7e79 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -124,7 +124,7 @@ def __init__( if buffer is not None: if self._order == "F": buffer = buffer.transpose() - self._tiles = np.ascontiguousarray(buffer, self.DTYPE) + self._tiles: NDArray[Any] = np.ascontiguousarray(buffer, self.DTYPE) else: self._tiles = np.ndarray((height, width), dtype=self.DTYPE) diff --git a/tcod/event.py b/tcod/event.py index e430ae49..3cfc878b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1091,8 +1091,8 @@ def get_keyboard_state() -> NDArray[np.bool_]: """ numkeys = ffi.new("int[1]") keyboard_state = lib.SDL_GetKeyboardState(numkeys) - out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=np.bool_) # type: ignore - out.flags["WRITEABLE"] = False # This buffer is supposed to be const. + out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=np.bool_) + out.flags["WRITEABLE"] = False # type: ignore[index] # This buffer is supposed to be const. return out diff --git a/tcod/image.py b/tcod/image.py index e47ddc39..0a017530 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -11,7 +11,7 @@ from typing import Any, Dict, Tuple, Union import numpy as np -from numpy.typing import NDArray +from numpy.typing import ArrayLike, NDArray import tcod.console from tcod._internal import _console, deprecate @@ -41,7 +41,7 @@ def _from_cdata(cls, cdata: Any) -> Image: return self @classmethod - def from_array(cls, array: Any) -> Image: + def from_array(cls, array: ArrayLike) -> Image: """Create a new Image from a copy of an array-like object. Example: @@ -52,10 +52,10 @@ def from_array(cls, array: Any) -> Image: .. versionadded:: 11.4 """ - array = np.asarray(array) + array = np.asarray(array, dtype=np.uint8) height, width, depth = array.shape image = cls(width, height) - image_array = np.asarray(image) + image_array: NDArray[np.uint8] = np.asarray(image) image_array[...] = array return image @@ -349,7 +349,7 @@ def load(filename: Union[str, PathLike[str]]) -> NDArray[np.uint8]: array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: - array = np.concatenate( # type: ignore + array = np.concatenate( ( array, np.full((height, width, 1), fill_value=255, dtype=np.uint8), diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index ad5fee77..626a05b6 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2305,7 +2305,7 @@ def _heightmap_cdata(array: NDArray[np.float32]) -> ffi.CData: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: raise ValueError("array must be a contiguous segment.") - if array.dtype != np.float32: # type: ignore + if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) height, width = array.shape pointer = ffi.from_buffer("float *", array) diff --git a/tcod/map.py b/tcod/map.py index 48ce9f9a..3ce4596b 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -87,7 +87,7 @@ def __init__( self.height = height self._order = tcod._internal.verify_order(order) - self.__buffer = np.zeros((height, width, 3), dtype=np.bool_) + self.__buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_) self.map_c = self.__as_cdata() def __as_cdata(self) -> Any: @@ -251,7 +251,7 @@ def compute_fov( RuntimeWarning, stacklevel=2, ) - map_buffer = np.empty( + map_buffer: NDArray[np.bool_] = np.empty( transparency.shape, dtype=[("transparent", bool), ("walkable", bool), ("fov", bool)], ) diff --git a/tcod/noise.py b/tcod/noise.py index 2ee91c96..cae99be0 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -38,7 +38,7 @@ import enum import warnings -from typing import Any, Optional, Sequence, Tuple, Union +from typing import Any, List, Optional, Sequence, Tuple, Union import numpy as np from numpy.typing import ArrayLike, NDArray @@ -245,7 +245,7 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: raise IndexError( "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) ) - indexes = np.broadcast_arrays(*indexes) # type: ignore + indexes = np.broadcast_arrays(*indexes) c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): if index.dtype.type == np.object_: @@ -253,7 +253,7 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: indexes[i] = np.ascontiguousarray(index, dtype=np.float32) c_input[i] = ffi.from_buffer("float*", indexes[i]) - out = np.empty(indexes[0].shape, dtype=np.float32) + out: NDArray[np.float32] = np.empty(indexes[0].shape, dtype=np.float32) if self.implementation == Implementation.SIMPLE: lib.TCOD_noise_get_vectorized( self.noise_c, @@ -332,7 +332,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: """ if len(ogrid) != self.dimensions: raise ValueError("len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions)) - ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid] + ogrids: List[NDArray[np.float32]] = [np.ascontiguousarray(array, np.float32) for array in ogrid] out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( self._tdl_noise_c, @@ -461,4 +461,4 @@ def grid( if len(shape) != len(origin): raise TypeError("shape must have the same length as origin") indexes = (np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin)) - return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) # type: ignore + return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) diff --git a/tcod/path.py b/tcod/path.py index 59b9b301..acc427af 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -473,7 +473,7 @@ def dijkstra2d( .. versionchanged:: 12.1 Added `out` parameter. Now returns the output array. """ - dist = np.asarray(distance) + dist: NDArray[Any] = np.asarray(distance) if out is ...: # type: ignore out = dist warnings.warn( @@ -560,7 +560,7 @@ def hillclimb2d( Added `edge_map` parameter. """ x, y = start - dist = np.asarray(distance) + dist: NDArray[Any] = np.asarray(distance) if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) c_dist = _export(dist) @@ -582,7 +582,7 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( - np.meshgrid( # type: ignore + np.meshgrid( *(np.arange(i, dtype=dtype) for i in shape), indexing="ij", copy=False, diff --git a/tcod/sdl.py b/tcod/sdl.py index 8aa38ecb..470011e6 100644 --- a/tcod/sdl.py +++ b/tcod/sdl.py @@ -8,7 +8,7 @@ from typing import Any, Tuple import numpy as np -from numpy.typing import ArrayLike +from numpy.typing import ArrayLike, NDArray from tcod.loader import ffi, lib @@ -19,7 +19,7 @@ class _TempSurface: """Holds a temporary surface derived from a NumPy array.""" def __init__(self, pixels: ArrayLike) -> None: - self._array = np.ascontiguousarray(pixels, dtype=np.uint8) + self._array: NDArray[np.uint8] = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array) != 3: raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape,)) if 3 <= self._array.shape[2] <= 4: diff --git a/tcod/tileset.py b/tcod/tileset.py index 98ea4520..cf26b0fb 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -74,7 +74,7 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]: uint8. Note that most grey-scale tiles will only use the alpha channel and will usually have a solid white color channel. """ - tile = np.zeros(self.tile_shape + (4,), dtype=np.uint8) + tile: NDArray[np.uint8] = np.zeros(self.tile_shape + (4,), dtype=np.uint8) lib.TCOD_tileset_get_tile_( self._tileset_p, codepoint, @@ -133,7 +133,7 @@ def set_tile(self, codepoint: int, tile: Union[ArrayLike, NDArray[np.uint8]]) -> """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: - full_tile = np.empty(self.tile_shape + (4,), dtype=np.uint8) + full_tile: NDArray[np.uint8] = np.empty(self.tile_shape + (4,), dtype=np.uint8) full_tile[:, :, :3] = 255 full_tile[:, :, 3] = tile return self.set_tile(codepoint, full_tile) @@ -167,7 +167,7 @@ def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: raise ValueError("'console' must not be the root console.") width = console.width * self.tile_width height = console.height * self.tile_height - out = np.empty((height, width, 4), np.uint8) + out: NDArray[np.uint8] = np.empty((height, width, 4), np.uint8) out[:] = 9 surface_p = ffi.gc( lib.SDL_CreateRGBSurfaceWithFormatFrom( @@ -415,7 +415,8 @@ def procedural_block_elements(*, tileset: Tileset) -> None: (0x259E, 0b0110), # "▞" Quadrant upper right and lower left. (0x259F, 0b0111), # "▟" Quadrant upper right and lower left and lower right. ): - alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) * 255 + alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) + alpha *= 255 tileset.set_tile(codepoint, alpha) for codepoint, axis, fraction, negative in ( diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 5db89034..bf12f9a1 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -5,6 +5,7 @@ import numpy import numpy as np import pytest +from numpy.typing import NDArray import tcod import tcod as libtcodpy @@ -253,7 +254,7 @@ def test_console_fill(console: tcod.console.Console) -> None: def test_console_fill_numpy(console: tcod.console.Console) -> None: width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) - fill = numpy.zeros((height, width), dtype=numpy.intc) + fill: NDArray[np.intc] = numpy.zeros((height, width), dtype=np.intc) for y in range(height): fill[y, :] = y % 256 @@ -262,9 +263,9 @@ def test_console_fill_numpy(console: tcod.console.Console) -> None: libtcodpy.console_fill_char(console, fill) # type: ignore # verify fill - bg = numpy.zeros((height, width), dtype=numpy.intc) - fg = numpy.zeros((height, width), dtype=numpy.intc) - ch = numpy.zeros((height, width), dtype=numpy.intc) + bg: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) + fg: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) + ch: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) for y in range(height): for x in range(width): bg[y, x] = libtcodpy.console_get_char_background(console, x, y)[0] @@ -639,7 +640,7 @@ def test_heightmap() -> None: libtcodpy.heightmap_delete(h_map) -MAP = np.array( +MAP: NDArray[Any] = np.array( [ list(line) for line in ( diff --git a/tests/test_noise.py b/tests/test_noise.py index ed77dce6..2067b852 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -40,17 +40,17 @@ def test_noise_class( noise.sample_mgrid(np.mgrid[:2, :3]) noise.sample_ogrid(np.ogrid[:2, :3]) - np.testing.assert_equal( # type: ignore + np.testing.assert_equal( noise.sample_mgrid(np.mgrid[:2, :3]), noise.sample_ogrid(np.ogrid[:2, :3]), ) - np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])]) # type: ignore + np.testing.assert_equal(noise.sample_mgrid(np.mgrid[:2, :3]), noise[tuple(np.mgrid[:2, :3])]) repr(noise) def test_noise_samples() -> None: noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX, tcod.noise.Implementation.SIMPLE) - np.testing.assert_equal( # type: ignore + np.testing.assert_equal( noise.sample_mgrid(np.mgrid[:32, :24]), noise.sample_ogrid(np.ogrid[:32, :24]), ) @@ -77,7 +77,7 @@ def test_noise_pickle(implementation: tcod.noise.Implementation) -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) noise = tcod.noise.Noise(2, implementation, seed=rand) noise2 = copy.copy(noise) - np.testing.assert_equal( # type: ignore + np.testing.assert_equal( noise.sample_ogrid(np.ogrid[:3, :1]), noise2.sample_ogrid(np.ogrid[:3, :1]), ) @@ -87,7 +87,7 @@ def test_noise_copy() -> None: rand = tcod.random.Random(tcod.random.MERSENNE_TWISTER, 42) noise = tcod.noise.Noise(2, seed=rand) noise2 = pickle.loads(pickle.dumps(noise)) - np.testing.assert_equal( # type: ignore + np.testing.assert_equal( noise.sample_ogrid(np.ogrid[:3, :1]), noise2.sample_ogrid(np.ogrid[:3, :1]), ) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index bfb6a36e..88821924 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -6,7 +6,7 @@ import numpy as np import pytest -from numpy.typing import DTypeLike +from numpy.typing import DTypeLike, NDArray import tcod @@ -125,7 +125,7 @@ def test_color_class() -> None: @pytest.mark.parametrize("dtype", [np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32, np.float32]) def test_path_numpy(dtype: DTypeLike) -> None: - map_np = np.ones((6, 6), dtype=dtype) + map_np: NDArray[Any] = np.ones((6, 6), dtype=dtype) map_np[1:4, 1:4] = 0 astar = tcod.path.AStar(map_np, 0) From 0ca94f0231e8b4922607c741940e47ca1db061e8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 5 Jan 2022 21:13:25 -0800 Subject: [PATCH 0621/1101] Apply BDF fixes. Refactor more types to os.PathLike. --- CHANGELOG.rst | 1 + libtcod | 2 +- tcod/tileset.py | 12 +++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8bb930a3..6e5ed581 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Added Fixed - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. + - BDF files with blank lines no longer fail to load with an "Unknown keyword" error. 13.2.0 - 2021-12-24 ------------------- diff --git a/libtcod b/libtcod index 4ff19abe..57f5216b 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 4ff19abea52c34eb07f2945b9657648a87820963 +Subproject commit 57f5216b66227f5af68a81e8d3118d6d15c58cff diff --git a/tcod/tileset.py b/tcod/tileset.py index cf26b0fb..fd4f2f21 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -14,7 +14,7 @@ import itertools import os -from pathlib import Path +from os import PathLike from typing import Any, Iterable, Optional, Tuple, Union import numpy as np @@ -250,7 +250,7 @@ def set_default(tileset: Tileset) -> None: lib.TCOD_set_default_tileset(tileset._tileset_p) -def load_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) -> Tileset: +def load_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_height: int) -> Tileset: """Return a new Tileset from a `.ttf` or `.otf` file. Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead. @@ -267,7 +267,7 @@ def load_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int @deprecate("Accessing the default tileset is deprecated.") -def set_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) -> None: +def set_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_height: int) -> None: """Set the default tileset from a `.ttf` or `.otf` file. `path` is the file path for the font file. @@ -293,7 +293,7 @@ def set_truetype_font(path: Union[str, Path], tile_width: int, tile_height: int) raise RuntimeError(ffi.string(lib.TCOD_get_error())) -def load_bdf(path: Union[str, Path]) -> Tileset: +def load_bdf(path: Union[str, PathLike[str]]) -> Tileset: """Return a new Tileset from a `.bdf` file. For the best results the font should be monospace, cell-based, and @@ -314,7 +314,9 @@ def load_bdf(path: Union[str, Path]) -> Tileset: return Tileset._claim(cdata) -def load_tilesheet(path: Union[str, Path], columns: int, rows: int, charmap: Optional[Iterable[int]]) -> Tileset: +def load_tilesheet( + path: Union[str, PathLike[str]], columns: int, rows: int, charmap: Optional[Iterable[int]] +) -> Tileset: """Return a new Tileset from a simple tilesheet image. `path` is the file path to a PNG file with the tileset. From b9571b3f6e92045dfba76d48e31126d15d61b2b0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 7 Jan 2022 17:53:18 -0800 Subject: [PATCH 0622/1101] Update changelog to markdown. Updates related references and scripts. --- CHANGELOG.md | 1176 ++++++++++++++++++++++++ CHANGELOG.rst | 1339 ---------------------------- README.rst | 2 +- docs/changelog.rst | 2 +- scripts/get_release_description.py | 9 +- scripts/tag_release.py | 25 +- setup.py | 2 +- 7 files changed, 1197 insertions(+), 1358 deletions(-) create mode 100644 CHANGELOG.md delete mode 100755 CHANGELOG.rst diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cabd4854 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1176 @@ +# Changelog +Changes relevant to the users of python-tcod are documented here. + +This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. + +## [Unreleased] +### Added +- New experimental renderer `tcod.context.RENDERER_XTERM`. +### Fixed +- Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. +- BDF files with blank lines no longer fail to load with an "Unknown keyword" error. + +## [13.2.0] - 2021-12-24 +### Added +- New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. + +### Changed +- Using `libtcod 1.20.0`. + +### Fixed +- Fixed segfault when an OpenGL2 context fails to load. +- Gaussian number generation no longer affects the results of unrelated RNG's. +- Gaussian number generation is now reentrant and thread-safe. +- Fixed potential crash in PNG image loading. + +## [13.1.0] - 2021-10-22 +### Added +- Added the `tcod.tileset.procedural_block_elements` function. + +### Removed +- Python 3.6 is no longer supported. + +## [13.0.0] - 2021-09-20 +### Changed +- Console print and drawing functions now always use absolute coordinates for negative numbers. + +## [12.7.3] - 2021-08-13 +### Deprecated +- `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`. +- `tcod.console_from_file` is deprecated. +- The `.asc` and `.apf` formats are no longer actively supported. + +### Fixed +- Fixed the parsing of SDL 2.0.16 headers. + +## [12.7.2] - 2021-07-01 +### Fixed +- *Scancode* and *KeySym* enums no longer crash when SDL returns an unexpected value. + +## [12.7.1] - 2021-06-30 +### Added +- Started uploading wheels for ARM64 macOS. + +## [12.7.0] - 2021-06-29 +### Added +- *tcod.image* and *tcod.tileset* now support *pathlib*. + +### Fixed +- Wheels for 32-bit Windows now deploy again. + +## [12.6.2] - 2021-06-15 +### Fixed +- Git is no longer required to install from source. + +## [12.6.1] - 2021-06-09 +### Fixed +- Fixed version mismatch when building from sources. + +## [12.6.0] - 2021-06-09 +### Added +- Added the *decoration* parameter to *Console.draw_frame*. + You may use this parameter to designate custom glyphs as the frame border. + +### Deprecated +- The handling of negative indexes given to console drawing and printing + functions will be changed to be used as absolute coordinates in the future. + +## [12.5.1] - 2021-05-30 +### Fixed +- The setup script should no longer fail silently when cffi is unavailable. + +## [12.5.0] - 2021-05-21 +### Changed +- `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums. + +## [12.4.0] - 2021-05-21 +### Added +- Added modernized REXPaint saving/loading functions. + - `tcod.console.load_xp` + - `tcod.console.save_xp` + +### Changed +- Using `libtcod 1.18.1`. +- `tcod.event.KeySym` and `tcod.event.Scancode` can now be hashed. + +## [12.3.2] - 2021-05-15 +### Changed +- Using `libtcod 1.17.1`. + +### Fixed +- Fixed regression with loading PNG images. + +## [12.3.1] - 2021-05-13 +### Fixed +- Fix Windows deployment. + +## [12.3.0] - 2021-05-13 +### Added +- New keyboard enums: + - `tcod.event.KeySym` + - `tcod.event.Scancode` + - `tcod.event.Modifier` +- New functions: + - `tcod.event.get_keyboard_state` + - `tcod.event.get_modifier_state` +- Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. +- Another name for the Console array attributes: `Console.rgb` and `Console.rgba`. + +### Changed +- Using `libtcod 1.17.0`. + +### Deprecated +- `Console_tiles_rgb` is being renamed to `Console.rgb`. +- `Console_tiles` being renamed to `Console.rgba`. + +### Fixed +- Contexts now give a more useful error when pickled. +- Fixed regressions with `tcod.console_print_frame` and `Console.print_frame` + when given empty strings as the banner. + +## [12.2.0] - 2021-04-09 +### Added +- Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. +- Added `tcod.noise.grid` helper function. + +### Deprecated +- The non-enum noise implementation names have been deprecated. + +### Fixed +- Indexing Noise classes now works with the FBM implementation. + +## [12.1.0] - 2021-04-01 +### Added +- Added package-level PyInstaller hook. + +### Changed +- Using `libtcod 1.16.7`. +- `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter. + +### Deprecated +- In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given. + +### Fixed +- Fixed crashes from loading tilesets with non-square tile sizes. +- Tilesets with a size of 0 should no longer crash when used. +- Prevent division by zero from recommended-console-size functions. + +## [12.0.0] - 2021-03-05 +### Deprecated +- The Random class will now warn if the seed it's given will not used + deterministically. It will no longer accept non-integer seeds in the future. + +### Changed +- Now bundles SDL 2.0.14 for MacOS. +- `tcod.event` can now detect and will warn about uninitialized tile + attributes on mouse events. + +### Removed +- Python 3.5 is no longer supported. +- The `tdl` module has been dropped. + +## [11.19.3] - 2021-01-07 +### Fixed +- Some wheels had broken version metadata. + +## [11.19.2] - 2020-12-30 +### Changed +- Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows. + +### Fixed +- MacOS wheels were failing to bundle dependencies for SDL2. + +## [11.19.1] - 2020-12-29 +### Fixed +- MacOS wheels failed to deploy for the previous version. + +## [11.19.0] - 2020-12-29 +### Added +- Added the important `order` parameter to `Context.new_console`. + +## [11.18.3] - 2020-12-28 +### Changed +- Now bundles SDL 2.0.14 for Windows/MacOS. + +### Deprecated +- Support for Python 3.5 will be dropped. +- `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load + these files without modifying an existing console. + +### Fixed +- `tcod.console_from_xp` now has better error handling (instead of crashing.) +- Can now compile with SDL 2.0.14 headers. + +## [11.18.2] - 2020-12-03 +### Fixed +- Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. +- Fixed regression in `tcod.sys_get_current_resolution` behavior. This + function now returns the monitor resolution as was previously expected. + +## [11.18.1] - 2020-11-30 +### Fixed +- Code points from the Private Use Area will now print correctly. + +## [11.18.0] - 2020-11-13 +### Added +- New context method `Context.new_console`. + +### Changed +- Using `libtcod 1.16.0-alpha.15`. + +## [11.17.0] - 2020-10-30 +### Added +- New FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. + +### Changed +- Using `libtcod 1.16.0-alpha.14`. + +## [11.16.1] - 2020-10-28 +### Deprecated +- Changed context deprecations to PendingDeprecationWarning to reduce mass + panic from tutorial followers. + +### Fixed +- Fixed garbled titles and crashing on some platforms. + +## [11.16.0] - 2020-10-23 +### Added +- Added `tcod.context.new` function. +- Contexts now support a CLI. +- You can now provide the window x,y position when making contexts. +- `tcod.noise.Noise` instances can now be indexed to generate noise maps. + +### Changed +- Using `libtcod 1.16.0-alpha.13`. +- The OpenGL 2 renderer can now use `SDL_HINT_RENDER_SCALE_QUALITY` to + determine the tileset upscaling filter. +- Improved performance of the FOV_BASIC algorithm. + +### Deprecated +- `tcod.context.new_window` and `tcod.context.new_terminal` have been replaced + by `tcod.context.new`. + +### Fixed +- Pathfinders will now work with boolean arrays. +- Console blits now ignore alpha compositing which would result in division by + zero. +- `tcod.console_is_key_pressed` should work even if libtcod events are ignored. +- The `TCOD_RENDERER` and `TCOD_VSYNC` environment variables should work now. +- `FOV_PERMISSIVE` algorithm is now reentrant. + +## [11.15.3] - 2020-07-30 +### Fixed +- `tcod.tileset.Tileset.remap`, codepoint and index were swapped. + +## [11.15.2] - 2020-07-27 +### Fixed +- `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays. + +## [11.15.1] - 2020-07-26 +### Changed +- `tcod.event.EventDispatch` now uses the absolute names for event type hints + so that IDE's can better auto-complete method overrides. + +### Fixed +- Fixed libtcodpy heightmap data alignment issues on non-square maps. + +## [11.15.0] - 2020-06-29 +### Added +- `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays. + +### Changed +- `tcod.path.CustomGraph` now accepts an `order` parameter. + +## [11.14.0] - 2020-06-23 +### Added +- New `tcod.los` module for NumPy-based line-of-sight algorithms. + Includes `tcod.los.bresenham`. + +### Deprecated +- `tcod.line_where` and `tcod.line_iter` have been deprecated. + +## [11.13.6] - 2020-06-19 +### Deprecated +- `console_init_root` and `console_set_custom_font` have been replaced by the + modern API. +- All functions which handle SDL windows without a context are deprecated. +- All functions which modify a globally active tileset are deprecated. +- `tcod.map.Map` is deprecated, NumPy arrays should be passed to functions + directly instead of through this class. + +## [11.13.5] - 2020-06-15 +### Fixed +- Install requirements will no longer try to downgrade `cffi`. + +## [11.13.4] - 2020-06-15 + +## [11.13.3] - 2020-06-13 +### Fixed +- `cffi` requirement has been updated to version `1.13.0`. + The older versions raise TypeError's. + +## [11.13.2] - 2020-06-12 +### Fixed +- SDL related errors during package installation are now more readable. + +## [11.13.1] - 2020-05-30 +### Fixed +- `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return + types. + +## [11.13.0] - 2020-05-22 +### Added +- `tcod.path`: New `Pathfinder` and `CustomGraph` classes. + +### Changed +- Added `edge_map` parameter to `tcod.path.dijkstra2d` and + `tcod.path.hillclimb2d`. + +### Fixed +- tcod.console_init_root` and context initializing functions were not + raising exceptions on failure. + +## [11.12.1] - 2020-05-02 +### Fixed +- Prevent adding non-existent 2nd halves to potential double-wide charterers. + +## [11.12.0] - 2020-04-30 +### Added +- Added `tcod.context` module. You now have more options for making libtcod + controlled contexts. +- `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset. +- `Tileset.remap`: Reassign codepoints to tiles on a Tileset. +- `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`. +- `tcod.tileset.CHARMAP_TCOD`: Older libtcod layout. + +### Changed +- `EventDispatch.dispatch` can now return the values returned by the `ev_*` + methods. The class is now generic to support type checking these values. +- Event mouse coordinates are now strictly int types. +- Submodules are now implicitly imported. + +## [11.11.4] - 2020-04-26 +### Changed +- Using `libtcod 1.16.0-alpha.10`. + +### Fixed +- Fixed characters being dropped when color codes were used. + +## [11.11.3] - 2020-04-24 +### Changed +- Using `libtcod 1.16.0-alpha.9`. + +### Fixed +- `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant. + [libtcod#48](https://github.com/libtcod/libtcod/pull/48) +- The `TCOD_VSYNC` environment variable was being ignored. + +## [11.11.2] - 2020-04-22 + +## [11.11.1] - 2020-04-03 +### Changed +- Using `libtcod 1.16.0-alpha.8`. + +### Fixed +- Changing the active tileset now redraws tiles correctly on the next frame. + +## [11.11.0] - 2020-04-02 +### Added +- Added `Console.close` as a more obvious way to close the active window of a + root console. + +### Changed +- GCC is no longer needed to compile the library on Windows. +- Using `libtcod 1.16.0-alpha.7`. +- `tcod.console_flush` will now accept an RGB tuple as a `clear_color`. + +### Fixed +- Changing the active tileset will now properly show it on the next render. + +## [11.10.0] - 2020-03-26 +### Added +- Added `tcod.tileset.load_bdf`, you can now load BDF fonts. +- `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable. + +### Changed +- Using `libtcod 1.16.0-alpha.6`. + +### Deprecated +- The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated + since it can cause minor scaling issues which don't exist when using + `integer_scaling` instead. + +## [11.9.2] - 2020-03-17 +### Fixed +- Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes + out of scope. + +## [11.9.1] - 2020-02-28 +### Changed +- Using `libtcod 1.16.0-alpha.5`. +- Mouse tile coordinates are now always zero before the first call to + `tcod.console_flush`. + +## [11.9.0] - 2020-02-22 +### Added +- New method `Tileset.render` renders an RGBA NumPy array from a tileset and + a console. + +## [11.8.2] - 2020-02-22 +### Fixed +- Prevent KeyError when representing unusual keyboard symbol constants. + +## [11.8.1] - 2020-02-22 +### Changed +- Using `libtcod 1.16.0-alpha.4`. + +### Fixed +- Mouse tile coordinates are now correct on any resized window. + +## [11.8.0] - 2020-02-21 +### Added +- Added `tcod.console.recommended_size` for when you want to change your main + console size at runtime. +- Added `Console.tiles_rgb` as a replacement for `Console.tiles2`. + +### Changed +- Using `libtcod 1.16.0-alpha.3`. +- Added parameters to `tcod.console_flush`, you can now manually provide a + console and adjust how it is presented. + +### Deprecated +- `Console.tiles2` is deprecated in favour of `Console.tiles_rgb`. +- `Console.buffer` is now deprecated in favour of `Console.tiles`, instead of + the other way around. + +### Fixed +- Fixed keyboard state and mouse state functions losing state when events were + flushed. + +## [11.7.2] - 2020-02-16 +### Fixed +- Fixed regression in `tcod.console_clear`. + +## [11.7.1] - 2020-02-16 +### Fixed +- Fixed regression in `Console.draw_frame`. +- The wavelet noise generator now excludes -1.0f and 1.0f as return values. +- Fixed console fading color regression. + +## [11.7.0] - 2020-02-14 +### Changed +- Using `libtcod 1.16.0-alpha.2`. +- When a renderer fails to load it will now fallback to a different one. + The order is: OPENGL2 -> OPENGL -> SDL2. +- The default renderer is now SDL2. +- The SDL and OPENGL renderers are no longer deprecated, but they now point to + slightly different backward compatible implementations. + +### Deprecated +- The use of `libtcod.cfg` and `terminal.png` is deprecated. + +### Fixed +- `tcod.sys_update_char` now works with the newer renderers. +- Fixed buffer overflow in name generator. +- `tcod.image_from_console` now works with the newer renderers. +- New renderers now auto-load fonts from `libtcod.cfg` or `terminal.png`. + +## [11.6.0] - 2019-12-05 +### Changed +- Console blit operations now perform per-cell alpha transparency. + +## [11.5.1] - 2019-11-23 +### Fixed +- Python 3.8 wheels failed to deploy. + +## [11.5.0] - 2019-11-22 +### Changed +- Quarter block elements are now rendered using Unicode instead of a custom + encoding. + +### Fixed +- `OPENGL` and `GLSL` renderers were not properly clearing space characters. + +## [11.4.1] - 2019-10-15 +### Added +- Uploaded Python 3.8 wheels to PyPI. + +## [11.4.0] - 2019-09-20 +### Added +- Added `__array_interface__` to the Image class. +- Added `Console.draw_semigraphics` as a replacement for blit_2x functions. + `draw_semigraphics` can handle array-like objects. +- `Image.from_array` class method creates an Image from an array-like object. +- `tcod.image.load` loads a PNG file as an RGBA array. + +### Changed +- `Console.tiles` is now named `Console.buffer`. + +## [11.3.0] - 2019-09-06 +### Added +- New attribute `Console.tiles2` is similar to `Console.tiles` but without an + alpha channel. + +## [11.2.2] - 2019-08-25 +### Fixed +- Fixed a regression preventing PyInstaller distributions from loading SDL2. + +## [11.2.1] - 2019-08-25 + +## [11.2.0] - 2019-08-24 +### Added +- `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state. +- `tcod.path.hillclimb2d`: Returns a path from a distance array. +- `tcod.path.maxarray`: Creates arrays filled with maximum finite values. + +### Fixed +- Changing the tiles of an active tileset on OPENGL2 will no longer leave + temporary artifact tiles. +- It's now harder to accidentally import tcod's internal modules. + +## [11.1.2] - 2019-08-02 +### Changed +- Now bundles SDL 2.0.10 for Windows/MacOS. + +### Fixed +- Can now parse SDL 2.0.10 headers during installation without crashing. + +## [11.1.1] - 2019-08-01 +### Deprecated +- Using an out-of-bounds index for field-of-view operations now raises a + warning, which will later become an error. + +### Fixed +- Changing the tiles of an active tileset will now work correctly. + +## [11.1.0] - 2019-07-05 +### Added +- You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to + force specific options to be used. + Example: ``TCOD_RENDERER=sdl2 TCOD_VSYNC=1`` + +### Changed +- `tcod.sys_set_renderer` now raises an exception if it fails. + +### Fixed +- `tcod.console_map_ascii_code_to_font` functions will now work when called + before `tcod.console_init_root`. + +## [11.0.2] - 2019-06-21 +### Changed +- You no longer need OpenGL to build python-tcod. + +## [11.0.1] - 2019-06-21 +### Changed +- Better runtime checks for Windows dependencies should now give distinct + errors depending on if the issue is SDL2 or missing redistributables. + +### Fixed +- Changed NumPy type hints from `np.array` to `np.ndarray` which should + resolve issues. + +## [11.0.0] - 2019-06-14 +### Changed +- `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and + `y` parameters. This causes less confusion over how axes are aligned. + +## [10.1.1] - 2019-06-02 +### Changed +- Better string representations for `tcod.event.Event` subclasses. + +### Fixed +- Fixed regressions in text alignment for non-rectangle print functions. + +## [10.1.0] - 2019-05-24 +### Added +- `tcod.console_init_root` now has an optional `vsync` parameter. + +## [10.0.5] - 2019-05-17 +### Fixed +- Fixed shader compilation issues in the OPENGL2 renderer. +- Fallback fonts should fail less on Linux. + +## [10.0.4] - 2019-05-17 +### Changed +- Now depends on cffi 0.12 or later. + +### Fixed +- `tcod.console_init_root` and `tcod.console_set_custom_font` will raise + exceptions instead of terminating. +- Fixed issues preventing `tcod.event` from working on 32-bit Windows. + +## [10.0.3] - 2019-05-10 +### Fixed +- Corrected bounding box issues with the `Console.print_box` method. + +## [10.0.2] - 2019-04-26 +### Fixed +- Resolved Color warnings when importing tcod. +- When compiling, fixed a name conflict with endianness macros on FreeBSD. + +## [10.0.1] - 2019-04-19 +### Fixed +- Fixed horizontal alignment for TrueType fonts. +- Fixed taking screenshots with the older SDL renderer. + +## [10.0.0] - 2019-03-29 +### Added +- New `Console.tiles` array attribute. +### Changed +- `Console.DTYPE` changed to add alpha to its color types. +### Fixed +- Console printing was ignoring color codes at the beginning of a string. + +## [9.3.0] - 2019-03-15 +### Added +- The SDL2/OPENGL2 renderers can potentially use a fall-back font when none + are provided. +- New function `tcod.event.get_mouse_state`. +- New function `tcod.map.compute_fov` lets you get a visibility array directly + from a transparency array. +### Deprecated +- The following functions and classes have been deprecated. + - `tcod.Key` + - `tcod.Mouse` + - `tcod.mouse_get_status` + - `tcod.console_is_window_closed` + - `tcod.console_check_for_keypress` + - `tcod.console_wait_for_keypress` + - `tcod.console_delete` + - `tcod.sys_check_for_event` + - `tcod.sys_wait_for_event` +- The SDL, OPENGL, and GLSL renderers have been deprecated. +- Many libtcodpy functions have been marked with PendingDeprecationWarning's. +### Fixed +- To be more compatible with libtcodpy `tcod.console_init_root` will default + to the SDL render, but will raise warnings when an old renderer is used. + +## [9.2.5] - 2019-03-04 +### Fixed +- Fixed `tcod.namegen_generate_custom`. + +## [9.2.4] - 2019-03-02 +### Fixed +- The `tcod` package is has been marked as typed and will now work with MyPy. + +## [9.2.3] - 2019-03-01 +### Deprecated +- The behavior for negative indexes on the new print functions may change in + the future. +- Methods and functionality preventing `tcod.Color` from behaving like a tuple + have been deprecated. + +## [9.2.2] - 2019-02-26 +### Fixed +- `Console.print_box` wasn't setting the background color by default. + +## [9.2.1] - 2019-02-25 +### Fixed +- `tcod.sys_get_char_size` fixed on the new renderers. + +## [9.2.0] - 2019-02-24 +### Added +- New `tcod.console.get_height_rect` function, which can be used to get the + height of a print call without an existing console. +- New `tcod.tileset` module, with a `set_truetype_font` function. +### Fixed +- The new print methods now handle alignment according to how they were + documented. +- `SDL2` and `OPENGL2` now support screenshots. +- Windows and MacOS builds now restrict exported SDL2 symbols to only + SDL 2.0.5; This will avoid hard to debug import errors when the wrong + version of SDL is dynamically linked. +- The root console now starts with a white foreground. + +## [9.1.0] - 2019-02-23 +### Added +- Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. +### Changed +- The overhead for warnings has been reduced when running Python with the + optimize `-O` flag. +- `tcod.random.Random` now provides a default algorithm. + +## [9.0.0] - 2019-02-17 +### Changed +- New console methods now default to an `fg` and `bg` of None instead of + white-on-black. + +## [8.5.0] - 2019-02-15 +### Added +- `tcod.console.Console` now supports `str` and `repr`. +- Added new Console methods which are independent from the console defaults. +- You can now give an array when initializing a `tcod.console.Console` + instance. +- `Console.clear` can now take `ch`, `fg`, and `bg` parameters. +### Changed +- Updated libtcod to 1.10.6 +- Printing generates more compact layouts. +### Deprecated +- Most libtcodpy console functions have been replaced by the tcod.console + module. +- Deprecated the `set_key_color` functions. You can pass key colors to + `Console.blit` instead. +- `Console.clear` should be given the colors to clear with as parameters, + rather than by using `default_fg` or `default_bg`. +- Most functions which depend on console default values have been deprecated. + The new deprecation warnings will give details on how to make default values + explicit. +### Fixed +- `tcod.console.Console.blit` was ignoring the key color set by + `Console.set_key_color`. +- The `SDL2` and `OPENGL2` renders can now large numbers of tiles. + +## [8.4.3] - 2019-02-06 +### Changed +- Updated libtcod to 1.10.5 +- The SDL2/OPENGL2 renderers will now auto-detect a custom fonts key-color. + +## [8.4.2] - 2019-02-05 +### Deprecated +- The tdl module has been deprecated. +- The libtcodpy parser functions have been deprecated. +### Fixed +- `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return + values. +- `Console.print_frame` was clearing tiles outside if its bounds. +- The `FONT_LAYOUT_CP437` layout was incorrect. + +## [8.4.1] - 2019-02-01 +### Fixed +- Window event types were not upper-case. +- Fixed regression where libtcodpy mouse wheel events unset mouse coordinates. + +## [8.4.0] - 2019-01-31 +### Added +- Added tcod.event module, based off of the sdlevent.py shim. +### Changed +- Updated libtcod to 1.10.3 +### Fixed +- Fixed libtcodpy `struct_add_value_list` function. +- Use correct math for tile-based delta in mouse events. +- New renderers now support tile-based mouse coordinates. +- SDL2 renderer will now properly refresh after the window is resized. + +## [8.3.2] - 2018-12-28 +### Fixed +- Fixed rare access violations for some functions which took strings as + parameters, such as `tcod.console_init_root`. + +## [8.3.1] - 2018-12-28 +### Fixed +- libtcodpy key and mouse functions will no longer accept the wrong types. +- The `new_struct` method was not being called for libtcodpy's custom parsers. + +## [8.3.0] - 2018-12-08 +### Added +- Added BSP traversal methods in tcod.bsp for parity with libtcodpy. +### Deprecated +- Already deprecated bsp functions are now even more deprecated. + +## [8.2.0] - 2018-11-27 +### Added +- New layout `tcod.FONT_LAYOUT_CP437`. +### Changed +- Updated libtcod to 1.10.2 +- `tcod.console_print_frame` and `Console.print_frame` now support Unicode + strings. +### Deprecated +- Deprecated using bytes strings for all printing functions. +### Fixed +- Console objects are now initialized with spaces. This fixes some blit + operations. +- Unicode code-points above U+FFFF will now work on all platforms. + +## [8.1.1] - 2018-11-16 +### Fixed +- Printing a frame with an empty string no longer displays a title bar. + +## [8.1.0] - 2018-11-15 +### Changed +- Heightmap functions now support 'F_CONTIGUOUS' arrays. +- `tcod.heightmap_new` now has an `order` parameter. +- Updated SDL to 2.0.9 +### Deprecated +- Deprecated heightmap functions which sample noise grids, this can be done + using the `Noise.sample_ogrid` method. + +## [8.0.0] - 2018-11-02 +### Changed +- The default renderer can now be anything if not set manually. +- Better error message for when a font file isn't found. + +## [7.0.1] - 2018-10-27 +### Fixed +- Building from source was failing because `console_2tris.glsl*` was missing + from source distributions. + +## [7.0.0] - 2018-10-25 +### Added +- New `RENDERER_SDL2` and `RENDERER_OPENGL2` renderers. +### Changed +- Updated libtcod to 1.9.0 +- Now requires SDL 2.0.5, which is not trivially installable on + Ubuntu 16.04 LTS. +### Removed +- Dropped support for Python versions before 3.5 +- Dropped support for MacOS versions before 10.9 Mavericks. + +## [6.0.7] - 2018-10-24 +### Fixed +- The root console no longer loses track of buffers and console defaults on a + renderer change. + +## [6.0.6] - 2018-10-01 +### Fixed +- Replaced missing wheels for older and 32-bit versions of MacOS. + +## [6.0.5] - 2018-09-28 +### Fixed +- Resolved CDefError error during source installs. + +## [6.0.4] - 2018-09-11 +### Fixed +- tcod.Key right-hand modifiers are now set independently at initialization, + instead of mirroring the left-hand modifier value. + +## [6.0.3] - 2018-09-05 +### Fixed +- tcod.Key and tcod.Mouse no longer ignore initiation parameters. + +## [6.0.2] - 2018-08-28 +### Fixed +- Fixed color constants missing at build-time. + +## [6.0.1] - 2018-08-24 +### Fixed +- Source distributions were missing C++ source files. + +## [6.0.0] - 2018-08-23 +### Changed +- Project renamed to tcod on PyPI. +### Deprecated +- Passing bytes strings to libtcodpy print functions is deprecated. +### Fixed +- Fixed libtcodpy print functions not accepting bytes strings. +- libtcod constants are now generated at build-time fixing static analysis + tools. + +## [5.0.1] - 2018-07-08 +### Fixed +- tdl.event no longer crashes with StopIteration on Python 3.7 + +## [5.0.0] - 2018-07-05 +### Changed +- tcod.path: all classes now use `shape` instead of `width` and `height`. +- tcod.path now respects NumPy array shape, instead of assuming that arrays + need to be transposed from C memory order. From now on `x` and `y` mean + 1st and 2nd axis. This doesn't affect non-NumPy code. +- tcod.path now has full support of non-contiguous memory. + +## [4.6.1] - 2018-06-30 +### Added +- New function `tcod.line_where` for indexing NumPy arrays using a Bresenham + line. +### Deprecated +- Python 2.7 support will be dropped in the near future. + +## [4.5.2] - 2018-06-29 +### Added +- New wheels for Python3.7 on Windows. +### Fixed +- Arrays from `tcod.heightmap_new` are now properly zeroed out. + +## [4.5.1] - 2018-06-23 +### Deprecated +- Deprecated all libtcodpy map functions. +### Fixed +- `tcod.map_copy` could break the `tcod.map.Map` class. +- `tcod.map_clear` `transparent` and `walkable` parameters were reversed. +- When multiple SDL2 headers were installed, the wrong ones would be used when + the library is built. +- Fails to build via pip unless Numpy is installed first. + +## [4.5.0] - 2018-06-12 +### Changed +- Updated libtcod to v1.7.0 +- Updated SDL to v2.0.8 +- Error messages when failing to create an SDL window should be a less vague. +- You no longer need to initialize libtcod before you can print to an + off-screen console. +### Fixed +- Avoid crashes if the root console has a character code higher than expected. +### Removed +- No more debug output when loading fonts. + +## [4.4.0] - 2018-05-02 +### Added +- Added the libtcodpy module as an alias for tcod. Actual use of it is + deprecated, it exists primarily for backward compatibility. +- Adding missing libtcodpy functions `console_has_mouse_focus` and + `console_is_active`. +### Changed +- Updated libtcod to v1.6.6 + +## [4.3.2] - 2018-03-18 +### Deprecated +- Deprecated the use of falsy console parameters with libtcodpy functions. +### Fixed +- Fixed libtcodpy image functions not supporting falsy console parameters. +- Fixed tdl `Window.get_char` method. (Kaczor2704) + +## [4.3.1] - 2018-03-07 +### Fixed +- Fixed cffi.api.FFIError "unsupported expression: expected a simple numeric + constant" error when building on platforms with an older cffi module and + newer SDL headers. +- tcod/tdl Map and Console objects were not saving stride data when pickled. + +## [4.3.0] - 2018-02-01 +### Added +- You can now set the numpy memory order on tcod.console.Console, + tcod.map.Map, and tdl.map.Map objects well as from the + tcod.console_init_root function. +### Changed +- The `console_init_root` `title` parameter is now optional. +### Fixed +- OpenGL renderer alpha blending is now consistent with all other render + modes. + +## [4.2.3] - 2018-01-06 +### Fixed +- Fixed setup.py regression that could prevent building outside of the git + repository. + +## [4.2.2] - 2018-01-06 +### Fixed +- The Windows dynamic linker will now prefer the bundled version of SDL. + This fixes: + "ImportError: DLL load failed: The specified procedure could not be found." +- `key.c` is no longer set when `key.vk == KEY_TEXT`, this fixes a regression + which was causing events to be heard twice in the libtcod/Python tutorial. + +## [4.2.0] - 2018-01-02 +### Changed +- Updated libtcod backend to v1.6.4 +- Updated SDL to v2.0.7 for Windows/MacOS. +### Removed +- Source distributions no longer include tests, examples, or fonts. + [Find these on GitHub.](https://github.com/libtcod/python-tcod) +### Fixed +- Fixed "final link failed: Nonrepresentable section on output" error + when compiling for Linux. +- `tcod.console_init_root` defaults to the SDL renderer, other renderers + cause issues with mouse movement events. + +## [4.1.1] - 2017-11-02 +### Fixed +- Fixed `ConsoleBuffer.blit` regression. +- Console defaults corrected, the root console's blend mode and alignment is + the default value for newly made Console's. +- You can give a byte string as a filename to load parsers. + +## [4.1.0] - 2017-07-19 +### Added +- tdl Map class can now be pickled. +### Changed +- Added protection to the `transparent`, `walkable`, and `fov` + attributes in tcod and tdl Map classes, to prevent them from being + accidentally overridden. +- tcod and tdl Map classes now use numpy arrays as their attributes. + +## [4.0.1] - 2017-07-12 +### Fixed +- tdl: Fixed NameError in `set_fps`. + +## [4.0.0] - 2017-07-08 +### Changed +- tcod.bsp: `BSP.split_recursive` parameter `random` is now `seed`. +- tcod.console: `Console.blit` parameters have been rearranged. + Most of the parameters are now optional. +- tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`. +- tdl: Changed `set_fps` parameter name to `fps`. +### Fixed +- tcod.bsp: Corrected spelling of max_vertical_ratio. + +## [3.2.0] - 2017-07-04 +### Changed +- Merged libtcod-cffi dependency with TDL. +### Fixed +- Fixed boolean related crashes with Key 'text' events. +- tdl.noise: Fixed crash when given a negative seed. As well as cases + where an instance could lose its seed being pickled. + +## [3.1.0] - 2017-05-28 +### Added +- You can now pass tdl Console instances as parameters to libtcod-cffi + functions expecting a tcod Console. +### Changed +- Dependencies updated: `libtcod-cffi>=2.5.0,<3` +- The `Console.tcod_console` attribute is being renamed to + `Console.console_c`. +### Deprecated +- The tdl.noise and tdl.map modules will be deprecated in the future. +### Fixed +- Resolved crash-on-exit issues for Windows platforms. + +## [3.0.2] - 2017-04-13 +### Changed +- Dependencies updated: `libtcod-cffi>=2.4.3,<3` +- You can now create Console instances before a call to `tdl.init`. +### Removed +- Dropped support for Python 3.3 +### Fixed +- Resolved issues with MacOS builds. +- 'OpenGL' and 'GLSL' renderers work again. + +## [3.0.1] - 2017-03-22 +### Changed +- `KeyEvent`'s with `text` now have all their modifier keys set to False. +### Fixed +- Undefined behavior in text events caused crashes on 32-bit builds. + +## [3.0.0] - 2017-03-21 +### Added +- `KeyEvent` supports libtcod text and meta keys. +### Changed +- `KeyEvent` parameters have been moved. +- This version requires `libtcod-cffi>=2.3.0`. +### Deprecated +- `KeyEvent` camel capped attribute names are deprecated. +### Fixed +- Crashes with key-codes undefined by libtcod. +- `tdl.map` typedef issues with libtcod-cffi. + + +## [2.0.1] - 2017-02-22 +### Fixed +- `tdl.init` renderer was defaulted to OpenGL which is not supported in the + current version of libtcod. + +## [2.0.0] - 2017-02-15 +### Changed +- Dependencies updated, tdl now requires libtcod-cffi 2.x.x +- Some event behaviors have changed with SDL2, event keys might be different + than what you expect. +### Removed +- Key repeat functions were removed from SDL2. + `set_key_repeat` is now stubbed, and does nothing. + +## [1.6.0] - 2016-11-18 +- Console.blit methods can now take fg_alpha and bg_alpha parameters. + +## [1.5.3] - 2016-06-04 +- set_font no longer crashes when loading a file without the implied font + size in its name + +## [1.5.2] - 2016-03-11 +- Fixed non-square Map instances + +## [1.5.1] - 2015-12-20 +- Fixed errors with Unicode and non-Unicode literals on Python 2 +- Fixed attribute error in compute_fov + +## [1.5.0] - 2015-07-13 +- python-tdl distributions are now universal builds +- New Map class +- map.bresenham now returns a list +- This release will require libtcod-cffi v0.2.3 or later + +## [1.4.0] - 2015-06-22 +- The DLL's have been moved into another library which you can find at + https://github.com/HexDecimal/libtcod-cffi + You can use this library to have some raw access to libtcod if you want. + Plus it can be used alongside TDL. +- The libtcod console objects in Console instances have been made public. +- Added tdl.event.wait function. This function can called with a timeout and + can automatically call tdl.flush. + +## [1.3.1] - 2015-06-19 +- Fixed pathfinding regressions. + +## [1.3.0] - 2015-06-19 +- Updated backend to use python-cffi instead of ctypes. This gives decent + boost to speed in CPython and a drastic to boost in speed in PyPy. + +## [1.2.0] - 2015-06-06 +- The set_colors method now changes the default colors used by the draw_* + methods. You can use Python's Ellipsis to explicitly select default colors + this way. +- Functions and Methods renamed to match Python's style-guide PEP 8, the old + function names still exist and are depreciated. +- The fgcolor and bgcolor parameters have been shortened to fg and bg. + +## [1.1.7] - 2015-03-19 +- Noise generator now seeds properly. +- The OS event queue will now be handled during a call to tdl.flush. This + prevents a common newbie programmer hang where events are handled + infrequently during long animations, simulations, or early development. +- Fixed a major bug that would cause a crash in later versions of Python 3 + +## [1.1.6] - 2014-06-27 +- Fixed a race condition when importing on some platforms. +- Fixed a type issue with quickFOV on Linux. +- Added a bresenham function to the tdl.map module. + +## [1.1.5] - 2013-11-10 +- A for loop can iterate over all coordinates of a Console. +- drawStr can be configured to scroll or raise an error. +- You can now configure or disable key repeating with tdl.event.setKeyRepeat +- Typewriter class removed, use a Window instance for the same functionality. +- setColors method fixed. + +## [1.1.4] - 2013-03-06 +- Merged the Typewriter and MetaConsole classes, + You now have a virtual cursor with Console and Window objects. +- Fixed the clear method on the Window class. +- Fixed screenshot function. +- Fixed some drawing operations with unchanging backgrounds. +- Instances of Console and Noise can be pickled and copied. +- Added KeyEvent.keychar +- Fixed event.keyWait, and now converts window closed events into Alt+F4. + +## [1.1.3] - 2012-12-17 +- Some of the setFont parameters were incorrectly labeled and documented. +- setFont can auto-detect tilesets if the font sizes are in the filenames. +- Added some X11 unicode tilesets, including Unifont. + +## [1.1.2] - 2012-12-13 +- Window title now defaults to the running scripts filename. +- Fixed incorrect deltaTime for App.update +- App will no longer call tdl.flush on its own, you'll need to call this + yourself. +- tdl.noise module added. +- clear method now defaults to black on black. + +## [1.1.1] - 2012-12-05 +- Map submodule added with AStar class and quickFOV function. +- New Typewriter class. +- Most console functions can use Python-style negative indexes now. +- New App.runOnce method. +- Rectangle geometry is less strict. + +## [1.1.0] - 2012-10-04 +- KeyEvent.keyname is now KeyEvent.key +- MouseButtonEvent.button now behaves like KeyEvent.keyname does. +- event.App class added. +- Drawing methods no longer have a default for the character parameter. +- KeyEvent.ctrl is now KeyEvent.control + +## [1.0.8] - 2010-04-07 +- No longer works in Python 2.5 but now works in 3.x and has been partly + tested. +- Many bug fixes. + +## [1.0.5] - 2010-04-06 +- Got rid of setuptools dependency, this will make it much more compatible + with Python 3.x +- Fixed a typo with the MacOS library import. + +## [1.0.4] - 2010-04-06 +- All constant colors (C_*) have been removed, they may be put back in later. +- Made some type assertion failures show the value they received to help in + general debugging. Still working on it. +- Added MacOS and 64-bit Linux support. + +## [1.0.0] - 2009-01-31 +- First public release. diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100755 index 6e5ed581..00000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,1339 +0,0 @@ -=========== - Changelog -=========== -Changes relevant to the users of python-tcod are documented here. - -This project adheres to `Semantic Versioning `_ since -v2.0.0 - -Unreleased ------------------- -Added - - New experimental renderer `tcod.context.RENDERER_XTERM`. - -Fixed - - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. - - BDF files with blank lines no longer fail to load with an "Unknown keyword" error. - -13.2.0 - 2021-12-24 -------------------- -Added - - New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. - -Changed - - Using `libtcod 1.20.0`. - -Fixed - - Fixed segfault when an OpenGL2 context fails to load. - - Gaussian number generation no longer affects the results of unrelated RNG's. - - Gaussian number generation is now reentrant and thread-safe. - - Fixed potential crash in PNG image loading. - -13.1.0 - 2021-10-22 -------------------- -Added - - Added the `tcod.tileset.procedural_block_elements` function. - -Removed - - Python 3.6 is no longer supported. - -13.0.0 - 2021-09-20 -------------------- -Changed - - Console print and drawing functions now always use absolute coordinates for negative numbers. - -12.7.3 - 2021-08-13 -------------------- -Deprecated - - `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`. - - `tcod.console_from_file` is deprecated. - - The `.asc` and `.apf` formats are no longer actively supported. - -Fixed - - Fixed the parsing of SDL 2.0.16 headers. - -12.7.2 - 2021-07-01 -------------------- -Fixed - - *Scancode* and *KeySym* enums no longer crash when SDL returns an unexpected value. - -12.7.1 - 2021-06-30 -------------------- -Added - - Started uploading wheels for ARM64 macOS. - -12.7.0 - 2021-06-29 -------------------- -Added - - *tcod.image* and *tcod.tileset* now support *pathlib*. - -Fixed - - Wheels for 32-bit Windows now deploy again. - -12.6.2 - 2021-06-15 -------------------- -Fixed - - Git is no longer required to install from source. - -12.6.1 - 2021-06-09 -------------------- -Fixed - - Fixed version mismatch when building from sources. - -12.6.0 - 2021-06-09 -------------------- -Added - - Added the *decoration* parameter to *Console.draw_frame*. - You may use this parameter to designate custom glyphs as the frame border. - -Deprecated - - The handling of negative indexes given to console drawing and printing - functions will be changed to be used as absolute coordinates in the future. - -12.5.1 - 2021-05-30 -------------------- -Fixed - - The setup script should no longer fail silently when cffi is unavailable. - -12.5.0 - 2021-05-21 -------------------- -Changed - - `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums. - -12.4.0 - 2021-05-21 -------------------- -Added - - Added modernized REXPaint saving/loading functions. - - `tcod.console.load_xp` - - `tcod.console.save_xp` - -Changed - - Using `libtcod 1.18.1`. - - `tcod.event.KeySym` and `tcod.event.Scancode` can now be hashed. - -12.3.2 - 2021-05-15 -------------------- -Changed - - Using `libtcod 1.17.1`. - -Fixed - - Fixed regression with loading PNG images. - -12.3.1 - 2021-05-13 -------------------- -Fixed - - Fix Windows deployment. - -12.3.0 - 2021-05-13 -------------------- -Added - - New keyboard enums: - - `tcod.event.KeySym` - - `tcod.event.Scancode` - - `tcod.event.Modifier` - - New functions: - - `tcod.event.get_keyboard_state` - - `tcod.event.get_modifier_state` - - Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. - - Another name for the Console array attributes: `Console.rgb` and `Console.rgba`. - -Changed - - Using `libtcod 1.17.0`. - -Deprecated - - `Console_tiles_rgb` is being renamed to `Console.rgb`. - - `Console_tiles` being renamed to `Console.rgba`. - -Fixed - - Contexts now give a more useful error when pickled. - - Fixed regressions with `tcod.console_print_frame` and `Console.print_frame` - when given empty strings as the banner. - -12.2.0 - 2021-04-09 -------------------- -Added - - Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. - - Added `tcod.noise.grid` helper function. - -Deprecated - - The non-enum noise implementation names have been deprecated. - -Fixed - - Indexing Noise classes now works with the FBM implementation. - -12.1.0 - 2021-04-01 -------------------- -Added - - Added package-level PyInstaller hook. - -Changed - - Using `libtcod 1.16.7`. - - `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter. - -Deprecated - - In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given. - -Fixed - - Fixed crashes from loading tilesets with non-square tile sizes. - - Tilesets with a size of 0 should no longer crash when used. - - Prevent division by zero from recommended-console-size functions. - -12.0.0 - 2021-03-05 -------------------- -Deprecated - - The Random class will now warn if the seed it's given will not used - deterministically. It will no longer accept non-integer seeds in the future. - -Changed - - Now bundles SDL 2.0.14 for MacOS. - - `tcod.event` can now detect and will warn about uninitialized tile - attributes on mouse events. - -Removed - - Python 3.5 is no longer supported. - - The `tdl` module has been dropped. - -11.19.3 - 2021-01-07 --------------------- -Fixed - - Some wheels had broken version metadata. - -11.19.2 - 2020-12-30 --------------------- -Changed - - Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows. - -Fixed - - MacOS wheels were failing to bundle dependencies for SDL2. - -11.19.1 - 2020-12-29 --------------------- -Fixed - - MacOS wheels failed to deploy for the previous version. - -11.19.0 - 2020-12-29 --------------------- -Added - - Added the important `order` parameter to `Context.new_console`. - -11.18.3 - 2020-12-28 --------------------- -Changed - - Now bundles SDL 2.0.14 for Windows/MacOS. - -Deprecated - - Support for Python 3.5 will be dropped. - - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load - these files without modifying an existing console. - -Fixed - - `tcod.console_from_xp` now has better error handling (instead of crashing.) - - Can now compile with SDL 2.0.14 headers. - -11.18.2 - 2020-12-03 --------------------- -Fixed - - Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. - - Fixed regression in `tcod.sys_get_current_resolution` behavior. This - function now returns the monitor resolution as was previously expected. - -11.18.1 - 2020-11-30 --------------------- -Fixed - - Code points from the Private Use Area will now print correctly. - -11.18.0 - 2020-11-13 --------------------- -Added - - New context method `Context.new_console`. - -Changed - - Using `libtcod 1.16.0-alpha.15`. - -11.17.0 - 2020-10-30 --------------------- -Added - - New FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. - -Changed - - Using `libtcod 1.16.0-alpha.14`. - -11.16.1 - 2020-10-28 --------------------- -Deprecated - - Changed context deprecations to PendingDeprecationWarning to reduce mass - panic from tutorial followers. - -Fixed - - Fixed garbled titles and crashing on some platforms. - -11.16.0 - 2020-10-23 --------------------- -Added - - Added `tcod.context.new` function. - - Contexts now support a CLI. - - You can now provide the window x,y position when making contexts. - - `tcod.noise.Noise` instances can now be indexed to generate noise maps. - -Changed - - Using `libtcod 1.16.0-alpha.13`. - - The OpenGL 2 renderer can now use `SDL_HINT_RENDER_SCALE_QUALITY` to - determine the tileset upscaling filter. - - Improved performance of the FOV_BASIC algorithm. - -Deprecated - - `tcod.context.new_window` and `tcod.context.new_terminal` have been replaced - by `tcod.context.new`. - -Fixed - - Pathfinders will now work with boolean arrays. - - Console blits now ignore alpha compositing which would result in division by - zero. - - `tcod.console_is_key_pressed` should work even if libtcod events are ignored. - - The `TCOD_RENDERER` and `TCOD_VSYNC` environment variables should work now. - - `FOV_PERMISSIVE` algorithm is now reentrant. - -11.15.3 - 2020-07-30 --------------------- -Fixed - - `tcod.tileset.Tileset.remap`, codepoint and index were swapped. - -11.15.2 - 2020-07-27 --------------------- -Fixed - - `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays. - -11.15.1 - 2020-07-26 --------------------- -Changed - - `tcod.event.EventDispatch` now uses the absolute names for event type hints - so that IDE's can better auto-complete method overrides. - -Fixed - - Fixed libtcodpy heightmap data alignment issues on non-square maps. - -11.15.0 - 2020-06-29 --------------------- -Added - - `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays. - -Changed - - `tcod.path.CustomGraph` now accepts an `order` parameter. - -11.14.0 - 2020-06-23 --------------------- -Added - - New `tcod.los` module for NumPy-based line-of-sight algorithms. - Includes `tcod.los.bresenham`. - -Deprecated - - `tcod.line_where` and `tcod.line_iter` have been deprecated. - -11.13.6 - 2020-06-19 --------------------- -Deprecated - - `console_init_root` and `console_set_custom_font` have been replaced by the - modern API. - - All functions which handle SDL windows without a context are deprecated. - - All functions which modify a globally active tileset are deprecated. - - `tcod.map.Map` is deprecated, NumPy arrays should be passed to functions - directly instead of through this class. - -11.13.5 - 2020-06-15 --------------------- -Fixed - - Install requirements will no longer try to downgrade `cffi`. - -11.13.4 - 2020-06-15 --------------------- - -11.13.3 - 2020-06-13 --------------------- -Fixed - - `cffi` requirement has been updated to version `1.13.0`. - The older versions raise TypeError's. - -11.13.2 - 2020-06-12 --------------------- -Fixed - - SDL related errors during package installation are now more readable. - -11.13.1 - 2020-05-30 --------------------- -Fixed - - `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return - types. - -11.13.0 - 2020-05-22 --------------------- -Added - - `tcod.path`: New `Pathfinder` and `CustomGraph` classes. - -Changed - - Added `edge_map` parameter to `tcod.path.dijkstra2d` and - `tcod.path.hillclimb2d`. - -Fixed - - tcod.console_init_root` and context initializing functions were not - raising exceptions on failure. - -11.12.1 - 2020-05-02 --------------------- -Fixed - - Prevent adding non-existent 2nd halves to potential double-wide charterers. - -11.12.0 - 2020-04-30 --------------------- -Added - - Added `tcod.context` module. You now have more options for making libtcod - controlled contexts. - - `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset. - - `Tileset.remap`: Reassign codepoints to tiles on a Tileset. - - `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`. - - `tcod.tileset.CHARMAP_TCOD`: Older libtcod layout. - -Changed - - `EventDispatch.dispatch` can now return the values returned by the `ev_*` - methods. The class is now generic to support type checking these values. - - Event mouse coordinates are now strictly int types. - - Submodules are now implicitly imported. - -11.11.4 - 2020-04-26 --------------------- -Changed - - Using `libtcod 1.16.0-alpha.10`. - -Fixed - - Fixed characters being dropped when color codes were used. - -11.11.3 - 2020-04-24 --------------------- -Changed - - Using `libtcod 1.16.0-alpha.9`. - -Fixed - - `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant. - `libtcod#48 `_ - - The `TCOD_VSYNC` environment variable was being ignored. - -11.11.2 - 2020-04-22 --------------------- - -11.11.1 - 2020-04-03 --------------------- -Changed - - Using `libtcod 1.16.0-alpha.8`. - -Fixed - - Changing the active tileset now redraws tiles correctly on the next frame. - -11.11.0 - 2020-04-02 --------------------- -Added - - Added `Console.close` as a more obvious way to close the active window of a - root console. - -Changed - - GCC is no longer needed to compile the library on Windows. - - Using `libtcod 1.16.0-alpha.7`. - - `tcod.console_flush` will now accept an RGB tuple as a `clear_color`. - -Fixed - - Changing the active tileset will now properly show it on the next render. - -11.10.0 - 2020-03-26 --------------------- -Added - - Added `tcod.tileset.load_bdf`, you can now load BDF fonts. - - `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable. - -Changed - - Using `libtcod 1.16.0-alpha.6`. - -Deprecated - - The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated - since it can cause minor scaling issues which don't exist when using - `integer_scaling` instead. - -11.9.2 - 2020-03-17 -------------------- -Fixed - - Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes - out of scope. - -11.9.1 - 2020-02-28 -------------------- -Changed - - Using `libtcod 1.16.0-alpha.5`. - - Mouse tile coordinates are now always zero before the first call to - `tcod.console_flush`. - -11.9.0 - 2020-02-22 -------------------- -Added - - New method `Tileset.render` renders an RGBA NumPy array from a tileset and - a console. - -11.8.2 - 2020-02-22 -------------------- -Fixed - - Prevent KeyError when representing unusual keyboard symbol constants. - -11.8.1 - 2020-02-22 -------------------- -Changed - - Using `libtcod 1.16.0-alpha.4`. - -Fixed - - Mouse tile coordinates are now correct on any resized window. - -11.8.0 - 2020-02-21 -------------------- -Added - - Added `tcod.console.recommended_size` for when you want to change your main - console size at runtime. - - Added `Console.tiles_rgb` as a replacement for `Console.tiles2`. - -Changed - - Using `libtcod 1.16.0-alpha.3`. - - Added parameters to `tcod.console_flush`, you can now manually provide a - console and adjust how it is presented. - -Deprecated - - `Console.tiles2` is deprecated in favour of `Console.tiles_rgb`. - - `Console.buffer` is now deprecated in favour of `Console.tiles`, instead of - the other way around. - -Fixed - - Fixed keyboard state and mouse state functions losing state when events were - flushed. - -11.7.2 - 2020-02-16 -------------------- -Fixed - - Fixed regression in `tcod.console_clear`. - -11.7.1 - 2020-02-16 -------------------- -Fixed - - Fixed regression in `Console.draw_frame`. - - The wavelet noise generator now excludes -1.0f and 1.0f as return values. - - Fixed console fading color regression. - -11.7.0 - 2020-02-14 -------------------- -Changed - - Using `libtcod 1.16.0-alpha.2`. - - When a renderer fails to load it will now fallback to a different one. - The order is: OPENGL2 -> OPENGL -> SDL2. - - The default renderer is now SDL2. - - The SDL and OPENGL renderers are no longer deprecated, but they now point to - slightly different backward compatible implementations. - -Deprecated - - The use of `libtcod.cfg` and `terminal.png` is deprecated. - -Fixed - - `tcod.sys_update_char` now works with the newer renderers. - - Fixed buffer overflow in name generator. - - `tcod.image_from_console` now works with the newer renderers. - - New renderers now auto-load fonts from `libtcod.cfg` or `terminal.png`. - -11.6.0 - 2019-12-05 -------------------- -Changed - - Console blit operations now perform per-cell alpha transparency. - -11.5.1 - 2019-11-23 -------------------- -Fixed - - Python 3.8 wheels failed to deploy. - -11.5.0 - 2019-11-22 -------------------- -Changed - - Quarter block elements are now rendered using Unicode instead of a custom - encoding. - -Fixed - - `OPENGL` and `GLSL` renderers were not properly clearing space characters. - -11.4.1 - 2019-10-15 -------------------- -Added - - Uploaded Python 3.8 wheels to PyPI. - -11.4.0 - 2019-09-20 -------------------- -Added - - Added `__array_interface__` to the Image class. - - Added `Console.draw_semigraphics` as a replacement for blit_2x functions. - `draw_semigraphics` can handle array-like objects. - - `Image.from_array` class method creates an Image from an array-like object. - - `tcod.image.load` loads a PNG file as an RGBA array. - -Changed - - `Console.tiles` is now named `Console.buffer`. - -11.3.0 - 2019-09-06 -------------------- -Added - - New attribute `Console.tiles2` is similar to `Console.tiles` but without an - alpha channel. - -11.2.2 - 2019-08-25 -------------------- -Fixed - - Fixed a regression preventing PyInstaller distributions from loading SDL2. - -11.2.1 - 2019-08-25 -------------------- - -11.2.0 - 2019-08-24 -------------------- -Added - - `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state. - - `tcod.path.hillclimb2d`: Returns a path from a distance array. - - `tcod.path.maxarray`: Creates arrays filled with maximum finite values. - -Fixed - - Changing the tiles of an active tileset on OPENGL2 will no longer leave - temporary artifact tiles. - - It's now harder to accidentally import tcod's internal modules. - -11.1.2 - 2019-08-02 -------------------- -Changed - - Now bundles SDL 2.0.10 for Windows/MacOS. - -Fixed - - Can now parse SDL 2.0.10 headers during installation without crashing. - -11.1.1 - 2019-08-01 -------------------- -Deprecated - - Using an out-of-bounds index for field-of-view operations now raises a - warning, which will later become an error. - -Fixed - - Changing the tiles of an active tileset will now work correctly. - -11.1.0 - 2019-07-05 -------------------- -Added - - You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to - force specific options to be used. - Example: ``TCOD_RENDERER=sdl2 TCOD_VSYNC=1`` - -Changed - - `tcod.sys_set_renderer` now raises an exception if it fails. - -Fixed - - `tcod.console_map_ascii_code_to_font` functions will now work when called - before `tcod.console_init_root`. - -11.0.2 - 2019-06-21 -------------------- -Changed - - You no longer need OpenGL to build python-tcod. - -11.0.1 - 2019-06-21 -------------------- -Changed - - Better runtime checks for Windows dependencies should now give distinct - errors depending on if the issue is SDL2 or missing redistributables. - -Fixed - - Changed NumPy type hints from `np.array` to `np.ndarray` which should - resolve issues. - -11.0.0 - 2019-06-14 -------------------- -Changed - - `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and - `y` parameters. This causes less confusion over how axes are aligned. - -10.1.1 - 2019-06-02 -------------------- -Changed - - Better string representations for `tcod.event.Event` subclasses. - -Fixed - - Fixed regressions in text alignment for non-rectangle print functions. - -10.1.0 - 2019-05-24 -------------------- -Added - - `tcod.console_init_root` now has an optional `vsync` parameter. - -10.0.5 - 2019-05-17 -------------------- -Fixed - - Fixed shader compilation issues in the OPENGL2 renderer. - - Fallback fonts should fail less on Linux. - -10.0.4 - 2019-05-17 -------------------- -Changed - - Now depends on cffi 0.12 or later. - -Fixed - - `tcod.console_init_root` and `tcod.console_set_custom_font` will raise - exceptions instead of terminating. - - Fixed issues preventing `tcod.event` from working on 32-bit Windows. - -10.0.3 - 2019-05-10 -------------------- -Fixed - - Corrected bounding box issues with the `Console.print_box` method. - -10.0.2 - 2019-04-26 -------------------- -Fixed - - Resolved Color warnings when importing tcod. - - When compiling, fixed a name conflict with endianness macros on FreeBSD. - -10.0.1 - 2019-04-19 -------------------- -Fixed - - Fixed horizontal alignment for TrueType fonts. - - Fixed taking screenshots with the older SDL renderer. - -10.0.0 - 2019-03-29 -------------------- -Added - - New `Console.tiles` array attribute. -Changed - - `Console.DTYPE` changed to add alpha to its color types. -Fixed - - Console printing was ignoring color codes at the beginning of a string. - -9.3.0 - 2019-03-15 ------------------- -Added - - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none - are provided. - - New function `tcod.event.get_mouse_state`. - - New function `tcod.map.compute_fov` lets you get a visibility array directly - from a transparency array. -Deprecated - - The following functions and classes have been deprecated. - - `tcod.Key` - - `tcod.Mouse` - - `tcod.mouse_get_status` - - `tcod.console_is_window_closed` - - `tcod.console_check_for_keypress` - - `tcod.console_wait_for_keypress` - - `tcod.console_delete` - - `tcod.sys_check_for_event` - - `tcod.sys_wait_for_event` - - The SDL, OPENGL, and GLSL renderers have been deprecated. - - Many libtcodpy functions have been marked with PendingDeprecationWarning's. -Fixed - - To be more compatible with libtcodpy `tcod.console_init_root` will default - to the SDL render, but will raise warnings when an old renderer is used. - -9.2.5 - 2019-03-04 ------------------- -Fixed - - Fixed `tcod.namegen_generate_custom`. - -9.2.4 - 2019-03-02 ------------------- -Fixed - - The `tcod` package is has been marked as typed and will now work with MyPy. - -9.2.3 - 2019-03-01 ------------------- -Deprecated - - The behavior for negative indexes on the new print functions may change in - the future. - - Methods and functionality preventing `tcod.Color` from behaving like a tuple - have been deprecated. - -9.2.2 - 2019-02-26 ------------------- -Fixed - - `Console.print_box` wasn't setting the background color by default. - -9.2.1 - 2019-02-25 ------------------- -Fixed - - `tcod.sys_get_char_size` fixed on the new renderers. - -9.2.0 - 2019-02-24 ------------------- -Added - - New `tcod.console.get_height_rect` function, which can be used to get the - height of a print call without an existing console. - - New `tcod.tileset` module, with a `set_truetype_font` function. -Fixed - - The new print methods now handle alignment according to how they were - documented. - - `SDL2` and `OPENGL2` now support screenshots. - - Windows and MacOS builds now restrict exported SDL2 symbols to only - SDL 2.0.5; This will avoid hard to debug import errors when the wrong - version of SDL is dynamically linked. - - The root console now starts with a white foreground. - -9.1.0 - 2019-02-23 ------------------- -Added - - Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. -Changed - - The overhead for warnings has been reduced when running Python with the - optimize `-O` flag. - - `tcod.random.Random` now provides a default algorithm. - -9.0.0 - 2019-02-17 ------------------- -Changed - - New console methods now default to an `fg` and `bg` of None instead of - white-on-black. - -8.5.0 - 2019-02-15 ------------------- -Added - - `tcod.console.Console` now supports `str` and `repr`. - - Added new Console methods which are independent from the console defaults. - - You can now give an array when initializing a `tcod.console.Console` - instance. - - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. -Changed - - Updated libtcod to 1.10.6 - - Printing generates more compact layouts. -Deprecated - - Most libtcodpy console functions have been replaced by the tcod.console - module. - - Deprecated the `set_key_color` functions. You can pass key colors to - `Console.blit` instead. - - `Console.clear` should be given the colors to clear with as parameters, - rather than by using `default_fg` or `default_bg`. - - Most functions which depend on console default values have been deprecated. - The new deprecation warnings will give details on how to make default values - explicit. -Fixed - - `tcod.console.Console.blit` was ignoring the key color set by - `Console.set_key_color`. - - The `SDL2` and `OPENGL2` renders can now large numbers of tiles. - -8.4.3 - 2019-02-06 ------------------- -Changed - - Updated libtcod to 1.10.5 - - The SDL2/OPENGL2 renderers will now auto-detect a custom fonts key-color. - -8.4.2 - 2019-02-05 ------------------- -Deprecated - - The tdl module has been deprecated. - - The libtcodpy parser functions have been deprecated. -Fixed - - `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return - values. - - `Console.print_frame` was clearing tiles outside if its bounds. - - The `FONT_LAYOUT_CP437` layout was incorrect. - -8.4.1 - 2019-02-01 ------------------- -Fixed - - Window event types were not upper-case. - - Fixed regression where libtcodpy mouse wheel events unset mouse coordinates. - -8.4.0 - 2019-01-31 ------------------- -Added - - Added tcod.event module, based off of the sdlevent.py shim. -Changed - - Updated libtcod to 1.10.3 -Fixed - - Fixed libtcodpy `struct_add_value_list` function. - - Use correct math for tile-based delta in mouse events. - - New renderers now support tile-based mouse coordinates. - - SDL2 renderer will now properly refresh after the window is resized. - -8.3.2 - 2018-12-28 ------------------- -Fixed - - Fixed rare access violations for some functions which took strings as - parameters, such as `tcod.console_init_root`. - -8.3.1 - 2018-12-28 ------------------- -Fixed - - libtcodpy key and mouse functions will no longer accept the wrong types. - - The `new_struct` method was not being called for libtcodpy's custom parsers. - -8.3.0 - 2018-12-08 ------------------- -Added - - Added BSP traversal methods in tcod.bsp for parity with libtcodpy. -Deprecated - - Already deprecated bsp functions are now even more deprecated. - -8.2.0 - 2018-11-27 ------------------- -Added - - New layout `tcod.FONT_LAYOUT_CP437`. -Changed - - Updated libtcod to 1.10.2 - - `tcod.console_print_frame` and `Console.print_frame` now support Unicode - strings. -Deprecated - - Deprecated using bytes strings for all printing functions. -Fixed - - Console objects are now initialized with spaces. This fixes some blit - operations. - - Unicode code-points above U+FFFF will now work on all platforms. - -8.1.1 - 2018-11-16 ------------------- -Fixed - - Printing a frame with an empty string no longer displays a title bar. - -8.1.0 - 2018-11-15 ------------------- -Changed - - Heightmap functions now support 'F_CONTIGUOUS' arrays. - - `tcod.heightmap_new` now has an `order` parameter. - - Updated SDL to 2.0.9 -Deprecated - - Deprecated heightmap functions which sample noise grids, this can be done - using the `Noise.sample_ogrid` method. - -8.0.0 - 2018-11-02 ------------------- -Changed - - The default renderer can now be anything if not set manually. - - Better error message for when a font file isn't found. - -7.0.1 - 2018-10-27 ------------------- -Fixed - - Building from source was failing because `console_2tris.glsl*` was missing - from source distributions. - -7.0.0 - 2018-10-25 ------------------- -Added - - New `RENDERER_SDL2` and `RENDERER_OPENGL2` renderers. -Changed - - Updated libtcod to 1.9.0 - - Now requires SDL 2.0.5, which is not trivially installable on - Ubuntu 16.04 LTS. -Removed - - Dropped support for Python versions before 3.5 - - Dropped support for MacOS versions before 10.9 Mavericks. - -6.0.7 - 2018-10-24 ------------------- -Fixed - - The root console no longer loses track of buffers and console defaults on a - renderer change. - -6.0.6 - 2018-10-01 ------------------- -Fixed - - Replaced missing wheels for older and 32-bit versions of MacOS. - -6.0.5 - 2018-09-28 ------------------- -Fixed - - Resolved CDefError error during source installs. - -6.0.4 - 2018-09-11 ------------------- -Fixed - - tcod.Key right-hand modifiers are now set independently at initialization, - instead of mirroring the left-hand modifier value. - -6.0.3 - 2018-09-05 ------------------- -Fixed - - tcod.Key and tcod.Mouse no longer ignore initiation parameters. - -6.0.2 - 2018-08-28 ------------------- -Fixed - - Fixed color constants missing at build-time. - -6.0.1 - 2018-08-24 ------------------- -Fixed - - Source distributions were missing C++ source files. - -6.0.0 - 2018-08-23 ------------------- -Changed - - Project renamed to tcod on PyPI. -Deprecated - - Passing bytes strings to libtcodpy print functions is deprecated. -Fixed - - Fixed libtcodpy print functions not accepting bytes strings. - - libtcod constants are now generated at build-time fixing static analysis - tools. - -5.0.1 - 2018-07-08 ------------------- -Fixed - - tdl.event no longer crashes with StopIteration on Python 3.7 - -5.0.0 - 2018-07-05 ------------------- -Changed - - tcod.path: all classes now use `shape` instead of `width` and `height`. - - tcod.path now respects NumPy array shape, instead of assuming that arrays - need to be transposed from C memory order. From now on `x` and `y` mean - 1st and 2nd axis. This doesn't affect non-NumPy code. - - tcod.path now has full support of non-contiguous memory. - -4.6.1 - 2018-06-30 ------------------- -Added - - New function `tcod.line_where` for indexing NumPy arrays using a Bresenham - line. -Deprecated - - Python 2.7 support will be dropped in the near future. - -4.5.2 - 2018-06-29 ------------------- -Added - - New wheels for Python3.7 on Windows. -Fixed - - Arrays from `tcod.heightmap_new` are now properly zeroed out. - -4.5.1 - 2018-06-23 ------------------- -Deprecated - - Deprecated all libtcodpy map functions. -Fixed - - `tcod.map_copy` could break the `tcod.map.Map` class. - - `tcod.map_clear` `transparent` and `walkable` parameters were reversed. - - When multiple SDL2 headers were installed, the wrong ones would be used when - the library is built. - - Fails to build via pip unless Numpy is installed first. - -4.5.0 - 2018-06-12 ------------------- -Changed - - Updated libtcod to v1.7.0 - - Updated SDL to v2.0.8 - - Error messages when failing to create an SDL window should be a less vague. - - You no longer need to initialize libtcod before you can print to an - off-screen console. -Fixed - - Avoid crashes if the root console has a character code higher than expected. -Removed - - No more debug output when loading fonts. - -4.4.0 - 2018-05-02 ------------------- -Added - - Added the libtcodpy module as an alias for tcod. Actual use of it is - deprecated, it exists primarily for backward compatibility. - - Adding missing libtcodpy functions `console_has_mouse_focus` and - `console_is_active`. -Changed - - Updated libtcod to v1.6.6 - -4.3.2 - 2018-03-18 ------------------- -Deprecated - - Deprecated the use of falsy console parameters with libtcodpy functions. -Fixed - - Fixed libtcodpy image functions not supporting falsy console parameters. - - Fixed tdl `Window.get_char` method. (Kaczor2704) - -4.3.1 - 2018-03-07 ------------------- -Fixed - - Fixed cffi.api.FFIError "unsupported expression: expected a simple numeric - constant" error when building on platforms with an older cffi module and - newer SDL headers. - - tcod/tdl Map and Console objects were not saving stride data when pickled. - -4.3.0 - 2018-02-01 ------------------- -Added - - You can now set the numpy memory order on tcod.console.Console, - tcod.map.Map, and tdl.map.Map objects well as from the - tcod.console_init_root function. -Changed - - The `console_init_root` `title` parameter is now optional. -Fixed - - OpenGL renderer alpha blending is now consistent with all other render - modes. - -4.2.3 - 2018-01-06 ------------------- -Fixed - - Fixed setup.py regression that could prevent building outside of the git - repository. - -4.2.2 - 2018-01-06 ------------------- -Fixed - - The Windows dynamic linker will now prefer the bundled version of SDL. - This fixes: - "ImportError: DLL load failed: The specified procedure could not be found." - - `key.c` is no longer set when `key.vk == KEY_TEXT`, this fixes a regression - which was causing events to be heard twice in the libtcod/Python tutorial. - -4.2.0 - 2018-01-02 ------------------- -Changed - - Updated libtcod backend to v1.6.4 - - Updated SDL to v2.0.7 for Windows/MacOS. -Removed - - Source distributions no longer include tests, examples, or fonts. - `Find these on GitHub. `_ -Fixed - - Fixed "final link failed: Nonrepresentable section on output" error - when compiling for Linux. - - `tcod.console_init_root` defaults to the SDL renderer, other renderers - cause issues with mouse movement events. - -4.1.1 - 2017-11-02 ------------------- -Fixed - - Fixed `ConsoleBuffer.blit` regression. - - Console defaults corrected, the root console's blend mode and alignment is - the default value for newly made Console's. - - You can give a byte string as a filename to load parsers. - -4.1.0 - 2017-07-19 ------------------- -Added - - tdl Map class can now be pickled. -Changed - - Added protection to the `transparent`, `walkable`, and `fov` - attributes in tcod and tdl Map classes, to prevent them from being - accidentally overridden. - - tcod and tdl Map classes now use numpy arrays as their attributes. - -4.0.1 - 2017-07-12 ------------------- -Fixed - - tdl: Fixed NameError in `set_fps`. - -4.0.0 - 2017-07-08 ------------------- -Changed - - tcod.bsp: `BSP.split_recursive` parameter `random` is now `seed`. - - tcod.console: `Console.blit` parameters have been rearranged. - Most of the parameters are now optional. - - tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`. - - tdl: Changed `set_fps` parameter name to `fps`. -Fixed - - tcod.bsp: Corrected spelling of max_vertical_ratio. - -3.2.0 - 2017-07-04 ------------------- -Changed - - Merged libtcod-cffi dependency with TDL. -Fixed - - Fixed boolean related crashes with Key 'text' events. - - tdl.noise: Fixed crash when given a negative seed. As well as cases - where an instance could lose its seed being pickled. - -3.1.0 - 2017-05-28 ------------------- -Added - - You can now pass tdl Console instances as parameters to libtcod-cffi - functions expecting a tcod Console. -Changed - - Dependencies updated: `libtcod-cffi>=2.5.0,<3` - - The `Console.tcod_console` attribute is being renamed to - `Console.console_c`. -Deprecated - - The tdl.noise and tdl.map modules will be deprecated in the future. -Fixed - - Resolved crash-on-exit issues for Windows platforms. - -3.0.2 - 2017-04-13 ------------------- -Changed - - Dependencies updated: `libtcod-cffi>=2.4.3,<3` - - You can now create Console instances before a call to `tdl.init`. -Removed - - Dropped support for Python 3.3 -Fixed - - Resolved issues with MacOS builds. - - 'OpenGL' and 'GLSL' renderers work again. - -3.0.1 - 2017-03-22 ------------------- -Changed - - `KeyEvent`'s with `text` now have all their modifier keys set to False. -Fixed - - Undefined behavior in text events caused crashes on 32-bit builds. - -3.0.0 - 2017-03-21 ------------------- -Added - - `KeyEvent` supports libtcod text and meta keys. -Changed - - `KeyEvent` parameters have been moved. - - This version requires `libtcod-cffi>=2.3.0`. -Deprecated - - `KeyEvent` camel capped attribute names are deprecated. -Fixed - - Crashes with key-codes undefined by libtcod. - - `tdl.map` typedef issues with libtcod-cffi. - - -2.0.1 - 2017-02-22 ------------------- -Fixed - - `tdl.init` renderer was defaulted to OpenGL which is not supported in the - current version of libtcod. - -2.0.0 - 2017-02-15 ------------------- -Changed - - Dependencies updated, tdl now requires libtcod-cffi 2.x.x - - Some event behaviors have changed with SDL2, event keys might be different - than what you expect. -Removed - - Key repeat functions were removed from SDL2. - `set_key_repeat` is now stubbed, and does nothing. - -1.6.0 - 2016-11-18 ------------------- -- Console.blit methods can now take fg_alpha and bg_alpha parameters. - -1.5.3 - 2016-06-04 ------------------- -- set_font no longer crashes when loading a file without the implied font - size in its name - -1.5.2 - 2016-03-11 ------------------- -- Fixed non-square Map instances - -1.5.1 - 2015-12-20 ------------------- -- Fixed errors with Unicode and non-Unicode literals on Python 2 -- Fixed attribute error in compute_fov - -1.5.0 - 2015-07-13 ------------------- -- python-tdl distributions are now universal builds -- New Map class -- map.bresenham now returns a list -- This release will require libtcod-cffi v0.2.3 or later - -1.4.0 - 2015-06-22 ------------------- -- The DLL's have been moved into another library which you can find at - https://github.com/HexDecimal/libtcod-cffi - You can use this library to have some raw access to libtcod if you want. - Plus it can be used alongside TDL. -- The libtcod console objects in Console instances have been made public. -- Added tdl.event.wait function. This function can called with a timeout and - can automatically call tdl.flush. - -1.3.1 - 2015-06-19 ------------------- -- Fixed pathfinding regressions. - -1.3.0 - 2015-06-19 ------------------- -- Updated backend to use python-cffi instead of ctypes. This gives decent - boost to speed in CPython and a drastic to boost in speed in PyPy. - -1.2.0 - 2015-06-06 ------------------- -- The set_colors method now changes the default colors used by the draw_* - methods. You can use Python's Ellipsis to explicitly select default colors - this way. -- Functions and Methods renamed to match Python's style-guide PEP 8, the old - function names still exist and are depreciated. -- The fgcolor and bgcolor parameters have been shortened to fg and bg. - -1.1.7 - 2015-03-19 ------------------- -- Noise generator now seeds properly. -- The OS event queue will now be handled during a call to tdl.flush. This - prevents a common newbie programmer hang where events are handled - infrequently during long animations, simulations, or early development. -- Fixed a major bug that would cause a crash in later versions of Python 3 - -1.1.6 - 2014-06-27 ------------------- -- Fixed a race condition when importing on some platforms. -- Fixed a type issue with quickFOV on Linux. -- Added a bresenham function to the tdl.map module. - -1.1.5 - 2013-11-10 ------------------- -- A for loop can iterate over all coordinates of a Console. -- drawStr can be configured to scroll or raise an error. -- You can now configure or disable key repeating with tdl.event.setKeyRepeat -- Typewriter class removed, use a Window instance for the same functionality. -- setColors method fixed. - -1.1.4 - 2013-03-06 ------------------- -- Merged the Typewriter and MetaConsole classes, - You now have a virtual cursor with Console and Window objects. -- Fixed the clear method on the Window class. -- Fixed screenshot function. -- Fixed some drawing operations with unchanging backgrounds. -- Instances of Console and Noise can be pickled and copied. -- Added KeyEvent.keychar -- Fixed event.keyWait, and now converts window closed events into Alt+F4. - -1.1.3 - 2012-12-17 ------------------- -- Some of the setFont parameters were incorrectly labeled and documented. -- setFont can auto-detect tilesets if the font sizes are in the filenames. -- Added some X11 unicode tilesets, including Unifont. - -1.1.2 - 2012-12-13 ------------------- -- Window title now defaults to the running scripts filename. -- Fixed incorrect deltaTime for App.update -- App will no longer call tdl.flush on its own, you'll need to call this - yourself. -- tdl.noise module added. -- clear method now defaults to black on black. - -1.1.1 - 2012-12-05 ------------------- -- Map submodule added with AStar class and quickFOV function. -- New Typewriter class. -- Most console functions can use Python-style negative indexes now. -- New App.runOnce method. -- Rectangle geometry is less strict. - -1.1.0 - 2012-10-04 ------------------- -- KeyEvent.keyname is now KeyEvent.key -- MouseButtonEvent.button now behaves like KeyEvent.keyname does. -- event.App class added. -- Drawing methods no longer have a default for the character parameter. -- KeyEvent.ctrl is now KeyEvent.control - -1.0.8 - 2010-04-07 ------------------- -- No longer works in Python 2.5 but now works in 3.x and has been partly - tested. -- Many bug fixes. - -1.0.5 - 2010-04-06 ------------------- -- Got rid of setuptools dependency, this will make it much more compatible - with Python 3.x -- Fixed a typo with the MacOS library import. - -1.0.4 - 2010-04-06 ------------------- -- All constant colors (C_*) have been removed, they may be put back in later. -- Made some type assertion failures show the value they received to help in - general debugging. Still working on it. -- Added MacOS and 64-bit Linux support. - -1.0.0 - 2009-01-31 ------------------- -- First public release. diff --git a/README.rst b/README.rst index 3c38bea3..823e3ac0 100755 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ For the most part it's just:: =========== You can find the most recent changelog -`here `_. +`here `_. ========= License diff --git a/docs/changelog.rst b/docs/changelog.rst index 1b4f389c..214182fa 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,4 +3,4 @@ =========== You can find the most recent changelog -`here `_. +`here `_. diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 0cd97f66..d91b359c 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -2,21 +2,18 @@ """Print the description used for GitHub Releases.""" import re -TAG_BANNER = r"\d+\.\d+\.\d+\S* - \d+-\d+-\d+\n-+\n" +TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n" RE_BODY = re.compile(fr".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) -RE_SECTION = re.compile(r"^(\w+)$", re.MULTILINE) def main() -> None: - # Get the most recent tag. - with open("CHANGELOG.rst", "r", encoding="utf-8") as f: + """Output the most recently tagged changelog body to stdout.""" + with open("CHANGELOG.md", "r", encoding="utf-8") as f: match = RE_BODY.match(f.read()) assert match body = match.groups()[0].strip() - # Add Markdown formatting to sections. - body = RE_SECTION.sub(r"### \1", body) print(body) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 68dd07f4..ef4ce3f6 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -4,7 +4,7 @@ import re import subprocess import sys -from typing import Any, Tuple +from typing import Tuple parser = argparse.ArgumentParser(description="Tags and releases the next version of this project.") @@ -17,20 +17,23 @@ parser.add_argument("-v", "--verbose", action="store_true", help="Print debug information.") -def parse_changelog(args: Any) -> Tuple[str, str]: +def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: """Return an updated changelog and and the list of changes.""" - with open("CHANGELOG.rst", "r", encoding="utf-8") as file: + with open("CHANGELOG.md", "r", encoding="utf-8") as file: match = re.match( - pattern=r"(.*?Unreleased\n---+\n)(.+?)(\n*[^\n]+\n---+\n.*)", + pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)", string=file.read(), flags=re.DOTALL, ) assert match header, changes, tail = match.groups() - tag = "%s - %s" % (args.tag, datetime.date.today().isoformat()) - - tagged = "\n%s\n%s\n%s" % (tag, "-" * len(tag), changes) + tagged = "\n## [%s] - %s\n%s" % ( + args.tag, + datetime.date.today().isoformat(), + changes, + ) if args.verbose: + print("--- Tagged section:") print(tagged) return "".join((header, tagged, tail)), changes @@ -42,13 +45,15 @@ def main() -> None: sys.exit(1) args = parser.parse_args() - if args.verbose: - print(args) new_changelog, changes = parse_changelog(args) + if args.verbose: + print("--- New changelog:") + print(new_changelog) + if not args.dry_run: - with open("CHANGELOG.rst", "w", encoding="utf-8") as f: + with open("CHANGELOG.md", "w", encoding="utf-8") as f: f.write(new_changelog) edit = ["-e"] if args.edit else [] subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) diff --git a/setup.py b/setup.py index f01db6f2..e0caa37b 100755 --- a/setup.py +++ b/setup.py @@ -111,7 +111,7 @@ def check_sdl_version() -> None: url="https://github.com/libtcod/python-tcod", project_urls={ "Documentation": "https://python-tcod.readthedocs.io", - "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.rst", + "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.md", "Source": "https://github.com/libtcod/python-tcod", "Tracker": "https://github.com/libtcod/python-tcod/issues", "Forum": "https://github.com/libtcod/python-tcod/discussions", From 28c2ffbd473498171b081785b69532cecb8c61cf Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 7 Jan 2022 22:30:10 -0800 Subject: [PATCH 0623/1101] Update libtcod. --- CHANGELOG.md | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cabd4854..06da498e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ### Added - New experimental renderer `tcod.context.RENDERER_XTERM`. +### Changed +- Using `libtcod 1.20.1`. ### Fixed - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. - BDF files with blank lines no longer fail to load with an "Unknown keyword" error. diff --git a/libtcod b/libtcod index 57f5216b..d54f68bf 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 57f5216b66227f5af68a81e8d3118d6d15c58cff +Subproject commit d54f68bf10ee862f47d3668348279a13f489d028 From e12c4172baa9efdfd74aff6ee9bab8454a835248 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 7 Jan 2022 22:30:42 -0800 Subject: [PATCH 0624/1101] Prepare 13.3.0 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06da498e..8ba44236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.3.0] - 2022-01-07 ### Added - New experimental renderer `tcod.context.RENDERER_XTERM`. ### Changed From 8ae52862c5b714ad6fdaebbd515a0169324740be Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 Jan 2022 03:02:01 -0800 Subject: [PATCH 0625/1101] Improve PathLike handling. --- CHANGELOG.md | 2 ++ tcod/console.py | 14 ++++++++------ tcod/image.py | 3 ++- tcod/tileset.py | 30 +++++++++++++++++------------- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ba44236..9a2ed57f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- Fixed handling of non-Path PathLike parameters and filepath encodings. ## [13.3.0] - 2022-01-07 ### Added diff --git a/tcod/console.py b/tcod/console.py index 895c7e79..f313b258 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -5,9 +5,9 @@ """ from __future__ import annotations -import os import warnings from os import PathLike +from pathlib import Path from typing import Any, Iterable, Optional, Sequence, Tuple, Union import numpy as np @@ -1300,11 +1300,12 @@ def load_xp(path: Union[str, PathLike[str]], order: Literal["C", "F"] = "C") -> is_transparent = (console.rgb["bg"] == KEY_COLOR).all(axis=-1) console.rgba[is_transparent] = (ord(" "), (0,), (0,)) """ - if not os.path.exists(path): - raise FileNotFoundError(f"File not found:\n\t{os.path.abspath(path)}") - layers = _check(tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), 0, ffi.NULL)) + path = Path(path) + if not path.exists(): + raise FileNotFoundError(f"File not found:\n\t{path.resolve()}") + layers = _check(tcod.lib.TCOD_load_xp(bytes(path), 0, ffi.NULL)) consoles = ffi.new("TCOD_Console*[]", layers) - _check(tcod.lib.TCOD_load_xp(str(path).encode("utf-8"), layers, consoles)) + _check(tcod.lib.TCOD_load_xp(bytes(path), layers, consoles)) return tuple(Console._from_cdata(console_p, order=order) for console_p in consoles) @@ -1358,12 +1359,13 @@ def save_xp( tcod.console.save_xp("example.xp", [console]) """ + path = Path(path) consoles_c = ffi.new("TCOD_Console*[]", [c.console_c for c in consoles]) _check( tcod.lib.TCOD_save_xp( len(consoles_c), consoles_c, - str(path).encode("utf-8"), + bytes(path), compress_level, ) ) diff --git a/tcod/image.py b/tcod/image.py index 0a017530..d94441cb 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -8,6 +8,7 @@ from __future__ import annotations from os import PathLike +from pathlib import Path from typing import Any, Dict, Tuple, Union import numpy as np @@ -345,7 +346,7 @@ def load(filename: Union[str, PathLike[str]]) -> NDArray[np.uint8]: .. versionadded:: 11.4 """ - image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(str(filename).encode()), lib.TCOD_image_delete)) + image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(Path(filename))), lib.TCOD_image_delete)) array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: diff --git a/tcod/tileset.py b/tcod/tileset.py index fd4f2f21..acff8176 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -13,8 +13,8 @@ from __future__ import annotations import itertools -import os from os import PathLike +from pathlib import Path from typing import Any, Iterable, Optional, Tuple, Union import numpy as np @@ -258,9 +258,10 @@ def load_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_he This function is provisional. The API may change. """ - if not os.path.exists(path): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_truetype_font_(str(path).encode(), tile_width, tile_height) + path = Path(path) + if not path.exists(): + raise RuntimeError(f"File not found:\n\t{path.resolve()}") + cdata = lib.TCOD_load_truetype_font_(bytes(path), tile_width, tile_height) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) return Tileset._claim(cdata) @@ -287,9 +288,10 @@ def set_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_hei This function does not support contexts. Use :any:`load_truetype_font` instead. """ - if not os.path.exists(path): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - if lib.TCOD_tileset_load_truetype_(str(path).encode(), tile_width, tile_height): + path = Path(path) + if not path.exists(): + raise RuntimeError(f"File not found:\n\t{path.resolve()}") + if lib.TCOD_tileset_load_truetype_(bytes(path), tile_width, tile_height): raise RuntimeError(ffi.string(lib.TCOD_get_error())) @@ -306,9 +308,10 @@ def load_bdf(path: Union[str, PathLike[str]]) -> Tileset: .. versionadded:: 11.10 """ # noqa: E501 - if not os.path.exists(path): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) - cdata = lib.TCOD_load_bdf(str(path).encode()) + path = Path(path) + if not path.exists(): + raise RuntimeError(f"File not found:\n\t{path.resolve()}") + cdata = lib.TCOD_load_bdf(bytes(path)) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) return Tileset._claim(cdata) @@ -335,12 +338,13 @@ def load_tilesheet( .. versionadded:: 11.12 """ - if not os.path.exists(path): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(path),)) + path = Path(path) + if not path.exists(): + raise RuntimeError(f"File not found:\n\t{path.resolve()}") mapping = [] if charmap is not None: mapping = list(itertools.islice(charmap, columns * rows)) - cdata = lib.TCOD_tileset_load(str(path).encode(), columns, rows, len(mapping), mapping) + cdata = lib.TCOD_tileset_load(bytes(path), columns, rows, len(mapping), mapping) if not cdata: _raise_tcod_error() return Tileset._claim(cdata) From b54412b9204221f93c3fac56b582eea72557831f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 Jan 2022 03:58:07 -0800 Subject: [PATCH 0626/1101] Refactor and modernize scripts. Use pathlib and f-strings more often. Clean up some deprecated code. --- build_libtcod.py | 148 ++++++++++++++--------------- parse_sdl2.py | 21 ++-- scripts/generate_charmap_table.py | 5 +- scripts/get_release_description.py | 6 +- scripts/tag_release.py | 17 ++-- setup.py | 24 ++--- tcod/console.py | 2 +- 7 files changed, 109 insertions(+), 114 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index a69933c6..1f65f122 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations import glob import os @@ -8,16 +9,13 @@ import subprocess import sys import zipfile +from pathlib import Path from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union - -try: - from urllib import urlretrieve # type: ignore -except ImportError: - from urllib.request import urlretrieve +from urllib.request import urlretrieve from cffi import FFI # type: ignore -sys.path.append(os.path.dirname(__file__)) +sys.path.append(str(Path(__file__).parent)) # Allow importing local modules. import parse_sdl2 # noqa: E402 @@ -52,18 +50,18 @@ class ParsedHeader: """ # Class dictionary of all parsed headers. - all_headers = {} # type: Dict[str, "ParsedHeader"] + all_headers: Dict[Path, ParsedHeader] = {} - def __init__(self, path: str) -> None: - self.path = path = os.path.normpath(path) - directory = os.path.dirname(path) + def __init__(self, path: Path) -> None: + self.path = path = path.resolve(True) + directory = path.parent depends = set() with open(self.path, "r", encoding="utf-8") as f: header = f.read() header = RE_COMMENT.sub("", header) header = RE_CPLUSPLUS.sub("", header) for dependency in RE_INCLUDE.findall(header): - depends.add(os.path.normpath(os.path.join(directory, dependency))) + depends.add((directory / dependency).resolve(True)) header = RE_PREPROCESSOR.sub("", header) header = RE_TAGS.sub("", header) header = RE_VAFUNC.sub("", header) @@ -87,17 +85,17 @@ def __str__(self) -> str: ) def __repr__(self) -> str: - return "ParsedHeader(%s)" % (self.path,) + return f"ParsedHeader({self.path})" def walk_includes(directory: str) -> Iterator[ParsedHeader]: """Parse all the include files in a directory and subdirectories.""" - for path, dirs, files in os.walk(directory): + for path, _dirs, files in os.walk(directory): for file in files: if file in HEADER_PARSE_EXCLUDES: continue if file.endswith(".h"): - yield ParsedHeader(os.path.join(path, file)) + yield ParsedHeader(Path(path, file).resolve(True)) def resolve_dependencies( @@ -105,7 +103,7 @@ def resolve_dependencies( ) -> List[ParsedHeader]: """Sort headers by their correct include order.""" unresolved = set(includes) - resolved = set() # type: Set[ParsedHeader] + resolved: Set[ParsedHeader] = set() result = [] while unresolved: for item in unresolved: @@ -115,7 +113,7 @@ def resolve_dependencies( if not unresolved & resolved: raise RuntimeError( "Could not resolve header load order.\n" - "Possible cyclic dependency with the unresolved headers:\n%s" % (unresolved,) + f"Possible cyclic dependency with the unresolved headers:\n{unresolved}" ) unresolved -= resolved return result @@ -124,49 +122,50 @@ def resolve_dependencies( def parse_includes() -> List[ParsedHeader]: """Collect all parsed header files and return them. - Reads HEADER_PARSE_PATHS and HEADER_PARSE_EXCLUDES.""" - includes = [] # type: List[ParsedHeader] + Reads HEADER_PARSE_PATHS and HEADER_PARSE_EXCLUDES. + """ + includes: List[ParsedHeader] = [] for dirpath in HEADER_PARSE_PATHS: includes.extend(walk_includes(dirpath)) return resolve_dependencies(includes) def walk_sources(directory: str) -> Iterator[str]: - for path, dirs, files in os.walk(directory): + for path, _dirs, files in os.walk(directory): for source in files: if source.endswith(".c"): - yield os.path.join(path, source) + yield str(Path(path, source)) -def get_sdl2_file(version: str) -> str: +def get_sdl2_file(version: str) -> Path: if sys.platform == "win32": - sdl2_file = "SDL2-devel-%s-VC.zip" % (version,) + sdl2_file = f"SDL2-devel-{version}-VC.zip" else: assert sys.platform == "darwin" - sdl2_file = "SDL2-%s.dmg" % (version,) - sdl2_local_file = os.path.join("dependencies", sdl2_file) - sdl2_remote_file = "https://www.libsdl.org/release/%s" % sdl2_file - if not os.path.exists(sdl2_local_file): - print("Downloading %s" % sdl2_remote_file) + sdl2_file = f"SDL2-{version}.dmg" + sdl2_local_file = Path("dependencies", sdl2_file) + sdl2_remote_file = f"https://www.libsdl.org/release/{sdl2_file}" + if not sdl2_local_file.exists(): + print(f"Downloading {sdl2_remote_file}") os.makedirs("dependencies/", exist_ok=True) urlretrieve(sdl2_remote_file, sdl2_local_file) return sdl2_local_file -def unpack_sdl2(version: str) -> str: - sdl2_path = "dependencies/SDL2-%s" % (version,) +def unpack_sdl2(version: str) -> Path: + sdl2_path = Path(f"dependencies/SDL2-{version}") if sys.platform == "darwin": sdl2_dir = sdl2_path - sdl2_path += "/SDL2.framework" - if os.path.exists(sdl2_path): + sdl2_path /= "SDL2.framework" + if sdl2_path.exists(): return sdl2_path sdl2_arc = get_sdl2_file(version) - print("Extracting %s" % sdl2_arc) - if sdl2_arc.endswith(".zip"): + print(f"Extracting {sdl2_arc}") + if sdl2_arc.suffix == ".zip": with zipfile.ZipFile(sdl2_arc) as zf: zf.extractall("dependencies/") elif sys.platform == "darwin": - assert sdl2_arc.endswith(".dmg") + assert sdl2_arc.suffix == ".dmg" subprocess.check_call(["hdiutil", "mount", sdl2_arc]) subprocess.check_call(["mkdir", "-p", sdl2_dir]) subprocess.check_call(["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir]) @@ -187,11 +186,11 @@ def unpack_sdl2(version: str) -> str: extra_parse_args = [] extra_compile_args = [] extra_link_args = [] -sources = [] # type: List[str] +sources: List[str] = [] libraries = [] library_dirs: List[str] = [] -define_macros = [("Py_LIMITED_API", 0x03060000)] # type: List[Tuple[str, Any]] +define_macros: List[Tuple[str, Any]] = [("Py_LIMITED_API", 0x03060000)] sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") @@ -219,9 +218,9 @@ def unpack_sdl2(version: str) -> str: include_dirs.append("libtcod/src/zlib/") if sys.platform == "win32": - SDL2_INCLUDE = os.path.join(SDL2_PARSE_PATH, "include") + SDL2_INCLUDE = Path(SDL2_PARSE_PATH, "include") elif sys.platform == "darwin": - SDL2_INCLUDE = os.path.join(SDL2_PARSE_PATH, "Versions/A/Headers") + SDL2_INCLUDE = Path(SDL2_PARSE_PATH, "Versions/A/Headers") else: matches = re.findall( r"-I(\S+)", @@ -231,43 +230,40 @@ def unpack_sdl2(version: str) -> str: SDL2_INCLUDE = None for match in matches: - if os.path.isfile(os.path.join(match, "SDL_stdinc.h")): + if Path(match, "SDL_stdinc.h").is_file(): SDL2_INCLUDE = match assert SDL2_INCLUDE if sys.platform == "win32": - include_dirs.append(SDL2_INCLUDE) + include_dirs.append(str(SDL2_INCLUDE)) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = os.path.join(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) - library_dirs.append(SDL2_LIB_DIR) - SDL2_LIB_DEST = os.path.join("tcod", ARCH_MAPPING[BITSIZE]) - if not os.path.exists(SDL2_LIB_DEST): + SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) + library_dirs.append(str(SDL2_LIB_DIR)) + SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BITSIZE]) + if not SDL2_LIB_DEST.exists(): os.mkdir(SDL2_LIB_DEST) - shutil.copy(os.path.join(SDL2_LIB_DIR, "SDL2.dll"), SDL2_LIB_DEST) + shutil.copy(Path(SDL2_LIB_DIR, "SDL2.dll"), SDL2_LIB_DEST) -def fix_header(filepath: str) -> None: +def fix_header(path: Path) -> None: """Removes leading whitespace from a MacOS header file. This whitespace is causing issues with directives on some platforms. """ - with open(filepath, "r+", encoding="utf-8") as f: - current = f.read() - fixed = "\n".join(line.strip() for line in current.split("\n")) - if current == fixed: - return - f.seek(0) - f.truncate() - f.write(fixed) + current = path.read_text(encoding="utf-8") + fixed = "\n".join(line.strip() for line in current.split("\n")) + if current == fixed: + return + path.write_text(fixed, encoding="utf-8") if sys.platform == "darwin": - HEADER_DIR = os.path.join(SDL2_PARSE_PATH, "Headers") - fix_header(os.path.join(HEADER_DIR, "SDL_assert.h")) - fix_header(os.path.join(HEADER_DIR, "SDL_config_macosx.h")) + HEADER_DIR = Path(SDL2_PARSE_PATH, "Headers") + fix_header(Path(HEADER_DIR, "SDL_assert.h")) + fix_header(Path(HEADER_DIR, "SDL_config_macosx.h")) include_dirs.append(HEADER_DIR) - extra_link_args += ["-F%s/.." % SDL2_BUNDLE_PATH] - extra_link_args += ["-rpath", "%s/.." % SDL2_BUNDLE_PATH] + extra_link_args += [f"-F{SDL2_BUNDLE_PATH}/.."] + extra_link_args += ["-rpath", f"{SDL2_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] # Fix "implicit declaration of function 'close'" in zlib. @@ -309,7 +305,7 @@ def fix_header(filepath: str) -> None: ffi.cdef(include.header) except Exception: # Print the source, for debugging. - print("Error with: %s" % include.path) + print(f"Error with: {include.path}") for i, line in enumerate(include.header.split("\n"), 1): print("%03i %s" % (i, line)) raise @@ -374,8 +370,8 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: lookup = [] for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): all_names.append(name) - names.append("%s = %s" % (name, value)) - lookup.append('%s: "%s"' % (value, name)) + names.append(f"{name} = {value}") + lookup.append(f'{value}: "{name}"') return "\n".join(names), "{\n %s,\n}" % (",\n ".join(lookup),) @@ -408,10 +404,10 @@ def update_module_all(filename: str, new_all: str) -> None: ) with open(filename, "r", encoding="utf-8") as f: match = RE_CONSTANTS_ALL.match(f.read()) - assert match, "Can't determine __all__ subsection in %s!" % (filename,) + assert match, f"Can't determine __all__ subsection in {filename}!" header, footer = match.groups() with open(filename, "w", encoding="utf-8") as f: - f.write("%s\n %s,\n %s" % (header, new_all, footer)) + f.write(f"{header}\n {new_all},\n {footer}") def generate_enums(prefix: str) -> Iterator[str]: @@ -446,14 +442,14 @@ def write_library_constants() -> None: value = getattr(lib, name) if name[:5] == "TCOD_": if name.isupper(): # const names - f.write("%s = %r\n" % (name[5:], value)) + f.write(f"{name[5:]} = {value!r}\n") all_names.append(name[5:]) elif name.startswith("FOV"): # fov const names - f.write("%s = %r\n" % (name, value)) + f.write(f"{name} = {value!r}\n") all_names.append(name) elif name[:6] == "TCODK_": # key name - f.write("KEY_%s = %r\n" % (name[6:], value)) - all_names.append("KEY_%s" % name[6:]) + f.write(f"KEY_{name[6:]} = {value!r}\n") + all_names.append(f"KEY_{name[6:]}") f.write("\n# --- colors ---\n") for name in dir(lib): @@ -465,11 +461,11 @@ def write_library_constants() -> None: if ffi.typeof(value) != ffi.typeof("TCOD_color_t"): continue color = tcod.color.Color._new_from_cdata(value) - f.write("%s = %r\n" % (name[5:], color)) + f.write(f"{name[5:]} = {color!r}\n") all_names.append(name[5:]) - all_names_merged = ",\n ".join('"%s"' % name for name in all_names) - f.write("\n__all__ = [\n %s,\n]\n" % (all_names_merged,)) + all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) + f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") update_module_all("tcod/__init__.py", all_names_merged) update_module_all("tcod/libtcodpy.py", all_names_merged) @@ -487,11 +483,10 @@ def write_library_constants() -> None: f.write("\n# --- SDL wheel ---\n") f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) - all_names_merged = ",\n ".join('"%s"' % name for name in all_names) - f.write("\n__all__ = [\n %s,\n]\n" % (all_names_merged,)) + all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) + f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") - with open("tcod/event.py", "r", encoding="utf-8") as f: - event_py = f.read() + event_py = Path("tcod/event.py").read_text(encoding="utf-8") event_py = re.sub( r"(?<=# --- SDL scancodes ---\n ).*?(?=\n # --- end ---\n)", @@ -506,8 +501,7 @@ def write_library_constants() -> None: flags=re.DOTALL, ) - with open("tcod/event.py", "w", encoding="utf-8") as f: - f.write(event_py) + Path("tcod/event.py").write_text(event_py, encoding="utf-8") if __name__ == "__main__": diff --git a/parse_sdl2.py b/parse_sdl2.py index bfdc0125..dd97940d 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -1,8 +1,11 @@ -import os.path +from __future__ import annotations + import platform import re import sys -from typing import Any, Dict, Iterator +from os import PathLike +from pathlib import Path +from typing import Any, Dict, Iterator, Union import cffi # type: ignore @@ -25,10 +28,9 @@ RE_EVENT_PADDING = re.compile(r"Uint8 padding\[[^]]+\];", re.MULTILINE | re.DOTALL) -def get_header(name: str) -> str: +def get_header(path: Path) -> str: """Return the source of a header in a partially preprocessed state.""" - with open(name, "r", encoding="utf-8") as f: - header = f.read() + header = path.read_text(encoding="utf-8") # Remove Doxygen code. header = RE_REMOVALS.sub("", header) # Remove comments. @@ -131,7 +133,8 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: ] -def add_to_ffi(ffi: cffi.FFI, path: str) -> None: +def add_to_ffi(ffi: cffi.FFI, path: Union[str, PathLike[str]]) -> None: + path = Path(path) BITS, _ = platform.architecture() cdef_args: Dict[str, Any] = {} NEEDS_PACK4 = False @@ -142,12 +145,12 @@ def add_to_ffi(ffi: cffi.FFI, path: str) -> None: # cdef_args["pack"] = 4 ffi.cdef( - "\n".join(RE_TYPEDEF.findall(get_header(os.path.join(path, "SDL_stdinc.h")))).replace("SDLCALL ", ""), + "\n".join(RE_TYPEDEF.findall(get_header(path / "SDL_stdinc.h"))).replace("SDLCALL ", ""), **cdef_args, ) for header in HEADERS: try: - for code in parse(get_header(os.path.join(path, header)), NEEDS_PACK4): + for code in parse(get_header(path / header), NEEDS_PACK4): if "typedef struct SDL_AudioCVT" in code and sys.platform != "win32" and not NEEDS_PACK4: # This specific struct needs to be packed. ffi.cdef(code, packed=1) @@ -158,7 +161,7 @@ def add_to_ffi(ffi: cffi.FFI, path: str) -> None: raise -def get_ffi(path: str) -> cffi.FFI: +def get_ffi(path: Union[str, PathLike[str]]) -> cffi.FFI: """Return an ffi for SDL2, needs to be compiled.""" ffi = cffi.FFI() add_to_ffi(ffi, path) diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index d4bd0b77..1f9f4639 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -3,6 +3,8 @@ Uses the tabulate module from PyPI. """ +from __future__ import annotations + import argparse import unicodedata from typing import Iterable, Iterator @@ -71,8 +73,9 @@ def main() -> None: ) args = parser.parse_args() charmap = getattr(tcod.tileset, f"CHARMAP_{args.charmap.upper()}") + output = generate_table(charmap) with args.out_file as f: - f.write(generate_table(charmap)) + f.write(output) if __name__ == "__main__": diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index d91b359c..29844caf 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 """Print the description used for GitHub Releases.""" +from __future__ import annotations + import re +from pathlib import Path TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n" @@ -9,8 +12,7 @@ def main() -> None: """Output the most recently tagged changelog body to stdout.""" - with open("CHANGELOG.md", "r", encoding="utf-8") as f: - match = RE_BODY.match(f.read()) + match = RE_BODY.match(Path("CHANGELOG.md").read_text(encoding="utf-8")) assert match body = match.groups()[0].strip() diff --git a/scripts/tag_release.py b/scripts/tag_release.py index ef4ce3f6..4f900ba2 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 +from __future__ import annotations + import argparse import datetime import re import subprocess import sys +from pathlib import Path from typing import Tuple parser = argparse.ArgumentParser(description="Tags and releases the next version of this project.") @@ -19,12 +22,11 @@ def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: """Return an updated changelog and and the list of changes.""" - with open("CHANGELOG.md", "r", encoding="utf-8") as file: - match = re.match( - pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)", - string=file.read(), - flags=re.DOTALL, - ) + match = re.match( + pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)", + string=Path("CHANGELOG.md").read_text(encoding="utf-8"), + flags=re.DOTALL, + ) assert match header, changes, tail = match.groups() tagged = "\n## [%s] - %s\n%s" % ( @@ -53,8 +55,7 @@ def main() -> None: print(new_changelog) if not args.dry_run: - with open("CHANGELOG.md", "w", encoding="utf-8") as f: - f.write(new_changelog) + Path("CHANGELOG.md").write_text(new_changelog, encoding="utf-8") edit = ["-e"] if args.edit else [] subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) subprocess.check_call(["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] + edit) diff --git a/setup.py b/setup.py index e0caa37b..d78363f3 100755 --- a/setup.py +++ b/setup.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 +from __future__ import annotations -import os -import pathlib import platform import re import subprocess import sys import warnings +from pathlib import Path from typing import List from setuptools import setup SDL_VERSION_NEEDED = (2, 0, 5) -PATH = pathlib.Path(__file__).parent # setup.py current directory +SETUP_DIR = Path(__file__).parent # setup.py current directory def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" - if (PATH / ".git").exists(): + if (SETUP_DIR / ".git").exists(): tag = subprocess.check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() assert not tag.startswith("v") version = tag @@ -30,13 +30,11 @@ def get_version() -> str: version += ".dev%i" % commits_since_tag # update tcod/version.py - with open(PATH / "tcod/version.py", "w", encoding="utf-8") as version_file: - version_file.write(f'__version__ = "{version}"\n') + (SETUP_DIR / "tcod/version.py").write_text(f'__version__ = "{version}"\n', encoding="utf-8") return version else: # Not a Git repository. try: - with open(PATH / "tcod/version.py", encoding="utf-8") as version_file: - match = re.match(r'__version__ = "(\S+)"', version_file.read()) + match = re.match(r'__version__ = "(\S+)"', (SETUP_DIR / "tcod/version.py").read_text(encoding="utf-8")) assert match return match.groups()[0] except FileNotFoundError: @@ -66,12 +64,6 @@ def get_package_data() -> List[str]: return files -def get_long_description() -> str: - """Return this projects description.""" - with open(PATH / "README.rst", "r", encoding="utf-8") as readme_file: - return readme_file.read() - - def check_sdl_version() -> None: """Check the local SDL version on Linux distributions.""" if not sys.platform.startswith("linux"): @@ -91,7 +83,7 @@ def check_sdl_version() -> None: raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) -if not os.path.exists(PATH / "libtcod/src"): +if not (SETUP_DIR / "libtcod/src").exists(): print("Libtcod submodule is uninitialized.") print("Did you forget to run 'git submodule update --init'?") sys.exit(1) @@ -107,7 +99,7 @@ def check_sdl_version() -> None: author="Kyle Benesch", author_email="4b796c65+tcod@gmail.com", description="The official Python port of libtcod.", - long_description=get_long_description(), + long_description=(SETUP_DIR / "README.rst").read_text(encoding="utf-8"), url="https://github.com/libtcod/python-tcod", project_urls={ "Documentation": "https://python-tcod.readthedocs.io", diff --git a/tcod/console.py b/tcod/console.py index f313b258..56eea2ac 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -393,7 +393,7 @@ def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: def clear( self, - ch: int = ord(" "), + ch: int = 0x20, fg: Tuple[int, int, int] = ..., # type: ignore bg: Tuple[int, int, int] = ..., # type: ignore ) -> None: From 858617bae4521af1883e4f983b53586c107a7b6d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 13 Jan 2022 02:42:08 -0800 Subject: [PATCH 0627/1101] Manually add SDL_free to FFI exports. Also add SDL_calloc for later tests. --- parse_sdl2.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/parse_sdl2.py b/parse_sdl2.py index dd97940d..bd28167b 100644 --- a/parse_sdl2.py +++ b/parse_sdl2.py @@ -132,6 +132,12 @@ def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: "SDL_version.h", ] +# It's easier to manually add these instead of parsing SDL_stdinc.h +CDEF_EXTRA = """ +void* SDL_calloc(size_t nmemb, size_t size); +void SDL_free(void *mem); +""" + def add_to_ffi(ffi: cffi.FFI, path: Union[str, PathLike[str]]) -> None: path = Path(path) @@ -148,6 +154,7 @@ def add_to_ffi(ffi: cffi.FFI, path: Union[str, PathLike[str]]) -> None: "\n".join(RE_TYPEDEF.findall(get_header(path / "SDL_stdinc.h"))).replace("SDLCALL ", ""), **cdef_args, ) + ffi.cdef(CDEF_EXTRA, **cdef_args) for header in HEADERS: try: for code in parse(get_header(path / header), NEEDS_PACK4): From 4385a4d65f20a6e1eefc8ab0a243c0d869adec45 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 25 Jan 2022 12:59:51 -0800 Subject: [PATCH 0628/1101] Update SDL CFFI parser. This is a backport of the SDL parser updates from the ESDL subproject. This may change what symbols are exported, but it will export more than before. It should also be more stable. --- .vscode/settings.json | 1 + build_libtcod.py | 120 ++---------------- build_sdl.py | 285 ++++++++++++++++++++++++++++++++++++++++++ parse_sdl2.py | 175 -------------------------- pyproject.toml | 8 +- requirements.txt | 3 +- setup.py | 5 +- 7 files changed, 309 insertions(+), 288 deletions(-) create mode 100644 build_sdl.py delete mode 100644 parse_sdl2.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 6e892bc5..ee28f37e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -216,6 +216,7 @@ "PAGEUP", "pathfinding", "pathlib", + "pcpp", "PILCROW", "pilmode", "PRINTF", diff --git a/build_libtcod.py b/build_libtcod.py index 1f65f122..93065ae3 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -5,19 +5,15 @@ import os import platform import re -import shutil -import subprocess import sys -import zipfile from pathlib import Path from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union -from urllib.request import urlretrieve from cffi import FFI # type: ignore sys.path.append(str(Path(__file__).parent)) # Allow importing local modules. -import parse_sdl2 # noqa: E402 +import build_sdl # noqa: E402 # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") @@ -25,6 +21,8 @@ # The SDL2 version to include in binary distributions. SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") +Py_LIMITED_API = 0x03060000 + HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") @@ -137,60 +135,24 @@ def walk_sources(directory: str) -> Iterator[str]: yield str(Path(path, source)) -def get_sdl2_file(version: str) -> Path: - if sys.platform == "win32": - sdl2_file = f"SDL2-devel-{version}-VC.zip" - else: - assert sys.platform == "darwin" - sdl2_file = f"SDL2-{version}.dmg" - sdl2_local_file = Path("dependencies", sdl2_file) - sdl2_remote_file = f"https://www.libsdl.org/release/{sdl2_file}" - if not sdl2_local_file.exists(): - print(f"Downloading {sdl2_remote_file}") - os.makedirs("dependencies/", exist_ok=True) - urlretrieve(sdl2_remote_file, sdl2_local_file) - return sdl2_local_file - - -def unpack_sdl2(version: str) -> Path: - sdl2_path = Path(f"dependencies/SDL2-{version}") - if sys.platform == "darwin": - sdl2_dir = sdl2_path - sdl2_path /= "SDL2.framework" - if sdl2_path.exists(): - return sdl2_path - sdl2_arc = get_sdl2_file(version) - print(f"Extracting {sdl2_arc}") - if sdl2_arc.suffix == ".zip": - with zipfile.ZipFile(sdl2_arc) as zf: - zf.extractall("dependencies/") - elif sys.platform == "darwin": - assert sdl2_arc.suffix == ".dmg" - subprocess.check_call(["hdiutil", "mount", sdl2_arc]) - subprocess.check_call(["mkdir", "-p", sdl2_dir]) - subprocess.check_call(["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir]) - subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL2"]) - return sdl2_path - - includes = parse_includes() module_name = "tcod._libtcod" -include_dirs = [ +include_dirs: List[str] = [ ".", "libtcod/src/vendor/", "libtcod/src/vendor/utf8proc", "libtcod/src/vendor/zlib/", + *build_sdl.include_dirs, ] -extra_parse_args = [] -extra_compile_args = [] -extra_link_args = [] +extra_compile_args: List[str] = [*build_sdl.extra_compile_args] +extra_link_args: List[str] = [*build_sdl.extra_link_args] sources: List[str] = [] -libraries = [] -library_dirs: List[str] = [] -define_macros: List[Tuple[str, Any]] = [("Py_LIMITED_API", 0x03060000)] +libraries: List[str] = [*build_sdl.libraries] +library_dirs: List[str] = [*build_sdl.library_dirs] +define_macros: List[Tuple[str, Any]] = [("Py_LIMITED_API", Py_LIMITED_API)] sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") @@ -205,74 +167,14 @@ def unpack_sdl2(version: str) -> Path: define_macros.append(("TCODLIB_API", "")) define_macros.append(("_CRT_SECURE_NO_WARNINGS", None)) -if sys.platform == "darwin": - extra_link_args += ["-framework", "SDL2"] -else: - libraries += ["SDL2"] - -# included SDL headers are for whatever OS's don't easily come with them - if sys.platform in ["win32", "darwin"]: - SDL2_PARSE_PATH = unpack_sdl2(SDL2_PARSE_VERSION) - SDL2_BUNDLE_PATH = unpack_sdl2(SDL2_BUNDLE_VERSION) include_dirs.append("libtcod/src/zlib/") -if sys.platform == "win32": - SDL2_INCLUDE = Path(SDL2_PARSE_PATH, "include") -elif sys.platform == "darwin": - SDL2_INCLUDE = Path(SDL2_PARSE_PATH, "Versions/A/Headers") -else: - matches = re.findall( - r"-I(\S+)", - subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True), - ) - assert matches - - SDL2_INCLUDE = None - for match in matches: - if Path(match, "SDL_stdinc.h").is_file(): - SDL2_INCLUDE = match - assert SDL2_INCLUDE - -if sys.platform == "win32": - include_dirs.append(str(SDL2_INCLUDE)) - ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) - library_dirs.append(str(SDL2_LIB_DIR)) - SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BITSIZE]) - if not SDL2_LIB_DEST.exists(): - os.mkdir(SDL2_LIB_DEST) - shutil.copy(Path(SDL2_LIB_DIR, "SDL2.dll"), SDL2_LIB_DEST) - - -def fix_header(path: Path) -> None: - """Removes leading whitespace from a MacOS header file. - - This whitespace is causing issues with directives on some platforms. - """ - current = path.read_text(encoding="utf-8") - fixed = "\n".join(line.strip() for line in current.split("\n")) - if current == fixed: - return - path.write_text(fixed, encoding="utf-8") - if sys.platform == "darwin": - HEADER_DIR = Path(SDL2_PARSE_PATH, "Headers") - fix_header(Path(HEADER_DIR, "SDL_assert.h")) - fix_header(Path(HEADER_DIR, "SDL_config_macosx.h")) - include_dirs.append(HEADER_DIR) - extra_link_args += [f"-F{SDL2_BUNDLE_PATH}/.."] - extra_link_args += ["-rpath", f"{SDL2_BUNDLE_PATH}/.."] - extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] - # Fix "implicit declaration of function 'close'" in zlib. define_macros.append(("HAVE_UNISTD_H", 1)) -if sys.platform not in ["win32", "darwin"]: - extra_parse_args += subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True).strip().split() - extra_compile_args += extra_parse_args - extra_link_args += subprocess.check_output(["sdl2-config", "--libs"], universal_newlines=True).strip().split() tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() @@ -299,7 +201,7 @@ def fix_header(path: Path) -> None: extra_link_args.extend(GCC_CFLAGS[tdl_build]) ffi = FFI() -parse_sdl2.add_to_ffi(ffi, SDL2_INCLUDE) +ffi.cdef(build_sdl.get_cdef()) for include in includes: try: ffi.cdef(include.header) diff --git a/build_sdl.py b/build_sdl.py new file mode 100644 index 00000000..7bc0bd8d --- /dev/null +++ b/build_sdl.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import io +import os +import platform +import re +import shutil +import subprocess +import sys +import zipfile +from pathlib import Path +from typing import Any, Dict, List, Set +from urllib.request import urlretrieve + +import pcpp # type: ignore + +BITSIZE, LINKAGE = platform.architecture() + +# The SDL2 version to parse and export symbols from. +SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") +# The SDL2 version to include in binary distributions. +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") + + +# Used to remove excessive newlines in debug outputs. +RE_NEWLINES = re.compile(r"\n\n+") +# Functions using va_list need to be culled. +RE_VAFUNC = re.compile(r"^.*?\([^()]*va_list[^()]*\);$", re.MULTILINE) +# Static inline functions need to be culled. +RE_INLINE = re.compile(r"^static inline.*?^}$", re.MULTILINE | re.DOTALL) +# Most SDL_PIXELFORMAT names need their values scrubbed. +RE_PIXELFORMAT = re.compile(r"(?PSDL_PIXELFORMAT_\w+) =[^,}]*") +# Most SDLK names need their values scrubbed. +RE_SDLK = re.compile(r"(?PSDLK_\w+) =.*?(?=,\n|}\n)") +# Remove compile time assertions from the cdef. +RE_ASSERT = re.compile(r"^.*SDL_compile_time_assert.*$", re.MULTILINE) +# Padding values need to be scrubbed. +RE_PADDING = re.compile(r"padding\[[^;]*\];") + +# These structs have an unusual size when packed by SDL on 32-bit platforms. +FLEXIBLE_STRUCTS = ( + "SDL_AudioCVT", + "SDL_TouchFingerEvent", + "SDL_MultiGestureEvent", + "SDL_DollarGestureEvent", +) + +# Other defined names which sometimes cause issues when parsed. +IGNORE_DEFINES = frozenset( + ( + "SDL_DEPRECATED", + "SDL_INLINE", + "SDL_FORCE_INLINE", + # Might show up in parsing and not in source. + "SDL_ANDROID_EXTERNAL_STORAGE_READ", + "SDL_ANDROID_EXTERNAL_STORAGE_WRITE", + "SDL_ASSEMBLY_ROUTINES", + "SDL_RWOPS_VITAFILE", + # Prevent double definition. + "SDL_FALSE", + "SDL_TRUE", + ) +) + + +def check_sdl_version() -> None: + """Check the local SDL version on Linux distributions.""" + if not sys.platform.startswith("linux"): + return + needed_version = SDL2_PARSE_VERSION + SDL_VERSION_NEEDED = tuple(int(n) for n in needed_version.split(".")) + try: + sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() + except FileNotFoundError: + raise RuntimeError( + "libsdl2-dev or equivalent must be installed on your system" + f" and must be at least version {needed_version}." + "\nsdl2-config must be on PATH." + ) + print(f"Found SDL {sdl_version_str}.") + sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) + if sdl_version < SDL_VERSION_NEEDED: + raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) + + +def get_sdl2_file(version: str) -> Path: + if sys.platform == "win32": + sdl2_file = f"SDL2-devel-{version}-VC.zip" + else: + assert sys.platform == "darwin" + sdl2_file = f"SDL2-{version}.dmg" + sdl2_local_file = Path("dependencies", sdl2_file) + sdl2_remote_file = f"https://www.libsdl.org/release/{sdl2_file}" + if not sdl2_local_file.exists(): + print(f"Downloading {sdl2_remote_file}") + os.makedirs("dependencies/", exist_ok=True) + urlretrieve(sdl2_remote_file, sdl2_local_file) + return sdl2_local_file + + +def unpack_sdl2(version: str) -> Path: + sdl2_path = Path(f"dependencies/SDL2-{version}") + if sys.platform == "darwin": + sdl2_dir = sdl2_path + sdl2_path /= "SDL2.framework" + if sdl2_path.exists(): + return sdl2_path + sdl2_arc = get_sdl2_file(version) + print(f"Extracting {sdl2_arc}") + if sdl2_arc.suffix == ".zip": + with zipfile.ZipFile(sdl2_arc) as zf: + zf.extractall("dependencies/") + elif sys.platform == "darwin": + assert sdl2_arc.suffix == ".dmg" + subprocess.check_call(["hdiutil", "mount", sdl2_arc]) + subprocess.check_call(["mkdir", "-p", sdl2_dir]) + subprocess.check_call(["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir]) + subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL2"]) + return sdl2_path + + +class SDLParser(pcpp.Preprocessor): # type: ignore + """A modified preprocessor to output code in a format for CFFI.""" + + def __init__(self) -> None: + super().__init__() + self.line_directive = None # Don't output line directives. + self.known_string_defines: Dict[str, str] = {} + self.known_defines: Set[str] = set() + + def get_output(self) -> str: + """Return this objects current tokens as a string.""" + with io.StringIO() as buffer: + self.write(buffer) + for name in self.known_defines: + buffer.write(f"#define {name} ...\n") + return buffer.getvalue() + + def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: + """Remove bad includes such as stddef.h and stdarg.h.""" + raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove) + + def _should_track_define(self, tokens: List[Any]) -> bool: + if len(tokens) < 3: + return False + if tokens[0].value in IGNORE_DEFINES: + return False + if not tokens[0].value.isupper(): + return False # Function-like name, such as SDL_snprintf. + if tokens[0].value.startswith("_") or tokens[0].value.endswith("_"): + return False # Private name. + if tokens[2].value.startswith("_") or tokens[2].value.endswith("_"): + return False # Likely calls a private function. + if tokens[1].type == "CPP_LPAREN": + return False # Function-like macro. + if len(tokens) >= 4 and tokens[2].type == "CPP_INTEGER" and tokens[3].type == "CPP_DOT": + return False # Value is a floating point number. + if tokens[0].value.startswith("SDL_PR") and (tokens[0].value.endswith("32") or tokens[0].value.endswith("64")): + return False # Data type for printing, which is not needed. + return bool( + tokens[0].value.startswith("KMOD_") + or tokens[0].value.startswith("SDL_") + or tokens[0].value.startswith("AUDIO_") + ) + + def on_directive_handle( + self, directive: Any, tokens: List[Any], if_passthru: bool, preceding_tokens: List[Any] + ) -> Any: + if directive.value == "define" and self._should_track_define(tokens): + if tokens[2].type == "CPP_STRING": + self.known_string_defines[tokens[0].value] = tokens[2].value + else: + self.known_defines.add(tokens[0].value) + return super().on_directive_handle(directive, tokens, if_passthru, preceding_tokens) + + +check_sdl_version() + +if sys.platform in ["win32", "darwin"]: + SDL2_PARSE_PATH = unpack_sdl2(SDL2_PARSE_VERSION) + SDL2_BUNDLE_PATH = unpack_sdl2(SDL2_BUNDLE_VERSION) + +SDL2_INCLUDE: Path +if sys.platform == "win32": + SDL2_INCLUDE = SDL2_PARSE_PATH / "include" +elif sys.platform == "darwin": + SDL2_INCLUDE = SDL2_PARSE_PATH / "Versions/A/Headers" +else: # Unix + matches = re.findall( + r"-I(\S+)", + subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True), + ) + assert matches + + for match in matches: + if os.path.isfile(Path(match, "SDL_stdinc.h")): + SDL2_INCLUDE = match + assert SDL2_INCLUDE + + +EXTRA_CDEF = """ +#define SDLK_SCANCODE_MASK ... + +extern "Python" { +// SDL_AudioCallback callback. +void _sdl_audio_callback(void* userdata, Uint8* stream, int len); +} +""" + + +def get_cdef() -> str: + parser = SDLParser() + parser.add_path(SDL2_INCLUDE) + parser.parse( + """ + // Remove extern keyword. + #define extern + // Ignore some SDL assert statements. + #define DOXYGEN_SHOULD_IGNORE_THIS + + #define _SIZE_T_DEFINED_ + typedef int... size_t; + + // Skip these headers. + #define SDL_atomic_h_ + #define SDL_thread_h_ + + #include + """ + ) + sdl2_cdef = parser.get_output() + sdl2_cdef = RE_VAFUNC.sub("", sdl2_cdef) + sdl2_cdef = RE_INLINE.sub("", sdl2_cdef) + sdl2_cdef = RE_PIXELFORMAT.sub(r"\g = ...", sdl2_cdef) + sdl2_cdef = RE_SDLK.sub(r"\g = ...", sdl2_cdef) + sdl2_cdef = RE_NEWLINES.sub("\n", sdl2_cdef) + sdl2_cdef = RE_ASSERT.sub("", sdl2_cdef) + sdl2_cdef = RE_PADDING.sub("padding[...];", sdl2_cdef) + sdl2_cdef = ( + sdl2_cdef.replace("int SDL_main(int argc, char *argv[]);", "") + .replace("typedef unsigned int uintptr_t;", "typedef int... uintptr_t;") + .replace("typedef unsigned int size_t;", "typedef int... size_t;") + ) + for name in FLEXIBLE_STRUCTS: + sdl2_cdef = sdl2_cdef.replace(f"}} {name};", f"...;}} {name};") + return sdl2_cdef + EXTRA_CDEF + + +include_dirs: List[str] = [] +extra_compile_args: List[str] = [] +extra_link_args: List[str] = [] + +libraries: List[str] = [] +library_dirs: List[str] = [] + + +if sys.platform == "darwin": + extra_link_args += ["-framework", "SDL2"] +else: + libraries += ["SDL2"] + +# Bundle the Windows SDL2 DLL. +if sys.platform == "win32": + include_dirs.append(str(SDL2_INCLUDE)) + ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} + SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) + library_dirs.append(str(SDL2_LIB_DIR)) + SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DEST.mkdir(exist_ok=True) + shutil.copy(SDL2_LIB_DIR / "SDL2.dll", SDL2_LIB_DEST) + +# Link to the SDL2 framework on MacOS. +# Delocate will bundle the binaries in a later step. +if sys.platform == "darwin": + HEADER_DIR = os.path.join(SDL2_PARSE_PATH, "Headers") + include_dirs.append(HEADER_DIR) + extra_link_args += [f"-F{SDL2_BUNDLE_PATH}/.."] + extra_link_args += ["-rpath", f"{SDL2_BUNDLE_PATH}/.."] + extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] + +# Use sdl2-config to link to SDL2 on Linux. +if sys.platform not in ["win32", "darwin"]: + extra_compile_args += subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True).strip().split() + extra_link_args += subprocess.check_output(["sdl2-config", "--libs"], universal_newlines=True).strip().split() diff --git a/parse_sdl2.py b/parse_sdl2.py deleted file mode 100644 index bd28167b..00000000 --- a/parse_sdl2.py +++ /dev/null @@ -1,175 +0,0 @@ -from __future__ import annotations - -import platform -import re -import sys -from os import PathLike -from pathlib import Path -from typing import Any, Dict, Iterator, Union - -import cffi # type: ignore - -# Various poorly made regular expressions, these will miss code which isn't -# supported by cffi. -RE_COMMENT = re.compile(r" */\*.*?\*/", re.DOTALL) -RE_REMOVALS = re.compile( - r"#ifndef DOXYGEN_SHOULD_IGNORE_THIS.*" r"#endif /\* DOXYGEN_SHOULD_IGNORE_THIS \*/", - re.DOTALL, -) -RE_DEFINE = re.compile(r"#define \w+(?!\() (?:.*?(?:\\\n)?)*$", re.MULTILINE) -RE_TYPEDEF = re.compile(r"^typedef[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) -RE_ENUM = re.compile(r"^(?:typedef )?enum[^{;#]*?(?:{[^}]*\n}[^;]*)?;", re.MULTILINE) -RE_DECL = re.compile(r"^extern[^#(]*\([^#]*?\);$", re.MULTILINE | re.DOTALL) -RE_ENDIAN = re.compile(r"#if SDL_BYTEORDER == SDL_LIL_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL) -RE_ENDIAN2 = re.compile(r"#if SDL_BYTEORDER == SDL_BIG_ENDIAN(.*?)#else(.*?)#endif", re.DOTALL) -RE_DEFINE_TRUNCATE = re.compile(r"(#define\s+\w+\s+).+$", flags=re.DOTALL) -RE_TYPEDEF_TRUNCATE = re.compile(r"(typedef\s+\w+\s+\w+)\s*{.*\n}(?=.*;$)", flags=re.DOTALL | re.MULTILINE) -RE_ENUM_TRUNCATE = re.compile(r"(\w+\s*=).+?(?=,$|})(?![^(']*\))", re.MULTILINE | re.DOTALL) -RE_EVENT_PADDING = re.compile(r"Uint8 padding\[[^]]+\];", re.MULTILINE | re.DOTALL) - - -def get_header(path: Path) -> str: - """Return the source of a header in a partially preprocessed state.""" - header = path.read_text(encoding="utf-8") - # Remove Doxygen code. - header = RE_REMOVALS.sub("", header) - # Remove comments. - header = RE_COMMENT.sub("", header) - # Deal with endianness in "SDL_audio.h". - header = RE_ENDIAN.sub(r"\1" if sys.byteorder == "little" else r"\2", header) - header = RE_ENDIAN2.sub(r"\1" if sys.byteorder != "little" else r"\2", header) - - # Ignore bad ARM compiler typedef. - header = header.replace("typedef int SDL_bool;", "") - return header - - -# Remove non-integer definitions. -DEFINE_BLACKLIST = [ - "SDL_AUDIOCVT_PACKED", - "SDL_BlitScaled", - "SDL_BlitSurface", - "SDL_Colour", - "SDL_IPHONE_MAX_GFORCE", -] - - -def parse(header: str, NEEDS_PACK4: bool) -> Iterator[str]: - """Pull individual sections from a header, processing them as needed.""" - for define in RE_DEFINE.findall(header): - if any(item in define for item in DEFINE_BLACKLIST): - continue # Remove non-integer definitions. - if '"' in define: - continue # Ignore definitions with strings. - # Replace various definitions with "..." since cffi is limited here. - yield RE_DEFINE_TRUNCATE.sub(r"\1 ...", define) - - for enum in RE_ENUM.findall(header): - yield RE_ENUM_TRUNCATE.sub(r"\1 ...", enum) - header = header.replace(enum, "") - - for typedef in RE_TYPEDEF.findall(header): - # Special case for SDL window flags enum. - if "SDL_WINDOW_FULLSCREEN_DESKTOP" in typedef: - typedef = typedef.replace("( SDL_WINDOW_FULLSCREEN | 0x00001000 )", "...") - # Detect array sizes at compile time. - typedef = typedef.replace("SDL_TEXTINPUTEVENT_TEXT_SIZE", "...") - typedef = typedef.replace("SDL_TEXTEDITINGEVENT_TEXT_SIZE", "...") - typedef = typedef.replace("SDL_AUDIOCVT_MAX_FILTERS + 1", "...") - - typedef = typedef.replace("SDLCALL", " ") - typedef = typedef.replace("SDL_AUDIOCVT_PACKED ", "") - typedef = RE_EVENT_PADDING.sub("Uint8 padding[...];", typedef) - - if NEEDS_PACK4 and "typedef struct SDL_AudioCVT" in typedef: - typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) - if NEEDS_PACK4 and "typedef struct SDL_TouchFingerEvent" in typedef: - typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) - if NEEDS_PACK4 and "typedef struct SDL_MultiGestureEvent" in typedef: - typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) - if NEEDS_PACK4 and "typedef struct SDL_DollarGestureEvent" in typedef: - typedef = RE_TYPEDEF_TRUNCATE.sub(r"\1 { ...; }", typedef) - yield typedef - - for decl in RE_DECL.findall(header): - if "SDL_RWops" in decl: - continue # Ignore SDL_RWops functions. - if "va_list" in decl: - continue - decl = re.sub(r"SDL_PRINTF_VARARG_FUNC\(\w*\)", "", decl) - decl = decl.replace("SDL_DEPRECATED", "") - decl = decl.replace("SDLCALL", "") - decl = re.sub(r"extern\s+DECLSPEC", "", decl) - yield decl.replace("SDL_PRINTF_FORMAT_STRING ", "") - - -# Parsed headers excluding "SDL_stdinc.h" -HEADERS = [ - "SDL_rect.h", - "SDL_pixels.h", - "SDL_blendmode.h", - "SDL_error.h", - "SDL_surface.h", - "SDL_video.h", - "SDL_render.h", - "SDL_audio.h", - "SDL_clipboard.h", - "SDL_touch.h", - "SDL_gesture.h", - "SDL_hints.h", - "SDL_joystick.h", - "SDL_haptic.h", - "SDL_power.h", - "SDL_log.h", - "SDL_messagebox.h", - "SDL_mouse.h", - "SDL_timer.h", - "SDL_keycode.h", - "SDL_scancode.h", - "SDL_keyboard.h", - "SDL_events.h", - "SDL.h", - "SDL_version.h", -] - -# It's easier to manually add these instead of parsing SDL_stdinc.h -CDEF_EXTRA = """ -void* SDL_calloc(size_t nmemb, size_t size); -void SDL_free(void *mem); -""" - - -def add_to_ffi(ffi: cffi.FFI, path: Union[str, PathLike[str]]) -> None: - path = Path(path) - BITS, _ = platform.architecture() - cdef_args: Dict[str, Any] = {} - NEEDS_PACK4 = False - if sys.platform == "win32" and BITS == "32bit": - NEEDS_PACK4 = True - # The following line is required but cffi does not currently support - # it for ABI mode. - # cdef_args["pack"] = 4 - - ffi.cdef( - "\n".join(RE_TYPEDEF.findall(get_header(path / "SDL_stdinc.h"))).replace("SDLCALL ", ""), - **cdef_args, - ) - ffi.cdef(CDEF_EXTRA, **cdef_args) - for header in HEADERS: - try: - for code in parse(get_header(path / header), NEEDS_PACK4): - if "typedef struct SDL_AudioCVT" in code and sys.platform != "win32" and not NEEDS_PACK4: - # This specific struct needs to be packed. - ffi.cdef(code, packed=1) - continue - ffi.cdef(code, **cdef_args) - except Exception: - print("Error parsing %r code:\n%s" % (header, code)) - raise - - -def get_ffi(path: Union[str, PathLike[str]]) -> cffi.FFI: - """Return an ffi for SDL2, needs to be compiled.""" - ffi = cffi.FFI() - add_to_ffi(ffi, path) - return ffi diff --git a/pyproject.toml b/pyproject.toml index df1a4a46..1fddcde9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,11 @@ [build-system] -requires = ["setuptools>=57.0.0", "wheel", "cffi~=1.13", "pycparser>=2.14"] +requires = [ + "setuptools>=57.0.0", + "wheel", + "cffi>=1.15", + "pycparser>=2.14", + "pcpp==1.30", +] build-backend = "setuptools.build_meta" [tool.black] diff --git a/requirements.txt b/requirements.txt index 3ce930b0..b5a68d21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ -cffi~=1.13 +cffi>=1.15 numpy>=1.20.3 pycparser>=2.14 setuptools>=36.0.1 types-setuptools types-tabulate typing_extensions +pcpp==1.30 diff --git a/setup.py b/setup.py index d78363f3..2f81769d 100755 --- a/setup.py +++ b/setup.py @@ -114,11 +114,12 @@ def check_sdl_version() -> None: python_requires=">=3.7", setup_requires=[ *pytest_runner, - "cffi~=1.13", + "cffi>=1.15", "pycparser>=2.14", + "pcpp==1.30", ], install_requires=[ - "cffi~=1.13", # Also required by pyproject.toml. + "cffi>=1.15", # Also required by pyproject.toml. "numpy>=1.20.3" if not is_pypy else "", "typing_extensions", ], From cbb56b4a84c9cd0ea9ea88332234f9c88298ae07 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 26 Jan 2022 15:40:27 -0800 Subject: [PATCH 0629/1101] Backport SDL audio features from ESDL project. Not making these public yet. --- tcod/sdl/__init__.py | 0 tcod/sdl/audio.py | 217 ++++++++++++++++++++++++++++++++++ tcod/sdl/sys.py | 72 +++++++++++ tcod/{sdl.py => sdl/video.py} | 0 4 files changed, 289 insertions(+) create mode 100644 tcod/sdl/__init__.py create mode 100644 tcod/sdl/audio.py create mode 100644 tcod/sdl/sys.py rename tcod/{sdl.py => sdl/video.py} (100%) diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py new file mode 100644 index 00000000..787ccf0d --- /dev/null +++ b/tcod/sdl/audio.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import sys +import threading +import time +import weakref +from typing import Any, Iterator, List, Optional + +import numpy as np +from numpy.typing import ArrayLike, DTypeLike, NDArray + +import tcod.sdl.sys +from tcod.loader import ffi, lib + + +def _get_format(format: DTypeLike) -> int: + """Return a SDL_AudioFormat bitfield from a NumPy dtype.""" + dt: Any = np.dtype(format) + assert dt.fields is None + bitsize = dt.itemsize * 8 + assert 0 < bitsize <= lib.SDL_AUDIO_MASK_BITSIZE + assert dt.str[1] in "uif" + is_signed = dt.str[1] != "u" + is_float = dt.str[1] == "f" + byteorder = dt.byteorder + if byteorder == "=": + byteorder = "<" if sys.byteorder == "little" else ">" + + return ( # type: ignore + bitsize + | (lib.SDL_AUDIO_MASK_DATATYPE * is_float) + | (lib.SDL_AUDIO_MASK_ENDIAN * (byteorder == ">")) + | (lib.SDL_AUDIO_MASK_SIGNED * is_signed) + ) + + +def _dtype_from_format(format: int) -> np.dtype[Any]: + """Return a dtype from a SDL_AudioFormat.""" + bitsize = format & lib.SDL_AUDIO_MASK_BITSIZE + assert bitsize % 8 == 0 + bytesize = bitsize // 8 + byteorder = ">" if format & lib.SDL_AUDIO_MASK_ENDIAN else "<" + if format & lib.SDL_AUDIO_MASK_DATATYPE: + kind = "f" + elif format & lib.SDL_AUDIO_MASK_SIGNED: + kind = "i" + else: + kind = "u" + return np.dtype(f"{byteorder}{kind}{bytesize}") + + +class AudioDevice: + def __init__( + self, + device: Optional[str] = None, + capture: bool = False, + *, + frequency: int = 44100, + format: DTypeLike = np.float32, + channels: int = 2, + samples: int = 0, + allowed_changes: int = 0, + ): + self.__sdl_subsystems = tcod.sdl.sys._ScopeInit(tcod.sdl.sys.Subsystem.AUDIO) + self.__handle = ffi.new_handle(weakref.ref(self)) + desired = ffi.new( + "SDL_AudioSpec*", + { + "freq": frequency, + "format": _get_format(format), + "channels": channels, + "samples": samples, + "callback": ffi.NULL, + "userdata": self.__handle, + }, + ) + obtained = ffi.new("SDL_AudioSpec*") + self.device_id = lib.SDL_OpenAudioDevice( + ffi.NULL if device is None else device.encode("utf-8"), + capture, + desired, + obtained, + allowed_changes, + ) + assert self.device_id != 0, tcod.sdl.sys._get_error() + self.frequency = obtained.freq + self.is_capture = capture + self.format = _dtype_from_format(obtained.format) + self.channels = int(obtained.channels) + self.silence = int(obtained.silence) + self.samples = int(obtained.samples) + self.buffer_size = int(obtained.size) + self.unpause() + + @property + def _sample_size(self) -> int: + return self.format.itemsize * self.channels + + def pause(self) -> None: + lib.SDL_PauseAudioDevice(self.device_id, True) + + def unpause(self) -> None: + lib.SDL_PauseAudioDevice(self.device_id, False) + + def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]: + if samples.dtype != self.format: + raise TypeError(f"Expected an array of dtype {self.format}, got {samples.dtype} instead.") + return samples + + def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: + if isinstance(samples_, np.ndarray): + samples_ = self._verify_array_format(samples_) + samples: NDArray[Any] = np.asarray(samples_, dtype=self.format) + if len(samples.shape) < 2: + samples = samples[:, np.newaxis] + return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) + + @property + def queued_audio_bytes(self) -> int: + return int(lib.SDL_GetQueuedAudioSize(self.device_id)) + + def queue_audio(self, samples: ArrayLike) -> None: + assert not self.is_capture + samples = self._convert_array(samples) + buffer = ffi.from_buffer(samples) + lib.SDL_QueueAudio(self.device_id, buffer, len(buffer)) + + def dequeue_audio(self) -> NDArray[Any]: + assert self.is_capture + out_samples = self.queued_audio_bytes // self._sample_size + out = np.empty((out_samples, self.channels), self.format) + buffer = ffi.from_buffer(out) + bytes_returned = lib.SDL_DequeueAudio(self.device_id, buffer, len(buffer)) + samples_returned = bytes_returned // self._sample_size + assert samples_returned == out_samples + return out + + def __del__(self) -> None: + self.close() + + def close(self) -> None: + if not self.device_id: + return + lib.SDL_CloseAudioDevice(self.device_id) + self.device_id = 0 + + @staticmethod + def __default_callback(stream: NDArray[Any], silence: int) -> None: + stream[...] = silence + + +class Mixer(threading.Thread): + def __init__(self, device: AudioDevice): + super().__init__(daemon=True) + self.device = device + self.device.unpause() + self.start() + + def run(self) -> None: + buffer = np.full((self.device.samples, self.device.channels), self.device.silence, dtype=self.device.format) + while True: + time.sleep(0.001) + if self.device.queued_audio_bytes == 0: + self.on_stream(buffer) + self.device.queue_audio(buffer) + buffer[:] = self.device.silence + + def on_stream(self, stream: NDArray[Any]) -> None: + pass + + +class BasicMixer(Mixer): + def __init__(self, device: AudioDevice): + super().__init__(device) + self.play_buffers: List[List[NDArray[Any]]] = [] + + def play(self, sound: ArrayLike) -> None: + array = np.asarray(sound, dtype=self.device.format) + assert array.size + if len(array.shape) == 1: + array = array[:, np.newaxis] + chunks: List[NDArray[Any]] = np.split(array, range(0, len(array), self.device.samples)[1:])[::-1] + self.play_buffers.append(chunks) + + def on_stream(self, stream: NDArray[Any]) -> None: + super().on_stream(stream) + for chunks in self.play_buffers: + chunk = chunks.pop() + stream[: len(chunk)] += chunk + + self.play_buffers = [chunks for chunks in self.play_buffers if chunks] + + +@ffi.def_extern() # type: ignore +def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: + """Handle audio device callbacks.""" + device: Optional[AudioDevice] = ffi.from_handle(userdata)() + assert device is not None + _ = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) + + +def _get_devices(capture: bool) -> Iterator[str]: + """Get audio devices from SDL_GetAudioDeviceName.""" + with tcod.sdl.sys._ScopeInit(tcod.sdl.sys.Subsystem.AUDIO): + device_count = lib.SDL_GetNumAudioDevices(capture) + for i in range(device_count): + yield str(ffi.string(lib.SDL_GetAudioDeviceName(i, capture)), encoding="utf-8") + + +def get_devices() -> Iterator[str]: + """Iterate over the available audio output devices.""" + yield from _get_devices(capture=False) + + +def get_capture_devices() -> Iterator[str]: + """Iterate over the available audio capture devices.""" + yield from _get_devices(capture=True) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py new file mode 100644 index 00000000..a47b2a70 --- /dev/null +++ b/tcod/sdl/sys.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import enum +from typing import Any, Tuple + +from tcod.loader import ffi, lib + + +class Subsystem(enum.IntFlag): + TIMER = lib.SDL_INIT_TIMER + AUDIO = lib.SDL_INIT_AUDIO + VIDEO = lib.SDL_INIT_VIDEO + JOYSTICK = lib.SDL_INIT_JOYSTICK + HAPTIC = lib.SDL_INIT_HAPTIC + GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER + EVENTS = lib.SDL_INIT_EVENTS + SENSOR = getattr(lib, "SDL_INIT_SENSOR", 0) + EVERYTHING = lib.SDL_INIT_EVERYTHING + + +def _check(result: int) -> int: + if result < 0: + raise RuntimeError(_get_error()) + return result + + +def init(flags: int = Subsystem.EVERYTHING) -> None: + _check(lib.SDL_InitSubSystem(flags)) + + +def quit(flags: int = Subsystem.EVERYTHING) -> None: + lib.SDL_QuitSubSystem(flags) + + +class _ScopeInit: + def __init__(self, flags: int) -> None: + init(flags) + self.flags = flags + + def close(self) -> None: + if self.flags: + quit(self.flags) + self.flags = 0 + + def __del__(self) -> None: + self.close() + + def __enter__(self) -> _ScopeInit: + return self + + def __exit__(self, *args: Any) -> None: + self.close() + + +def _get_error() -> str: + return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") + + +class _PowerState(enum.IntEnum): + UNKNOWN = lib.SDL_POWERSTATE_UNKNOWN + ON_BATTERY = lib.SDL_POWERSTATE_ON_BATTERY + NO_BATTERY = lib.SDL_POWERSTATE_NO_BATTERY + CHARGING = lib.SDL_POWERSTATE_CHARGING + CHARGED = lib.SDL_POWERSTATE_CHARGED + + +def _get_power_info() -> Tuple[_PowerState, int, int]: + buffer = ffi.new("int[2]") + power_state = _PowerState(lib.SDL_GetPowerInfo(buffer, buffer + 1)) + seconds_of_power = buffer[0] + percenage = buffer[1] + return power_state, seconds_of_power, percenage diff --git a/tcod/sdl.py b/tcod/sdl/video.py similarity index 100% rename from tcod/sdl.py rename to tcod/sdl/video.py From 9715a4617ff812693a1b91a972ac40815e779f6b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 27 Jan 2022 23:48:50 -0800 Subject: [PATCH 0630/1101] Catch logs from SDL. --- build_sdl.py | 1 + tcod/sdl/__init__.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/build_sdl.py b/build_sdl.py index 7bc0bd8d..9a26132e 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -205,6 +205,7 @@ def on_directive_handle( extern "Python" { // SDL_AudioCallback callback. void _sdl_audio_callback(void* userdata, Uint8* stream, int len); +void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message); } """ diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index e69de29b..cd77bcf2 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import logging +from typing import Any + +from tcod.loader import ffi, lib + +logger = logging.getLogger(__name__) + +_LOG_PRIORITY = { + int(lib.SDL_LOG_PRIORITY_VERBOSE): logging.DEBUG, + int(lib.SDL_LOG_PRIORITY_DEBUG): logging.DEBUG, + int(lib.SDL_LOG_PRIORITY_INFO): logging.INFO, + int(lib.SDL_LOG_PRIORITY_WARN): logging.WARNING, + int(lib.SDL_LOG_PRIORITY_ERROR): logging.ERROR, + int(lib.SDL_LOG_PRIORITY_CRITICAL): logging.CRITICAL, +} + + +@ffi.def_extern() # type: ignore +def _sdl_log_output_function(_userdata: Any, category: int, priority: int, message: Any) -> None: + """Pass logs sent by SDL to Python's logging system.""" + logger.log(_LOG_PRIORITY.get(priority, 0), "%i:%s", category, ffi.string(message).decode("utf-8")) + + +lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) From afdd1d4c58c5a24d939fd7df1187fcf6af5eaa9c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Jan 2022 13:39:15 -0800 Subject: [PATCH 0631/1101] Add SDL Renderer and Texture helpers. Perform some cleanups. --- .vscode/settings.json | 3 + tcod/sdl/__init__.py | 12 ++++ tcod/sdl/audio.py | 3 +- tcod/sdl/render.py | 127 ++++++++++++++++++++++++++++++++++++++++++ tcod/sdl/sys.py | 11 +--- 5 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tcod/sdl/render.py diff --git a/.vscode/settings.json b/.vscode/settings.json index ee28f37e..5b8556b3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -170,6 +170,7 @@ "LMASK", "lmeta", "lodepng", + "LPAREN", "LTCG", "lucida", "LWIN", @@ -250,6 +251,7 @@ "RRGGBB", "rtype", "RWIN", + "RWOPS", "scalex", "scaley", "Scancode", @@ -264,6 +266,7 @@ "setuptools", "SHADOWCAST", "SMILIE", + "snprintf", "stdeb", "struct", "structs", diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index cd77bcf2..66aa7885 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -23,4 +23,16 @@ def _sdl_log_output_function(_userdata: Any, category: int, priority: int, messa logger.log(_LOG_PRIORITY.get(priority, 0), "%i:%s", category, ffi.string(message).decode("utf-8")) +def _get_error() -> str: + """Return a message from SDL_GetError as a Unicode string.""" + return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") + + +def _check(result: int) -> int: + """Check if an SDL function returned without errors, and raise an exception if it did.""" + if result < 0: + raise RuntimeError(_get_error()) + return result + + lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 787ccf0d..ac5cd0d1 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -11,6 +11,7 @@ import tcod.sdl.sys from tcod.loader import ffi, lib +from tcod.sdl import _get_error def _get_format(format: DTypeLike) -> int: @@ -82,7 +83,7 @@ def __init__( obtained, allowed_changes, ) - assert self.device_id != 0, tcod.sdl.sys._get_error() + assert self.device_id != 0, _get_error() self.frequency = obtained.freq self.is_capture = capture self.format = _dtype_from_format(obtained.format) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py new file mode 100644 index 00000000..fa5ae333 --- /dev/null +++ b/tcod/sdl/render.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +from typing import Any, Optional, Tuple + +import numpy as np +from numpy.typing import NDArray + +from tcod.loader import ffi, lib +from tcod.sdl import _check + + +class Texture: + def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: + self.p = sdl_texture_p + self._sdl_renderer_p = sdl_renderer_p # Keep alive. + + def __eq__(self, other: Any) -> bool: + return bool(self.p == getattr(other, "p", None)) + + def _query(self) -> Tuple[int, int, int, int]: + """Return (format, access, width, height).""" + format = ffi.new("uint32_t*") + buffer = ffi.new("int[3]") + lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) + return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2]) + + @property + def format(self) -> int: + """Texture format, read only.""" + buffer = ffi.new("uint32_t*") + lib.SDL_QueryTexture(self.p, buffer, ffi.NULL, ffi.NULL, ffi.NULL) + return int(buffer[0]) + + @property + def access(self) -> int: + """Texture access mode, read only.""" + buffer = ffi.new("int*") + lib.SDL_QueryTexture(self.p, ffi.NULL, buffer, ffi.NULL, ffi.NULL) + return int(buffer[0]) + + @property + def width(self) -> int: + """Texture pixel width, read only.""" + buffer = ffi.new("int*") + lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, buffer, ffi.NULL) + return int(buffer[0]) + + @property + def height(self) -> int: + """Texture pixel height, read only.""" + buffer = ffi.new("int*") + lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, ffi.NULL, buffer) + return int(buffer[0]) + + @property + def alpha_mod(self) -> int: + """Texture alpha modulate value, can be set to: 0 - 255.""" + return int(lib.SDL_GetTextureAlphaMod(self.p)) + + @alpha_mod.setter + def alpha_mod(self, value: int) -> None: + _check(lib.SDL_SetTextureAlphaMod(self.p, value)) + + @property + def blend_mode(self) -> int: + """Texture blend mode, can be set.""" + return int(lib.SDL_GetTextureBlendMode(self.p)) + + @blend_mode.setter + def blend_mode(self, value: int) -> None: + _check(lib.SDL_SetTextureBlendMode(self.p, value)) + + @property + def rgb_mod(self) -> Tuple[int, int, int]: + """Texture RGB color modulate values, can be set.""" + rgb = ffi.new("uint8_t[3]") + _check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2)) + return int(rgb[0]), int(rgb[1]), int(rgb[2]) + + @rgb_mod.setter + def rgb_mod(self, rgb: Tuple[int, int, int]) -> None: + _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2])) + + +class Renderer: + def __init__(self, sdl_renderer_p: Any) -> None: + if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): + raise TypeError(f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)}).") + self.p = sdl_renderer_p + + def __eq__(self, other: Any) -> bool: + return bool(self.p == getattr(other, "p", None)) + + def new_texture( + self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None + ) -> Texture: + """Allocate and return a new Texture for this renderer.""" + if format is None: + format = 0 + if access is None: + access = int(lib.SDL_TEXTUREACCESS_STATIC) + format = int(lib.SDL_PIXELFORMAT_RGBA32) + access = int(lib.SDL_TEXTUREACCESS_STATIC) + texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture) + return Texture(texture_p, self.p) + + def upload_texture( + self, pixels: NDArray[Any], *, format: Optional[int] = None, access: Optional[int] = None + ) -> Texture: + """Return a new Texture from an array of pixels.""" + if format is None: + assert len(pixels.shape) == 3 + assert pixels.dtype == np.uint8 + if pixels.shape[2] == 4: + format = int(lib.SDL_PIXELFORMAT_RGBA32) + elif pixels.shape[2] == 3: + format = int(lib.SDL_PIXELFORMAT_RGB32) + else: + assert False + + texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access) + if not pixels[0].flags["C_CONTIGUOUS"]: + pixels = np.ascontiguousarray(pixels) + _check( + lib.SDL_UpdateTexture(texture.p, ffi.NULL, ffi.cast("const void*", pixels.ctypes.data), pixels.strides[0]) + ) + return texture diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index a47b2a70..a16d09a0 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -4,6 +4,7 @@ from typing import Any, Tuple from tcod.loader import ffi, lib +from tcod.sdl import _check class Subsystem(enum.IntFlag): @@ -18,12 +19,6 @@ class Subsystem(enum.IntFlag): EVERYTHING = lib.SDL_INIT_EVERYTHING -def _check(result: int) -> int: - if result < 0: - raise RuntimeError(_get_error()) - return result - - def init(flags: int = Subsystem.EVERYTHING) -> None: _check(lib.SDL_InitSubSystem(flags)) @@ -52,10 +47,6 @@ def __exit__(self, *args: Any) -> None: self.close() -def _get_error() -> str: - return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") - - class _PowerState(enum.IntEnum): UNKNOWN = lib.SDL_POWERSTATE_UNKNOWN ON_BATTERY = lib.SDL_POWERSTATE_ON_BATTERY From 849201439ee85aea4a3c3966f67ad6b35d9f7bc3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 15:49:14 -0800 Subject: [PATCH 0632/1101] Port libtcod direct SDL rendering functions. Allow access to the fancy SDL classes from tcod contexts. --- tcod/context.py | 20 +++++++++++++++++ tcod/render.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++ tcod/sdl/render.py | 2 +- tcod/sdl/video.py | 11 ++++++++- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tcod/render.py diff --git a/tcod/context.py b/tcod/context.py index e62ac042..3c8470b1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -58,6 +58,8 @@ import tcod import tcod.event +import tcod.sdl.render +import tcod.sdl.video import tcod.tileset from tcod._internal import _check, _check_warn, pending_deprecate from tcod.loader import ffi, lib @@ -351,6 +353,24 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: ''' # noqa: E501 return lib.TCOD_context_get_sdl_window(self._context_p) + @property + def sdl_window(self) -> Optional[tcod.sdl.video.Window]: + """Return a tcod.sdl.video.Window referencing this contexts SDL window if it exists. + + .. versionadded:: 13.4 + """ + p = self.sdl_window_p + return tcod.sdl.video.Window(p) if p else None + + @property + def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: + """Return a tcod.sdl.render.Renderer referencing this contexts SDL renderer if it exists. + + .. versionadded:: 13.4 + """ + p = lib.TCOD_context_get_sdl_renderer(self._context_p) + return tcod.sdl.render.Renderer(p) if p else None + def __reduce__(self) -> NoReturn: """Contexts can not be pickled, so this class will raise :class:`pickle.PicklingError`. diff --git a/tcod/render.py b/tcod/render.py new file mode 100644 index 00000000..1cfc9f9c --- /dev/null +++ b/tcod/render.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import Optional + +import tcod.console +import tcod.sdl.render +import tcod.tileset +from tcod._internal import _check, _check_p +from tcod.loader import ffi, lib + + +class SDLTilesetAtlas: + """Prepares a tileset for rendering using SDL.""" + + def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Tileset) -> None: + self._renderer = renderer + self.tileset = tileset + self.p = ffi.gc(_check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete) + + +class SDLConsoleRender: + """Holds an internal cache console and texture which are used to optimized console rendering.""" + + def __init__(self, atlas: SDLTilesetAtlas) -> None: + self._atlas = atlas + self._renderer = atlas._renderer + self._cache_console: Optional[tcod.console.Console] = None + self._texture: Optional[tcod.sdl.render.Texture] = None + + def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture: + """Render a console to a cached Texture and then return the Texture. + + You should not draw onto the returned Texture as only changed parts of it will be updated on the next call. + + This function requires the SDL renderer to have target texture support. + It will also change the SDL target texture for the duration of the call. + """ + if self._cache_console and ( + self._cache_console.width != console.width or self._cache_console.height != console.height + ): + self._cache_console = None + self._texture = None + if self._cache_console is None or self._texture is None: + self._cache_console = tcod.console.Console(console.width, console.height) + self._texture = self._renderer.new_texture( + self._atlas.tileset.tile_width * console.width, + self._atlas.tileset.tile_height * console.height, + format=int(lib.SDL_PIXELFORMAT_RGBA32), + access=int(lib.SDL_TEXTUREACCESS_TARGET), + ) + _check( + lib.TCOD_sdl2_render_texture( + self._atlas.p, console.console_c, self._cache_console.console_c, self._texture.p + ) + ) + return self._texture diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index fa5ae333..2fa3601e 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -116,7 +116,7 @@ def upload_texture( elif pixels.shape[2] == 3: format = int(lib.SDL_PIXELFORMAT_RGB32) else: - assert False + raise TypeError(f"Can't determine the format required for an array of shape {pixels.shape}.") texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access) if not pixels[0].flags["C_CONTIGUOUS"]: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 470011e6..635d5bda 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -114,8 +114,17 @@ def size(self, xy: Tuple[int, int]) -> None: x, y = xy lib.SDL_SetWindowSize(self.p, x, y) + @property + def title(self) -> str: + """The title of the window. You may set this attribute to change it.""" + return str(ffi.string(lib.SDL_GetWindowtitle(self.p)), encoding="utf-8") + + @title.setter + def title(self, value: str) -> None: + lib.SDL_SetWindowtitle(self.p, value.encode("utf-8")) + -def get_active_window() -> Window: +def _get_active_window() -> Window: """Return the SDL2 window current managed by libtcod. Will raise an error if libtcod does not currently have a window. From ebbac13a87069660d571601fb364a8bd1bc0f95c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 15:59:50 -0800 Subject: [PATCH 0633/1101] Port SDL_RenderCopy. This should be the minimum needed to render tcod tileset directly. --- tcod/sdl/render.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 2fa3601e..641a0bf6 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -91,6 +91,20 @@ def __init__(self, sdl_renderer_p: Any) -> None: def __eq__(self, other: Any) -> bool: return bool(self.p == getattr(other, "p", None)) + def copy( + self, + texture: Texture, + source: Optional[Tuple[int, int, int, int]] = None, + dest: Optional[Tuple[int, int, int, int]] = None, + ) -> None: + """Copy a texture to the rendering target. + + `source` and `dest` are (x, y, width, height) regions of the texture parameter and target texture respectively. + """ + source_ = ffi.NULL if source is None else ffi.new("SDL_Rect*", source) + dest_ = ffi.NULL if dest is None else ffi.new("SDL_Rect*", dest) + _check(lib.SDL_RenderCopy(self.p, texture.p, source_, dest_)) + def new_texture( self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None ) -> Texture: From 957e4b778b7abfe6d6deada94317fab850343316 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 16:29:32 -0800 Subject: [PATCH 0634/1101] Run the latest version of Black. --- examples/samples_libtcodpy.py | 8 ++++---- examples/samples_tcod.py | 6 +++--- scripts/get_release_description.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 6b5b05a7..ba91aeca 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -1422,8 +1422,8 @@ def render_py(first, key, mouse): ) + libtcod.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0) if use_numpy: # squared distance from center, clipped to sensible minimum and maximum values - sqr_dist = xc ** 2 + yc ** 2 - sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2) + sqr_dist = xc**2 + yc**2 + sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V**2) # one coordinate into the texture, represents depth in the tunnel v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t @@ -1444,8 +1444,8 @@ def render_py(first, key, mouse): for y in range(-HALF_H, HALF_H): for x in range(-HALF_W, HALF_W): # squared distance from center, clipped to sensible minimum and maximum values - sqr_dist = x ** 2 + y ** 2 - sqr_dist = min(max(sqr_dist, 1.0 / RES_V), RES_V ** 2) + sqr_dist = x**2 + y**2 + sqr_dist = min(max(sqr_dist, 1.0 / RES_V), RES_V**2) # one coordinate into the texture, represents depth in the tunnel v = TEX_STRETCH * float(RES_V) / sqr_dist + frac_t diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 2d95b315..d1fc0f79 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -608,7 +608,7 @@ def on_draw(self) -> None: x = x.astype(np.float32) - torch_x y = y.astype(np.float32) - torch_y - distance_squared = x ** 2 + y ** 2 # 2D squared distance array. + distance_squared = x**2 + y**2 # 2D squared distance array. # Get the currently visible cells. visible = (distance_squared < SQUARED_TORCH_RADIUS) & fov @@ -1299,8 +1299,8 @@ def on_draw(self) -> None: # squared distance from center, # clipped to sensible minimum and maximum values - sqr_dist = xc ** 2 + yc ** 2 - sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V ** 2) + sqr_dist = xc**2 + yc**2 + sqr_dist = sqr_dist.clip(1.0 / RES_V, RES_V**2) # one coordinate into the texture, represents depth in the tunnel vv = TEX_STRETCH * float(RES_V) / sqr_dist + self.frac_t diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 29844caf..7d47d9f9 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -7,7 +7,7 @@ TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n" -RE_BODY = re.compile(fr".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) +RE_BODY = re.compile(rf".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) def main() -> None: From c0a890c9a4d99480749cb9555bdff32644e31319 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 16:31:04 -0800 Subject: [PATCH 0635/1101] Fix remaining issues with rendering. --- tcod/render.py | 10 ++++++---- tcod/sdl/render.py | 26 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tcod/render.py b/tcod/render.py index 1cfc9f9c..21e98db0 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -48,9 +48,11 @@ def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture: format=int(lib.SDL_PIXELFORMAT_RGBA32), access=int(lib.SDL_TEXTUREACCESS_TARGET), ) - _check( - lib.TCOD_sdl2_render_texture( - self._atlas.p, console.console_c, self._cache_console.console_c, self._texture.p + + with self._renderer.set_render_target(self._texture): + _check( + lib.TCOD_sdl2_render_texture( + self._atlas.p, console.console_c, self._cache_console.console_c, self._texture.p + ) ) - ) return self._texture diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 641a0bf6..bc929bb5 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -82,6 +82,20 @@ def rgb_mod(self, rgb: Tuple[int, int, int]) -> None: _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2])) +class _RestoreTargetContext: + """A context manager which tracks the current render target and restores it on exiting.""" + + def __init__(self, renderer: Renderer) -> None: + self.renderer = renderer + self.old_texture_p = lib.SDL_GetRenderTarget(renderer.p) + + def __enter__(self) -> None: + pass + + def __exit__(self, *_: Any) -> None: + _check(lib.SDL_SetRenderTarget(self.renderer.p, self.old_texture_p)) + + class Renderer: def __init__(self, sdl_renderer_p: Any) -> None: if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): @@ -105,6 +119,10 @@ def copy( dest_ = ffi.NULL if dest is None else ffi.new("SDL_Rect*", dest) _check(lib.SDL_RenderCopy(self.p, texture.p, source_, dest_)) + def present(self) -> None: + """Present the currently rendered image to the screen.""" + lib.SDL_RenderPresent(self.p) + def new_texture( self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None ) -> Texture: @@ -113,11 +131,15 @@ def new_texture( format = 0 if access is None: access = int(lib.SDL_TEXTUREACCESS_STATIC) - format = int(lib.SDL_PIXELFORMAT_RGBA32) - access = int(lib.SDL_TEXTUREACCESS_STATIC) texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture) return Texture(texture_p, self.p) + def set_render_target(self, texture: Texture) -> _RestoreTargetContext: + """Change the render target to `texture`, returns a context that will restore the original target when exited.""" + restore = _RestoreTargetContext(self) + _check(lib.SDL_SetRenderTarget(self.p, texture.p)) + return restore + def upload_texture( self, pixels: NDArray[Any], *, format: Optional[int] = None, access: Optional[int] = None ) -> Texture: From 0165926ba139399f293d1875b4e624f9761490ef Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 16:36:18 -0800 Subject: [PATCH 0636/1101] Have Flake8 ignore slightly too long lines. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4b26be03..faafa1fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ test=pytest [flake8] ignore = E203 W503 -max-line-length = 120 +max-line-length = 130 [mypy] python_version = 3.8 From 1cf53b4988790d6c5366fb211eefbffb80ae4e2c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 29 Jan 2022 16:53:47 -0800 Subject: [PATCH 0637/1101] Add tcod.sdl package to setup.py. --- examples/samples_tcod.py | 9 ++++++++- setup.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index d1fc0f79..8eba7149 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -18,6 +18,7 @@ import numpy as np import tcod +import tcod.render from numpy.typing import NDArray if not sys.warnoptions: @@ -1417,6 +1418,10 @@ def main() -> None: global context, tileset tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) context = init_context(tcod.RENDERER_SDL2) + sdl_renderer = context.sdl_renderer + assert sdl_renderer + atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset) + console_render = tcod.render.SDLConsoleRender(atlas) try: SAMPLES[cur_sample].on_enter() @@ -1429,7 +1434,9 @@ def main() -> None: SAMPLES[cur_sample].on_draw() sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() - context.present(root_console) + # context.present(root_console) + sdl_renderer.copy(console_render.render(root_console)) + sdl_renderer.present() handle_time() handle_events() finally: diff --git a/setup.py b/setup.py index 2f81769d..411653fd 100755 --- a/setup.py +++ b/setup.py @@ -109,7 +109,7 @@ def check_sdl_version() -> None: "Forum": "https://github.com/libtcod/python-tcod/discussions", }, py_modules=["libtcodpy"], - packages=["tcod", "tcod.__pyinstaller"], + packages=["tcod", "tcod.sdl", "tcod.__pyinstaller"], package_data={"tcod": get_package_data()}, python_requires=">=3.7", setup_requires=[ From ee8cde1e4e10440ed5771941a360706b113ed109 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 Jan 2022 19:27:34 -0800 Subject: [PATCH 0638/1101] Add support for creating plain SDL windows and renderers. --- .vscode/settings.json | 1 + tcod/sdl/__init__.py | 7 +++++++ tcod/sdl/render.py | 34 +++++++++++++++++++++++++++++++++- tcod/sdl/video.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b8556b3..8385c134 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -220,6 +220,7 @@ "pcpp", "PILCROW", "pilmode", + "PRESENTVSYNC", "PRINTF", "printn", "pycall", diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 66aa7885..2415234d 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -35,4 +35,11 @@ def _check(result: int) -> int: return result +def _check_p(result: Any) -> Any: + """Check if an SDL function returned NULL, and raise an exception if it did.""" + if not result: + raise RuntimeError(_get_error()) + return result + + lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index bc929bb5..36a56c52 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -5,8 +5,9 @@ import numpy as np from numpy.typing import NDArray +import tcod.sdl.video from tcod.loader import ffi, lib -from tcod.sdl import _check +from tcod.sdl import _check, _check_p class Texture: @@ -161,3 +162,34 @@ def upload_texture( lib.SDL_UpdateTexture(texture.p, ffi.NULL, ffi.cast("const void*", pixels.ctypes.data), pixels.strides[0]) ) return texture + + +def new_renderer( + window: tcod.sdl.video.Window, + *, + driver: Optional[int] = None, + software: bool = False, + vsync: bool = True, + target_textures: bool = False, +) -> Renderer: + """Initialize and return a new SDL Renderer. + + Example:: + + # Start by creating a window. + sdl_window = tcod.sdl.video.new_window(640, 480) + # Create a renderer with target texture support. + sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True) + + .. seealso:: + :func:`tcod.sdl.video.new_window` + """ + driver = driver if driver is not None else -1 + flags = 0 + if vsync: + flags |= int(lib.SDL_RENDERER_PRESENTVSYNC) + if target_textures: + flags |= int(lib.SDL_RENDERER_TARGETTEXTURE) + flags |= int(lib.SDL_RENDERER_SOFTWARE) if software else int(lib.SDL_RENDERER_ACCELERATED) + renderer_p = _check_p(ffi.gc(lib.SDL_CreateRenderer(window.p, driver, flags), lib.SDL_DestroyRenderer)) + return Renderer(renderer_p) diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 635d5bda..53b10da6 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -5,12 +5,14 @@ """ from __future__ import annotations -from typing import Any, Tuple +import sys +from typing import Any, Optional, Tuple import numpy as np from numpy.typing import ArrayLike, NDArray from tcod.loader import ffi, lib +from tcod.sdl import _check_p __all__ = ("Window",) @@ -124,6 +126,33 @@ def title(self, value: str) -> None: lib.SDL_SetWindowtitle(self.p, value.encode("utf-8")) +def new_window( + width: int, + height: int, + *, + x: Optional[int] = None, + y: Optional[int] = None, + title: Optional[str] = None, + flags: int = 0, +) -> Window: + """Initialize and return a new SDL Window. + + Example:: + + # Create a new resizable window with a custom title. + window = tcod.sdl.video.new_window(640, 480, title="Title bar text", flags=tcod.lib.SDL_WINDOW_RESIZABLE) + + .. seealso:: + :func:`tcod.sdl.render.new_renderer` + """ + x = x if x is not None else int(lib.SDL_WINDOWPOS_UNDEFINED) + y = y if y is not None else int(lib.SDL_WINDOWPOS_UNDEFINED) + if title is None: + title = sys.argv[0] + window_p = ffi.gc(lib.SDL_CreateWindow(title.encode("utf-8"), x, y, width, height, flags), lib.SDL_DestroyWindow) + return Window(_check_p(window_p)) + + def _get_active_window() -> Window: """Return the SDL2 window current managed by libtcod. From 9073a8ecf18b8c2121d649916f547b966438b76a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 Jan 2022 20:18:16 -0800 Subject: [PATCH 0639/1101] Don't access tcod.lib at the top-level, for doc generation. --- tcod/sdl/__init__.py | 15 ++++++++------- tcod/sdl/sys.py | 28 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 2415234d..0e260bde 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -8,12 +8,12 @@ logger = logging.getLogger(__name__) _LOG_PRIORITY = { - int(lib.SDL_LOG_PRIORITY_VERBOSE): logging.DEBUG, - int(lib.SDL_LOG_PRIORITY_DEBUG): logging.DEBUG, - int(lib.SDL_LOG_PRIORITY_INFO): logging.INFO, - int(lib.SDL_LOG_PRIORITY_WARN): logging.WARNING, - int(lib.SDL_LOG_PRIORITY_ERROR): logging.ERROR, - int(lib.SDL_LOG_PRIORITY_CRITICAL): logging.CRITICAL, + 1: logging.DEBUG, # SDL_LOG_PRIORITY_VERBOSE + 2: logging.DEBUG, # SDL_LOG_PRIORITY_DEBUG + 3: logging.INFO, # SDL_LOG_PRIORITY_INFO + 4: logging.WARNING, # SDL_LOG_PRIORITY_WARN + 5: logging.ERROR, # SDL_LOG_PRIORITY_ERROR + 6: logging.CRITICAL, # SDL_LOG_PRIORITY_CRITICAL } @@ -42,4 +42,5 @@ def _check_p(result: Any) -> Any: return result -lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) +if lib._sdl_log_output_function: + lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index a16d09a0..4d9c0cf1 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -8,15 +8,15 @@ class Subsystem(enum.IntFlag): - TIMER = lib.SDL_INIT_TIMER - AUDIO = lib.SDL_INIT_AUDIO - VIDEO = lib.SDL_INIT_VIDEO - JOYSTICK = lib.SDL_INIT_JOYSTICK - HAPTIC = lib.SDL_INIT_HAPTIC - GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER - EVENTS = lib.SDL_INIT_EVENTS - SENSOR = getattr(lib, "SDL_INIT_SENSOR", 0) - EVERYTHING = lib.SDL_INIT_EVERYTHING + TIMER = getattr(lib, "SDL_INIT_TIMER", 0x00000001) + AUDIO = getattr(lib, "SDL_INIT_AUDIO", 0x00000010) + VIDEO = getattr(lib, "SDL_INIT_VIDEO", 0x00000020) + JOYSTICK = getattr(lib, "SDL_INIT_JOYSTICK", 0x00000200) + HAPTIC = getattr(lib, "SDL_INIT_HAPTIC", 0x00001000) + GAMECONTROLLER = getattr(lib, "SDL_INIT_GAMECONTROLLER", 0x00002000) + EVENTS = getattr(lib, "SDL_INIT_EVENTS", 0x00004000) + SENSOR = getattr(lib, "SDL_INIT_SENSOR", 0x00008000) + EVERYTHING = getattr(lib, "SDL_INIT_EVERYTHING", 0) def init(flags: int = Subsystem.EVERYTHING) -> None: @@ -48,11 +48,11 @@ def __exit__(self, *args: Any) -> None: class _PowerState(enum.IntEnum): - UNKNOWN = lib.SDL_POWERSTATE_UNKNOWN - ON_BATTERY = lib.SDL_POWERSTATE_ON_BATTERY - NO_BATTERY = lib.SDL_POWERSTATE_NO_BATTERY - CHARGING = lib.SDL_POWERSTATE_CHARGING - CHARGED = lib.SDL_POWERSTATE_CHARGED + UNKNOWN = getattr(lib, "SDL_POWERSTATE_UNKNOWN", 0) + ON_BATTERY = getattr(lib, "SDL_POWERSTATE_ON_BATTERY", 0) + NO_BATTERY = getattr(lib, "SDL_POWERSTATE_NO_BATTERY", 0) + CHARGING = getattr(lib, "SDL_POWERSTATE_CHARGING", 0) + CHARGED = getattr(lib, "SDL_POWERSTATE_CHARGED", 0) def _get_power_info() -> Tuple[_PowerState, int, int]: From 65d6bf3cee47128652c4f9e31280d570ca9677fb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Feb 2022 13:30:02 -0800 Subject: [PATCH 0640/1101] Port more methods to SDL windows. --- tcod/render.py | 23 +++++ tcod/sdl/__init__.py | 28 +++++- tcod/sdl/sys.py | 18 ++-- tcod/sdl/video.py | 202 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 246 insertions(+), 25 deletions(-) diff --git a/tcod/render.py b/tcod/render.py index 21e98db0..f1bc87ce 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -1,3 +1,26 @@ +"""Handle the rendering of libtcod's tilesets. + +Example:: + + tileset = tcod.tileset.load_tilsheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD) + console = tcod.Console(20, 8) + console.print(0, 0, "Hello World") + sdl_window = tcod.sdl.video.new_window( + console.width * tileset.tile_width, + console.height * tileset.tile_height, + flags=tcod.lib.SDL_WINDOW_RESIZABLE, + ) + sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True) + atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset) + console_render = tcod.render.SDLConsoleRender(atlas) + while True: + sdl_renderer.copy(console_render.render(console)) + sdl_renderer.present() + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() +""" + from __future__ import annotations from typing import Optional diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 0e260bde..524f24b6 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -1,10 +1,12 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, Callable, Tuple, TypeVar from tcod.loader import ffi, lib +T = TypeVar("T") + logger = logging.getLogger(__name__) _LOG_PRIORITY = { @@ -44,3 +46,27 @@ def _check_p(result: Any) -> Any: if lib._sdl_log_output_function: lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) + + +def _compiled_version() -> Tuple[int, int, int]: + return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) + + +def _linked_version() -> Tuple[int, int, int]: + sdl_version = ffi.new("SDL_version*") + lib.SDL_GetVersion(sdl_version) + return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) + + +def _required_version(required: Tuple[int, int, int]) -> Callable[[T], T]: + if not lib: # Read the docs mock object. + return lambda x: x + if required <= _compiled_version(): + return lambda x: x + + def replacement(*_args: Any, **_kwargs: Any) -> Any: + raise RuntimeError( + f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + ) + + return lambda x: replacement # type: ignore[return-value] diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 4d9c0cf1..7569ed83 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -8,15 +8,15 @@ class Subsystem(enum.IntFlag): - TIMER = getattr(lib, "SDL_INIT_TIMER", 0x00000001) - AUDIO = getattr(lib, "SDL_INIT_AUDIO", 0x00000010) - VIDEO = getattr(lib, "SDL_INIT_VIDEO", 0x00000020) - JOYSTICK = getattr(lib, "SDL_INIT_JOYSTICK", 0x00000200) - HAPTIC = getattr(lib, "SDL_INIT_HAPTIC", 0x00001000) - GAMECONTROLLER = getattr(lib, "SDL_INIT_GAMECONTROLLER", 0x00002000) - EVENTS = getattr(lib, "SDL_INIT_EVENTS", 0x00004000) - SENSOR = getattr(lib, "SDL_INIT_SENSOR", 0x00008000) - EVERYTHING = getattr(lib, "SDL_INIT_EVERYTHING", 0) + TIMER = lib.SDL_INIT_TIMER or 0x00000001 + AUDIO = lib.SDL_INIT_AUDIO or 0x00000010 + VIDEO = lib.SDL_INIT_VIDEO or 0x00000020 + JOYSTICK = lib.SDL_INIT_JOYSTICK or 0x00000200 + HAPTIC = lib.SDL_INIT_HAPTIC or 0x00001000 + GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER or 0x00002000 + EVENTS = lib.SDL_INIT_EVENTS or 0x00004000 + SENSOR = getattr(lib, "SDL_INIT_SENSOR", None) or 0x00008000 # SDL >= 2.0.9 + EVERYTHING = lib.SDL_INIT_EVERYTHING or 0 def init(flags: int = Subsystem.EVERYTHING) -> None: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 53b10da6..c77d91ad 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -5,6 +5,7 @@ """ from __future__ import annotations +import enum import sys from typing import Any, Optional, Tuple @@ -12,9 +13,52 @@ from numpy.typing import ArrayLike, NDArray from tcod.loader import ffi, lib -from tcod.sdl import _check_p +from tcod.sdl import _check, _check_p, _required_version -__all__ = ("Window",) +__all__ = ( + "WindowFlags", + "FlashOperation", + "Window", + "new_window", + "get_grabbed_window", +) + + +class WindowFlags(enum.IntFlag): + """Bit flags which make up a windows state. + + .. seealso:: + https://wiki.libsdl.org/SDL_WindowFlags + """ + + FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN or 0 + FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP or 0 + OPENGL = lib.SDL_WINDOW_OPENGL or 0 + SHOWN = lib.SDL_WINDOW_SHOWN or 0 + HIDDEN = lib.SDL_WINDOW_HIDDEN or 0 + BORDERLESS = lib.SDL_WINDOW_BORDERLESS or 0 + RESIZABLE = lib.SDL_WINDOW_RESIZABLE or 0 + MINIMIZED = lib.SDL_WINDOW_MINIMIZED or 0 + MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED or 0 + MOUSE_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED or 0 + INPUT_FOCUS = lib.SDL_WINDOW_INPUT_FOCUS or 0 + MOUSE_FOCUS = lib.SDL_WINDOW_MOUSE_FOCUS or 0 + FOREIGN = lib.SDL_WINDOW_FOREIGN or 0 + ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI or 0 + MOUSE_CAPTURE = lib.SDL_WINDOW_MOUSE_CAPTURE or 0 + ALWAYS_ON_TOP = lib.SDL_WINDOW_ALWAYS_ON_TOP or 0 + SKIP_TASKBAR = lib.SDL_WINDOW_SKIP_TASKBAR or 0 + UTILITY = lib.SDL_WINDOW_UTILITY or 0 + TOOLTIP = lib.SDL_WINDOW_TOOLTIP or 0 + POPUP_MENU = lib.SDL_WINDOW_POPUP_MENU or 0 + VULKAN = lib.SDL_WINDOW_VULKAN or 0 + METAL = getattr(lib, "SDL_WINDOW_METAL", None) or 0x20000000 # SDL >= 2.0.14 + + +class FlashOperation(enum.IntEnum): + CANCEL = 0 + BRIEFLY = 1 + UNTIL_FOCUSED = 2 class _TempSurface: @@ -67,10 +111,7 @@ def set_icon(self, image: ArrayLike) -> None: @property def allow_screen_saver(self) -> bool: - """If True the operating system is allowed to display a screen saver. - - You can set this attribute to enable or disable the screen saver. - """ + """Get or set if the operating system is allowed to display a screen saver.""" return bool(lib.SDL_IsScreenSaverEnabled(self.p)) @allow_screen_saver.setter @@ -82,11 +123,10 @@ def allow_screen_saver(self, value: bool) -> None: @property def position(self) -> Tuple[int, int]: - """Return the (x, y) position of the window. + """Get or set the (x, y) position of the window. This attribute can be set the move the window. - The constants tcod.lib.SDL_WINDOWPOS_CENTERED or - tcod.lib.SDL_WINDOWPOS_UNDEFINED can be used. + The constants tcod.lib.SDL_WINDOWPOS_CENTERED or tcod.lib.SDL_WINDOWPOS_UNDEFINED may be used. """ xy = ffi.new("int[2]") lib.SDL_GetWindowPosition(self.p, xy, xy + 1) @@ -99,11 +139,10 @@ def position(self, xy: Tuple[int, int]) -> None: @property def size(self) -> Tuple[int, int]: - """Return the pixel (width, height) of the window. + """Get or set the pixel (width, height) of the window client area. - This attribute can be set to change the size of the window but the - given size must be greater than (1, 1) or else an exception will be - raised. + This attribute can be set to change the size of the window but the given size must be greater than (1, 1) or + else ValueError will be raised. """ xy = ffi.new("int[2]") lib.SDL_GetWindowSize(self.p, xy, xy + 1) @@ -112,19 +151,146 @@ def size(self) -> Tuple[int, int]: @size.setter def size(self, xy: Tuple[int, int]) -> None: if any(i <= 0 for i in xy): - raise ValueError("Window size must be greater than zero, not %r" % (xy,)) + raise ValueError(f"Window size must be greater than zero, not {xy}") x, y = xy lib.SDL_SetWindowSize(self.p, x, y) + @property + def min_size(self) -> Tuple[int, int]: + """Get or set this windows minimum client area.""" + xy = ffi.new("int[2]") + lib.SDL_GetWindowMinimumSize(self.p, xy, xy + 1) + return xy[0], xy[1] + + @min_size.setter + def min_size(self, xy: Tuple[int, int]) -> None: + lib.SDL_SetWindowMinimumSize(self.p, xy[0], xy[1]) + + @property + def max_size(self) -> Tuple[int, int]: + """Get or set this windows maximum client area.""" + xy = ffi.new("int[2]") + lib.SDL_GetWindowMaximumSize(self.p, xy, xy + 1) + return xy[0], xy[1] + + @max_size.setter + def max_size(self, xy: Tuple[int, int]) -> None: + lib.SDL_SetWindowMaximumSize(self.p, xy[0], xy[1]) + @property def title(self) -> str: - """The title of the window. You may set this attribute to change it.""" + """Get or set the title of the window.""" return str(ffi.string(lib.SDL_GetWindowtitle(self.p)), encoding="utf-8") @title.setter def title(self, value: str) -> None: lib.SDL_SetWindowtitle(self.p, value.encode("utf-8")) + @property + def flags(self) -> WindowFlags: + """The current flags of this window, read-only.""" + return WindowFlags(lib.SDL_GetWindowFlags(self.p)) + + @property + def fullscreen(self) -> int: + """Get or set the fullscreen status of this window. + + Can be set to :any:`WindowFlags.FULLSCREEN` or :any:`WindowFlags.FULLSCREEN_DESKTOP` flags + + Example:: + + # Toggle fullscreen. + window: tcod.sdl.video.Window + if window.fullscreen: + window.fullscreen = False # Set windowed mode. + else: + window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP + """ + return self.flags & (WindowFlags.FULLSCREEN | WindowFlags.FULLSCREEN_DESKTOP) + + @fullscreen.setter + def fullscreen(self, value: int) -> None: + _check(lib.SDL_SetWindowFullscreen(self.p, value)) + + @property + def resizable(self) -> bool: + """Get or set if this window can be resized.""" + return bool(self.flags & WindowFlags.RESIZABLE) + + @resizable.setter + def resizable(self, value: bool) -> None: + lib.SDL_SetWindowResizable(self.p, value) + + @property + def border_size(self) -> Tuple[int, int, int, int]: + """Get the (top, left, bottom, right) size of the window decorations around the client area. + + If this fails or the window doesn't have decorations yet then the value will be (0, 0, 0, 0). + + .. seealso:: + https://wiki.libsdl.org/SDL_GetWindowBordersSize + """ + borders = ffi.new("int[4]") + # The return code is ignored. + _ = lib.SDL_GetWindowBordersSize(self.p, borders, borders + 1, borders + 2, borders + 3) + return borders[0], borders[1], borders[2], borders[3] + + @property + def opacity(self) -> float: + """Get or set this windows opacity. 0.0 is fully transarpent and 1.0 is fully opaque. + + Will error if you try to set this and opacity isn't supported. + """ + out = ffi.new("float*") + _check(lib.SDL_GetWindowOpacity(self.p, out)) + return float(out[0]) + + @opacity.setter + def opacity(self, value: float) -> None: + _check(lib.SDL_SetWindowOpacity(self.p, value)) + + @property + def grab(self) -> bool: + """Get or set this windows input grab mode. + + .. seealso:: + https://wiki.libsdl.org/SDL_SetWindowGrab + """ + return bool(lib.SDL_GetWindowGrab(self.p)) + + @grab.setter + def grab(self, value: bool) -> None: + lib.SDL_SetWindowGrab(self.p, value) + + @_required_version((2, 0, 16)) + def flash(self, operation: FlashOperation = FlashOperation.UNTIL_FOCUSED) -> None: + """Get the users attention.""" + _check(lib.SDL_FlashWindow(self.p, operation)) + + def raise_window(self) -> None: + """Raise the window and set input focus.""" + lib.SDL_RaiseWindow(self.p) + + def restore(self) -> None: + """Restore a minimized or maximized window to its original size and position.""" + lib.SDL_RestoreWindow(self.p) + + def maximize(self) -> None: + """Make the window as big as possible.""" + lib.SDL_MaximizeWindow(self.p) + + def minimize(self) -> None: + """Minimize the window to an iconic state.""" + lib.SDL_MinimizeWindow(self.p) + + def show(self) -> None: + """Show this window.""" + lib.SDL_ShowWindow(self.p) + + def hide(self) -> None: + """Hide this window.""" + lib.SDL_HideWindow(self.p) + def new_window( width: int, @@ -153,6 +319,12 @@ def new_window( return Window(_check_p(window_p)) +def get_grabbed_window() -> Optional[Window]: + """Return the window which has input grab enabled, if any.""" + sdl_window_p = lib.SDL_GetGrabbedWindow() + return Window(sdl_window_p) if sdl_window_p else None + + def _get_active_window() -> Window: """Return the SDL2 window current managed by libtcod. From 1d0eff457b403141335b9d1d482e18509e040f23 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Feb 2022 17:48:40 -0800 Subject: [PATCH 0641/1101] Prefer compiling with SDL 2.0.20. --- build_sdl.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index 9a26132e..0c374132 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -17,10 +17,11 @@ BITSIZE, LINKAGE = platform.architecture() +SDL_MIN_VERSION = (2, 0, 10) # The SDL2 version to parse and export symbols from. -SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") +SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") # Used to remove excessive newlines in debug outputs. @@ -52,6 +53,7 @@ "SDL_DEPRECATED", "SDL_INLINE", "SDL_FORCE_INLINE", + "SDL_FALLTHROUGH", # Might show up in parsing and not in source. "SDL_ANDROID_EXTERNAL_STORAGE_READ", "SDL_ANDROID_EXTERNAL_STORAGE_WRITE", @@ -68,8 +70,7 @@ def check_sdl_version() -> None: """Check the local SDL version on Linux distributions.""" if not sys.platform.startswith("linux"): return - needed_version = SDL2_PARSE_VERSION - SDL_VERSION_NEEDED = tuple(int(n) for n in needed_version.split(".")) + needed_version = f"{SDL_MIN_VERSION[0]}.{SDL_MIN_VERSION[1]}.{SDL_MIN_VERSION[2]}" try: sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() except FileNotFoundError: @@ -80,7 +81,7 @@ def check_sdl_version() -> None: ) print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) - if sdl_version < SDL_VERSION_NEEDED: + if sdl_version < SDL_MIN_VERSION: raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) From 5602fc4435c50a043c5e2b29ba434f95fa1bb84f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 1 Feb 2022 18:52:04 -0800 Subject: [PATCH 0642/1101] Fix missing CPython builds on Linux. Update build packages. Split matrix jobs. --- .github/workflows/python-package.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 41d4c886..caf0f836 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -189,6 +189,7 @@ jobs: strategy: matrix: arch: ["x86_64", "aarch64"] + build: ["cp37-manylinux*", "pp37-manylinux*"] steps: - uses: actions/checkout@v1 - name: Set up QEMU @@ -204,12 +205,12 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install twine cibuildwheel==2.0.0 + pip install twine cibuildwheel==2.3.1 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse env: - CIBW_BUILD: cp36-* pp* + CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_MANYLINUX_*_IMAGE: manylinux2014 CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014 From 20a190f443a6625ba8fa0b2b3ca16606cfb63fca Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 2 Feb 2022 10:06:49 -0800 Subject: [PATCH 0643/1101] Note SDL requirements have changed. --- README.rst | 2 +- build_sdl.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 823e3ac0..91444371 100755 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ For the most part it's just:: ============== * Python 3.7+ * Windows, Linux, or MacOS X 10.9+. -* On Linux, requires libsdl2 (2.0.5+). +* On Linux, requires libsdl2 (2.0.10+). =========== Changelog diff --git a/build_sdl.py b/build_sdl.py index 0c374132..aadbff5c 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -17,6 +17,7 @@ BITSIZE, LINKAGE = platform.architecture() +# Reject versions of SDL older than this, update the requirements in the readme if you change this. SDL_MIN_VERSION = (2, 0, 10) # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") From 858b630cd2773af8c81f2db5990a019c406b4661 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 2 Feb 2022 17:22:11 -0800 Subject: [PATCH 0644/1101] Add tests for SDL. --- tcod/sdl/render.py | 62 +++++++++++++++++++++++++++++++------- tcod/sdl/video.py | 61 +++++++++++++++++++------------------- tests/test_sdl.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 42 deletions(-) create mode 100644 tests/test_sdl.py diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 36a56c52..228e0133 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum from typing import Any, Optional, Tuple import numpy as np @@ -10,6 +11,17 @@ from tcod.sdl import _check, _check_p +class TextureAccess(enum.IntEnum): + """Determines how a texture is expected to be used.""" + + STATIC = lib.SDL_TEXTUREACCESS_STATIC or 0 + """Texture rarely changes.""" + STREAMING = lib.SDL_TEXTUREACCESS_STREAMING or 0 + """Texture frequently changes.""" + TARGET = lib.SDL_TEXTUREACCESS_TARGET or 0 + """Texture will be used as a render target.""" + + class Texture: def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: self.p = sdl_texture_p @@ -56,7 +68,9 @@ def height(self) -> int: @property def alpha_mod(self) -> int: """Texture alpha modulate value, can be set to: 0 - 255.""" - return int(lib.SDL_GetTextureAlphaMod(self.p)) + out = ffi.new("uint8_t*") + _check(lib.SDL_GetTextureAlphaMod(self.p, out)) + return int(out[0]) @alpha_mod.setter def alpha_mod(self, value: int) -> None: @@ -65,7 +79,9 @@ def alpha_mod(self, value: int) -> None: @property def blend_mode(self) -> int: """Texture blend mode, can be set.""" - return int(lib.SDL_GetTextureBlendMode(self.p)) + out = ffi.new("SDL_BlendMode*") + _check(lib.SDL_GetTextureBlendMode(self.p, out)) + return int(out[0]) @blend_mode.setter def blend_mode(self, value: int) -> None: @@ -101,6 +117,8 @@ class Renderer: def __init__(self, sdl_renderer_p: Any) -> None: if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): raise TypeError(f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)}).") + if not sdl_renderer_p: + raise TypeError("C pointer must not be null.") self.p = sdl_renderer_p def __eq__(self, other: Any) -> bool: @@ -124,10 +142,24 @@ def present(self) -> None: """Present the currently rendered image to the screen.""" lib.SDL_RenderPresent(self.p) + def set_render_target(self, texture: Texture) -> _RestoreTargetContext: + """Change the render target to `texture`, returns a context that will restore the original target when exited.""" + restore = _RestoreTargetContext(self) + _check(lib.SDL_SetRenderTarget(self.p, texture.p)) + return restore + def new_texture( self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None ) -> Texture: - """Allocate and return a new Texture for this renderer.""" + """Allocate and return a new Texture for this renderer. + + Args: + width: The pixel width of the new texture. + height: The pixel height of the new texture. + format: The format the new texture. + access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`. + See :any:`TextureAccess` for more options. + """ if format is None: format = 0 if access is None: @@ -135,23 +167,24 @@ def new_texture( texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture) return Texture(texture_p, self.p) - def set_render_target(self, texture: Texture) -> _RestoreTargetContext: - """Change the render target to `texture`, returns a context that will restore the original target when exited.""" - restore = _RestoreTargetContext(self) - _check(lib.SDL_SetRenderTarget(self.p, texture.p)) - return restore - def upload_texture( self, pixels: NDArray[Any], *, format: Optional[int] = None, access: Optional[int] = None ) -> Texture: - """Return a new Texture from an array of pixels.""" + """Return a new Texture from an array of pixels. + + Args: + pixels: An RGB or RGBA array of pixels in row-major order. + format: The format of `pixels` when it isn't a simple RGB or RGBA array. + access: The access mode of the texture. Defaults to :any:`TextureAccess.STATIC`. + See :any:`TextureAccess` for more options. + """ if format is None: assert len(pixels.shape) == 3 assert pixels.dtype == np.uint8 if pixels.shape[2] == 4: format = int(lib.SDL_PIXELFORMAT_RGBA32) elif pixels.shape[2] == 3: - format = int(lib.SDL_PIXELFORMAT_RGB32) + format = int(lib.SDL_PIXELFORMAT_RGB24) else: raise TypeError(f"Can't determine the format required for an array of shape {pixels.shape}.") @@ -174,6 +207,13 @@ def new_renderer( ) -> Renderer: """Initialize and return a new SDL Renderer. + Args: + window: The window that this renderer will be attached to. + driver: Force SDL to use a specific video driver. + software: If True then a software renderer will be forced. By default a hardware renderer is used. + vsync: If True then Vsync will be enabled. + target_textures: If True then target textures can be used by the renderer. + Example:: # Start by creating a window. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index c77d91ad..e12dcc57 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -21,6 +21,7 @@ "Window", "new_window", "get_grabbed_window", + "screen_saver_allowed", ) @@ -66,10 +67,10 @@ class _TempSurface: def __init__(self, pixels: ArrayLike) -> None: self._array: NDArray[np.uint8] = np.ascontiguousarray(pixels, dtype=np.uint8) - if len(self._array) != 3: - raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" % (self._array.shape,)) - if 3 <= self._array.shape[2] <= 4: - raise TypeError("NumPy array must have RGB or RGBA channels. (got %r)" % (self._array.shape,)) + if len(self._array.shape) != 3: + raise TypeError(f"NumPy shape must be 3D [y, x, ch] (got {self._array.shape})") + if not (3 <= self._array.shape[2] <= 4): + raise TypeError(f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})") self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( ffi.from_buffer("void*", self._array), @@ -95,32 +96,21 @@ def __init__(self, sdl_window_p: Any) -> None: "sdl_window_p must be %r type (was %r)." % (ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)) ) if not sdl_window_p: - raise ValueError("sdl_window_p can not be a null pointer.") + raise TypeError("sdl_window_p can not be a null pointer.") self.p = sdl_window_p def __eq__(self, other: Any) -> bool: return bool(self.p == other.p) - def set_icon(self, image: ArrayLike) -> None: + def set_icon(self, pixels: ArrayLike) -> None: """Set the window icon from an image. - `image` is a C memory order RGB or RGBA NumPy array. + Args: + pixels: A row-major array of RGB or RGBA pixel values. """ - surface = _TempSurface(image) + surface = _TempSurface(pixels) lib.SDL_SetWindowIcon(self.p, surface.p) - @property - def allow_screen_saver(self) -> bool: - """Get or set if the operating system is allowed to display a screen saver.""" - return bool(lib.SDL_IsScreenSaverEnabled(self.p)) - - @allow_screen_saver.setter - def allow_screen_saver(self, value: bool) -> None: - if value: - lib.SDL_EnableScreenSaver(self.p) - else: - lib.SDL_DisableScreenSaver(self.p) - @property def position(self) -> Tuple[int, int]: """Get or set the (x, y) position of the window. @@ -180,11 +170,11 @@ def max_size(self, xy: Tuple[int, int]) -> None: @property def title(self) -> str: """Get or set the title of the window.""" - return str(ffi.string(lib.SDL_GetWindowtitle(self.p)), encoding="utf-8") + return str(ffi.string(lib.SDL_GetWindowTitle(self.p)), encoding="utf-8") @title.setter def title(self, value: str) -> None: - lib.SDL_SetWindowtitle(self.p, value.encode("utf-8")) + lib.SDL_SetWindowTitle(self.p, value.encode("utf-8")) @property def flags(self) -> WindowFlags: @@ -303,6 +293,15 @@ def new_window( ) -> Window: """Initialize and return a new SDL Window. + Args: + width: The requested pixel width of the window. + height: The requested pixel height of the window. + x: The left-most position of the window. + y: The top-most position of the window. + title: The title text of the new window. If no option is given then `sys.arg[0]` will be used as the title. + flags: The SDL flags to use for this window, such as `tcod.sdl.video.WindowFlags.RESIZABLE`. + See :any:`WindowFlags` for more options. + Example:: # Create a new resizable window with a custom title. @@ -325,12 +324,12 @@ def get_grabbed_window() -> Optional[Window]: return Window(sdl_window_p) if sdl_window_p else None -def _get_active_window() -> Window: - """Return the SDL2 window current managed by libtcod. - - Will raise an error if libtcod does not currently have a window. - """ - sdl_window = lib.TCOD_sys_get_window() - if not sdl_window: - raise RuntimeError("TCOD does not have an active window.") - return Window(sdl_window) +def screen_saver_allowed(allow: Optional[bool] = None) -> bool: + """Allow or prevent a screen saver from being displayed and return the current allowed status.""" + if allow is None: + pass + elif allow: + lib.SDL_EnableScreenSaver() + else: + lib.SDL_DisableScreenSaver() + return bool(lib.SDL_IsScreenSaverEnabled()) diff --git a/tests/test_sdl.py b/tests/test_sdl.py new file mode 100644 index 00000000..348ebe60 --- /dev/null +++ b/tests/test_sdl.py @@ -0,0 +1,74 @@ +import sys + +import numpy as np +import pytest + +import tcod.sdl.render +import tcod.sdl.video + + +def test_sdl_window() -> None: + assert tcod.sdl.video.get_grabbed_window() is None + window = tcod.sdl.video.new_window(1, 1) + window.raise_window() + window.maximize() + window.restore() + window.minimize() + window.hide() + window.show() + assert window.title == sys.argv[0] + window.title = "Title" + assert window.title == "Title" + assert window.opacity == 1.0 + window.position = window.position + window.fullscreen = window.fullscreen + window.resizable = window.resizable + window.size = window.size + window.min_size = window.min_size + window.max_size = window.max_size + window.border_size + window.set_icon(np.zeros((32, 32, 3), dtype=np.uint8)) + with pytest.raises(TypeError): + window.set_icon(np.zeros((32, 32, 5), dtype=np.uint8)) + with pytest.raises(TypeError): + window.set_icon(np.zeros((32, 32), dtype=np.uint8)) + window.opacity = window.opacity + window.grab = window.grab + + +def test_sdl_window_bad_types() -> None: + with pytest.raises(TypeError): + tcod.sdl.video.Window(tcod.ffi.cast("SDL_Window*", tcod.ffi.NULL)) + with pytest.raises(TypeError): + tcod.sdl.video.Window(tcod.ffi.new("SDL_Rect*")) + + +def test_sdl_screen_saver() -> None: + assert tcod.sdl.video.screen_saver_allowed(False) is False + assert tcod.sdl.video.screen_saver_allowed(True) is True + assert tcod.sdl.video.screen_saver_allowed() is True + + +def test_sdl_render() -> None: + window = tcod.sdl.video.new_window(1, 1) + render = tcod.sdl.render.new_renderer(window, software=True, vsync=False, target_textures=True) + render.present() + rgb = render.upload_texture(np.zeros((8, 8, 3), np.uint8)) + assert (rgb.width, rgb.height) == (8, 8) + assert rgb.access == tcod.sdl.render.TextureAccess.STATIC + assert rgb.format == tcod.lib.SDL_PIXELFORMAT_RGB24 + rgb.alpha_mod = rgb.alpha_mod + rgb.blend_mode = rgb.blend_mode + rgb.rgb_mod = rgb.rgb_mod + rgba = render.upload_texture(np.zeros((8, 8, 4), np.uint8), access=tcod.sdl.render.TextureAccess.TARGET) + with render.set_render_target(rgba): + render.copy(rgb) + with pytest.raises(TypeError): + render.upload_texture(np.zeros((8, 8, 5), np.uint8)) + + +def test_sdl_render_bad_types() -> None: + with pytest.raises(TypeError): + tcod.sdl.render.Renderer(tcod.ffi.cast("SDL_Renderer*", tcod.ffi.NULL)) + with pytest.raises(TypeError): + tcod.sdl.render.Renderer(tcod.ffi.new("SDL_Rect*")) From ba2de759716184f7ee8f69c68cfdcfc45c84df7c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 2 Feb 2022 23:10:59 -0800 Subject: [PATCH 0645/1101] Add some SDL modules to the documentation. --- docs/index.rst | 3 +++ docs/sdl/render.rst | 5 +++++ docs/sdl/video.rst | 5 +++++ docs/tcod/render.rst | 5 +++++ tcod/render.py | 8 +++++++- tcod/sdl/render.py | 8 ++++++++ tcod/sdl/video.py | 37 ++++++++++++++++++++++++++++++++----- 7 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 docs/sdl/render.rst create mode 100644 docs/sdl/video.rst create mode 100644 docs/tcod/render.rst diff --git a/docs/index.rst b/docs/index.rst index 1ae35b97..7ddc5583 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,8 +36,11 @@ Contents: tcod/noise tcod/path tcod/random + tcod/render tcod/tileset libtcodpy + sdl/render + sdl/video Indices and tables ================== diff --git a/docs/sdl/render.rst b/docs/sdl/render.rst new file mode 100644 index 00000000..0dc462cd --- /dev/null +++ b/docs/sdl/render.rst @@ -0,0 +1,5 @@ +tcod.sdl.render - SDL Rendering +=============================== + +.. automodule:: tcod.sdl.render + :members: diff --git a/docs/sdl/video.rst b/docs/sdl/video.rst new file mode 100644 index 00000000..56d43408 --- /dev/null +++ b/docs/sdl/video.rst @@ -0,0 +1,5 @@ +tcod.sdl.video - SDL Window and Display API +=========================================== + +.. automodule:: tcod.sdl.video + :members: diff --git a/docs/tcod/render.rst b/docs/tcod/render.rst new file mode 100644 index 00000000..605d52f2 --- /dev/null +++ b/docs/tcod/render.rst @@ -0,0 +1,5 @@ +tcod.render - Console Rendering Extension +========================================= + +.. automodule:: tcod.render + :members: diff --git a/tcod/render.py b/tcod/render.py index f1bc87ce..9e2918da 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -1,4 +1,8 @@ -"""Handle the rendering of libtcod's tilesets. +"""Handles the rendering of libtcod's tilesets. + +Using this module you can render a console to an SDL :any:`Texture` directly, letting you have full control over how +conoles are displayed. +This includes rendering multiple tilesets in a single frame and rendering consoles on top of each other. Example:: @@ -19,6 +23,8 @@ for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): raise SystemExit() + +.. versionadded:: 13.4 """ from __future__ import annotations diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 228e0133..48d810fa 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -1,3 +1,7 @@ +"""SDL2 Rendering functionality. + +.. versionadded:: 13.4 +""" from __future__ import annotations import enum @@ -23,6 +27,8 @@ class TextureAccess(enum.IntEnum): class Texture: + """SDL hardware textures.""" + def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: self.p = sdl_texture_p self._sdl_renderer_p = sdl_renderer_p # Keep alive. @@ -114,6 +120,8 @@ def __exit__(self, *_: Any) -> None: class Renderer: + """SDL Renderer.""" + def __init__(self, sdl_renderer_p: Any) -> None: if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): raise TypeError(f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)}).") diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index e12dcc57..4d1d3b7e 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -1,7 +1,6 @@ -"""SDL2 specific functionality. +"""SDL2 Window and Display handling. -Add the line ``import tcod.sdl`` to include this module, as importing this -module is not implied by ``import tcod``. +.. versionadded:: 13.4 """ from __future__ import annotations @@ -33,33 +32,60 @@ class WindowFlags(enum.IntFlag): """ FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN or 0 + """""" FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP or 0 + """""" OPENGL = lib.SDL_WINDOW_OPENGL or 0 + """""" SHOWN = lib.SDL_WINDOW_SHOWN or 0 + """""" HIDDEN = lib.SDL_WINDOW_HIDDEN or 0 + """""" BORDERLESS = lib.SDL_WINDOW_BORDERLESS or 0 + """""" RESIZABLE = lib.SDL_WINDOW_RESIZABLE or 0 + """""" MINIMIZED = lib.SDL_WINDOW_MINIMIZED or 0 + """""" MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED or 0 + """""" MOUSE_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED or 0 + """""" INPUT_FOCUS = lib.SDL_WINDOW_INPUT_FOCUS or 0 + """""" MOUSE_FOCUS = lib.SDL_WINDOW_MOUSE_FOCUS or 0 + """""" FOREIGN = lib.SDL_WINDOW_FOREIGN or 0 + """""" ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI or 0 + """""" MOUSE_CAPTURE = lib.SDL_WINDOW_MOUSE_CAPTURE or 0 + """""" ALWAYS_ON_TOP = lib.SDL_WINDOW_ALWAYS_ON_TOP or 0 + """""" SKIP_TASKBAR = lib.SDL_WINDOW_SKIP_TASKBAR or 0 + """""" UTILITY = lib.SDL_WINDOW_UTILITY or 0 + """""" TOOLTIP = lib.SDL_WINDOW_TOOLTIP or 0 + """""" POPUP_MENU = lib.SDL_WINDOW_POPUP_MENU or 0 + """""" VULKAN = lib.SDL_WINDOW_VULKAN or 0 + """""" METAL = getattr(lib, "SDL_WINDOW_METAL", None) or 0x20000000 # SDL >= 2.0.14 + """""" class FlashOperation(enum.IntEnum): + """Values for :any:`Window.flash`.""" + CANCEL = 0 + """Stop flashing.""" BRIEFLY = 1 + """Flash breifly.""" UNTIL_FOCUSED = 2 + """Flash until focus is gained.""" class _TempSurface: @@ -185,7 +211,7 @@ def flags(self) -> WindowFlags: def fullscreen(self) -> int: """Get or set the fullscreen status of this window. - Can be set to :any:`WindowFlags.FULLSCREEN` or :any:`WindowFlags.FULLSCREEN_DESKTOP` flags + Can be set to the :any:`WindowFlags.FULLSCREEN` or :any:`WindowFlags.FULLSCREEN_DESKTOP` flags. Example:: @@ -304,8 +330,9 @@ def new_window( Example:: + import tcod.sdl.video # Create a new resizable window with a custom title. - window = tcod.sdl.video.new_window(640, 480, title="Title bar text", flags=tcod.lib.SDL_WINDOW_RESIZABLE) + window = tcod.sdl.video.new_window(640, 480, title="Title bar text", flags=tcod.sdl.video.WindowFlags.RESIZABLE) .. seealso:: :func:`tcod.sdl.render.new_renderer` From 8a678fd1fea56eff3521f1f6f3330269920fdf4b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Feb 2022 18:50:57 -0800 Subject: [PATCH 0646/1101] Add event watch functions. --- build_sdl.py | 3 +++ tcod/cdef.h | 2 -- tcod/event.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index aadbff5c..19a4d4f2 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -207,7 +207,10 @@ def on_directive_handle( extern "Python" { // SDL_AudioCallback callback. void _sdl_audio_callback(void* userdata, Uint8* stream, int len); +// SDL to Python log function. void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message); +// Generic event watcher callback. +int _sdl_event_watcher(void* userdata, SDL_Event* event); } """ diff --git a/tcod/cdef.h b/tcod/cdef.h index 70ed74f9..03db6cbc 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -16,7 +16,5 @@ float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data); void _pycall_sdl_hook(struct SDL_Surface*); -int _pycall_event_watch(void* userdata, union SDL_Event* event); - void _pycall_cli_output(void* userdata, const char* output); } diff --git a/tcod/event.py b/tcod/event.py index 3cfc878b..73cc7409 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -23,7 +23,7 @@ import enum import warnings -from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, Type, TypeVar, Union import numpy as np from numpy.typing import NDArray @@ -238,7 +238,7 @@ def __init__(self, type: Optional[str] = None): self.sdl_event = None @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Any: + def from_sdl_event(cls, sdl_event: Any) -> Event: """Return a class instance from a python-cffi 'SDL_Event*' pointer.""" raise NotImplementedError() @@ -739,7 +739,7 @@ def __str__(self) -> str: return "" -_SDL_TO_CLASS_TABLE: Dict[int, Any] = { +_SDL_TO_CLASS_TABLE: Dict[int, Type[Event]] = { lib.SDL_QUIT: Quit, lib.SDL_KEYDOWN: KeyDown, lib.SDL_KEYUP: KeyUp, @@ -752,6 +752,13 @@ def __str__(self) -> str: } +def _parse_event(sdl_event: Any) -> Event: + """Convert a C SDL_Event* type into a tcod Event sub-class.""" + if sdl_event.type not in _SDL_TO_CLASS_TABLE: + return Undefined.from_sdl_event(sdl_event) + return _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) + + def get() -> Iterator[Any]: """Return an iterator for all pending events. @@ -1065,10 +1072,63 @@ def get_mouse_state() -> MouseState: @ffi.def_extern() # type: ignore -def _pycall_event_watch(userdata: Any, sdl_event: Any) -> int: +def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: + callback: Callable[[Event], None] = ffi.from_handle(userdata) + callback(_parse_event(sdl_event)) return 0 +_EventCallback = TypeVar("_EventCallback", bound=Callable[[Event], None]) +_event_watch_handles: Dict[Callable[[Event], None], Any] = {} # Callbacks and their FFI handles. + + +def add_watch(callback: _EventCallback) -> _EventCallback: + """Add a callback for watching events. + + This function can be called with the callback to register, or be used as a decorator. + + Callbacks added as event watchers can later be removed with :any:`tcod.event.remove_watch`. + + Args: + callback (Callable[[Event], None]): + A function which accepts :any:`Event` parameters. + + Example:: + + import tcod.event + + @tcod.event.add_watch + def handle_events(event: tcod.event.Event) -> None: + if isinstance(event, tcod.event.KeyDown): + print(event) + + .. versionadded:: 13.4 + """ + if callback in _event_watch_handles: + warnings.warn(f"{callback} is already an active event watcher, nothing was added.", RuntimeWarning) + return callback + handle = _event_watch_handles[callback] = ffi.new_handle(callback) + lib.SDL_AddEventWatch(lib._sdl_event_watcher, handle) + return callback + + +def remove_watch(callback: Callable[[Event], None]) -> None: + """Remove a callback as an event wacher. + + Args: + callback (Callable[[Event], None]): + A function which has been previously registered with :any:`tcod.event.add_watch`. + + .. versionadded:: 13.4 + """ + if callback not in _event_watch_handles: + warnings.warn(f"{callback} is not an active event watcher, nothing was removed.", RuntimeWarning) + return + handle = _event_watch_handles[callback] + lib.SDL_DelEventWatch(lib._sdl_event_watcher, handle) + del _event_watch_handles[callback] + + def get_keyboard_state() -> NDArray[np.bool_]: """Return a boolean array with the current keyboard state. From 304eedb9cd5c20ffbb25d3a70827f8a31acac8df Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Feb 2022 01:03:57 -0800 Subject: [PATCH 0647/1101] Clean up and refactor SDL audio module. --- tcod/sdl/audio.py | 135 +++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 54 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index ac5cd0d1..8e0e3c70 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -3,7 +3,6 @@ import sys import threading import time -import weakref from typing import Any, Iterator, List, Optional import numpy as np @@ -27,7 +26,7 @@ def _get_format(format: DTypeLike) -> int: if byteorder == "=": byteorder = "<" if sys.byteorder == "little" else ">" - return ( # type: ignore + return int( bitsize | (lib.SDL_AUDIO_MASK_DATATYPE * is_float) | (lib.SDL_AUDIO_MASK_ENDIAN * (byteorder == ">")) @@ -51,57 +50,45 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: class AudioDevice: + """An SDL audio device.""" + def __init__( self, - device: Optional[str] = None, - capture: bool = False, - *, - frequency: int = 44100, - format: DTypeLike = np.float32, - channels: int = 2, - samples: int = 0, - allowed_changes: int = 0, + device_id: int, + capture: bool, + spec: Any, # SDL_AudioSpec* ): - self.__sdl_subsystems = tcod.sdl.sys._ScopeInit(tcod.sdl.sys.Subsystem.AUDIO) - self.__handle = ffi.new_handle(weakref.ref(self)) - desired = ffi.new( - "SDL_AudioSpec*", - { - "freq": frequency, - "format": _get_format(format), - "channels": channels, - "samples": samples, - "callback": ffi.NULL, - "userdata": self.__handle, - }, - ) - obtained = ffi.new("SDL_AudioSpec*") - self.device_id = lib.SDL_OpenAudioDevice( - ffi.NULL if device is None else device.encode("utf-8"), - capture, - desired, - obtained, - allowed_changes, - ) - assert self.device_id != 0, _get_error() - self.frequency = obtained.freq + assert device_id >= 0 + assert ffi.typeof(spec) is ffi.typeof("SDL_AudioSpec*") + assert spec + self.device_id = device_id + self.spec = spec + self.frequency = spec.freq self.is_capture = capture - self.format = _dtype_from_format(obtained.format) - self.channels = int(obtained.channels) - self.silence = int(obtained.silence) - self.samples = int(obtained.samples) - self.buffer_size = int(obtained.size) - self.unpause() + self.format = _dtype_from_format(spec.format) + self.channels = int(spec.channels) + self.silence = int(spec.silence) + self.samples = int(spec.samples) + self.buffer_size = int(spec.size) + self._callback = self.__default_callback @property def _sample_size(self) -> int: return self.format.itemsize * self.channels - def pause(self) -> None: - lib.SDL_PauseAudioDevice(self.device_id, True) + @property + def stopped(self) -> bool: + """Is True if the device has failed or was closed.""" + return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) != lib.SDL_AUDIO_STOPPED) + + @property + def paused(self) -> bool: + """Get or set the device paused state.""" + return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) != lib.SDL_AUDIO_PLAYING) - def unpause(self) -> None: - lib.SDL_PauseAudioDevice(self.device_id, False) + @paused.setter + def paused(self, value: bool) -> None: + lib.SDL_PauseAudioDevice(self.device_id, value) def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]: if samples.dtype != self.format: @@ -121,12 +108,14 @@ def queued_audio_bytes(self) -> int: return int(lib.SDL_GetQueuedAudioSize(self.device_id)) def queue_audio(self, samples: ArrayLike) -> None: + """Append audio samples to the audio data queue.""" assert not self.is_capture samples = self._convert_array(samples) buffer = ffi.from_buffer(samples) lib.SDL_QueueAudio(self.device_id, buffer, len(buffer)) def dequeue_audio(self) -> NDArray[Any]: + """Return the audio buffer from a capture stream.""" assert self.is_capture out_samples = self.queued_audio_bytes // self._sample_size out = np.empty((out_samples, self.channels), self.format) @@ -140,31 +129,30 @@ def __del__(self) -> None: self.close() def close(self) -> None: + """Close this audio device.""" if not self.device_id: return lib.SDL_CloseAudioDevice(self.device_id) self.device_id = 0 - @staticmethod - def __default_callback(stream: NDArray[Any], silence: int) -> None: - stream[...] = silence + def __default_callback(self, stream: NDArray[Any]) -> None: + stream[...] = self.silence class Mixer(threading.Thread): def __init__(self, device: AudioDevice): super().__init__(daemon=True) self.device = device - self.device.unpause() - self.start() def run(self) -> None: buffer = np.full((self.device.samples, self.device.channels), self.device.silence, dtype=self.device.format) while True: - time.sleep(0.001) - if self.device.queued_audio_bytes == 0: - self.on_stream(buffer) - self.device.queue_audio(buffer) - buffer[:] = self.device.silence + if self.device.queued_audio_bytes > 0: + time.sleep(0.001) + continue + self.on_stream(buffer) + self.device.queue_audio(buffer) + buffer[:] = self.device.silence def on_stream(self, stream: NDArray[Any]) -> None: pass @@ -197,7 +185,8 @@ def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: """Handle audio device callbacks.""" device: Optional[AudioDevice] = ffi.from_handle(userdata)() assert device is not None - _ = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) + buffer = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) + device._callback(buffer) def _get_devices(capture: bool) -> Iterator[str]: @@ -216,3 +205,41 @@ def get_devices() -> Iterator[str]: def get_capture_devices() -> Iterator[str]: """Iterate over the available audio capture devices.""" yield from _get_devices(capture=True) + + +def open( + name: Optional[str] = None, + capture: bool = False, + *, + frequency: int = 44100, + format: DTypeLike = np.float32, + channels: int = 2, + samples: int = 0, + allowed_changes: int = 0, + paused: bool = False, +) -> AudioDevice: + """Open an audio device for playback or capture.""" + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) + desired = ffi.new( + "SDL_AudioSpec*", + { + "freq": frequency, + "format": _get_format(format), + "channels": channels, + "samples": samples, + "callback": ffi.NULL, + "userdata": ffi.NULL, + }, + ) + obtained = ffi.new("SDL_AudioSpec*") + device_id: int = lib.SDL_OpenAudioDevice( + ffi.NULL if name is None else name.encode("utf-8"), + capture, + desired, + obtained, + allowed_changes, + ) + assert device_id >= 0, _get_error() + device = AudioDevice(device_id, capture, obtained) + device.paused = paused + return device From 45936e5ec217beb8e086b67a1f87f3a3b3120611 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Feb 2022 17:48:34 -0800 Subject: [PATCH 0648/1101] Note recent additions to the changelog. --- .vscode/settings.json | 1 + CHANGELOG.md | 6 ++++++ tcod/context.py | 4 ++-- tcod/event.py | 6 ++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8385c134..87df363c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -292,6 +292,7 @@ "truetype", "undoc", "Unifont", + "unraisablehook", "upscaling", "VAFUNC", "vcoef", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2ed57f..a0c57acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Adds `sdl_window` and `sdl_renderer` to tcod contexts. +- Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback. +- Adds the `tcod.sdl.video` module to handle SDL windows. +- Adds the `tcod.sdl.render` module to handle SDL renderers. +- Adds the `tcod.render` module which gives more control over the rendering of consoles and tilesets. ### Fixed - Fixed handling of non-Path PathLike parameters and filepath encodings. diff --git a/tcod/context.py b/tcod/context.py index 3c8470b1..14c81f6f 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -355,7 +355,7 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: @property def sdl_window(self) -> Optional[tcod.sdl.video.Window]: - """Return a tcod.sdl.video.Window referencing this contexts SDL window if it exists. + """Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists. .. versionadded:: 13.4 """ @@ -364,7 +364,7 @@ def sdl_window(self) -> Optional[tcod.sdl.video.Window]: @property def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: - """Return a tcod.sdl.render.Renderer referencing this contexts SDL renderer if it exists. + """Return a :any:`tcod.sdl.render.Renderer` referencing this contexts SDL renderer if it exists. .. versionadded:: 13.4 """ diff --git a/tcod/event.py b/tcod/event.py index 73cc7409..7eccabd9 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1089,6 +1089,10 @@ def add_watch(callback: _EventCallback) -> _EventCallback: Callbacks added as event watchers can later be removed with :any:`tcod.event.remove_watch`. + .. warning:: + How uncaught exceptions in a callback are handled is not currently defined by tcod. + They will likely be handled by :any:`sys.unraisablehook`. + Args: callback (Callable[[Event], None]): A function which accepts :any:`Event` parameters. @@ -2303,6 +2307,8 @@ def __repr__(self) -> str: "get", "wait", "get_mouse_state", + "add_watch", + "remove_watch", "EventDispatch", "get_keyboard_state", "get_modifier_state", From c6c746061c9caff0068d50bd84b68a3792583c8f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Feb 2022 17:57:29 -0800 Subject: [PATCH 0649/1101] Rename rgb_mod to color_mod. I've decided that this should match SDL names rather than tcod names. --- tcod/sdl/render.py | 6 +++--- tests/test_sdl.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 48d810fa..25f0d211 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -94,14 +94,14 @@ def blend_mode(self, value: int) -> None: _check(lib.SDL_SetTextureBlendMode(self.p, value)) @property - def rgb_mod(self) -> Tuple[int, int, int]: + def color_mod(self) -> Tuple[int, int, int]: """Texture RGB color modulate values, can be set.""" rgb = ffi.new("uint8_t[3]") _check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2)) return int(rgb[0]), int(rgb[1]), int(rgb[2]) - @rgb_mod.setter - def rgb_mod(self, rgb: Tuple[int, int, int]) -> None: + @color_mod.setter + def color_mod(self, rgb: Tuple[int, int, int]) -> None: _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2])) diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 348ebe60..ebb05a81 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -59,7 +59,7 @@ def test_sdl_render() -> None: assert rgb.format == tcod.lib.SDL_PIXELFORMAT_RGB24 rgb.alpha_mod = rgb.alpha_mod rgb.blend_mode = rgb.blend_mode - rgb.rgb_mod = rgb.rgb_mod + rgb.color_mod = rgb.color_mod rgba = render.upload_texture(np.zeros((8, 8, 4), np.uint8), access=tcod.sdl.render.TextureAccess.TARGET) with render.set_render_target(rgba): render.copy(rgb) From c80f966c06180d9e96de03d84e6e72f39ff81feb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Feb 2022 18:06:19 -0800 Subject: [PATCH 0650/1101] Fix broken docstring, add note on add_watch errors. The `:` caused Sphinx to think this was a type description. --- tcod/event.py | 1 + tcod/sdl/render.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index 7eccabd9..b333b1fc 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1092,6 +1092,7 @@ def add_watch(callback: _EventCallback) -> _EventCallback: .. warning:: How uncaught exceptions in a callback are handled is not currently defined by tcod. They will likely be handled by :any:`sys.unraisablehook`. + This may be later changed to pass the excpetion to a :any`tcod.event.get` or :any:`tcod.event.wait` call. Args: callback (Callable[[Event], None]): diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 25f0d211..0a177596 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -73,7 +73,7 @@ def height(self) -> int: @property def alpha_mod(self) -> int: - """Texture alpha modulate value, can be set to: 0 - 255.""" + """Texture alpha modulate value, can be set to 0 - 255.""" out = ffi.new("uint8_t*") _check(lib.SDL_GetTextureAlphaMod(self.p, out)) return int(out[0]) From 1149f9a1d0f4a46e95c47abf12cb204ec3d0fc86 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Feb 2022 19:59:18 -0800 Subject: [PATCH 0651/1101] Prepare 13.4.0 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c57acc..1bff981d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.4.0] - 2022-02-04 ### Added - Adds `sdl_window` and `sdl_renderer` to tcod contexts. - Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback. From c0e7401b42c55564b733be74f1297f5c5e92fc5b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Feb 2022 00:46:32 -0800 Subject: [PATCH 0652/1101] Add many more renderer functions. --- .vscode/settings.json | 1 + tcod/sdl/render.py | 318 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 317 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 87df363c..63947009 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -238,6 +238,7 @@ "randomizer", "rbutton", "RCTRL", + "rects", "redist", "Redistributable", "redistributables", diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 0a177596..12dbf4e5 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -5,14 +5,14 @@ from __future__ import annotations import enum -from typing import Any, Optional, Tuple +from typing import Any, Optional, Tuple, Union import numpy as np from numpy.typing import NDArray import tcod.sdl.video from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p +from tcod.sdl import _check, _check_p, _required_version class TextureAccess(enum.IntEnum): @@ -43,6 +43,17 @@ def _query(self) -> Tuple[int, int, int, int]: lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2]) + def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None: + """Update the pixel data of this texture. + + .. versionadded:: unreleased + """ + if rect is None: + rect = (0, 0, self.width, self.height) + assert pixels.shape[:2] == rect[3], rect[2] + assert pixels[0].flags.c_contiguous + _check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0])) + @property def format(self) -> int: """Texture format, read only.""" @@ -204,6 +215,309 @@ def upload_texture( ) return texture + @property + def draw_color(self) -> Tuple[int, int, int, int]: + """Get or set the active RGBA draw color for this renderer. + + .. versionadded:: unreleased + """ + rgba = ffi.new("uint8_t[4]") + _check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3)) + return tuple(rgba) # type: ignore[return-value] + + @draw_color.setter + def draw_color(self, rgba: Tuple[int, int, int, int]) -> None: + _check(lib.SDL_SetRenderDrawColor(self.p, *rgba)) + + @property + def draw_blend_mode(self) -> int: + """Get or set the active blend mode of this renderer. + + .. versionadded:: unreleased + """ + out = ffi.new("SDL_BlendMode*") + _check(lib.SDL_GetRenderDrawBlendMode(self.p, out)) + return int(out[0]) + + @draw_blend_mode.setter + def draw_blend_mode(self, value: int) -> None: + _check(lib.SDL_SetRenderDrawBlendMode(self.p, value)) + + @property + def output_size(self) -> Tuple[int, int]: + """Get the (width, height) pixel resolution of the rendering context. + + .. seealso:: + https://wiki.libsdl.org/SDL_GetRendererOutputSize + + .. versionadded:: unreleased + """ + out = ffi.new("int[2]") + _check(lib.SDL_GetRendererOutputSize(self.p, out, out + 1)) + return out[0], out[1] + + @property + def clip_rect(self) -> Optional[Tuple[int, int, int, int]]: + """Get or set the clipping rectangle of this renderer. + + Set to None to disable clipping. + + .. versionadded:: unreleased + """ + if not lib.SDL_RenderIsClipEnabled(self.p): + return None + rect = ffi.new("SDL_Rect*") + lib.SDL_RenderGetClipRect(self.p, rect) + return rect.x, rect.y, rect.w, rect.h + + @clip_rect.setter + def clip_rect(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect) + _check(lib.SDL_RenderSetClipRect(self.p, rect_p)) + + @property + def integer_scaling(self) -> bool: + """Get or set if this renderer enforces integer scaling. + + .. seealso:: + https://wiki.libsdl.org/SDL_RenderSetIntegerScale + + .. versionadded:: unreleased + """ + return bool(lib.SDL_RenderGetIntegerScale(self.p)) + + @integer_scaling.setter + def integer_scaling(self, enable: bool) -> None: + _check(lib.SDL_RenderSetIntegerScale(self.p, enable)) + + @property + def logical_size(self) -> Tuple[int, int]: + """Get or set a device independent (width, height) resolution. + + Might be (0, 0) if a resolution was never assigned. + + .. seealso:: + https://wiki.libsdl.org/SDL_RenderSetLogicalSize + + .. versionadded:: unreleased + """ + out = ffi.new("int[2]") + lib.SDL_RenderGetLogicalSize(self.p, out, out + 1) + return out[0], out[1] + + @logical_size.setter + def logical_size(self, size: Tuple[int, int]) -> None: + _check(lib.SDL_RenderSetLogicalSize(self.p, *size)) + + @property + def scale(self) -> Tuple[float, float]: + """Get or set an (x_scale, y_scale) multiplier for drawing. + + .. seealso:: + https://wiki.libsdl.org/SDL_RenderSetScale + + .. versionadded:: unreleased + """ + out = ffi.new("float[2]") + lib.SDL_RenderGetScale(self.p, out, out + 1) + return out[0], out[1] + + @scale.setter + def scale(self, scale: Tuple[float, float]) -> None: + _check(lib.SDL_RenderSetScale(self.p, *scale)) + + @property + def viewport(self) -> Optional[Tuple[int, int, int, int]]: + """Get or set the drawing area for the current rendering target. + + .. seealso:: + https://wiki.libsdl.org/SDL_RenderSetViewport + + .. versionadded:: unreleased + """ + rect = ffi.new("SDL_Rect*") + lib.SDL_RenderGetViewport(self.p, rect) + return rect.x, rect.y, rect.w, rect.h + + @viewport.setter + def viewport(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + _check(lib.SDL_RenderSetViewport(self.p, (rect,))) + + def read_pixels( + self, + *, + rect: Optional[Tuple[int, int, int, int]] = None, + format: Optional[int] = None, + out: Optional[NDArray[Any]] = None, + ) -> NDArray[Any]: + """ + .. versionadded:: unreleased + """ + if format is None: + format = lib.SDL_PIXELFORMAT_RGBA32 + if rect is None: + texture_p = lib.SDL_GetRenderTarget(self.p) + if texture_p: + texture = Texture(texture_p) + rect = (0, 0, texture.width, texture.height) + else: + rect = (0, 0, *self.output_size) + width, height = rect[2:4] + if out is None: + if format == lib.SDL_PIXELFORMAT_RGBA32: + out = np.empty((height, width, 4), dtype=np.uint8) + elif format == lib.SDL_PIXELFORMAT_RGB24: + out = np.empty((height, width, 3), dtype=np.uint8) + else: + raise TypeError("Pixel format not supported yet.") + assert out.shape[:2] == height, width + assert out[0].flags.c_contiguous + _check(lib.SDL_RenderReadPixels(self.p, format, ffi.cast("void*", out.ctypes.data), out.strides[0])) + return out + + def clear(self) -> None: + """Clear the current render target with :any:`draw_color`. + + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderClear(self.p)) + + def fill_rect(self, rect: Tuple[float, float, float, float]) -> None: + """Fill a rectangle with :any:`draw_color`. + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderFillRectF(self.p, (rect,))) + + def draw_rect(self, rect: Tuple[float, float, float, float]) -> None: + """Draw a rectangle outline. + + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderDrawRectF(self.p, (rect,))) + + def draw_point(self, xy: Tuple[float, float]) -> None: + """Draw a point. + + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderDrawPointF(self.p, (xy,))) + + def draw_line(self, start: Tuple[float, float], end: Tuple[float, float]) -> None: + """Draw a single line. + + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderDrawLineF(self.p, *start, *end)) + + def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: + """Fill multiple rectangles from an array. + + .. versionadded:: unreleased + """ + assert len(rects.shape) == 2 + assert rects.shape[1] == 4 + rects = np.ascontiguousarray(rects) + if rects.dtype == np.intc: + _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) + elif rects.dtype == np.float32: + _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + else: + raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.") + + def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: + """Draw multiple outlined rectangles from an array. + + .. versionadded:: unreleased + """ + assert len(rects.shape) == 2 + assert rects.shape[1] == 4 + rects = np.ascontiguousarray(rects) + if rects.dtype == np.intc: + _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) + elif rects.dtype == np.float32: + _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + else: + raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.") + + def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None: + """Draw an array of points. + + .. versionadded:: unreleased + """ + assert len(points.shape) == 2 + assert points.shape[1] == 2 + points = np.ascontiguousarray(points) + if points.dtype == np.intc: + _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) + elif points.dtype == np.float32: + _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) + else: + raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.") + + def draw_lines(self, points: NDArray[Union[np.intc, np.float32]]) -> None: + """Draw a connected series of lines from an array. + + .. versionadded:: unreleased + """ + assert len(points.shape) == 2 + assert points.shape[1] == 2 + points = np.ascontiguousarray(points) + if points.dtype == np.intc: + _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) + elif points.dtype == np.float32: + _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1)) + else: + raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.") + + @_required_version((2, 0, 18)) + def geometry( + self, + texture: Optional[Texture], + xy: NDArray[np.float32], + color: NDArray[np.uint8], + uv: NDArray[np.float32], + indices: Optional[NDArray[Union[np.uint8, np.uint16, np.uint32]]] = None, + ) -> None: + """Render triangles from texture and vertex data. + + .. versionadded:: unreleased + """ + assert xy.dtype == np.float32 + assert len(xy.shape) == 2 + assert xy.shape[1] == 2 + assert xy[0].flags.c_contiguous + + assert color.dtype == np.uint8 + assert len(color.shape) == 2 + assert color.shape[1] == 4 + assert color[0].flags.c_contiguous + + assert uv.dtype == np.float32 + assert len(uv.shape) == 2 + assert uv.shape[1] == 2 + assert uv[0].flags.c_contiguous + if indices is not None: + assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32) + indices = np.ascontiguousarray(indices) + assert len(indices.shape) == 1 + assert xy.shape[0] == color.shape[0] == uv.shape[0] + _check( + lib.SDL_RenderGeometryRaw( + self.p, + texture.p if texture else ffi.NULL, + ffi.cast("float*", xy.ctypes.data), + xy.strides[0], + ffi.cast("uint8_t*", color.ctypes.data), + color.strides[0], + ffi.cast("float*", uv.ctypes.data), + uv.strides[0], + xy.shape[0], # Number of vertices. + ffi.cast("void*", indices.ctypes.data) if indices is not None else ffi.NULL, + indices.size if indices is not None else 0, + indices.itemsize if indices is not None else 0, + ) + ) + def new_renderer( window: tcod.sdl.video.Window, From 6f1b22c887c43d1c838928eb52e0c976ad6b9809 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Feb 2022 01:04:34 -0800 Subject: [PATCH 0653/1101] Add extended copy parameters. Fix tests. --- tcod/sdl/render.py | 44 ++++++++++++++++++++++++++++++++++++++------ tests/test_sdl.py | 2 ++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 12dbf4e5..94b54f5e 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -26,6 +26,17 @@ class TextureAccess(enum.IntEnum): """Texture will be used as a render target.""" +class RendererFlip(enum.IntFlag): + """Flip parameter for :any:`Renderer.copy`.""" + + NONE = 0 + """Default value, no flip.""" + HORIZONTAL = 1 + """Flip the image horizontally.""" + VERTICAL = 2 + """Flip the image vertically.""" + + class Texture: """SDL hardware textures.""" @@ -146,16 +157,37 @@ def __eq__(self, other: Any) -> bool: def copy( self, texture: Texture, - source: Optional[Tuple[int, int, int, int]] = None, - dest: Optional[Tuple[int, int, int, int]] = None, + source: Optional[Tuple[float, float, float, float]] = None, + dest: Optional[Tuple[float, float, float, float]] = None, + angle: float = 0, + center: Optional[Tuple[float, float]] = None, + flip: RendererFlip = RendererFlip.NONE, ) -> None: """Copy a texture to the rendering target. - `source` and `dest` are (x, y, width, height) regions of the texture parameter and target texture respectively. + Args: + texture: The texture to copy onto the current texture target. + source: The (x, y, width, height) region of `texture` to copy. If None then the entire texture is copied. + dest: The (x, y, width, height) region of the target. If None then the entire target is drawn over. + angle: The angle in degrees to rotate the image clockwise. + center: The (x, y) point where rotation is applied. If None then the center of `dest` is used. + flip: Flips the `texture` when drawing it. + + .. versionchanged:: unreleased + `source` and `dest` can now be float tuples. + Added the `angle`, `center`, and `flip` parameters. """ - source_ = ffi.NULL if source is None else ffi.new("SDL_Rect*", source) - dest_ = ffi.NULL if dest is None else ffi.new("SDL_Rect*", dest) - _check(lib.SDL_RenderCopy(self.p, texture.p, source_, dest_)) + _check( + lib.SDL_RenderCopyExF( + self.p, + texture.p, + (source,) if source is not None else ffi.NULL, + (dest,) if dest is not None else ffi.NULL, + angle, + (center,) if center is not None else ffi.NULL, + flip, + ) + ) def present(self) -> None: """Present the currently rendered image to the screen.""" diff --git a/tests/test_sdl.py b/tests/test_sdl.py index ebb05a81..8dea7f33 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -4,6 +4,7 @@ import pytest import tcod.sdl.render +import tcod.sdl.sys import tcod.sdl.video @@ -44,6 +45,7 @@ def test_sdl_window_bad_types() -> None: def test_sdl_screen_saver() -> None: + tcod.sdl.sys.init() assert tcod.sdl.video.screen_saver_allowed(False) is False assert tcod.sdl.video.screen_saver_allowed(True) is True assert tcod.sdl.video.screen_saver_allowed() is True From 45d308b28d0a92bb314dea632ec909ad569852ff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Feb 2022 12:28:47 -0800 Subject: [PATCH 0654/1101] Experiment with mixer channels. --- tcod/sdl/audio.py | 97 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 8e0e3c70..4edfe974 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -3,7 +3,7 @@ import sys import threading import time -from typing import Any, Iterator, List, Optional +from typing import Any, Callable, Dict, Hashable, Iterator, List, Optional, Tuple, Union import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray @@ -139,8 +139,65 @@ def __default_callback(self, stream: NDArray[Any]) -> None: stream[...] = self.silence +class Channel: + mixer: Mixer + + def __init__(self) -> None: + self.volume: Union[float, Tuple[float, ...]] = 1.0 + self.sound_queue: List[NDArray[Any]] = [] + self.on_end_callback: Optional[Callable[[Channel], None]] = None + + @property + def busy(self) -> bool: + return bool(self.sound_queue) + + def play( + self, + sound: ArrayLike, + *, + on_end: Optional[Callable[[Channel], None]] = None, + ) -> None: + self.sound_queue[:] = [self._verify_audio_sample(sound)] + self.on_end_callback = on_end + + def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]: + """Verify an audio sample is valid and return it as a Numpy array.""" + array: NDArray[Any] = np.asarray(sample) + assert array.dtype == self.mixer.device.format + if len(array.shape) == 1: + array = array[:, np.newaxis] + return array + + def _on_mix(self, stream: NDArray[Any]) -> None: + while self.sound_queue and stream.size: + buffer = self.sound_queue[0] + if buffer.shape[0] > stream.shape[0]: + # Mix part of the buffer into the stream. + stream[:] += buffer[: stream.shape[0]] * self.volume + self.sound_queue[0] = buffer[stream.shape[0] :] + break # Stream was filled. + # Remaining buffer fits the stream array. + stream[: buffer.shape[0]] += buffer * self.volume + stream = stream[buffer.shape[0] :] + self.sound_queue.pop(0) + if not self.sound_queue and self.on_end_callback is not None: + self.on_end_callback(self) + + def fadeout(self, time: float) -> None: + assert time >= 0 + time_samples = round(time * self.mixer.device.frequency) + 1 + buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32) + self._on_mix(buffer) + buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:] + self.sound_queue[:] = [buffer] + + def stop(self) -> None: + self.fadeout(0.0005) + + class Mixer(threading.Thread): def __init__(self, device: AudioDevice): + assert device.format == np.float32 super().__init__(daemon=True) self.device = device @@ -161,23 +218,35 @@ def on_stream(self, stream: NDArray[Any]) -> None: class BasicMixer(Mixer): def __init__(self, device: AudioDevice): super().__init__(device) - self.play_buffers: List[List[NDArray[Any]]] = [] + self.channels: Dict[Hashable, Channel] = {} - def play(self, sound: ArrayLike) -> None: - array = np.asarray(sound, dtype=self.device.format) - assert array.size - if len(array.shape) == 1: - array = array[:, np.newaxis] - chunks: List[NDArray[Any]] = np.split(array, range(0, len(array), self.device.samples)[1:])[::-1] - self.play_buffers.append(chunks) + def get_channel(self, key: Hashable) -> Channel: + if key not in self.channels: + self.channels[key] = Channel() + self.channels[key].mixer = self + return self.channels[key] + + def get_free_channel(self) -> Channel: + i = 0 + while True: + if not self.get_channel(i).busy: + return self.channels[i] + i += 1 + + def play( + self, + sound: ArrayLike, + *, + on_end: Optional[Callable[[Channel], None]] = None, + ) -> Channel: + channel = self.get_free_channel() + channel.play(sound, on_end=on_end) + return channel def on_stream(self, stream: NDArray[Any]) -> None: super().on_stream(stream) - for chunks in self.play_buffers: - chunk = chunks.pop() - stream[: len(chunk)] += chunk - - self.play_buffers = [chunks for chunks in self.play_buffers if chunks] + for channel in list(self.channels.values()): + channel._on_mix(stream) @ffi.def_extern() # type: ignore From 40435f5e5110a061b74e142c1c6e43fcf40a4b13 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Feb 2022 15:16:54 -0800 Subject: [PATCH 0655/1101] Upload Windows wheel artifacts. Keep these for 7 days in case anyone is testing them this way. --- .github/workflows/python-package.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index caf0f836..dc00a1ae 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -152,7 +152,13 @@ jobs: with: name: sdist path: dist/tcod-*.tar.gz - retention-days: 3 + retention-days: 7 + - uses: actions/upload-artifact@v2 + if: runner.os == 'Windows' + with: + name: wheels-windows + path: dist/*.whl + retention-days: 7 isolated: # Test installing the package from source. needs: build @@ -224,9 +230,9 @@ jobs: - name: Archive wheel uses: actions/upload-artifact@v2 with: - name: wheel-linux + name: wheels-linux path: wheelhouse/*.whl - retention-days: 1 + retention-days: 7 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') env: @@ -266,9 +272,9 @@ jobs: - name: Archive wheel uses: actions/upload-artifact@v2 with: - name: wheel-macos + name: wheels-macos path: wheelhouse/*.whl - retention-days: 1 + retention-days: 7 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') env: From 5657e3b8daa8003e5d7fb094db28bc6c72ba96e9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Feb 2022 19:03:07 -0800 Subject: [PATCH 0656/1101] Add Renderer.set_vsync. --- tcod/sdl/render.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 94b54f5e..ddb42b26 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -375,6 +375,14 @@ def viewport(self) -> Optional[Tuple[int, int, int, int]]: def viewport(self, rect: Optional[Tuple[int, int, int, int]]) -> None: _check(lib.SDL_RenderSetViewport(self.p, (rect,))) + @_required_version((2, 0, 18)) + def set_vsync(self, enable: bool) -> None: + """Enable or disable VSync for this renderer. + + .. versionadded:: unreleased + """ + _check(lib.SDL_RenderSetVSync(self.p, enable)) + def read_pixels( self, *, From 571eafd3b697dc7d088894911d2049e339b853a1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Feb 2022 12:21:29 -0800 Subject: [PATCH 0657/1101] Add compose_blend_mode and blend mode enums. Refactor TextureAccess enums to not need library lookup. --- tcod/sdl/render.py | 110 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index ddb42b26..8bec1880 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -18,11 +18,11 @@ class TextureAccess(enum.IntEnum): """Determines how a texture is expected to be used.""" - STATIC = lib.SDL_TEXTUREACCESS_STATIC or 0 + STATIC = 0 """Texture rarely changes.""" - STREAMING = lib.SDL_TEXTUREACCESS_STREAMING or 0 + STREAMING = 1 """Texture frequently changes.""" - TARGET = lib.SDL_TEXTUREACCESS_TARGET or 0 + TARGET = 2 """Texture will be used as a render target.""" @@ -37,6 +37,110 @@ class RendererFlip(enum.IntFlag): """Flip the image vertically.""" +class BlendFactor(enum.IntEnum): + """SDL blend factors. + + .. seealso:: + :any:`compose_blend_mode` + https://wiki.libsdl.org/SDL_BlendFactor + + .. versionadded:: unreleased + """ + + ZERO = 0x1 + """""" + ONE = 0x2 + """""" + SRC_COLOR = 0x3 + """""" + ONE_MINUS_SRC_COLOR = 0x4 + """""" + SRC_ALPHA = 0x5 + """""" + ONE_MINUS_SRC_ALPHA = 0x6 + """""" + DST_COLOR = 0x7 + """""" + ONE_MINUS_DST_COLOR = 0x8 + """""" + DST_ALPHA = 0x9 + """""" + ONE_MINUS_DST_ALPHA = 0xA + """""" + + +class BlendOperation(enum.IntEnum): + """SDL blend operations. + + .. seealso:: + :any:`compose_blend_mode` + https://wiki.libsdl.org/SDL_BlendOperation + + .. versionadded:: unreleased + """ + + ADD = 0x1 + """dest + source""" + SUBTRACT = 0x2 + """dest - source""" + REV_SUBTRACT = 0x3 + """source - dest""" + MINIMUM = 0x4 + """min(dest, source)""" + MAXIMUM = 0x5 + """max(dest, source)""" + + +class BlendMode(enum.IntEnum): + """SDL blend modes. + + .. seealso:: + :any:`Texture.blend_mode` + :any:`Renderer.draw_blend_mode` + :any:`compose_blend_mode` + + .. versionadded:: unreleased + """ + + NONE = 0x00000000 + """""" + BLEND = 0x00000001 + """""" + ADD = 0x00000002 + """""" + MOD = 0x00000004 + """""" + INVALID = 0x7FFFFFFF + """""" + + +def compose_blend_mode( + source_color_factor: BlendFactor, + dest_color_factor: BlendFactor, + color_operation: BlendOperation, + source_alpha_factor: BlendFactor, + dest_alpha_factor: BlendFactor, + alpha_operation: BlendOperation, +) -> BlendMode: + """Return a custom blend mode composed of the given factors and operations. + + .. seealso:: + https://wiki.libsdl.org/SDL_ComposeCustomBlendMode + + .. versionadded:: unreleased + """ + return BlendMode( + lib.SDL_ComposeCustomBlendMode( + source_color_factor, + dest_color_factor, + color_operation, + source_alpha_factor, + dest_alpha_factor, + alpha_operation, + ) + ) + + class Texture: """SDL hardware textures.""" From e3fc45fd1093fa59a0e7e26bd9f72c37d3173591 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Feb 2022 12:33:03 -0800 Subject: [PATCH 0658/1101] Have enum properties return enum instances. --- CHANGELOG.md | 3 +++ tcod/sdl/render.py | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bff981d..fef7ea70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- `Texture.access` and `Texture.blend_mode` properties now return enum instances. + You can still set them with `int` but Mypy will complain. ## [13.4.0] - 2022-02-04 ### Added diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 8bec1880..b005bcd5 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -177,11 +177,15 @@ def format(self) -> int: return int(buffer[0]) @property - def access(self) -> int: - """Texture access mode, read only.""" + def access(self) -> TextureAccess: + """Texture access mode, read only. + + .. versionadded:: unreleased + Property now returns a TextureAccess instance. + """ buffer = ffi.new("int*") lib.SDL_QueryTexture(self.p, ffi.NULL, buffer, ffi.NULL, ffi.NULL) - return int(buffer[0]) + return TextureAccess(buffer[0]) @property def width(self) -> int: @@ -209,11 +213,15 @@ def alpha_mod(self, value: int) -> None: _check(lib.SDL_SetTextureAlphaMod(self.p, value)) @property - def blend_mode(self) -> int: - """Texture blend mode, can be set.""" + def blend_mode(self) -> BlendMode: + """Texture blend mode, can be set. + + .. versionadded:: unreleased + Property now returns a BlendMode instance. + """ out = ffi.new("SDL_BlendMode*") _check(lib.SDL_GetTextureBlendMode(self.p, out)) - return int(out[0]) + return BlendMode(out[0]) @blend_mode.setter def blend_mode(self, value: int) -> None: @@ -366,14 +374,14 @@ def draw_color(self, rgba: Tuple[int, int, int, int]) -> None: _check(lib.SDL_SetRenderDrawColor(self.p, *rgba)) @property - def draw_blend_mode(self) -> int: + def draw_blend_mode(self) -> BlendMode: """Get or set the active blend mode of this renderer. .. versionadded:: unreleased """ out = ffi.new("SDL_BlendMode*") _check(lib.SDL_GetRenderDrawBlendMode(self.p, out)) - return int(out[0]) + return BlendMode(out[0]) @draw_blend_mode.setter def draw_blend_mode(self, value: int) -> None: From c8d0b7bcdd8a6131ff3d91a683499e08ebb7a0a6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Feb 2022 13:13:10 -0800 Subject: [PATCH 0659/1101] Add Window.mouse_rect. Fix docs of changed properties. --- tcod/sdl/__init__.py | 9 +++++++++ tcod/sdl/render.py | 4 ++-- tcod/sdl/video.py | 19 ++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 524f24b6..81f04d12 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -58,6 +58,15 @@ def _linked_version() -> Tuple[int, int, int]: return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) +def _version_at_least(required: Tuple[int, int, int]) -> None: + """Raise an error if the compiled version is less than required. Used to guard recentally defined SDL functions.""" + if required <= _compiled_version(): + return + raise RuntimeError( + f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + ) + + def _required_version(required: Tuple[int, int, int]) -> Callable[[T], T]: if not lib: # Read the docs mock object. return lambda x: x diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index b005bcd5..98017772 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -180,7 +180,7 @@ def format(self) -> int: def access(self) -> TextureAccess: """Texture access mode, read only. - .. versionadded:: unreleased + .. versionchanged:: unreleased Property now returns a TextureAccess instance. """ buffer = ffi.new("int*") @@ -216,7 +216,7 @@ def alpha_mod(self, value: int) -> None: def blend_mode(self) -> BlendMode: """Texture blend mode, can be set. - .. versionadded:: unreleased + .. versionchanged:: unreleased Property now returns a BlendMode instance. """ out = ffi.new("SDL_BlendMode*") diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 4d1d3b7e..bdcb6b6d 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -12,7 +12,7 @@ from numpy.typing import ArrayLike, NDArray from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p, _required_version +from tcod.sdl import _check, _check_p, _required_version, _version_at_least __all__ = ( "WindowFlags", @@ -278,6 +278,23 @@ def grab(self) -> bool: def grab(self, value: bool) -> None: lib.SDL_SetWindowGrab(self.p, value) + @property + def mouse_rect(self) -> Optional[Tuple[int, int, int, int]]: + """Get or set the mouse confinement area when the window has mouse focus. + + Setting this will not automatically grab the cursor. + + .. versionadded:: unreleased + """ + _version_at_least((2, 0, 18)) + rect = lib.SDL_GetWindowMouseRect(self.p) + return (rect.x, rect.y, rect.w, rect.h) if rect else None + + @mouse_rect.setter + def mouse_rect(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + _version_at_least((2, 0, 18)) + _check(lib.SDL_SetWindowMouseRect(self.p, (rect,) if rect else ffi.NULL)) + @_required_version((2, 0, 16)) def flash(self, operation: FlashOperation = FlashOperation.UNTIL_FOCUSED) -> None: """Get the users attention.""" From 9676735cff468b723e47dcd3ddf1cad6f78d2972 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Feb 2022 13:27:12 -0800 Subject: [PATCH 0660/1101] Experiment with clipboard handing. --- tcod/sdl/sys.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 7569ed83..34f5d4fc 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -1,10 +1,11 @@ from __future__ import annotations import enum +import warnings from typing import Any, Tuple from tcod.loader import ffi, lib -from tcod.sdl import _check +from tcod.sdl import _check, _get_error class Subsystem(enum.IntFlag): @@ -61,3 +62,16 @@ def _get_power_info() -> Tuple[_PowerState, int, int]: seconds_of_power = buffer[0] percenage = buffer[1] return power_state, seconds_of_power, percenage + + +def _get_clipboard() -> str: + """Return the text of the clipboard.""" + text = str(ffi.string(lib.SDL_GetClipboardText()), encoding="utf-8") + if not text: # Show the reason for an empty return, this should probably be logged instead. + warnings.warn(f"Return string is empty because: {_get_error()}") + return text + + +def _set_clipboard(text: str) -> None: + """Replace the clipboard with text.""" + _check(lib.SDL_SetClipboardText(text.encode("utf-8"))) From 3654e2f1d134748b58c31c21c13b6c216afa64d0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 7 Feb 2022 13:05:29 -0800 Subject: [PATCH 0661/1101] Add public SDL mouse module. --- .vscode/settings.json | 5 + docs/index.rst | 1 + docs/sdl/mouse.rst | 5 + tcod/sdl/mouse.py | 212 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 docs/sdl/mouse.rst create mode 100644 tcod/sdl/mouse.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 63947009..92284692 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -127,6 +127,7 @@ "howto", "htbp", "htmlzip", + "IBEAM", "ifdef", "ifndef", "iinfo", @@ -267,6 +268,10 @@ "servernum", "setuptools", "SHADOWCAST", + "SIZENESW", + "SIZENS", + "SIZENWSE", + "SIZEWE", "SMILIE", "snprintf", "stdeb", diff --git a/docs/index.rst b/docs/index.rst index 7ddc5583..3638a87c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ Contents: tcod/tileset libtcodpy sdl/render + sdl/mouse sdl/video Indices and tables diff --git a/docs/sdl/mouse.rst b/docs/sdl/mouse.rst new file mode 100644 index 00000000..932f4d9f --- /dev/null +++ b/docs/sdl/mouse.rst @@ -0,0 +1,5 @@ +tcod.sdl.mouse - SDL Mouse Functions +==================================== + +.. automodule:: tcod.sdl.mouse + :members: diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py new file mode 100644 index 00000000..aa174ad9 --- /dev/null +++ b/tcod/sdl/mouse.py @@ -0,0 +1,212 @@ +"""SDL mouse and cursor functions. + +.. versionadded:: unreleased +""" +from __future__ import annotations + +import enum +from typing import Any, Optional, Tuple, Union + +import numpy as np +from numpy.typing import ArrayLike, NDArray + +import tcod.event +import tcod.sdl.video +from tcod.loader import ffi, lib +from tcod.sdl import _check, _check_p + + +class Cursor: + """A cursor icon for use with :any:`set_cursor`.""" + + def __init__(self, sdl_cursor_p: Any): + if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"): + raise TypeError(f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)}).") + if not sdl_cursor_p: + raise TypeError("C pointer must not be null.") + self.p = sdl_cursor_p + + def __eq__(self, other: Any) -> bool: + return bool(self.p == getattr(other, "p", None)) + + @classmethod + def _claim(cls, sdl_cursor_p: Any) -> Cursor: + """Verify and wrap this pointer in a garbage collector before returning a Cursor.""" + return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_FreeCursor)) + + +class SystemCursor(enum.IntEnum): + """An enumerator of system cursor icons.""" + + ARROW = 0 + """""" + IBEAM = enum.auto() + """""" + WAIT = enum.auto() + """""" + CROSSHAIR = enum.auto() + """""" + WAITARROW = enum.auto() + """""" + SIZENWSE = enum.auto() + """""" + SIZENESW = enum.auto() + """""" + SIZEWE = enum.auto() + """""" + SIZENS = enum.auto() + """""" + SIZEALL = enum.auto() + """""" + NO = enum.auto() + """""" + HAND = enum.auto() + """""" + + +def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: Tuple[int, int] = (0, 0)) -> Cursor: + """Return a new non-color Cursor from the provided parameters. + + Args: + data: A row-major boolean array for the data parameters. See the SDL docs for more info. + mask: A row-major boolean array for the mask parameters. See the SDL docs for more info. + hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). + + .. seealso:: + :any:`set_cursor` + https://wiki.libsdl.org/SDL_CreateCursor + """ + if len(data.shape) != 2: + raise TypeError("Data and mask arrays must be 2D.") + if data.shape != mask.shape: + raise TypeError("Data and mask arrays must have the same shape.") + height, width = data.shape + data_packed = np.packbits(data, axis=0, bitorder="big") + mask_packed = np.packbits(mask, axis=0, bitorder="big") + return Cursor._claim( + lib.SDL_CreateCursor( + ffi.from_buffer("uint8_t*", data_packed), ffi.from_buffer("uint8_t*", mask_packed), width, height, *hot_xy + ) + ) + + +def new_color_cursor(pixels: ArrayLike, hot_xy: Tuple[int, int]) -> Cursor: + """ + Args: + pixels: A row-major array of RGB or RGBA pixels. + hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). + + .. seealso:: + :any:`set_cursor` + """ + surface = tcod.sdl.video._TempSurface(pixels) + return Cursor._claim(lib.SDL_CreateColorCursor(surface.p, *hot_xy)) + + +def new_system_cursor(cursor: SystemCursor) -> Cursor: + """Return a new Cursor from one of the system cursors labeled by SystemCursor. + + .. seealso:: + :any:`set_cursor` + """ + return Cursor._claim(lib.SDL_CreateSystemCursor(cursor)) + + +def set_cursor(cursor: Optional[Union[Cursor, SystemCursor]]) -> None: + """Change the active cursor to the one provided. + + Args: + cursor: A cursor created from :any:`new_cursor`, :any:`new_color_cursor`, or :any:`new_system_cursor`. + Can also take values of :any:`SystemCursor` directly. + None will force the current cursor to be redrawn. + """ + if isinstance(cursor, SystemCursor): + cursor = new_system_cursor(cursor) + lib.SDL_SetCursor(cursor.p if cursor is not None else ffi.NULL) + + +def get_default_cursor() -> Cursor: + """Return the default cursor.""" + return Cursor(_check_p(lib.SDL_GetDefaultCursor())) + + +def get_cursor() -> Optional[Cursor]: + """Return the active cursor, or None if these is no mouse.""" + cursor_p = lib.SDL_GetCursor() + return Cursor(cursor_p) if cursor_p else None + + +def capture(enable: bool) -> None: + """Enable or disable mouse capture to track the mouse outside of a window. + + It is highly reccomended to read the related remarks section in the SDL docs before using this. + + .. seealso:: + :any:`tcod.sdl.mouse.set_relative_mode` + https://wiki.libsdl.org/SDL_CaptureMouse + """ + _check(lib.SDL_CaptureMouse(enable)) + + +def set_relative_mode(enable: bool) -> None: + """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion. + + .. seealso:: + :any:`tcod.sdl.mouse.capture` + https://wiki.libsdl.org/SDL_SetRelativeMouseMode + """ + _check(lib.SDL_SetRelativeMouseMode(enable)) + + +def get_relative_mode() -> bool: + """Return True if relative mouse mode is enabled.""" + return bool(lib.SDL_GetRelativeMouseMode()) + + +def get_global_state() -> tcod.event.MouseState: + """Return the mouse state relative to the desktop. + + .. seealso:: + https://wiki.libsdl.org/SDL_GetGlobalMouseState + """ + xy = ffi.new("int[2]") + state = lib.SDL_GetGlobalMouseState(xy, xy + 1) + return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + + +def get_relative_state() -> tcod.event.MouseState: + """Return the mouse state, the coordinates are relative to the last time this function was called. + + .. seealso:: + https://wiki.libsdl.org/SDL_GetRelativeMouseState + """ + xy = ffi.new("int[2]") + state = lib.SDL_GetRelativeMouseState(xy, xy + 1) + return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + + +def get_state() -> tcod.event.MouseState: + """Return the mouse state relative to the window with mouse focus. + + .. seealso:: + https://wiki.libsdl.org/SDL_GetMouseState + """ + xy = ffi.new("int[2]") + state = lib.SDL_GetMouseState(xy, xy + 1) + return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + + +def get_focus() -> Optional[tcod.sdl.video.Window]: + """Return the window which currently has mouse focus.""" + window_p = lib.SDL_GetMouseFocus() + return tcod.sdl.video.Window(window_p) if window_p else None + + +def warp_global(x: int, y: int) -> None: + """Move the mouse cursor to a position on the desktop.""" + _check(lib.SDL_WarpMouseGlobal(x, y)) + + +def warp_in_window(window: tcod.sdl.video.Window, x: int, y: int) -> None: + """Move the mouse cursor to a position within a window.""" + _check(lib.SDL_WarpMouseInWindow(window.p, x, y)) From 2d0a2e512c97a1fb21b32d202ac116a0a1abe170 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 9 Feb 2022 21:36:54 -0800 Subject: [PATCH 0662/1101] Add more audio callback support and document audio device opening. --- .vscode/settings.json | 1 + tcod/sdl/audio.py | 99 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 92284692..e02d3ea2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -299,6 +299,7 @@ "undoc", "Unifont", "unraisablehook", + "unraiseable", "upscaling", "VAFUNC", "vcoef", diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 4edfe974..c483fd15 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum import sys import threading import time @@ -7,6 +8,7 @@ import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray +from typing_extensions import Literal import tcod.sdl.sys from tcod.loader import ffi, lib @@ -70,7 +72,20 @@ def __init__( self.silence = int(spec.silence) self.samples = int(spec.samples) self.buffer_size = int(spec.size) - self._callback = self.__default_callback + self._handle: Optional[Any] = None + self._callback: Callable[[AudioDevice, NDArray[Any]], None] = self.__default_callback + + @property + def callback(self) -> Callable[[AudioDevice, NDArray[Any]], None]: + if self._handle is None: + raise TypeError("This AudioDevice was opened without a callback.") + return self._callback + + @callback.setter + def callback(self, new_callback: Callable[[AudioDevice, NDArray[Any]], None]) -> None: + if self._handle is None: + raise TypeError("This AudioDevice was opened without a callback.") + self._callback = new_callback @property def _sample_size(self) -> int: @@ -135,8 +150,9 @@ def close(self) -> None: lib.SDL_CloseAudioDevice(self.device_id) self.device_id = 0 - def __default_callback(self, stream: NDArray[Any]) -> None: - stream[...] = self.silence + @staticmethod + def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: + stream[...] = device.silence class Channel: @@ -249,13 +265,17 @@ def on_stream(self, stream: NDArray[Any]) -> None: channel._on_mix(stream) +class _AudioCallbackUserdata: + device: AudioDevice + + @ffi.def_extern() # type: ignore def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: """Handle audio device callbacks.""" - device: Optional[AudioDevice] = ffi.from_handle(userdata)() - assert device is not None + data: _AudioCallbackUserdata = ffi.from_handle(userdata)() + device = data.device buffer = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) - device._callback(buffer) + device._callback(device, buffer) def _get_devices(capture: bool) -> Iterator[str]: @@ -276,6 +296,23 @@ def get_capture_devices() -> Iterator[str]: yield from _get_devices(capture=True) +class AllowedChanges(enum.IntFlag): + """Which parameters are allowed to be changed when the values given are not supported.""" + + NONE = 0 + """""" + FREQUENCY = 0x01 + """""" + FORMAT = 0x02 + """""" + CHANNELS = 0x04 + """""" + SAMPLES = 0x08 + """""" + ANY = FREQUENCY | FORMAT | CHANNELS | SAMPLES + """""" + + def open( name: Optional[str] = None, capture: bool = False, @@ -284,10 +321,43 @@ def open( format: DTypeLike = np.float32, channels: int = 2, samples: int = 0, - allowed_changes: int = 0, + allowed_changes: AllowedChanges = AllowedChanges.NONE, paused: bool = False, + callback: Union[None, Literal[True], Callable[[AudioDevice, NDArray[Any]], None]] = None, ) -> AudioDevice: - """Open an audio device for playback or capture.""" + """Open an audio device for playback or capture and return it. + + Args: + name: The name of the device to open, or None for the most reasonable default. + capture: True if this is a recording device, or False if this is an output device. + frequency: The desired sample rate to open the device with. + format: The data format to use for samples as a NumPy dtype. + channels: The number of speakers for the device. 1, 2, 4, or 6 are typical options. + samples: The desired size of the audio buffer, must be a power of two. + allowed_changes: + By default if the hardware does not support the desired format than SDL will transparently convert between + formats for you. + Otherwise you can specify which parameters are allowed to be changed to fit the hardware better. + paused: + If True then the device will begin in a paused state. + It can then be unpaused by assigning False to :any:`AudioDevice.paused`. + callback: + If None then this device will be opened in push mode and you'll have to use :any:`AudioDevice.queue_audio` + to send audio data or :any:`AudioDevice.dequeue_audio` to receive it. + If a callback is given then you can change it later, but you can not enable or disable the callback on an + opened device. + If True then a default callback which plays silence will be used, this is useful if you need the audio + device before your callback is ready. + + If a callback is given then it will be called with the `AudioDevice` and a Numpy buffer of the data stream. + This callback will be run on a separate thread. + Exceptions not handled by the callback become unraiseable and will be handled by :any:`sys.unraisablehook`. + + .. seealso:: + https://wiki.libsdl.org/SDL_AudioSpec + https://wiki.libsdl.org/SDL_OpenAudioDevice + + """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) desired = ffi.new( "SDL_AudioSpec*", @@ -300,6 +370,14 @@ def open( "userdata": ffi.NULL, }, ) + callback_data = _AudioCallbackUserdata() + if callback is not None: + handle = ffi.new_handle(callback_data) + desired.callback = lib._sdl_audio_callback + desired.userdata = handle + else: + handle = None + obtained = ffi.new("SDL_AudioSpec*") device_id: int = lib.SDL_OpenAudioDevice( ffi.NULL if name is None else name.encode("utf-8"), @@ -310,5 +388,10 @@ def open( ) assert device_id >= 0, _get_error() device = AudioDevice(device_id, capture, obtained) + if callback is not None: + callback_data.device = device + device._handle = handle + if callback is not True: + device._callback = callback device.paused = paused return device From e0da84a3554d35607fb00f90b3a83815d0bb788f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 10 Feb 2022 18:27:49 -0800 Subject: [PATCH 0663/1101] Update loose unreleased tags with the release script. --- scripts/tag_release.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 4f900ba2..1479ec82 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -3,12 +3,15 @@ import argparse import datetime +import os import re import subprocess import sys from pathlib import Path from typing import Tuple +PROJECT_DIR = Path(__file__).parent.parent + parser = argparse.ArgumentParser(description="Tags and releases the next version of this project.") parser.add_argument("tag", help="Semantic version number to use as the tag.") @@ -24,7 +27,7 @@ def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: """Return an updated changelog and and the list of changes.""" match = re.match( pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)", - string=Path("CHANGELOG.md").read_text(encoding="utf-8"), + string=(PROJECT_DIR / "CHANGELOG.md").read_text(encoding="utf-8"), flags=re.DOTALL, ) assert match @@ -41,6 +44,23 @@ def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: return "".join((header, tagged, tail)), changes +def replace_unreleased_tags(tag: str, dry_run: bool) -> None: + match = re.match(r"\d+\.\d+", tag) + assert match + short_tag = match.group() + for directory, _, files in os.walk(PROJECT_DIR / "tcod"): + for filename in files: + file = Path(directory, filename) + if file.suffix != ".py": + continue + text = file.read_text(encoding="utf-8") + new_text = re.sub(r":: unreleased", rf":: {short_tag}", text) + if text != new_text: + print(f"Update tags in {file}") + if not dry_run: + file.write_text(new_text, encoding="utf-8") + + def main() -> None: if len(sys.argv) == 1: parser.print_help(sys.stderr) @@ -54,8 +74,10 @@ def main() -> None: print("--- New changelog:") print(new_changelog) + replace_unreleased_tags(args.tag, args.dry_run) + if not args.dry_run: - Path("CHANGELOG.md").write_text(new_changelog, encoding="utf-8") + (PROJECT_DIR / "CHANGELOG.md").write_text(new_changelog, encoding="utf-8") edit = ["-e"] if args.edit else [] subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) subprocess.check_call(["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] + edit) From b0695329c37d118ca804438fc226a4865fe7e103 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 10 Feb 2022 18:28:31 -0800 Subject: [PATCH 0664/1101] Document the SDL audio module. --- docs/index.rst | 1 + docs/sdl/audio.rst | 5 ++++ tcod/sdl/audio.py | 62 ++++++++++++++++++++++++++++++++-------------- tcod/sdl/sys.py | 26 +++++++++---------- 4 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 docs/sdl/audio.rst diff --git a/docs/index.rst b/docs/index.rst index 3638a87c..981ce6bf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,6 +39,7 @@ Contents: tcod/render tcod/tileset libtcodpy + sdl/audio sdl/render sdl/mouse sdl/video diff --git a/docs/sdl/audio.rst b/docs/sdl/audio.rst new file mode 100644 index 00000000..702bd470 --- /dev/null +++ b/docs/sdl/audio.rst @@ -0,0 +1,5 @@ +tcod.sdl.audio - SDL Audio +========================== + +.. automodule:: tcod.sdl.audio + :members: diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index c483fd15..5edff741 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,3 +1,7 @@ +"""SDL2 audio playback and recording tools. + +.. versionadded:: unreleased +""" from __future__ import annotations import enum @@ -8,7 +12,7 @@ import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray -from typing_extensions import Literal +from typing_extensions import Final, Literal import tcod.sdl.sys from tcod.loader import ffi, lib @@ -52,7 +56,10 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: class AudioDevice: - """An SDL audio device.""" + """An SDL audio device. + + Open new audio devices using :any:`tcod.sdl.audio.open`. + """ def __init__( self, @@ -63,20 +70,30 @@ def __init__( assert device_id >= 0 assert ffi.typeof(spec) is ffi.typeof("SDL_AudioSpec*") assert spec - self.device_id = device_id - self.spec = spec - self.frequency = spec.freq - self.is_capture = capture - self.format = _dtype_from_format(spec.format) - self.channels = int(spec.channels) - self.silence = int(spec.silence) - self.samples = int(spec.samples) - self.buffer_size = int(spec.size) + self.device_id: Final[int] = device_id + """The SDL device identifier used for SDL C functions.""" + self.spec: Final[Any] = spec + """The SDL_AudioSpec as a CFFI object.""" + self.frequency: Final[int] = spec.freq + """The audio device sound frequency.""" + self.is_capture: Final[bool] = capture + """True if this is a recording device instead of an output device.""" + self.format: Final[np.dtype[Any]] = _dtype_from_format(spec.format) + """The format used for audio samples with this device.""" + self.channels: Final[int] = int(spec.channels) + """The number of audio channels for this device.""" + self.silence: float = int(spec.silence) + """The value of silence, according to SDL.""" + self.buffer_samples: Final[int] = int(spec.samples) + """The size of the audio buffer in samples.""" + self.buffer_bytes: Final[int] = int(spec.size) + """The size of the audio buffer in bytes.""" self._handle: Optional[Any] = None self._callback: Callable[[AudioDevice, NDArray[Any]], None] = self.__default_callback @property def callback(self) -> Callable[[AudioDevice, NDArray[Any]], None]: + """If the device was opened with a callback enabled, then you may get or set the callback with this attribute.""" if self._handle is None: raise TypeError("This AudioDevice was opened without a callback.") return self._callback @@ -89,6 +106,7 @@ def callback(self, new_callback: Callable[[AudioDevice, NDArray[Any]], None]) -> @property def _sample_size(self) -> int: + """The size of a sample in bytes.""" return self.format.itemsize * self.channels @property @@ -119,9 +137,15 @@ def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) @property - def queued_audio_bytes(self) -> int: + def _queued_bytes(self) -> int: + """The current amount of bytes remaining in the audio queue.""" return int(lib.SDL_GetQueuedAudioSize(self.device_id)) + @property + def queued_samples(self) -> int: + """The current amount of samples remaining in the audio queue.""" + return self._queued_bytes // self._sample_size + def queue_audio(self, samples: ArrayLike) -> None: """Append audio samples to the audio data queue.""" assert not self.is_capture @@ -132,7 +156,7 @@ def queue_audio(self, samples: ArrayLike) -> None: def dequeue_audio(self) -> NDArray[Any]: """Return the audio buffer from a capture stream.""" assert self.is_capture - out_samples = self.queued_audio_bytes // self._sample_size + out_samples = self._queued_bytes // self._sample_size out = np.empty((out_samples, self.channels), self.format) buffer = ffi.from_buffer(out) bytes_returned = lib.SDL_DequeueAudio(self.device_id, buffer, len(buffer)) @@ -144,11 +168,11 @@ def __del__(self) -> None: self.close() def close(self) -> None: - """Close this audio device.""" - if not self.device_id: + """Close this audio device. Using this object after it has been closed is invalid.""" + if not hasattr(self, "device_id"): return lib.SDL_CloseAudioDevice(self.device_id) - self.device_id = 0 + del self.device_id @staticmethod def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: @@ -218,9 +242,11 @@ def __init__(self, device: AudioDevice): self.device = device def run(self) -> None: - buffer = np.full((self.device.samples, self.device.channels), self.device.silence, dtype=self.device.format) + buffer = np.full( + (self.device.buffer_samples, self.device.channels), self.device.silence, dtype=self.device.format + ) while True: - if self.device.queued_audio_bytes > 0: + if self.device._queued_bytes > 0: time.sleep(0.001) continue self.on_stream(buffer) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 34f5d4fc..7987081d 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -9,14 +9,14 @@ class Subsystem(enum.IntFlag): - TIMER = lib.SDL_INIT_TIMER or 0x00000001 - AUDIO = lib.SDL_INIT_AUDIO or 0x00000010 - VIDEO = lib.SDL_INIT_VIDEO or 0x00000020 - JOYSTICK = lib.SDL_INIT_JOYSTICK or 0x00000200 - HAPTIC = lib.SDL_INIT_HAPTIC or 0x00001000 - GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER or 0x00002000 - EVENTS = lib.SDL_INIT_EVENTS or 0x00004000 - SENSOR = getattr(lib, "SDL_INIT_SENSOR", None) or 0x00008000 # SDL >= 2.0.9 + TIMER = 0x00000001 + AUDIO = 0x00000010 + VIDEO = 0x00000020 + JOYSTICK = 0x00000200 + HAPTIC = 0x00001000 + GAMECONTROLLER = 0x00002000 + EVENTS = 0x00004000 + SENSOR = 0x00008000 EVERYTHING = lib.SDL_INIT_EVERYTHING or 0 @@ -49,11 +49,11 @@ def __exit__(self, *args: Any) -> None: class _PowerState(enum.IntEnum): - UNKNOWN = getattr(lib, "SDL_POWERSTATE_UNKNOWN", 0) - ON_BATTERY = getattr(lib, "SDL_POWERSTATE_ON_BATTERY", 0) - NO_BATTERY = getattr(lib, "SDL_POWERSTATE_NO_BATTERY", 0) - CHARGING = getattr(lib, "SDL_POWERSTATE_CHARGING", 0) - CHARGED = getattr(lib, "SDL_POWERSTATE_CHARGED", 0) + UNKNOWN = 0 + ON_BATTERY = enum.auto() + NO_BATTERY = enum.auto() + CHARGING = enum.auto() + CHARGED = enum.auto() def _get_power_info() -> Tuple[_PowerState, int, int]: From f78fe39181182cf60b325801f99151bcf056701e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Feb 2022 02:19:41 -0800 Subject: [PATCH 0665/1101] Add minimap to tcod samples. Update changelog. Add context atlas access. Refactor Texture attributes. --- .vscode/launch.json | 6 ++-- CHANGELOG.md | 10 +++++-- examples/samples_tcod.py | 64 ++++++++++++++++++++++++++++++---------- tcod/context.py | 12 ++++++++ tcod/render.py | 10 ++++++- tcod/sdl/render.py | 58 ++++++++++++++---------------------- tcod/tileset.py | 8 ++++- 7 files changed, 108 insertions(+), 60 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4bd84cbe..c9892886 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,8 +9,7 @@ "type": "python", "request": "launch", "program": "${file}", - "console": "integratedTerminal", - "preLaunchTask": "develop python-tcod", + "console": "internalConsole", }, { // Run the Python samples. @@ -20,8 +19,7 @@ "request": "launch", "program": "${workspaceFolder}/examples/samples_tcod.py", "cwd": "${workspaceFolder}/examples", - "console": "integratedTerminal", - "preLaunchTask": "develop python-tcod", + "console": "internalConsole", }, { "name": "Python: Run tests", diff --git a/CHANGELOG.md b/CHANGELOG.md index fef7ea70..595c8e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,19 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet. +- `tcod.sdl.mouse`, for SDL mouse and cursor handing. +- `Context.sdl_atlas`, which provides the relevant `SDLTilesetAtlas` when one is being used by the context. +- Several missing features were added to `tcod.sdl.render`. +- `Window.mouse_rect` added to SDL windows to set the mouse confinement area. ### Changed - `Texture.access` and `Texture.blend_mode` properties now return enum instances. - You can still set them with `int` but Mypy will complain. + You can still set `blend_mode` with `int` but Mypy will complain. ## [13.4.0] - 2022-02-04 ### Added -- Adds `sdl_window` and `sdl_renderer` to tcod contexts. +- Adds `sdl_window` and `sdl_renderer` properties to tcod contexts. - Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback. - Adds the `tcod.sdl.video` module to handle SDL windows. - Adds the `tcod.sdl.render` module to handle SDL renderers. diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 8eba7149..f92d0151 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -19,6 +19,7 @@ import numpy as np import tcod import tcod.render +import tcod.sdl.render from numpy.typing import NDArray if not sys.warnoptions: @@ -50,6 +51,8 @@ def get_data(path: str) -> str: # Mutable global names. context: tcod.context.Context tileset: tcod.tileset.Tileset +console_render: tcod.render.SDLConsoleRender # Optional SDL renderer. +sample_minimap: tcod.sdl.render.Texture # Optional minimap texture. root_console = tcod.Console(80, 50, order="F") sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F") cur_sample = 0 # Current selected sample. @@ -68,7 +71,7 @@ def on_draw(self) -> None: pass def ev_keydown(self, event: tcod.event.KeyDown) -> None: - global cur_sample, context + global cur_sample if event.sym == tcod.event.K_DOWN: cur_sample = (cur_sample + 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() @@ -91,8 +94,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: raise SystemExit() elif event.sym in RENDERER_KEYS: # Swap the active context for one with a different renderer. - context.close() - context = init_context(RENDERER_KEYS[event.sym]) + init_context(RENDERER_KEYS[event.sym]) def ev_quit(self, event: tcod.event.Quit) -> None: raise SystemExit() @@ -541,7 +543,7 @@ def __init__(self) -> None: self.player_y = 10 self.torch = False self.light_walls = True - self.algo_num = 0 + self.algo_num = tcod.FOV_SYMMETRIC_SHADOWCAST self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering. map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) @@ -582,7 +584,7 @@ def on_draw(self) -> None: self.draw_ui() sample_console.print(self.player_x, self.player_y, "@") # Draw windows. - sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = tcod.CHAR_DHLINE + sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = BLACK # Get a 2D boolean array of visible cells. @@ -1394,17 +1396,20 @@ def on_draw(self) -> None: ) -def init_context(renderer: int) -> tcod.context.Context: - """Return a new context with common parameters set. +def init_context(renderer: int) -> None: + """Setup or reset a global context with common parameters set. This function exists to more easily switch between renderers. """ + global context, console_render, sample_minimap + if "context" in globals(): + context.close() libtcod_version = "%i.%i.%i" % ( tcod.lib.TCOD_MAJOR_VERSION, tcod.lib.TCOD_MINOR_VERSION, tcod.lib.TCOD_PATCHLEVEL, ) - return tcod.context.new( + context = tcod.context.new( columns=root_console.width, rows=root_console.height, title=f"python-tcod samples" f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})", @@ -1412,16 +1417,27 @@ def init_context(renderer: int) -> tcod.context.Context: vsync=False, # VSync turned off since this is for benchmarking. tileset=tileset, ) + if context.sdl_renderer: # If this context supports SDL rendering. + # Start by setting the logical size so that window resizing doesn't break anything. + context.sdl_renderer.logical_size = ( + tileset.tile_width * root_console.width, + tileset.tile_height * root_console.height, + ) + assert context.sdl_atlas + # Generate the console renderer and minimap. + console_render = tcod.render.SDLConsoleRender(context.sdl_atlas) + sample_minimap = context.sdl_renderer.new_texture( + SAMPLE_SCREEN_WIDTH, + SAMPLE_SCREEN_HEIGHT, + format=tcod.lib.SDL_PIXELFORMAT_RGB24, + access=tcod.sdl.render.TextureAccess.STREAMING, # Updated every frame. + ) def main() -> None: global context, tileset tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) - context = init_context(tcod.RENDERER_SDL2) - sdl_renderer = context.sdl_renderer - assert sdl_renderer - atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset) - console_render = tcod.render.SDLConsoleRender(atlas) + init_context(tcod.RENDERER_SDL2) try: SAMPLES[cur_sample].on_enter() @@ -1434,9 +1450,25 @@ def main() -> None: SAMPLES[cur_sample].on_draw() sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() - # context.present(root_console) - sdl_renderer.copy(console_render.render(root_console)) - sdl_renderer.present() + if context.sdl_renderer: + # SDL renderer support, upload the sample console background to a minimap texture. + sample_minimap.update(sample_console.rgb.T["bg"]) + # Render the root_console normally, this is the drawing step of context.present without presenting. + context.sdl_renderer.copy(console_render.render(root_console)) + # Render the minimap to the screen. + context.sdl_renderer.copy( + sample_minimap, + dest=( + tileset.tile_width * 24, + tileset.tile_height * 36, + SAMPLE_SCREEN_WIDTH * 3, + SAMPLE_SCREEN_HEIGHT * 3, + ), + ) + context.sdl_renderer.present() + else: # No SDL renderer, just use plain context rendering. + context.present(root_console) + handle_time() handle_events() finally: diff --git a/tcod/context.py b/tcod/context.py index 14c81f6f..b26627e1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -58,6 +58,7 @@ import tcod import tcod.event +import tcod.render import tcod.sdl.render import tcod.sdl.video import tcod.tileset @@ -371,6 +372,17 @@ def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: p = lib.TCOD_context_get_sdl_renderer(self._context_p) return tcod.sdl.render.Renderer(p) if p else None + @property + def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]: + """Return a :any:`tcod.render.SDLTilesetAtlas` referencing libtcod's SDL texture atlas if it exists. + + .. versionadded:: unreleased + """ + if self._context_p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2): + return None + context_data = ffi.cast("struct TCOD_RendererSDL2*", self._context_p.contextdata_) + return tcod.render.SDLTilesetAtlas._from_ref(context_data.renderer, context_data.atlas) + def __reduce__(self) -> NoReturn: """Contexts can not be pickled, so this class will raise :class:`pickle.PicklingError`. diff --git a/tcod/render.py b/tcod/render.py index 9e2918da..79dc5060 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -29,7 +29,7 @@ from __future__ import annotations -from typing import Optional +from typing import Any, Optional import tcod.console import tcod.sdl.render @@ -46,6 +46,14 @@ def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Til self.tileset = tileset self.p = ffi.gc(_check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete) + @classmethod + def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas: + self = object.__new__(cls) + self._renderer = tcod.sdl.render.Renderer(renderer_p) + self.tileset = tcod.tileset.Tileset._from_ref(atlas_p.tileset) + self.p = atlas_p + return self + class SDLConsoleRender: """Holds an internal cache console and texture which are used to optimized console rendering.""" diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 98017772..15ccb931 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -9,6 +9,7 @@ import numpy as np from numpy.typing import NDArray +from typing_extensions import Final import tcod.sdl.video from tcod.loader import ffi, lib @@ -142,11 +143,27 @@ def compose_blend_mode( class Texture: - """SDL hardware textures.""" + """SDL hardware textures. + + Create a new texture using :any:`Renderer.new_texture` or :any:`Renderer.upload_texture`. + """ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: self.p = sdl_texture_p self._sdl_renderer_p = sdl_renderer_p # Keep alive. + query = self._query() + self.format: Final[int] = query[0] + """Texture format, read only.""" + self.access: Final[TextureAccess] = TextureAccess(query[1]) + """Texture access mode, read only. + + .. versionchanged:: unreleased + Attribute is now a :any:`TextureAccess` value. + """ + self.width: Final[int] = query[2] + """Texture pixel width, read only.""" + self.height: Final[int] = query[3] + """Texture pixel height, read only.""" def __eq__(self, other: Any) -> bool: return bool(self.p == getattr(other, "p", None)) @@ -156,7 +173,7 @@ def _query(self) -> Tuple[int, int, int, int]: format = ffi.new("uint32_t*") buffer = ffi.new("int[3]") lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) - return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2]) + return int(format[0]), int(buffer[0]), int(buffer[1]), int(buffer[2]) def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None: """Update the pixel data of this texture. @@ -165,42 +182,11 @@ def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] """ if rect is None: rect = (0, 0, self.width, self.height) - assert pixels.shape[:2] == rect[3], rect[2] - assert pixels[0].flags.c_contiguous + assert pixels.shape[:2] == (self.height, self.width) + if not pixels[0].flags.c_contiguous: + pixels = np.ascontiguousarray(pixels) _check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0])) - @property - def format(self) -> int: - """Texture format, read only.""" - buffer = ffi.new("uint32_t*") - lib.SDL_QueryTexture(self.p, buffer, ffi.NULL, ffi.NULL, ffi.NULL) - return int(buffer[0]) - - @property - def access(self) -> TextureAccess: - """Texture access mode, read only. - - .. versionchanged:: unreleased - Property now returns a TextureAccess instance. - """ - buffer = ffi.new("int*") - lib.SDL_QueryTexture(self.p, ffi.NULL, buffer, ffi.NULL, ffi.NULL) - return TextureAccess(buffer[0]) - - @property - def width(self) -> int: - """Texture pixel width, read only.""" - buffer = ffi.new("int*") - lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, buffer, ffi.NULL) - return int(buffer[0]) - - @property - def height(self) -> int: - """Texture pixel height, read only.""" - buffer = ffi.new("int*") - lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, ffi.NULL, buffer) - return int(buffer[0]) - @property def alpha_mod(self) -> int: """Texture alpha modulate value, can be set to 0 - 255.""" diff --git a/tcod/tileset.py b/tcod/tileset.py index acff8176..14767e5b 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -40,12 +40,18 @@ def __init__(self, tile_width: int, tile_height: int) -> None: @classmethod def _claim(cls, cdata: Any) -> Tileset: """Return a new Tileset that owns the provided TCOD_Tileset* object.""" - self: Tileset = object.__new__(cls) + self = object.__new__(cls) if cdata == ffi.NULL: raise RuntimeError("Tileset initialized with nullptr.") self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete) return self + @classmethod + def _from_ref(cls, tileset_p: Any) -> Tileset: + self = object.__new__(cls) + self._tileset_p = tileset_p + return self + @property def tile_width(self) -> int: """The width of the tile in pixels.""" From 1efd2631b806e7fc511ca9806a824d8d060c767b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Feb 2022 15:58:04 -0800 Subject: [PATCH 0666/1101] Prepare 13.5.0 release. --- CHANGELOG.md | 2 ++ tcod/context.py | 2 +- tcod/sdl/audio.py | 2 +- tcod/sdl/mouse.py | 2 +- tcod/sdl/render.py | 56 +++++++++++++++++++++++----------------------- tcod/sdl/video.py | 2 +- 6 files changed, 34 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 595c8e05..b05c8459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.5.0] - 2022-02-11 ### Added - `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet. - `tcod.sdl.mouse`, for SDL mouse and cursor handing. diff --git a/tcod/context.py b/tcod/context.py index b26627e1..8b324b83 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -376,7 +376,7 @@ def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]: """Return a :any:`tcod.render.SDLTilesetAtlas` referencing libtcod's SDL texture atlas if it exists. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ if self._context_p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2): return None diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 5edff741..cbc07b3d 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,6 +1,6 @@ """SDL2 audio playback and recording tools. -.. versionadded:: unreleased +.. versionadded:: 13.5 """ from __future__ import annotations diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index aa174ad9..13349ceb 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -1,6 +1,6 @@ """SDL mouse and cursor functions. -.. versionadded:: unreleased +.. versionadded:: 13.5 """ from __future__ import annotations diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 15ccb931..f44acb6a 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -45,7 +45,7 @@ class BlendFactor(enum.IntEnum): :any:`compose_blend_mode` https://wiki.libsdl.org/SDL_BlendFactor - .. versionadded:: unreleased + .. versionadded:: 13.5 """ ZERO = 0x1 @@ -77,7 +77,7 @@ class BlendOperation(enum.IntEnum): :any:`compose_blend_mode` https://wiki.libsdl.org/SDL_BlendOperation - .. versionadded:: unreleased + .. versionadded:: 13.5 """ ADD = 0x1 @@ -100,7 +100,7 @@ class BlendMode(enum.IntEnum): :any:`Renderer.draw_blend_mode` :any:`compose_blend_mode` - .. versionadded:: unreleased + .. versionadded:: 13.5 """ NONE = 0x00000000 @@ -128,7 +128,7 @@ def compose_blend_mode( .. seealso:: https://wiki.libsdl.org/SDL_ComposeCustomBlendMode - .. versionadded:: unreleased + .. versionadded:: 13.5 """ return BlendMode( lib.SDL_ComposeCustomBlendMode( @@ -157,7 +157,7 @@ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: self.access: Final[TextureAccess] = TextureAccess(query[1]) """Texture access mode, read only. - .. versionchanged:: unreleased + .. versionchanged:: 13.5 Attribute is now a :any:`TextureAccess` value. """ self.width: Final[int] = query[2] @@ -178,7 +178,7 @@ def _query(self) -> Tuple[int, int, int, int]: def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None: """Update the pixel data of this texture. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ if rect is None: rect = (0, 0, self.width, self.height) @@ -202,7 +202,7 @@ def alpha_mod(self, value: int) -> None: def blend_mode(self) -> BlendMode: """Texture blend mode, can be set. - .. versionchanged:: unreleased + .. versionchanged:: 13.5 Property now returns a BlendMode instance. """ out = ffi.new("SDL_BlendMode*") @@ -271,7 +271,7 @@ def copy( center: The (x, y) point where rotation is applied. If None then the center of `dest` is used. flip: Flips the `texture` when drawing it. - .. versionchanged:: unreleased + .. versionchanged:: 13.5 `source` and `dest` can now be float tuples. Added the `angle`, `center`, and `flip` parameters. """ @@ -349,7 +349,7 @@ def upload_texture( def draw_color(self) -> Tuple[int, int, int, int]: """Get or set the active RGBA draw color for this renderer. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ rgba = ffi.new("uint8_t[4]") _check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3)) @@ -363,7 +363,7 @@ def draw_color(self, rgba: Tuple[int, int, int, int]) -> None: def draw_blend_mode(self) -> BlendMode: """Get or set the active blend mode of this renderer. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ out = ffi.new("SDL_BlendMode*") _check(lib.SDL_GetRenderDrawBlendMode(self.p, out)) @@ -380,7 +380,7 @@ def output_size(self) -> Tuple[int, int]: .. seealso:: https://wiki.libsdl.org/SDL_GetRendererOutputSize - .. versionadded:: unreleased + .. versionadded:: 13.5 """ out = ffi.new("int[2]") _check(lib.SDL_GetRendererOutputSize(self.p, out, out + 1)) @@ -392,7 +392,7 @@ def clip_rect(self) -> Optional[Tuple[int, int, int, int]]: Set to None to disable clipping. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ if not lib.SDL_RenderIsClipEnabled(self.p): return None @@ -412,7 +412,7 @@ def integer_scaling(self) -> bool: .. seealso:: https://wiki.libsdl.org/SDL_RenderSetIntegerScale - .. versionadded:: unreleased + .. versionadded:: 13.5 """ return bool(lib.SDL_RenderGetIntegerScale(self.p)) @@ -429,7 +429,7 @@ def logical_size(self) -> Tuple[int, int]: .. seealso:: https://wiki.libsdl.org/SDL_RenderSetLogicalSize - .. versionadded:: unreleased + .. versionadded:: 13.5 """ out = ffi.new("int[2]") lib.SDL_RenderGetLogicalSize(self.p, out, out + 1) @@ -446,7 +446,7 @@ def scale(self) -> Tuple[float, float]: .. seealso:: https://wiki.libsdl.org/SDL_RenderSetScale - .. versionadded:: unreleased + .. versionadded:: 13.5 """ out = ffi.new("float[2]") lib.SDL_RenderGetScale(self.p, out, out + 1) @@ -463,7 +463,7 @@ def viewport(self) -> Optional[Tuple[int, int, int, int]]: .. seealso:: https://wiki.libsdl.org/SDL_RenderSetViewport - .. versionadded:: unreleased + .. versionadded:: 13.5 """ rect = ffi.new("SDL_Rect*") lib.SDL_RenderGetViewport(self.p, rect) @@ -477,7 +477,7 @@ def viewport(self, rect: Optional[Tuple[int, int, int, int]]) -> None: def set_vsync(self, enable: bool) -> None: """Enable or disable VSync for this renderer. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderSetVSync(self.p, enable)) @@ -489,7 +489,7 @@ def read_pixels( out: Optional[NDArray[Any]] = None, ) -> NDArray[Any]: """ - .. versionadded:: unreleased + .. versionadded:: 13.5 """ if format is None: format = lib.SDL_PIXELFORMAT_RGBA32 @@ -516,41 +516,41 @@ def read_pixels( def clear(self) -> None: """Clear the current render target with :any:`draw_color`. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderClear(self.p)) def fill_rect(self, rect: Tuple[float, float, float, float]) -> None: """Fill a rectangle with :any:`draw_color`. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderFillRectF(self.p, (rect,))) def draw_rect(self, rect: Tuple[float, float, float, float]) -> None: """Draw a rectangle outline. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawRectF(self.p, (rect,))) def draw_point(self, xy: Tuple[float, float]) -> None: """Draw a point. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawPointF(self.p, (xy,))) def draw_line(self, start: Tuple[float, float], end: Tuple[float, float]) -> None: """Draw a single line. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawLineF(self.p, *start, *end)) def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: """Fill multiple rectangles from an array. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ assert len(rects.shape) == 2 assert rects.shape[1] == 4 @@ -565,7 +565,7 @@ def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: """Draw multiple outlined rectangles from an array. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ assert len(rects.shape) == 2 assert rects.shape[1] == 4 @@ -580,7 +580,7 @@ def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None: """Draw an array of points. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ assert len(points.shape) == 2 assert points.shape[1] == 2 @@ -595,7 +595,7 @@ def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None: def draw_lines(self, points: NDArray[Union[np.intc, np.float32]]) -> None: """Draw a connected series of lines from an array. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ assert len(points.shape) == 2 assert points.shape[1] == 2 @@ -618,7 +618,7 @@ def geometry( ) -> None: """Render triangles from texture and vertex data. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ assert xy.dtype == np.float32 assert len(xy.shape) == 2 diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index bdcb6b6d..3061ab58 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -284,7 +284,7 @@ def mouse_rect(self) -> Optional[Tuple[int, int, int, int]]: Setting this will not automatically grab the cursor. - .. versionadded:: unreleased + .. versionadded:: 13.5 """ _version_at_least((2, 0, 18)) rect = lib.SDL_GetWindowMouseRect(self.p) From b1048ca5d31550632c5c7a86d82b36af74cfc076 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 14 Feb 2022 18:52:37 -0800 Subject: [PATCH 0667/1101] Add locks to sound mixer. Add volume and Pygame style loop parameters to sound play functions. --- tcod/sdl/audio.py | 96 +++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index cbc07b3d..6f067765 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -179,10 +179,27 @@ def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: stream[...] = device.silence +class _LoopSoundFunc: + def __init__(self, sound: NDArray[Any], loops: int, on_end: Optional[Callable[[Channel], None]]): + self.sound = sound + self.loops = loops + self.on_end = on_end + + def __call__(self, channel: Channel) -> None: + if not self.loops: + if self.on_end is not None: + self.on_end(channel) + return + channel.play(self.sound, volume=channel.volume, on_end=self) + if self.loops > 0: + self.loops -= 1 + + class Channel: mixer: Mixer def __init__(self) -> None: + self._lock = threading.RLock() self.volume: Union[float, Tuple[float, ...]] = 1.0 self.sound_queue: List[NDArray[Any]] = [] self.on_end_callback: Optional[Callable[[Channel], None]] = None @@ -195,10 +212,17 @@ def play( self, sound: ArrayLike, *, + volume: Union[float, Tuple[float, ...]] = 1.0, + loops: int = 0, on_end: Optional[Callable[[Channel], None]] = None, ) -> None: - self.sound_queue[:] = [self._verify_audio_sample(sound)] - self.on_end_callback = on_end + sound = self._verify_audio_sample(sound) + with self._lock: + self.volume = volume + self.sound_queue[:] = [sound] + self.on_end_callback = on_end + if loops: + self.on_end_callback = _LoopSoundFunc(sound, loops, on_end) def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]: """Verify an audio sample is valid and return it as a Numpy array.""" @@ -209,27 +233,29 @@ def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]: return array def _on_mix(self, stream: NDArray[Any]) -> None: - while self.sound_queue and stream.size: - buffer = self.sound_queue[0] - if buffer.shape[0] > stream.shape[0]: - # Mix part of the buffer into the stream. - stream[:] += buffer[: stream.shape[0]] * self.volume - self.sound_queue[0] = buffer[stream.shape[0] :] - break # Stream was filled. - # Remaining buffer fits the stream array. - stream[: buffer.shape[0]] += buffer * self.volume - stream = stream[buffer.shape[0] :] - self.sound_queue.pop(0) - if not self.sound_queue and self.on_end_callback is not None: - self.on_end_callback(self) + with self._lock: + while self.sound_queue and stream.size: + buffer = self.sound_queue[0] + if buffer.shape[0] > stream.shape[0]: + # Mix part of the buffer into the stream. + stream[:] += buffer[: stream.shape[0]] * self.volume + self.sound_queue[0] = buffer[stream.shape[0] :] + break # Stream was filled. + # Remaining buffer fits the stream array. + stream[: buffer.shape[0]] += buffer * self.volume + stream = stream[buffer.shape[0] :] + self.sound_queue.pop(0) + if not self.sound_queue and self.on_end_callback is not None: + self.on_end_callback(self) def fadeout(self, time: float) -> None: assert time >= 0 - time_samples = round(time * self.mixer.device.frequency) + 1 - buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32) - self._on_mix(buffer) - buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:] - self.sound_queue[:] = [buffer] + with self._lock: + time_samples = round(time * self.mixer.device.frequency) + 1 + buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32) + self._on_mix(buffer) + buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:] + self.sound_queue[:] = [buffer] def stop(self) -> None: self.fadeout(0.0005) @@ -240,6 +266,7 @@ def __init__(self, device: AudioDevice): assert device.format == np.float32 super().__init__(daemon=True) self.device = device + self._lock = threading.RLock() def run(self) -> None: buffer = np.full( @@ -263,32 +290,37 @@ def __init__(self, device: AudioDevice): self.channels: Dict[Hashable, Channel] = {} def get_channel(self, key: Hashable) -> Channel: - if key not in self.channels: - self.channels[key] = Channel() - self.channels[key].mixer = self - return self.channels[key] + with self._lock: + if key not in self.channels: + self.channels[key] = Channel() + self.channels[key].mixer = self + return self.channels[key] def get_free_channel(self) -> Channel: - i = 0 - while True: - if not self.get_channel(i).busy: - return self.channels[i] - i += 1 + with self._lock: + i = 0 + while True: + if not self.get_channel(i).busy: + return self.channels[i] + i += 1 def play( self, sound: ArrayLike, *, + volume: Union[float, Tuple[float, ...]] = 1.0, + loops: int = 0, on_end: Optional[Callable[[Channel], None]] = None, ) -> Channel: channel = self.get_free_channel() - channel.play(sound, on_end=on_end) + channel.play(sound, volume=volume, loops=loops, on_end=on_end) return channel def on_stream(self, stream: NDArray[Any]) -> None: super().on_stream(stream) - for channel in list(self.channels.values()): - channel._on_mix(stream) + with self._lock: + for channel in list(self.channels.values()): + channel._on_mix(stream) class _AudioCallbackUserdata: From 3b16ca81248abe1d1e44126f48c3451f2c324834 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 14 Feb 2022 19:22:50 -0800 Subject: [PATCH 0668/1101] Increase lower bound for setuptools. Add wheel version to requires. --- pyproject.toml | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1fddcde9..02f90c15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ - "setuptools>=57.0.0", - "wheel", + "setuptools>=60.9.0", + "wheel>=0.37.1", "cffi>=1.15", "pycparser>=2.14", "pcpp==1.30", diff --git a/requirements.txt b/requirements.txt index b5a68d21..a6a74931 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ cffi>=1.15 numpy>=1.20.3 pycparser>=2.14 -setuptools>=36.0.1 +setuptools>=60.9.0 types-setuptools types-tabulate typing_extensions From 9ccf7777f01530cb7cbd5d12f703ce884fbbbda4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 14 Feb 2022 20:31:05 -0800 Subject: [PATCH 0669/1101] Pin setuptools==60.8.2. The latest version breaks in PyPy for an unknown reason. --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 02f90c15..a861e465 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "setuptools>=60.9.0", + "setuptools==60.8.2", "wheel>=0.37.1", "cffi>=1.15", "pycparser>=2.14", diff --git a/requirements.txt b/requirements.txt index a6a74931..5b572578 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ cffi>=1.15 numpy>=1.20.3 pycparser>=2.14 -setuptools>=60.9.0 +setuptools==60.8.2 types-setuptools types-tabulate typing_extensions From 463b5b0ccc8be02ea039dff063a8358436f635d9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 15 Feb 2022 01:58:37 -0800 Subject: [PATCH 0670/1101] Automatically run the mixer thread. Making this not automatic caused usability issues. I'm defaulting it to run again. --- tcod/sdl/audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 6f067765..3779c2bb 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -267,6 +267,7 @@ def __init__(self, device: AudioDevice): super().__init__(daemon=True) self.device = device self._lock = threading.RLock() + self.start() def run(self) -> None: buffer = np.full( @@ -286,8 +287,8 @@ def on_stream(self, stream: NDArray[Any]) -> None: class BasicMixer(Mixer): def __init__(self, device: AudioDevice): - super().__init__(device) self.channels: Dict[Hashable, Channel] = {} + super().__init__(device) def get_channel(self, key: Hashable) -> Channel: with self._lock: From b69720adca24ea3ba3d745f7fa0de81178b23f4c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 17 Feb 2022 13:34:10 -0800 Subject: [PATCH 0671/1101] Add closing of Mixer instances. --- tcod/sdl/audio.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 3779c2bb..fd6c0edb 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -267,13 +267,14 @@ def __init__(self, device: AudioDevice): super().__init__(daemon=True) self.device = device self._lock = threading.RLock() + self._running = True self.start() def run(self) -> None: buffer = np.full( (self.device.buffer_samples, self.device.channels), self.device.silence, dtype=self.device.format ) - while True: + while self._running: if self.device._queued_bytes > 0: time.sleep(0.001) continue @@ -281,6 +282,9 @@ def run(self) -> None: self.device.queue_audio(buffer) buffer[:] = self.device.silence + def close(self) -> None: + self._running = False + def on_stream(self, stream: NDArray[Any]) -> None: pass From efa027bcbccfeffdcc713b24b9421c07a7526f25 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 18 Feb 2022 22:51:49 -0800 Subject: [PATCH 0672/1101] Add audio conversion, make mixer features public. Add partial audio tests. --- .vscode/settings.json | 1 + CHANGELOG.md | 4 + tcod/sdl/audio.py | 167 +++++++++++++++++++++++++++++++++++++----- tests/test_sdl.py | 14 ++++ 4 files changed, 169 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e02d3ea2..1df05195 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -192,6 +192,7 @@ "msilib", "MSVC", "msvcr", + "mult", "mulx", "muly", "mypy", diff --git a/CHANGELOG.md b/CHANGELOG.md index b05c8459..65976d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- `BasicMixer` and `Channel` classes added to `tcod.sdl.audio`. These handle simple audio mixing. +- `AudioDevice.convert` added to handle simple conversions to the active devices format. +- `tcod.sdl.audio.convert_audio` added to handle any other conversions needed. ## [13.5.0] - 2022-02-11 ### Added diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index fd6c0edb..13f10a5c 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -16,7 +16,7 @@ import tcod.sdl.sys from tcod.loader import ffi, lib -from tcod.sdl import _get_error +from tcod.sdl import _check, _get_error def _get_format(format: DTypeLike) -> int: @@ -55,10 +55,65 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: return np.dtype(f"{byteorder}{kind}{bytesize}") +def convert_audio( + in_sound: ArrayLike, in_rate: int, *, out_rate: int, out_format: DTypeLike, out_channels: int +) -> NDArray[Any]: + """Convert an audio sample into a format supported by this device. + + Returns the converted array. This might be a reference to the input array if no conversion was needed. + + Args: + in_sound: The input ArrayLike sound sample. Input format and channels are derived from the array. + in_rate: The samplerate of the input array. + out_rate: The samplerate of the output array. + out_format: The output format of the converted array. + out_channels: The number of audio channels of the output array. + + .. versionadded:: unreleased + + .. seealso:: + :any:`AudioDevice.convert` + """ + in_array: NDArray[Any] = np.asarray(in_sound) + if len(in_array.shape) == 1: + in_array = in_array[:, np.newaxis] + if not len(in_array.shape) == 2: + raise TypeError(f"Expected a 1 or 2 ndim input, got {in_array.shape} instead.") + cvt = ffi.new("SDL_AudioCVT*") + in_channels = in_array.shape[1] + in_format = _get_format(in_array.dtype) + out_sdl_format = _get_format(out_format) + if _check(lib.SDL_BuildAudioCVT(cvt, in_format, in_channels, in_rate, out_sdl_format, out_channels, out_rate)) == 0: + return in_array # No conversion needed. + # Upload to the SDL_AudioCVT buffer. + cvt.len = in_array.itemsize * in_array.size + out_buffer = cvt.buf = ffi.new("uint8_t[]", cvt.len * cvt.len_mult) + np.frombuffer(ffi.buffer(out_buffer[0 : cvt.len]), dtype=in_array.dtype).reshape(in_array.shape)[:] = in_array + + _check(lib.SDL_ConvertAudio(cvt)) + out_array: NDArray[Any] = ( + np.frombuffer(ffi.buffer(out_buffer[0 : cvt.len_cvt]), dtype=out_format).reshape(-1, out_channels).copy() + ) + return out_array + + class AudioDevice: """An SDL audio device. Open new audio devices using :any:`tcod.sdl.audio.open`. + + Example:: + + import soundfile # pip install soundfile + import tcod.sdl.audio + + device = tcod.sdl.audio.open() + sound, samplerate = soundfile.read("example_sound.wav") + converted = device.convert(sound, samplerate) + device.queue_audio(converted) # Play the audio syncroniously. + + When you use this object directly the audio passed to :any:`queue_audio` is always played syncroniously. + For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. """ def __init__( @@ -136,6 +191,32 @@ def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: samples = samples[:, np.newaxis] return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) + def convert(self, sound: ArrayLike, rate: Optional[int] = None) -> NDArray[Any]: + """Convert an audio sample into a format supported by this device. + + Returns the converted array. This might be a reference to the input array if no conversion was needed. + + Args: + sound: An ArrayLike sound sample. + rate: The samplerate of the input array. + If None is given then it's assumed to be the same as the device. + + .. versionadded:: unreleased + + .. seealso:: + :any:`convert_audio` + """ + in_array: NDArray[Any] = np.asarray(sound) + if len(in_array.shape) == 1: + in_array = in_array[:, np.newaxis] + return convert_audio( + in_sound=sound, + in_rate=rate if rate is not None else self.frequency, + out_channels=self.channels if in_array.shape[1] > 1 else 1, + out_format=self.format, + out_rate=self.frequency, + ) + @property def _queued_bytes(self) -> int: """The current amount of bytes remaining in the audio queue.""" @@ -196,7 +277,13 @@ def __call__(self, channel: Channel) -> None: class Channel: - mixer: Mixer + """An audio channel for :any:`BasicMixer`. Use :any:`BasicMixer.get_channel` to initialize this object. + + .. versionadded:: unreleased + """ + + mixer: BasicMixer + """The :any:`BasicMixer` is channel belongs to.""" def __init__(self) -> None: self._lock = threading.RLock() @@ -206,6 +293,7 @@ def __init__(self) -> None: @property def busy(self) -> bool: + """Is True when this channel is playing audio.""" return bool(self.sound_queue) def play( @@ -216,6 +304,10 @@ def play( loops: int = 0, on_end: Optional[Callable[[Channel], None]] = None, ) -> None: + """Play an audio sample, stopping any audio currently playing on this channel. + + Parameters are the same as :any:`BasicMixer.play`. + """ sound = self._verify_audio_sample(sound) with self._lock: self.volume = volume @@ -233,6 +325,7 @@ def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]: return array def _on_mix(self, stream: NDArray[Any]) -> None: + """Mix the next part of this channels audio into an active audio stream.""" with self._lock: while self.sound_queue and stream.size: buffer = self.sound_queue[0] @@ -249,8 +342,10 @@ def _on_mix(self, stream: NDArray[Any]) -> None: self.on_end_callback(self) def fadeout(self, time: float) -> None: - assert time >= 0 + """Fadeout this channel then stop playing.""" with self._lock: + if not self.sound_queue: + return time_samples = round(time * self.mixer.device.frequency) + 1 buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32) self._on_mix(buffer) @@ -258,14 +353,36 @@ def fadeout(self, time: float) -> None: self.sound_queue[:] = [buffer] def stop(self) -> None: + """Stop audio on this channel.""" self.fadeout(0.0005) -class Mixer(threading.Thread): +class BasicMixer(threading.Thread): + """An SDL sound mixer implemented in Python and Numpy. + + Example:: + + import time + + import soundfile # pip install soundfile + import tcod.sdl.audio + + mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) + sound, samplerate = soundfile.read("example_sound.wav") + sound = mixer.device.convert(sound, samplerate) # Needed if dtype or samplerate differs. + channel = mixer.play(sound) + while channel.busy: + time.sleep(0.001) + + .. versionadded:: unreleased + """ + def __init__(self, device: AudioDevice): + self.channels: Dict[Hashable, Channel] = {} assert device.format == np.float32 super().__init__(daemon=True) self.device = device + """The :any:`AudioDevice`""" self._lock = threading.RLock() self._running = True self.start() @@ -278,30 +395,30 @@ def run(self) -> None: if self.device._queued_bytes > 0: time.sleep(0.001) continue - self.on_stream(buffer) + self._on_stream(buffer) self.device.queue_audio(buffer) buffer[:] = self.device.silence def close(self) -> None: + """Shutdown this mixer, all playing audio will be abruptly stopped.""" self._running = False - def on_stream(self, stream: NDArray[Any]) -> None: - pass - + def get_channel(self, key: Hashable) -> Channel: + """Return a channel tied to with the given key. -class BasicMixer(Mixer): - def __init__(self, device: AudioDevice): - self.channels: Dict[Hashable, Channel] = {} - super().__init__(device) + Channels are initialized as you access them with this function. + :any:`int` channels starting from zero are used internally. - def get_channel(self, key: Hashable) -> Channel: + This can be used to generate a ``"music"`` channel for example. + """ with self._lock: if key not in self.channels: self.channels[key] = Channel() self.channels[key].mixer = self return self.channels[key] - def get_free_channel(self) -> Channel: + def _get_next_channel(self) -> Channel: + """Return the next available channel for the play method.""" with self._lock: i = 0 while True: @@ -317,12 +434,28 @@ def play( loops: int = 0, on_end: Optional[Callable[[Channel], None]] = None, ) -> Channel: - channel = self.get_free_channel() + """Play a sound, return the channel the sound is playing on. + + Args: + sound: The sound to play. This a Numpy array matching the format of the loaded audio device. + volume: The volume to play the sound at. + You can also pass a tuple of floats to set the volume for each channel/speaker. + loops: How many times to play the sound, `-1` can be used to loop the sound forever. + on_end: A function to call when this sound has ended. + This is called with the :any:`Channel` which was playing the sound. + """ + channel = self._get_next_channel() channel.play(sound, volume=volume, loops=loops, on_end=on_end) return channel - def on_stream(self, stream: NDArray[Any]) -> None: - super().on_stream(stream) + def stop(self) -> None: + """Stop playback on all channels from this mixer.""" + with self._lock: + for channel in self.channels.values(): + channel.stop() + + def _on_stream(self, stream: NDArray[Any]) -> None: + """Called to fill the audio buffer.""" with self._lock: for channel in list(self.channels.values()): channel._on_mix(stream) diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 8dea7f33..3deec406 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -1,8 +1,10 @@ +import contextlib import sys import numpy as np import pytest +import tcod.sdl.audio import tcod.sdl.render import tcod.sdl.sys import tcod.sdl.video @@ -74,3 +76,15 @@ def test_sdl_render_bad_types() -> None: tcod.sdl.render.Renderer(tcod.ffi.cast("SDL_Renderer*", tcod.ffi.NULL)) with pytest.raises(TypeError): tcod.sdl.render.Renderer(tcod.ffi.new("SDL_Rect*")) + + +def test_sdl_audio_device() -> None: + with contextlib.closing(tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True)) as device: + assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 + assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape == (4, 2) + assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 + device.paused = False + device.paused = True + assert device.queued_samples == 0 + with contextlib.closing(tcod.sdl.audio.BasicMixer(device)) as mixer: + assert mixer From 934d2dd0ba5bc4d2dd1e4435c826a6bf512c7a70 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 19 Feb 2022 00:42:42 -0800 Subject: [PATCH 0673/1101] Prepare 13.6.0 release. --- CHANGELOG.md | 2 ++ tcod/sdl/audio.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65976d2e..e6cf855e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.6.0] - 2022-02-19 ### Added - `BasicMixer` and `Channel` classes added to `tcod.sdl.audio`. These handle simple audio mixing. - `AudioDevice.convert` added to handle simple conversions to the active devices format. diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 13f10a5c..65c66aef 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -69,7 +69,7 @@ def convert_audio( out_format: The output format of the converted array. out_channels: The number of audio channels of the output array. - .. versionadded:: unreleased + .. versionadded:: 13.6 .. seealso:: :any:`AudioDevice.convert` @@ -201,7 +201,7 @@ def convert(self, sound: ArrayLike, rate: Optional[int] = None) -> NDArray[Any]: rate: The samplerate of the input array. If None is given then it's assumed to be the same as the device. - .. versionadded:: unreleased + .. versionadded:: 13.6 .. seealso:: :any:`convert_audio` @@ -279,7 +279,7 @@ def __call__(self, channel: Channel) -> None: class Channel: """An audio channel for :any:`BasicMixer`. Use :any:`BasicMixer.get_channel` to initialize this object. - .. versionadded:: unreleased + .. versionadded:: 13.6 """ mixer: BasicMixer @@ -374,7 +374,7 @@ class BasicMixer(threading.Thread): while channel.busy: time.sleep(0.001) - .. versionadded:: unreleased + .. versionadded:: 13.6 """ def __init__(self, device: AudioDevice): From 807fdca464389e3febd8c86991bd4564d091f48d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 8 Mar 2022 06:19:05 -0800 Subject: [PATCH 0674/1101] Explain non-blocking event loops in the getting started example. --- docs/tcod/getting-started.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index ddca121c..4954fe6a 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -45,6 +45,8 @@ Example:: console.print(x=0, y=0, string="Hello World!") context.present(console) # Show the console. + # This event loop will wait until at least one event is processed before exiting. + # For a non-blocking event loop replace `tcod.event.wait` with `tcod.event.get`. for event in tcod.event.wait(): context.convert_event(event) # Sets tile coordinates for mouse events. print(event) # Print event names and attributes. From 1a142ff09afab67fcd498e96193d80f2d899d943 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Mar 2022 12:05:38 -0700 Subject: [PATCH 0675/1101] Fetch updates from libtcod and change the default renderer to SDL2. --- CHANGELOG.md | 3 +++ build_libtcod.py | 6 ------ libtcod | 2 +- tcod/context.py | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6cf855e..009a9750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- The SDL2 renderer has had a major performance update when compiled with SDL 2.0.18. +- SDL2 is now the default renderer to avoid rare issues with the OpenGL 2 renderer. ## [13.6.0] - 2022-02-19 ### Added diff --git a/build_libtcod.py b/build_libtcod.py index 93065ae3..f84d7736 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -15,12 +15,6 @@ import build_sdl # noqa: E402 -# The SDL2 version to parse and export symbols from. -SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.5") - -# The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.14") - Py_LIMITED_API = 0x03060000 HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") diff --git a/libtcod b/libtcod index d54f68bf..577f83b3 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit d54f68bf10ee862f47d3668348279a13f489d028 +Subproject commit 577f83b39ca25dcc50051751182ba1defe9b62c4 diff --git a/tcod/context.py b/tcod/context.py index 8b324b83..1c15382a 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -464,7 +464,7 @@ def new( Added the `console` parameter. """ if renderer is None: - renderer = RENDERER_OPENGL2 + renderer = RENDERER_SDL2 if sdl_window_flags is None: sdl_window_flags = SDL_WINDOW_RESIZABLE if argv is None: From d385df427d23510798d9ddb79219c05fd5b23597 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 29 Mar 2022 12:32:08 -0700 Subject: [PATCH 0676/1101] Prepare 13.6.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009a9750..0874c558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.6.1] - 2022-03-29 ### Changed - The SDL2 renderer has had a major performance update when compiled with SDL 2.0.18. - SDL2 is now the default renderer to avoid rare issues with the OpenGL 2 renderer. From 8f4d00c8338abc55c32fefbaacfbb3c2e6cbdb33 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 30 Mar 2022 13:23:48 -0700 Subject: [PATCH 0677/1101] Raise min version of NumPy dependency. Tcod always uses the type hinting features of the latest NumPy. This is the final version of NumPy with Python 3.7 support. Fixes #115 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5b572578..a7275c20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ cffi>=1.15 -numpy>=1.20.3 +numpy>=1.21.4 pycparser>=2.14 setuptools==60.8.2 types-setuptools diff --git a/setup.py b/setup.py index 411653fd..a364e516 100755 --- a/setup.py +++ b/setup.py @@ -120,7 +120,7 @@ def check_sdl_version() -> None: ], install_requires=[ "cffi>=1.15", # Also required by pyproject.toml. - "numpy>=1.20.3" if not is_pypy else "", + "numpy>=1.21.4" if not is_pypy else "", "typing_extensions", ], cffi_modules=["build_libtcod.py:ffi"], From c2fb67ec1d275f6d105c9c6cdd705b100ad23660 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 22 Apr 2022 17:44:00 -0700 Subject: [PATCH 0678/1101] Improve docs. Focus more on showing examples in the page headers. --- tcod/context.py | 72 ++++++++++++++++++++++++--- tcod/event.py | 122 +++++++++++++++++++++++++++------------------- tcod/image.py | 13 +++-- tcod/sdl/audio.py | 58 +++++++++++++--------- tcod/sdl/mouse.py | 18 +++++++ tcod/sdl/video.py | 22 ++++++++- 6 files changed, 219 insertions(+), 86 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 1c15382a..a112d0d5 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -179,9 +179,10 @@ def __enter__(self) -> Context: return self def close(self) -> None: - """Delete the context, closing any windows opened by this context. + """Close this context, closing any windows opened by this context. - This instance is invalid after this call.""" + Afterwards doing anything with this instance other than closing it again is invalid. + """ if hasattr(self, "_context_p"): ffi.release(self._context_p) del self._context_p @@ -246,7 +247,21 @@ def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: return xy[0], xy[1] def convert_event(self, event: tcod.event.Event) -> None: - """Fill in the tile coordinates of a mouse event using this context.""" + """Fill in the tile coordinates of a mouse event using this context. + + Example:: + + context: tcod.context.Context + for event in tcod.event.get(): + if isinstance(event, tcod.event.MouseMotion): + # Pixel coordinates are always accessible. + print(f"{event.pixel=}, {event.pixel_motion=}") + context.convert_event(event) + if isinstance(event, tcod.event.MouseMotion): + # Now tile coordinate attributes can be accessed. + print(f"{event.tile=}, {event.tile_motion=}") + # A warning will be raised if you try to access these without convert_event. + """ if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) if isinstance(event, tcod.event.MouseMotion): @@ -262,7 +277,17 @@ def save_screenshot(self, path: Optional[str] = None) -> None: _check(lib.TCOD_context_save_screenshot(self._context_p, c_path)) def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None: - """Change the active tileset used by this context.""" + """Change the active tileset used by this context. + + The new tileset will take effect on the next call to :any:`present`. + Contexts not using a renderer with an emulated terminal will be unaffected by this method. + + This does not do anything to resize the window, keep this in mind if the tileset as a differing tile size. + Access the window with :any:`sdl_window` to resize it manually, if needed. + + Using this method only one tileset is active per-frame. + See :any:`tcod.render` if you want to renderer with multiple tilesets in a single frame. + """ _check(lib.TCOD_context_change_tileset(self._context_p, _handle_tileset(tileset))) def new_console( @@ -299,6 +324,25 @@ def new_console( .. seealso:: :any:`tcod.console.Console` + + Example:: + + scale = 1 # Tile size scale. This example uses integers but floating point numbers are also valid. + context = tcod.context.new() + while True: + # Create a cleared, dynamically-sized console for each frame. + console = context.new_console(magnification=scale) + # This printed output will wrap if the window is shrunk. + console.print_box(0, 0, console.width, console.height, "Hello world") + # Use integer_scaling to prevent subpixel distorsion. + # This may add padding around the rendered console. + context.present(console, integer_scaling=True) + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + elif isinstance(event, tcod.event.MouseWheel): + # Use the mouse wheel to change the rendered tile size. + scale = max(1, scale + event.y) """ if magnification < 0: raise ValueError("Magnification must be greater than zero. (Got %f)" % magnification) @@ -351,15 +395,31 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: context.sdl_window_p, 0 if fullscreen else tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP, ) + ''' # noqa: E501 return lib.TCOD_context_get_sdl_window(self._context_p) @property def sdl_window(self) -> Optional[tcod.sdl.video.Window]: - """Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists. + '''Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists. + + Example:: + + import tcod + improt tcod.sdl.video + + def toggle_fullscreen(context: tcod.context.Context) -> None: + """Toggle a context window between fullscreen and windowed modes.""" + window = context.sdl_window + if not window: + return + if window.fullscreen: + window.fullscreen = False + else: + window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP .. versionadded:: 13.4 - """ + ''' p = self.sdl_window_p return tcod.sdl.video.Window(p) if p else None diff --git a/tcod/event.py b/tcod/event.py index b333b1fc..102ade1d 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1,5 +1,4 @@ -""" -A light-weight implementation of event handling built on calls to SDL. +"""A light-weight implementation of event handling built on calls to SDL. Many event constants are derived directly from SDL. For example: ``tcod.event.K_UP`` and ``tcod.event.SCANCODE_A`` refer to @@ -8,14 +7,75 @@ `_ Printing any event will tell you its attributes in a human readable format. -An events type attribute if omitted is just the classes name with all letters -upper-case. +An events type attribute if omitted is just the classes name with all letters upper-case. + +As a general guideline, you should use :any:`KeyboardEvent.sym` for command inputs, +and :any:`TextInput.text` for name entry fields. + +Example:: + + import tcod + + KEY_COMMANDS = { + tcod.event.KeySym.UP: "move N", + tcod.event.KeySym.DOWN: "move S", + tcod.event.KeySym.LEFT: "move W", + tcod.event.KeySym.RIGHT: "move E", + } + + context = tcod.context.new() + while True: + console = context.new_console() + context.present(console, integer_scaling=True) + for event in tcod.event.wait(): + context.convert_event(event) # Adds tile coordinates to mouse events. + if isinstance(event, tcod.event.Quit): + print(event) + raise SystemExit() + elif isinstance(event, tcod.event.KeyDown): + print(event) # Prints the Scancode and KeySym enums for this event. + if event.sym in KEY_COMMANDS: + print(f"Command: {KEY_COMMANDS[event.sym]}") + elif isinstance(event, tcod.event.MouseButtonDown): + print(event) # Prints the mouse button constant names for this event. + elif isinstance(event, tcod.event.MouseMotion): + print(event) # Prints the mouse button mask bits in a readable format. + else: + print(event) # Print any unhandled events. + +Python 3.10 introduced `match statements `_ +which can be used to dispatch events more gracefully: + +Example:: -As a general guideline, you should use :any:`KeyboardEvent.sym` for command -inputs, and :any:`TextInput.text` for name entry fields. + import tcod -Remember to add the line ``import tcod.event``, as importing this module is not -implied by ``import tcod``. + KEY_COMMANDS = { + tcod.event.KeySym.UP: "move N", + tcod.event.KeySym.DOWN: "move S", + tcod.event.KeySym.LEFT: "move W", + tcod.event.KeySym.RIGHT: "move E", + } + + context = tcod.context.new() + while True: + console = context.new_console() + context.present(console, integer_scaling=True) + for event in tcod.event.wait(): + context.convert_event(event) # Adds tile coordinates to mouse events. + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym) if sym in KEY_COMMANDS: + print(f"Command: {KEY_COMMANDS[sym]}") + case tcod.event.KeyDown(sym, scancode, mod, repeat): + print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") + case tcod.event.MouseButtonDown(button, pixel, tile): + print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}") + case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion): + print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}") + case tcod.event.Event() as event: + print(event) # Show any unhandled events. .. versionadded:: 8.4 """ @@ -762,48 +822,10 @@ def _parse_event(sdl_event: Any) -> Event: def get() -> Iterator[Any]: """Return an iterator for all pending events. - Events are processed as the iterator is consumed. Breaking out of, or - discarding the iterator will leave the remaining events on the event queue. - It is also safe to call this function inside of a loop that is already - handling events (the event iterator is reentrant.) - - Example:: - - context: tcod.context.Context # Context object initialized earlier. - for event in tcod.event.get(): - context.convert_event(event) # Add tile coordinates to mouse events. - if isinstance(event, tcod.event.Quit): - print(event) - raise SystemExit() - elif isinstance(event, tcod.event.KeyDown): - print(event) # Prints the Scancode and KeySym enums for this event. - elif isinstance(event, tcod.event.MouseButtonDown): - print(event) # Prints the mouse button constant names for this event. - elif isinstance(event, tcod.event.MouseMotion): - print(event) # Prints the mouse button mask bits in a readable format. - else: - print(event) # Print any unhandled events. - # For loop exits after all current events are processed. - - Python 3.10 introduced `match statements `_ - which can be used to dispatch events more gracefully: - - Example:: - - context: tcod.context.Context # Context object initialized earlier. - for event in tcod.event.get(): - context.convert_event(event) # Add tile coordinates to mouse events. - match event: - case tcod.event.Quit(): - raise SystemExit() - case tcod.event.KeyDown(sym, scancode, mod, repeat): - print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") - case tcod.event.MouseButtonDown(button, pixel, tile): - print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}") - case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion): - print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}") - case tcod.event.Event() as event: - print(event) # Show any unhandled events. + Events are processed as the iterator is consumed. + Breaking out of, or discarding the iterator will leave the remaining events on the event queue. + It is also safe to call this function inside of a loop that is already handling events + (the event iterator is reentrant.) """ sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): diff --git a/tcod/image.py b/tcod/image.py index d94441cb..0f5760e9 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -1,9 +1,12 @@ -"""Functionality for handling images. +"""Libtcod functionality for handling images. -**Python-tcod is unable to render pixels to the screen directly.** -If your image can't be represented as tiles then you'll need to use -`an alternative library for graphics rendering -`_. +This module is generally seen as outdated. +To load images you should typically use `Pillow `_ or +`imageio `_ unless you need to use a feature exclusive to libtcod. + +**Python-tcod is unable to render pixels to consoles.** +The best it can do with consoles is convert an image into semigraphics which can be shown on non-emulated terminals. +For true pixel-based rendering you'll want to access the SDL rendering port at :any:`tcod.sdl.render`. """ from __future__ import annotations diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 65c66aef..6bee2587 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,5 +1,39 @@ """SDL2 audio playback and recording tools. +This module includes SDL's low-level audio API and a naive implentation of an SDL mixer. +If you have experience with audio mixing then you might be better off writing your own mixer or +modifying the existing one which was written using Python/Numpy. + +This module is designed to integrate with the wider Python ecosystem. +It leaves the loading to sound samples to other libaries like +`SoundFile `_. + +Example:: + + # Synchronous audio example using SDL's low-level API. + import soundfile # pip install soundfile + import tcod.sdl.audio + + device = tcod.sdl.audio.open() # Open the default output device. + sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. + converted = device.convert(sound, samplerate) # Convert this sample to the format expected by the device. + device.queue_audio(converted) # Play audio syncroniously by appending it to the device buffer. + +Example:: + + # Asynchronous audio example using BasicMixer. + import time + + import soundfile # pip install soundfile + import tcod.sdl.audio + + mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) # Setup BasicMixer with the default audio output. + sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. + sound = mixer.device.convert(sound, samplerate) # Convert this sample to the format expected by the device. + channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread. + while channel.busy: # Wait until the sample is done playing. + time.sleep(0.001) + .. versionadded:: 13.5 """ from __future__ import annotations @@ -102,16 +136,6 @@ class AudioDevice: Open new audio devices using :any:`tcod.sdl.audio.open`. - Example:: - - import soundfile # pip install soundfile - import tcod.sdl.audio - - device = tcod.sdl.audio.open() - sound, samplerate = soundfile.read("example_sound.wav") - converted = device.convert(sound, samplerate) - device.queue_audio(converted) # Play the audio syncroniously. - When you use this object directly the audio passed to :any:`queue_audio` is always played syncroniously. For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. """ @@ -360,20 +384,6 @@ def stop(self) -> None: class BasicMixer(threading.Thread): """An SDL sound mixer implemented in Python and Numpy. - Example:: - - import time - - import soundfile # pip install soundfile - import tcod.sdl.audio - - mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) - sound, samplerate = soundfile.read("example_sound.wav") - sound = mixer.device.convert(sound, samplerate) # Needed if dtype or samplerate differs. - channel = mixer.play(sound) - while channel.busy: - time.sleep(0.001) - .. versionadded:: 13.6 """ diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 13349ceb..a12f2bca 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -1,5 +1,9 @@ """SDL mouse and cursor functions. +You can use this module to move or capture the cursor. + +You can also set the cursor icon to an OS-defined or custom icon. + .. versionadded:: 13.5 """ from __future__ import annotations @@ -141,6 +145,20 @@ def capture(enable: bool) -> None: It is highly reccomended to read the related remarks section in the SDL docs before using this. + Example:: + + # Make mouse button presses capture the mouse until all buttons are released. + # This means that dragging the mouse outside of the window will not cause an interruption in motion events. + for event in tcod.event.get(): + match event: + case tcod.event.MouseButtonDown(button, pixel): # Clicking the window captures the mouse. + tcod.sdl.mouse.capture(True) + case tcod.event.MouseButtonUp(): # When all buttons are released then the mouse is released. + if tcod.event.mouse.get_global_state().state == 0: + tcod.sdl.mouse.capture(False) + case tcod.event.MouseMotion(pixel, pixel_motion, state): + pass # While a button is held this event is still captured outside of the window. + .. seealso:: :any:`tcod.sdl.mouse.set_relative_mode` https://wiki.libsdl.org/SDL_CaptureMouse diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 3061ab58..7c65a374 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -1,5 +1,9 @@ """SDL2 Window and Display handling. +There are two main ways to access the SDL window. +Either you can use this module to open a window yourself bypassing libtcod's context, +or you can use :any:`Context.sdl_window` to get the window being controlled by that context (if the context has one.) + .. versionadded:: 13.4 """ from __future__ import annotations @@ -369,7 +373,23 @@ def get_grabbed_window() -> Optional[Window]: def screen_saver_allowed(allow: Optional[bool] = None) -> bool: - """Allow or prevent a screen saver from being displayed and return the current allowed status.""" + """Allow or prevent a screen saver from being displayed and return the current allowed status. + + If `allow` is `None` then only the current state is returned. + Otherwise it will change the state before checking it. + + SDL typically disables the screensaver by default. + If you're unsure, then don't touch this. + + Example:: + + import tcod.sdl.video + + print(f"Screen saver was allowed: {tcod.sdl.video.screen_saver_allowed()}") + # Allow the screen saver. + # Might be okay for some turn-based games which don't use a gamepad. + tcod.sdl.video.screen_saver_allowed(True) + """ if allow is None: pass elif allow: From 80afe770400e1887883db489e2ce779312f65534 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 2 May 2022 11:43:37 -0700 Subject: [PATCH 0679/1101] Fetch red background fix from libtcod. Fixes #116 --- CHANGELOG.md | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0874c558..00dad55e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- SDL renderers were ignoring tiles where only the background red channel was changed. ## [13.6.1] - 2022-03-29 ### Changed diff --git a/libtcod b/libtcod index 577f83b3..6f0a9bc8 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 577f83b39ca25dcc50051751182ba1defe9b62c4 +Subproject commit 6f0a9bc8c31f769709299f248681b57a6f9659be From 2b0cbe66be4b8c5f2e932db9c0807081ed38a071 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 2 May 2022 11:52:21 -0700 Subject: [PATCH 0680/1101] Prepare 13.6.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00dad55e..8a214db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.6.2] - 2022-05-02 ### Fixed - SDL renderers were ignoring tiles where only the background red channel was changed. From 6976563a676a2f7111254f49cee61c02f9dd2dd3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 18 May 2022 14:39:54 -0700 Subject: [PATCH 0681/1101] Redirect links to the old develop branch to main. --- docs/changelog.rst | 2 +- docs/faq.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 214182fa..2f021a4b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,4 +3,4 @@ =========== You can find the most recent changelog -`here `_. +`here `_. diff --git a/docs/faq.rst b/docs/faq.rst index 109ac981..ed7987fe 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -6,7 +6,7 @@ How do you set a frames-per-second while using contexts? You'll need to use an external tool to manage the framerate. This can either be your own custom tool or you can copy the Clock class from the -`framerate.py `_ +`framerate.py `_ example. diff --git a/setup.py b/setup.py index a364e516..c55ef0c2 100755 --- a/setup.py +++ b/setup.py @@ -103,7 +103,7 @@ def check_sdl_version() -> None: url="https://github.com/libtcod/python-tcod", project_urls={ "Documentation": "https://python-tcod.readthedocs.io", - "Changelog": "https://github.com/libtcod/python-tcod/blob/develop/CHANGELOG.md", + "Changelog": "https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md", "Source": "https://github.com/libtcod/python-tcod", "Tracker": "https://github.com/libtcod/python-tcod/issues", "Forum": "https://github.com/libtcod/python-tcod/discussions", From 751901e45ddf2b413d5c7f48ac2579d897472503 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 May 2022 15:45:44 -0700 Subject: [PATCH 0682/1101] Redirect broken link in readme. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 91444371..1f69e003 100755 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ For the most part it's just:: =========== You can find the most recent changelog -`here `_. +`here `_. ========= License From 7a9a134a98cf9cb9d20dfd4a758e7ff8c0ffa2d0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 May 2022 18:04:32 -0700 Subject: [PATCH 0683/1101] Update type hints for NumPy 1.22.4. Fix broken assert in Renderer.read_pixels. Show error codes by default in MyPy. --- examples/samples_tcod.py | 2 +- setup.cfg | 1 + tcod/console.py | 2 +- tcod/path.py | 8 ++++---- tcod/sdl/render.py | 2 +- tcod/tileset.py | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f92d0151..bc7bc0af 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -140,7 +140,7 @@ def interpolate_corner_colors(self) -> None: def darken_background_characters(self) -> None: # darken background characters sample_console.fg[:] = sample_console.bg[:] - sample_console.fg[:] //= 2 + sample_console.fg[:] //= 2 # type: ignore[arg-type] # https://github.com/numpy/numpy/issues/21592 def randomize_sample_conole(self) -> None: # randomize sample console characters diff --git a/setup.cfg b/setup.cfg index faafa1fc..c70c33ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,7 @@ max-line-length = 130 [mypy] python_version = 3.8 warn_unused_configs = True +show_error_codes = True disallow_subclassing_any = True disallow_any_generics = True disallow_untyped_calls = True diff --git a/tcod/console.py b/tcod/console.py index 56eea2ac..030f8bd1 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -243,7 +243,7 @@ def ch(self) -> NDArray[np.intc]: Index this array with ``console.ch[i, j] # order='C'`` or ``console.ch[x, y] # order='F'``. """ - return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] # type: ignore + return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] @property # type: ignore @deprecate("This attribute has been renamed to `rgba`.") diff --git a/tcod/path.py b/tcod/path.py index acc427af..d581e7da 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -344,7 +344,7 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: edge_array = np.transpose(edge_nz) edge_array -= edge_center c_edges = ffi.new("int[]", len(edge_array) * 3) - edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3) # type: ignore + edges = np.frombuffer(ffi.buffer(c_edges), dtype=np.intc).reshape(len(edge_array), 3) edges[:, :2] = edge_array edges[:, 2] = edge_map[edge_nz] return c_edges, len(edge_array) @@ -1148,7 +1148,7 @@ def traversal(self) -> NDArray[Any]: """ if self._order == "F": axes = range(self._travel.ndim) - return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] # type: ignore + return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] return self._travel def clear(self) -> None: @@ -1320,7 +1320,7 @@ def path_from(self, index: Tuple[int, ...]) -> NDArray[Any]: ffi.from_buffer("int*", path), ) ) - return path[:, ::-1] if self._order == "F" else path # type: ignore + return path[:, ::-1] if self._order == "F" else path def path_to(self, index: Tuple[int, ...]) -> NDArray[Any]: """Return the shortest path from the nearest root to `index`. @@ -1347,4 +1347,4 @@ def path_to(self, index: Tuple[int, ...]) -> NDArray[Any]: >>> pf.path_to((0, 0))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. [] """ # noqa: E501 - return self.path_from(index)[::-1] # type: ignore + return self.path_from(index)[::-1] diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index f44acb6a..3ea6bbd9 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -508,7 +508,7 @@ def read_pixels( out = np.empty((height, width, 3), dtype=np.uint8) else: raise TypeError("Pixel format not supported yet.") - assert out.shape[:2] == height, width + assert out.shape[:2] == (height, width) assert out[0].flags.c_contiguous _check(lib.SDL_RenderReadPixels(self.p, format, ffi.cast("void*", out.ctypes.data), out.strides[0])) return out diff --git a/tcod/tileset.py b/tcod/tileset.py index 14767e5b..93e8bc57 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -428,7 +428,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: (0x259F, 0b0111), # "▟" Quadrant upper right and lower left and lower right. ): alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) - alpha *= 255 + alpha *= 255 # type: ignore[arg-type] # https://github.com/numpy/numpy/issues/21592 tileset.set_tile(codepoint, alpha) for codepoint, axis, fraction, negative in ( From 52f8ba918ccf24639214ba0e89ce4c5839d2b3e0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 27 May 2022 15:10:44 -0700 Subject: [PATCH 0684/1101] Add SDL TTF rendering example. --- examples/DejaVuSerif.ttf | Bin 0 -> 380132 bytes examples/sdl-hello-world.py | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 examples/DejaVuSerif.ttf create mode 100644 examples/sdl-hello-world.py diff --git a/examples/DejaVuSerif.ttf b/examples/DejaVuSerif.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0b803d206c1a4f19559d14f4d3cbd5bbfe0a86a1 GIT binary patch literal 380132 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%0A6)5fz^b{+9WlpW(OXq}w{QV`9e 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

h-1mI`j?mn@#k|La->TpE!0$!%8zdVs3AcoHeV~ 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⪻^aYFO!99beHFX-Akj_(lfyO_-YO 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 tcod.sdl.render.Texture: + """Render text, upload it to VRAM, then return it as an SDL Texture.""" + # Use Pillow normally to render the font. This code is standard. + width, height = font.getsize(text) + image = Image.new("RGBA", (width, height)) + draw = ImageDraw.Draw(image) + draw.text((0, 0), text, font=font) + # Push to VRAM using SDL. + texture = renderer.upload_texture(np.asarray(image)) + texture.blend_mode = tcod.sdl.render.BlendMode.BLEND # Enable alpha blending by default. + return texture + + +def main() -> None: + """Show hello world until the window is closed.""" + # Open an SDL window and renderer. + window = tcod.sdl.video.new_window(720, 480, flags=tcod.sdl.video.WindowFlags.RESIZABLE) + renderer = tcod.sdl.render.new_renderer(window, target_textures=True) + # Render the text once, then reuse the texture. + hello_world = render_text(renderer, "Hello World") + hello_world.color_mod = (64, 255, 64) # Set the color when copied. + + while True: + renderer.draw_color = (0, 0, 0, 255) + renderer.clear() + renderer.copy(hello_world, dest=(0, 0, hello_world.width, hello_world.height)) + renderer.present() + for event in tcod.event.get(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + + +if __name__ == "__main__": + main() From f2d23ce247d35665cc4880c2a5e7eb33ce217bf1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 3 Jul 2022 11:12:51 -0700 Subject: [PATCH 0685/1101] Use requests package instead of urlretrieve. Fixes certificate issues, at least on Windows. --- build_sdl.py | 6 ++++-- pyproject.toml | 1 + requirements.txt | 2 ++ setup.py | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index 19a4d4f2..2484ec7e 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -11,9 +11,9 @@ import zipfile from pathlib import Path from typing import Any, Dict, List, Set -from urllib.request import urlretrieve import pcpp # type: ignore +import requests BITSIZE, LINKAGE = platform.architecture() @@ -97,7 +97,9 @@ def get_sdl2_file(version: str) -> Path: if not sdl2_local_file.exists(): print(f"Downloading {sdl2_remote_file}") os.makedirs("dependencies/", exist_ok=True) - urlretrieve(sdl2_remote_file, sdl2_local_file) + with requests.get(sdl2_remote_file) as response: + response.raise_for_status() + sdl2_local_file.write_bytes(response.content) return sdl2_local_file diff --git a/pyproject.toml b/pyproject.toml index a861e465..63a4c899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ requires = [ "cffi>=1.15", "pycparser>=2.14", "pcpp==1.30", + "requests>=2.28.1", ] build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index a7275c20..a6fc227e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ cffi>=1.15 numpy>=1.21.4 pycparser>=2.14 +requests>=2.28.1 setuptools==60.8.2 +types-requests types-setuptools types-tabulate typing_extensions diff --git a/setup.py b/setup.py index c55ef0c2..95ce8f6c 100755 --- a/setup.py +++ b/setup.py @@ -115,6 +115,7 @@ def check_sdl_version() -> None: setup_requires=[ *pytest_runner, "cffi>=1.15", + "requests>=2.28.1", "pycparser>=2.14", "pcpp==1.30", ], From a737db02f1ec00a08b91270604ed4d9d5622cd12 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 3 Jul 2022 11:13:56 -0700 Subject: [PATCH 0686/1101] Ignore new floating point definition in SDL headers. Fixes the parsing of SDL 2.0.22 headers. --- CHANGELOG.md | 2 ++ build_sdl.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a214db1..98a03373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- Fixed the parsing of SDL 2.0.22 headers. Specifically `SDL_FLT_EPSILON`. ## [13.6.2] - 2022-05-02 ### Fixed diff --git a/build_sdl.py b/build_sdl.py index 2484ec7e..1fc6b788 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -63,6 +63,8 @@ # Prevent double definition. "SDL_FALSE", "SDL_TRUE", + # Ignore floating point symbols. + "SDL_FLT_EPSILON", ) ) From b4112b52d0042d2b640ec7e5fa5337d4f9b9c157 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 3 Jul 2022 11:19:09 -0700 Subject: [PATCH 0687/1101] Update Sphinx config language option. Fixes a warning from ReadTheDocs. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 45d78195..cb508dc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -85,7 +85,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From 655e4f9cc0a313c8de8f146c21dc91a54a17cbcd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 3 Jul 2022 12:10:05 -0700 Subject: [PATCH 0688/1101] Remove outdated type ignores. Were fixed upstream by Numpy. --- examples/samples_tcod.py | 2 +- tcod/tileset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index bc7bc0af..f92d0151 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -140,7 +140,7 @@ def interpolate_corner_colors(self) -> None: def darken_background_characters(self) -> None: # darken background characters sample_console.fg[:] = sample_console.bg[:] - sample_console.fg[:] //= 2 # type: ignore[arg-type] # https://github.com/numpy/numpy/issues/21592 + sample_console.fg[:] //= 2 def randomize_sample_conole(self) -> None: # randomize sample console characters diff --git a/tcod/tileset.py b/tcod/tileset.py index 93e8bc57..14767e5b 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -428,7 +428,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: (0x259F, 0b0111), # "▟" Quadrant upper right and lower left and lower right. ): alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) - alpha *= 255 # type: ignore[arg-type] # https://github.com/numpy/numpy/issues/21592 + alpha *= 255 tileset.set_tile(codepoint, alpha) for codepoint, axis, fraction, negative in ( From db937cdf236a9c3b44cbb194a40e7fbcd3f2528e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 12 Jul 2022 13:57:22 -0700 Subject: [PATCH 0689/1101] Add tcod and libtcod to the glossary. --- .vscode/settings.json | 1 + docs/glossary.rst | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1df05195..ffdcb8ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -88,6 +88,7 @@ "devel", "DHLINE", "dlopen", + "Doryen", "DTEEE", "DTEEN", "DTEES", diff --git a/docs/glossary.rst b/docs/glossary.rst index 5380de58..3cfb22a7 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -10,6 +10,17 @@ Glossary These have been deprecated since version `8.5`. + tcod + `tcod` on its own is shorthand for both :term:`libtcod` and all of its bindings including :term:`python-tcod`. + + It originated as an acronym for the game the library was first created for: + `The Chronicles Of Doryen `_ + + libtcod + This is the original C library which contains the implementations and algorithms used by C programs. + + :term:`python-tcod` includes a statically linked version of this library. + libtcod-cffi This is the `cffi` implementation of libtcodpy, the original was made using `ctypes` which was more difficult to maintain. @@ -19,8 +30,10 @@ Glossary implemented. python-tcod - `python-tcod` is a superset of the :term:`libtcodpy` API. The major - additions include class functionality in returned objects, no manual + `python-tcod` is the main Python port of :term:`libtcod`. + + Originally a superset of the :term:`libtcodpy` API. The major + additions included class functionality in returned objects, no manual memory management, pickle-able objects, and `numpy` array attributes in most objects. @@ -38,14 +51,13 @@ Glossary This left it impractical for any real use as a roguelike library. Currently no new features are planned for `tdl`, instead new features - are added to `libtcod` itself and then ported to :term:`python-tcod`. + are added to :term:`libtcod` itself and then ported to :term:`python-tcod`. :term:`python-tdl` and :term:`libtcodpy` are included in installations of `python-tcod`. libtcodpy - `libtcodpy` is more or less a direct port of `libtcod`'s C API to - Python. + :term:`libtcodpy` is more or less a direct port of :term:`libtcod`'s C API to Python. This caused a handful of issues including instances needing to be freed manually or else a memory leak would occur, and many functions performing badly in Python due to the need to call them frequently. From 7dc1179da93218629b4df461f481efe9704a8882 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Jul 2022 13:08:50 -0700 Subject: [PATCH 0690/1101] Always use keywords in structural pattern matching. The docs were erroneously using positional arguments, but this is not supported by the current classes. Related to #120. --- tcod/event.py | 6 +++--- tcod/sdl/mouse.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 102ade1d..3044ef95 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -68,11 +68,11 @@ raise SystemExit() case tcod.event.KeyDown(sym) if sym in KEY_COMMANDS: print(f"Command: {KEY_COMMANDS[sym]}") - case tcod.event.KeyDown(sym, scancode, mod, repeat): + case tcod.event.KeyDown(sym=sym, scancode=scancode, mod=mod, repeat=repeat): print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") - case tcod.event.MouseButtonDown(button, pixel, tile): + case tcod.event.MouseButtonDown(button=button, pixel=pixel, tile=tile): print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}") - case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion): + case tcod.event.MouseMotion(pixel=pixel, pixel_motion=pixel_motion, tile=tile, tile_motion=tile_motion): print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}") case tcod.event.Event() as event: print(event) # Show any unhandled events. diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index a12f2bca..94794be6 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -151,12 +151,12 @@ def capture(enable: bool) -> None: # This means that dragging the mouse outside of the window will not cause an interruption in motion events. for event in tcod.event.get(): match event: - case tcod.event.MouseButtonDown(button, pixel): # Clicking the window captures the mouse. + case tcod.event.MouseButtonDown(button=button, pixel=pixel): # Clicking the window captures the mouse. tcod.sdl.mouse.capture(True) case tcod.event.MouseButtonUp(): # When all buttons are released then the mouse is released. if tcod.event.mouse.get_global_state().state == 0: tcod.sdl.mouse.capture(False) - case tcod.event.MouseMotion(pixel, pixel_motion, state): + case tcod.event.MouseMotion(pixel=pixel, pixel_motion=pixel_motion, state=state): pass # While a button is held this event is still captured outside of the window. .. seealso:: From 79c8e382f53e0665d4a6842cf2a7f853d82fb7c4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Jul 2022 14:35:22 -0700 Subject: [PATCH 0691/1101] Avoid Sphinx 5.1.0. This version has a regression. --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 6cdabebb..5f954b04 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx>=4.2 +sphinx>=5.0.2,!=5.1.0 sphinx_rtd_theme From 90d3574cb2f1b4f1e11abae976f32ddead75f5e7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 14 Jul 2022 11:29:10 -0700 Subject: [PATCH 0692/1101] Workaround GitHub's broken annotated tag handing. Update action/checkout versions. --- .github/workflows/python-package.yml | 35 +++++++++++++++++----------- .github/workflows/release-on-tag.yml | 2 +- setup.py | 4 +++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index dc00a1ae..5e305f8c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -11,11 +11,14 @@ defaults: run: shell: bash +env: + git-depth: 0 # Depth to search for tags. + jobs: black: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Black run: pip install black - name: Run Black @@ -24,7 +27,7 @@ jobs: isort: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install isort run: pip install isort - name: isort @@ -41,7 +44,7 @@ jobs: flake8: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Flake8 run: pip install Flake8 - name: Flake8 @@ -53,7 +56,7 @@ jobs: mypy: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install Python dependencies @@ -76,7 +79,9 @@ jobs: sdl-version: ["2.0.14", "2.0.16"] fail-fast: true steps: - - uses: actions/checkout@v1 # v1 required to build package. + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Build package @@ -101,9 +106,9 @@ jobs: fail-fast: false steps: - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 @@ -197,7 +202,9 @@ jobs: arch: ["x86_64", "aarch64"] build: ["cp37-manylinux*", "pp37-manylinux*"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} - name: Set up QEMU if: ${{ matrix.arch == 'aarch64' }} uses: docker/setup-qemu-action@v1 @@ -248,13 +255,15 @@ jobs: matrix: python: ["cp38-*_universal2", "cp38-*_x86_64", "cp38-*_arm64", "pp37-*"] steps: - # v2 breaks `git describe` so v1 is needed. - # https://github.com/actions/checkout/issues/272 - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Print git describe - run: git describe + # "--tags" is required to workaround actions/checkout's broken annotated tag handing. + # https://github.com/actions/checkout/issues/290 + run: git describe --tags - name: Install Python dependencies run: pip3 install wheel twine -r requirements.txt - name: Prepare package diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index fd5e5377..cecfbda6 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Generate body run: | scripts/get_release_description.py | tee release_body.md diff --git a/setup.py b/setup.py index 95ce8f6c..c358930c 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,9 @@ def get_version() -> str: """Get the current version from a git tag, or by reading tcod/version.py""" if (SETUP_DIR / ".git").exists(): - tag = subprocess.check_output(["git", "describe", "--abbrev=0"], universal_newlines=True).strip() + # "--tags" is required to workaround actions/checkout's broken annotated tag handing. + # https://github.com/actions/checkout/issues/290 + tag = subprocess.check_output(["git", "describe", "--abbrev=0", "--tags"], universal_newlines=True).strip() assert not tag.startswith("v") version = tag From efc3f5c4b5f0abf46311db33970d11e3319aabc7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 15 Jul 2022 16:08:33 -0700 Subject: [PATCH 0693/1101] Substitute versions more accurately. --- scripts/tag_release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 1479ec82..32d47d70 100644 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -54,7 +54,7 @@ def replace_unreleased_tags(tag: str, dry_run: bool) -> None: if file.suffix != ".py": continue text = file.read_text(encoding="utf-8") - new_text = re.sub(r":: unreleased", rf":: {short_tag}", text) + new_text = re.sub(r":: *unreleased", rf":: {short_tag}", text, flags=re.IGNORECASE) if text != new_text: print(f"Update tags in {file}") if not dry_run: From f59050b2f290aa26bdda9df5c24803992b39fe36 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Aug 2022 18:19:57 -0700 Subject: [PATCH 0694/1101] Make SDLConsoleRender.atlas public. Resolves #121 Also updates related docs and fixes typos. --- .vscode/settings.json | 2 ++ CHANGELOG.md | 4 ++++ tcod/render.py | 31 +++++++++++++++++++++---------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ffdcb8ab..76f51720 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -223,6 +223,7 @@ "pcpp", "PILCROW", "pilmode", + "PIXELFORMAT", "PRESENTVSYNC", "PRINTF", "printn", @@ -288,6 +289,7 @@ "TCODLIB", "TEEE", "TEEW", + "TEXTUREACCESS", "thirdparty", "Tileset", "tilesets", diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a03373..880a3d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- You can new use `SDLConsoleRender.atlas` to access the `SDLTilesetAtlas` used to create it. + [#121](https://github.com/libtcod/python-tcod/issues/121) + ### Fixed - Fixed the parsing of SDL 2.0.22 headers. Specifically `SDL_FLT_EPSILON`. diff --git a/tcod/render.py b/tcod/render.py index 79dc5060..a8aad4d5 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -1,12 +1,12 @@ """Handles the rendering of libtcod's tilesets. Using this module you can render a console to an SDL :any:`Texture` directly, letting you have full control over how -conoles are displayed. +consoles are displayed. This includes rendering multiple tilesets in a single frame and rendering consoles on top of each other. Example:: - tileset = tcod.tileset.load_tilsheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD) + tileset = tcod.tileset.load_tilesheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD) console = tcod.Console(20, 8) console.print(0, 0, "Hello World") sdl_window = tcod.sdl.video.new_window( @@ -31,6 +31,8 @@ from typing import Any, Optional +from typing_extensions import Final + import tcod.console import tcod.sdl.render import tcod.tileset @@ -43,15 +45,20 @@ class SDLTilesetAtlas: def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Tileset) -> None: self._renderer = renderer - self.tileset = tileset - self.p = ffi.gc(_check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete) + self.tileset: Final[tcod.tileset.Tileset] = tileset + """The tileset used to create this SDLTilesetAtlas.""" + self.p: Final = ffi.gc( + _check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete + ) @classmethod def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas: self = object.__new__(cls) + # Ignore Final reassignment type errors since this is an alternative constructor. + # This could be a sign that the current constructor was badly implemented. self._renderer = tcod.sdl.render.Renderer(renderer_p) - self.tileset = tcod.tileset.Tileset._from_ref(atlas_p.tileset) - self.p = atlas_p + self.tileset = tcod.tileset.Tileset._from_ref(atlas_p.tileset) # type: ignore[misc] + self.p = atlas_p # type: ignore[misc] return self @@ -59,7 +66,11 @@ class SDLConsoleRender: """Holds an internal cache console and texture which are used to optimized console rendering.""" def __init__(self, atlas: SDLTilesetAtlas) -> None: - self._atlas = atlas + self.atlas: Final[SDLTilesetAtlas] = atlas + """The SDLTilesetAtlas used to create this SDLConsoleRender. + + .. versionadded:: Unreleased + """ self._renderer = atlas._renderer self._cache_console: Optional[tcod.console.Console] = None self._texture: Optional[tcod.sdl.render.Texture] = None @@ -80,8 +91,8 @@ def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture: if self._cache_console is None or self._texture is None: self._cache_console = tcod.console.Console(console.width, console.height) self._texture = self._renderer.new_texture( - self._atlas.tileset.tile_width * console.width, - self._atlas.tileset.tile_height * console.height, + self.atlas.tileset.tile_width * console.width, + self.atlas.tileset.tile_height * console.height, format=int(lib.SDL_PIXELFORMAT_RGBA32), access=int(lib.SDL_TEXTUREACCESS_TARGET), ) @@ -89,7 +100,7 @@ def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture: with self._renderer.set_render_target(self._texture): _check( lib.TCOD_sdl2_render_texture( - self._atlas.p, console.console_c, self._cache_console.console_c, self._texture.p + self.atlas.p, console.console_c, self._cache_console.console_c, self._texture.p ) ) return self._texture From 8d9c72a99bb387a939b7f817722431a76fca493f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Aug 2022 19:32:53 -0700 Subject: [PATCH 0695/1101] Prepare 13.7.0 release. --- CHANGELOG.md | 2 ++ tcod/render.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 880a3d5b..129c8776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.7.0] - 2022-08-07 ### Added - You can new use `SDLConsoleRender.atlas` to access the `SDLTilesetAtlas` used to create it. [#121](https://github.com/libtcod/python-tcod/issues/121) diff --git a/tcod/render.py b/tcod/render.py index a8aad4d5..e604d29f 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -69,7 +69,7 @@ def __init__(self, atlas: SDLTilesetAtlas) -> None: self.atlas: Final[SDLTilesetAtlas] = atlas """The SDLTilesetAtlas used to create this SDLConsoleRender. - .. versionadded:: Unreleased + .. versionadded:: 13.7 """ self._renderer = atlas._renderer self._cache_console: Optional[tcod.console.Console] = None From d5f8663a599dc0117aadd297c718c7b93fd881d5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 21 Jul 2022 15:58:03 -0700 Subject: [PATCH 0696/1101] Add basic joystick port of SDL2. --- CHANGELOG.md | 3 + docs/index.rst | 1 + docs/sdl/joystick.rst | 6 ++ examples/eventget.py | 3 + tcod/event.py | 244 +++++++++++++++++++++++++++++++++++++++++- tcod/sdl/joystick.py | 119 ++++++++++++++++++++ 6 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 docs/sdl/joystick.rst create mode 100644 tcod/sdl/joystick.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c8776..eef28017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Ported SDL2 joystick handing as `tcod.sdl.joystick`. +- New joystick related events. ## [13.7.0] - 2022-08-07 ### Added diff --git a/docs/index.rst b/docs/index.rst index 981ce6bf..7a138378 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ Contents: tcod/tileset libtcodpy sdl/audio + sdl/joystick sdl/render sdl/mouse sdl/video diff --git a/docs/sdl/joystick.rst b/docs/sdl/joystick.rst new file mode 100644 index 00000000..81628124 --- /dev/null +++ b/docs/sdl/joystick.rst @@ -0,0 +1,6 @@ +tcod.sdl.joystick - SDL Joystick Support +======================================== + +.. automodule:: tcod.sdl.joystick + :members: + :member-order: bysource diff --git a/examples/eventget.py b/examples/eventget.py index 55d6a58b..bb3534aa 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -8,6 +8,8 @@ from typing import List import tcod +import tcod.sdl.joystick +import tcod.sdl.sys WIDTH, HEIGHT = 720, 480 @@ -17,6 +19,7 @@ def main() -> None: event_log: List[str] = [] motion_desc = "" + joysticks = tcod.sdl.joystick.get_joysticks() with tcod.context.new(width=WIDTH, height=HEIGHT) as context: console = context.new_console() diff --git a/tcod/event.py b/tcod/event.py index 3044ef95..bd332c97 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -93,6 +93,7 @@ from tcod.event_constants import * # noqa: F4 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT from tcod.loader import ffi, lib +from tcod.sdl.joystick import _HAT_DIRECTIONS T = TypeVar("T") @@ -303,7 +304,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Event: raise NotImplementedError() def __str__(self) -> str: - return "" % (self.type,) + return f"" class Quit(Event): @@ -779,6 +780,199 @@ def __str__(self) -> str: ) +class JoystickEvent(Event): + """A base class for joystick events. + + .. versionadded:: Unreleased + """ + + def __init__(self, type: str, which: int): + super().__init__(type) + self.which = which + """The ID of the joystick this event is for.""" + + def __repr__(self) -> str: + return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})" + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, which={self.which}>" + + +class JoystickAxis(JoystickEvent): + """When a joystick axis changes in value. + + .. versionadded:: Unreleased + + .. seealso:: + :any:`tcod.sdl.joystick` + """ + + which: int + """The ID of the joystick this event is for.""" + + def __init__(self, type: str, which: int, axis: int, value: int): + super().__init__(type, which) + self.axis = axis + """The index of the changed axis.""" + self.value = value + """The raw value of the axis in the range -32768 to 32767.""" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> JoystickAxis: + return cls("JOYAXISMOTION", sdl_event.jaxis.which, sdl_event.jaxis.axis, sdl_event.jaxis.value) + + def __repr__(self) -> str: + return ( + f"tcod.event.{self.__class__.__name__}" + f"(type={self.type!r}, which={self.which}, axis={self.axis}, value={self.value})" + ) + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, axis={self.axis}, value={self.value}>" + + +class JoystickBall(JoystickEvent): + """When a joystick ball is moved. + + .. versionadded:: Unreleased + + .. seealso:: + :any:`tcod.sdl.joystick` + """ + + which: int + """The ID of the joystick this event is for.""" + + def __init__(self, type: str, which: int, ball: int, dx: int, dy: int): + super().__init__(type, which) + self.ball = ball + """The index of the moved ball.""" + self.dx = dx + """The X motion of the ball.""" + self.dy = dy + """The Y motion of the ball.""" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> JoystickBall: + return cls( + "JOYBALLMOTION", sdl_event.jball.which, sdl_event.jball.ball, sdl_event.jball.xrel, sdl_event.jball.yrel + ) + + def __repr__(self) -> str: + return ( + f"tcod.event.{self.__class__.__name__}" + f"(type={self.type!r}, which={self.which}, ball={self.ball}, dx={self.dx}, dy={self.dy})" + ) + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, ball={self.ball}, dx={self.dx}, dy={self.dy}>" + + +class JoystickHat(JoystickEvent): + """When a joystick hat changes direction. + + .. versionadded:: Unreleased + + .. seealso:: + :any:`tcod.sdl.joystick` + """ + + which: int + """The ID of the joystick this event is for.""" + + def __init__(self, type: str, which: int, x: Literal[-1, 0, 1], y: Literal[-1, 0, 1]): + super().__init__(type, which) + self.x = x + """The new X direction of the hat.""" + self.y = y + """The new Y direction of the hat.""" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> JoystickHat: + return cls("JOYHATMOTION", sdl_event.jhat.which, *_HAT_DIRECTIONS[sdl_event.jhat.hat]) + + def __repr__(self) -> str: + return ( + f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which}, x={self.x}, y={self.y})" + ) + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, x={self.x}, y={self.y}>" + + +class JoystickButton(JoystickEvent): + """When a joystick button is pressed or released. + + .. versionadded:: Unreleased + + Example:: + + for event in tcod.event.get(): + match event: + case JoystickButton(which=which, button=button, pressed=True): + print(f"Pressed {button=} on controller {which}.") + case JoystickButton(which=which, button=button, pressed=False): + print(f"Released {button=} on controller {which}.") + """ + + which: int + """The ID of the joystick this event is for.""" + + def __init__(self, type: str, which: int, button: int): + super().__init__(type, which) + self.button = button + """The index of the button this event is for.""" + + @property + def pressed(self) -> bool: + """True if the joystick button has been pressed, False when the button was released.""" + return self.type == "JOYBUTTONDOWN" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> JoystickButton: + type = {lib.SDL_JOYBUTTONDOWN: "JOYBUTTONDOWN", lib.SDL_JOYBUTTONUP: "JOYBUTTONUP"}[sdl_event.type] + return cls(type, sdl_event.jbutton.which, sdl_event.jbutton.button) + + def __repr__(self) -> str: + return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which}, button={self.button})" + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, button={self.button}>" + + +class JoystickDevice(JoystickEvent): + """An event for when a joystick is added or removed. + + .. versionadded:: Unreleased + + Example:: + + joysticks: dict[int, tcod.sdl.joystick.Joystick] = {} + for event in tcod.event.get(): + match event: + case tcod.event.JoystickDevice(type="JOYDEVICEADDED", which=device_id): + new_joystick = tcod.sdl.joystick.Joystick(device_id) + joysticks[new_joystick.id] = new_joystick + case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", which=which): + del joysticks[which] + """ + + which: int + """When type="JOYDEVICEADDED" this is the device ID. + When type="JOYDEVICEREMOVED" this is the instance ID. + """ + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice: + type = {lib.SDL_JOYDEVICEADDED: "JOYDEVICEADDED", lib.SDL_JOYDEVICEREMOVED: "JOYDEVICEREMOVED"}[sdl_event.type] + return cls(type, sdl_event.jdevice.which) + + class Undefined(Event): """This class is a place holder for SDL events without their own tcod.event class. @@ -809,6 +1003,13 @@ def __str__(self) -> str: lib.SDL_MOUSEWHEEL: MouseWheel, lib.SDL_TEXTINPUT: TextInput, lib.SDL_WINDOWEVENT: WindowEvent, + lib.SDL_JOYAXISMOTION: JoystickAxis, + lib.SDL_JOYBALLMOTION: JoystickBall, + lib.SDL_JOYHATMOTION: JoystickHat, + lib.SDL_JOYBUTTONDOWN: JoystickButton, + lib.SDL_JOYBUTTONUP: JoystickButton, + lib.SDL_JOYDEVICEADDED: JoystickDevice, + lib.SDL_JOYDEVICEREMOVED: JoystickDevice, } @@ -1076,6 +1277,41 @@ def ev_windowtakefocus(self, event: tcod.event.WindowEvent) -> Optional[T]: def ev_windowhittest(self, event: tcod.event.WindowEvent) -> Optional[T]: pass + def ev_joyaxismotion(self, event: tcod.event.JoystickAxis) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joyballmotion(self, event: tcod.event.JoystickBall) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joyhatmotion(self, event: tcod.event.JoystickHat) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joybuttondown(self, event: tcod.event.JoystickButton) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joybuttonup(self, event: tcod.event.JoystickButton) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joydeviceadded(self, event: tcod.event.JoystickDevice) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + def ev_(self, event: Any) -> Optional[T]: pass @@ -2326,6 +2562,12 @@ def __repr__(self) -> str: "WindowEvent", "WindowMoved", "WindowResized", + "JoystickEvent", + "JoystickAxis", + "JoystickBall", + "JoystickHat", + "JoystickButton", + "JoystickDevice", "Undefined", "get", "wait", diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py new file mode 100644 index 00000000..a2001cf4 --- /dev/null +++ b/tcod/sdl/joystick.py @@ -0,0 +1,119 @@ +"""SDL Joystick Support + +.. versionadded:: Unreleased +""" +from __future__ import annotations + +import enum +from typing import Dict, List, Optional, Tuple + +from typing_extensions import Final, Literal + +import tcod.sdl.sys +from tcod.loader import ffi, lib +from tcod.sdl import _check, _check_p + +_HAT_DIRECTIONS: Dict[int, Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { + lib.SDL_HAT_CENTERED or 0: (0, 0), + lib.SDL_HAT_UP or 0: (0, -1), + lib.SDL_HAT_RIGHT or 0: (1, 0), + lib.SDL_HAT_DOWN or 0: (0, 1), + lib.SDL_HAT_LEFT or 0: (-1, 0), + lib.SDL_HAT_RIGHTUP or 0: (1, -1), + lib.SDL_HAT_RIGHTDOWN or 0: (1, 1), + lib.SDL_HAT_LEFTUP or 0: (-1, -1), + lib.SDL_HAT_LEFTDOWN or 0: (-1, 1), +} + + +class Power(enum.IntEnum): + """The possible power states of a controller. + + .. seealso:: + :any:`Joystick.get_current_power` + """ + + UNKNOWN = lib.SDL_JOYSTICK_POWER_UNKNOWN or -1 + """Power state is unknown.""" + EMPTY = lib.SDL_JOYSTICK_POWER_EMPTY or 0 + """<= 5% power.""" + LOW = lib.SDL_JOYSTICK_POWER_LOW or 1 + """<= 20% power.""" + MEDIUM = lib.SDL_JOYSTICK_POWER_MEDIUM or 2 + """<= 70% power.""" + FULL = lib.SDL_JOYSTICK_POWER_FULL or 3 + """<= 100% power.""" + WIRED = lib.SDL_JOYSTICK_POWER_WIRED or 4 + """""" + MAX = lib.SDL_JOYSTICK_POWER_MAX or 5 + """""" + + +class Joystick: + """An SDL joystick. + + .. seealso:: + https://wiki.libsdl.org/CategoryJoystick + """ + + def __init__(self, device_index: int): + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) + self.sdl_joystick_p: Final = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) + self.axes: Final = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) + self.balls: Final = _check(lib.SDL_JoystickNumBalls(self.sdl_joystick_p)) + self.buttons: Final = _check(lib.SDL_JoystickNumButtons(self.sdl_joystick_p)) + self.hats: Final = _check(lib.SDL_JoystickNumHats(self.sdl_joystick_p)) + self.name: Final = str(ffi.string(lib.SDL_JoystickName(self.sdl_joystick_p)), encoding="utf-8") + """The name of this joystick.""" + self.guid: Final = self._get_guid() + """The GUID of this joystick.""" + self.id: Final = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p)) + """The instance ID of this joystick. This is not the same as the device ID.""" + + def _get_guid(self) -> str: + guid_str = ffi.new("char[33]") + lib.SDL_JoystickGetGUIDString(lib.SDL_JoystickGetGUID(self.sdl_joystick_p), guid_str, len(guid_str)) + return str(tcod.ffi.string(guid_str), encoding="utf-8") + + def get_current_power(self) -> Power: + """Return the power level/state of this joystick. See :any:`Power`.""" + return Power(lib.SDL_JoystickCurrentPowerLevel(self.sdl_joystick_p)) + + def get_axis(self, axis: int) -> int: + """Return the raw value of `axis` in the range -32768 to 32767.""" + return int(lib.SDL_JoystickGetAxis(self.sdl_joystick_p, axis)) + + def get_ball(self, ball: int) -> Tuple[int, int]: + """Return the values (delta_x, delta_y) of `ball` since the last poll.""" + xy = ffi.new("int[2]") + _check(lib.SDL_JoystickGetBall(ball, xy, xy + 1)) + return int(xy[0]), int(xy[1]) + + def get_button(self, button: int) -> bool: + """Return True if `button` is pressed.""" + return bool(lib.SDL_JoystickGetButton(self.sdl_joystick_p, button)) + + def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: + """Return the direction of `hat` as (x, y). With (-1, -1) being in the upper-left.""" + return _HAT_DIRECTIONS[lib.SDL_JoystickGetHat(self.sdl_joystick_p, hat)] + + +def get_number() -> int: + """Return the number of attached joysticks.""" + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) + return _check(lib.SDL_NumJoysticks()) + + +def get_joysticks() -> List[Joystick]: + """Return a list of all connected joystick devices.""" + return [Joystick(i) for i in range(get_number())] + + +def event_state(new_state: Optional[bool] = None) -> bool: + """Check or set joystick event polling. + + .. seealso:: + https://wiki.libsdl.org/SDL_JoystickEventState + """ + _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE} + return bool(_check(lib.SDL_JoystickEventState(_OPTIONS[new_state]))) From 9a9fa27718f75d966abafd78652e90dcefa5a6fe Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 21 Jul 2022 23:32:56 -0700 Subject: [PATCH 0697/1101] Clean up docs. --- docs/sdl/joystick.rst | 5 +++++ tcod/sdl/joystick.py | 19 ++++++++++++------- tcod/sdl/video.py | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/sdl/joystick.rst b/docs/sdl/joystick.rst index 81628124..e1b22ca2 100644 --- a/docs/sdl/joystick.rst +++ b/docs/sdl/joystick.rst @@ -2,5 +2,10 @@ tcod.sdl.joystick - SDL Joystick Support ======================================== .. automodule:: tcod.sdl.joystick + :members: + :exclude-members: + Power + +.. autoclass:: tcod.sdl.joystick.Power :members: :member-order: bysource diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index a2001cf4..e3abb8b6 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -59,15 +59,20 @@ class Joystick: def __init__(self, device_index: int): tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) self.sdl_joystick_p: Final = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) - self.axes: Final = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) - self.balls: Final = _check(lib.SDL_JoystickNumBalls(self.sdl_joystick_p)) - self.buttons: Final = _check(lib.SDL_JoystickNumButtons(self.sdl_joystick_p)) - self.hats: Final = _check(lib.SDL_JoystickNumHats(self.sdl_joystick_p)) - self.name: Final = str(ffi.string(lib.SDL_JoystickName(self.sdl_joystick_p)), encoding="utf-8") + """The CFFI pointer to an SDL_Joystick struct.""" + self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) + """The total number of axes.""" + self.balls: Final[int] = _check(lib.SDL_JoystickNumBalls(self.sdl_joystick_p)) + """The total number of trackballs.""" + self.buttons: Final[int] = _check(lib.SDL_JoystickNumButtons(self.sdl_joystick_p)) + """The total number of buttons.""" + self.hats: Final[int] = _check(lib.SDL_JoystickNumHats(self.sdl_joystick_p)) + """The total number of hats.""" + self.name: Final[str] = str(ffi.string(lib.SDL_JoystickName(self.sdl_joystick_p)), encoding="utf-8") """The name of this joystick.""" - self.guid: Final = self._get_guid() + self.guid: Final[str] = self._get_guid() """The GUID of this joystick.""" - self.id: Final = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p)) + self.id: Final[int] = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p)) """The instance ID of this joystick. This is not the same as the device ID.""" def _get_guid(self) -> str: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 7c65a374..2ef4553e 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -87,7 +87,7 @@ class FlashOperation(enum.IntEnum): CANCEL = 0 """Stop flashing.""" BRIEFLY = 1 - """Flash breifly.""" + """Flash briefly.""" UNTIL_FOCUSED = 2 """Flash until focus is gained.""" From 6e71a35b91349fef367448844ea830a5e4a9b88e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Jul 2022 14:23:25 -0700 Subject: [PATCH 0698/1101] Update event type attributes. --- tcod/event.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index bd332c97..6caec3e7 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -656,7 +656,7 @@ class WindowEvent(Event): type (str): A window event could mean various event types. """ - type: Final[ # type: ignore[misc] # Narrowing contant type. + type: Final[ # type: ignore[misc] # Narrowing final type. Literal[ "WindowShown", "WindowHidden", @@ -726,7 +726,7 @@ class WindowMoved(WindowEvent): y (int): Movement on the y-axis. """ - type: Literal["WINDOWMOVED"] # type: ignore[assignment,misc] + type: Final[Literal["WINDOWMOVED"]] # type: ignore[assignment,misc] def __init__(self, x: int, y: int) -> None: super().__init__(None) @@ -757,7 +757,7 @@ class WindowResized(WindowEvent): height (int): The current height of the window. """ - type: Literal["WINDOWRESIZED", "WINDOWSIZECHANGED"] # type: ignore[assignment,misc] + type: Final[Literal["WINDOWRESIZED", "WINDOWSIZECHANGED"]] # type: ignore[assignment,misc] def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) @@ -962,6 +962,8 @@ class JoystickDevice(JoystickEvent): del joysticks[which] """ + type = Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[assignment,misc] + which: int """When type="JOYDEVICEADDED" this is the device ID. When type="JOYDEVICEREMOVED" this is the instance ID. From a0602f8d656b6c5c6ea62981b3efbfdca8709253 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 11 Sep 2022 14:44:06 -0700 Subject: [PATCH 0699/1101] Add GameController class. --- tcod/sdl/joystick.py | 289 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 280 insertions(+), 9 deletions(-) diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index e3abb8b6..7678e36d 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -5,7 +5,7 @@ from __future__ import annotations import enum -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple, Union from typing_extensions import Final, Literal @@ -26,6 +26,72 @@ } +class ControllerAxis(enum.IntEnum): + """The standard axes for a game controller.""" + + INVALID = lib.SDL_CONTROLLER_AXIS_INVALID or -1 + LEFTX = lib.SDL_CONTROLLER_AXIS_LEFTX or 0 + """""" + LEFTY = lib.SDL_CONTROLLER_AXIS_LEFTY or 1 + """""" + RIGHTX = lib.SDL_CONTROLLER_AXIS_RIGHTX or 2 + """""" + RIGHTY = lib.SDL_CONTROLLER_AXIS_RIGHTY or 3 + """""" + TRIGGERLEFT = lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT or 4 + """""" + TRIGGERRIGHT = lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT or 5 + """""" + + +class ControllerButton(enum.IntEnum): + """The standard buttons for a game controller.""" + + INVALID = lib.SDL_CONTROLLER_BUTTON_INVALID or -1 + A = lib.SDL_CONTROLLER_BUTTON_A or 0 + """""" + B = lib.SDL_CONTROLLER_BUTTON_B or 1 + """""" + X = lib.SDL_CONTROLLER_BUTTON_X or 2 + """""" + Y = lib.SDL_CONTROLLER_BUTTON_Y or 3 + """""" + BACK = lib.SDL_CONTROLLER_BUTTON_BACK or 4 + """""" + GUIDE = lib.SDL_CONTROLLER_BUTTON_GUIDE or 5 + """""" + START = lib.SDL_CONTROLLER_BUTTON_START or 6 + """""" + LEFTSTICK = lib.SDL_CONTROLLER_BUTTON_LEFTSTICK or 7 + """""" + RIGHTSTICK = lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK or 8 + """""" + LEFTSHOULDER = lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER or 9 + """""" + RIGHTSHOULDER = lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER or 10 + """""" + DPAD_UP = lib.SDL_CONTROLLER_BUTTON_DPAD_UP or 11 + """""" + DPAD_DOWN = lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN or 12 + """""" + DPAD_LEFT = lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT or 13 + """""" + DPAD_RIGHT = lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT or 14 + """""" + MISC1 = 15 + """""" + PADDLE1 = 16 + """""" + PADDLE2 = 17 + """""" + PADDLE3 = 18 + """""" + PADDLE4 = 19 + """""" + TOUCHPAD = 20 + """""" + + class Power(enum.IntEnum): """The possible power states of a controller. @@ -50,15 +116,14 @@ class Power(enum.IntEnum): class Joystick: - """An SDL joystick. + """A low-level SDL joystick. .. seealso:: https://wiki.libsdl.org/CategoryJoystick """ - def __init__(self, device_index: int): - tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) - self.sdl_joystick_p: Final = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) + def __init__(self, sdl_joystick_p: Any): + self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) """The total number of axes.""" @@ -74,6 +139,24 @@ def __init__(self, device_index: int): """The GUID of this joystick.""" self.id: Final[int] = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p)) """The instance ID of this joystick. This is not the same as the device ID.""" + self._keep_alive: Any = None + """The owner of this objects memory if this object does not own itself.""" + + @classmethod + def _open(cls, device_index: int) -> Joystick: + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) + p = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) + return cls(p) + + def __eq__(self, other: object) -> bool: + if isinstance(other, GameController): + return self == other.joystick.id + if isinstance(other, Joystick): + return self.id == other.id + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) def _get_guid(self) -> str: guid_str = ffi.new("char[33]") @@ -95,7 +178,7 @@ def get_ball(self, ball: int) -> Tuple[int, int]: return int(xy[0]), int(xy[1]) def get_button(self, button: int) -> bool: - """Return True if `button` is pressed.""" + """Return True if `button` is currently held.""" return bool(lib.SDL_JoystickGetButton(self.sdl_joystick_p, button)) def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: @@ -103,7 +186,168 @@ def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: return _HAT_DIRECTIONS[lib.SDL_JoystickGetHat(self.sdl_joystick_p, hat)] -def get_number() -> int: +class GameController: + """A standard interface for an Xbox 360 style game controller.""" + + def __init__(self, sdl_controller_p: Any): + self.sdl_controller_p: Final = sdl_controller_p + self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p)) + """The :any:`Joystick` associated with this controller.""" + self.joystick._keep_alive = self.sdl_controller_p # This objects real owner needs to be kept alive. + + @classmethod + def _open(cls, joystick_index: int) -> GameController: + return cls(_check_p(ffi.gc(lib.SDL_GameControllerOpen(joystick_index), lib.SDL_GameControllerClose))) + + def get_button(self, button: ControllerButton) -> bool: + """Return True if `button` is currently held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, button)) + + def get_axis(self, axis: ControllerAxis) -> int: + """Return the state of the given `axis`. + + The state is usually a value from -32768 to 32767, with positive values towards the lower-right direction. + Triggers have the range of 0 to 32767 instead. + """ + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, axis)) + + def __eq__(self, other: object) -> bool: + if isinstance(other, GameController): + return self.joystick.id == other.joystick.id + if isinstance(other, Joystick): + return self.joystick.id == other.id + return NotImplemented + + def __hash__(self) -> int: + return hash(self.joystick.id) + + # These could exist as convenience functions, but the get_X functions are probably better. + @property + def _left_x(self) -> int: + "Return the position of this axis. (-32768 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTX)) + + @property + def _left_y(self) -> int: + "Return the position of this axis. (-32768 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTY)) + + @property + def _right_x(self) -> int: + "Return the position of this axis. (-32768 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTX)) + + @property + def _right_y(self) -> int: + "Return the position of this axis. (-32768 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTY)) + + @property + def _trigger_left(self) -> int: + "Return the position of this trigger. (0 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) + + @property + def _trigger_right(self) -> int: + "Return the position of this trigger. (0 to 32767)" + return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) + + @property + def _a(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_A)) + + @property + def _b(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_B)) + + @property + def _x(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_X)) + + @property + def _y(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_Y)) + + @property + def _back(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_BACK)) + + @property + def _guide(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_GUIDE)) + + @property + def _start(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_START)) + + @property + def _left_stick(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSTICK)) + + @property + def _right_stick(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK)) + + @property + def _left_shoulder(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) + + @property + def _right_shoulder(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) + + @property + def _dpad(self) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: + return ( + lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) + - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT), + lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN) + - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_UP), + ) + + @property + def _misc1(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_MISC1)) + + @property + def _paddle1(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE1)) + + @property + def _paddle2(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE2)) + + @property + def _paddle3(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE3)) + + @property + def _paddle4(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE4)) + + @property + def _touchpad(self) -> bool: + """Return True if this button is held.""" + return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_TOUCHPAD)) + + +def _get_number() -> int: """Return the number of attached joysticks.""" tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) return _check(lib.SDL_NumJoysticks()) @@ -111,10 +355,27 @@ def get_number() -> int: def get_joysticks() -> List[Joystick]: """Return a list of all connected joystick devices.""" - return [Joystick(i) for i in range(get_number())] + return [Joystick._open(i) for i in range(_get_number())] + + +def get_controllers() -> List[GameController]: + """Return a list of all connected game controllers. + + This ignores joysticks without a game controller mapping. + """ + return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGameController(i)] -def event_state(new_state: Optional[bool] = None) -> bool: +def get_all() -> List[Union[Joystick, GameController]]: + """Return a list of all connected joystick or controller devices. + + If the joystick has a controller mapping then it is returned as a :any:`GameController`. + Otherwise it is returned as a :any:`Joystick`. + """ + return [GameController._open(i) if lib.SDL_IsGameController(i) else Joystick._open(i) for i in range(_get_number())] + + +def joystick_event_state(new_state: Optional[bool] = None) -> bool: """Check or set joystick event polling. .. seealso:: @@ -122,3 +383,13 @@ def event_state(new_state: Optional[bool] = None) -> bool: """ _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE} return bool(_check(lib.SDL_JoystickEventState(_OPTIONS[new_state]))) + + +def controller_event_state(new_state: Optional[bool] = None) -> bool: + """Check or set game controller event polling. + + .. seealso:: + https://wiki.libsdl.org/SDL_GameControllerEventState + """ + _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE} + return bool(_check(lib.SDL_GameControllerEventState(_OPTIONS[new_state]))) From 2b8388f360fa9e75c98074cf23c1087974371d48 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Sep 2022 15:01:35 -0700 Subject: [PATCH 0700/1101] Add controller events. --- examples/eventget.py | 18 +++++- tcod/event.py | 146 ++++++++++++++++++++++++++++++++++++++++--- tcod/sdl/joystick.py | 15 ++++- 3 files changed, 168 insertions(+), 11 deletions(-) diff --git a/examples/eventget.py b/examples/eventget.py index bb3534aa..52e26c09 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -5,7 +5,7 @@ # https://creativecommons.org/publicdomain/zero/1.0/ """An demonstration of event handling using the tcod.event module. """ -from typing import List +from typing import List, Set import tcod import tcod.sdl.joystick @@ -19,7 +19,9 @@ def main() -> None: event_log: List[str] = [] motion_desc = "" - joysticks = tcod.sdl.joystick.get_joysticks() + tcod.sdl.joystick.init() + controllers: Set[tcod.sdl.joystick.GameController] = set() + joysticks: Set[tcod.sdl.joystick.Joystick] = set() with tcod.context.new(width=WIDTH, height=HEIGHT) as context: console = context.new_console() @@ -42,9 +44,19 @@ def main() -> None: raise SystemExit() if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED": console = context.new_console() + if isinstance(event, tcod.event.ControllerDevice): + if event.type == "CONTROLLERDEVICEADDED": + controllers.add(event.controller) + elif event.type == "CONTROLLERDEVICEREMOVED": + controllers.remove(event.controller) + if isinstance(event, tcod.event.JoystickDevice): + if event.type == "JOYDEVICEADDED": + joysticks.add(event.joystick) + elif event.type == "JOYDEVICEREMOVED": + joysticks.remove(event.joystick) if isinstance(event, tcod.event.MouseMotion): motion_desc = str(event) - else: + else: # Log all events other than MouseMotion. event_log.append(str(event)) diff --git a/tcod/event.py b/tcod/event.py index 6caec3e7..dba3054a 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -90,6 +90,7 @@ from typing_extensions import Final, Literal import tcod.event_constants +import tcod.sdl.joystick from tcod.event_constants import * # noqa: F4 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT from tcod.loader import ffi, lib @@ -791,6 +792,12 @@ def __init__(self, type: str, which: int): self.which = which """The ID of the joystick this event is for.""" + @property + def joystick(self) -> tcod.sdl.joystick.Joystick: + if self.type == "JOYDEVICEADDED": + return tcod.sdl.joystick.Joystick._open(self.which) + return tcod.sdl.joystick.Joystick._from_instance_id(self.which) + def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})" @@ -952,17 +959,16 @@ class JoystickDevice(JoystickEvent): Example:: - joysticks: dict[int, tcod.sdl.joystick.Joystick] = {} + joysticks: set[tcod.sdl.joystick.Joystick] = {} for event in tcod.event.get(): match event: - case tcod.event.JoystickDevice(type="JOYDEVICEADDED", which=device_id): - new_joystick = tcod.sdl.joystick.Joystick(device_id) - joysticks[new_joystick.id] = new_joystick - case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", which=which): - del joysticks[which] + case tcod.event.JoystickDevice(type="JOYDEVICEADDED", joystick=new_joystick): + joysticks.add(new_joystick) + case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", joystick=joystick): + joysticks.remove(joystick) """ - type = Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[assignment,misc] + type: Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[misc] which: int """When type="JOYDEVICEADDED" this is the device ID. @@ -975,6 +981,126 @@ def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice: return cls(type, sdl_event.jdevice.which) +class ControllerEvent(Event): + """Base class for controller events. + + .. versionadded:: Unreleased + """ + + def __init__(self, type: str, which: int): + super().__init__(type) + self.which = which + """The ID of the joystick this event is for.""" + + @property + def controller(self) -> tcod.sdl.joystick.GameController: + """The :any:`GameController: for this event.""" + if self.type == "CONTROLLERDEVICEADDED": + return tcod.sdl.joystick.GameController._open(self.which) + return tcod.sdl.joystick.GameController._from_instance_id(self.which) + + def __repr__(self) -> str: + return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})" + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, which={self.which}>" + + +class ControllerAxis(ControllerEvent): + """When a controller axis is moved. + + .. versionadded:: Unreleased + """ + + type: Final[Literal["CONTROLLERAXISMOTION"]] # type: ignore[misc] + + def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis, value: int): + super().__init__(type, which) + self.axis = axis + """Which axis is being moved. One of :any:`ControllerAxis`.""" + self.value = value + """The new value of this events axis. + + This will be -32768 to 32767 for all axes except for triggers which are 0 to 32767 instead.""" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> ControllerAxis: + return cls( + "CONTROLLERAXISMOTION", + sdl_event.caxis.which, + tcod.sdl.joystick.ControllerAxis(sdl_event.caxis.axis), + sdl_event.caxis.value, + ) + + def __repr__(self) -> str: + return ( + f"tcod.event.{self.__class__.__name__}" + f"(type={self.type!r}, which={self.which}, axis={self.axis}, value={self.value})" + ) + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, axis={self.axis}, value={self.value}>" + + +class ControllerButton(ControllerEvent): + """When a controller button is pressed or released. + + .. versionadded:: Unreleased + """ + + type: Final[Literal["CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP"]] # type: ignore[misc] + + def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerButton, pressed: bool): + super().__init__(type, which) + self.button = button + """The button for this event. One of :any:`ControllerButton`.""" + self.pressed = pressed + """True if the button was pressed, False if it was released.""" + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> ControllerButton: + type = { + lib.SDL_CONTROLLERBUTTONDOWN: "CONTROLLERBUTTONDOWN", + lib.SDL_CONTROLLERBUTTONUP: "CONTROLLERBUTTONUP", + }[sdl_event.type] + return cls( + type, + sdl_event.cbutton.which, + tcod.sdl.joystick.ControllerButton(sdl_event.cbutton.button), + sdl_event.cbutton.state == lib.SDL_PRESSED, + ) + + def __repr__(self) -> str: + return ( + f"tcod.event.{self.__class__.__name__}" + f"(type={self.type!r}, which={self.which}, button={self.button}, pressed={self.pressed})" + ) + + def __str__(self) -> str: + prefix = super().__str__().strip("<>") + return f"<{prefix}, button={self.button}, pressed={self.pressed}>" + + +class ControllerDevice(ControllerEvent): + """When a controller is added, removed, or remapped. + + .. versionadded:: Unreleased + """ + + type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] # type: ignore[misc] + + @classmethod + def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: + type = { + lib.SDL_CONTROLLERDEVICEADDED: "CONTROLLERDEVICEADDED", + lib.SDL_CONTROLLERDEVICEREMOVED: "CONTROLLERDEVICEREMOVED", + lib.SDL_CONTROLLERDEVICEREMAPPED: "CONTROLLERDEVICEREMAPPED", + }[sdl_event.type] + return cls(type, sdl_event.cdevice.which) + + class Undefined(Event): """This class is a place holder for SDL events without their own tcod.event class. @@ -1012,6 +1138,12 @@ def __str__(self) -> str: lib.SDL_JOYBUTTONUP: JoystickButton, lib.SDL_JOYDEVICEADDED: JoystickDevice, lib.SDL_JOYDEVICEREMOVED: JoystickDevice, + lib.SDL_CONTROLLERAXISMOTION: ControllerAxis, + lib.SDL_CONTROLLERBUTTONDOWN: ControllerButton, + lib.SDL_CONTROLLERBUTTONUP: ControllerButton, + lib.SDL_CONTROLLERDEVICEADDED: ControllerDevice, + lib.SDL_CONTROLLERDEVICEREMOVED: ControllerDevice, + lib.SDL_CONTROLLERDEVICEREMAPPED: ControllerDevice, } diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 7678e36d..0ef32624 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -148,6 +148,10 @@ def _open(cls, device_index: int) -> Joystick: p = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) return cls(p) + @classmethod + def _from_instance_id(cls, instance_id: int) -> Joystick: + return cls(_check_p(ffi.gc(lib.SDL_JoystickFromInstanceID(instance_id), lib.SDL_JoystickClose))) + def __eq__(self, other: object) -> bool: if isinstance(other, GameController): return self == other.joystick.id @@ -199,6 +203,10 @@ def __init__(self, sdl_controller_p: Any): def _open(cls, joystick_index: int) -> GameController: return cls(_check_p(ffi.gc(lib.SDL_GameControllerOpen(joystick_index), lib.SDL_GameControllerClose))) + @classmethod + def _from_instance_id(cls, instance_id: int) -> GameController: + return cls(_check_p(ffi.gc(lib.SDL_GameControllerFromInstanceID(instance_id), lib.SDL_GameControllerClose))) + def get_button(self, button: ControllerButton) -> bool: """Return True if `button` is currently held.""" return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, button)) @@ -347,9 +355,14 @@ def _touchpad(self) -> bool: return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_TOUCHPAD)) +def init() -> None: + """Initialize SDL's joystick and game controller subsystems.""" + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER) + + def _get_number() -> int: """Return the number of attached joysticks.""" - tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) + init() return _check(lib.SDL_NumJoysticks()) From 8ce4b7e82f0069ea7c0ef5d54648d76fe6830600 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Sep 2022 15:03:39 -0700 Subject: [PATCH 0701/1101] Make joystick class comparing more strict. I'm worried about comparing these across classes. I might relax this compare again in the future. --- tcod/sdl/joystick.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 0ef32624..f9923920 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -153,8 +153,6 @@ def _from_instance_id(cls, instance_id: int) -> Joystick: return cls(_check_p(ffi.gc(lib.SDL_JoystickFromInstanceID(instance_id), lib.SDL_JoystickClose))) def __eq__(self, other: object) -> bool: - if isinstance(other, GameController): - return self == other.joystick.id if isinstance(other, Joystick): return self.id == other.id return NotImplemented @@ -222,8 +220,6 @@ def get_axis(self, axis: ControllerAxis) -> int: def __eq__(self, other: object) -> bool: if isinstance(other, GameController): return self.joystick.id == other.joystick.id - if isinstance(other, Joystick): - return self.joystick.id == other.id return NotImplemented def __hash__(self) -> int: From fd6a961772377582748bba0bac63005d5ce73460 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Sep 2022 17:36:04 -0700 Subject: [PATCH 0702/1101] Hide get all function. This syntax is weird. I don't want it public for now. --- tcod/sdl/joystick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index f9923920..d83aa9c7 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -375,7 +375,7 @@ def get_controllers() -> List[GameController]: return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGameController(i)] -def get_all() -> List[Union[Joystick, GameController]]: +def _get_all() -> List[Union[Joystick, GameController]]: """Return a list of all connected joystick or controller devices. If the joystick has a controller mapping then it is returned as a :any:`GameController`. From a605cfd2354999517b7a14ac39abfe534efbc110 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Sep 2022 17:42:03 -0700 Subject: [PATCH 0703/1101] Update libtcod. --- CHANGELOG.md | 7 +++++++ libtcod | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eef28017..b8454a44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Ported SDL2 joystick handing as `tcod.sdl.joystick`. - New joystick related events. +### Changed +- Using `libtcod 1.22.3`. + +### Fixed +- Fixed double present bug in non-context flush functions. + This was affecting performance and also caused a screen flicker whenever the global fade color was active. + ## [13.7.0] - 2022-08-07 ### Added - You can new use `SDLConsoleRender.atlas` to access the `SDLTilesetAtlas` used to create it. diff --git a/libtcod b/libtcod index 6f0a9bc8..38110b9d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 6f0a9bc8c31f769709299f248681b57a6f9659be +Subproject commit 38110b9d785664392706b3de35450d64f565b37b From f1348726a4a3ebf407aa2861571d6bc73a6ff53c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Sep 2022 18:17:32 -0700 Subject: [PATCH 0704/1101] Bundle SDL 2.24.0. --- CHANGELOG.md | 2 ++ build_sdl.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8454a44..06007d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Changed - Using `libtcod 1.22.3`. +- Bundle `SDL 2.24.0` on Windows and MacOS. ### Fixed - Fixed double present bug in non-context flush functions. This was affecting performance and also caused a screen flicker whenever the global fade color was active. +- Fixed the parsing of SDL 2.24.0 headers on Windows. ## [13.7.0] - 2022-08-07 ### Added diff --git a/build_sdl.py b/build_sdl.py index 1fc6b788..77e46e68 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -22,7 +22,7 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.24.0") # Used to remove excessive newlines in debug outputs. @@ -65,6 +65,10 @@ "SDL_TRUE", # Ignore floating point symbols. "SDL_FLT_EPSILON", + # Conditional config flags which might be missing. + "SDL_VIDEO_RENDER_D3D12", + "SDL_SENSOR_WINDOWS", + "SDL_SENSOR_DUMMY", ) ) From 383eec53ebda6ce2126400a695b9748605ab9488 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Sep 2022 20:34:19 -0700 Subject: [PATCH 0705/1101] Discourage the use of alternative renderers. I'll likely address the rare issues with the other renderers by removing them. --- CHANGELOG.md | 3 +++ tcod/context.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06007d98..2c53986f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Using `libtcod 1.22.3`. - Bundle `SDL 2.24.0` on Windows and MacOS. +### Deprecated +- Renderers other than `tcod.RENDERER_SDL2` are now discouraged. + ### Fixed - Fixed double present bug in non-context flush functions. This was affecting performance and also caused a screen flicker whenever the global fade color was active. diff --git a/tcod/context.py b/tcod/context.py index a112d0d5..aacea6a1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -52,6 +52,7 @@ import os import pickle import sys +import warnings from typing import Any, Iterable, List, Optional, Tuple from typing_extensions import Literal, NoReturn @@ -489,8 +490,8 @@ def new( Providing no size information at all is also acceptable. `renderer` is the desired libtcod renderer to use. - Typical options are :any:`tcod.context.RENDERER_OPENGL2` for a faster - renderer or :any:`tcod.context.RENDERER_SDL2` for a reliable renderer. + The default is :any:`tcod.context.RENDERER_SDL2` which is a fast renderer that runs reliably on all platforms. + If unsure then don't set this parameter. `tileset` is the font/tileset for the new context to render with. The fall-back tileset available from passing None is useful for @@ -525,6 +526,12 @@ def new( """ if renderer is None: renderer = RENDERER_SDL2 + if renderer != RENDERER_SDL2: + warnings.warn( + "In the future all renderers other than tcod.RENDERER_SDL2 may be removed or ignored.", + PendingDeprecationWarning, + stacklevel=2, + ) if sdl_window_flags is None: sdl_window_flags = SDL_WINDOW_RESIZABLE if argv is None: From 26f309b396ff8cd4fe276b163a8137983bda8999 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Sep 2022 23:17:17 -0700 Subject: [PATCH 0706/1101] Prepare 13.8.0 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 34 +++++++++++++++++----------------- tcod/sdl/joystick.py | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c53986f..df42adef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.8.0] - 2022-09-22 ### Added - Ported SDL2 joystick handing as `tcod.sdl.joystick`. - New joystick related events. diff --git a/tcod/event.py b/tcod/event.py index dba3054a..f6783e4b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -784,7 +784,7 @@ def __str__(self) -> str: class JoystickEvent(Event): """A base class for joystick events. - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def __init__(self, type: str, which: int): @@ -809,7 +809,7 @@ def __str__(self) -> str: class JoystickAxis(JoystickEvent): """When a joystick axis changes in value. - .. versionadded:: Unreleased + .. versionadded:: 13.8 .. seealso:: :any:`tcod.sdl.joystick` @@ -843,7 +843,7 @@ def __str__(self) -> str: class JoystickBall(JoystickEvent): """When a joystick ball is moved. - .. versionadded:: Unreleased + .. versionadded:: 13.8 .. seealso:: :any:`tcod.sdl.joystick` @@ -881,7 +881,7 @@ def __str__(self) -> str: class JoystickHat(JoystickEvent): """When a joystick hat changes direction. - .. versionadded:: Unreleased + .. versionadded:: 13.8 .. seealso:: :any:`tcod.sdl.joystick` @@ -914,7 +914,7 @@ def __str__(self) -> str: class JoystickButton(JoystickEvent): """When a joystick button is pressed or released. - .. versionadded:: Unreleased + .. versionadded:: 13.8 Example:: @@ -955,7 +955,7 @@ def __str__(self) -> str: class JoystickDevice(JoystickEvent): """An event for when a joystick is added or removed. - .. versionadded:: Unreleased + .. versionadded:: 13.8 Example:: @@ -984,7 +984,7 @@ def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice: class ControllerEvent(Event): """Base class for controller events. - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def __init__(self, type: str, which: int): @@ -1010,7 +1010,7 @@ def __str__(self) -> str: class ControllerAxis(ControllerEvent): """When a controller axis is moved. - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ type: Final[Literal["CONTROLLERAXISMOTION"]] # type: ignore[misc] @@ -1047,7 +1047,7 @@ def __str__(self) -> str: class ControllerButton(ControllerEvent): """When a controller button is pressed or released. - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ type: Final[Literal["CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP"]] # type: ignore[misc] @@ -1086,7 +1086,7 @@ def __str__(self) -> str: class ControllerDevice(ControllerEvent): """When a controller is added, removed, or remapped. - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] # type: ignore[misc] @@ -1413,37 +1413,37 @@ def ev_windowhittest(self, event: tcod.event.WindowEvent) -> Optional[T]: def ev_joyaxismotion(self, event: tcod.event.JoystickAxis) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joyballmotion(self, event: tcod.event.JoystickBall) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joyhatmotion(self, event: tcod.event.JoystickHat) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joybuttondown(self, event: tcod.event.JoystickButton) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joybuttonup(self, event: tcod.event.JoystickButton) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joydeviceadded(self, event: tcod.event.JoystickDevice) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_(self, event: Any) -> Optional[T]: diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index d83aa9c7..dde74192 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -1,6 +1,6 @@ """SDL Joystick Support -.. versionadded:: Unreleased +.. versionadded:: 13.8 """ from __future__ import annotations From 9ed914be72ca41b3a5292fa9ac89419017d67298 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 11:36:13 -0700 Subject: [PATCH 0707/1101] Fix docs typo. --- tcod/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index f6783e4b..6cb8fe0b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1484,7 +1484,7 @@ def add_watch(callback: _EventCallback) -> _EventCallback: .. warning:: How uncaught exceptions in a callback are handled is not currently defined by tcod. They will likely be handled by :any:`sys.unraisablehook`. - This may be later changed to pass the excpetion to a :any`tcod.event.get` or :any:`tcod.event.wait` call. + This may be later changed to pass the excpetion to a :any:`tcod.event.get` or :any:`tcod.event.wait` call. Args: callback (Callable[[Event], None]): From 7e64ae4abdab621283e699cc1c742c24840765ca Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 12:22:26 -0700 Subject: [PATCH 0708/1101] Add missing event names to EventDispatch. Change EventDispatch so that missing names will warn instead of crashing. --- CHANGELOG.md | 2 ++ tcod/event.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df42adef..9ac747ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- `EventDispatch` was missing new event names. ## [13.8.0] - 2022-09-22 ### Added diff --git a/tcod/event.py b/tcod/event.py index 6cb8fe0b..bc1062db 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1325,7 +1325,11 @@ def dispatch(self, event: Any) -> Optional[T]: stacklevel=2, ) return None - func: Callable[[Any], Optional[T]] = getattr(self, "ev_%s" % (event.type.lower(),)) + func_name = f"ev_{event.type.lower()}" + func: Optional[Callable[[Any], Optional[T]]] = getattr(self, func_name, None) + if func is None: + warnings.warn(f"{func_name} is missing from this EventDispatch object.", RuntimeWarning, stacklevel=2) + return None return func(event) def event_get(self) -> None: @@ -1446,6 +1450,36 @@ def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> Optional[T]: .. versionadded:: 13.8 """ + def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_controllerbuttondown(self, event: tcod.event.ControllerButton) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_controllerbuttonup(self, event: tcod.event.ControllerButton) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_controllerdeviceremoved(self, event: ControllerDevice) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + + def ev_controllerdeviceremapped(self, event: ControllerDevice) -> Optional[T]: + """ + .. versionadded:: Unreleased + """ + def ev_(self, event: Any) -> Optional[T]: pass From d2f3c5b7a16864091f4e32ee5bb28f1695d2e31e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 12:31:10 -0700 Subject: [PATCH 0709/1101] Prepare 13.8.1 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac747ed..5590ccb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [13.8.1] - 2022-09-23 ### Fixed - `EventDispatch` was missing new event names. diff --git a/tcod/event.py b/tcod/event.py index bc1062db..4d5b1536 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1452,32 +1452,32 @@ def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> Optional[T]: def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_controllerbuttondown(self, event: tcod.event.ControllerButton) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_controllerbuttonup(self, event: tcod.event.ControllerButton) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_controllerdeviceremoved(self, event: ControllerDevice) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_controllerdeviceremapped(self, event: ControllerDevice) -> Optional[T]: """ - .. versionadded:: Unreleased + .. versionadded:: 13.8 """ def ev_(self, event: Any) -> Optional[T]: From 52d102f37aeee7594d7f78ee50a571245e6e6c9b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 12:41:55 -0700 Subject: [PATCH 0710/1101] Fix inconsistent type annotations. --- tcod/event.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 4d5b1536..3e9d9a69 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1376,13 +1376,13 @@ def ev_windowexposed(self, event: tcod.event.WindowEvent) -> Optional[T]: This usually means a call to :any:`tcod.console_flush` is necessary. """ - def ev_windowmoved(self, event: "tcod.event.WindowMoved") -> Optional[T]: + def ev_windowmoved(self, event: tcod.event.WindowMoved) -> Optional[T]: """Called when the window is moved.""" - def ev_windowresized(self, event: "tcod.event.WindowResized") -> Optional[T]: + def ev_windowresized(self, event: tcod.event.WindowResized) -> Optional[T]: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: "tcod.event.WindowResized") -> Optional[T]: + def ev_windowsizechanged(self, event: tcod.event.WindowResized) -> Optional[T]: """Called when the system or user changes the size of the window.""" def ev_windowminimized(self, event: tcod.event.WindowEvent) -> Optional[T]: @@ -1470,12 +1470,12 @@ def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> Option .. versionadded:: 13.8 """ - def ev_controllerdeviceremoved(self, event: ControllerDevice) -> Optional[T]: + def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice) -> Optional[T]: """ .. versionadded:: 13.8 """ - def ev_controllerdeviceremapped(self, event: ControllerDevice) -> Optional[T]: + def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice) -> Optional[T]: """ .. versionadded:: 13.8 """ From 6f964d168a0d3ce4639d0895f81b12e9d8f16ca6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 12:42:56 -0700 Subject: [PATCH 0711/1101] Add missing controller events to __all__. --- tcod/event.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tcod/event.py b/tcod/event.py index 3e9d9a69..1ff5cfea 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -2736,6 +2736,10 @@ def __repr__(self) -> str: "JoystickHat", "JoystickButton", "JoystickDevice", + "ControllerEvent", + "ControllerAxis", + "ControllerButton", + "ControllerDevice", "Undefined", "get", "wait", From 6dafab584f60bdf268b5746aa1e7f1c8577c1e23 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Sep 2022 12:45:03 -0700 Subject: [PATCH 0712/1101] Fix docs typo. --- tcod/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index 1ff5cfea..d663858c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -994,7 +994,7 @@ def __init__(self, type: str, which: int): @property def controller(self) -> tcod.sdl.joystick.GameController: - """The :any:`GameController: for this event.""" + """The :any:`GameController` for this event.""" if self.type == "CONTROLLERDEVICEADDED": return tcod.sdl.joystick.GameController._open(self.which) return tcod.sdl.joystick.GameController._from_instance_id(self.which) From 30832182ca6b612e793ea2f73d189d1cf497d7f3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 Sep 2022 12:16:53 -0700 Subject: [PATCH 0713/1101] Fix doc ref linked to the wrong function. --- tcod/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/map.py b/tcod/map.py index 3ce4596b..8ccc3b1f 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -136,7 +136,7 @@ def compute_fov( algorithm (int): Defaults to tcod.FOV_RESTRICTIVE If you already have transparency in a NumPy array then you could use - :any:`tcod.map_compute_fov` instead. + :any:`tcod.map.compute_fov` instead. """ if not (0 <= x < self.width and 0 <= y < self.height): warnings.warn( From de0758e782e1492dd50846c57609ff4631f470d6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 Sep 2022 13:24:21 -0700 Subject: [PATCH 0714/1101] Remove old type ignores for new Mypy version. --- tcod/color.py | 8 ++++---- tcod/console.py | 16 ++++++++-------- tcod/noise.py | 2 +- tests/conftest.py | 8 ++++---- tests/test_console.py | 8 ++++---- tests/test_tcod.py | 6 +++--- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tcod/color.py b/tcod/color.py index 43fe0aab..fce63995 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -31,7 +31,7 @@ def r(self) -> int: """ return int(self[0]) - @r.setter # type: ignore + @r.setter @deprecate("Setting color attributes has been deprecated.") def r(self, value: int) -> None: self[0] = value & 0xFF @@ -45,7 +45,7 @@ def g(self) -> int: """ return int(self[1]) - @g.setter # type: ignore + @g.setter @deprecate("Setting color attributes has been deprecated.") def g(self, value: int) -> None: self[1] = value & 0xFF @@ -59,7 +59,7 @@ def b(self) -> int: """ return int(self[2]) - @b.setter # type: ignore + @b.setter @deprecate("Setting color attributes has been deprecated.") def b(self, value: int) -> None: self[2] = value & 0xFF @@ -102,7 +102,7 @@ def __eq__(self, other: Any) -> bool: return False @deprecate("Use NumPy instead for color math operations.") - def __add__(self, other: Any) -> Color: + def __add__(self, other: Any) -> Color: # type: ignore[override] """Add two colors together. .. deprecated:: 9.2 diff --git a/tcod/console.py b/tcod/console.py index 030f8bd1..c5dfbbff 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -245,7 +245,7 @@ def ch(self) -> NDArray[np.intc]: """ return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] - @property # type: ignore + @property @deprecate("This attribute has been renamed to `rgba`.") def tiles(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -261,7 +261,7 @@ def tiles(self) -> NDArray[Any]: """ return self.rgba - @property # type: ignore + @property @deprecate("This attribute has been renamed to `rgba`.") def buffer(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -273,7 +273,7 @@ def buffer(self) -> NDArray[Any]: """ return self.rgba - @property # type: ignore + @property @deprecate("This attribute has been renamed to `rgb`.") def tiles_rgb(self) -> NDArray[Any]: """An array of this consoles data without the alpha channel. @@ -285,7 +285,7 @@ def tiles_rgb(self) -> NDArray[Any]: """ return self.rgb - @property # type: ignore + @property @deprecate("This attribute has been renamed to `rgb`.") def tiles2(self) -> NDArray[Any]: """This name is deprecated in favour of :any:`rgb`. @@ -347,7 +347,7 @@ def default_bg(self) -> Tuple[int, int, int]: color = self._console_data.back return color.r, color.g, color.b - @default_bg.setter # type: ignore + @default_bg.setter @deprecate("Console defaults have been deprecated.") def default_bg(self, color: Tuple[int, int, int]) -> None: self._console_data.back = color @@ -358,7 +358,7 @@ def default_fg(self) -> Tuple[int, int, int]: color = self._console_data.fore return color.r, color.g, color.b - @default_fg.setter # type: ignore + @default_fg.setter @deprecate("Console defaults have been deprecated.") def default_fg(self, color: Tuple[int, int, int]) -> None: self._console_data.fore = color @@ -368,7 +368,7 @@ def default_bg_blend(self) -> int: """int: The default blending mode.""" return self._console_data.bkgnd_flag # type: ignore - @default_bg_blend.setter # type: ignore + @default_bg_blend.setter @deprecate("Console defaults have been deprecated.") def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @@ -378,7 +378,7 @@ def default_alignment(self) -> int: """int: The default text alignment.""" return self._console_data.alignment # type: ignore - @default_alignment.setter # type: ignore + @default_alignment.setter @deprecate("Console defaults have been deprecated.") def default_alignment(self, value: int) -> None: self._console_data.alignment = value diff --git a/tcod/noise.py b/tcod/noise.py index cae99be0..7328965d 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -180,7 +180,7 @@ def __repr__(self) -> str: def dimensions(self) -> int: return int(self._tdl_noise_c.dimensions) - @property # type: ignore + @property @deprecate("This is a misspelling of 'dimensions'.") def dimentions(self) -> int: return self.dimensions diff --git a/tests/conftest.py b/tests/conftest.py index da22cd44..0b3fb63b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,10 +33,10 @@ def console(session_console: tcod.console.Console) -> tcod.console.Console: tcod.console_flush() with warnings.catch_warnings(): warnings.simplefilter("ignore") - console.default_fg = (255, 255, 255) # type: ignore - console.default_bg = (0, 0, 0) # type: ignore - console.default_bg_blend = tcod.BKGND_SET # type: ignore - console.default_alignment = tcod.LEFT # type: ignore + console.default_fg = (255, 255, 255) + console.default_bg = (0, 0, 0) + console.default_bg_blend = tcod.BKGND_SET + console.default_alignment = tcod.LEFT console.clear() return console diff --git a/tests/test_console.py b/tests/test_console.py index e4f15ef7..790ddcfe 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -44,16 +44,16 @@ def test_array_read_write() -> None: def test_console_defaults() -> None: console = tcod.console.Console(width=12, height=10) - console.default_bg = [2, 3, 4] # type: ignore + console.default_bg = [2, 3, 4] # type: ignore[assignment] assert console.default_bg == (2, 3, 4) - console.default_fg = (4, 5, 6) # type: ignore + console.default_fg = (4, 5, 6) assert console.default_fg == (4, 5, 6) - console.default_bg_blend = tcod.BKGND_ADD # type: ignore + console.default_bg_blend = tcod.BKGND_ADD assert console.default_bg_blend == tcod.BKGND_ADD - console.default_alignment = tcod.RIGHT # type: ignore + console.default_alignment = tcod.RIGHT assert console.default_alignment == tcod.RIGHT diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 88821924..dbe2e89c 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -117,9 +117,9 @@ def test_color_class() -> None: assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) color = tcod.Color() - color.r = 1 # type: ignore - color.g = 2 # type: ignore - color.b = 3 # type: ignore + color.r = 1 + color.g = 2 + color.b = 3 assert color == (1, 2, 3) From 3eb49dfa951ebc0bb53ac960028a41da0bd9ef8b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 30 Sep 2022 04:19:03 -0700 Subject: [PATCH 0715/1101] Set up sponsorship. --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..ac5a1634 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository + +github: HexDecimal From d3419a5b4593c7df1580427fc07616d798c85856 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 3 Oct 2022 22:47:29 -0700 Subject: [PATCH 0716/1101] Fix synchronous audio example. --- tcod/sdl/audio.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 6bee2587..55b080c6 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -11,14 +11,19 @@ Example:: # Synchronous audio example using SDL's low-level API. + import time + import soundfile # pip install soundfile import tcod.sdl.audio device = tcod.sdl.audio.open() # Open the default output device. - sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. + sound, samplerate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile. converted = device.convert(sound, samplerate) # Convert this sample to the format expected by the device. device.queue_audio(converted) # Play audio syncroniously by appending it to the device buffer. + while device.queued_samples: # Wait until device is done playing. + time.sleep(0.001) + Example:: # Asynchronous audio example using BasicMixer. From 33ead26ab6bc8eb5f991216f0a34b4366e3ac961 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 30 Oct 2022 11:31:29 -0700 Subject: [PATCH 0717/1101] Don't run actions twice on pull requests. --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5e305f8c..e0acab53 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -6,6 +6,7 @@ name: Package on: push: pull_request: + types: [opened, reopened] defaults: run: From ba4ac692e174aa9162fa320820f690ae1d5360f3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Dec 2022 19:04:58 -0800 Subject: [PATCH 0718/1101] Update libtcod and SDL versions. Apply new fixes from libtcod and warn for changes in renderers. Update parser to handle a more complex deprecation qualifier. Remove references to GLAD. Update Numpy typing. --- CHANGELOG.md | 8 ++++++++ build_libtcod.py | 4 +++- build_sdl.py | 2 +- docs/tcod/charmap-reference.rst | 2 +- libtcod | 2 +- tcod/context.py | 14 ++++++-------- tcod/event.py | 8 +++++++- tcod/event_constants.py | 18 +++++++++++++++--- tcod/tileset.py | 5 ++++- 9 files changed, 46 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5590ccb3..e86b05e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- Using `libtcod 1.23.1`. +- Bundle `SDL 2.26.0` on Windows and MacOS. +- Code Page 437: Character 0x7F is now assigned to 0x2302 (HOUSE). +- Forced all renderers to ``RENDERER_SDL2`` to fix rare graphical artifacts with OpenGL. + +### Deprecated +- The `renderer` parameter of new contexts is now deprecated. ## [13.8.1] - 2022-09-23 ### Fixed diff --git a/build_libtcod.py b/build_libtcod.py index f84d7736..e8d800cf 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -29,6 +29,7 @@ RE_INCLUDE = re.compile(r'#include "([^"]*)"') RE_TAGS = re.compile( r"TCODLIB_C?API|TCOD_PUBLIC|TCOD_NODISCARD|TCOD_DEPRECATED_NOMESSAGE|TCOD_DEPRECATED_ENUM" + r"|(TCOD_DEPRECATED\(\".*?\"\))" r"|(TCOD_DEPRECATED|TCODLIB_FORMAT)\([^)]*\)|__restrict" ) RE_VAFUNC = re.compile(r"^[^;]*\([^;]*va_list.*\);", re.MULTILINE) @@ -151,7 +152,6 @@ def walk_sources(directory: str) -> Iterator[str]: sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") sources += ["libtcod/src/vendor/stb.c"] -sources += ["libtcod/src/vendor/glad.c"] sources += ["libtcod/src/vendor/lodepng.c"] sources += ["libtcod/src/vendor/utf8proc/utf8proc.c"] sources += glob.glob("libtcod/src/vendor/zlib/*.c") @@ -265,6 +265,8 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: names = [] lookup = [] for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): + if name == "KMOD_RESERVED": + continue all_names.append(name) names.append(f"{name} = {value}") lookup.append(f'{value}: "{name}"') diff --git a/build_sdl.py b/build_sdl.py index 77e46e68..0bbe5241 100644 --- a/build_sdl.py +++ b/build_sdl.py @@ -22,7 +22,7 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.24.0") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.26.0") # Used to remove excessive newlines in debug outputs. diff --git a/docs/tcod/charmap-reference.rst b/docs/tcod/charmap-reference.rst index 0fa8150a..3ad42b16 100644 --- a/docs/tcod/charmap-reference.rst +++ b/docs/tcod/charmap-reference.rst @@ -173,7 +173,7 @@ https://en.wikipedia.org/wiki/Code_page_437 124 0x7C \'\|\' VERTICAL LINE 125 0x7D \'}\' RIGHT CURLY BRACKET 126 0x7E \'~\' TILDE - 127 0x7F \'\\x7f\' + 127 0x2302 \'⌂\' HOUSE 128 0xC7 \'Ç\' LATIN CAPITAL LETTER C WITH CEDILLA 129 0xFC \'ü\' LATIN SMALL LETTER U WITH DIAERESIS 130 0xE9 \'é\' LATIN SMALL LETTER E WITH ACUTE diff --git a/libtcod b/libtcod index 38110b9d..168ab8ce 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 38110b9d785664392706b3de35450d64f565b37b +Subproject commit 168ab8ce054f84087e04595a54ad02093c31a5a9 diff --git a/tcod/context.py b/tcod/context.py index aacea6a1..c2e57bee 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -489,9 +489,7 @@ def new( Providing no size information at all is also acceptable. - `renderer` is the desired libtcod renderer to use. - The default is :any:`tcod.context.RENDERER_SDL2` which is a fast renderer that runs reliably on all platforms. - If unsure then don't set this parameter. + `renderer` now does nothing and should not be set. It may be removed in the future. `tileset` is the font/tileset for the new context to render with. The fall-back tileset available from passing None is useful for @@ -524,14 +522,14 @@ def new( .. versionchanged:: 13.2 Added the `console` parameter. """ - if renderer is None: - renderer = RENDERER_SDL2 - if renderer != RENDERER_SDL2: + if renderer is not None: warnings.warn( - "In the future all renderers other than tcod.RENDERER_SDL2 may be removed or ignored.", - PendingDeprecationWarning, + "The renderer parameter was deprecated and will likely be removed in a future version of libtcod. " + "Remove the renderer parameter to fix this warning.", + FutureWarning, stacklevel=2, ) + renderer = RENDERER_SDL2 if sdl_window_flags is None: sdl_window_flags = SDL_WINDOW_RESIZABLE if argv is None: diff --git a/tcod/event.py b/tcod/event.py index d663858c..7be7eebd 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1583,7 +1583,7 @@ def get_keyboard_state() -> NDArray[np.bool_]: numkeys = ffi.new("int[1]") keyboard_state = lib.SDL_GetKeyboardState(numkeys) out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=np.bool_) - out.flags["WRITEABLE"] = False # type: ignore[index] # This buffer is supposed to be const. + out.flags["WRITEABLE"] = False # This buffer is supposed to be const. return out @@ -2094,6 +2094,8 @@ class Scancode(enum.IntEnum): SLEEP = 282 APP1 = 283 APP2 = 284 + AUDIOREWIND = 285 + AUDIOFASTFORWARD = 286 # --- end --- @property @@ -2639,6 +2641,10 @@ class KeySym(enum.IntEnum): KBDILLUMUP = 1073742104 EJECT = 1073742105 SLEEP = 1073742106 + APP1 = 1073742107 + APP2 = 1073742108 + AUDIOREWIND = 1073742109 + AUDIOFASTFORWARD = 1073742110 # --- end --- @property diff --git a/tcod/event_constants.py b/tcod/event_constants.py index 6d6069be..aca5d5ec 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -245,6 +245,8 @@ SCANCODE_SLEEP = 282 SCANCODE_APP1 = 283 SCANCODE_APP2 = 284 +SCANCODE_AUDIOREWIND = 285 +SCANCODE_AUDIOFASTFORWARD = 286 # --- SDL keyboard symbols --- K_UNKNOWN = 0 @@ -484,6 +486,10 @@ K_KBDILLUMUP = 1073742104 K_EJECT = 1073742105 K_SLEEP = 1073742106 +K_APP1 = 1073742107 +K_APP2 = 1073742108 +K_AUDIOREWIND = 1073742109 +K_AUDIOFASTFORWARD = 1073742110 # --- SDL keyboard modifiers --- KMOD_NONE = 0 @@ -502,7 +508,7 @@ KMOD_NUM = 4096 KMOD_CAPS = 8192 KMOD_MODE = 16384 -KMOD_RESERVED = 32768 +KMOD_SCROLL = 32768 _REVERSE_MOD_TABLE = { 0: "KMOD_NONE", 1: "KMOD_LSHIFT", @@ -520,7 +526,7 @@ 4096: "KMOD_NUM", 8192: "KMOD_CAPS", 16384: "KMOD_MODE", - 32768: "KMOD_RESERVED", + 32768: "KMOD_SCROLL", } # --- SDL wheel --- @@ -775,6 +781,8 @@ "SCANCODE_SLEEP", "SCANCODE_APP1", "SCANCODE_APP2", + "SCANCODE_AUDIOREWIND", + "SCANCODE_AUDIOFASTFORWARD", "K_UNKNOWN", "K_BACKSPACE", "K_TAB", @@ -1012,6 +1020,10 @@ "K_KBDILLUMUP", "K_EJECT", "K_SLEEP", + "K_APP1", + "K_APP2", + "K_AUDIOREWIND", + "K_AUDIOFASTFORWARD", "KMOD_NONE", "KMOD_LSHIFT", "KMOD_RSHIFT", @@ -1028,7 +1040,7 @@ "KMOD_NUM", "KMOD_CAPS", "KMOD_MODE", - "KMOD_RESERVED", + "KMOD_SCROLL", "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", "MOUSEWHEEL", diff --git a/tcod/tileset.py b/tcod/tileset.py index 14767e5b..1e72559f 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -584,7 +584,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: 0x007C, 0x007D, 0x007E, - 0x007F, + 0x2302, 0x00C7, 0x00FC, 0x00E9, @@ -719,6 +719,9 @@ def procedural_block_elements(*, tileset: Tileset) -> None: See :ref:`code-page-437` for more info and a table of glyphs. .. versionadded:: 11.12 + +.. versionchanged:: Unreleased + Character at index ``0x7F`` was changed from value ``0x7F`` to the HOUSE ``⌂`` glyph ``0x2302``. """ CHARMAP_TCOD = [ From eccad15a7ec558d140296ab059ecf7cfd0b27c4e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 Dec 2022 20:29:32 -0800 Subject: [PATCH 0719/1101] Fix mouse tile coords in samples when context.present is skipped. --- examples/samples_tcod.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f92d0151..f14c9c4b 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -""" -This code demonstrates various usages of python-tcod. -""" +"""This code demonstrates various usages of python-tcod.""" # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ @@ -1412,7 +1410,7 @@ def init_context(renderer: int) -> None: context = tcod.context.new( columns=root_console.width, rows=root_console.height, - title=f"python-tcod samples" f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})", + title=f"python-tcod samples (python-tcod {tcod.__version__}, libtcod {libtcod_version})", renderer=renderer, vsync=False, # VSync turned off since this is for benchmarking. tileset=tileset, @@ -1488,7 +1486,19 @@ def handle_time() -> None: def handle_events() -> None: for event in tcod.event.get(): - context.convert_event(event) + if context.sdl_renderer: + # Manual handing of tile coordinates since context.present is skipped. + if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): + event.tile = tcod.event.Point(event.pixel.x // tileset.tile_width, event.pixel.y // tileset.tile_height) + if isinstance(event, tcod.event.MouseMotion): + prev_tile = ( + (event.pixel[0] - event.pixel_motion[0]) // tileset.tile_width, + (event.pixel[1] - event.pixel_motion[1]) // tileset.tile_height, + ) + event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) + else: + context.convert_event(event) + SAMPLES[cur_sample].dispatch(event) if isinstance(event, tcod.event.Quit): raise SystemExit() From 5a661306ee75c3a4dd6a8120e50ff8bd2e3a7fcc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Dec 2022 13:45:54 -0800 Subject: [PATCH 0720/1101] Various doc and spelling fixes. --- .vscode/settings.json | 99 +++++++++++++++++++++++++++++++++++++++++++ tcod/_internal.py | 17 +++----- tcod/bsp.py | 9 ++-- tcod/color.py | 3 +- tcod/console.py | 21 +++++---- tcod/context.py | 25 ++++++----- tcod/event.py | 14 +++--- tcod/libtcodpy.py | 37 ++++++++-------- tcod/loader.py | 3 +- tcod/los.py | 3 +- tcod/noise.py | 4 +- tcod/path.py | 32 +++++++------- tcod/random.py | 2 +- tcod/tileset.py | 8 ++-- 14 files changed, 181 insertions(+), 96 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 76f51720..e49af1b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,12 +20,14 @@ }, "cSpell.words": [ "ADDA", + "ADDALPHA", "addressof", "addsub", "addx", "addy", "algo", "ALPH", + "ALTERASE", "arange", "ARCHS", "asarray", @@ -34,7 +36,13 @@ "astype", "atexit", "AUDIOCVT", + "AUDIOFASTFORWARD", + "AUDIOMUTE", + "AUDIONEXT", + "AUDIOPLAY", "AUDIOPREV", + "AUDIOREWIND", + "AUDIOSTOP", "autoclass", "autofunction", "autogenerated", @@ -45,17 +53,24 @@ "bezier", "bfade", "bgcolor", + "bitmask", "BKGND", "Blit", "blits", "blitting", + "BORDERLESS", "bresenham", "Bresenham", + "BRIGHTNESSDOWN", "BRIGHTNESSUP", "bysource", "caeldera", + "CAPSLOCK", + "caxis", + "cbutton", "ccoef", "cdef", + "cdevice", "cffi", "cflags", "CFLAGS", @@ -63,6 +78,8 @@ "Chebyshev", "cibuildwheel", "CIBW", + "CLEARAGAIN", + "CLEARENTRY", "CMWC", "Codacy", "Codecov", @@ -71,15 +88,28 @@ "coef", "Coef", "COLCTRL", + "COMPILEDVERSION", "consolas", + "contextdata", + "CONTROLLERAXISMOTION", + "CONTROLLERBUTTONDOWN", + "CONTROLLERBUTTONUP", + "CONTROLLERDEVICEADDED", + "CONTROLLERDEVICEREMAPPED", + "CONTROLLERDEVICEREMOVED", "cplusplus", "CPLUSPLUS", "CRSEL", "ctypes", + "CURRENCYSUBUNIT", + "CURRENCYUNIT", "currentmodule", "datas", + "DBLAMPERSAND", + "DBLVERTICALBAR", "dcost", "DCROSS", + "DECIMALSEPARATOR", "dejavu", "delocate", "deque", @@ -87,6 +117,7 @@ "DESATURATED", "devel", "DHLINE", + "DISPLAYSWITCH", "dlopen", "Doryen", "DTEEE", @@ -101,6 +132,7 @@ "epub", "EQUALSAS", "errorvf", + "EXCLAM", "EXSEL", "favicon", "ffade", @@ -110,6 +142,7 @@ "fmean", "fontx", "fonty", + "frombuffer", "fullscreen", "fwidth", "genindex", @@ -122,6 +155,7 @@ "heapify", "heightmap", "hflip", + "HIGHDPI", "hillclimb", "hline", "horiz", @@ -138,17 +172,32 @@ "INROW", "isinstance", "isort", + "itemsize", "itleref", "ivar", + "jaxis", + "jball", + "jbutton", + "jdevice", + "jhat", "jice", "jieba", + "JOYAXISMOTION", + "JOYBALLMOTION", + "JOYBUTTONDOWN", + "JOYBUTTONUP", + "JOYDEVICEADDED", + "JOYDEVICEREMOVED", + "JOYHATMOTION", "Kaczor", "KBDILLUMDOWN", "KBDILLUMTOGGLE", "KBDILLUMUP", "keychar", + "KEYDOWN", "keyname", "keypress", + "keysym", "KEYUP", "KMOD", "KPADD", @@ -162,6 +211,8 @@ "lbutton", "LCTRL", "LDFLAGS", + "LEFTBRACE", + "LEFTBRACKET", "LEFTPAREN", "lerp", "LGUI", @@ -180,8 +231,17 @@ "maxarray", "maxdepth", "mbutton", + "MEDIASELECT", "MEIPASS", + "MEMADD", + "MEMCLEAR", + "MEMDIVIDE", + "MEMMULTIPLY", + "MEMRECALL", + "MEMSTORE", + "MEMSUBTRACT", "mersenne", + "meshgrid", "mgrid", "milli", "minmax", @@ -189,7 +249,10 @@ "mipmaps", "MMASK", "modindex", + "MOUSEBUTTONDOWN", "MOUSEBUTTONUP", + "MOUSEMOTION", + "MOUSESTATE", "msilib", "MSVC", "msvcr", @@ -200,6 +263,7 @@ "namegen", "ndarray", "ndim", + "newaxis", "newh", "neww", "noarchive", @@ -217,16 +281,21 @@ "onefile", "OPENGL", "OPER", + "PAGEDOWN", "PAGEUP", "pathfinding", "pathlib", "pcpp", + "PERLIN", "PILCROW", "pilmode", "PIXELFORMAT", + "PLUSMINUS", "PRESENTVSYNC", "PRINTF", "printn", + "PRINTSCREEN", + "propname", "pycall", "pycparser", "pyinstaller", @@ -238,6 +307,7 @@ "PYTHONOPTIMIZE", "Pyup", "quickstart", + "QUOTEDBL", "RALT", "randomizer", "rbutton", @@ -249,6 +319,8 @@ "repr", "rgba", "RGUI", + "RIGHTBRACE", + "RIGHTBRACKET", "RIGHTPAREN", "RMASK", "rmeta", @@ -261,13 +333,16 @@ "scalex", "scaley", "Scancode", + "scancodes", "scipy", "scoef", + "SCROLLLOCK", "sdist", "SDL's", "SDLCALL", "sdlevent", "SDLK", + "seealso", "servernum", "setuptools", "SHADOWCAST", @@ -291,32 +366,56 @@ "TEEW", "TEXTUREACCESS", "thirdparty", + "THOUSANDSSEPARATOR", "Tileset", "tilesets", "tilesheet", + "tilesheets", "timeit", "toctree", "todos", "tolist", "tris", "truetype", + "typestr", "undoc", "Unifont", "unraisablehook", "unraiseable", "upscaling", + "userdata", "VAFUNC", + "VALUELIST", "vcoef", "venv", "vertic", + "VERTICALBAR", "vflip", "vline", + "VOLUMEDOWN", "VOLUMEUP", "voronoi", "VRAM", "vsync", "WASD", + "windowclose", + "windowenter", + "WINDOWEVENT", + "windowexposed", + "windowfocusgained", + "windowfocuslost", + "windowhidden", + "windowhittest", + "windowleave", + "windowmaximized", + "windowminimized", + "windowmoved", + "WINDOWPOS", "WINDOWRESIZED", + "windowrestored", + "windowshown", + "windowsizechanged", + "windowtakefocus", "xdst", "xrel", "xvfb", diff --git a/tcod/_internal.py b/tcod/_internal.py index bde088e9..08730332 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,5 +1,4 @@ -"""This module internal helper functions used by the rest of the library. -""" +"""This module internal helper functions used by the rest of the library.""" from __future__ import annotations import functools @@ -39,8 +38,7 @@ def pending_deprecate( category: Any = PendingDeprecationWarning, stacklevel: int = 0, ) -> Callable[[F], F]: - """Like deprecate, but the default parameters are filled out for a generic - pending deprecation warning.""" + """Like deprecate, but the default parameters are filled out for a generic pending deprecation warning.""" return deprecate(message, category, stacklevel) @@ -88,7 +86,7 @@ def _unpack_char_p(char_p: Any) -> str: def _int(int_or_str: Any) -> int: - "return an integer where a single character string may be expected" + """Return an integer where a single character string may be expected.""" if isinstance(int_or_str, str): return ord(int_or_str) if isinstance(int_or_str, bytes): @@ -125,8 +123,9 @@ def _fmt(string: str, stacklevel: int = 2) -> bytes: class _PropagateException: - """Context manager designed to propagate exceptions outside of a cffi - callback context. Normally cffi suppresses the exception. + """Context manager designed to propagate exceptions outside of a cffi callback context. + + Normally cffi suppresses the exception. When propagate is called this class will hold onto the error until the control flow leaves the context, then the error will be raised. @@ -148,9 +147,7 @@ def propagate(self, *exc_info: Any) -> None: self.exc_info = exc_info def __enter__(self) -> Callable[[Any], None]: - """Once in context, only the propagate call is needed to use this - class effectively. - """ + """Once in context, only the propagate call is needed to use this class effectively.""" return self.propagate def __exit__(self, type: Any, value: Any, traceback: Any) -> None: diff --git a/tcod/bsp.py b/tcod/bsp.py index 42f82460..23c54ef0 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -1,4 +1,4 @@ -""" +r""" The following example shows how to traverse the BSP tree using Python. This assumes `create_room` and `connect_rooms` will be replaced by custom code. @@ -19,7 +19,7 @@ for node in bsp.pre_order(): if node.children: node1, node2 = node.children - print('Connect the rooms:\\n%s\\n%s' % (node1, node2)) + print('Connect the rooms:\n%s\n%s' % (node1, node2)) else: print('Dig a room for %s.' % node) """ @@ -33,8 +33,7 @@ class BSP(object): - """A binary space partitioning tree which can be used for simple dungeon - generation. + """A binary space partitioning tree which can be used for simple dungeon generation. Attributes: x (int): Rectangle left coordinate. @@ -242,7 +241,7 @@ def inverted_level_order(self) -> Iterator[BSP]: yield from levels.pop() def contains(self, x: int, y: int) -> bool: - """Returns True if this node contains these coordinates. + """Return True if this node contains these coordinates. Args: x (int): X position to check. diff --git a/tcod/color.py b/tcod/color.py index fce63995..470d7ac7 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -11,7 +11,7 @@ class Color(List[int]): - """ + """Old-style libtcodpy color class. Args: r (int): Red value, from 0 to 255. @@ -66,7 +66,6 @@ def b(self, value: int) -> None: @classmethod def _new_from_cdata(cls, cdata: Any) -> Color: - """new in libtcod-cffi""" return cls(cdata.r, cdata.g, cdata.b) def __getitem__(self, index: Any) -> Any: diff --git a/tcod/console.py b/tcod/console.py index c5dfbbff..81f209d9 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -45,8 +45,7 @@ def _fmt(string: str) -> bytes: class Console: - """A console object containing a grid of characters with - foreground/background colors. + """A console object containing a grid of characters with foreground/background colors. `width` and `height` are the size of the console (in tiles.) @@ -771,7 +770,7 @@ def blit( None, or a (red, green, blue) tuple with values of 0-255. .. versionchanged:: 4.0 - Parameters were rearraged and made optional. + Parameters were rearranged and made optional. Previously they were: `(x, y, width, height, dest, dest_x, dest_y, *)` @@ -841,7 +840,7 @@ def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: self._key_color = color def __enter__(self) -> Console: - """Returns this console in a managed context. + """Return this console in a managed context. When the root console is used as a context, the graphical window will close once the context is left as if :any:`tcod.console_delete` was @@ -870,14 +869,14 @@ def close(self) -> None: lib.TCOD_console_delete(self.console_c) def __exit__(self, *args: Any) -> None: - """Closes the graphical window on exit. + """Close the graphical window on exit. Some tcod functions may have undefined behavior after this point. """ self.close() def __bool__(self) -> bool: - """Returns False if this is the root console. + """Return False if this is the root console. This mimics libtcodpy behavior. """ @@ -939,14 +938,14 @@ def print( bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, ) -> None: - """Print a string on a console with manual line breaks. + r"""Print a string on a console with manual line breaks. `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. `string` is a Unicode string which may include color control characters. Strings which are too long will be truncated until the - next newline character ``"\\n"``. + next newline character ``"\n"``. `fg` and `bg` are the foreground text color and background tile color respectfully. This is a 3-item tuple with (r, g, b) color values from @@ -1051,7 +1050,7 @@ def draw_frame( *, decoration: Union[str, Tuple[int, int, int, int, int, int, int, int, int]] = "┌─┐│ │└─┘", ) -> None: - """Draw a framed rectangle with an optional title. + r"""Draw a framed rectangle with an optional title. `x` and `y` are the starting tile, with ``0,0`` as the upper-left corner of the console. @@ -1104,9 +1103,9 @@ def draw_frame( >>> console.print_box(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=tcod.CENTER) 1 >>> print(console) - <┌─┐╔═╗123/-\\ + <┌─┐╔═╗123/-\ │ │║ ║456| | - └─┘╚═╝789\\-/ + └─┘╚═╝789\-/ ┌─ Title ──┐ │ │ └─┤Lower├──┘> diff --git a/tcod/context.py b/tcod/context.py index c2e57bee..a77562e4 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -168,7 +168,7 @@ class Context: """ def __init__(self, context_p: Any): - """Creates a context from a cffi pointer.""" + """Create a context from a cffi pointer.""" self._context_p = context_p @classmethod @@ -176,7 +176,7 @@ def _claim(cls, context_p: Any) -> Context: return cls(ffi.gc(context_p, lib.TCOD_context_delete)) def __enter__(self) -> Context: - """This context can be used as a context manager.""" + """Enter this context which will close on exiting.""" return self def close(self) -> None: @@ -189,7 +189,7 @@ def close(self) -> None: del self._context_p def __exit__(self, *args: Any) -> None: - """The libtcod context is closed as this context manager exits.""" + """Automatically close on the context on exit.""" self.close() def present( @@ -335,7 +335,7 @@ def new_console( console = context.new_console(magnification=scale) # This printed output will wrap if the window is shrunk. console.print_box(0, 0, console.width, console.height, "Hello world") - # Use integer_scaling to prevent subpixel distorsion. + # Use integer_scaling to prevent subpixel distortion. # This may add padding around the rendered console. context.present(console, integer_scaling=True) for event in tcod.event.wait(): @@ -353,13 +353,11 @@ def new_console( return tcod.console.Console(width, height, order=order) def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> Tuple[int, int]: - """Return the recommended (columns, rows) of a console for this - context. + """Return the recommended (columns, rows) of a console for this context. `min_columns`, `min_rows` are the lowest values which will be returned. - If result is only used to create a new console then you may want to - call :any:`Context.new_console` instead. + If result is only used to create a new console then you may want to call :any:`Context.new_console` instead. """ with ffi.new("int[2]") as size: _check(lib.TCOD_context_recommended_console_size(self._context_p, 1.0, size, size + 1)) @@ -407,7 +405,7 @@ def sdl_window(self) -> Optional[tcod.sdl.video.Window]: Example:: import tcod - improt tcod.sdl.video + import tcod.sdl.video def toggle_fullscreen(context: tcod.context.Context) -> None: """Toggle a context window between fullscreen and windowed modes.""" @@ -445,15 +443,16 @@ def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]: return tcod.render.SDLTilesetAtlas._from_ref(context_data.renderer, context_data.atlas) def __reduce__(self) -> NoReturn: - """Contexts can not be pickled, so this class will raise - :class:`pickle.PicklingError`. - """ + """Contexts can not be pickled, so this class will raise :class:`pickle.PicklingError`.""" raise pickle.PicklingError("Python-tcod contexts can not be pickled.") @ffi.def_extern() # type: ignore def _pycall_cli_output(catch_reference: Any, output: Any) -> None: - """Callback for the libtcod context CLI. Catches the CLI output.""" + """Callback for the libtcod context CLI. + + Catches the CLI output. + """ catch: List[str] = ffi.from_handle(catch_reference) catch.append(ffi.string(output).decode("utf-8")) diff --git a/tcod/event.py b/tcod/event.py index 7be7eebd..dbb8e6b0 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -185,7 +185,7 @@ def _init_sdl_video() -> None: class Modifier(enum.IntFlag): - """Keyboard modifier flags, a bitfield of held modifier keys. + """Keyboard modifier flags, a bit-field of held modifier keys. Use `bitwise and` to check if a modifier key is held. @@ -195,8 +195,6 @@ class Modifier(enum.IntFlag): Example:: >>> mod = tcod.event.Modifier(4098) - >>> mod - >>> mod & tcod.event.Modifier.SHIFT # Check if any shift key is held. >>> mod & tcod.event.Modifier.LSHIFT # Check if left shift key is held. @@ -1518,7 +1516,7 @@ def add_watch(callback: _EventCallback) -> _EventCallback: .. warning:: How uncaught exceptions in a callback are handled is not currently defined by tcod. They will likely be handled by :any:`sys.unraisablehook`. - This may be later changed to pass the excpetion to a :any:`tcod.event.get` or :any:`tcod.event.wait` call. + This may be later changed to pass the exception to a :any:`tcod.event.get` or :any:`tcod.event.wait` call. Args: callback (Callable[[Event], None]): @@ -1544,7 +1542,7 @@ def handle_events(event: tcod.event.Event) -> None: def remove_watch(callback: Callable[[Event], None]) -> None: - """Remove a callback as an event wacher. + """Remove a callback as an event watcher. Args: callback (Callable[[Event], None]): @@ -1580,9 +1578,9 @@ def get_keyboard_state() -> NDArray[np.bool_]: .. versionadded:: 12.3 """ - numkeys = ffi.new("int[1]") - keyboard_state = lib.SDL_GetKeyboardState(numkeys) - out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : numkeys[0]]), dtype=np.bool_) + num_keys = ffi.new("int[1]") + keyboard_state = lib.SDL_GetKeyboardState(num_keys) + out: NDArray[np.bool_] = np.frombuffer(ffi.buffer(keyboard_state[0 : num_keys[0]]), dtype=np.bool_) out.flags["WRITEABLE"] = False # This buffer is supposed to be const. return out diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 626a05b6..1271a3c1 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1,5 +1,4 @@ -"""This module handles backward compatibility with the ctypes libtcodpy module. -""" +"""This module handles backward compatibility with the ctypes libtcodpy module.""" from __future__ import annotations import atexit @@ -72,8 +71,7 @@ def BKGND_ADDALPHA(a: int) -> int: class ConsoleBuffer(object): - """Simple console that allows direct (fast) access to cells. simplifies - use of the "fill" functions. + """Simple console that allows direct (fast) access to cells. Simplifies use of the "fill" functions. .. deprecated:: 6.0 Console array attributes perform better than this class. @@ -102,8 +100,9 @@ def __init__( fore_b: int = 0, char: str = " ", ) -> None: - """initialize with given width and height. values to fill the buffer - are optional, defaults to black with no characters. + """Initialize with given width and height. + + Values to fill the buffer are optional, defaults to black with no characters. """ warnings.warn( "Console array attributes perform better than this class.", @@ -124,8 +123,9 @@ def clear( fore_b: int = 0, char: str = " ", ) -> None: - """Clears the console. Values to fill it with are optional, defaults - to black with no characters. + """Clear the console. + + Values to fill it with are optional, defaults to black with no characters. Args: back_r (int): Red background color, from 0 to 255. @@ -146,7 +146,7 @@ def clear( self.char = [ord(char)] * n def copy(self) -> ConsoleBuffer: - """Returns a copy of this ConsoleBuffer. + """Return a copy of this ConsoleBuffer. Returns: ConsoleBuffer: A new ConsoleBuffer copy. @@ -332,7 +332,7 @@ def __repr__(self) -> str: class Key(_CDataWrapper): - """Key Event instance + r"""Key Event instance Attributes: vk (int): TCOD_keycode_t key code @@ -631,9 +631,7 @@ def _bsp_traverse( callback: Callable[[tcod.bsp.BSP, Any], None], userData: Any, ) -> None: - """pack callback into a handle for use with the callback - _pycall_bsp_callback - """ + """Pack callback into a handle for use with the callback _pycall_bsp_callback.""" for node in node_iter: callback(node, userData) @@ -1057,12 +1055,11 @@ def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX @deprecate("Setup fonts using the tcod.tileset module.") def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: - """Remap a string of codes to a contiguous set of tiles. + r"""Remap a string of codes to a contiguous set of tiles. Args: s (AnyStr): A string of character codes to map to new values. - The null character `'\\x00'` will prematurely end this - function. + Any null character `'\x00'` will prematurely end the printed text. fontCharX (int): The starting X tile coordinate on the loaded tileset. 0 is the leftmost tile. fontCharY (int): The starting Y tile coordinate on the loaded tileset. @@ -3144,7 +3141,7 @@ def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], b A callback which takes x and y parameters and returns bool. Returns: - bool: False if the callback cancels the line interation by + bool: False if the callback cancels the line interaction by returning False or None, otherwise True. .. deprecated:: 2.0 @@ -3947,7 +3944,7 @@ def sys_elapsed_milli() -> int: """Get number of milliseconds since the start of the program. Returns: - int: Time since the progeam has started in milliseconds. + int: Time since the program has started in milliseconds. .. deprecated:: 2.0 Use Python's :mod:`time` module instead. @@ -3960,7 +3957,7 @@ def sys_elapsed_seconds() -> float: """Get number of seconds since the start of the program. Returns: - float: Time since the progeam has started in seconds. + float: Time since the program has started in seconds. .. deprecated:: 2.0 Use Python's :mod:`time` module instead. @@ -4097,7 +4094,7 @@ def sys_update_char( @deprecate("This function is not supported if contexts are being used.") def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: - """Register a custom randering function with libtcod. + """Register a custom rendering function with libtcod. Note: This callback will only be called by the SDL renderer. diff --git a/tcod/loader.py b/tcod/loader.py index 645246ba..13efe66d 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -1,5 +1,4 @@ -"""This module handles loading of the libtcod cffi API. -""" +"""This module handles loading of the libtcod cffi API.""" from __future__ import annotations import os diff --git a/tcod/los.py b/tcod/los.py index ce48d23b..9cbd87da 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -1,5 +1,4 @@ -"""This modules holds functions for NumPy-based line of sight algorithms. -""" +"""This modules holds functions for NumPy-based line of sight algorithms.""" from __future__ import annotations from typing import Any, Tuple diff --git a/tcod/noise.py b/tcod/noise.py index 7328965d..92676c8a 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -181,7 +181,7 @@ def dimensions(self) -> int: return int(self._tdl_noise_c.dimensions) @property - @deprecate("This is a misspelling of 'dimensions'.") + @deprecate("This is a misspelling of 'dimensions'.", FutureWarning) def dimentions(self) -> int: return self.dimensions @@ -416,7 +416,7 @@ def grid( origin: Optional[Tuple[int, ...]] = None, indexing: Literal["ij", "xy"] = "xy", ) -> Tuple[NDArray[Any], ...]: - """A helper function for generating a grid of noise samples. + """Helper function for generating a grid of noise samples. `shape` is the shape of the returned mesh grid. This can be any number of dimensions, but :class:`Noise` classes only support up to 4. diff --git a/tcod/path.py b/tcod/path.py index d581e7da..d3c9a514 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -33,7 +33,7 @@ @ffi.def_extern() # type: ignore def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: - """libtcodpy style callback, needs to preserve the old userData issue.""" + """Libtcodpy style callback, needs to preserve the old userData issue.""" func, userData = ffi.from_handle(handle) return func(x1, y1, x2, y2, userData) # type: ignore @@ -56,7 +56,7 @@ def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, handle: Any) -> f return ffi.from_handle(handle)(x2, y2) # type: ignore -def _get_pathcost_func( +def _get_path_cost_func( name: str, ) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" @@ -80,7 +80,7 @@ def __init__(self, userdata: Any, shape: Tuple[int, int]) -> None: self.shape = shape def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: - """Return (C callback, userdata handle, shape)""" + """Return (C callback, userdata handle, shape).""" return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape def __repr__(self) -> str: @@ -123,14 +123,14 @@ class NodeCostArray(np.ndarray): # type: ignore """ _C_ARRAY_CALLBACKS = { - np.float32: ("float*", _get_pathcost_func("PathCostArrayFloat32")), - np.bool_: ("int8_t*", _get_pathcost_func("PathCostArrayInt8")), - np.int8: ("int8_t*", _get_pathcost_func("PathCostArrayInt8")), - np.uint8: ("uint8_t*", _get_pathcost_func("PathCostArrayUInt8")), - np.int16: ("int16_t*", _get_pathcost_func("PathCostArrayInt16")), - np.uint16: ("uint16_t*", _get_pathcost_func("PathCostArrayUInt16")), - np.int32: ("int32_t*", _get_pathcost_func("PathCostArrayInt32")), - np.uint32: ("uint32_t*", _get_pathcost_func("PathCostArrayUInt32")), + np.float32: ("float*", _get_path_cost_func("PathCostArrayFloat32")), + np.bool_: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")), + np.int8: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")), + np.uint8: ("uint8_t*", _get_path_cost_func("PathCostArrayUInt8")), + np.int16: ("int16_t*", _get_path_cost_func("PathCostArrayInt16")), + np.uint16: ("uint16_t*", _get_path_cost_func("PathCostArrayUInt16")), + np.int32: ("int32_t*", _get_path_cost_func("PathCostArrayInt32")), + np.uint32: ("uint32_t*", _get_path_cost_func("PathCostArrayUInt32")), } def __new__(cls, array: ArrayLike) -> NodeCostArray: @@ -676,12 +676,12 @@ def __init__(self, shape: Tuple[int, ...], *, order: str = "C"): @property def ndim(self) -> int: - """The number of dimensions.""" + """Return the number of dimensions.""" return self._ndim @property def shape(self) -> Tuple[int, ...]: - """The shape of this graph.""" + """Return the shape of this graph.""" return self._shape def add_edge( @@ -895,7 +895,7 @@ def add_edges( self.add_edge(edge, edge_cost, cost=cost, condition=condition) def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0) -> None: - """Sets a pathfinder heuristic so that pathfinding can done with A*. + """Set a pathfinder heuristic so that pathfinding can done with A*. `cardinal`, `diagonal`, `z, and `w` are the lower-bound cost of movement in those directions. Values above the lower-bound can be @@ -1092,7 +1092,7 @@ def __init__(self, graph: Union[CustomGraph, SimpleGraph]): @property def distance(self) -> NDArray[Any]: - """The distance values of the pathfinder. + """Distance values of the pathfinder. The array returned from this property maintains the graphs `order`. @@ -1112,7 +1112,7 @@ def distance(self) -> NDArray[Any]: @property def traversal(self) -> NDArray[Any]: - """An array used to generate paths from any point to the nearest root. + """Array used to generate paths from any point to the nearest root. The array returned from this property maintains the graphs `order`. It has an extra dimension which includes the index of the next path. diff --git a/tcod/random.py b/tcod/random.py index ef64df2c..013dd6ab 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -151,7 +151,7 @@ def __getstate__(self) -> Any: return state def __setstate__(self, state: Any) -> None: - """Create a new cdata object with the stored paramaters.""" + """Create a new cdata object with the stored parameters.""" if "algo" in state["random_c"]: # Handle old/deprecated format. Covert to libtcod's new union type. state["random_c"]["algorithm"] = state["random_c"]["algo"] diff --git a/tcod/tileset.py b/tcod/tileset.py index 1e72559f..18879474 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -54,17 +54,17 @@ def _from_ref(cls, tileset_p: Any) -> Tileset: @property def tile_width(self) -> int: - """The width of the tile in pixels.""" + """Width of the tile in pixels.""" return int(lib.TCOD_tileset_get_tile_width_(self._tileset_p)) @property def tile_height(self) -> int: - """The height of the tile in pixels.""" + """Height of the tile in pixels.""" return int(lib.TCOD_tileset_get_tile_height_(self._tileset_p)) @property def tile_shape(self) -> Tuple[int, int]: - """The shape (height, width) of the tile in pixels.""" + """Shape (height, width) of the tile in pixels.""" return self.tile_height, self.tile_width def __contains__(self, codepoint: int) -> bool: @@ -357,7 +357,7 @@ def load_tilesheet( def procedural_block_elements(*, tileset: Tileset) -> None: - """Overwrites the block element codepoints in `tileset` with prodecually generated glyphs. + """Overwrite the block element codepoints in `tileset` with procedurally generated glyphs. Args: tileset (Tileset): A :any:`Tileset` with tiles of any shape. From 3ed5b15ada10ca4e7bb954a9dcd185fc704d7ed5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Dec 2022 14:54:01 -0800 Subject: [PATCH 0721/1101] Add explicit support for namespace packages. Fixes work that I'm trying out with new packages which I want to be in the tcod namespace. --- CHANGELOG.md | 3 +++ tcod/__init__.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e86b05e0..5626f7c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Added explicit support for namespace packages. + ### Changed - Using `libtcod 1.23.1`. - Bundle `SDL 2.26.0` on Windows and MacOS. diff --git a/tcod/__init__.py b/tcod/__init__.py index 91f41cc6..92885a2f 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -10,6 +10,9 @@ import sys import warnings +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset from tcod.console import Console # noqa: F401 From 42083ba4753792537d8cef77797cf6fba8c0f1e3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Dec 2022 15:09:05 -0800 Subject: [PATCH 0722/1101] Update classifiers. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index c358930c..4dadf34f 100755 --- a/setup.py +++ b/setup.py @@ -144,11 +144,13 @@ def check_sdl_version() -> None: "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", ], keywords="roguelike cffi Unicode libtcod field-of-view pathfinding", platforms=["Windows", "MacOS", "Linux"], From b6fdc960af0d575b6996be4f32187398a89a26b5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Dec 2022 15:14:53 -0800 Subject: [PATCH 0723/1101] Arrange workflows to avoid needless job running. Skip main tests if linters fail. Do aarch64 last since it takes forever. --- .github/workflows/python-package.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e0acab53..fafeff34 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -91,6 +91,7 @@ jobs: SDL_VERSION: ${{ matrix.sdl-version }} build: + needs: [black, isort, flake8, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -197,6 +198,7 @@ jobs: python -c "import tcod" linux-wheels: + needs: build # These take a while to build/test, so wait for normal tests to pass first. runs-on: "ubuntu-20.04" strategy: matrix: @@ -250,6 +252,7 @@ jobs: twine upload --skip-existing wheelhouse/* build-macos: + needs: [black, isort, flake8, mypy] runs-on: "macos-10.15" strategy: fail-fast: true From 117943a366dc09a642b975b801a1763cfaff9477 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 9 Dec 2022 16:18:20 -0800 Subject: [PATCH 0724/1101] Prepare 14.0.0 release. --- CHANGELOG.md | 2 ++ tcod/tileset.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5626f7c6..cccfb653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [14.0.0] - 2022-12-09 ### Added - Added explicit support for namespace packages. diff --git a/tcod/tileset.py b/tcod/tileset.py index 18879474..ac418f1b 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -720,7 +720,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: .. versionadded:: 11.12 -.. versionchanged:: Unreleased +.. versionchanged:: 14.0 Character at index ``0x7F`` was changed from value ``0x7F`` to the HOUSE ``⌂`` glyph ``0x2302``. """ From d88e5924833f8a4b4039840a9e7258ce0620505d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 16:33:20 +0000 Subject: [PATCH 0725/1101] Bump setuptools from 60.8.2 to 65.5.1 Bumps [setuptools](https://github.com/pypa/setuptools) from 60.8.2 to 65.5.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/CHANGES.rst) - [Commits](https://github.com/pypa/setuptools/compare/v60.8.2...v65.5.1) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a6fc227e..7bb32eec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ cffi>=1.15 numpy>=1.21.4 pycparser>=2.14 requests>=2.28.1 -setuptools==60.8.2 +setuptools==65.5.1 types-requests types-setuptools types-tabulate From 15f9765e1de10c84bbc9f6e53281789b5b993077 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 30 Dec 2022 19:36:58 -0800 Subject: [PATCH 0726/1101] Fix window even case not matching its type hints. --- CHANGELOG.md | 3 +++ tcod/event.py | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cccfb653..95120c50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- Updated the case of window event types to match their type annotations. + This may cause regressions. Run Mypy to check for ``[comparison-overlap]`` errors. ## [14.0.0] - 2022-12-09 ### Added diff --git a/tcod/event.py b/tcod/event.py index dbb8e6b0..13bd8aa8 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -650,10 +650,7 @@ def __str__(self) -> str: class WindowEvent(Event): - """ - Attributes: - type (str): A window event could mean various event types. - """ + """A window event.""" type: Final[ # type: ignore[misc] # Narrowing final type. Literal[ @@ -675,12 +672,13 @@ class WindowEvent(Event): "WindowHitTest", ] ] + """The current window event. This can be one of various options.""" @classmethod def from_sdl_event(cls, sdl_event: Any) -> Union[WindowEvent, Undefined]: if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) - event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event].upper() + event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event] self: WindowEvent if sdl_event.window.event == lib.SDL_WINDOWEVENT_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) @@ -720,12 +718,12 @@ def __repr__(self) -> str: class WindowMoved(WindowEvent): """ Attributes: - type (str): Always "WINDOWMOVED". x (int): Movement on the x-axis. y (int): Movement on the y-axis. """ type: Final[Literal["WINDOWMOVED"]] # type: ignore[assignment,misc] + """Always "WINDOWMOVED".""" def __init__(self, x: int, y: int) -> None: super().__init__(None) @@ -751,12 +749,12 @@ def __str__(self) -> str: class WindowResized(WindowEvent): """ Attributes: - type (str): "WINDOWRESIZED" or "WINDOWSIZECHANGED" width (int): The current width of the window. height (int): The current height of the window. """ type: Final[Literal["WINDOWRESIZED", "WINDOWSIZECHANGED"]] # type: ignore[assignment,misc] + """WINDOWRESIZED" or "WINDOWSIZECHANGED""" def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) From 15241974baa5d3521301ee9711fdfe6908f49159 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 1 Jan 2023 02:17:50 -0800 Subject: [PATCH 0727/1101] Update copyright years. --- LICENSE.txt | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index c00f298e..d91bf759 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2009-2021, Kyle Benesch and the python-tcod contributors. +Copyright (c) 2009-2023, Kyle Benesch and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index cb508dc3..de4337ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # General information about the project. project = "python-tcod" -copyright = "2009-2021, Kyle Benesch" +copyright = "2009-2023, Kyle Benesch" author = "Kyle Benesch" # The version info for the project you're documenting, acts as replacement for From d1d85a99e5872d96e2bbf1a298de78b4ece0b651 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 1 Jan 2023 22:15:46 -0800 Subject: [PATCH 0728/1101] Refactor events to deprecate mouse tile attributes. Renamed mouse pixel attributes to position and motion. Old names have been deprecated. Context.convert_event now returns an event with tile coordinates active. --- examples/samples_tcod.py | 12 +++-- tcod/context.py | 39 +++++++++----- tcod/event.py | 112 +++++++++++++++++++++++++++++++-------- tcod/sdl/mouse.py | 6 +-- 4 files changed, 124 insertions(+), 45 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f14c9c4b..60bd8416 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1105,8 +1105,8 @@ def on_draw(self) -> None: "Right button : %s\n" "Middle button : %s\n" % ( - self.motion.pixel.x, - self.motion.pixel.y, + self.motion.position.x, + self.motion.position.y, self.motion.tile.x, self.motion.tile.y, self.motion.tile_motion.x, @@ -1489,11 +1489,13 @@ def handle_events() -> None: if context.sdl_renderer: # Manual handing of tile coordinates since context.present is skipped. if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): - event.tile = tcod.event.Point(event.pixel.x // tileset.tile_width, event.pixel.y // tileset.tile_height) + event.tile = tcod.event.Point( + event.position.x // tileset.tile_width, event.position.y // tileset.tile_height + ) if isinstance(event, tcod.event.MouseMotion): prev_tile = ( - (event.pixel[0] - event.pixel_motion[0]) // tileset.tile_width, - (event.pixel[1] - event.pixel_motion[1]) // tileset.tile_height, + (event.position[0] - event.motion[0]) // tileset.tile_width, + (event.position[1] - event.motion[1]) // tileset.tile_height, ) event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) else: diff --git a/tcod/context.py b/tcod/context.py index a77562e4..414f1bd2 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -49,11 +49,12 @@ """ # noqa: E501 from __future__ import annotations +import copy import os import pickle import sys import warnings -from typing import Any, Iterable, List, Optional, Tuple +from typing import Any, Iterable, List, Optional, Tuple, TypeVar from typing_extensions import Literal, NoReturn @@ -87,6 +88,8 @@ "RENDERER_XTERM", ) +_Event = TypeVar("_Event", bound=tcod.event.Event) + SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN """Exclusive fullscreen mode. @@ -247,30 +250,38 @@ def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: _check(lib.TCOD_context_screen_pixel_to_tile_d(self._context_p, xy, xy + 1)) return xy[0], xy[1] - def convert_event(self, event: tcod.event.Event) -> None: - """Fill in the tile coordinates of a mouse event using this context. + def convert_event(self, event: _Event) -> _Event: + """Return an event with mouse pixel coordinates converted into tile coordinates. Example:: context: tcod.context.Context for event in tcod.event.get(): + event_tile = context.convert_event(event) if isinstance(event, tcod.event.MouseMotion): - # Pixel coordinates are always accessible. - print(f"{event.pixel=}, {event.pixel_motion=}") - context.convert_event(event) - if isinstance(event, tcod.event.MouseMotion): - # Now tile coordinate attributes can be accessed. - print(f"{event.tile=}, {event.tile_motion=}") - # A warning will be raised if you try to access these without convert_event. + # Events start with pixel coordinates and motion. + print(f"Pixels: {event.position=}, {event.motion=}") + if isinstance(event_tile, tcod.event.MouseMotion): + # Tile coordinates are used in the returned event. + print(f"Tiles: {event_tile.position=}, {event_tile.motion=}") + + .. versionchanged:: Unreleased + Now returns a new event with the coordinates converted into tiles. """ + event_copy = copy.copy(event) if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): - event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel)) + assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion)) + event_copy.position = event.tile = tcod.event.Point(*self.pixel_to_tile(*event.position)) if isinstance(event, tcod.event.MouseMotion): + assert isinstance(event_copy, tcod.event.MouseMotion) prev_tile = self.pixel_to_tile( - event.pixel[0] - event.pixel_motion[0], - event.pixel[1] - event.pixel_motion[1], + event.position[0] - event.motion[0], + event.position[1] - event.motion[1], + ) + event_copy.motion = event.tile_motion = tcod.event.Point( + event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1] ) - event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) + return event_copy def save_screenshot(self, path: Optional[str] = None) -> None: """Save a screen-shot to the given file path.""" diff --git a/tcod/event.py b/tcod/event.py index 13bd8aa8..58ca9bac 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -384,7 +384,7 @@ class MouseState(Event): """ Attributes: type (str): Always "MOUSESTATE". - pixel (Point): The pixel coordinates of the mouse. + position (Point): The position coordinates of the mouse. tile (Point): The integer tile coordinates of the mouse on the screen. state (int): A bitmask of which mouse buttons are currently held. @@ -397,39 +397,70 @@ class MouseState(Event): * tcod.event.BUTTON_X2MASK .. versionadded:: 9.3 + + .. versionchanged:: Unreleased + Renamed `pixel` attribute to `position`. """ def __init__( self, - pixel: Tuple[int, int] = (0, 0), + position: Tuple[int, int] = (0, 0), tile: Optional[Tuple[int, int]] = (0, 0), state: int = 0, ): super().__init__() - self.pixel = Point(*pixel) + self.position = Point(*position) self.__tile = Point(*tile) if tile is not None else None self.state = state + @property + def pixel(self) -> Point: + warnings.warn( + "The mouse.pixel attribute is deprecated. Use mouse.position instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.position + + @pixel.setter + def pixel(self, value: Point) -> None: + warnings.warn( + "The mouse.pixel attribute is deprecated. Use mouse.position instead.", + DeprecationWarning, + stacklevel=2, + ) + self.position = value + @property def tile(self) -> Point: + warnings.warn( + "The mouse.tile attribute is deprecated. Use mouse.position of the event returned by context.convert_event instead.", + DeprecationWarning, + stacklevel=2, + ) return _verify_tile_coordinates(self.__tile) @tile.setter def tile(self, xy: Tuple[int, int]) -> None: + warnings.warn( + "The mouse.tile attribute is deprecated. Use mouse.position of the event returned by context.convert_event instead.", + DeprecationWarning, + stacklevel=2, + ) self.__tile = Point(*xy) def __repr__(self) -> str: - return ("tcod.event.%s(pixel=%r, tile=%r, state=%s)") % ( + return ("tcod.event.%s(position=%r, tile=%r, state=%s)") % ( self.__class__.__name__, - tuple(self.pixel), + tuple(self.position), tuple(self.tile), _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), ) def __str__(self) -> str: - return ("<%s, pixel=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( + return ("<%s, position=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( super().__str__().strip("<>"), - *self.pixel, + *self.position, *self.tile, _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), ) @@ -439,8 +470,8 @@ class MouseMotion(MouseState): """ Attributes: type (str): Always "MOUSEMOTION". - pixel (Point): The pixel coordinates of the mouse. - pixel_motion (Point): The pixel delta. + position (Point): The pixel coordinates of the mouse. + motion (Point): The pixel delta. tile (Point): The integer tile coordinates of the mouse on the screen. tile_motion (Point): The integer tile delta. state (int): A bitmask of which mouse buttons are currently held. @@ -452,26 +483,60 @@ class MouseMotion(MouseState): * tcod.event.BUTTON_RMASK * tcod.event.BUTTON_X1MASK * tcod.event.BUTTON_X2MASK + + .. versionchanged:: Unreleased + Renamed `pixel` attribute to `position`. + Renamed `pixel_motion` attribute to `motion`. """ def __init__( self, - pixel: Tuple[int, int] = (0, 0), - pixel_motion: Tuple[int, int] = (0, 0), + position: Tuple[int, int] = (0, 0), + motion: Tuple[int, int] = (0, 0), tile: Optional[Tuple[int, int]] = (0, 0), tile_motion: Optional[Tuple[int, int]] = (0, 0), state: int = 0, ): - super().__init__(pixel, tile, state) - self.pixel_motion = Point(*pixel_motion) + super().__init__(position, tile, state) + self.motion = Point(*motion) self.__tile_motion = Point(*tile_motion) if tile_motion is not None else None + @property + def pixel_motion(self) -> Point: + warnings.warn( + "The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.motion + + @pixel_motion.setter + def pixel_motion(self, value: Point) -> None: + warnings.warn( + "The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.", + DeprecationWarning, + stacklevel=2, + ) + self.motion = value + @property def tile_motion(self) -> Point: + warnings.warn( + "The mouse.tile_motion attribute is deprecated." + " Use mouse.motion of the event returned by context.convert_event instead.", + DeprecationWarning, + stacklevel=2, + ) return _verify_tile_coordinates(self.__tile_motion) @tile_motion.setter def tile_motion(self, xy: Tuple[int, int]) -> None: + warnings.warn( + "The mouse.tile_motion attribute is deprecated." + " Use mouse.motion of the event returned by context.convert_event instead.", + DeprecationWarning, + stacklevel=2, + ) self.__tile_motion = Point(*xy) @classmethod @@ -494,19 +559,19 @@ def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: return self def __repr__(self) -> str: - return ("tcod.event.%s(pixel=%r, pixel_motion=%r, " "tile=%r, tile_motion=%r, state=%s)") % ( + return ("tcod.event.%s(position=%r, motion=%r, tile=%r, tile_motion=%r, state=%s)") % ( self.__class__.__name__, - tuple(self.pixel), - tuple(self.pixel_motion), + tuple(self.position), + tuple(self.motion), tuple(self.tile), tuple(self.tile_motion), _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), ) def __str__(self) -> str: - return ("<%s, pixel_motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>") % ( + return ("<%s, motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>") % ( super().__str__().strip("<>"), - *self.pixel_motion, + *self.motion, *self.tile_motion, ) @@ -516,7 +581,7 @@ class MouseButtonEvent(MouseState): Attributes: type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", depending on the event. - pixel (Point): The pixel coordinates of the mouse. + position (Point): The pixel coordinates of the mouse. tile (Point): The integer tile coordinates of the mouse on the screen. button (int): Which mouse button. @@ -527,6 +592,7 @@ class MouseButtonEvent(MouseState): * tcod.event.BUTTON_RIGHT * tcod.event.BUTTON_X1 * tcod.event.BUTTON_X2 + """ def __init__( @@ -559,17 +625,17 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.%s(pixel=%r, tile=%r, button=%s)" % ( + return "tcod.event.%s(position=%r, tile=%r, button=%s)" % ( self.__class__.__name__, - tuple(self.pixel), + tuple(self.position), tuple(self.tile), _REVERSE_BUTTON_TABLE_PREFIX[self.button], ) def __str__(self) -> str: - return " tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetGlobalMouseState(xy, xy + 1) - return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + return tcod.event.MouseState((xy[0], xy[1]), state=state) def get_relative_state() -> tcod.event.MouseState: @@ -200,7 +200,7 @@ def get_relative_state() -> tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetRelativeMouseState(xy, xy + 1) - return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + return tcod.event.MouseState((xy[0], xy[1]), state=state) def get_state() -> tcod.event.MouseState: @@ -211,7 +211,7 @@ def get_state() -> tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetMouseState(xy, xy + 1) - return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state) + return tcod.event.MouseState((xy[0], xy[1]), state=state) def get_focus() -> Optional[tcod.sdl.video.Window]: From 65ec5b9a705aeced7bd50b53779848e392f03846 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 4 Jan 2023 13:14:16 -0800 Subject: [PATCH 0729/1101] Prepare 15.0.0 release. --- CHANGELOG.md | 9 ++++++++- tcod/context.py | 2 +- tcod/event.py | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95120c50..591b7289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,16 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [15.0.0] - 2023-01-04 ### Changed -- Updated the case of window event types to match their type annotations. +- Modified the letter case of window event types to match their type annotations. This may cause regressions. Run Mypy to check for ``[comparison-overlap]`` errors. +- Mouse event attributes have been changed ``.pixel -> .position`` and ``.pixel_motion -> .motion``. +- `Context.convert_event` now returns copies of events with mouse coordinates converted into tile positions. + +### Deprecated +- Mouse event pixel and tile attributes have been deprecated. ## [14.0.0] - 2022-12-09 ### Added diff --git a/tcod/context.py b/tcod/context.py index 414f1bd2..cc82c90f 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -265,7 +265,7 @@ def convert_event(self, event: _Event) -> _Event: # Tile coordinates are used in the returned event. print(f"Tiles: {event_tile.position=}, {event_tile.motion=}") - .. versionchanged:: Unreleased + .. versionchanged:: 15.0 Now returns a new event with the coordinates converted into tiles. """ event_copy = copy.copy(event) diff --git a/tcod/event.py b/tcod/event.py index 58ca9bac..a54eeb61 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -398,7 +398,7 @@ class MouseState(Event): .. versionadded:: 9.3 - .. versionchanged:: Unreleased + .. versionchanged:: 15.0 Renamed `pixel` attribute to `position`. """ @@ -484,7 +484,7 @@ class MouseMotion(MouseState): * tcod.event.BUTTON_X1MASK * tcod.event.BUTTON_X2MASK - .. versionchanged:: Unreleased + .. versionchanged:: 15.0 Renamed `pixel` attribute to `position`. Renamed `pixel_motion` attribute to `motion`. """ From 3c9da8232743f6a36193db42e2f48fb400b6ec0d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Jan 2023 02:45:07 -0800 Subject: [PATCH 0730/1101] Extend namespace package support to tcod.sdl In case any SDL tools such as audio mixing are put in another package. Fix typo. --- CHANGELOG.md | 2 ++ tcod/sdl/__init__.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 591b7289..9d3f4b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Added support for `tcod.sdl` namespace packages. ## [15.0.0] - 2023-01-04 ### Changed diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 81f04d12..e1cbde7a 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -1,10 +1,13 @@ from __future__ import annotations import logging +from pkgutil import extend_path from typing import Any, Callable, Tuple, TypeVar from tcod.loader import ffi, lib +__path__ = extend_path(__path__, __name__) + T = TypeVar("T") logger = logging.getLogger(__name__) @@ -59,7 +62,7 @@ def _linked_version() -> Tuple[int, int, int]: def _version_at_least(required: Tuple[int, int, int]) -> None: - """Raise an error if the compiled version is less than required. Used to guard recentally defined SDL functions.""" + """Raise an error if the compiled version is less than required. Used to guard recently defined SDL functions.""" if required <= _compiled_version(): return raise RuntimeError( From f544ea1ebfd09ec7aee9535a14adfbc2ffd509a2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 16 Feb 2023 02:58:52 -0800 Subject: [PATCH 0731/1101] Update noise docs. --- tcod/noise.py | 115 ++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 61 deletions(-) diff --git a/tcod/noise.py b/tcod/noise.py index 92676c8a..94178b56 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -12,27 +12,24 @@ ... algorithm=tcod.noise.Algorithm.SIMPLEX, ... seed=42, ... ) - >>> samples = noise[tcod.noise.grid(shape=(5, 5), scale=0.25, origin=(0, 0))] + >>> samples = noise[tcod.noise.grid(shape=(5, 4), scale=0.25, origin=(0, 0))] >>> samples # Samples are a grid of floats between -1.0 and 1.0 array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785], [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625], [-0.40398532, -0.57662135, -0.33160293, 0.12860827, 0.2864191 ], - [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ], - [-0.64945626, -0.12529983, 0.5346834 , 0.80402255, 0.52655405]], + [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ]], dtype=float32) >>> (samples + 1.0) * 0.5 # You can normalize samples to 0.0 - 1.0 array([[0.5 , 0.22476822, 0.11963567, 0.14556766, 0.15917107], [0.36238313, 0.1397433 , 0.12971482, 0.28040403, 0.35402188], [0.29800734, 0.21168932, 0.33419853, 0.5643041 , 0.6432096 ], - [0.24613297, 0.3678193 , 0.6222316 , 0.8195127 , 0.79614234], - [0.17527187, 0.4373501 , 0.76734173, 0.9020113 , 0.76327705]], + [0.24613297, 0.3678193 , 0.6222316 , 0.8195127 , 0.79614234]], dtype=float32) >>> ((samples + 1.0) * (256 / 2)).astype(np.uint8) # Or as 8-bit unsigned bytes. array([[128, 57, 30, 37, 40], [ 92, 35, 33, 71, 90], [ 76, 54, 85, 144, 164], - [ 63, 94, 159, 209, 203], - [ 44, 111, 196, 230, 195]], dtype=uint8) + [ 63, 94, 159, 209, 203]], dtype=uint8) """ # noqa: E501 from __future__ import annotations @@ -103,26 +100,23 @@ def __getattr__(name: str) -> Implementation: class Noise(object): - """ + """A configurable noise sampler. The ``hurst`` exponent describes the raggedness of the resultant noise, with a higher value leading to a smoother noise. Not used with tcod.noise.SIMPLE. - ``lacunarity`` is a multiplier that determines how fast the noise - frequency increases for each successive octave. + ``lacunarity`` is a multiplier that determines how fast the noise frequency increases for each successive octave. Not used with tcod.noise.SIMPLE. Args: - dimensions (int): Must be from 1 to 4. - algorithm (int): Defaults to :any:`tcod.noise.Algorithm.SIMPLEX` - implementation (int): - Defaults to :any:`tcod.noise.Implementation.SIMPLE` - hurst (float): The hurst exponent. Should be in the 0.0-1.0 range. - lacunarity (float): The noise lacunarity. - octaves (float): The level of detail on fBm and turbulence - implementations. - seed (Optional[Random]): A Random instance, or None. + dimensions: Must be from 1 to 4. + algorithm: Defaults to :any:`tcod.noise.Algorithm.SIMPLEX` + implementation: Defaults to :any:`tcod.noise.Implementation.SIMPLE` + hurst: The hurst exponent. Should be in the 0.0-1.0 range. + lacunarity: The noise lacunarity. + octaves: The level of detail on fBm and turbulence implementations. + seed: A Random instance, or None. Attributes: noise_c (CData): A cffi pointer to a TCOD_noise_t object. @@ -224,18 +218,17 @@ def get_point(self, x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> f """Return the noise value at the (x, y, z, w) point. Args: - x (float): The position on the 1st axis. - y (float): The position on the 2nd axis. - z (float): The position on the 3rd axis. - w (float): The position on the 4th axis. + x: The position on the 1st axis. + y: The position on the 2nd axis. + z: The position on the 3rd axis. + w: The position on the 4th axis. """ return float(lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))) def __getitem__(self, indexes: Any) -> NDArray[np.float32]: """Sample a noise map through NumPy indexing. - This follows NumPy's advanced indexing rules, but allows for floating - point values. + This follows NumPy's advanced indexing rules, but allows for floating point values. .. versionadded:: 11.16 """ @@ -292,14 +285,14 @@ def sample_mgrid(self, mgrid: ArrayLike) -> NDArray[np.float32]: overhead when working with large mesh-grids. Args: - mgrid (numpy.ndarray): A mesh-grid array of points to sample. + mgrid: A mesh-grid array of points to sample. A contiguous array of type `numpy.float32` is preferred. Returns: - numpy.ndarray: An array of sampled points. + An array of sampled points. - This array has the shape: ``mgrid.shape[:-1]``. - The ``dtype`` is `numpy.float32`. + This array has the shape: ``mgrid.shape[:-1]``. + The ``dtype`` is `numpy.float32`. """ mgrid = np.ascontiguousarray(mgrid, np.float32) if mgrid.shape[0] != self.dimensions: @@ -320,15 +313,14 @@ def sample_mgrid(self, mgrid: ArrayLike) -> NDArray[np.float32]: def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: """Sample an open mesh-grid array and return the result. - Args - ogrid (Sequence[ArrayLike]): An open mesh-grid. + Args: + ogrid: An open mesh-grid. Returns: - numpy.ndarray: An array of sampled points. + An array of sampled points. - The ``shape`` is based on the lengths of the open mesh-grid - arrays. - The ``dtype`` is `numpy.float32`. + The ``shape`` is based on the lengths of the open mesh-grid arrays. + The ``dtype`` is `numpy.float32`. """ if len(ogrid) != self.dimensions: raise ValueError("len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions)) @@ -416,38 +408,39 @@ def grid( origin: Optional[Tuple[int, ...]] = None, indexing: Literal["ij", "xy"] = "xy", ) -> Tuple[NDArray[Any], ...]: - """Helper function for generating a grid of noise samples. - - `shape` is the shape of the returned mesh grid. This can be any number of - dimensions, but :class:`Noise` classes only support up to 4. - - `scale` is the step size of indexes away from `origin`. - This can be a single float, or it can be a tuple of floats with one float - for each axis in `shape`. A lower scale gives smoother transitions - between noise values. + """Generate a mesh-grid of sample points to use with noise sampling. - `origin` is the first sample of the grid. - If `None` then the `origin` will be zero on each axis. - `origin` is not scaled by the `scale` parameter. - - `indexing` is passed to :any:`numpy.meshgrid`. + Args: + shape: The shape of the grid. + This can be any number of dimensions, but :class:`Noise` classes only support up to 4. + scale: The step size between samples. + This can be a single float, or it can be a tuple of floats with one float for each axis in `shape`. + A lower scale gives smoother transitions between noise values. + origin: The position of the first sample. + If `None` then the `origin` will be zero on each axis. + `origin` is not scaled by the `scale` parameter. + indexing: Passed to :any:`numpy.meshgrid`. + + Returns: + A sparse mesh-grid to be passed into a :class:`Noise` instance. Example:: >>> noise = tcod.noise.Noise(dimensions=2, seed=42) - >>> noise[tcod.noise.grid(shape=(5, 5), scale=0.25)] - array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785], - [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625], - [-0.40398532, -0.57662135, -0.33160293, 0.12860827, 0.2864191 ], - [-0.50773406, -0.2643614 , 0.24446318, 0.6390255 , 0.5922846 ], - [-0.64945626, -0.12529983, 0.5346834 , 0.80402255, 0.52655405]], + + # Common case for ij-indexed arrays. + >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij")] + array([[ 0. , -0.27523372, -0.40398532, -0.50773406, -0.64945626], + [-0.55046356, -0.7205134 , -0.57662135, -0.2643614 , -0.12529983], + [-0.76072866, -0.74057037, -0.33160293, 0.24446318, 0.5346834 ]], dtype=float32) - >>> noise[tcod.noise.grid(shape=(5, 5), scale=(0.5, 0.25), origin=(1, 1))] - array([[ 0.52655405, -0.5037453 , -0.81221616, -0.7057655 , 0.24630858], - [ 0.25038874, -0.75348294, -0.6379566 , -0.5817767 , -0.02789652], - [-0.03488023, -0.73630923, -0.12449139, -0.22774395, -0.22243626], - [-0.18455243, -0.35063767, 0.4495706 , 0.02399864, -0.42226675], - [-0.16333057, 0.18149695, 0.7547447 , -0.07006818, -0.6546707 ]], + + # Transpose an xy-indexed array to get a standard order="F" result. + >>> noise[tcod.noise.grid(shape=(4, 5), scale=(0.5, 0.25), origin=(1.0, 1.0))].T + array([[ 0.52655405, 0.25038874, -0.03488023, -0.18455243, -0.16333057], + [-0.5037453 , -0.75348294, -0.73630923, -0.35063767, 0.18149695], + [-0.81221616, -0.6379566 , -0.12449139, 0.4495706 , 0.7547447 ], + [-0.7057655 , -0.5817767 , -0.22774395, 0.02399864, -0.07006818]], dtype=float32) .. versionadded:: 12.2 From 992292c00de5a9a30372f025f259bcd55e6c7f16 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 16 Feb 2023 03:20:13 -0800 Subject: [PATCH 0732/1101] Updates sources for latest black/mypy versions. --- examples/samples_libtcodpy.py | 2 ++ examples/samples_tcod.py | 1 - tcod/console.py | 2 +- tcod/path.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index ba91aeca..c503cefb 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -930,6 +930,8 @@ def render_path(first, key, mouse): # if true, there is always a wall on north & west side of a room bsp_room_walls = True bsp_map = None + + # draw a vertical line def vline(m, x, y1, y2): if y1 > y2: diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 60bd8416..6f81cd0d 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -230,7 +230,6 @@ def on_draw(self) -> None: class LineDrawingSample(Sample): - FLAG_NAMES = [ "BKGND_NONE", "BKGND_SET", diff --git a/tcod/console.py b/tcod/console.py index 81f209d9..3ffa437b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -782,7 +782,7 @@ def blit( """ # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): - (src_x, src_y, width, height, dest, dest_x, dest_y,) = ( + (src_x, src_y, width, height, dest, dest_x, dest_y) = ( dest, # type: ignore dest_x, dest_y, diff --git a/tcod/path.py b/tcod/path.py index d3c9a514..db42fba5 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -474,7 +474,7 @@ def dijkstra2d( Added `out` parameter. Now returns the output array. """ dist: NDArray[Any] = np.asarray(distance) - if out is ...: # type: ignore + if out is ...: out = dist warnings.warn( "No `out` parameter was given. " From c6bb7eed924508508f93a0fdc4d2101f42198a4e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 16 Feb 2023 03:49:07 -0800 Subject: [PATCH 0733/1101] Update GitHub Actions versions. --- .github/workflows/python-package.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index fafeff34..c8749e3f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -32,12 +32,12 @@ jobs: - name: Install isort run: pip install isort - name: isort - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v2 with: linters: isort run: isort scripts/ tcod/ tests/ --check --diff - name: isort (examples) - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v2 with: linters: isort run: isort examples/ --check --diff --thirdparty tcod @@ -49,7 +49,7 @@ jobs: - name: Install Flake8 run: pip install Flake8 - name: Flake8 - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v2 with: linters: flake8 run: flake8 scripts/ tcod/ tests/ @@ -66,7 +66,7 @@ jobs: run: | echo '__version__ = ""' > tcod/version.py - name: Mypy - uses: liskin/gh-problem-matcher-wrap@v1 + uses: liskin/gh-problem-matcher-wrap@v2 with: linters: mypy run: mypy --show-column-numbers . @@ -115,7 +115,7 @@ jobs: run: | git submodule update --init --recursive --depth 1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -146,7 +146,7 @@ jobs: - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v3 - name: Upload to PyPI if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' env: @@ -154,13 +154,13 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | twine upload --skip-existing dist/* - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: runner.os == 'Linux' with: name: sdist path: dist/tcod-*.tar.gz retention-days: 7 - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: runner.os == 'Windows' with: name: wheels-windows @@ -175,7 +175,7 @@ jobs: os: ["ubuntu-20.04", "windows-2019"] steps: - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.x - name: Install Python dependencies @@ -187,7 +187,7 @@ jobs: run: | sudo apt-get update sudo apt-get install libsdl2-dev - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: sdist - name: Build package in isolation @@ -210,12 +210,12 @@ jobs: fetch-depth: ${{ env.git-depth }} - name: Set up QEMU if: ${{ matrix.arch == 'aarch64' }} - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.x" - name: Install Python dependencies @@ -238,7 +238,7 @@ jobs: CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod" - name: Archive wheel - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: wheels-linux path: wheelhouse/*.whl @@ -283,7 +283,7 @@ jobs: CIBW_TEST_COMMAND: python -c "import tcod" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" - name: Archive wheel - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: wheels-macos path: wheelhouse/*.whl From a77ce55ab1ee3329aefe6ae0c90e4f67e68f6eb9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Mar 2023 22:08:25 -0700 Subject: [PATCH 0734/1101] Add Ruff config. Taking from another project, settings might need tweaking. Update ignore file for Ruff. --- .gitignore | 1 + pyproject.toml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/.gitignore b/.gitignore index 85bf65c5..6ec1f7fb 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,4 @@ debian/python* .pytest_cache Thumbs.db .mypy_cache/ +.ruff_cache/ diff --git a/pyproject.toml b/pyproject.toml index 63a4c899..f27434f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,47 @@ filterwarnings = [ "ignore::PendingDeprecationWarning:tcod.libtcodpy", "ignore:This class may perform poorly and is no longer needed.::tcod.map", ] + +[tool.ruff] +# https://beta.ruff.rs/docs/rules/ +select = [ + "C90", # mccabe + "E", # pycodestyle + "W", # pycodestyle + "F", # Pyflakes + "I", # isort + "UP", # pyupgrade + "YTT", # flake8-2020 + "ANN", # flake8-annotations + "S", # flake8-bandit + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "EM", # flake8-errmsg + "EXE", # flake8-executable + "RET", # flake8-return + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "PIE", # flake8-pie + "PT", # flake8-pytest-style + "SIM", # flake8-simplify + "PTH", # flake8-use-pathlib + "PL", # Pylint + "TRY", # tryceratops + "RUF", # NumPy-specific rules + "G", # flake8-logging-format + "D", # pydocstyle +] +ignore = [ + "E501", # line-too-long + "S101", # assert + "ANN101", # missing-type-self + "D203", # one-blank-line-before-class + "D204", # one-blank-line-after-class + "D213", # multi-line-summary-second-line + "D407", # dashed-underline-after-section + "D408", # section-underline-after-name + "D409", # section-underline-matches-section-length +] +line-length = 120 +target-version = "py37" From 849ac8f797597a2bf45c22ce53ffa7355814d8dc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 26 Mar 2023 22:14:36 -0700 Subject: [PATCH 0735/1101] Docs: fix typo and clean up lines. --- tcod/event.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index a54eeb61..0ef109d5 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1264,15 +1264,14 @@ def wait(timeout: Optional[float] = None) -> Iterator[Any]: class EventDispatch(Generic[T]): - '''This class dispatches events to methods depending on the events type - attribute. + '''Dispatches events to methods depending on the events type attribute. - To use this class, make a sub-class and override the relevant `ev_*` - methods. Then send events to the dispatch method. + To use this class, make a sub-class and override the relevant `ev_*` methods. + Then send events to the dispatch method. .. versionchanged:: 11.12 - This is now a generic class. The type hists at the return value of - :any:`dispatch` and the `ev_*` methods. + This is now a generic class. + The type hints at the return value of :any:`dispatch` and the `ev_*` methods. Example:: From 14a80d450249872dc502f8a4ed91fc329845eb22 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 27 Mar 2023 18:04:56 -0700 Subject: [PATCH 0736/1101] Update keyboard examples to use enums. --- examples/samples_tcod.py | 96 ++++++++++++++++++++-------------------- tcod/event.py | 58 ++++++++++++------------ 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 6f81cd0d..bf20be3b 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -70,17 +70,17 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: global cur_sample - if event.sym == tcod.event.K_DOWN: + if event.sym == tcod.event.KeySym.DOWN: cur_sample = (cur_sample + 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() draw_samples_menu() - elif event.sym == tcod.event.K_UP: + elif event.sym == tcod.event.KeySym.UP: cur_sample = (cur_sample - 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() draw_samples_menu() - elif event.sym == tcod.event.K_RETURN and event.mod & tcod.event.KMOD_LALT: + elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.KMOD_LALT: tcod.console_set_fullscreen(not tcod.console_is_fullscreen()) - elif event.sym == tcod.event.K_PRINTSCREEN or event.sym == ord("p"): + elif event.sym == tcod.event.KeySym.PRINTSCREEN or event.sym == tcod.event.KeySym.p: print("screenshot") if event.mod & tcod.event.KMOD_LALT: tcod.console_save_apf(root_console, "samples.apf") @@ -88,7 +88,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: else: tcod.sys_save_screenshot() print("png") - elif event.sym == tcod.event.K_ESCAPE: + elif event.sym == tcod.event.KeySym.ESCAPE: raise SystemExit() elif event.sym in RENDERER_KEYS: # Swap the active context for one with a different renderer. @@ -259,7 +259,7 @@ def __init__(self) -> None: self.bk.ch[:] = ord(" ") def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): + if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): self.bk_flag += 1 if (self.bk_flag & 0xFF) > tcod.BKGND_ALPH: self.bk_flag = tcod.BKGND_NONE @@ -449,30 +449,30 @@ def on_draw(self) -> None: ) def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if ord("9") >= event.sym >= ord("1"): - self.func = event.sym - ord("1") + if tcod.event.KeySym.N9 >= event.sym >= tcod.event.KeySym.N1: + self.func = event.sym - tcod.event.KeySym.N1 self.noise = self.get_noise() - elif event.sym == ord("e"): + elif event.sym == tcod.event.KeySym.e: self.hurst += 0.1 self.noise = self.get_noise() - elif event.sym == ord("d"): + elif event.sym == tcod.event.KeySym.d: self.hurst -= 0.1 self.noise = self.get_noise() - elif event.sym == ord("r"): + elif event.sym == tcod.event.KeySym.r: self.lacunarity += 0.5 self.noise = self.get_noise() - elif event.sym == ord("f"): + elif event.sym == tcod.event.KeySym.f: self.lacunarity -= 0.5 self.noise = self.get_noise() - elif event.sym == ord("t"): + elif event.sym == tcod.event.KeySym.t: self.octaves += 0.5 self.noise.octaves = self.octaves - elif event.sym == ord("g"): + elif event.sym == tcod.event.KeySym.g: self.octaves -= 0.5 self.noise.octaves = self.octaves - elif event.sym == ord("y"): + elif event.sym == tcod.event.KeySym.y: self.zoom += 0.2 - elif event.sym == ord("h"): + elif event.sym == tcod.event.KeySym.h: self.zoom -= 0.2 else: super().ev_keydown(event) @@ -631,25 +631,25 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: MOVE_KEYS = { - ord("i"): (0, -1), - ord("j"): (-1, 0), - ord("k"): (0, 1), - ord("l"): (1, 0), + tcod.event.KeySym.i: (0, -1), + tcod.event.KeySym.j: (-1, 0), + tcod.event.KeySym.k: (0, 1), + tcod.event.KeySym.l: (1, 0), } FOV_SELECT_KEYS = { - ord("-"): -1, - ord("="): 1, - tcod.event.K_KP_MINUS: -1, - tcod.event.K_KP_PLUS: 1, + tcod.event.KeySym.MINUS: -1, + tcod.event.KeySym.EQUALS: 1, + tcod.event.KeySym.KP_MINUS: -1, + tcod.event.KeySym.KP_PLUS: 1, } if event.sym in MOVE_KEYS: x, y = MOVE_KEYS[event.sym] if self.walkable[self.player_x + x, self.player_y + y]: self.player_x += x self.player_y += y - elif event.sym == ord("t"): + elif event.sym == tcod.event.KeySym.t: self.torch = not self.torch - elif event.sym == ord("w"): + elif event.sym == tcod.event.KeySym.w: self.light_walls = not self.light_walls elif event.sym in FOV_SELECT_KEYS: self.algo_num += FOV_SELECT_KEYS[event.sym] @@ -775,7 +775,7 @@ def on_draw(self) -> None: self.recalculate = True def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == ord("i") and self.dy > 0: + if event.sym == tcod.event.KeySym.i and self.dy > 0: # destination move north tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dy -= 1 @@ -783,7 +783,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True - elif event.sym == ord("k") and self.dy < SAMPLE_SCREEN_HEIGHT - 1: + elif event.sym == tcod.event.KeySym.k and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dy += 1 @@ -791,7 +791,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True - elif event.sym == ord("j") and self.dx > 0: + elif event.sym == tcod.event.KeySym.j and self.dx > 0: # destination move west tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dx -= 1 @@ -799,7 +799,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True - elif event.sym == ord("l") and self.dx < SAMPLE_SCREEN_WIDTH - 1: + elif event.sym == tcod.event.KeySym.l and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) self.dx += 1 @@ -807,7 +807,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True - elif event.sym == tcod.event.K_TAB: + elif event.sym == tcod.event.KeySym.TAB: self.using_astar = not self.using_astar if self.using_astar: tcod.console_print(sample_console, 1, 4, "Using : A* ") @@ -999,28 +999,28 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size - if event.sym in (tcod.event.K_RETURN, tcod.event.K_KP_ENTER): + if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): self.bsp_generate() - elif event.sym == ord(" "): + elif event.sym == tcod.event.KeySym.SPACE: self.bsp_refresh() - elif event.sym in (tcod.event.K_EQUALS, tcod.event.K_KP_PLUS): + elif event.sym in (tcod.event.KeySym.EQUALS, tcod.event.KeySym.KP_PLUS): bsp_depth += 1 self.bsp_generate() - elif event.sym in (tcod.event.K_MINUS, tcod.event.K_KP_MINUS): + elif event.sym in (tcod.event.KeySym.MINUS, tcod.event.KeySym.KP_MINUS): bsp_depth = max(1, bsp_depth - 1) self.bsp_generate() - elif event.sym in (tcod.event.K_8, tcod.event.K_KP_MULTIPLY): + elif event.sym in (tcod.event.KeySym.N8, tcod.event.KeySym.KP_MULTIPLY): bsp_min_room_size += 1 self.bsp_generate() - elif event.sym in (tcod.event.K_SLASH, tcod.event.K_KP_DIVIDE): + elif event.sym in (tcod.event.KeySym.SLASH, tcod.event.KeySym.KP_DIVIDE): bsp_min_room_size = max(2, bsp_min_room_size - 1) self.bsp_generate() - elif event.sym in (tcod.event.K_1, tcod.event.K_KP_1): + elif event.sym in (tcod.event.KeySym.N1, tcod.event.KeySym.KP_1): bsp_random_room = not bsp_random_room if not bsp_random_room: bsp_room_walls = True self.bsp_refresh() - elif event.sym in (tcod.event.K_2, tcod.event.K_KP_2): + elif event.sym in (tcod.event.KeySym.N2, tcod.event.KeySym.KP_2): bsp_room_walls = not bsp_room_walls self.bsp_refresh() else: @@ -1126,9 +1126,9 @@ def on_draw(self) -> None: ) def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == ord("1"): + if event.sym == tcod.event.KeySym.N1: tcod.mouse_show_cursor(False) - elif event.sym == ord("2"): + elif event.sym == tcod.event.KeySym.N2: tcod.mouse_show_cursor(True) else: super().ev_keydown(event) @@ -1177,10 +1177,10 @@ def on_draw(self) -> None: self.names.append(tcod.namegen_generate(self.sets[self.curset])) def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == ord("="): + if event.sym == tcod.event.KeySym.EQUALS: self.curset += 1 self.names.append("======") - elif event.sym == ord("-"): + elif event.sym == tcod.event.KeySym.MINUS: self.curset -= 1 self.names.append("======") else: @@ -1363,11 +1363,11 @@ def on_draw(self) -> None: ############################################# RENDERER_KEYS = { - tcod.event.K_F1: tcod.RENDERER_GLSL, - tcod.event.K_F2: tcod.RENDERER_OPENGL, - tcod.event.K_F3: tcod.RENDERER_SDL, - tcod.event.K_F4: tcod.RENDERER_SDL2, - tcod.event.K_F5: tcod.RENDERER_OPENGL2, + tcod.event.KeySym.F1: tcod.RENDERER_GLSL, + tcod.event.KeySym.F2: tcod.RENDERER_OPENGL, + tcod.event.KeySym.F3: tcod.RENDERER_SDL, + tcod.event.KeySym.F4: tcod.RENDERER_SDL2, + tcod.event.KeySym.F5: tcod.RENDERER_OPENGL2, } RENDERER_NAMES = ( diff --git a/tcod/event.py b/tcod/event.py index 0ef109d5..02d21f87 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1,7 +1,7 @@ """A light-weight implementation of event handling built on calls to SDL. Many event constants are derived directly from SDL. -For example: ``tcod.event.K_UP`` and ``tcod.event.SCANCODE_A`` refer to +For example: ``tcod.event.KeySym.UP`` and ``tcod.event.Scancode.A`` refer to SDL's ``SDLK_UP`` and ``SDL_SCANCODE_A`` respectfully. `See this table for all of SDL's keyboard constants. `_ @@ -1279,35 +1279,35 @@ class EventDispatch(Generic[T]): MOVE_KEYS = { # key_symbol: (x, y) # Arrow keys. - tcod.event.K_LEFT: (-1, 0), - tcod.event.K_RIGHT: (1, 0), - tcod.event.K_UP: (0, -1), - tcod.event.K_DOWN: (0, 1), - tcod.event.K_HOME: (-1, -1), - tcod.event.K_END: (-1, 1), - tcod.event.K_PAGEUP: (1, -1), - tcod.event.K_PAGEDOWN: (1, 1), - tcod.event.K_PERIOD: (0, 0), + tcod.event.KeySym.LEFT: (-1, 0), + tcod.event.KeySym.RIGHT: (1, 0), + tcod.event.KeySym.UP: (0, -1), + tcod.event.KeySym.DOWN: (0, 1), + tcod.event.KeySym.HOME: (-1, -1), + tcod.event.KeySym.END: (-1, 1), + tcod.event.KeySym.PAGEUP: (1, -1), + tcod.event.KeySym.PAGEDOWN: (1, 1), + tcod.event.KeySym.PERIOD: (0, 0), # Numpad keys. - tcod.event.K_KP_1: (-1, 1), - tcod.event.K_KP_2: (0, 1), - tcod.event.K_KP_3: (1, 1), - tcod.event.K_KP_4: (-1, 0), - tcod.event.K_KP_5: (0, 0), - tcod.event.K_KP_6: (1, 0), - tcod.event.K_KP_7: (-1, -1), - tcod.event.K_KP_8: (0, -1), - tcod.event.K_KP_9: (1, -1), - tcod.event.K_CLEAR: (0, 0), # Numpad `clear` key. + tcod.event.KeySym.KP_1: (-1, 1), + tcod.event.KeySym.KP_2: (0, 1), + tcod.event.KeySym.KP_3: (1, 1), + tcod.event.KeySym.KP_4: (-1, 0), + tcod.event.KeySym.KP_5: (0, 0), + tcod.event.KeySym.KP_6: (1, 0), + tcod.event.KeySym.KP_7: (-1, -1), + tcod.event.KeySym.KP_8: (0, -1), + tcod.event.KeySym.KP_9: (1, -1), + tcod.event.KeySym.CLEAR: (0, 0), # Numpad `clear` key. # Vi Keys. - tcod.event.K_h: (-1, 0), - tcod.event.K_j: (0, 1), - tcod.event.K_k: (0, -1), - tcod.event.K_l: (1, 0), - tcod.event.K_y: (-1, -1), - tcod.event.K_u: (1, -1), - tcod.event.K_b: (-1, 1), - tcod.event.K_n: (1, 1), + tcod.event.KeySym.h: (-1, 0), + tcod.event.KeySym.j: (0, 1), + tcod.event.KeySym.k: (0, -1), + tcod.event.KeySym.l: (1, 0), + tcod.event.KeySym.y: (-1, -1), + tcod.event.KeySym.u: (1, -1), + tcod.event.KeySym.b: (-1, 1), + tcod.event.KeySym.n: (1, 1), } @@ -1333,7 +1333,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym in MOVE_KEYS: # Send movement keys to the cmd_move method with parameters. self.cmd_move(*MOVE_KEYS[event.sym]) - elif event.sym == tcod.event.K_ESCAPE: + elif event.sym == tcod.event.KeySym.ESCAPE: self.cmd_escape() def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: From a756e389c8d4f547ce19b7cb7e99bb8f9b64d554 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 27 Mar 2023 18:15:28 -0700 Subject: [PATCH 0737/1101] Fix WASD typo. --- tcod/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index 02d21f87..1e6bd44a 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1632,7 +1632,7 @@ def get_keyboard_state() -> NDArray[np.bool_]: state = tcod.event.get_keyboard_state() # Get a WASD movement vector: - x = int(state[tcod.event.Scancode.E]) - int(state[tcod.event.Scancode.A]) + x = int(state[tcod.event.Scancode.D]) - int(state[tcod.event.Scancode.A]) y = int(state[tcod.event.Scancode.S]) - int(state[tcod.event.Scancode.W]) # Key with 'z' glyph is held: From 2e447300d0b0993acf0ef5d8a2cd6c04d1988744 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 30 Mar 2023 19:26:54 -0700 Subject: [PATCH 0738/1101] Fix Renderer.read_pixels, add simple tests, update docs. --- CHANGELOG.md | 3 +++ tcod/sdl/render.py | 65 +++++++++++++++++++++++++++++++++++----------- tests/test_sdl.py | 4 +++ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d3f4b71..8cd60ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Added - Added support for `tcod.sdl` namespace packages. +### Fixed +- ``Renderer.read_pixels`` method was completely broken. + ## [15.0.0] - 2023-01-04 ### Changed - Modified the letter case of window event types to match their type annotations. diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 3ea6bbd9..b39a994e 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -9,7 +9,7 @@ import numpy as np from numpy.typing import NDArray -from typing_extensions import Final +from typing_extensions import Final, Literal import tcod.sdl.video from tcod.loader import ffi, lib @@ -484,15 +484,33 @@ def set_vsync(self, enable: bool) -> None: def read_pixels( self, *, - rect: Optional[Tuple[int, int, int, int]] = None, - format: Optional[int] = None, - out: Optional[NDArray[Any]] = None, - ) -> NDArray[Any]: - """ - .. versionadded:: 13.5 + rect: tuple[int, int, int, int] | None = None, + format: int | Literal["RGB", "RGBA"] = "RGBA", + out: NDArray[np.uint8] | None = None, + ) -> NDArray[np.uint8]: + """Fetch the pixel contents of the current rendering target to an array. + + By default returns an RGBA pixel array of the full target in the shape: ``(height, width, rgba)``. + The target can be changed with :any:`set_render_target` + + Args: + rect: The ``(left, top, width, height)`` region of the target to fetch, or None for the entire target. + format: The pixel format. Defaults to ``"RGBA"``. + out: The output array. + Can be None or must be an ``np.uint8`` array of shape: ``(height, width, channels)``. + Must be C contiguous along the ``(width, channels)`` axes. + + This operation is slow due to coping from VRAM to RAM. + When reading the main rendering target this should be called after rendering and before :any:`present`. + See https://wiki.libsdl.org/SDL2/SDL_RenderReadPixels + + Returns: + The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels. + + .. versionadded:: Unreleased """ - if format is None: - format = lib.SDL_PIXELFORMAT_RGBA32 + FORMATS: Final = {"RGB": lib.SDL_PIXELFORMAT_RGB24, "RGBA": lib.SDL_PIXELFORMAT_RGBA32} + sdl_format = FORMATS.get(format) if isinstance(format, str) else format if rect is None: texture_p = lib.SDL_GetRenderTarget(self.p) if texture_p: @@ -502,15 +520,31 @@ def read_pixels( rect = (0, 0, *self.output_size) width, height = rect[2:4] if out is None: - if format == lib.SDL_PIXELFORMAT_RGBA32: + if sdl_format == lib.SDL_PIXELFORMAT_RGBA32: out = np.empty((height, width, 4), dtype=np.uint8) - elif format == lib.SDL_PIXELFORMAT_RGB24: + elif sdl_format == lib.SDL_PIXELFORMAT_RGB24: out = np.empty((height, width, 3), dtype=np.uint8) else: - raise TypeError("Pixel format not supported yet.") - assert out.shape[:2] == (height, width) - assert out[0].flags.c_contiguous - _check(lib.SDL_RenderReadPixels(self.p, format, ffi.cast("void*", out.ctypes.data), out.strides[0])) + msg = f"Pixel format {format!r} not supported by tcod." + raise TypeError(msg) + if out.dtype != np.uint8: + msg = "`out` must be a uint8 array." + raise TypeError(msg) + expected_shape = (height, width, {lib.SDL_PIXELFORMAT_RGB24: 3, lib.SDL_PIXELFORMAT_RGBA32: 4}[sdl_format]) + if out.shape != expected_shape: + msg = f"Expected `out` to be an array of shape {expected_shape}, got {out.shape} instead." + raise TypeError(msg) + if not out[0].flags.c_contiguous: + msg = "`out` array must be C contiguous." + _check( + lib.SDL_RenderReadPixels( + self.p, + (rect,), + sdl_format, + ffi.cast("void*", out.ctypes.data), + out.strides[0], + ) + ) return out def clear(self) -> None: @@ -522,6 +556,7 @@ def clear(self) -> None: def fill_rect(self, rect: Tuple[float, float, float, float]) -> None: """Fill a rectangle with :any:`draw_color`. + .. versionadded:: 13.5 """ _check(lib.SDL_RenderFillRectF(self.p, (rect,))) diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 3deec406..84dcf3a7 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -70,6 +70,10 @@ def test_sdl_render() -> None: with pytest.raises(TypeError): render.upload_texture(np.zeros((8, 8, 5), np.uint8)) + assert (render.read_pixels() == (0, 0, 0, 255)).all() + assert (render.read_pixels(format="RGB") == (0, 0, 0)).all() + assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4) + def test_sdl_render_bad_types() -> None: with pytest.raises(TypeError): From b81395a564eb9b742ac62be597a74be77816a4c6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 30 Mar 2023 21:14:15 -0700 Subject: [PATCH 0739/1101] Prepare 15.0.1 release. --- CHANGELOG.md | 2 ++ tcod/sdl/render.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd60ae8..8960156e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [15.0.1] - 2023-03-30 ### Added - Added support for `tcod.sdl` namespace packages. diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index b39a994e..d7e3226d 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -507,7 +507,7 @@ def read_pixels( Returns: The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels. - .. versionadded:: Unreleased + .. versionadded:: 15.0 """ FORMATS: Final = {"RGB": lib.SDL_PIXELFORMAT_RGB24, "RGBA": lib.SDL_PIXELFORMAT_RGBA32} sdl_format = FORMATS.get(format) if isinstance(format, str) else format From fadbd85c5296ac8311f5deada7eccf269f294a7c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 16:49:29 -0700 Subject: [PATCH 0740/1101] Update VSCode formatting config. --- .vscode/extensions.json | 1 + .vscode/settings.json | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 59e34caf..e9737cb3 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,6 +6,7 @@ "austin.code-gnu-global", "editorconfig.editorconfig", "ms-python.python", + "ms-python.black-formatter", "ms-python.vscode-pylance", "ms-vscode.cpptools", "redhat.vscode-yaml", diff --git a/.vscode/settings.json b/.vscode/settings.json index e49af1b9..f251f993 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,7 @@ "--follow-imports=silent", "--show-column-numbers" ], - "python.formatting.provider": "black", + "python.formatting.provider": "none", "files.associations": { "*.spec": "python", }, @@ -424,5 +424,8 @@ ], "python.testing.pytestArgs": [], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } } \ No newline at end of file From 9bcc5c6afe9addd9281a44471e3bb9283c00726e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 18:18:23 -0700 Subject: [PATCH 0741/1101] Migrate setuptools meta data to pyproject.toml. Replaces version code with setuptools-scm. --- pyproject.toml | 65 ++++++++++++++++++++++++++- setup.cfg | 4 -- setup.py | 117 ++++++------------------------------------------- 3 files changed, 77 insertions(+), 109 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f27434f9..08fcbb7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] requires = [ - "setuptools==60.8.2", + "setuptools>=61.0.0", + "setuptools_scm[toml]>=6.2", "wheel>=0.37.1", "cffi>=1.15", "pycparser>=2.14", @@ -9,6 +10,66 @@ requires = [ ] build-backend = "setuptools.build_meta" +[project] +name = "tcod" +dynamic = ["version"] +description = "The official Python port of libtcod." +authors = [{ name = "Kyle Benesch", email = "4b796c65+tcod@gmail.com" }] +readme = "README.rst" +requires-python = ">=3.7" +license = { text = "Simplified BSD License" } +dependencies = [ + "cffi>=1.15", + 'numpy>=1.21.4; implementation_name != "pypy"', + "typing_extensions", +] +keywords = [ + "roguelike", + "cffi", + "Unicode", + "libtcod", + "field-of-view", + "pathfinding", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Win32 (MS Windows)", + "Environment :: MacOS X", + "Environment :: X11 Applications", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Games/Entertainment", + "Topic :: Multimedia :: Graphics", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", +] + +[project.entry-points.pyinstaller40] +hook-dirs = "tcod.__pyinstaller:get_hook_dirs" + +[project.urls] +Homepage = "https://github.com/libtcod/python-tcod" +Documentation = "https://python-tcod.readthedocs.io" +Changelog = "https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md" +Source = "https://github.com/libtcod/python-tcod" +Tracker = "https://github.com/libtcod/python-tcod/issues" +Forum = "https://github.com/libtcod/python-tcod/discussions" + +[tool.setuptools_scm] +write_to = "tcod/version.py" + [tool.black] line-length = 120 target-version = ["py37"] @@ -21,7 +82,7 @@ line_length = 120 [tool.pytest.ini_options] minversion = "6.0" -required_plugins = ["pytest-cov"] +required_plugins = ["pytest-cov", "pytest-benchmark"] testpaths = ["tcod/", "tests/", "docs/"] addopts = [ "--doctest-modules", diff --git a/setup.cfg b/setup.cfg index c70c33ef..33d12878 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,3 @@ -[options.entry_points] -pyinstaller40 = - hook-dirs = tcod.__pyinstaller:get_hook_dirs - [bdist_wheel] py-limited-api = cp36 diff --git a/setup.py b/setup.py index 4dadf34f..dc2139e7 100755 --- a/setup.py +++ b/setup.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 +"""Python-tcod setup script.""" from __future__ import annotations import platform -import re import subprocess import sys -import warnings from pathlib import Path -from typing import List from setuptools import setup @@ -16,40 +14,9 @@ SETUP_DIR = Path(__file__).parent # setup.py current directory -def get_version() -> str: - """Get the current version from a git tag, or by reading tcod/version.py""" - if (SETUP_DIR / ".git").exists(): - # "--tags" is required to workaround actions/checkout's broken annotated tag handing. - # https://github.com/actions/checkout/issues/290 - tag = subprocess.check_output(["git", "describe", "--abbrev=0", "--tags"], universal_newlines=True).strip() - assert not tag.startswith("v") - version = tag - - # add .devNN if needed - log = subprocess.check_output(["git", "log", f"{tag}..HEAD", "--oneline"], universal_newlines=True) - commits_since_tag = log.count("\n") - if commits_since_tag: - version += ".dev%i" % commits_since_tag - - # update tcod/version.py - (SETUP_DIR / "tcod/version.py").write_text(f'__version__ = "{version}"\n', encoding="utf-8") - return version - else: # Not a Git repository. - try: - match = re.match(r'__version__ = "(\S+)"', (SETUP_DIR / "tcod/version.py").read_text(encoding="utf-8")) - assert match - return match.groups()[0] - except FileNotFoundError: - warnings.warn("Unknown version: Not in a Git repository and not from a sdist bundle or wheel.") - return "0.0.0" - - -is_pypy = platform.python_implementation() == "PyPy" - - -def get_package_data() -> List[str]: - """get data files which will be included in the main tcod/ directory""" - BITSIZE, _ = platform.architecture() +def get_package_data() -> list[str]: + """Get data files which will be included in the main tcod/ directory.""" + BIT_SIZE, _ = platform.architecture() files = [ "py.typed", "lib/LIBTCOD-CREDITS.txt", @@ -57,7 +24,7 @@ def get_package_data() -> List[str]: "lib/README-SDL.txt", ] if "win32" in sys.platform: - if BITSIZE == "32bit": + if BIT_SIZE == "32bit": files += ["x86/SDL2.dll"] else: files += ["x64/SDL2.dll"] @@ -70,19 +37,20 @@ def check_sdl_version() -> None: """Check the local SDL version on Linux distributions.""" if not sys.platform.startswith("linux"): return - needed_version = "%i.%i.%i" % SDL_VERSION_NEEDED + needed_version = "{}.{}.{}".format(*SDL_VERSION_NEEDED) try: sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() - except FileNotFoundError: - raise RuntimeError( - "libsdl2-dev or equivalent must be installed on your system" - " and must be at least version %s." - "\nsdl2-config must be on PATH." % (needed_version,) + except FileNotFoundError as exc: + msg = ( + f"libsdl2-dev or equivalent must be installed on your system and must be at least version {needed_version}." + "\nsdl2-config must be on PATH." ) - print("Found SDL %s." % (sdl_version_str,)) + raise RuntimeError(msg) from exc + print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_VERSION_NEEDED: - raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) + msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})" + raise RuntimeError(msg) if not (SETUP_DIR / "libtcod/src").exists(): @@ -92,67 +60,10 @@ def check_sdl_version() -> None: check_sdl_version() -needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) -pytest_runner = ["pytest-runner"] if needs_pytest else [] - setup( - name="tcod", - version=get_version(), - author="Kyle Benesch", - author_email="4b796c65+tcod@gmail.com", - description="The official Python port of libtcod.", - long_description=(SETUP_DIR / "README.rst").read_text(encoding="utf-8"), - url="https://github.com/libtcod/python-tcod", - project_urls={ - "Documentation": "https://python-tcod.readthedocs.io", - "Changelog": "https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md", - "Source": "https://github.com/libtcod/python-tcod", - "Tracker": "https://github.com/libtcod/python-tcod/issues", - "Forum": "https://github.com/libtcod/python-tcod/discussions", - }, py_modules=["libtcodpy"], packages=["tcod", "tcod.sdl", "tcod.__pyinstaller"], package_data={"tcod": get_package_data()}, - python_requires=">=3.7", - setup_requires=[ - *pytest_runner, - "cffi>=1.15", - "requests>=2.28.1", - "pycparser>=2.14", - "pcpp==1.30", - ], - install_requires=[ - "cffi>=1.15", # Also required by pyproject.toml. - "numpy>=1.21.4" if not is_pypy else "", - "typing_extensions", - ], cffi_modules=["build_libtcod.py:ffi"], - tests_require=["pytest", "pytest-cov", "pytest-benchmark"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Win32 (MS Windows)", - "Environment :: MacOS X", - "Environment :: X11 Applications", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: POSIX", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Games/Entertainment", - "Topic :: Multimedia :: Graphics", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed", - ], - keywords="roguelike cffi Unicode libtcod field-of-view pathfinding", platforms=["Windows", "MacOS", "Linux"], - license="Simplified BSD License", ) From 5b59572af6944eda81c6c0cb1549ce9197ca29cc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 18:36:43 -0700 Subject: [PATCH 0742/1101] Clean up PyInstaller hooks. Just general maintenance. --- .vscode/settings.json | 1 + CHANGELOG.md | 3 +++ tcod/__pyinstaller/__init__.py | 9 +++++---- tcod/__pyinstaller/hook-tcod.py | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f251f993..c2a007d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -155,6 +155,7 @@ "heapify", "heightmap", "hflip", + "hiddenimports", "HIGHDPI", "hillclimb", "hline", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8960156e..6593ee6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -255,6 +255,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Prevent division by zero from recommended-console-size functions. ## [12.0.0] - 2021-03-05 +### Added +- Now includes PyInstaller hooks within the package itself. + ### Deprecated - The Random class will now warn if the seed it's given will not used deterministically. It will no longer accept non-integer seeds in the future. diff --git a/tcod/__pyinstaller/__init__.py b/tcod/__pyinstaller/__init__.py index a2a36e88..7224bb10 100644 --- a/tcod/__pyinstaller/__init__.py +++ b/tcod/__pyinstaller/__init__.py @@ -1,8 +1,9 @@ """PyInstaller entry point for tcod.""" -import os -from typing import List +from __future__ import annotations +from pathlib import Path -def get_hook_dirs() -> List[str]: + +def get_hook_dirs() -> list[str]: """Return the current directory.""" - return [os.path.dirname(__file__)] + return [str(Path(__file__).parent)] diff --git a/tcod/__pyinstaller/hook-tcod.py b/tcod/__pyinstaller/hook-tcod.py index 2df121e6..9790d583 100644 --- a/tcod/__pyinstaller/hook-tcod.py +++ b/tcod/__pyinstaller/hook-tcod.py @@ -1,8 +1,8 @@ """PyInstaller hook for tcod. -Added here after tcod 12.0.0. +There were added since tcod 12.0.0. -If this hook is modified then the contributed hook needs to be removed from: +If this hook is ever modified then the contributed hook needs to be removed from: https://github.com/pyinstaller/pyinstaller-hooks-contrib """ from PyInstaller.utils.hooks import collect_dynamic_libs # type: ignore From 66da1b241bbe1eeb0336b8d0f8051f0c01d461c9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 19:41:15 -0700 Subject: [PATCH 0743/1101] Clean up internal functions. Mostly following Ruff notes to upgrade code. Add __init__.py to test package folder to resolve issues with pytest. --- setup.cfg | 2 +- tcod/_internal.py | 95 ++++++++++++++++++++++++----------------------- tests/__init__.py | 1 + 3 files changed, 50 insertions(+), 48 deletions(-) create mode 100644 tests/__init__.py diff --git a/setup.cfg b/setup.cfg index 33d12878..0e25efb1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ py-limited-api = cp36 test=pytest [flake8] -ignore = E203 W503 +ignore = E203 W503 TYP001 max-line-length = 130 [mypy] diff --git a/tcod/_internal.py b/tcod/_internal.py index 08730332..694c9aaf 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,21 +1,23 @@ -"""This module internal helper functions used by the rest of the library.""" +"""Internal helper functions used by the rest of the library.""" from __future__ import annotations import functools import warnings -from typing import Any, AnyStr, Callable, TypeVar, cast +from types import TracebackType +from typing import Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar, cast import numpy as np -from numpy.typing import NDArray -from typing_extensions import Literal, NoReturn +from numpy.typing import ArrayLike, NDArray +from typing_extensions import Literal from tcod.loader import ffi, lib FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) +T = TypeVar("T") -def deprecate(message: str, category: Any = DeprecationWarning, stacklevel: int = 0) -> Callable[[F], F]: +def deprecate(message: str, category: type[Warning] = DeprecationWarning, stacklevel: int = 0) -> Callable[[F], F]: """Return a decorator which adds a warning to functions.""" def decorator(func: F) -> F: @@ -23,7 +25,7 @@ def decorator(func: F) -> F: return func @functools.wraps(func) - def wrapper(*args, **kwargs): # type: ignore + def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 warnings.warn(message, category, stacklevel=stacklevel + 2) return func(*args, **kwargs) @@ -35,7 +37,7 @@ def wrapper(*args, **kwargs): # type: ignore def pending_deprecate( message: str = "This function may be deprecated in the future." " Consider raising an issue on GitHub if you need this feature.", - category: Any = PendingDeprecationWarning, + category: type[Warning] = PendingDeprecationWarning, stacklevel: int = 0, ) -> Callable[[F], F]: """Like deprecate, but the default parameters are filled out for a generic pending deprecation warning.""" @@ -43,9 +45,11 @@ def pending_deprecate( def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]: + """Verify and return a Numpy order string.""" order = order.upper() # type: ignore if order not in ("C", "F"): - raise TypeError("order must be 'C' or 'F', not %r" % (order,)) + msg = f"order must be 'C' or 'F', not {order!r}" + raise TypeError(msg) return order @@ -61,7 +65,7 @@ def _check(error: int) -> int: return error -def _check_p(pointer: Any) -> Any: +def _check_p(pointer: T) -> T: """Treats NULL pointers as errors and raises a libtcod exception.""" if not pointer: _raise_tcod_error() @@ -79,19 +83,19 @@ def _check_warn(error: int, stacklevel: int = 2) -> int: return error -def _unpack_char_p(char_p: Any) -> str: +def _unpack_char_p(char_p: Any) -> str: # noqa: ANN401 if char_p == ffi.NULL: return "" return ffi.string(char_p).decode() # type: ignore -def _int(int_or_str: Any) -> int: +def _int(int_or_str: SupportsInt | str | bytes) -> int: """Return an integer where a single character string may be expected.""" if isinstance(int_or_str, str): return ord(int_or_str) if isinstance(int_or_str, bytes): return int_or_str[0] - return int(int_or_str) # check for __count__ + return int(int_or_str) def _bytes(string: AnyStr) -> bytes: @@ -103,8 +107,8 @@ def _bytes(string: AnyStr) -> bytes: def _unicode(string: AnyStr, stacklevel: int = 2) -> str: if isinstance(string, bytes): warnings.warn( - ("Passing byte strings as parameters to Unicode functions is " "deprecated."), - DeprecationWarning, + "Passing byte strings as parameters to Unicode functions is deprecated.", + FutureWarning, stacklevel=stacklevel + 1, ) return string.decode("latin-1") @@ -114,8 +118,8 @@ def _unicode(string: AnyStr, stacklevel: int = 2) -> str: def _fmt(string: str, stacklevel: int = 2) -> bytes: if isinstance(string, bytes): warnings.warn( - ("Passing byte strings as parameters to Unicode functions is " "deprecated."), - DeprecationWarning, + "Passing byte strings as parameters to Unicode functions is deprecated.", + FutureWarning, stacklevel=stacklevel + 1, ) string = string.decode("latin-1") @@ -135,51 +139,46 @@ class _PropagateException: """ def __init__(self) -> None: - # (exception, exc_value, traceback) - self.exc_info = None # type: Any + self.caught: BaseException | None = None - def propagate(self, *exc_info: Any) -> None: + def propagate(self, *exc_info: Any) -> None: # noqa: ANN401 """Set an exception to be raised once this context exits. If multiple errors are caught, only keep the first exception raised. """ - if not self.exc_info: - self.exc_info = exc_info + if self.caught is None: + self.caught = exc_info[1] def __enter__(self) -> Callable[[Any], None]: """Once in context, only the propagate call is needed to use this class effectively.""" return self.propagate - def __exit__(self, type: Any, value: Any, traceback: Any) -> None: + def __exit__( + self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> None: """If we're holding on to an exception, raise it now. - Prefers our held exception over any current raising error. - - self.exc_info is reset now in case of nested manager shenanigans. + self.caught is reset now in case of nested manager shenanigans. """ - if self.exc_info: - type, value, traceback = self.exc_info - self.exc_info = None - if type: - # Python 2/3 compatible throw - exception = type(value) - exception.__traceback__ = traceback - raise exception - - -class _CDataWrapper(object): - def __init__(self, *args: Any, **kwargs: Any): + to_raise, self.caught = self.caught, None + if to_raise is not None: + raise to_raise from value + + +class _CDataWrapper: + """A generally deprecated CData wrapper class used by libtcodpy.""" + + def __init__(self, *args: Any, **kwargs: Any): # noqa: ANN401 self.cdata = self._get_cdata_from_args(*args, **kwargs) if self.cdata is None: self.cdata = ffi.NULL - super(_CDataWrapper, self).__init__() + super().__init__() @staticmethod - def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: + def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 if len(args) == 1 and isinstance(args[0], ffi.CData) and not kwargs: return args[0] - else: - return None + return None def __hash__(self) -> int: return hash(self.cdata) @@ -199,11 +198,11 @@ def __setattr__(self, attr: str, value: Any) -> None: if hasattr(self, "cdata") and hasattr(self.cdata, attr): setattr(self.cdata, attr, value) else: - super(_CDataWrapper, self).__setattr__(attr, value) + super().__setattr__(attr, value) -def _console(console: Any) -> Any: - """Return a cffi console.""" +def _console(console: Any) -> Any: # noqa: ANN401 + """Return a cffi console pointer.""" try: return console.console_c except AttributeError: @@ -219,14 +218,16 @@ def _console(console: Any) -> Any: return ffi.NULL -class TempImage(object): +class TempImage: """An Image-like container for NumPy arrays.""" - def __init__(self, array: Any): + def __init__(self, array: ArrayLike) -> None: + """Initialize an image from the given array. May copy or reference the array.""" self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape if depth != 3: - raise TypeError("Array must have RGB channels. Shape is: %r" % (self._array.shape,)) + msg = f"Array must have RGB channels. Shape is: {self._array.shape!r}" + raise TypeError(msg) self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) self._mipmaps = ffi.new( "struct TCOD_mipmap_*", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..38bb211b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test package.""" From 74d6bb8721b14b4905e90c1b5cb7afce156632fa Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 22:44:04 -0700 Subject: [PATCH 0744/1101] Restrict setuptools version to fix editable installs. --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 08fcbb7a..6c4c9a34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,8 @@ [build-system] requires = [ - "setuptools>=61.0.0", + # Newer versions of setuptools break editable installs + # https://github.com/pypa/setuptools/issues/3548 + "setuptools >=61.0.0, <64.0.0", "setuptools_scm[toml]>=6.2", "wheel>=0.37.1", "cffi>=1.15", From 4570f56d791d53d24194321cae9ca1afa61bc994 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 22:53:23 -0700 Subject: [PATCH 0745/1101] Fix DLL path issues on Windows. `__path__` is incorrect now that this project has namespace packages. --- CHANGELOG.md | 2 ++ tcod/loader.py | 15 +++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6593ee6b..13a11f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- DLL loader could fail to load `SDL2.dll` when other tcod namespace packages were installed. ## [15.0.1] - 2023-03-30 ### Added diff --git a/tcod/loader.py b/tcod/loader.py index 13efe66d..33ff0dd9 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -4,12 +4,11 @@ import os import platform import sys +from pathlib import Path from typing import Any # noqa: F401 import cffi # type: ignore -from tcod import __path__ - __sdl_version__ = "" ffi_check = cffi.FFI() @@ -46,20 +45,12 @@ def get_architecture() -> str: def get_sdl_version() -> str: sdl_version = ffi.new("SDL_version*") lib.SDL_GetVersion(sdl_version) - return "%s.%s.%s" % ( - sdl_version.major, - sdl_version.minor, - sdl_version.patch, - ) + return f"{sdl_version.major}.{sdl_version.minor}.{sdl_version.patch}" if sys.platform == "win32": # add Windows dll's to PATH - _bits, _linkage = platform.architecture() - os.environ["PATH"] = "%s;%s" % ( - os.path.join(__path__[0], get_architecture()), - os.environ["PATH"], - ) + os.environ["PATH"] = f"""{Path(__file__).parent / get_architecture()}{os.pathsep}{os.environ["PATH"]}""" class _Mock(object): From 0ce1a310725203c606ac67f4e4678087be0a85fd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 22:54:43 -0700 Subject: [PATCH 0746/1101] Minor updates to libtcodpy samples. Mostly need to preserve this as a reference to older uses of the code. At least until a cull of the API. --- examples/samples_libtcodpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index c503cefb..c59b5b46 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -1268,7 +1268,7 @@ def render_name(first, key, mouse): if ng_nbsets == 0: # parse all *.cfg files in data/namegen for file in os.listdir(get_data("namegen")): - if file.find(b".cfg") > 0: + if file.find(".cfg") > 0: libtcod.namegen_parse(get_data(os.path.join("namegen", file))) # get the sets list ng_sets = libtcod.namegen_get_sets() @@ -1597,7 +1597,7 @@ def __init__(self, name, func): libtcod.console_set_default_foreground(None, libtcod.grey) libtcod.console_set_default_background(None, libtcod.black) libtcod.console_print_ex(None, 42, 46 - (libtcod.NB_RENDERERS + 1), libtcod.BKGND_SET, libtcod.LEFT, "Renderer :") - for i in range(libtcod.NB_RENDERERS): + for i in range(len(renderer_name)): if i == cur_renderer: libtcod.console_set_default_foreground(None, libtcod.white) libtcod.console_set_default_background(None, libtcod.light_blue) From a828e13c96d71f2c29cbee7350668b7a9360757b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 23:12:23 -0700 Subject: [PATCH 0747/1101] Clean up and deprecate colors. Colors removed from __all__ and warnings are given when they're accessed. --- CHANGELOG.md | 3 + build_libtcod.py | 1 - pyproject.toml | 1 + tcod/__init__.py | 214 +++------------------------------------------- tcod/color.py | 48 +++++------ tcod/constants.py | 197 ------------------------------------------ tcod/libtcodpy.py | 197 ------------------------------------------ 7 files changed, 36 insertions(+), 625 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a11f93..cf27a946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Deprecated +- Deprecated all color constants + ### Fixed - DLL loader could fail to load `SDL2.dll` when other tcod namespace packages were installed. diff --git a/build_libtcod.py b/build_libtcod.py index e8d800cf..4ff65329 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -360,7 +360,6 @@ def write_library_constants() -> None: continue color = tcod.color.Color._new_from_cdata(value) f.write(f"{name[5:]} = {color!r}\n") - all_names.append(name[5:]) all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") diff --git a/pyproject.toml b/pyproject.toml index 6c4c9a34..495cacb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -135,6 +135,7 @@ ignore = [ "E501", # line-too-long "S101", # assert "ANN101", # missing-type-self + "ANN102", # missing-type-cls "D203", # one-blank-line-before-class "D204", # one-blank-line-after-class "D213", # multi-line-summary-second-line diff --git a/tcod/__init__.py b/tcod/__init__.py index 92885a2f..4a4b5173 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -8,13 +8,12 @@ """ from __future__ import annotations -import sys import warnings from pkgutil import extend_path __path__ = extend_path(__path__, __name__) -from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset +from tcod import bsp, color, console, constants, context, event, image, los, map, noise, path, random, tileset from tcod.console import Console # noqa: F401 from tcod.constants import * # noqa: F4 from tcod.libtcodpy import * # noqa: F4 @@ -25,12 +24,20 @@ except ImportError: # Gets imported without version.py by ReadTheDocs __version__ = "" -if sys.version_info < (3, 6): + +def __getattr__(name: str) -> color.Color: + """Mark access to color constants as deprecated.""" + value: color.Color | None = getattr(constants, name, None) + if value is None: + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) warnings.warn( - "Support for Python 3.5 has been dropped from python-tcod.", - DeprecationWarning, + f"Color constants will be removed from future releases.\nReplace `tcod.{name}` with `{tuple(value)}`.", + FutureWarning, stacklevel=2, ) + return value + __all__ = [ # noqa: F405 "__version__", @@ -604,202 +611,5 @@ "TYPE_VALUELIST13", "TYPE_VALUELIST14", "TYPE_VALUELIST15", - "amber", - "azure", - "black", - "blue", - "brass", - "celadon", - "chartreuse", - "copper", - "crimson", - "cyan", - "dark_amber", - "dark_azure", - "dark_blue", - "dark_chartreuse", - "dark_crimson", - "dark_cyan", - "dark_flame", - "dark_fuchsia", - "dark_gray", - "dark_green", - "dark_grey", - "dark_han", - "dark_lime", - "dark_magenta", - "dark_orange", - "dark_pink", - "dark_purple", - "dark_red", - "dark_sea", - "dark_sepia", - "dark_sky", - "dark_turquoise", - "dark_violet", - "dark_yellow", - "darker_amber", - "darker_azure", - "darker_blue", - "darker_chartreuse", - "darker_crimson", - "darker_cyan", - "darker_flame", - "darker_fuchsia", - "darker_gray", - "darker_green", - "darker_grey", - "darker_han", - "darker_lime", - "darker_magenta", - "darker_orange", - "darker_pink", - "darker_purple", - "darker_red", - "darker_sea", - "darker_sepia", - "darker_sky", - "darker_turquoise", - "darker_violet", - "darker_yellow", - "darkest_amber", - "darkest_azure", - "darkest_blue", - "darkest_chartreuse", - "darkest_crimson", - "darkest_cyan", - "darkest_flame", - "darkest_fuchsia", - "darkest_gray", - "darkest_green", - "darkest_grey", - "darkest_han", - "darkest_lime", - "darkest_magenta", - "darkest_orange", - "darkest_pink", - "darkest_purple", - "darkest_red", - "darkest_sea", - "darkest_sepia", - "darkest_sky", - "darkest_turquoise", - "darkest_violet", - "darkest_yellow", - "desaturated_amber", - "desaturated_azure", - "desaturated_blue", - "desaturated_chartreuse", - "desaturated_crimson", - "desaturated_cyan", - "desaturated_flame", - "desaturated_fuchsia", - "desaturated_green", - "desaturated_han", - "desaturated_lime", - "desaturated_magenta", - "desaturated_orange", - "desaturated_pink", - "desaturated_purple", - "desaturated_red", - "desaturated_sea", - "desaturated_sky", - "desaturated_turquoise", - "desaturated_violet", - "desaturated_yellow", - "flame", - "fuchsia", - "gold", - "gray", - "green", - "grey", - "han", - "light_amber", - "light_azure", - "light_blue", - "light_chartreuse", - "light_crimson", - "light_cyan", - "light_flame", - "light_fuchsia", - "light_gray", - "light_green", - "light_grey", - "light_han", - "light_lime", - "light_magenta", - "light_orange", - "light_pink", - "light_purple", - "light_red", - "light_sea", - "light_sepia", - "light_sky", - "light_turquoise", - "light_violet", - "light_yellow", - "lighter_amber", - "lighter_azure", - "lighter_blue", - "lighter_chartreuse", - "lighter_crimson", - "lighter_cyan", - "lighter_flame", - "lighter_fuchsia", - "lighter_gray", - "lighter_green", - "lighter_grey", - "lighter_han", - "lighter_lime", - "lighter_magenta", - "lighter_orange", - "lighter_pink", - "lighter_purple", - "lighter_red", - "lighter_sea", - "lighter_sepia", - "lighter_sky", - "lighter_turquoise", - "lighter_violet", - "lighter_yellow", - "lightest_amber", - "lightest_azure", - "lightest_blue", - "lightest_chartreuse", - "lightest_crimson", - "lightest_cyan", - "lightest_flame", - "lightest_fuchsia", - "lightest_gray", - "lightest_green", - "lightest_grey", - "lightest_han", - "lightest_lime", - "lightest_magenta", - "lightest_orange", - "lightest_pink", - "lightest_purple", - "lightest_red", - "lightest_sea", - "lightest_sepia", - "lightest_sky", - "lightest_turquoise", - "lightest_violet", - "lightest_yellow", - "lime", - "magenta", - "orange", - "peach", - "pink", - "purple", - "red", - "sea", - "sepia", - "silver", - "sky", - "turquoise", - "violet", - "white", - "yellow", # --- End constants.py --- ] diff --git a/tcod/color.py b/tcod/color.py index 470d7ac7..f5fed4c2 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,6 +1,4 @@ -""" - -""" +"""Old libtcod color management.""" from __future__ import annotations import warnings @@ -32,7 +30,7 @@ def r(self) -> int: return int(self[0]) @r.setter - @deprecate("Setting color attributes has been deprecated.") + @deprecate("Setting color attributes has been deprecated.", FutureWarning) def r(self, value: int) -> None: self[0] = value & 0xFF @@ -46,7 +44,7 @@ def g(self) -> int: return int(self[1]) @g.setter - @deprecate("Setting color attributes has been deprecated.") + @deprecate("Setting color attributes has been deprecated.", FutureWarning) def g(self, value: int) -> None: self[1] = value & 0xFF @@ -60,35 +58,35 @@ def b(self) -> int: return int(self[2]) @b.setter - @deprecate("Setting color attributes has been deprecated.") + @deprecate("Setting color attributes has been deprecated.", FutureWarning) def b(self, value: int) -> None: self[2] = value & 0xFF @classmethod - def _new_from_cdata(cls, cdata: Any) -> Color: + def _new_from_cdata(cls, cdata: Any) -> Color: # noqa: ANN401 return cls(cdata.r, cdata.g, cdata.b) - def __getitem__(self, index: Any) -> Any: - """ + def __getitem__(self, index: Any) -> Any: # noqa: ANN401 + """Return a color channel. + .. deprecated:: 9.2 Accessing colors via a letter index is deprecated. """ - try: - return super().__getitem__(index) - except TypeError: + if isinstance(index, str): warnings.warn( "Accessing colors via a letter index is deprecated", DeprecationWarning, stacklevel=2, ) return super().__getitem__("rgb".index(index)) + return super().__getitem__(index) - @deprecate("This class will not be mutable in the future.") - def __setitem__(self, index: Any, value: Any) -> None: - try: - super().__setitem__(index, value) - except TypeError: + @deprecate("This class will not be mutable in the future.", FutureWarning) + def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401 + if isinstance(index, str): super().__setitem__("rgb".index(index), value) + else: + super().__setitem__(index, value) def __eq__(self, other: Any) -> bool: """Compare equality between colors. @@ -100,7 +98,7 @@ def __eq__(self, other: Any) -> bool: except TypeError: return False - @deprecate("Use NumPy instead for color math operations.") + @deprecate("Use NumPy instead for color math operations.", FutureWarning) def __add__(self, other: Any) -> Color: # type: ignore[override] """Add two colors together. @@ -109,7 +107,7 @@ def __add__(self, other: Any) -> Color: # type: ignore[override] """ return Color._new_from_cdata(lib.TCOD_color_add(self, other)) - @deprecate("Use NumPy instead for color math operations.") + @deprecate("Use NumPy instead for color math operations.", FutureWarning) def __sub__(self, other: Any) -> Color: """Subtract one color from another. @@ -118,7 +116,7 @@ def __sub__(self, other: Any) -> Color: """ return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) - @deprecate("Use NumPy instead for color math operations.") + @deprecate("Use NumPy instead for color math operations.", FutureWarning) def __mul__(self, other: Any) -> Color: """Multiply with a scaler or another color. @@ -127,14 +125,8 @@ def __mul__(self, other: Any) -> Color: """ if isinstance(other, (Color, list, tuple)): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) - else: - return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) + return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) def __repr__(self) -> str: """Return a printable representation of the current color.""" - return "%s(%r, %r, %r)" % ( - self.__class__.__name__, - self.r, - self.g, - self.b, - ) + return f"{self.__class__.__name__}({self.r!r}, {self.g!r}, {self.b!r})" diff --git a/tcod/constants.py b/tcod/constants.py index 4cb84313..f0a596dc 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -801,201 +801,4 @@ "TYPE_VALUELIST13", "TYPE_VALUELIST14", "TYPE_VALUELIST15", - "amber", - "azure", - "black", - "blue", - "brass", - "celadon", - "chartreuse", - "copper", - "crimson", - "cyan", - "dark_amber", - "dark_azure", - "dark_blue", - "dark_chartreuse", - "dark_crimson", - "dark_cyan", - "dark_flame", - "dark_fuchsia", - "dark_gray", - "dark_green", - "dark_grey", - "dark_han", - "dark_lime", - "dark_magenta", - "dark_orange", - "dark_pink", - "dark_purple", - "dark_red", - "dark_sea", - "dark_sepia", - "dark_sky", - "dark_turquoise", - "dark_violet", - "dark_yellow", - "darker_amber", - "darker_azure", - "darker_blue", - "darker_chartreuse", - "darker_crimson", - "darker_cyan", - "darker_flame", - "darker_fuchsia", - "darker_gray", - "darker_green", - "darker_grey", - "darker_han", - "darker_lime", - "darker_magenta", - "darker_orange", - "darker_pink", - "darker_purple", - "darker_red", - "darker_sea", - "darker_sepia", - "darker_sky", - "darker_turquoise", - "darker_violet", - "darker_yellow", - "darkest_amber", - "darkest_azure", - "darkest_blue", - "darkest_chartreuse", - "darkest_crimson", - "darkest_cyan", - "darkest_flame", - "darkest_fuchsia", - "darkest_gray", - "darkest_green", - "darkest_grey", - "darkest_han", - "darkest_lime", - "darkest_magenta", - "darkest_orange", - "darkest_pink", - "darkest_purple", - "darkest_red", - "darkest_sea", - "darkest_sepia", - "darkest_sky", - "darkest_turquoise", - "darkest_violet", - "darkest_yellow", - "desaturated_amber", - "desaturated_azure", - "desaturated_blue", - "desaturated_chartreuse", - "desaturated_crimson", - "desaturated_cyan", - "desaturated_flame", - "desaturated_fuchsia", - "desaturated_green", - "desaturated_han", - "desaturated_lime", - "desaturated_magenta", - "desaturated_orange", - "desaturated_pink", - "desaturated_purple", - "desaturated_red", - "desaturated_sea", - "desaturated_sky", - "desaturated_turquoise", - "desaturated_violet", - "desaturated_yellow", - "flame", - "fuchsia", - "gold", - "gray", - "green", - "grey", - "han", - "light_amber", - "light_azure", - "light_blue", - "light_chartreuse", - "light_crimson", - "light_cyan", - "light_flame", - "light_fuchsia", - "light_gray", - "light_green", - "light_grey", - "light_han", - "light_lime", - "light_magenta", - "light_orange", - "light_pink", - "light_purple", - "light_red", - "light_sea", - "light_sepia", - "light_sky", - "light_turquoise", - "light_violet", - "light_yellow", - "lighter_amber", - "lighter_azure", - "lighter_blue", - "lighter_chartreuse", - "lighter_crimson", - "lighter_cyan", - "lighter_flame", - "lighter_fuchsia", - "lighter_gray", - "lighter_green", - "lighter_grey", - "lighter_han", - "lighter_lime", - "lighter_magenta", - "lighter_orange", - "lighter_pink", - "lighter_purple", - "lighter_red", - "lighter_sea", - "lighter_sepia", - "lighter_sky", - "lighter_turquoise", - "lighter_violet", - "lighter_yellow", - "lightest_amber", - "lightest_azure", - "lightest_blue", - "lightest_chartreuse", - "lightest_crimson", - "lightest_cyan", - "lightest_flame", - "lightest_fuchsia", - "lightest_gray", - "lightest_green", - "lightest_grey", - "lightest_han", - "lightest_lime", - "lightest_magenta", - "lightest_orange", - "lightest_pink", - "lightest_purple", - "lightest_red", - "lightest_sea", - "lightest_sepia", - "lightest_sky", - "lightest_turquoise", - "lightest_violet", - "lightest_yellow", - "lime", - "magenta", - "orange", - "peach", - "pink", - "purple", - "red", - "sea", - "sepia", - "silver", - "sky", - "turquoise", - "violet", - "white", - "yellow", ] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 1271a3c1..51974108 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4755,202 +4755,5 @@ def _atexit_verify() -> None: "TYPE_VALUELIST13", "TYPE_VALUELIST14", "TYPE_VALUELIST15", - "amber", - "azure", - "black", - "blue", - "brass", - "celadon", - "chartreuse", - "copper", - "crimson", - "cyan", - "dark_amber", - "dark_azure", - "dark_blue", - "dark_chartreuse", - "dark_crimson", - "dark_cyan", - "dark_flame", - "dark_fuchsia", - "dark_gray", - "dark_green", - "dark_grey", - "dark_han", - "dark_lime", - "dark_magenta", - "dark_orange", - "dark_pink", - "dark_purple", - "dark_red", - "dark_sea", - "dark_sepia", - "dark_sky", - "dark_turquoise", - "dark_violet", - "dark_yellow", - "darker_amber", - "darker_azure", - "darker_blue", - "darker_chartreuse", - "darker_crimson", - "darker_cyan", - "darker_flame", - "darker_fuchsia", - "darker_gray", - "darker_green", - "darker_grey", - "darker_han", - "darker_lime", - "darker_magenta", - "darker_orange", - "darker_pink", - "darker_purple", - "darker_red", - "darker_sea", - "darker_sepia", - "darker_sky", - "darker_turquoise", - "darker_violet", - "darker_yellow", - "darkest_amber", - "darkest_azure", - "darkest_blue", - "darkest_chartreuse", - "darkest_crimson", - "darkest_cyan", - "darkest_flame", - "darkest_fuchsia", - "darkest_gray", - "darkest_green", - "darkest_grey", - "darkest_han", - "darkest_lime", - "darkest_magenta", - "darkest_orange", - "darkest_pink", - "darkest_purple", - "darkest_red", - "darkest_sea", - "darkest_sepia", - "darkest_sky", - "darkest_turquoise", - "darkest_violet", - "darkest_yellow", - "desaturated_amber", - "desaturated_azure", - "desaturated_blue", - "desaturated_chartreuse", - "desaturated_crimson", - "desaturated_cyan", - "desaturated_flame", - "desaturated_fuchsia", - "desaturated_green", - "desaturated_han", - "desaturated_lime", - "desaturated_magenta", - "desaturated_orange", - "desaturated_pink", - "desaturated_purple", - "desaturated_red", - "desaturated_sea", - "desaturated_sky", - "desaturated_turquoise", - "desaturated_violet", - "desaturated_yellow", - "flame", - "fuchsia", - "gold", - "gray", - "green", - "grey", - "han", - "light_amber", - "light_azure", - "light_blue", - "light_chartreuse", - "light_crimson", - "light_cyan", - "light_flame", - "light_fuchsia", - "light_gray", - "light_green", - "light_grey", - "light_han", - "light_lime", - "light_magenta", - "light_orange", - "light_pink", - "light_purple", - "light_red", - "light_sea", - "light_sepia", - "light_sky", - "light_turquoise", - "light_violet", - "light_yellow", - "lighter_amber", - "lighter_azure", - "lighter_blue", - "lighter_chartreuse", - "lighter_crimson", - "lighter_cyan", - "lighter_flame", - "lighter_fuchsia", - "lighter_gray", - "lighter_green", - "lighter_grey", - "lighter_han", - "lighter_lime", - "lighter_magenta", - "lighter_orange", - "lighter_pink", - "lighter_purple", - "lighter_red", - "lighter_sea", - "lighter_sepia", - "lighter_sky", - "lighter_turquoise", - "lighter_violet", - "lighter_yellow", - "lightest_amber", - "lightest_azure", - "lightest_blue", - "lightest_chartreuse", - "lightest_crimson", - "lightest_cyan", - "lightest_flame", - "lightest_fuchsia", - "lightest_gray", - "lightest_green", - "lightest_grey", - "lightest_han", - "lightest_lime", - "lightest_magenta", - "lightest_orange", - "lightest_pink", - "lightest_purple", - "lightest_red", - "lightest_sea", - "lightest_sepia", - "lightest_sky", - "lightest_turquoise", - "lightest_violet", - "lightest_yellow", - "lime", - "magenta", - "orange", - "peach", - "pink", - "purple", - "red", - "sea", - "sepia", - "silver", - "sky", - "turquoise", - "violet", - "white", - "yellow", # --- End constants.py --- ] From cac2835bed5cfed8bb2d591696d1c82335c7d542 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 May 2023 23:42:11 -0700 Subject: [PATCH 0748/1101] Update Mypy config. Migrate to pyproject.toml. Add types-cffi stubs. --- .github/workflows/python-package.yml | 2 +- build_libtcod.py | 2 +- pyproject.toml | 33 ++++++++++++++++++++++++++++ requirements.txt | 1 + tcod/loader.py | 13 ++++++----- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c8749e3f..bac48070 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -69,7 +69,7 @@ jobs: uses: liskin/gh-problem-matcher-wrap@v2 with: linters: mypy - run: mypy --show-column-numbers . + run: mypy --show-column-numbers # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: diff --git a/build_libtcod.py b/build_libtcod.py index 4ff65329..aaa60661 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union -from cffi import FFI # type: ignore +from cffi import FFI sys.path.append(str(Path(__file__).parent)) # Allow importing local modules. diff --git a/pyproject.toml b/pyproject.toml index 495cacb9..d4baab4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,39 @@ filterwarnings = [ "ignore:This class may perform poorly and is no longer needed.::tcod.map", ] +[tool.mypy] +files = ["."] +python_version = 3.8 +warn_unused_configs = true +show_error_codes = true +disallow_subclassing_any = true +disallow_any_generics = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_return_any = true +implicit_reexport = false +strict_equality = true +exclude = [ + "build/", + "venv/", + "libtcod/", + "docs/", + "distribution/", + "termbox/", + "samples_libtcodpy.py", +] + +[[tool.mypy.overrides]] +module = "numpy.*" +ignore_missing_imports = true + + [tool.ruff] # https://beta.ruff.rs/docs/rules/ select = [ diff --git a/requirements.txt b/requirements.txt index 7bb32eec..ee1ea8a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ numpy>=1.21.4 pycparser>=2.14 requests>=2.28.1 setuptools==65.5.1 +types-cffi types-requests types-setuptools types-tabulate diff --git a/tcod/loader.py b/tcod/loader.py index 33ff0dd9..1a296b12 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any # noqa: F401 -import cffi # type: ignore +import cffi __sdl_version__ = "" @@ -29,12 +29,13 @@ def verify_dependencies() -> None: """Try to make sure dependencies exist on this system.""" if sys.platform == "win32": - lib_test = ffi_check.dlopen("SDL2.dll") # Make sure SDL2.dll is here. - version = ffi_check.new("struct SDL_version*") + lib_test: Any = ffi_check.dlopen("SDL2.dll") # Make sure SDL2.dll is here. + version: Any = ffi_check.new("struct SDL_version*") lib_test.SDL_GetVersion(version) # Need to check this version. - version = version.major, version.minor, version.patch - if version < (2, 0, 5): - raise RuntimeError("Tried to load an old version of SDL %r" % (version,)) + version_tuple = version.major, version.minor, version.patch + if version_tuple < (2, 0, 5): + msg = f"Tried to load an old version of SDL {version_tuple!r}" + raise RuntimeError(msg) def get_architecture() -> str: From 6c0b70fbbb74dc5546929341c75cba64766de920 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 00:49:58 -0700 Subject: [PATCH 0749/1101] Deprecate older keyboard constants. Add warnings telling which enum replaces any key. Test deprecated keys and colors. --- CHANGELOG.md | 4 +- build_libtcod.py | 14 +- tcod/event.py | 507 +++------------------------------------ tcod/event_constants.py | 484 ------------------------------------- tests/test_deprecated.py | 23 ++ 5 files changed, 62 insertions(+), 970 deletions(-) create mode 100644 tests/test_deprecated.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cf27a946..d9043af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ### Deprecated -- Deprecated all color constants +- Deprecated all libtcod color constants. Replace these with your own manually defined colors. + Using a color will tell you the color values of the deprecated color in the warning. +- Deprecated older scancode and keysym constants. These were replaced with the Scancode and KeySym enums. ### Fixed - DLL loader could fail to load `SDL2.dll` when other tcod namespace packages were installed. diff --git a/build_libtcod.py b/build_libtcod.py index aaa60661..b0491e81 100644 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -256,9 +256,8 @@ def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Union[int, str, Any]]]: yield attr[name_starts_at:], getattr(lib, attr) -def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: - """Return the name/value pairs, and the final dictionary string for the - library attributes with `prefix`. +def parse_sdl_attrs(prefix: str, all_names: list[str] | None) -> tuple[str, str]: + """Return the name/value pairs, and the final dictionary string for the library attributes with `prefix`. Append matching names to the `all_names` list. """ @@ -267,10 +266,11 @@ def parse_sdl_attrs(prefix: str, all_names: List[str]) -> Tuple[str, str]: for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): if name == "KMOD_RESERVED": continue - all_names.append(name) + if all_names is not None: + all_names.append(name) names.append(f"{name} = {value}") lookup.append(f'{value}: "{name}"') - return "\n".join(names), "{\n %s,\n}" % (",\n ".join(lookup),) + return "\n".join(names), "{{\n {},\n}}".format(",\n ".join(lookup)) EXCLUDE_CONSTANTS = [ @@ -370,10 +370,10 @@ def write_library_constants() -> None: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) f.write("\n# --- SDL scancodes ---\n") - f.write(f"""{parse_sdl_attrs("SDL_SCANCODE", all_names)[0]}\n""") + f.write(f"""{parse_sdl_attrs("SDL_SCANCODE", None)[0]}\n""") f.write("\n# --- SDL keyboard symbols ---\n") - f.write(f"""{parse_sdl_attrs("SDLK", all_names)[0]}\n""") + f.write(f"""{parse_sdl_attrs("SDLK", None)[0]}\n""") f.write("\n# --- SDL keyboard modifiers ---\n") f.write("%s\n_REVERSE_MOD_TABLE = %s\n" % parse_sdl_attrs("KMOD", all_names)) diff --git a/tcod/event.py b/tcod/event.py index 1e6bd44a..19e0ee1e 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -2770,6 +2770,35 @@ def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}.{self.name}" +def __getattr__(name: str) -> int: + """Migrate deprecated access of event constants.""" + value: int | None = getattr(tcod.event_constants, name, None) + if not value: + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) + if name.startswith("SCANCODE_"): + scancode = name[9:] + if scancode.isdigit(): + scancode = f"N{scancode}" + warnings.warn( + "Key constants have been replaced with enums.\n" + f"`tcod.event.{name}` should be replaced with `tcod.event.Scancode.{scancode}`", + FutureWarning, + stacklevel=2, + ) + elif name.startswith("K_"): + sym = name[2:] + if sym.isdigit(): + sym = f"N{sym}" + warnings.warn( + "Key constants have been replaced with enums.\n" + f"`tcod.event.{name}` should be replaced with `tcod.event.KeySym.{sym}`", + FutureWarning, + stacklevel=2, + ) + return value + + __all__ = [ # noqa: F405 "Modifier", "Point", @@ -2819,484 +2848,6 @@ def __repr__(self) -> str: "Scancode", "KeySym", # --- From event_constants.py --- - "SCANCODE_UNKNOWN", - "SCANCODE_A", - "SCANCODE_B", - "SCANCODE_C", - "SCANCODE_D", - "SCANCODE_E", - "SCANCODE_F", - "SCANCODE_G", - "SCANCODE_H", - "SCANCODE_I", - "SCANCODE_J", - "SCANCODE_K", - "SCANCODE_L", - "SCANCODE_M", - "SCANCODE_N", - "SCANCODE_O", - "SCANCODE_P", - "SCANCODE_Q", - "SCANCODE_R", - "SCANCODE_S", - "SCANCODE_T", - "SCANCODE_U", - "SCANCODE_V", - "SCANCODE_W", - "SCANCODE_X", - "SCANCODE_Y", - "SCANCODE_Z", - "SCANCODE_1", - "SCANCODE_2", - "SCANCODE_3", - "SCANCODE_4", - "SCANCODE_5", - "SCANCODE_6", - "SCANCODE_7", - "SCANCODE_8", - "SCANCODE_9", - "SCANCODE_0", - "SCANCODE_RETURN", - "SCANCODE_ESCAPE", - "SCANCODE_BACKSPACE", - "SCANCODE_TAB", - "SCANCODE_SPACE", - "SCANCODE_MINUS", - "SCANCODE_EQUALS", - "SCANCODE_LEFTBRACKET", - "SCANCODE_RIGHTBRACKET", - "SCANCODE_BACKSLASH", - "SCANCODE_NONUSHASH", - "SCANCODE_SEMICOLON", - "SCANCODE_APOSTROPHE", - "SCANCODE_GRAVE", - "SCANCODE_COMMA", - "SCANCODE_PERIOD", - "SCANCODE_SLASH", - "SCANCODE_CAPSLOCK", - "SCANCODE_F1", - "SCANCODE_F2", - "SCANCODE_F3", - "SCANCODE_F4", - "SCANCODE_F5", - "SCANCODE_F6", - "SCANCODE_F7", - "SCANCODE_F8", - "SCANCODE_F9", - "SCANCODE_F10", - "SCANCODE_F11", - "SCANCODE_F12", - "SCANCODE_PRINTSCREEN", - "SCANCODE_SCROLLLOCK", - "SCANCODE_PAUSE", - "SCANCODE_INSERT", - "SCANCODE_HOME", - "SCANCODE_PAGEUP", - "SCANCODE_DELETE", - "SCANCODE_END", - "SCANCODE_PAGEDOWN", - "SCANCODE_RIGHT", - "SCANCODE_LEFT", - "SCANCODE_DOWN", - "SCANCODE_UP", - "SCANCODE_NUMLOCKCLEAR", - "SCANCODE_KP_DIVIDE", - "SCANCODE_KP_MULTIPLY", - "SCANCODE_KP_MINUS", - "SCANCODE_KP_PLUS", - "SCANCODE_KP_ENTER", - "SCANCODE_KP_1", - "SCANCODE_KP_2", - "SCANCODE_KP_3", - "SCANCODE_KP_4", - "SCANCODE_KP_5", - "SCANCODE_KP_6", - "SCANCODE_KP_7", - "SCANCODE_KP_8", - "SCANCODE_KP_9", - "SCANCODE_KP_0", - "SCANCODE_KP_PERIOD", - "SCANCODE_NONUSBACKSLASH", - "SCANCODE_APPLICATION", - "SCANCODE_POWER", - "SCANCODE_KP_EQUALS", - "SCANCODE_F13", - "SCANCODE_F14", - "SCANCODE_F15", - "SCANCODE_F16", - "SCANCODE_F17", - "SCANCODE_F18", - "SCANCODE_F19", - "SCANCODE_F20", - "SCANCODE_F21", - "SCANCODE_F22", - "SCANCODE_F23", - "SCANCODE_F24", - "SCANCODE_EXECUTE", - "SCANCODE_HELP", - "SCANCODE_MENU", - "SCANCODE_SELECT", - "SCANCODE_STOP", - "SCANCODE_AGAIN", - "SCANCODE_UNDO", - "SCANCODE_CUT", - "SCANCODE_COPY", - "SCANCODE_PASTE", - "SCANCODE_FIND", - "SCANCODE_MUTE", - "SCANCODE_VOLUMEUP", - "SCANCODE_VOLUMEDOWN", - "SCANCODE_KP_COMMA", - "SCANCODE_KP_EQUALSAS400", - "SCANCODE_INTERNATIONAL1", - "SCANCODE_INTERNATIONAL2", - "SCANCODE_INTERNATIONAL3", - "SCANCODE_INTERNATIONAL4", - "SCANCODE_INTERNATIONAL5", - "SCANCODE_INTERNATIONAL6", - "SCANCODE_INTERNATIONAL7", - "SCANCODE_INTERNATIONAL8", - "SCANCODE_INTERNATIONAL9", - "SCANCODE_LANG1", - "SCANCODE_LANG2", - "SCANCODE_LANG3", - "SCANCODE_LANG4", - "SCANCODE_LANG5", - "SCANCODE_LANG6", - "SCANCODE_LANG7", - "SCANCODE_LANG8", - "SCANCODE_LANG9", - "SCANCODE_ALTERASE", - "SCANCODE_SYSREQ", - "SCANCODE_CANCEL", - "SCANCODE_CLEAR", - "SCANCODE_PRIOR", - "SCANCODE_RETURN2", - "SCANCODE_SEPARATOR", - "SCANCODE_OUT", - "SCANCODE_OPER", - "SCANCODE_CLEARAGAIN", - "SCANCODE_CRSEL", - "SCANCODE_EXSEL", - "SCANCODE_KP_00", - "SCANCODE_KP_000", - "SCANCODE_THOUSANDSSEPARATOR", - "SCANCODE_DECIMALSEPARATOR", - "SCANCODE_CURRENCYUNIT", - "SCANCODE_CURRENCYSUBUNIT", - "SCANCODE_KP_LEFTPAREN", - "SCANCODE_KP_RIGHTPAREN", - "SCANCODE_KP_LEFTBRACE", - "SCANCODE_KP_RIGHTBRACE", - "SCANCODE_KP_TAB", - "SCANCODE_KP_BACKSPACE", - "SCANCODE_KP_A", - "SCANCODE_KP_B", - "SCANCODE_KP_C", - "SCANCODE_KP_D", - "SCANCODE_KP_E", - "SCANCODE_KP_F", - "SCANCODE_KP_XOR", - "SCANCODE_KP_POWER", - "SCANCODE_KP_PERCENT", - "SCANCODE_KP_LESS", - "SCANCODE_KP_GREATER", - "SCANCODE_KP_AMPERSAND", - "SCANCODE_KP_DBLAMPERSAND", - "SCANCODE_KP_VERTICALBAR", - "SCANCODE_KP_DBLVERTICALBAR", - "SCANCODE_KP_COLON", - "SCANCODE_KP_HASH", - "SCANCODE_KP_SPACE", - "SCANCODE_KP_AT", - "SCANCODE_KP_EXCLAM", - "SCANCODE_KP_MEMSTORE", - "SCANCODE_KP_MEMRECALL", - "SCANCODE_KP_MEMCLEAR", - "SCANCODE_KP_MEMADD", - "SCANCODE_KP_MEMSUBTRACT", - "SCANCODE_KP_MEMMULTIPLY", - "SCANCODE_KP_MEMDIVIDE", - "SCANCODE_KP_PLUSMINUS", - "SCANCODE_KP_CLEAR", - "SCANCODE_KP_CLEARENTRY", - "SCANCODE_KP_BINARY", - "SCANCODE_KP_OCTAL", - "SCANCODE_KP_DECIMAL", - "SCANCODE_KP_HEXADECIMAL", - "SCANCODE_LCTRL", - "SCANCODE_LSHIFT", - "SCANCODE_LALT", - "SCANCODE_LGUI", - "SCANCODE_RCTRL", - "SCANCODE_RSHIFT", - "SCANCODE_RALT", - "SCANCODE_RGUI", - "SCANCODE_MODE", - "SCANCODE_AUDIONEXT", - "SCANCODE_AUDIOPREV", - "SCANCODE_AUDIOSTOP", - "SCANCODE_AUDIOPLAY", - "SCANCODE_AUDIOMUTE", - "SCANCODE_MEDIASELECT", - "SCANCODE_WWW", - "SCANCODE_MAIL", - "SCANCODE_CALCULATOR", - "SCANCODE_COMPUTER", - "SCANCODE_AC_SEARCH", - "SCANCODE_AC_HOME", - "SCANCODE_AC_BACK", - "SCANCODE_AC_FORWARD", - "SCANCODE_AC_STOP", - "SCANCODE_AC_REFRESH", - "SCANCODE_AC_BOOKMARKS", - "SCANCODE_BRIGHTNESSDOWN", - "SCANCODE_BRIGHTNESSUP", - "SCANCODE_DISPLAYSWITCH", - "SCANCODE_KBDILLUMTOGGLE", - "SCANCODE_KBDILLUMDOWN", - "SCANCODE_KBDILLUMUP", - "SCANCODE_EJECT", - "SCANCODE_SLEEP", - "SCANCODE_APP1", - "SCANCODE_APP2", - "K_UNKNOWN", - "K_BACKSPACE", - "K_TAB", - "K_RETURN", - "K_ESCAPE", - "K_SPACE", - "K_EXCLAIM", - "K_QUOTEDBL", - "K_HASH", - "K_DOLLAR", - "K_PERCENT", - "K_AMPERSAND", - "K_QUOTE", - "K_LEFTPAREN", - "K_RIGHTPAREN", - "K_ASTERISK", - "K_PLUS", - "K_COMMA", - "K_MINUS", - "K_PERIOD", - "K_SLASH", - "K_0", - "K_1", - "K_2", - "K_3", - "K_4", - "K_5", - "K_6", - "K_7", - "K_8", - "K_9", - "K_COLON", - "K_SEMICOLON", - "K_LESS", - "K_EQUALS", - "K_GREATER", - "K_QUESTION", - "K_AT", - "K_LEFTBRACKET", - "K_BACKSLASH", - "K_RIGHTBRACKET", - "K_CARET", - "K_UNDERSCORE", - "K_BACKQUOTE", - "K_a", - "K_b", - "K_c", - "K_d", - "K_e", - "K_f", - "K_g", - "K_h", - "K_i", - "K_j", - "K_k", - "K_l", - "K_m", - "K_n", - "K_o", - "K_p", - "K_q", - "K_r", - "K_s", - "K_t", - "K_u", - "K_v", - "K_w", - "K_x", - "K_y", - "K_z", - "K_DELETE", - "K_SCANCODE_MASK", - "K_CAPSLOCK", - "K_F1", - "K_F2", - "K_F3", - "K_F4", - "K_F5", - "K_F6", - "K_F7", - "K_F8", - "K_F9", - "K_F10", - "K_F11", - "K_F12", - "K_PRINTSCREEN", - "K_SCROLLLOCK", - "K_PAUSE", - "K_INSERT", - "K_HOME", - "K_PAGEUP", - "K_END", - "K_PAGEDOWN", - "K_RIGHT", - "K_LEFT", - "K_DOWN", - "K_UP", - "K_NUMLOCKCLEAR", - "K_KP_DIVIDE", - "K_KP_MULTIPLY", - "K_KP_MINUS", - "K_KP_PLUS", - "K_KP_ENTER", - "K_KP_1", - "K_KP_2", - "K_KP_3", - "K_KP_4", - "K_KP_5", - "K_KP_6", - "K_KP_7", - "K_KP_8", - "K_KP_9", - "K_KP_0", - "K_KP_PERIOD", - "K_APPLICATION", - "K_POWER", - "K_KP_EQUALS", - "K_F13", - "K_F14", - "K_F15", - "K_F16", - "K_F17", - "K_F18", - "K_F19", - "K_F20", - "K_F21", - "K_F22", - "K_F23", - "K_F24", - "K_EXECUTE", - "K_HELP", - "K_MENU", - "K_SELECT", - "K_STOP", - "K_AGAIN", - "K_UNDO", - "K_CUT", - "K_COPY", - "K_PASTE", - "K_FIND", - "K_MUTE", - "K_VOLUMEUP", - "K_VOLUMEDOWN", - "K_KP_COMMA", - "K_KP_EQUALSAS400", - "K_ALTERASE", - "K_SYSREQ", - "K_CANCEL", - "K_CLEAR", - "K_PRIOR", - "K_RETURN2", - "K_SEPARATOR", - "K_OUT", - "K_OPER", - "K_CLEARAGAIN", - "K_CRSEL", - "K_EXSEL", - "K_KP_00", - "K_KP_000", - "K_THOUSANDSSEPARATOR", - "K_DECIMALSEPARATOR", - "K_CURRENCYUNIT", - "K_CURRENCYSUBUNIT", - "K_KP_LEFTPAREN", - "K_KP_RIGHTPAREN", - "K_KP_LEFTBRACE", - "K_KP_RIGHTBRACE", - "K_KP_TAB", - "K_KP_BACKSPACE", - "K_KP_A", - "K_KP_B", - "K_KP_C", - "K_KP_D", - "K_KP_E", - "K_KP_F", - "K_KP_XOR", - "K_KP_POWER", - "K_KP_PERCENT", - "K_KP_LESS", - "K_KP_GREATER", - "K_KP_AMPERSAND", - "K_KP_DBLAMPERSAND", - "K_KP_VERTICALBAR", - "K_KP_DBLVERTICALBAR", - "K_KP_COLON", - "K_KP_HASH", - "K_KP_SPACE", - "K_KP_AT", - "K_KP_EXCLAM", - "K_KP_MEMSTORE", - "K_KP_MEMRECALL", - "K_KP_MEMCLEAR", - "K_KP_MEMADD", - "K_KP_MEMSUBTRACT", - "K_KP_MEMMULTIPLY", - "K_KP_MEMDIVIDE", - "K_KP_PLUSMINUS", - "K_KP_CLEAR", - "K_KP_CLEARENTRY", - "K_KP_BINARY", - "K_KP_OCTAL", - "K_KP_DECIMAL", - "K_KP_HEXADECIMAL", - "K_LCTRL", - "K_LSHIFT", - "K_LALT", - "K_LGUI", - "K_RCTRL", - "K_RSHIFT", - "K_RALT", - "K_RGUI", - "K_MODE", - "K_AUDIONEXT", - "K_AUDIOPREV", - "K_AUDIOSTOP", - "K_AUDIOPLAY", - "K_AUDIOMUTE", - "K_MEDIASELECT", - "K_WWW", - "K_MAIL", - "K_CALCULATOR", - "K_COMPUTER", - "K_AC_SEARCH", - "K_AC_HOME", - "K_AC_BACK", - "K_AC_FORWARD", - "K_AC_STOP", - "K_AC_REFRESH", - "K_AC_BOOKMARKS", - "K_BRIGHTNESSDOWN", - "K_BRIGHTNESSUP", - "K_DISPLAYSWITCH", - "K_KBDILLUMTOGGLE", - "K_KBDILLUMDOWN", - "K_KBDILLUMUP", - "K_EJECT", - "K_SLEEP", "KMOD_NONE", "KMOD_LSHIFT", "KMOD_RSHIFT", diff --git a/tcod/event_constants.py b/tcod/event_constants.py index aca5d5ec..d10eeb74 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -540,490 +540,6 @@ } __all__ = [ - "SCANCODE_UNKNOWN", - "SCANCODE_A", - "SCANCODE_B", - "SCANCODE_C", - "SCANCODE_D", - "SCANCODE_E", - "SCANCODE_F", - "SCANCODE_G", - "SCANCODE_H", - "SCANCODE_I", - "SCANCODE_J", - "SCANCODE_K", - "SCANCODE_L", - "SCANCODE_M", - "SCANCODE_N", - "SCANCODE_O", - "SCANCODE_P", - "SCANCODE_Q", - "SCANCODE_R", - "SCANCODE_S", - "SCANCODE_T", - "SCANCODE_U", - "SCANCODE_V", - "SCANCODE_W", - "SCANCODE_X", - "SCANCODE_Y", - "SCANCODE_Z", - "SCANCODE_1", - "SCANCODE_2", - "SCANCODE_3", - "SCANCODE_4", - "SCANCODE_5", - "SCANCODE_6", - "SCANCODE_7", - "SCANCODE_8", - "SCANCODE_9", - "SCANCODE_0", - "SCANCODE_RETURN", - "SCANCODE_ESCAPE", - "SCANCODE_BACKSPACE", - "SCANCODE_TAB", - "SCANCODE_SPACE", - "SCANCODE_MINUS", - "SCANCODE_EQUALS", - "SCANCODE_LEFTBRACKET", - "SCANCODE_RIGHTBRACKET", - "SCANCODE_BACKSLASH", - "SCANCODE_NONUSHASH", - "SCANCODE_SEMICOLON", - "SCANCODE_APOSTROPHE", - "SCANCODE_GRAVE", - "SCANCODE_COMMA", - "SCANCODE_PERIOD", - "SCANCODE_SLASH", - "SCANCODE_CAPSLOCK", - "SCANCODE_F1", - "SCANCODE_F2", - "SCANCODE_F3", - "SCANCODE_F4", - "SCANCODE_F5", - "SCANCODE_F6", - "SCANCODE_F7", - "SCANCODE_F8", - "SCANCODE_F9", - "SCANCODE_F10", - "SCANCODE_F11", - "SCANCODE_F12", - "SCANCODE_PRINTSCREEN", - "SCANCODE_SCROLLLOCK", - "SCANCODE_PAUSE", - "SCANCODE_INSERT", - "SCANCODE_HOME", - "SCANCODE_PAGEUP", - "SCANCODE_DELETE", - "SCANCODE_END", - "SCANCODE_PAGEDOWN", - "SCANCODE_RIGHT", - "SCANCODE_LEFT", - "SCANCODE_DOWN", - "SCANCODE_UP", - "SCANCODE_NUMLOCKCLEAR", - "SCANCODE_KP_DIVIDE", - "SCANCODE_KP_MULTIPLY", - "SCANCODE_KP_MINUS", - "SCANCODE_KP_PLUS", - "SCANCODE_KP_ENTER", - "SCANCODE_KP_1", - "SCANCODE_KP_2", - "SCANCODE_KP_3", - "SCANCODE_KP_4", - "SCANCODE_KP_5", - "SCANCODE_KP_6", - "SCANCODE_KP_7", - "SCANCODE_KP_8", - "SCANCODE_KP_9", - "SCANCODE_KP_0", - "SCANCODE_KP_PERIOD", - "SCANCODE_NONUSBACKSLASH", - "SCANCODE_APPLICATION", - "SCANCODE_POWER", - "SCANCODE_KP_EQUALS", - "SCANCODE_F13", - "SCANCODE_F14", - "SCANCODE_F15", - "SCANCODE_F16", - "SCANCODE_F17", - "SCANCODE_F18", - "SCANCODE_F19", - "SCANCODE_F20", - "SCANCODE_F21", - "SCANCODE_F22", - "SCANCODE_F23", - "SCANCODE_F24", - "SCANCODE_EXECUTE", - "SCANCODE_HELP", - "SCANCODE_MENU", - "SCANCODE_SELECT", - "SCANCODE_STOP", - "SCANCODE_AGAIN", - "SCANCODE_UNDO", - "SCANCODE_CUT", - "SCANCODE_COPY", - "SCANCODE_PASTE", - "SCANCODE_FIND", - "SCANCODE_MUTE", - "SCANCODE_VOLUMEUP", - "SCANCODE_VOLUMEDOWN", - "SCANCODE_KP_COMMA", - "SCANCODE_KP_EQUALSAS400", - "SCANCODE_INTERNATIONAL1", - "SCANCODE_INTERNATIONAL2", - "SCANCODE_INTERNATIONAL3", - "SCANCODE_INTERNATIONAL4", - "SCANCODE_INTERNATIONAL5", - "SCANCODE_INTERNATIONAL6", - "SCANCODE_INTERNATIONAL7", - "SCANCODE_INTERNATIONAL8", - "SCANCODE_INTERNATIONAL9", - "SCANCODE_LANG1", - "SCANCODE_LANG2", - "SCANCODE_LANG3", - "SCANCODE_LANG4", - "SCANCODE_LANG5", - "SCANCODE_LANG6", - "SCANCODE_LANG7", - "SCANCODE_LANG8", - "SCANCODE_LANG9", - "SCANCODE_ALTERASE", - "SCANCODE_SYSREQ", - "SCANCODE_CANCEL", - "SCANCODE_CLEAR", - "SCANCODE_PRIOR", - "SCANCODE_RETURN2", - "SCANCODE_SEPARATOR", - "SCANCODE_OUT", - "SCANCODE_OPER", - "SCANCODE_CLEARAGAIN", - "SCANCODE_CRSEL", - "SCANCODE_EXSEL", - "SCANCODE_KP_00", - "SCANCODE_KP_000", - "SCANCODE_THOUSANDSSEPARATOR", - "SCANCODE_DECIMALSEPARATOR", - "SCANCODE_CURRENCYUNIT", - "SCANCODE_CURRENCYSUBUNIT", - "SCANCODE_KP_LEFTPAREN", - "SCANCODE_KP_RIGHTPAREN", - "SCANCODE_KP_LEFTBRACE", - "SCANCODE_KP_RIGHTBRACE", - "SCANCODE_KP_TAB", - "SCANCODE_KP_BACKSPACE", - "SCANCODE_KP_A", - "SCANCODE_KP_B", - "SCANCODE_KP_C", - "SCANCODE_KP_D", - "SCANCODE_KP_E", - "SCANCODE_KP_F", - "SCANCODE_KP_XOR", - "SCANCODE_KP_POWER", - "SCANCODE_KP_PERCENT", - "SCANCODE_KP_LESS", - "SCANCODE_KP_GREATER", - "SCANCODE_KP_AMPERSAND", - "SCANCODE_KP_DBLAMPERSAND", - "SCANCODE_KP_VERTICALBAR", - "SCANCODE_KP_DBLVERTICALBAR", - "SCANCODE_KP_COLON", - "SCANCODE_KP_HASH", - "SCANCODE_KP_SPACE", - "SCANCODE_KP_AT", - "SCANCODE_KP_EXCLAM", - "SCANCODE_KP_MEMSTORE", - "SCANCODE_KP_MEMRECALL", - "SCANCODE_KP_MEMCLEAR", - "SCANCODE_KP_MEMADD", - "SCANCODE_KP_MEMSUBTRACT", - "SCANCODE_KP_MEMMULTIPLY", - "SCANCODE_KP_MEMDIVIDE", - "SCANCODE_KP_PLUSMINUS", - "SCANCODE_KP_CLEAR", - "SCANCODE_KP_CLEARENTRY", - "SCANCODE_KP_BINARY", - "SCANCODE_KP_OCTAL", - "SCANCODE_KP_DECIMAL", - "SCANCODE_KP_HEXADECIMAL", - "SCANCODE_LCTRL", - "SCANCODE_LSHIFT", - "SCANCODE_LALT", - "SCANCODE_LGUI", - "SCANCODE_RCTRL", - "SCANCODE_RSHIFT", - "SCANCODE_RALT", - "SCANCODE_RGUI", - "SCANCODE_MODE", - "SCANCODE_AUDIONEXT", - "SCANCODE_AUDIOPREV", - "SCANCODE_AUDIOSTOP", - "SCANCODE_AUDIOPLAY", - "SCANCODE_AUDIOMUTE", - "SCANCODE_MEDIASELECT", - "SCANCODE_WWW", - "SCANCODE_MAIL", - "SCANCODE_CALCULATOR", - "SCANCODE_COMPUTER", - "SCANCODE_AC_SEARCH", - "SCANCODE_AC_HOME", - "SCANCODE_AC_BACK", - "SCANCODE_AC_FORWARD", - "SCANCODE_AC_STOP", - "SCANCODE_AC_REFRESH", - "SCANCODE_AC_BOOKMARKS", - "SCANCODE_BRIGHTNESSDOWN", - "SCANCODE_BRIGHTNESSUP", - "SCANCODE_DISPLAYSWITCH", - "SCANCODE_KBDILLUMTOGGLE", - "SCANCODE_KBDILLUMDOWN", - "SCANCODE_KBDILLUMUP", - "SCANCODE_EJECT", - "SCANCODE_SLEEP", - "SCANCODE_APP1", - "SCANCODE_APP2", - "SCANCODE_AUDIOREWIND", - "SCANCODE_AUDIOFASTFORWARD", - "K_UNKNOWN", - "K_BACKSPACE", - "K_TAB", - "K_RETURN", - "K_ESCAPE", - "K_SPACE", - "K_EXCLAIM", - "K_QUOTEDBL", - "K_HASH", - "K_DOLLAR", - "K_PERCENT", - "K_AMPERSAND", - "K_QUOTE", - "K_LEFTPAREN", - "K_RIGHTPAREN", - "K_ASTERISK", - "K_PLUS", - "K_COMMA", - "K_MINUS", - "K_PERIOD", - "K_SLASH", - "K_0", - "K_1", - "K_2", - "K_3", - "K_4", - "K_5", - "K_6", - "K_7", - "K_8", - "K_9", - "K_COLON", - "K_SEMICOLON", - "K_LESS", - "K_EQUALS", - "K_GREATER", - "K_QUESTION", - "K_AT", - "K_LEFTBRACKET", - "K_BACKSLASH", - "K_RIGHTBRACKET", - "K_CARET", - "K_UNDERSCORE", - "K_BACKQUOTE", - "K_a", - "K_b", - "K_c", - "K_d", - "K_e", - "K_f", - "K_g", - "K_h", - "K_i", - "K_j", - "K_k", - "K_l", - "K_m", - "K_n", - "K_o", - "K_p", - "K_q", - "K_r", - "K_s", - "K_t", - "K_u", - "K_v", - "K_w", - "K_x", - "K_y", - "K_z", - "K_DELETE", - "K_SCANCODE_MASK", - "K_CAPSLOCK", - "K_F1", - "K_F2", - "K_F3", - "K_F4", - "K_F5", - "K_F6", - "K_F7", - "K_F8", - "K_F9", - "K_F10", - "K_F11", - "K_F12", - "K_PRINTSCREEN", - "K_SCROLLLOCK", - "K_PAUSE", - "K_INSERT", - "K_HOME", - "K_PAGEUP", - "K_END", - "K_PAGEDOWN", - "K_RIGHT", - "K_LEFT", - "K_DOWN", - "K_UP", - "K_NUMLOCKCLEAR", - "K_KP_DIVIDE", - "K_KP_MULTIPLY", - "K_KP_MINUS", - "K_KP_PLUS", - "K_KP_ENTER", - "K_KP_1", - "K_KP_2", - "K_KP_3", - "K_KP_4", - "K_KP_5", - "K_KP_6", - "K_KP_7", - "K_KP_8", - "K_KP_9", - "K_KP_0", - "K_KP_PERIOD", - "K_APPLICATION", - "K_POWER", - "K_KP_EQUALS", - "K_F13", - "K_F14", - "K_F15", - "K_F16", - "K_F17", - "K_F18", - "K_F19", - "K_F20", - "K_F21", - "K_F22", - "K_F23", - "K_F24", - "K_EXECUTE", - "K_HELP", - "K_MENU", - "K_SELECT", - "K_STOP", - "K_AGAIN", - "K_UNDO", - "K_CUT", - "K_COPY", - "K_PASTE", - "K_FIND", - "K_MUTE", - "K_VOLUMEUP", - "K_VOLUMEDOWN", - "K_KP_COMMA", - "K_KP_EQUALSAS400", - "K_ALTERASE", - "K_SYSREQ", - "K_CANCEL", - "K_CLEAR", - "K_PRIOR", - "K_RETURN2", - "K_SEPARATOR", - "K_OUT", - "K_OPER", - "K_CLEARAGAIN", - "K_CRSEL", - "K_EXSEL", - "K_KP_00", - "K_KP_000", - "K_THOUSANDSSEPARATOR", - "K_DECIMALSEPARATOR", - "K_CURRENCYUNIT", - "K_CURRENCYSUBUNIT", - "K_KP_LEFTPAREN", - "K_KP_RIGHTPAREN", - "K_KP_LEFTBRACE", - "K_KP_RIGHTBRACE", - "K_KP_TAB", - "K_KP_BACKSPACE", - "K_KP_A", - "K_KP_B", - "K_KP_C", - "K_KP_D", - "K_KP_E", - "K_KP_F", - "K_KP_XOR", - "K_KP_POWER", - "K_KP_PERCENT", - "K_KP_LESS", - "K_KP_GREATER", - "K_KP_AMPERSAND", - "K_KP_DBLAMPERSAND", - "K_KP_VERTICALBAR", - "K_KP_DBLVERTICALBAR", - "K_KP_COLON", - "K_KP_HASH", - "K_KP_SPACE", - "K_KP_AT", - "K_KP_EXCLAM", - "K_KP_MEMSTORE", - "K_KP_MEMRECALL", - "K_KP_MEMCLEAR", - "K_KP_MEMADD", - "K_KP_MEMSUBTRACT", - "K_KP_MEMMULTIPLY", - "K_KP_MEMDIVIDE", - "K_KP_PLUSMINUS", - "K_KP_CLEAR", - "K_KP_CLEARENTRY", - "K_KP_BINARY", - "K_KP_OCTAL", - "K_KP_DECIMAL", - "K_KP_HEXADECIMAL", - "K_LCTRL", - "K_LSHIFT", - "K_LALT", - "K_LGUI", - "K_RCTRL", - "K_RSHIFT", - "K_RALT", - "K_RGUI", - "K_MODE", - "K_AUDIONEXT", - "K_AUDIOPREV", - "K_AUDIOSTOP", - "K_AUDIOPLAY", - "K_AUDIOMUTE", - "K_MEDIASELECT", - "K_WWW", - "K_MAIL", - "K_CALCULATOR", - "K_COMPUTER", - "K_AC_SEARCH", - "K_AC_HOME", - "K_AC_BACK", - "K_AC_FORWARD", - "K_AC_STOP", - "K_AC_REFRESH", - "K_AC_BOOKMARKS", - "K_BRIGHTNESSDOWN", - "K_BRIGHTNESSUP", - "K_DISPLAYSWITCH", - "K_KBDILLUMTOGGLE", - "K_KBDILLUMDOWN", - "K_KBDILLUMUP", - "K_EJECT", - "K_SLEEP", - "K_APP1", - "K_APP2", - "K_AUDIOREWIND", - "K_AUDIOFASTFORWARD", "KMOD_NONE", "KMOD_LSHIFT", "KMOD_RSHIFT", diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py new file mode 100644 index 00000000..8aa15303 --- /dev/null +++ b/tests/test_deprecated.py @@ -0,0 +1,23 @@ +"""Test deprecated features.""" +from __future__ import annotations + +import pytest + +import tcod +import tcod.event + +# ruff: noqa: D103 + + +@pytest.mark.filterwarnings("error") +def test_deprecate_color() -> None: + with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): + _ = tcod.black + + +@pytest.mark.filterwarnings("error") +def test_deprecate_key_constants() -> None: + with pytest.raises(FutureWarning, match=r".*KeySym.N1"): + _ = tcod.event.K_1 + with pytest.raises(FutureWarning, match=r".*Scancode.N1"): + _ = tcod.event.SCANCODE_1 From 1a59d17e44a8f58bb5704ff74cf167fc7676647f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 00:58:32 -0700 Subject: [PATCH 0750/1101] Add commits since last release badge. Mostly as a self-reminder for me to make a release. --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1f69e003..f84370ee 100755 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ ======== |VersionsBadge| |ImplementationBadge| |LicenseBadge| -|PyPI| |RTD| |Codecov| |Pyup| +|PyPI| |RTD| |Codecov| |Pyup| |CommitsSinceLastRelease| ======= About @@ -100,3 +100,6 @@ python-tcod is distributed under the `Simplified 2-clause FreeBSD license .. |Pyup| image:: https://pyup.io/repos/github/libtcod/python-tcod/shield.svg :target: https://pyup.io/repos/github/libtcod/python-tcod/ :alt: Updates + +.. |CommitsSinceLastRelease| image:: https://img.shields.io/github/commits-since/libtcod/python-tcod/latest + :target: https://github.com/libtcod/python-tcod/blob/main/CHANGELOG.md From fe86ca6b7533d03cd846d8ae19cbe51e1a43119c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 00:52:08 -0700 Subject: [PATCH 0751/1101] Update MacOS runner to macos-11. The older runner is no longer available. --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bac48070..4652840a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -253,7 +253,7 @@ jobs: build-macos: needs: [black, isort, flake8, mypy] - runs-on: "macos-10.15" + runs-on: "macos-11" strategy: fail-fast: true matrix: @@ -274,7 +274,7 @@ jobs: # Downloads SDL2 for the later step. run: python3 setup.py check - name: Build wheels - uses: pypa/cibuildwheel@v2.0.0a4 + uses: pypa/cibuildwheel@v2.12.3 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 From f045dafe01a85af848544416a38f30e658c77a10 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 01:18:34 -0700 Subject: [PATCH 0752/1101] Remove old Mypy config. --- setup.cfg | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0e25efb1..75665f21 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,25 +7,3 @@ test=pytest [flake8] ignore = E203 W503 TYP001 max-line-length = 130 - -[mypy] -python_version = 3.8 -warn_unused_configs = True -show_error_codes = True -disallow_subclassing_any = True -disallow_any_generics = True -disallow_untyped_calls = True -disallow_untyped_defs = True -disallow_incomplete_defs = True -check_untyped_defs = True -disallow_untyped_decorators = True -no_implicit_optional = True -warn_redundant_casts = True -warn_unused_ignores = True -warn_return_any = True -implicit_reexport = False -strict_equality = True -exclude = (build/|venv/|libtcod/|docs/|distribution/|termbox/|samples_libtcodpy.py) - -[mypy-numpy] -ignore_missing_imports = True From f21b7501a1dee23affe29c94137689c3b577b524 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 04:51:55 -0700 Subject: [PATCH 0753/1101] Update workflows to not invoke setup.py as much. Use latest OS versions. --- .github/workflows/python-package.yml | 35 ++++++++++++++++------------ .vscode/settings.json | 5 +++- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 4652840a..d63b1173 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -17,7 +17,7 @@ env: jobs: black: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Black @@ -26,7 +26,7 @@ jobs: run: black --check --diff examples/ scripts/ tcod/ tests/ *.py isort: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install isort @@ -43,7 +43,7 @@ jobs: run: isort examples/ --check --diff --thirdparty tcod flake8: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Flake8 @@ -55,7 +55,7 @@ jobs: run: flake8 scripts/ tcod/ tests/ mypy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Checkout submodules @@ -76,7 +76,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["windows-2019", "macos-11"] + os: ["windows-latest", "macos-latest"] sdl-version: ["2.0.14", "2.0.16"] fail-fast: true steps: @@ -85,8 +85,13 @@ jobs: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install build dependencies + run: pip install build - name: Build package - run: ./setup.py build + run: python -m build env: SDL_VERSION: ${{ matrix.sdl-version }} @@ -95,14 +100,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["ubuntu-20.04", "windows-2019"] + os: ["ubuntu-latest", "windows-latest"] python-version: ["3.7", "3.8", "3.9", "pypy-3.7"] architecture: ["x64"] include: - - os: "windows-2019" + - os: "windows-latest" python-version: "3.7" architecture: "x86" - - os: "windows-2019" + - os: "windows-latest" python-version: "pypy-3.7" architecture: "x86" fail-fast: false @@ -127,14 +132,14 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark wheel twine + pip install pytest pytest-cov pytest-benchmark build twine if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | - python setup.py check # Creates tcod/version.py. + pip install -e . # Install the package in-place. - name: Build package. run: | - python setup.py build sdist develop bdist_wheel --py-limited-api cp36 # Install the package in-place. + python -m build - name: Test with pytest if: runner.os == 'Windows' run: | @@ -172,7 +177,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["ubuntu-20.04", "windows-2019"] + os: ["ubuntu-latest", "windows-latest"] steps: - name: Set up Python uses: actions/setup-python@v4 @@ -199,7 +204,7 @@ jobs: linux-wheels: needs: build # These take a while to build/test, so wait for normal tests to pass first. - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-latest" strategy: matrix: arch: ["x86_64", "aarch64"] @@ -272,7 +277,7 @@ jobs: run: pip3 install wheel twine -r requirements.txt - name: Prepare package # Downloads SDL2 for the later step. - run: python3 setup.py check + run: python3 setup.py || true - name: Build wheels uses: pypa/cibuildwheel@v2.12.3 env: diff --git a/.vscode/settings.json b/.vscode/settings.json index c2a007d0..25a75f16 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -428,5 +428,8 @@ "python.testing.pytestEnabled": true, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" - } + }, + "cSpell.enableFiletypes": [ + "github-actions-workflow" + ] } \ No newline at end of file From 5fd5b9e19ba44498970491f30d2cc57c44dae5d0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 May 2023 07:19:08 -0700 Subject: [PATCH 0754/1101] Organize workflow for better performance. Skip long running wheel building unless on a tag release. Improve build speed of SDL parse tests, and only do those when linters pass. --- .github/workflows/python-package.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d63b1173..d8ccdfc2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -73,6 +73,7 @@ jobs: # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: + needs: [black, isort, flake8, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -94,6 +95,7 @@ jobs: run: python -m build env: SDL_VERSION: ${{ matrix.sdl-version }} + TDL_BUILD: DEBUG build: needs: [black, isort, flake8, mypy] @@ -204,6 +206,7 @@ jobs: linux-wheels: needs: build # These take a while to build/test, so wait for normal tests to pass first. + if: startsWith(github.event.ref, 'refs/tags/') runs-on: "ubuntu-latest" strategy: matrix: From 498c50aa9c099ef2e665fbdbf50f1f75d77c222c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 24 May 2023 23:14:10 -0700 Subject: [PATCH 0755/1101] Add pre-commit hooks. --- .pre-commit-config.yaml | 26 ++++++++++++++++++++++++++ CONTRIBUTING.md | 10 ++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..54a9a88e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-shebang-scripts-are-executable + - id: check-merge-conflict + - id: check-toml + - id: debug-statements + - id: fix-byte-order-marker + - id: detect-private-key + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +default_language_version: + python: python3.11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58da55b3..c4f7d5ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,12 @@ ## Code style -New and refactored Python code should follow the -[PEP 8](https://www.python.org/dev/peps/pep-0008/) guidelines. +Code styles are enforced using black and linters. +These are best enabled with a pre-commit which you can setup with: -It's recommended to use an editor supporting -[EditorConfig](https://editorconfig.org/). +```sh +pip install pre-commit +pre-commit install +``` ## Building python-tcod From 5a1d70cab1bf010cf78fb52d153505b7baf7898a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 24 May 2023 23:22:04 -0700 Subject: [PATCH 0756/1101] Clean up minor formatting issues. Don't try to force import order in examples to look like external projects. Remove shebangs from test scripts. Add +x to examples and scripts. Fix all whitespace and final newlines. --- .editorconfig | 1 - .github/workflows/python-package.yml | 7 +------ .pyup.yml | 2 +- .vscode/extensions.json | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 5 ++++- .vscode/tasks.json | 2 +- build_libtcod.py | 0 build_sdl.py | 0 docs/index.rst | 1 - examples/.isort.cfg | 5 ----- examples/cavegen.py | 0 examples/framerate.py | 0 examples/samples_libtcodpy.py | 1 + examples/samples_tcod.py | 3 ++- examples/sdl-hello-world.py | 3 ++- examples/ttf.py | 3 ++- fonts/libtcod/README.txt | 2 +- scripts/tag_release.py | 0 tests/test_libtcodpy.py | 2 -- tests/test_parser.py | 2 -- tests/test_tcod.py | 2 -- tests/test_testing.py | 2 -- 23 files changed, 17 insertions(+), 30 deletions(-) mode change 100644 => 100755 build_libtcod.py mode change 100644 => 100755 build_sdl.py delete mode 100644 examples/.isort.cfg mode change 100644 => 100755 examples/cavegen.py mode change 100644 => 100755 examples/framerate.py mode change 100644 => 100755 examples/ttf.py mode change 100644 => 100755 scripts/tag_release.py diff --git a/.editorconfig b/.editorconfig index 2cd81a2a..bd4f19f2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -26,4 +26,3 @@ indent_size = 2 [*.json] indent_style = space indent_size = 4 -insert_final_newline = false diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d8ccdfc2..0ec9701e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -35,12 +35,7 @@ jobs: uses: liskin/gh-problem-matcher-wrap@v2 with: linters: isort - run: isort scripts/ tcod/ tests/ --check --diff - - name: isort (examples) - uses: liskin/gh-problem-matcher-wrap@v2 - with: - linters: isort - run: isort examples/ --check --diff --thirdparty tcod + run: isort scripts/ tcod/ tests/ examples/ --check --diff flake8: runs-on: ubuntu-latest diff --git a/.pyup.yml b/.pyup.yml index bf632f0b..fa58aa6e 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -1,4 +1,4 @@ -# autogenerated pyup.io config file +# autogenerated pyup.io config file # see https://pyup.io/docs/configuration/ for all available options update: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e9737cb3..d589600c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -16,4 +16,4 @@ ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] -} \ No newline at end of file +} diff --git a/.vscode/launch.json b/.vscode/launch.json index c9892886..0f237387 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,4 +45,4 @@ "preLaunchTask": "build documentation", } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 25a75f16..12dc2d77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,9 @@ "editor.codeActionsOnSave": { "source.organizeImports": true }, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, "python.linting.enabled": true, "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, @@ -432,4 +435,4 @@ "cSpell.enableFiletypes": [ "github-actions-workflow" ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ba6b05d3..5e1a0fb2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,4 +41,4 @@ } } ] -} \ No newline at end of file +} diff --git a/build_libtcod.py b/build_libtcod.py old mode 100644 new mode 100755 diff --git a/build_sdl.py b/build_sdl.py old mode 100644 new mode 100755 diff --git a/docs/index.rst b/docs/index.rst index 7a138378..c9760f9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -51,4 +51,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/examples/.isort.cfg b/examples/.isort.cfg deleted file mode 100644 index 9e0911c7..00000000 --- a/examples/.isort.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[isort] -profile= black -py_version = 36 -skip_gitignore = true -line_length = 120 diff --git a/examples/cavegen.py b/examples/cavegen.py old mode 100644 new mode 100755 diff --git a/examples/framerate.py b/examples/framerate.py old mode 100644 new mode 100755 diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index c59b5b46..ab68eb91 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -4,6 +4,7 @@ # This code demonstrates various usages of libtcod modules # It's in the public domain. # +# ruff: noqa from __future__ import division import math diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index bf20be3b..bf0f7101 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -15,10 +15,11 @@ from typing import Any, List import numpy as np +from numpy.typing import NDArray + import tcod import tcod.render import tcod.sdl.render -from numpy.typing import NDArray if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index e3d45245..d0ae93c0 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -2,10 +2,11 @@ from pathlib import Path import numpy as np +from PIL import Image, ImageDraw, ImageFont # type: ignore # pip install Pillow + import tcod import tcod.sdl.render import tcod.sdl.video -from PIL import Image, ImageDraw, ImageFont # type: ignore # pip install Pillow CURRENT_DIR = Path(__file__).parent # Directory of this script. font = ImageFont.truetype(bytes(CURRENT_DIR / "DejaVuSerif.ttf"), size=18) # Preloaded font file. diff --git a/examples/ttf.py b/examples/ttf.py old mode 100644 new mode 100755 index 249c771a..7268109f --- a/examples/ttf.py +++ b/examples/ttf.py @@ -12,9 +12,10 @@ import freetype # type: ignore # pip install freetype-py import numpy as np -import tcod from numpy.typing import NDArray +import tcod + FONT = "VeraMono.ttf" diff --git a/fonts/libtcod/README.txt b/fonts/libtcod/README.txt index c9726fbc..772f3efc 100755 --- a/fonts/libtcod/README.txt +++ b/fonts/libtcod/README.txt @@ -1,5 +1,5 @@ This directory contains antialiased fonts for libtcod. -These fonts are in public domain. +These fonts are in public domain. The file names are composed with : __.png diff --git a/scripts/tag_release.py b/scripts/tag_release.py old mode 100644 new mode 100755 diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index bf12f9a1..33cba999 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from typing import Any, Callable, Iterator, List, Optional, Tuple, Union import numpy diff --git a/tests/test_parser.py b/tests/test_parser.py index 2e0ef5ed..9a1dad7f 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import os from typing import Any diff --git a/tests/test_tcod.py b/tests/test_tcod.py index dbe2e89c..d3216e02 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import copy import pickle from typing import Any, NoReturn diff --git a/tests/test_testing.py b/tests/test_testing.py index 027da622..92604ae2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import os curdir = os.path.dirname(__file__) From 1aaaf7c486f850e03a8ef2ef3f6456d9a86fdf63 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 24 May 2023 23:45:55 -0700 Subject: [PATCH 0757/1101] Ensure deprecated colors still work. --- libtcodpy.py | 6 ++++-- pyproject.toml | 1 + tcod/__init__.py | 4 ++-- tcod/libtcodpy.py | 9 +++++++++ tests/test_deprecated.py | 6 ++++++ 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/libtcodpy.py b/libtcodpy.py index bb1a8cc8..63e2e0b0 100644 --- a/libtcodpy.py +++ b/libtcodpy.py @@ -1,9 +1,11 @@ -"""This module just an alias for tcod""" +"""Module alias for tcod.""" import warnings +from tcod import * # noqa: F4 +from tcod.libtcodpy import __getattr__ # noqa: F401 + warnings.warn( "'import tcod as libtcodpy' is preferred.", DeprecationWarning, stacklevel=2, ) -from tcod import * # noqa: F4 diff --git a/pyproject.toml b/pyproject.toml index d4baab4e..06c30b87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,7 @@ filterwarnings = [ "ignore::DeprecationWarning:tcod.libtcodpy", "ignore::PendingDeprecationWarning:tcod.libtcodpy", "ignore:This class may perform poorly and is no longer needed.::tcod.map", + "ignore:'import tcod as libtcodpy' is preferred.", ] [tool.mypy] diff --git a/tcod/__init__.py b/tcod/__init__.py index 4a4b5173..b14581dd 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -25,7 +25,7 @@ __version__ = "" -def __getattr__(name: str) -> color.Color: +def __getattr__(name: str, stacklevel: int = 1) -> color.Color: """Mark access to color constants as deprecated.""" value: color.Color | None = getattr(constants, name, None) if value is None: @@ -34,7 +34,7 @@ def __getattr__(name: str) -> color.Color: warnings.warn( f"Color constants will be removed from future releases.\nReplace `tcod.{name}` with `{tuple(value)}`.", FutureWarning, - stacklevel=2, + stacklevel=stacklevel + 1, ) return value diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 51974108..a14403c3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4201,6 +4201,15 @@ def _atexit_verify() -> None: lib.TCOD_console_delete(ffi.NULL) +def __getattr__(name: str) -> Color: + """Mark access to color constants as deprecated.""" + try: + return tcod.__getattr__(name, stacklevel=2) # type: ignore[call-arg] + except AttributeError: + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) from None + + __all__ = [ # noqa: F405 "Color", "Bsp", diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 8aa15303..425cafb1 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -3,8 +3,10 @@ import pytest +import libtcodpy import tcod import tcod.event +import tcod.libtcodpy # ruff: noqa: D103 @@ -13,6 +15,10 @@ def test_deprecate_color() -> None: with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): _ = tcod.black + with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): + _ = tcod.libtcodpy.black + with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): + _ = libtcodpy.black @pytest.mark.filterwarnings("error") From 00ebb839fff1e6fceb0200be65f63d609fc1b223 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 00:18:56 -0700 Subject: [PATCH 0758/1101] Set Sphinx version to not break the RTD theme. --- docs/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5f954b04..f25589d5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx>=5.0.2,!=5.1.0 -sphinx_rtd_theme +sphinx>=5.0.2,!=5.1.0,<6.1 # https://github.com/readthedocs/sphinx_rtd_theme/issues/1463 +sphinx_rtd_theme>=1.2.1 From 2749f2ef454d132645bd1c211676160f825cd9c1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 00:31:15 -0700 Subject: [PATCH 0759/1101] Update ReadTheDocs config file. --- .readthedocs.yaml | 31 +++++++++++++++++++++++++++++++ .readthedocs.yml | 24 ------------------------ 2 files changed, 31 insertions(+), 24 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 .readthedocs.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..390d6115 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,31 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + apt_packages: + - libsdl2-dev + +submodules: + include: all + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +# If using Sphinx, optionally build your docs in additional formats such as PDF +# formats: +# - pdf + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 2521036c..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,24 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -version: 2 - -build: - image: latest - -sphinx: - builder: html - configuration: docs/conf.py - fail_on_warning: true - -formats: - - htmlzip - - pdf - - epub - -python: - version: "3.8" - install: - - requirements: requirements.txt - - requirements: docs/requirements.txt From be8dfe67e7223c4d369792adaaa3261d264ff432 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 01:29:24 -0700 Subject: [PATCH 0760/1101] Setup trusted publishing for testing. The old deployment is kept in case anything breaks. That will be removed once this looks good. --- .github/workflows/python-package.yml | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0ec9701e..0dd25329 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -297,3 +297,61 @@ jobs: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: twine upload --skip-existing wheelhouse/* + + publish: + needs: [build] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + environment: + name: release + url: https://pypi.org/p/tcod + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: sdist + path: dist/ + - uses: actions/download-artifact@v3 + with: + name: wheels-windows + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + + publish-macos: + needs: [build-macos] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + environment: + name: release + url: https://pypi.org/p/tcod + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels-macos + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + + publish-linux: + needs: [linux-wheels] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + environment: + name: release + url: https://pypi.org/p/tcod + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels-linux + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true From dc2f84f61408fbe0d847a90ef847126c73f7237d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 01:38:52 -0700 Subject: [PATCH 0761/1101] Don't build package just to test SDL parsing. --- .github/workflows/python-package.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0dd25329..71976b35 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -85,12 +85,11 @@ jobs: with: python-version: "3.x" - name: Install build dependencies - run: pip install build - - name: Build package - run: python -m build + run: pip install -r requirements.txt + - name: Test SDL parsing + run: python build_sdl.py env: SDL_VERSION: ${{ matrix.sdl-version }} - TDL_BUILD: DEBUG build: needs: [black, isort, flake8, mypy] From f2c72095b247e033820e93d23f43761ddb23539d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 01:58:24 -0700 Subject: [PATCH 0762/1101] Modernize SDL build script. --- build_sdl.py | 57 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index 0bbe5241..2b7c19e7 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +"""Build script to parse SDL headers and generate CFFI bindings.""" from __future__ import annotations import io @@ -10,12 +11,14 @@ import sys import zipfile from pathlib import Path -from typing import Any, Dict, List, Set +from typing import Any import pcpp # type: ignore import requests -BITSIZE, LINKAGE = platform.architecture() +# ruff: noqa: S603, S607 # This script calls a lot of programs. + +BIT_SIZE, LINKAGE = platform.architecture() # Reject versions of SDL older than this, update the requirements in the readme if you change this. SDL_MIN_VERSION = (2, 0, 10) @@ -80,19 +83,21 @@ def check_sdl_version() -> None: needed_version = f"{SDL_MIN_VERSION[0]}.{SDL_MIN_VERSION[1]}.{SDL_MIN_VERSION[2]}" try: sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() - except FileNotFoundError: - raise RuntimeError( - "libsdl2-dev or equivalent must be installed on your system" - f" and must be at least version {needed_version}." - "\nsdl2-config must be on PATH." + except FileNotFoundError as exc: + msg = ( + "libsdl2-dev or equivalent must be installed on your system and must be at least version" + f" {needed_version}.\nsdl2-config must be on PATH." ) + raise RuntimeError(msg) from exc print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_MIN_VERSION: - raise RuntimeError("SDL version must be at least %s, (found %s)" % (needed_version, sdl_version_str)) + msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})" + raise RuntimeError(msg) def get_sdl2_file(version: str) -> Path: + """Return a path to an SDL2 archive for the current platform. The archive is downloaded if missing.""" if sys.platform == "win32": sdl2_file = f"SDL2-devel-{version}-VC.zip" else: @@ -102,14 +107,15 @@ def get_sdl2_file(version: str) -> Path: sdl2_remote_file = f"https://www.libsdl.org/release/{sdl2_file}" if not sdl2_local_file.exists(): print(f"Downloading {sdl2_remote_file}") - os.makedirs("dependencies/", exist_ok=True) - with requests.get(sdl2_remote_file) as response: + Path("dependencies/").mkdir(parents=True, exist_ok=True) + with requests.get(sdl2_remote_file) as response: # noqa: S113 response.raise_for_status() sdl2_local_file.write_bytes(response.content) return sdl2_local_file def unpack_sdl2(version: str) -> Path: + """Return the path to an extracted SDL distribution. Creates it if missing.""" sdl2_path = Path(f"dependencies/SDL2-{version}") if sys.platform == "darwin": sdl2_dir = sdl2_path @@ -134,10 +140,11 @@ class SDLParser(pcpp.Preprocessor): # type: ignore """A modified preprocessor to output code in a format for CFFI.""" def __init__(self) -> None: + """Initialise the object with empty values.""" super().__init__() self.line_directive = None # Don't output line directives. - self.known_string_defines: Dict[str, str] = {} - self.known_defines: Set[str] = set() + self.known_string_defines: dict[str, str] = {} + self.known_defines: set[str] = set() def get_output(self) -> str: """Return this objects current tokens as a string.""" @@ -151,7 +158,7 @@ def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curd """Remove bad includes such as stddef.h and stdarg.h.""" raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove) - def _should_track_define(self, tokens: List[Any]) -> bool: + def _should_track_define(self, tokens: list[Any]) -> bool: if len(tokens) < 3: return False if tokens[0].value in IGNORE_DEFINES: @@ -175,8 +182,9 @@ def _should_track_define(self, tokens: List[Any]) -> bool: ) def on_directive_handle( - self, directive: Any, tokens: List[Any], if_passthru: bool, preceding_tokens: List[Any] - ) -> Any: + self, directive: Any, tokens: list[Any], if_passthru: bool, preceding_tokens: list[Any] # noqa: ANN401 + ) -> Any: # noqa: ANN401 + """Catch and store definitions.""" if directive.value == "define" and self._should_track_define(tokens): if tokens[2].type == "CPP_STRING": self.known_string_defines[tokens[0].value] = tokens[2].value @@ -204,7 +212,7 @@ def on_directive_handle( assert matches for match in matches: - if os.path.isfile(Path(match, "SDL_stdinc.h")): + if Path(match, "SDL_stdinc.h").is_file(): SDL2_INCLUDE = match assert SDL2_INCLUDE @@ -224,6 +232,7 @@ def on_directive_handle( def get_cdef() -> str: + """Return the parsed code of SDL for CFFI.""" parser = SDLParser() parser.add_path(SDL2_INCLUDE) parser.parse( @@ -261,12 +270,12 @@ def get_cdef() -> str: return sdl2_cdef + EXTRA_CDEF -include_dirs: List[str] = [] -extra_compile_args: List[str] = [] -extra_link_args: List[str] = [] +include_dirs: list[str] = [] +extra_compile_args: list[str] = [] +extra_link_args: list[str] = [] -libraries: List[str] = [] -library_dirs: List[str] = [] +libraries: list[str] = [] +library_dirs: list[str] = [] if sys.platform == "darwin": @@ -278,16 +287,16 @@ def get_cdef() -> str: if sys.platform == "win32": include_dirs.append(str(SDL2_INCLUDE)) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) library_dirs.append(str(SDL2_LIB_DIR)) - SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BITSIZE]) + SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BIT_SIZE]) SDL2_LIB_DEST.mkdir(exist_ok=True) shutil.copy(SDL2_LIB_DIR / "SDL2.dll", SDL2_LIB_DEST) # Link to the SDL2 framework on MacOS. # Delocate will bundle the binaries in a later step. if sys.platform == "darwin": - HEADER_DIR = os.path.join(SDL2_PARSE_PATH, "Headers") + HEADER_DIR = Path(SDL2_PARSE_PATH, "Headers") include_dirs.append(HEADER_DIR) extra_link_args += [f"-F{SDL2_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", f"{SDL2_BUNDLE_PATH}/.."] From 30a7fa401b7e3257553dddc75938780b6553f1ac Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 02:50:33 -0700 Subject: [PATCH 0763/1101] Modernize libtcod build script. --- build_libtcod.py | 87 +++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index b0491e81..98571002 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 +"""Parse and compile libtcod and SDL sources for CFFI.""" from __future__ import annotations +import contextlib import glob import os import platform import re import sys from pathlib import Path -from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple, Union +from typing import Any, Iterable, Iterator from cffi import FFI @@ -20,7 +22,7 @@ HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") -BITSIZE, LINKAGE = platform.architecture() +BIT_SIZE, LINKAGE = platform.architecture() # Regular expressions to parse the headers for cffi. RE_COMMENT = re.compile(r"\s*/\*.*?\*/|\s*//*?$", re.DOTALL | re.MULTILINE) @@ -43,18 +45,18 @@ class ParsedHeader: """ # Class dictionary of all parsed headers. - all_headers: Dict[Path, ParsedHeader] = {} + all_headers: dict[Path, ParsedHeader] = {} def __init__(self, path: Path) -> None: + """Initialize and organize a header file.""" self.path = path = path.resolve(True) directory = path.parent depends = set() - with open(self.path, "r", encoding="utf-8") as f: - header = f.read() + header = self.path.read_text(encoding="utf-8") header = RE_COMMENT.sub("", header) header = RE_CPLUSPLUS.sub("", header) for dependency in RE_INCLUDE.findall(header): - depends.add((directory / dependency).resolve(True)) + depends.add((directory / str(dependency)).resolve(True)) header = RE_PREPROCESSOR.sub("", header) header = RE_TAGS.sub("", header) header = RE_VAFUNC.sub("", header) @@ -63,22 +65,22 @@ def __init__(self, path: Path) -> None: self.depends = frozenset(depends) self.all_headers[self.path] = self - def parsed_depends(self) -> Iterator["ParsedHeader"]: + def parsed_depends(self) -> Iterator[ParsedHeader]: """Return dependencies excluding ones that were not loaded.""" for dep in self.depends: - try: + with contextlib.suppress(KeyError): yield self.all_headers[dep] - except KeyError: - pass def __str__(self) -> str: - return "Parsed harder at '%s'\n Depends on: %s" % ( + """Return useful info on this object.""" + return "Parsed harder at '{}'\n Depends on: {}".format( self.path, - "\n\t".join(self.depends), + "\n\t".join(str(d) for d in self.depends), ) def __repr__(self) -> str: - return f"ParsedHeader({self.path})" + """Return the representation of this object.""" + return f"ParsedHeader({self.path!r})" def walk_includes(directory: str) -> Iterator[ParsedHeader]: @@ -93,10 +95,10 @@ def walk_includes(directory: str) -> Iterator[ParsedHeader]: def resolve_dependencies( includes: Iterable[ParsedHeader], -) -> List[ParsedHeader]: +) -> list[ParsedHeader]: """Sort headers by their correct include order.""" unresolved = set(includes) - resolved: Set[ParsedHeader] = set() + resolved: set[ParsedHeader] = set() result = [] while unresolved: for item in unresolved: @@ -104,26 +106,29 @@ def resolve_dependencies( resolved.add(item) result.append(item) if not unresolved & resolved: - raise RuntimeError( - "Could not resolve header load order.\n" - f"Possible cyclic dependency with the unresolved headers:\n{unresolved}" + msg = ( + "Could not resolve header load order." + "\nPossible cyclic dependency with the unresolved headers:" + f"\n{unresolved}" ) + raise RuntimeError(msg) unresolved -= resolved return result -def parse_includes() -> List[ParsedHeader]: +def parse_includes() -> list[ParsedHeader]: """Collect all parsed header files and return them. Reads HEADER_PARSE_PATHS and HEADER_PARSE_EXCLUDES. """ - includes: List[ParsedHeader] = [] + includes: list[ParsedHeader] = [] for dirpath in HEADER_PARSE_PATHS: includes.extend(walk_includes(dirpath)) return resolve_dependencies(includes) def walk_sources(directory: str) -> Iterator[str]: + """Iterate over the C sources of a directory recursively.""" for path, _dirs, files in os.walk(directory): for source in files: if source.endswith(".c"): @@ -133,7 +138,7 @@ def walk_sources(directory: str) -> Iterator[str]: includes = parse_includes() module_name = "tcod._libtcod" -include_dirs: List[str] = [ +include_dirs: list[str] = [ ".", "libtcod/src/vendor/", "libtcod/src/vendor/utf8proc", @@ -141,13 +146,13 @@ def walk_sources(directory: str) -> Iterator[str]: *build_sdl.include_dirs, ] -extra_compile_args: List[str] = [*build_sdl.extra_compile_args] -extra_link_args: List[str] = [*build_sdl.extra_link_args] -sources: List[str] = [] +extra_compile_args: list[str] = [*build_sdl.extra_compile_args] +extra_link_args: list[str] = [*build_sdl.extra_link_args] +sources: list[str] = [] -libraries: List[str] = [*build_sdl.libraries] -library_dirs: List[str] = [*build_sdl.library_dirs] -define_macros: List[Tuple[str, Any]] = [("Py_LIMITED_API", Py_LIMITED_API)] +libraries: list[str] = [*build_sdl.libraries] +library_dirs: list[str] = [*build_sdl.library_dirs] +define_macros: list[tuple[str, Any]] = [("Py_LIMITED_API", Py_LIMITED_API)] sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") @@ -173,7 +178,7 @@ def walk_sources(directory: str) -> Iterator[str]: tdl_build = os.environ.get("TDL_BUILD", "RELEASE").upper() MSVC_CFLAGS = {"DEBUG": ["/Od"], "RELEASE": ["/GL", "/O2", "/GS-", "/wd4996"]} -MSVC_LDFLAGS: Dict[str, List[str]] = {"DEBUG": [], "RELEASE": ["/LTCG"]} +MSVC_LDFLAGS: dict[str, list[str]] = {"DEBUG": [], "RELEASE": ["/LTCG"]} GCC_CFLAGS = { "DEBUG": ["-std=c99", "-Og", "-g", "-fPIC"], "RELEASE": [ @@ -238,7 +243,7 @@ def walk_sources(directory: str) -> Iterator[str]: ''' -def find_sdl_attrs(prefix: str) -> Iterator[Tuple[str, Union[int, str, Any]]]: +def find_sdl_attrs(prefix: str) -> Iterator[tuple[str, int | str | Any]]: """Return names and values from `tcod.lib`. `prefix` is used to filter out which names to copy. @@ -294,24 +299,22 @@ def parse_sdl_attrs(prefix: str, all_names: list[str] | None) -> tuple[str, str] ] -def update_module_all(filename: str, new_all: str) -> None: +def update_module_all(filename: Path, new_all: str) -> None: """Update the __all__ of a file with the constants from new_all.""" RE_CONSTANTS_ALL = re.compile( r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)", re.DOTALL, ) - with open(filename, "r", encoding="utf-8") as f: - match = RE_CONSTANTS_ALL.match(f.read()) + match = RE_CONSTANTS_ALL.match(filename.read_text(encoding="utf-8")) assert match, f"Can't determine __all__ subsection in {filename}!" header, footer = match.groups() - with open(filename, "w", encoding="utf-8") as f: - f.write(f"{header}\n {new_all},\n {footer}") + filename.write_text(f"{header}\n {new_all},\n {footer}", encoding="utf-8") def generate_enums(prefix: str) -> Iterator[str]: """Generate attribute assignments suitable for a Python enum.""" - for name, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): - name = name.split("_", 1)[1] + for symbol, value in sorted(find_sdl_attrs(prefix), key=lambda item: item[1]): + _, name = symbol.split("_", 1) if name.isdigit(): name = f"N{name}" if name in "IOl": # Handle Flake8 warnings. @@ -325,7 +328,7 @@ def write_library_constants() -> None: import tcod.color from tcod._libtcod import ffi, lib - with open("tcod/constants.py", "w", encoding="utf-8") as f: + with Path("tcod/constants.py").open("w", encoding="utf-8") as f: all_names = [] f.write(CONSTANT_MODULE_HEADER) for name in dir(lib): @@ -363,10 +366,10 @@ def write_library_constants() -> None: all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") - update_module_all("tcod/__init__.py", all_names_merged) - update_module_all("tcod/libtcodpy.py", all_names_merged) + update_module_all(Path("tcod/__init__.py"), all_names_merged) + update_module_all(Path("tcod/libtcodpy.py"), all_names_merged) - with open("tcod/event_constants.py", "w", encoding="utf-8") as f: + with Path("tcod/event_constants.py").open("w", encoding="utf-8") as f: all_names = [] f.write(EVENT_CONSTANT_MODULE_HEADER) f.write("\n# --- SDL scancodes ---\n") @@ -376,10 +379,10 @@ def write_library_constants() -> None: f.write(f"""{parse_sdl_attrs("SDLK", None)[0]}\n""") f.write("\n# --- SDL keyboard modifiers ---\n") - f.write("%s\n_REVERSE_MOD_TABLE = %s\n" % parse_sdl_attrs("KMOD", all_names)) + f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("KMOD", all_names))) f.write("\n# --- SDL wheel ---\n") - f.write("%s\n_REVERSE_WHEEL_TABLE = %s\n" % parse_sdl_attrs("SDL_MOUSEWHEEL", all_names)) + f.write("{}\n_REVERSE_WHEEL_TABLE = {}\n".format(*parse_sdl_attrs("SDL_MOUSEWHEEL", all_names))) all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") From 38e6ca9478493ce2b32f1c578468467888679445 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:20:31 -0700 Subject: [PATCH 0764/1101] Have Ruff inherit the target version from the project. --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 06c30b87..dd068d98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,4 +178,3 @@ ignore = [ "D409", # section-underline-matches-section-length ] line-length = 120 -target-version = "py37" From 71a015220c2641757131f2c53abee7aad9171111 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:33:52 -0700 Subject: [PATCH 0765/1101] Modernize tag release script. --- scripts/tag_release.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 32d47d70..92e896fe 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +"""Automate tagged releases of this project.""" from __future__ import annotations import argparse @@ -8,7 +9,8 @@ import subprocess import sys from pathlib import Path -from typing import Tuple + +# ruff: noqa: INP001, S603, S607 PROJECT_DIR = Path(__file__).parent.parent @@ -23,7 +25,7 @@ parser.add_argument("-v", "--verbose", action="store_true", help="Print debug information.") -def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: +def parse_changelog(args: argparse.Namespace) -> tuple[str, str]: """Return an updated changelog and and the list of changes.""" match = re.match( pattern=r"(.*?## \[Unreleased]\n)(.+?\n)(\n*## \[.*)", @@ -32,9 +34,9 @@ def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: ) assert match header, changes, tail = match.groups() - tagged = "\n## [%s] - %s\n%s" % ( + tagged = "\n## [{}] - {}\n{}".format( args.tag, - datetime.date.today().isoformat(), + datetime.date.today().isoformat(), # Local timezone is fine, probably. # noqa: DTZ011 changes, ) if args.verbose: @@ -45,6 +47,7 @@ def parse_changelog(args: argparse.Namespace) -> Tuple[str, str]: def replace_unreleased_tags(tag: str, dry_run: bool) -> None: + """Walk though sources and replace pending tags with the new tag.""" match = re.match(r"\d+\.\d+", tag) assert match short_tag = match.group() @@ -62,7 +65,8 @@ def replace_unreleased_tags(tag: str, dry_run: bool) -> None: def main() -> None: - if len(sys.argv) == 1: + """Entry function.""" + if len(sys.argv) <= 1: parser.print_help(sys.stderr) sys.exit(1) @@ -79,8 +83,8 @@ def main() -> None: if not args.dry_run: (PROJECT_DIR / "CHANGELOG.md").write_text(new_changelog, encoding="utf-8") edit = ["-e"] if args.edit else [] - subprocess.check_call(["git", "commit", "-avm", "Prepare %s release." % args.tag] + edit) - subprocess.check_call(["git", "tag", args.tag, "-am", "%s\n\n%s" % (args.tag, changes)] + edit) + subprocess.check_call(["git", "commit", "-avm", f"Prepare {args.tag} release.", *edit]) + subprocess.check_call(["git", "tag", args.tag, "-am", f"{args.tag}\n\n{changes}", *edit]) if __name__ == "__main__": From aff2fefed7c2919abf755c73281dd4858394a4ff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:36:07 -0700 Subject: [PATCH 0766/1101] Prepare 15.0.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9043af2..f0a2ab97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [15.0.2] - 2023-05-25 ### Deprecated - Deprecated all libtcod color constants. Replace these with your own manually defined colors. Using a color will tell you the color values of the deprecated color in the warning. From dc244d4c8d86fd27e9a891a814de5d6bdfdfe9ab Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:47:13 -0700 Subject: [PATCH 0767/1101] Remove Twine uploads from CI. The Twine command failed to run. I'll have to fix this and redeploy. --- .github/workflows/python-package.yml | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 71976b35..56bd1aa2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -128,7 +128,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark build twine + pip install pytest pytest-cov pytest-benchmark build if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | @@ -148,13 +148,6 @@ jobs: if: runner.os != 'Windows' run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v3 - - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') && runner.os != 'Linux' - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing dist/* - uses: actions/upload-artifact@v3 if: runner.os == 'Linux' with: @@ -223,7 +216,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install twine cibuildwheel==2.3.1 + pip install cibuildwheel==2.3.1 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -245,13 +238,6 @@ jobs: name: wheels-linux path: wheelhouse/*.whl retention-days: 7 - - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload --skip-existing wheelhouse/* build-macos: needs: [black, isort, flake8, mypy] @@ -271,7 +257,7 @@ jobs: # https://github.com/actions/checkout/issues/290 run: git describe --tags - name: Install Python dependencies - run: pip3 install wheel twine -r requirements.txt + run: pip3 install -r requirements.txt - name: Prepare package # Downloads SDL2 for the later step. run: python3 setup.py || true @@ -290,12 +276,6 @@ jobs: name: wheels-macos path: wheelhouse/*.whl retention-days: 7 - - name: Upload to PyPI - if: startsWith(github.ref, 'refs/tags/') - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: twine upload --skip-existing wheelhouse/* publish: needs: [build] From b4a04365a63b5fd3896e51b5e2e3dfa4d7f35d06 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:57:40 -0700 Subject: [PATCH 0768/1101] Replace deprecated release action. --- .github/workflows/release-on-tag.yml | 13 +++++-------- .vscode/settings.json | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index cecfbda6..95cf02d3 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -9,6 +9,8 @@ jobs: build: name: Create Release runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v3 @@ -17,12 +19,7 @@ jobs: scripts/get_release_description.py | tee release_body.md - name: Create Release id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + uses: ncipollo/release-action@v1 with: - tag_name: ${{ github.ref }} - release_name: "" - body_path: release_body.md - draft: false - prerelease: false + name: "" + bodyFile: release_body.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 12dc2d77..6351b6e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -265,6 +265,7 @@ "muly", "mypy", "namegen", + "ncipollo", "ndarray", "ndim", "newaxis", From 814f54f55c424ff8330868fa8c844be8010324d2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 03:58:44 -0700 Subject: [PATCH 0769/1101] Prepare 15.0.3 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a2ab97..d73c8da5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [15.0.3] - 2023-05-25 + ## [15.0.2] - 2023-05-25 ### Deprecated - Deprecated all libtcod color constants. Replace these with your own manually defined colors. From 2539d296678dc0bcaeaaa163a9cda716bf1bc345 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 05:18:43 -0700 Subject: [PATCH 0770/1101] Reorganize workflows. Skip tests on emulated architectures. Make an sdist in its own job so that isolated tests can start sooner. Let more builds/tests run in parallel. Merge publish jobs so that PyPI updates are done all at once. --- .github/workflows/python-package.yml | 95 ++++++++++++---------------- pyproject.toml | 4 ++ 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 56bd1aa2..2f4960a9 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -55,17 +55,37 @@ jobs: - uses: actions/checkout@v3 - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - name: Install Python dependencies + - name: Install typing dependencies run: pip install mypy pytest -r requirements.txt - - name: Fake initialize package - run: | - echo '__version__ = ""' > tcod/version.py - name: Mypy uses: liskin/gh-problem-matcher-wrap@v2 with: linters: mypy run: mypy --show-column-numbers + sdist: + runs-on: ubuntu-latest + steps: + - name: APT update + run: sudo apt-get update + - name: Install APT dependencies + run: sudo apt-get install libsdl2-dev + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - name: Install build + run: pip install build + - name: Build source distribution + run: python -m build --sdist + - uses: actions/upload-artifact@v3 + with: + name: sdist + path: dist/tcod-*.tar.gz + retention-days: 7 + + # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: needs: [black, isort, flake8, mypy] @@ -133,7 +153,7 @@ jobs: - name: Initialize package run: | pip install -e . # Install the package in-place. - - name: Build package. + - name: Build package run: | python -m build - name: Test with pytest @@ -148,12 +168,6 @@ jobs: if: runner.os != 'Windows' run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v3 - - uses: actions/upload-artifact@v3 - if: runner.os == 'Linux' - with: - name: sdist - path: dist/tcod-*.tar.gz - retention-days: 7 - uses: actions/upload-artifact@v3 if: runner.os == 'Windows' with: @@ -162,7 +176,7 @@ jobs: retention-days: 7 isolated: # Test installing the package from source. - needs: build + needs: [black, isort, flake8, mypy, sdist] runs-on: ${{ matrix.os }} strategy: matrix: @@ -192,8 +206,7 @@ jobs: python -c "import tcod" linux-wheels: - needs: build # These take a while to build/test, so wait for normal tests to pass first. - if: startsWith(github.event.ref, 'refs/tags/') + needs: [black, isort, flake8, mypy] runs-on: "ubuntu-latest" strategy: matrix: @@ -232,6 +245,8 @@ jobs: yum install -y SDL2-devel CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod" + # Skip test on emulated architectures + CIBW_TEST_SKIP: "*_aarch64" - name: Archive wheel uses: actions/upload-artifact@v3 with: @@ -252,15 +267,11 @@ jobs: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - name: Print git describe - # "--tags" is required to workaround actions/checkout's broken annotated tag handing. - # https://github.com/actions/checkout/issues/290 - run: git describe --tags - name: Install Python dependencies run: pip3 install -r requirements.txt - name: Prepare package # Downloads SDL2 for the later step. - run: python3 setup.py || true + run: python3 build_sdl.py - name: Build wheels uses: pypa/cibuildwheel@v2.12.3 env: @@ -278,7 +289,7 @@ jobs: retention-days: 7 publish: - needs: [build] + needs: [sdist, build, build-macos, linux-wheels] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') environment: @@ -295,42 +306,14 @@ jobs: with: name: wheels-windows path: dist/ + - uses: actions/download-artifact@v3 + with: + name: wheels-macos + path: dist/ + - uses: actions/download-artifact@v3 + with: + name: wheels-linux + path: dist/ - uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true - - publish-macos: - needs: [build-macos] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - environment: - name: release - url: https://pypi.org/p/tcod - permissions: - id-token: write - steps: - - uses: actions/download-artifact@v3 - with: - name: wheels-macos - path: dist/ - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true - - publish-linux: - needs: [linux-wheels] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - environment: - name: release - url: https://pypi.org/p/tcod - permissions: - id-token: write - steps: - - uses: actions/download-artifact@v3 - with: - name: wheels-linux - path: dist/ - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true diff --git a/pyproject.toml b/pyproject.toml index dd068d98..e181a747 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,10 @@ exclude = [ module = "numpy.*" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "tcod.version" +ignore_missing_imports = true + [tool.ruff] # https://beta.ruff.rs/docs/rules/ From 531e72ded1243f5e8a6e644a2781dcb51f502790 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 25 May 2023 05:45:24 -0700 Subject: [PATCH 0771/1101] Remove missing version from changelog. It failed to deploy. 15.0.2 only exists as a tag at this point. --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d73c8da5..e5730d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,6 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ## [15.0.3] - 2023-05-25 - -## [15.0.2] - 2023-05-25 ### Deprecated - Deprecated all libtcod color constants. Replace these with your own manually defined colors. Using a color will tell you the color values of the deprecated color in the warning. From 5692a258d09dc6d77d687146697ec3635494601d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 26 May 2023 21:55:16 -0700 Subject: [PATCH 0772/1101] Disable and remove Flake8. Converting over to Ruff which has slightly different warnings for the same things, and the Flake8 warnings are being distracting. --- .github/workflows/python-package.yml | 22 +++++----------------- .vscode/settings.json | 2 +- build_libtcod.py | 2 +- setup.cfg | 4 ---- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2f4960a9..9161d910 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,18 +37,6 @@ jobs: linters: isort run: isort scripts/ tcod/ tests/ examples/ --check --diff - flake8: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Flake8 - run: pip install Flake8 - - name: Flake8 - uses: liskin/gh-problem-matcher-wrap@v2 - with: - linters: flake8 - run: flake8 scripts/ tcod/ tests/ - mypy: runs-on: ubuntu-latest steps: @@ -88,7 +76,7 @@ jobs: # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: - needs: [black, isort, flake8, mypy] + needs: [black, isort, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -112,7 +100,7 @@ jobs: SDL_VERSION: ${{ matrix.sdl-version }} build: - needs: [black, isort, flake8, mypy] + needs: [black, isort, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -176,7 +164,7 @@ jobs: retention-days: 7 isolated: # Test installing the package from source. - needs: [black, isort, flake8, mypy, sdist] + needs: [black, isort, mypy, sdist] runs-on: ${{ matrix.os }} strategy: matrix: @@ -206,7 +194,7 @@ jobs: python -c "import tcod" linux-wheels: - needs: [black, isort, flake8, mypy] + needs: [black, isort, mypy] runs-on: "ubuntu-latest" strategy: matrix: @@ -255,7 +243,7 @@ jobs: retention-days: 7 build-macos: - needs: [black, isort, flake8, mypy] + needs: [black, isort, mypy] runs-on: "macos-11" strategy: fail-fast: true diff --git a/.vscode/settings.json b/.vscode/settings.json index 6351b6e7..4274b1dd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,7 @@ "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "python.linting.enabled": true, - "python.linting.flake8Enabled": true, + "python.linting.flake8Enabled": false, "python.linting.mypyEnabled": true, "python.linting.mypyArgs": [ "--follow-imports=silent", diff --git a/build_libtcod.py b/build_libtcod.py index 98571002..3f5d2772 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -317,7 +317,7 @@ def generate_enums(prefix: str) -> Iterator[str]: _, name = symbol.split("_", 1) if name.isdigit(): name = f"N{name}" - if name in "IOl": # Handle Flake8 warnings. + if name in "IOl": # Ignore ambiguous variable name warnings. yield f"{name} = {value} # noqa: E741" else: yield f"{name} = {value}" diff --git a/setup.cfg b/setup.cfg index 75665f21..9cc8e393 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,3 @@ py-limited-api = cp36 [aliases] test=pytest - -[flake8] -ignore = E203 W503 TYP001 -max-line-length = 130 From 756722f8c1a636e46a384d20c9e926dbace76ed2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 26 May 2023 23:04:41 -0700 Subject: [PATCH 0773/1101] Use Ruff to clean up tcod, bsp, console, context, event, and image modules. --- pyproject.toml | 4 + tcod/__init__.py | 9 +- tcod/bsp.py | 42 ++++---- tcod/console.py | 127 +++++++++++------------ tcod/context.py | 85 ++++++++-------- tcod/event.py | 259 +++++++++++++++++++++++++---------------------- tcod/image.py | 31 +++--- 7 files changed, 289 insertions(+), 268 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e181a747..4df86fb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,3 +182,7 @@ ignore = [ "D409", # section-underline-matches-section-length ] line-length = 120 + +[tool.ruff.pydocstyle] +# Use Google-style docstrings. +convention = "google" diff --git a/tcod/__init__.py b/tcod/__init__.py index b14581dd..0082d521 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -14,10 +14,10 @@ __path__ = extend_path(__path__, __name__) from tcod import bsp, color, console, constants, context, event, image, los, map, noise, path, random, tileset -from tcod.console import Console # noqa: F401 -from tcod.constants import * # noqa: F4 -from tcod.libtcodpy import * # noqa: F4 -from tcod.loader import __sdl_version__, ffi, lib # noqa: F4 +from tcod.console import Console +from tcod.constants import * # noqa: F403 +from tcod.libtcodpy import * # noqa: F403 +from tcod.loader import __sdl_version__, ffi, lib try: from tcod.version import __version__ @@ -41,6 +41,7 @@ def __getattr__(name: str, stacklevel: int = 1) -> color.Color: __all__ = [ # noqa: F405 "__version__", + "__sdl_version__", "lib", "ffi", "bsp", diff --git a/tcod/bsp.py b/tcod/bsp.py index 23c54ef0..8734c340 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -1,10 +1,11 @@ -r""" +r"""Libtcod's Binary Space Partitioning. + The following example shows how to traverse the BSP tree using Python. This assumes `create_room` and `connect_rooms` will be replaced by custom code. Example:: - import tcod + import tcod.bsp bsp = tcod.bsp.BSP(x=0, y=0, width=80, height=60) bsp.split_recursive( @@ -25,14 +26,14 @@ """ from __future__ import annotations -from typing import Any, Iterator, List, Optional, Tuple, Union # noqa: F401 +from typing import Any, Iterator import tcod.random from tcod._internal import deprecate from tcod.loader import ffi, lib -class BSP(object): +class BSP: """A binary space partitioning tree which can be used for simple dungeon generation. Attributes: @@ -55,7 +56,8 @@ class BSP(object): height (int): Rectangle height. """ - def __init__(self, x: int, y: int, width: int, height: int): + def __init__(self, x: int, y: int, width: int, height: int) -> None: + """Initialize a root node of a BSP tree.""" self.x = x self.y = y self.width = width @@ -65,11 +67,12 @@ def __init__(self, x: int, y: int, width: int, height: int): self.position = 0 self.horizontal = False - self.parent: Optional[BSP] = None - self.children: Union[Tuple[()], Tuple[BSP, BSP]] = () + self.parent: BSP | None = None + self.children: tuple[()] | tuple[BSP, BSP] = () @property - def w(self) -> int: + @deprecate("This attribute has been renamed to `width`.", FutureWarning) + def w(self) -> int: # noqa: D102 return self.width @w.setter @@ -77,7 +80,8 @@ def w(self, value: int) -> None: self.width = value @property - def h(self) -> int: + @deprecate("This attribute has been renamed to `height`.", FutureWarning) + def h(self) -> int: # noqa: D102 return self.height @h.setter @@ -92,7 +96,7 @@ def _as_cdata(self) -> Any: cdata.level = self.level return cdata - def __str__(self) -> str: + def __repr__(self) -> str: """Provide a useful readout when printed.""" status = "leaf" if self.children: @@ -101,7 +105,7 @@ def __str__(self) -> str: self.horizontal, ) - return "<%s(x=%i,y=%i,width=%i,height=%i)level=%i,%s>" % ( + return "<%s(x=%i,y=%i,width=%i,height=%i) level=%i %s>" % ( self.__class__.__name__, self.x, self.y, @@ -131,21 +135,21 @@ def split_once(self, horizontal: bool, position: int) -> None: """Split this partition into 2 sub-partitions. Args: - horizontal (bool): - position (int): + horizontal (bool): If True then the sub-partition is split into an upper and bottom half. + position (int): The position of where to put the divider relative to the current node. """ cdata = self._as_cdata() lib.TCOD_bsp_split_once(cdata, horizontal, position) self._unpack_bsp_tree(cdata) - def split_recursive( + def split_recursive( # noqa: PLR0913 self, depth: int, min_width: int, min_height: int, max_horizontal_ratio: float, max_vertical_ratio: float, - seed: Optional[tcod.random.Random] = None, + seed: tcod.random.Random | None = None, ) -> None: """Divide this partition recursively. @@ -229,7 +233,7 @@ def inverted_level_order(self) -> Iterator[BSP]: .. versionadded:: 8.3 """ - levels: List[List[BSP]] = [] + levels: list[list[BSP]] = [] next = [self] while next: levels.append(next) @@ -253,16 +257,16 @@ def contains(self, x: int, y: int) -> bool: """ return self.x <= x < self.x + self.width and self.y <= y < self.y + self.height - def find_node(self, x: int, y: int) -> Optional[BSP]: + def find_node(self, x: int, y: int) -> BSP | None: """Return the deepest node which contains these coordinates. Returns: - Optional[BSP]: BSP object or None. + BSP object or None. """ if not self.contains(x, y): return None for child in self.children: - found: Optional[BSP] = child.find_node(x, y) + found = child.find_node(x, y) if found: return found return self diff --git a/tcod/console.py b/tcod/console.py index 3ffa437b..77a75c0e 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -8,7 +8,7 @@ import warnings from os import PathLike from pathlib import Path -from typing import Any, Iterable, Optional, Sequence, Tuple, Union +from typing import Any, Iterable import numpy as np from numpy.typing import NDArray @@ -116,9 +116,9 @@ def __init__( width: int, height: int, order: Literal["C", "F"] = "C", - buffer: Optional[NDArray[Any]] = None, + buffer: NDArray[Any] | None = None, ): - self._key_color: Optional[Tuple[int, int, int]] = None + self._key_color: tuple[int, int, int] | None = None self._order = tcod._internal.verify_order(order) if buffer is not None: if self._order == "F": @@ -152,7 +152,7 @@ def __init__( self.clear() @classmethod - def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> Console: + def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> Console: # noqa: ANN401 """Return a Console instance which wraps this `TCOD_Console*` object.""" if isinstance(cdata, cls): return cdata @@ -162,7 +162,7 @@ def _from_cdata(cls, cdata: Any, order: Literal["C", "F"] = "C") -> Console: return self @classmethod - def _get_root(cls, order: Optional[Literal["C", "F"]] = None) -> Console: + def _get_root(cls, order: Literal["C", "F"] | None = None) -> Console: """Return a root console singleton with valid buffers. This function will also update an already active root console. @@ -196,12 +196,12 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: @property def width(self) -> int: - """int: The width of this Console. (read-only)""" + """The width of this Console.""" return lib.TCOD_console_get_width(self.console_c) # type: ignore @property def height(self) -> int: - """int: The height of this Console. (read-only)""" + """The height of this Console.""" return lib.TCOD_console_get_height(self.console_c) # type: ignore @property @@ -341,25 +341,25 @@ def rgb(self) -> NDArray[Any]: return self.rgba.view(self._DTYPE_RGB) @property - def default_bg(self) -> Tuple[int, int, int]: + def default_bg(self) -> tuple[int, int, int]: """Tuple[int, int, int]: The default background color.""" color = self._console_data.back return color.r, color.g, color.b @default_bg.setter @deprecate("Console defaults have been deprecated.") - def default_bg(self, color: Tuple[int, int, int]) -> None: + def default_bg(self, color: tuple[int, int, int]) -> None: self._console_data.back = color @property - def default_fg(self) -> Tuple[int, int, int]: + def default_fg(self) -> tuple[int, int, int]: """Tuple[int, int, int]: The default foreground color.""" color = self._console_data.fore return color.r, color.g, color.b @default_fg.setter @deprecate("Console defaults have been deprecated.") - def default_fg(self, color: Tuple[int, int, int]) -> None: + def default_fg(self, color: tuple[int, int, int]) -> None: self._console_data.fore = color @property @@ -382,10 +382,10 @@ def default_alignment(self) -> int: def default_alignment(self, value: int) -> None: self._console_data.alignment = value - def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: + def __clear_warning(self, name: str, value: tuple[int, int, int]) -> None: """Raise a warning for bad default values during calls to clear.""" warnings.warn( - "Clearing with the console default values is deprecated.\n" "Add %s=%r to this call." % (name, value), + f"Clearing with the console default values is deprecated.\nAdd {name}={value!r} to this call.", DeprecationWarning, stacklevel=3, ) @@ -393,8 +393,8 @@ def __clear_warning(self, name: str, value: Tuple[int, int, int]) -> None: def clear( self, ch: int = 0x20, - fg: Tuple[int, int, int] = ..., # type: ignore - bg: Tuple[int, int, int] = ..., # type: ignore + fg: tuple[int, int, int] = ..., # type: ignore + bg: tuple[int, int, int] = ..., # type: ignore ) -> None: """Reset all values in this console to a single value. @@ -459,25 +459,22 @@ def put_char( 13: "tcod.BKGND_DEFAULT", } - def __deprecate_defaults( + def __deprecate_defaults( # noqa: C901, PLR0912 self, new_func: str, - bg_blend: Any, - alignment: Any = ..., - clear: Any = ..., + bg_blend: Any, # noqa: ANN401 + alignment: Any = ..., # noqa: ANN401 + clear: Any = ..., # noqa: ANN401 ) -> None: """Return the parameters needed to recreate the current default state.""" if not __debug__: return - fg: Optional[Tuple[int, int, int]] = self.default_fg - bg: Optional[Tuple[int, int, int]] = self.default_bg + fg: tuple[int, int, int] | None = self.default_fg + bg: tuple[int, int, int] | None = self.default_bg if bg_blend == tcod.constants.BKGND_NONE: bg = None - if bg_blend == tcod.constants.BKGND_DEFAULT: - bg_blend = self.default_bg_blend - else: - bg_blend = None + bg_blend = self.default_bg_blend if bg_blend == tcod.constants.BKGND_DEFAULT else None if bg_blend == tcod.constants.BKGND_NONE: bg = None bg_blend = None @@ -497,22 +494,19 @@ def __deprecate_defaults( if clear is False: params.append("ch=0") if fg is not None: - params.append("fg=%s" % (fg,)) + params.append(f"fg={fg}") if bg is not None: - params.append("bg=%s" % (bg,)) + params.append(f"bg={bg}") if bg_blend is not None: - params.append("bg_blend=%s" % (self.__BG_BLEND_LOOKUP[bg_blend],)) + params.append(f"bg_blend={self.__BG_BLEND_LOOKUP[bg_blend]}") if alignment is not None: - params.append("alignment=%s" % (self.__ALIGNMENT_LOOKUP[alignment],)) + params.append(f"alignment={self.__ALIGNMENT_LOOKUP[alignment]}") param_str = ", ".join(params) - if not param_str: - param_str = "." - else: - param_str = " and add the following parameters:\n%s" % (param_str,) + param_str = "." if not param_str else f" and add the following parameters:\n{param_str}" warnings.warn( "Console functions using default values have been deprecated.\n" - "Replace this method with `Console.%s`%s" % (new_func, param_str), - DeprecationWarning, + f"Replace this method with `Console.{new_func}`{param_str}", + FutureWarning, stacklevel=3, ) @@ -522,7 +516,7 @@ def print_( y: int, string: str, bg_blend: int = tcod.constants.BKGND_DEFAULT, - alignment: Optional[int] = None, + alignment: int | None = None, ) -> None: """Print a color formatted string on a console. @@ -551,7 +545,7 @@ def print_rect( height: int, string: str, bg_blend: int = tcod.constants.BKGND_DEFAULT, - alignment: Optional[int] = None, + alignment: int | None = None, ) -> int: """Print a string constrained to a rectangle. @@ -748,7 +742,7 @@ def blit( height: int = 0, fg_alpha: float = 1.0, bg_alpha: float = 1.0, - key_color: Optional[Tuple[int, int, int]] = None, + key_color: tuple[int, int, int] | None = None, ) -> None: """Blit from this console onto the ``dest`` console. @@ -828,7 +822,7 @@ def blit( ) @deprecate("Pass the key color to Console.blit instead of calling this function.") - def set_key_color(self, color: Optional[Tuple[int, int, int]]) -> None: + def set_key_color(self, color: tuple[int, int, int] | None) -> None: """Set a consoles blit transparent color. `color` is the (r, g, b) color, or None to disable key color. @@ -853,7 +847,8 @@ def __enter__(self) -> Console: :any:`tcod.console_init_root` """ if self.console_c != ffi.NULL: - raise NotImplementedError("Only the root console has a context.") + msg = "Only the root console has a context." + raise NotImplementedError(msg) return self def close(self) -> None: @@ -865,7 +860,8 @@ def close(self) -> None: .. versionadded:: 11.11 """ if self.console_c != ffi.NULL: - raise NotImplementedError("Only the root console can be used to close libtcod's window.") + msg = "Only the root console can be used to close libtcod's window." + raise NotImplementedError(msg) lib.TCOD_console_delete(self.console_c) def __exit__(self, *args: Any) -> None: @@ -882,7 +878,7 @@ def __bool__(self) -> bool: """ return bool(self.console_c != ffi.NULL) - def __getstate__(self) -> Any: + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() del state["console_c"] state["_console_data"] = { @@ -897,7 +893,7 @@ def __getstate__(self) -> Any: state["_tiles"] = np.array(self._tiles, copy=True) return state - def __setstate__(self, state: Any) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: self._key_color = None if "_tiles" not in state: tiles: NDArray[Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE) @@ -933,8 +929,8 @@ def print( x: int, y: int, string: str, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, ) -> None: @@ -984,8 +980,8 @@ def print_box( width: int, height: int, string: str, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, ) -> int: @@ -1044,11 +1040,11 @@ def draw_frame( height: int, title: str = "", clear: bool = True, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, *, - decoration: Union[str, Tuple[int, int, int, int, int, int, int, int, int]] = "┌─┐│ │└─┘", + decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘", ) -> None: r"""Draw a framed rectangle with an optional title. @@ -1111,9 +1107,8 @@ def draw_frame( └─┤Lower├──┘> """ if title and decoration != "┌─┐│ │└─┘": - raise TypeError( - "The title and decoration parameters are mutually exclusive. You should print the title manually." - ) + msg = "The title and decoration parameters are mutually exclusive. You should print the title manually." + raise TypeError(msg) if title: warnings.warn( "The title parameter will be removed in the future since the style is hard-coded.", @@ -1135,13 +1130,10 @@ def draw_frame( clear, ) return - decoration_: Sequence[int] - if isinstance(decoration, str): - decoration_ = [ord(c) for c in decoration] - else: - decoration_ = decoration + decoration_ = [ord(c) for c in decoration] if isinstance(decoration, str) else decoration if len(decoration_) != 9: - raise TypeError(f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)") + msg = f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)" + raise TypeError(msg) _check( lib.TCOD_console_draw_frame_rgb( self.console_c, @@ -1164,8 +1156,8 @@ def draw_rect( width: int, height: int, ch: int, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, ) -> None: """Draw characters and colors over a rectangular region. @@ -1234,7 +1226,7 @@ def get_height_rect(width: int, string: str) -> int: @deprecate("This function does not support contexts.") -def recommended_size() -> Tuple[int, int]: +def recommended_size() -> tuple[int, int]: """Return the recommended size of a console for the current active window. The return is determined from the active tileset size and active window @@ -1253,7 +1245,8 @@ def recommended_size() -> Tuple[int, int]: Use :any:`Context.recommended_console_size` instead. """ if not lib.TCOD_ctx.engine: - raise RuntimeError("The libtcod engine was not initialized first.") + msg = "The libtcod engine was not initialized first." + raise RuntimeError(msg) window = lib.TCOD_sys_get_sdl_window() renderer = lib.TCOD_sys_get_sdl_renderer() with ffi.new("int[2]") as xy: @@ -1266,7 +1259,7 @@ def recommended_size() -> Tuple[int, int]: return w, h -def load_xp(path: Union[str, PathLike[str]], order: Literal["C", "F"] = "C") -> Tuple[Console, ...]: +def load_xp(path: str | PathLike[str], order: Literal["C", "F"] = "C") -> tuple[Console, ...]: """Load a REXPaint file as a tuple of consoles. `path` is the name of the REXPaint file to load. @@ -1299,9 +1292,7 @@ def load_xp(path: Union[str, PathLike[str]], order: Literal["C", "F"] = "C") -> is_transparent = (console.rgb["bg"] == KEY_COLOR).all(axis=-1) console.rgba[is_transparent] = (ord(" "), (0,), (0,)) """ - path = Path(path) - if not path.exists(): - raise FileNotFoundError(f"File not found:\n\t{path.resolve()}") + path = Path(path).resolve(strict=True) layers = _check(tcod.lib.TCOD_load_xp(bytes(path), 0, ffi.NULL)) consoles = ffi.new("TCOD_Console*[]", layers) _check(tcod.lib.TCOD_load_xp(bytes(path), layers, consoles)) @@ -1309,7 +1300,7 @@ def load_xp(path: Union[str, PathLike[str]], order: Literal["C", "F"] = "C") -> def save_xp( - path: Union[str, PathLike[str]], + path: str | PathLike[str], consoles: Iterable[Console], compress_level: int = 9, ) -> None: diff --git a/tcod/context.py b/tcod/context.py index cc82c90f..b8f359d4 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -46,17 +46,17 @@ :any:`tcod.mouse_get_status`. .. versionadded:: 11.12 -""" # noqa: E501 +""" from __future__ import annotations import copy -import os import pickle import sys import warnings -from typing import Any, Iterable, List, Optional, Tuple, TypeVar +from pathlib import Path +from typing import Any, Iterable, NoReturn, TypeVar -from typing_extensions import Literal, NoReturn +from typing_extensions import Literal import tcod import tcod.event @@ -149,18 +149,18 @@ """ -def _handle_tileset(tileset: Optional[tcod.tileset.Tileset]) -> Any: +def _handle_tileset(tileset: tcod.tileset.Tileset | None) -> Any: """Get the TCOD_Tileset pointer from a Tileset or return a NULL pointer.""" return tileset._tileset_p if tileset else ffi.NULL -def _handle_title(title: Optional[str]) -> Any: +def _handle_title(title: str | None) -> Any: """Return title as a CFFI string. If title is None then return a decent default title is returned. """ if title is None: - title = os.path.basename(sys.argv[0]) + title = Path(sys.argv[0]).name return ffi.new("char[]", title.encode("utf-8")) @@ -170,7 +170,7 @@ class Context: Use :any:`tcod.context.new` to create a new context. """ - def __init__(self, context_p: Any): + def __init__(self, context_p: Any) -> None: """Create a context from a cffi pointer.""" self._context_p = context_p @@ -201,8 +201,8 @@ def present( *, keep_aspect: bool = False, integer_scaling: bool = False, - clear_color: Tuple[int, int, int] = (0, 0, 0), - align: Tuple[float, float] = (0.5, 0.5), + clear_color: tuple[int, int, int] = (0, 0, 0), + align: tuple[float, float] = (0.5, 0.5), ) -> None: """Present a console to this context's display. @@ -238,13 +238,13 @@ def present( ) _check(lib.TCOD_context_present(self._context_p, console.console_c, viewport_args)) - def pixel_to_tile(self, x: int, y: int) -> Tuple[int, int]: + def pixel_to_tile(self, x: int, y: int) -> tuple[int, int]: """Convert window pixel coordinates to tile coordinates.""" with ffi.new("int[2]", (x, y)) as xy: _check(lib.TCOD_context_screen_pixel_to_tile_i(self._context_p, xy, xy + 1)) return xy[0], xy[1] - def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]: + def pixel_to_subtile(self, x: int, y: int) -> tuple[float, float]: """Convert window pixel coordinates to sub-tile coordinates.""" with ffi.new("double[2]", (x, y)) as xy: _check(lib.TCOD_context_screen_pixel_to_tile_d(self._context_p, xy, xy + 1)) @@ -283,12 +283,12 @@ def convert_event(self, event: _Event) -> _Event: ) return event_copy - def save_screenshot(self, path: Optional[str] = None) -> None: + def save_screenshot(self, path: str | None = None) -> None: """Save a screen-shot to the given file path.""" c_path = path.encode("utf-8") if path is not None else ffi.NULL _check(lib.TCOD_context_save_screenshot(self._context_p, c_path)) - def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None: + def change_tileset(self, tileset: tcod.tileset.Tileset | None) -> None: """Change the active tileset used by this context. The new tileset will take effect on the next call to :any:`present`. @@ -363,7 +363,7 @@ def new_console( width, height = max(min_columns, size[0]), max(min_rows, size[1]) return tcod.console.Console(width, height, order=order) - def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> Tuple[int, int]: + def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> tuple[int, int]: """Return the recommended (columns, rows) of a console for this context. `min_columns`, `min_rows` are the lowest values which will be returned. @@ -406,11 +406,11 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: 0 if fullscreen else tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP, ) - ''' # noqa: E501 + ''' return lib.TCOD_context_get_sdl_window(self._context_p) @property - def sdl_window(self) -> Optional[tcod.sdl.video.Window]: + def sdl_window(self) -> tcod.sdl.video.Window | None: '''Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists. Example:: @@ -434,7 +434,7 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: return tcod.sdl.video.Window(p) if p else None @property - def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: + def sdl_renderer(self) -> tcod.sdl.render.Renderer | None: """Return a :any:`tcod.sdl.render.Renderer` referencing this contexts SDL renderer if it exists. .. versionadded:: 13.4 @@ -443,7 +443,7 @@ def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]: return tcod.sdl.render.Renderer(p) if p else None @property - def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]: + def sdl_atlas(self) -> tcod.render.SDLTilesetAtlas | None: """Return a :any:`tcod.render.SDLTilesetAtlas` referencing libtcod's SDL texture atlas if it exists. .. versionadded:: 13.5 @@ -455,7 +455,8 @@ def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]: def __reduce__(self) -> NoReturn: """Contexts can not be pickled, so this class will raise :class:`pickle.PicklingError`.""" - raise pickle.PicklingError("Python-tcod contexts can not be pickled.") + msg = "Python-tcod contexts can not be pickled." + raise pickle.PicklingError(msg) @ffi.def_extern() # type: ignore @@ -464,25 +465,25 @@ def _pycall_cli_output(catch_reference: Any, output: Any) -> None: Catches the CLI output. """ - catch: List[str] = ffi.from_handle(catch_reference) + catch: list[str] = ffi.from_handle(catch_reference) catch.append(ffi.string(output).decode("utf-8")) def new( *, - x: Optional[int] = None, - y: Optional[int] = None, - width: Optional[int] = None, - height: Optional[int] = None, - columns: Optional[int] = None, - rows: Optional[int] = None, - renderer: Optional[int] = None, - tileset: Optional[tcod.tileset.Tileset] = None, + x: int | None = None, + y: int | None = None, + width: int | None = None, + height: int | None = None, + columns: int | None = None, + rows: int | None = None, + renderer: int | None = None, + tileset: tcod.tileset.Tileset | None = None, vsync: bool = True, - sdl_window_flags: Optional[int] = None, - title: Optional[str] = None, - argv: Optional[Iterable[str]] = None, - console: Optional[tcod.Console] = None, + sdl_window_flags: int | None = None, + title: str | None = None, + argv: Iterable[str] | None = None, + console: tcod.Console | None = None, ) -> Context: """Create a new context with the desired pixel size. @@ -550,7 +551,7 @@ def new( argv_encoded = [ffi.new("char[]", arg.encode("utf-8")) for arg in argv] # Needs to be kept alive for argv_c. argv_c = ffi.new("char*[]", argv_encoded) - catch_msg: List[str] = [] + catch_msg: list[str] = [] catch_handle = ffi.new_handle(catch_msg) # Keep alive. title_p = _handle_title(title) # Keep alive. @@ -590,11 +591,11 @@ def new_window( width: int, height: int, *, - renderer: Optional[int] = None, - tileset: Optional[tcod.tileset.Tileset] = None, + renderer: int | None = None, + tileset: tcod.tileset.Tileset | None = None, vsync: bool = True, - sdl_window_flags: Optional[int] = None, - title: Optional[str] = None, + sdl_window_flags: int | None = None, + title: str | None = None, ) -> Context: """Create a new context with the desired pixel size. @@ -617,11 +618,11 @@ def new_terminal( columns: int, rows: int, *, - renderer: Optional[int] = None, - tileset: Optional[tcod.tileset.Tileset] = None, + renderer: int | None = None, + tileset: tcod.tileset.Tileset | None = None, vsync: bool = True, - sdl_window_flags: Optional[int] = None, - title: Optional[str] = None, + sdl_window_flags: int | None = None, + title: str | None = None, ) -> Context: """Create a new context with the desired console size. diff --git a/tcod/event.py b/tcod/event.py index 19e0ee1e..c415cb34 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -83,7 +83,7 @@ import enum import warnings -from typing import Any, Callable, Dict, Generic, Iterator, Mapping, NamedTuple, Optional, Tuple, Type, TypeVar, Union +from typing import Any, Callable, Generic, Iterator, Mapping, NamedTuple, TypeVar import numpy as np from numpy.typing import NDArray @@ -91,7 +91,7 @@ import tcod.event_constants import tcod.sdl.joystick -from tcod.event_constants import * # noqa: F4 +from tcod.event_constants import * # noqa: F403 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT from tcod.loader import ffi, lib from tcod.sdl.joystick import _HAT_DIRECTIONS @@ -100,7 +100,7 @@ class _ConstantsWithPrefix(Mapping[int, str]): - def __init__(self, constants: Mapping[int, str]): + def __init__(self, constants: Mapping[int, str]) -> None: self.constants = constants def __getitem__(self, key: int) -> str: @@ -133,7 +133,7 @@ def _describe_bitmask(bits: int, table: Mapping[int, str], default: str = "0") - return "|".join(result) -def _pixel_to_tile(x: float, y: float) -> Optional[Tuple[float, float]]: +def _pixel_to_tile(x: float, y: float) -> tuple[float, float] | None: """Convert pixel coordinates to tile coordinates.""" if not lib.TCOD_ctx.engine: return None @@ -155,7 +155,7 @@ class Point(NamedTuple): """A pixel or tile coordinate starting with zero as the top-most position.""" -def _verify_tile_coordinates(xy: Optional[Point]) -> Point: +def _verify_tile_coordinates(xy: Point | None) -> Point: """Check if an events tile coordinate is initialized and warn if not. Always returns a valid Point object for backwards compatibility. @@ -291,7 +291,7 @@ class Event: pointer. All sub-classes have this attribute. """ - def __init__(self, type: Optional[str] = None): + def __init__(self, type: str | None = None) -> None: if type is None: type = self.__class__.__name__.upper() self.type: Final = type @@ -323,11 +323,12 @@ def from_sdl_event(cls, sdl_event: Any) -> Quit: return self def __repr__(self) -> str: - return "tcod.event.%s()" % (self.__class__.__name__,) + return f"tcod.event.{self.__class__.__name__}()" class KeyboardEvent(Event): - """ + """Base keyboard event. + Attributes: type (str): Will be "KEYDOWN" or "KEYUP", depending on the event. scancode (Scancode): The keyboard scan-code, this is the physical location @@ -345,7 +346,7 @@ class KeyboardEvent(Event): `scancode`, `sym`, and `mod` now use their respective enums. """ - def __init__(self, scancode: int, sym: int, mod: int, repeat: bool = False): + def __init__(self, scancode: int, sym: int, mod: int, repeat: bool = False) -> None: super().__init__() self.scancode = Scancode(scancode) self.sym = KeySym(sym) @@ -360,7 +361,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.%s(scancode=%r, sym=%r, mod=%s%s)" % ( + return "tcod.event.{}(scancode={!r}, sym={!r}, mod={}{})".format( self.__class__.__name__, self.scancode, self.sym, @@ -381,7 +382,8 @@ class KeyUp(KeyboardEvent): class MouseState(Event): - """ + """Mouse state. + Attributes: type (str): Always "MOUSESTATE". position (Point): The position coordinates of the mouse. @@ -404,10 +406,10 @@ class MouseState(Event): def __init__( self, - position: Tuple[int, int] = (0, 0), - tile: Optional[Tuple[int, int]] = (0, 0), + position: tuple[int, int] = (0, 0), + tile: tuple[int, int] | None = (0, 0), state: int = 0, - ): + ) -> None: super().__init__() self.position = Point(*position) self.__tile = Point(*tile) if tile is not None else None @@ -441,7 +443,7 @@ def tile(self) -> Point: return _verify_tile_coordinates(self.__tile) @tile.setter - def tile(self, xy: Tuple[int, int]) -> None: + def tile(self, xy: tuple[int, int]) -> None: warnings.warn( "The mouse.tile attribute is deprecated. Use mouse.position of the event returned by context.convert_event instead.", DeprecationWarning, @@ -450,7 +452,7 @@ def tile(self, xy: Tuple[int, int]) -> None: self.__tile = Point(*xy) def __repr__(self) -> str: - return ("tcod.event.%s(position=%r, tile=%r, state=%s)") % ( + return ("tcod.event.{}(position={!r}, tile={!r}, state={})").format( self.__class__.__name__, tuple(self.position), tuple(self.tile), @@ -467,7 +469,8 @@ def __str__(self) -> str: class MouseMotion(MouseState): - """ + """Mouse motion event. + Attributes: type (str): Always "MOUSEMOTION". position (Point): The pixel coordinates of the mouse. @@ -491,12 +494,12 @@ class MouseMotion(MouseState): def __init__( self, - position: Tuple[int, int] = (0, 0), - motion: Tuple[int, int] = (0, 0), - tile: Optional[Tuple[int, int]] = (0, 0), - tile_motion: Optional[Tuple[int, int]] = (0, 0), + position: tuple[int, int] = (0, 0), + motion: tuple[int, int] = (0, 0), + tile: tuple[int, int] | None = (0, 0), + tile_motion: tuple[int, int] | None = (0, 0), state: int = 0, - ): + ) -> None: super().__init__(position, tile, state) self.motion = Point(*motion) self.__tile_motion = Point(*tile_motion) if tile_motion is not None else None @@ -530,7 +533,7 @@ def tile_motion(self) -> Point: return _verify_tile_coordinates(self.__tile_motion) @tile_motion.setter - def tile_motion(self, xy: Tuple[int, int]) -> None: + def tile_motion(self, xy: tuple[int, int]) -> None: warnings.warn( "The mouse.tile_motion attribute is deprecated." " Use mouse.motion of the event returned by context.convert_event instead.", @@ -559,7 +562,7 @@ def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: return self def __repr__(self) -> str: - return ("tcod.event.%s(position=%r, motion=%r, tile=%r, tile_motion=%r, state=%s)") % ( + return ("tcod.event.{}(position={!r}, motion={!r}, tile={!r}, tile_motion={!r}, state={})").format( self.__class__.__name__, tuple(self.position), tuple(self.motion), @@ -577,7 +580,8 @@ def __str__(self) -> str: class MouseButtonEvent(MouseState): - """ + """Mouse button event. + Attributes: type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", depending on the event. @@ -597,10 +601,10 @@ class MouseButtonEvent(MouseState): def __init__( self, - pixel: Tuple[int, int] = (0, 0), - tile: Optional[Tuple[int, int]] = (0, 0), + pixel: tuple[int, int] = (0, 0), + tile: tuple[int, int] | None = (0, 0), button: int = 0, - ): + ) -> None: super().__init__(pixel, tile, button) @property @@ -617,7 +621,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: pixel = button.x, button.y subtile = _pixel_to_tile(*pixel) if subtile is None: - tile: Optional[Tuple[int, int]] = None + tile: tuple[int, int] | None = None else: tile = int(subtile[0]), int(subtile[1]) self = cls(pixel, tile, button.button) @@ -625,7 +629,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.%s(position=%r, tile=%r, button=%s)" % ( + return "tcod.event.{}(position={!r}, tile={!r}, button={})".format( self.__class__.__name__, tuple(self.position), tuple(self.tile), @@ -650,7 +654,8 @@ class MouseButtonUp(MouseButtonEvent): class MouseWheel(Event): - """ + """Mouse wheel event. + Attributes: type (str): Always "MOUSEWHEEL". x (int): Horizontal scrolling. A positive value means scrolling right. @@ -661,7 +666,7 @@ class MouseWheel(Event): the Operating System. """ - def __init__(self, x: int, y: int, flipped: bool = False): + def __init__(self, x: int, y: int, flipped: bool = False) -> None: super().__init__() self.x = x self.y = y @@ -692,13 +697,14 @@ def __str__(self) -> str: class TextInput(Event): - """ + """SDL text input event. + Attributes: type (str): Always "TEXTINPUT". text (str): A Unicode string with the input. """ - def __init__(self, text: str): + def __init__(self, text: str) -> None: super().__init__() self.text = text @@ -709,10 +715,10 @@ def from_sdl_event(cls, sdl_event: Any) -> TextInput: return self def __repr__(self) -> str: - return "tcod.event.%s(text=%r)" % (self.__class__.__name__, self.text) + return f"tcod.event.{self.__class__.__name__}(text={self.text!r})" def __str__(self) -> str: - return "<%s, text=%r)" % (super().__str__().strip("<>"), self.text) + return "<{}, text={!r})".format(super().__str__().strip("<>"), self.text) class WindowEvent(Event): @@ -741,7 +747,7 @@ class WindowEvent(Event): """The current window event. This can be one of various options.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Union[WindowEvent, Undefined]: + def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: if sdl_event.window.event not in cls.__WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event] @@ -759,7 +765,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Union[WindowEvent, Undefined]: return self def __repr__(self) -> str: - return "tcod.event.%s(type=%r)" % (self.__class__.__name__, self.type) + return f"tcod.event.{self.__class__.__name__}(type={self.type!r})" __WINDOW_TYPES = { lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", @@ -782,7 +788,8 @@ def __repr__(self) -> str: class WindowMoved(WindowEvent): - """ + """Window moved event. + Attributes: x (int): Movement on the x-axis. y (int): Movement on the y-axis. @@ -797,7 +804,7 @@ def __init__(self, x: int, y: int) -> None: self.y = y def __repr__(self) -> str: - return "tcod.event.%s(type=%r, x=%r, y=%r)" % ( + return "tcod.event.{}(type={!r}, x={!r}, y={!r})".format( self.__class__.__name__, self.type, self.x, @@ -805,7 +812,7 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return "<%s, x=%r, y=%r)" % ( + return "<{}, x={!r}, y={!r})".format( super().__str__().strip("<>"), self.x, self.y, @@ -813,7 +820,8 @@ def __str__(self) -> str: class WindowResized(WindowEvent): - """ + """Window resized event. + Attributes: width (int): The current width of the window. height (int): The current height of the window. @@ -828,7 +836,7 @@ def __init__(self, type: str, width: int, height: int) -> None: self.height = height def __repr__(self) -> str: - return "tcod.event.%s(type=%r, width=%r, height=%r)" % ( + return "tcod.event.{}(type={!r}, width={!r}, height={!r})".format( self.__class__.__name__, self.type, self.width, @@ -836,7 +844,7 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return "<%s, width=%r, height=%r)" % ( + return "<{}, width={!r}, height={!r})".format( super().__str__().strip("<>"), self.width, self.height, @@ -849,7 +857,7 @@ class JoystickEvent(Event): .. versionadded:: 13.8 """ - def __init__(self, type: str, which: int): + def __init__(self, type: str, which: int) -> None: super().__init__(type) self.which = which """The ID of the joystick this event is for.""" @@ -880,7 +888,7 @@ class JoystickAxis(JoystickEvent): which: int """The ID of the joystick this event is for.""" - def __init__(self, type: str, which: int, axis: int, value: int): + def __init__(self, type: str, which: int, axis: int, value: int) -> None: super().__init__(type, which) self.axis = axis """The index of the changed axis.""" @@ -914,7 +922,7 @@ class JoystickBall(JoystickEvent): which: int """The ID of the joystick this event is for.""" - def __init__(self, type: str, which: int, ball: int, dx: int, dy: int): + def __init__(self, type: str, which: int, ball: int, dx: int, dy: int) -> None: super().__init__(type, which) self.ball = ball """The index of the moved ball.""" @@ -952,7 +960,7 @@ class JoystickHat(JoystickEvent): which: int """The ID of the joystick this event is for.""" - def __init__(self, type: str, which: int, x: Literal[-1, 0, 1], y: Literal[-1, 0, 1]): + def __init__(self, type: str, which: int, x: Literal[-1, 0, 1], y: Literal[-1, 0, 1]) -> None: super().__init__(type, which) self.x = x """The new X direction of the hat.""" @@ -991,7 +999,7 @@ class JoystickButton(JoystickEvent): which: int """The ID of the joystick this event is for.""" - def __init__(self, type: str, which: int, button: int): + def __init__(self, type: str, which: int, button: int) -> None: super().__init__(type, which) self.button = button """The index of the button this event is for.""" @@ -1049,7 +1057,7 @@ class ControllerEvent(Event): .. versionadded:: 13.8 """ - def __init__(self, type: str, which: int): + def __init__(self, type: str, which: int) -> None: super().__init__(type) self.which = which """The ID of the joystick this event is for.""" @@ -1077,7 +1085,7 @@ class ControllerAxis(ControllerEvent): type: Final[Literal["CONTROLLERAXISMOTION"]] # type: ignore[misc] - def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis, value: int): + def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis, value: int) -> None: super().__init__(type, which) self.axis = axis """Which axis is being moved. One of :any:`ControllerAxis`.""" @@ -1114,7 +1122,7 @@ class ControllerButton(ControllerEvent): type: Final[Literal["CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP"]] # type: ignore[misc] - def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerButton, pressed: bool): + def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerButton, pressed: bool) -> None: super().__init__(type, which) self.button = button """The button for this event. One of :any:`ControllerButton`.""" @@ -1164,9 +1172,7 @@ def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: class Undefined(Event): - """This class is a place holder for SDL events without their own tcod.event - class. - """ + """This class is a place holder for SDL events without their own tcod.event class.""" def __init__(self) -> None: super().__init__("") @@ -1183,7 +1189,7 @@ def __str__(self) -> str: return "" -_SDL_TO_CLASS_TABLE: Dict[int, Type[Event]] = { +_SDL_TO_CLASS_TABLE: dict[int, type[Event]] = { lib.SDL_QUIT: Quit, lib.SDL_KEYDOWN: KeyDown, lib.SDL_KEYUP: KeyUp, @@ -1232,7 +1238,7 @@ def get() -> Iterator[Any]: yield Undefined.from_sdl_event(sdl_event) -def wait(timeout: Optional[float] = None) -> Iterator[Any]: +def wait(timeout: float | None = None) -> Iterator[Any]: """Block until events exist, then return an event iterator. `timeout` is the maximum number of seconds to wait as a floating point @@ -1365,9 +1371,9 @@ def cmd_quit(self) -> None: tcod.console_flush() for event in tcod.event.wait(): state.dispatch(event) - ''' # noqa: E501 + ''' - def dispatch(self, event: Any) -> Optional[T]: + def dispatch(self, event: Any) -> T | None: """Send an event to an `ev_*` method. `*` will be the `event.type` attribute converted to lower-case. @@ -1387,7 +1393,7 @@ def dispatch(self, event: Any) -> Optional[T]: ) return None func_name = f"ev_{event.type.lower()}" - func: Optional[Callable[[Any], Optional[T]]] = getattr(self, func_name, None) + func: Callable[[Any], T | None] | None = getattr(self, func_name, None) if func is None: warnings.warn(f"{func_name} is missing from this EventDispatch object.", RuntimeWarning, stacklevel=2) return None @@ -1397,151 +1403,164 @@ def event_get(self) -> None: for event in get(): self.dispatch(event) - def event_wait(self, timeout: Optional[float]) -> None: + def event_wait(self, timeout: float | None) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: tcod.event.Quit) -> Optional[T]: + def ev_quit(self, event: tcod.event.Quit) -> T | None: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[T]: + def ev_keydown(self, event: tcod.event.KeyDown) -> T | None: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: tcod.event.KeyUp) -> Optional[T]: + def ev_keyup(self, event: tcod.event.KeyUp) -> T | None: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: tcod.event.MouseMotion) -> Optional[T]: + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> T | None: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> Optional[T]: + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> T | None: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> Optional[T]: + def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> T | None: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: tcod.event.MouseWheel) -> Optional[T]: + def ev_mousewheel(self, event: tcod.event.MouseWheel) -> T | None: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: tcod.event.TextInput) -> Optional[T]: + def ev_textinput(self, event: tcod.event.TextInput) -> T | None: """Called to handle Unicode input.""" - def ev_windowshown(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowshown(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window is shown.""" - def ev_windowhidden(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowhidden(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window is hidden.""" - def ev_windowexposed(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowexposed(self, event: tcod.event.WindowEvent) -> T | None: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`tcod.console_flush` is necessary. """ - def ev_windowmoved(self, event: tcod.event.WindowMoved) -> Optional[T]: + def ev_windowmoved(self, event: tcod.event.WindowMoved) -> T | None: """Called when the window is moved.""" - def ev_windowresized(self, event: tcod.event.WindowResized) -> Optional[T]: + def ev_windowresized(self, event: tcod.event.WindowResized) -> T | None: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: tcod.event.WindowResized) -> Optional[T]: + def ev_windowsizechanged(self, event: tcod.event.WindowResized) -> T | None: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowminimized(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowmaximized(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window is maximized.""" - def ev_windowrestored(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowrestored(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window is restored.""" - def ev_windowenter(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowenter(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowleave(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowfocusgained(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowfocuslost(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowclose(self, event: tcod.event.WindowEvent) -> T | None: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowtakefocus(self, event: tcod.event.WindowEvent) -> T | None: pass - def ev_windowhittest(self, event: tcod.event.WindowEvent) -> Optional[T]: + def ev_windowhittest(self, event: tcod.event.WindowEvent) -> T | None: pass - def ev_joyaxismotion(self, event: tcod.event.JoystickAxis) -> Optional[T]: - """ + def ev_joyaxismotion(self, event: tcod.event.JoystickAxis) -> T | None: + """Called when a joystick analog is moved. + .. versionadded:: 13.8 """ - def ev_joyballmotion(self, event: tcod.event.JoystickBall) -> Optional[T]: - """ + def ev_joyballmotion(self, event: tcod.event.JoystickBall) -> T | None: + """Called when a joystick ball is moved. + .. versionadded:: 13.8 """ - def ev_joyhatmotion(self, event: tcod.event.JoystickHat) -> Optional[T]: - """ + def ev_joyhatmotion(self, event: tcod.event.JoystickHat) -> T | None: + """Called when a joystick hat is moved. + .. versionadded:: 13.8 """ - def ev_joybuttondown(self, event: tcod.event.JoystickButton) -> Optional[T]: - """ + def ev_joybuttondown(self, event: tcod.event.JoystickButton) -> T | None: + """Called when a joystick button is pressed. + .. versionadded:: 13.8 """ - def ev_joybuttonup(self, event: tcod.event.JoystickButton) -> Optional[T]: - """ + def ev_joybuttonup(self, event: tcod.event.JoystickButton) -> T | None: + """Called when a joystick button is released. + .. versionadded:: 13.8 """ - def ev_joydeviceadded(self, event: tcod.event.JoystickDevice) -> Optional[T]: - """ + def ev_joydeviceadded(self, event: tcod.event.JoystickDevice) -> T | None: + """Called when a joystick is added. + .. versionadded:: 13.8 """ - def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> Optional[T]: - """ + def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> T | None: + """Called when a joystick is removed. + .. versionadded:: 13.8 """ - def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis) -> Optional[T]: - """ + def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis) -> T | None: + """Called when a controller analog is moved. + .. versionadded:: 13.8 """ - def ev_controllerbuttondown(self, event: tcod.event.ControllerButton) -> Optional[T]: - """ + def ev_controllerbuttondown(self, event: tcod.event.ControllerButton) -> T | None: + """Called when a controller button is pressed. + .. versionadded:: 13.8 """ - def ev_controllerbuttonup(self, event: tcod.event.ControllerButton) -> Optional[T]: - """ + def ev_controllerbuttonup(self, event: tcod.event.ControllerButton) -> T | None: + """Called when a controller button is released. + .. versionadded:: 13.8 """ - def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> Optional[T]: - """ + def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> T | None: + """Called when a standard controller is added. + .. versionadded:: 13.8 """ - def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice) -> Optional[T]: - """ + def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice) -> T | None: + """Called when a standard controller is removed. + .. versionadded:: 13.8 """ - def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice) -> Optional[T]: - """ + def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice) -> T | None: + """Called when a standard controller is remapped. + .. versionadded:: 13.8 """ - def ev_(self, event: Any) -> Optional[T]: + def ev_(self, event: Any) -> T | None: pass @@ -1566,7 +1585,7 @@ def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: _EventCallback = TypeVar("_EventCallback", bound=Callable[[Event], None]) -_event_watch_handles: Dict[Callable[[Event], None], Any] = {} # Callbacks and their FFI handles. +_event_watch_handles: dict[Callable[[Event], None], Any] = {} # Callbacks and their FFI handles. def add_watch(callback: _EventCallback) -> _EventCallback: @@ -2192,7 +2211,7 @@ def scancode(self) -> Scancode: return self @classmethod - def _missing_(cls, value: object) -> Optional[Scancode]: + def _missing_(cls, value: object) -> Scancode | None: if not isinstance(value, int): return None result = cls(0) @@ -2201,9 +2220,8 @@ def _missing_(cls, value: object) -> Optional[Scancode]: def __eq__(self, other: Any) -> bool: if isinstance(other, KeySym): - raise TypeError( - "Scancode and KeySym enums can not be compared directly." " Convert one or the other to the same type." - ) + msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." + raise TypeError(msg) return super().__eq__(other) def __hash__(self) -> int: @@ -2747,7 +2765,7 @@ def scancode(self) -> Scancode: return Scancode(lib.SDL_GetScancodeFromKey(self.value)) @classmethod - def _missing_(cls, value: object) -> Optional[KeySym]: + def _missing_(cls, value: object) -> KeySym | None: if not isinstance(value, int): return None result = cls(0) @@ -2756,9 +2774,8 @@ def _missing_(cls, value: object) -> Optional[KeySym]: def __eq__(self, other: Any) -> bool: if isinstance(other, Scancode): - raise TypeError( - "Scancode and KeySym enums can not be compared directly." " Convert one or the other to the same type." - ) + msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." + raise TypeError(msg) return super().__eq__(other) def __hash__(self) -> int: diff --git a/tcod/image.py b/tcod/image.py index 0f5760e9..85e315ea 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -12,7 +12,7 @@ from os import PathLike from pathlib import Path -from typing import Any, Dict, Tuple, Union +from typing import Any import numpy as np from numpy.typing import ArrayLike, NDArray @@ -22,8 +22,9 @@ from tcod.loader import ffi, lib -class Image(object): - """ +class Image: + """A libtcod image. + Args: width (int): Width of the new Image. height (int): Height of the new Image. @@ -33,7 +34,8 @@ class Image(object): height (int): Read only height of this Image. """ - def __init__(self, width: int, height: int): + def __init__(self, width: int, height: int) -> None: + """Initialize a blank image.""" self.width, self.height = width, height self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete) @@ -63,7 +65,7 @@ def from_array(cls, array: ArrayLike) -> Image: image_array[...] = array return image - def clear(self, color: Tuple[int, int, int]) -> None: + def clear(self, color: tuple[int, int, int]) -> None: """Fill this entire Image with color. Args: @@ -102,7 +104,7 @@ def scale(self, width: int, height: int) -> None: lib.TCOD_image_scale(self.image_c, width, height) self.width, self.height = width, height - def set_key_color(self, color: Tuple[int, int, int]) -> None: + def set_key_color(self, color: tuple[int, int, int]) -> None: """Set a color to be transparent during blitting functions. Args: @@ -138,7 +140,7 @@ def refresh_console(self, console: tcod.console.Console) -> None: """ lib.TCOD_image_refresh_console(self.image_c, _console(console)) - def _get_size(self) -> Tuple[int, int]: + def _get_size(self) -> tuple[int, int]: """Return the (width, height) for this Image. Returns: @@ -149,7 +151,7 @@ def _get_size(self) -> Tuple[int, int]: lib.TCOD_image_get_size(self.image_c, w, h) return w[0], h[0] - def get_pixel(self, x: int, y: int) -> Tuple[int, int, int]: + def get_pixel(self, x: int, y: int) -> tuple[int, int, int]: """Get the color of a pixel in this Image. Args: @@ -164,7 +166,7 @@ def get_pixel(self, x: int, y: int) -> Tuple[int, int, int]: color = lib.TCOD_image_get_pixel(self.image_c, x, y) return color.r, color.g, color.b - def get_mipmap_pixel(self, left: float, top: float, right: float, bottom: float) -> Tuple[int, int, int]: + def get_mipmap_pixel(self, left: float, top: float, right: float, bottom: float) -> tuple[int, int, int]: """Get the average color of a rectangle in this Image. Parameters should stay within the following limits: @@ -185,7 +187,7 @@ def get_mipmap_pixel(self, left: float, top: float, right: float, bottom: float) color = lib.TCOD_image_get_mipmap_pixel(self.image_c, left, top, right, bottom) return (color.r, color.g, color.b) - def put_pixel(self, x: int, y: int, color: Tuple[int, int, int]) -> None: + def put_pixel(self, x: int, y: int, color: tuple[int, int, int]) -> None: """Change a pixel on this Image. Args: @@ -295,7 +297,7 @@ def save_as(self, filename: str) -> None: lib.TCOD_image_save(self.image_c, filename.encode("utf-8")) @property - def __array_interface__(self) -> Dict[str, Any]: + def __array_interface__(self) -> dict[str, Any]: """Return an interface for this images pixel buffer. Use :any:`numpy.asarray` to get the read-write array of this Image. @@ -314,7 +316,8 @@ def __array_interface__(self) -> Dict[str, Any]: depth = 3 data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf)) else: - raise TypeError("Image has no initialized data.") + msg = "Image has no initialized data." + raise TypeError(msg) return { "shape": (self.height, self.width, depth), "typestr": "|u1", @@ -329,7 +332,7 @@ def _get_format_name(format: int) -> str: for attr in dir(lib): if not attr.startswith("SDL_PIXELFORMAT"): continue - if not getattr(lib, attr) == format: + if getattr(lib, attr) != format: continue return attr return str(format) @@ -340,7 +343,7 @@ def _get_format_name(format: int) -> str: " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", category=PendingDeprecationWarning, ) -def load(filename: Union[str, PathLike[str]]) -> NDArray[np.uint8]: +def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: """Load a PNG file as an RGBA array. `filename` is the name of the file to load. From 77f4a434f9a7eb2d88b030795d80d4d0156c7f0d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 26 May 2023 23:14:57 -0700 Subject: [PATCH 0774/1101] Use Ruff to clean up libtcodpy. --- .vscode/settings.json | 3 + tcod/libtcodpy.py | 333 ++++++++++++++++++++++-------------------- 2 files changed, 178 insertions(+), 158 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4274b1dd..2ab94f02 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -157,6 +157,7 @@ "guass", "heapify", "heightmap", + "heightmaps", "hflip", "hiddenimports", "HIGHDPI", @@ -197,6 +198,7 @@ "KBDILLUMDOWN", "KBDILLUMTOGGLE", "KBDILLUMUP", + "kernelsize", "keychar", "KEYDOWN", "keyname", @@ -403,6 +405,7 @@ "VRAM", "vsync", "WASD", + "waterlevel", "windowclose", "windowenter", "WINDOWEVENT", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a14403c3..b06572ec 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2,11 +2,11 @@ from __future__ import annotations import atexit -import os import sys import threading import warnings -from typing import Any, AnyStr, Callable, Hashable, Iterable, Iterator, List, Optional, Sequence, Tuple, Union +from pathlib import Path +from typing import Any, AnyStr, Callable, Hashable, Iterable, Iterator, Sequence import numpy as np from numpy.typing import NDArray @@ -36,7 +36,7 @@ pending_deprecate, ) from tcod.color import Color -from tcod.constants import * # noqa: F4 +from tcod.constants import * # noqa: F403 from tcod.constants import ( BKGND_ADDA, BKGND_ALPH, @@ -50,6 +50,8 @@ ) from tcod.loader import ffi, lib +# ruff: noqa: ANN401 PLR0913 # Functions are too deprecated to make changes. + Bsp = tcod.bsp.BSP NB_FOV_ALGORITHMS = 13 @@ -70,7 +72,7 @@ def BKGND_ADDALPHA(a: int) -> int: return BKGND_ADDA | (int(a * 255) << 8) -class ConsoleBuffer(object): +class ConsoleBuffer: """Simple console that allows direct (fast) access to cells. Simplifies use of the "fill" functions. .. deprecated:: 6.0 @@ -247,7 +249,8 @@ def blit( if not dest: dest = tcod.console.Console._from_cdata(ffi.NULL) if dest.width != self.width or dest.height != self.height: - raise ValueError("ConsoleBuffer.blit: " "Destination console has an incorrect size.") + msg = "ConsoleBuffer.blit: Destination console has an incorrect size." + raise ValueError(msg) if fill_back: bg = dest.bg.ravel() @@ -264,7 +267,7 @@ def blit( class Dice(_CDataWrapper): - """ + """A libtcod dice object. Args: nb_dices (int): Number of dice. @@ -283,7 +286,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: DeprecationWarning, stacklevel=2, ) - super(Dice, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.cdata == ffi.NULL: self._init(*args, **kwargs) @@ -318,7 +321,7 @@ def __str__(self) -> str: ) def __repr__(self) -> str: - return "%s(nb_dices=%r,nb_faces=%r,multiplier=%r,addsub=%r)" % ( + return "{}(nb_dices={!r},nb_faces={!r},multiplier={!r},addsub={!r})".format( self.__class__.__name__, self.nb_dices, self.nb_faces, @@ -332,7 +335,7 @@ def __repr__(self) -> str: class Key(_CDataWrapper): - r"""Key Event instance + r"""Key Event instance. Attributes: vk (int): TCOD_keycode_t key code @@ -377,7 +380,7 @@ def __init__( rctrl: bool = False, rmeta: bool = False, shift: bool = False, - ): + ) -> None: if isinstance(vk, ffi.CData): self.cdata = vk return @@ -401,7 +404,7 @@ def __getattr__(self, attr: str) -> Any: return ord(self.cdata.c) if attr == "text": return ffi.string(self.cdata.text).decode() - return super(Key, self).__getattr__(attr) + return super().__getattr__(attr) def __setattr__(self, attr: str, value: Any) -> None: if attr == "c": @@ -409,12 +412,12 @@ def __setattr__(self, attr: str, value: Any) -> None: elif attr == "text": self.cdata.text = value.encode() else: - super(Key, self).__setattr__(attr, value) + super().__setattr__(attr, value) def __repr__(self) -> str: """Return a representation of this Key object.""" params = [] - params.append("pressed=%r, vk=tcod.%s" % (self.pressed, _LOOKUP_VK[self.vk])) + params.append(f"pressed={self.pressed!r}, vk=tcod.{_LOOKUP_VK[self.vk]}") if self.c: params.append("c=ord(%r)" % chr(self.c)) if self.text: @@ -429,7 +432,7 @@ def __repr__(self) -> str: "rmeta", ]: if getattr(self, attr): - params.append("%s=%r" % (attr, getattr(self, attr))) + params.append(f"{attr}={getattr(self, attr)!r}") return "tcod.Key(%s)" % ", ".join(params) @property @@ -438,7 +441,7 @@ def key_p(self) -> Any: class Mouse(_CDataWrapper): - """Mouse event instance + """Mouse event instance. Attributes: x (int): Absolute mouse position at pixel x. @@ -473,7 +476,7 @@ def __init__( dcx: int = 0, dcy: int = 0, **kwargs: Any, - ): + ) -> None: if isinstance(x, ffi.CData): self.cdata = x return @@ -495,7 +498,7 @@ def __repr__(self) -> str: for attr in ["x", "y", "dx", "dy", "cx", "cy", "dcx", "dcy"]: if getattr(self, attr) == 0: continue - params.append("%s=%r" % (attr, getattr(self, attr))) + params.append(f"{attr}={getattr(self, attr)!r}") for attr in [ "lbutton", "rbutton", @@ -507,7 +510,7 @@ def __repr__(self) -> str: "wheel_down", ]: if getattr(self, attr): - params.append("%s=%r" % (attr, getattr(self, attr))) + params.append(f"{attr}={getattr(self, attr)!r}") return "tcod.Mouse(%s)" % ", ".join(params) @property @@ -515,7 +518,7 @@ def mouse_p(self) -> Any: return self.cdata -@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.") +@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.", FutureWarning) def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: """Create a new BSP instance with the given rectangle. @@ -534,35 +537,38 @@ def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: return Bsp(x, y, w, h) -@deprecate("Call node.split_once instead.") +@deprecate("Call node.split_once instead.", FutureWarning) def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: - """ + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.split_once` instead. """ node.split_once(horizontal, position) -@deprecate("Call node.split_recursive instead.") +@deprecate("Call node.split_recursive instead.", FutureWarning) def bsp_split_recursive( node: tcod.bsp.BSP, - randomizer: Optional[tcod.random.Random], + randomizer: tcod.random.Random | None, nb: int, minHSize: int, minVSize: int, maxHRatio: float, maxVRatio: float, ) -> None: - """ + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.split_recursive` instead. """ node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer) -@deprecate("Assign values via attribute instead.") +@deprecate("Assign values via attribute instead.", FutureWarning) def bsp_resize(node: tcod.bsp.BSP, x: int, y: int, w: int, h: int) -> None: - """ + """Deprecated function. + .. deprecated:: 2.0 Assign directly to :any:`BSP` attributes instead. """ @@ -573,8 +579,9 @@ def bsp_resize(node: tcod.bsp.BSP, x: int, y: int, w: int, h: int) -> None: @deprecate("Access children with 'node.children' instead.") -def bsp_left(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: - """ +def bsp_left(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.children` instead. """ @@ -582,44 +589,49 @@ def bsp_left(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: @deprecate("Access children with 'node.children' instead.") -def bsp_right(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: - """ +def bsp_right(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.children` instead. """ return None if not node.children else node.children[1] -@deprecate("Get the parent with 'node.parent' instead.") -def bsp_father(node: tcod.bsp.BSP) -> Optional[tcod.bsp.BSP]: - """ +@deprecate("Get the parent with 'node.parent' instead.", FutureWarning) +def bsp_father(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.parent` instead. """ return node.parent -@deprecate("Check for children with 'bool(node.children)' instead.") +@deprecate("Check for children with 'bool(node.children)' instead.", FutureWarning) def bsp_is_leaf(node: tcod.bsp.BSP) -> bool: - """ + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.children` instead. """ return not node.children -@deprecate("Use 'node.contains' instead.") +@deprecate("Use 'node.contains' instead.", FutureWarning) def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool: - """ + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.contains` instead. """ return node.contains(cx, cy) -@deprecate("Use 'node.find_node' instead.") -def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> Optional[tcod.bsp.BSP]: - """ +@deprecate("Use 'node.find_node' instead.", FutureWarning) +def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> tcod.bsp.BSP | None: + """Deprecated function. + .. deprecated:: 2.0 Use :any:`BSP.find_node` instead. """ @@ -720,7 +732,7 @@ def bsp_remove_sons(node: tcod.bsp.BSP) -> None: node.children = () -@deprecate("libtcod objects are deleted automatically.") +@deprecate("libtcod objects are deleted automatically.", FutureWarning) def bsp_delete(node: tcod.bsp.BSP) -> None: """Exists for backward compatibility. Does nothing. @@ -734,7 +746,7 @@ def bsp_delete(node: tcod.bsp.BSP) -> None: @pending_deprecate() -def color_lerp(c1: Tuple[int, int, int], c2: Tuple[int, int, int], a: float) -> Color: +def color_lerp(c1: tuple[int, int, int], c2: tuple[int, int, int], a: float) -> Color: """Return the linear interpolation between two colors. ``a`` is the interpolation value, with 0 returning ``c1``, @@ -771,7 +783,7 @@ def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: @pending_deprecate() -def color_get_hsv(c: Tuple[int, int, int]) -> Tuple[float, float, float]: +def color_get_hsv(c: tuple[int, int, int]) -> tuple[float, float, float]: """Return the (hue, saturation, value) of a color. Args: @@ -807,7 +819,7 @@ def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: @pending_deprecate() -def color_gen_map(colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int]) -> List[Color]: +def color_gen_map(colors: Iterable[tuple[int, int, int]], indexes: Iterable[int]) -> list[Color]: """Return a smoothly defined scale of colors. If ``indexes`` is [0, 3, 9] for example, the first color from ``colors`` @@ -842,11 +854,11 @@ def color_gen_map(colors: Iterable[Tuple[int, int, int]], indexes: Iterable[int] def console_init_root( w: int, h: int, - title: Optional[str] = None, + title: str | None = None, fullscreen: bool = False, - renderer: Optional[int] = None, + renderer: int | None = None, order: Literal["C", "F"] = "C", - vsync: Optional[bool] = None, + vsync: bool | None = None, ) -> tcod.console.Console: """Set up the primary display and return the root console. @@ -913,7 +925,7 @@ def console_init_root( """ if title is None: # Use the scripts filename as the title. - title = os.path.basename(sys.argv[0]) + title = Path(sys.argv[0]).name if renderer is None: renderer = tcod.constants.RENDERER_SDL2 elif renderer == tcod.constants.RENDERER_GLSL: @@ -972,9 +984,8 @@ def console_set_custom_font( Load fonts using :any:`tcod.tileset.load_tilesheet` instead. See :ref:`getting-started` for more info. """ - if not os.path.exists(_unicode(fontFile)): - raise RuntimeError("File not found:\n\t%s" % (str(os.path.realpath(fontFile)),)) - _check(lib.TCOD_console_set_custom_font(_bytes(fontFile), flags, nb_char_horiz, nb_char_vertic)) + path = Path(_unicode(fontFile)).resolve(strict=True) + _check(lib.TCOD_console_set_custom_font(path, flags, nb_char_horiz, nb_char_vertic)) @deprecate("Check `con.width` instead.") @@ -1157,17 +1168,13 @@ def console_credits_render(x: int, y: int, alpha: bool) -> bool: @deprecate("This function is not supported if contexts are being used.") def console_flush( - console: Optional[tcod.console.Console] = None, + console: tcod.console.Console | None = None, *, keep_aspect: bool = False, integer_scaling: bool = False, - snap_to_integer: Optional[bool] = None, - clear_color: Union[Tuple[int, int, int], Tuple[int, int, int, int]] = ( - 0, - 0, - 0, - ), - align: Tuple[float, float] = (0.5, 0.5), + snap_to_integer: bool | None = None, + clear_color: tuple[int, int, int] | tuple[int, int, int, int] = (0, 0, 0), + align: tuple[float, float] = (0.5, 0.5), ) -> None: """Update the display to represent the root consoles current state. @@ -1221,17 +1228,14 @@ def console_flush( "align_x": align[0], "align_y": align[1], } - if console is None: - console_p = ffi.NULL - else: - console_p = _console(console) + console_p = ffi.NULL if console is None else _console(console) with ffi.new("struct TCOD_ViewportOptions*", options) as viewport_opts: _check(lib.TCOD_console_flush_ex(console_p, viewport_opts)) # drawing on a console @deprecate("Set the `con.default_bg` attribute instead.") -def console_set_default_background(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: +def console_set_default_background(con: tcod.console.Console, col: tuple[int, int, int]) -> None: """Change the default background color for a console. Args: @@ -1246,7 +1250,7 @@ def console_set_default_background(con: tcod.console.Console, col: Tuple[int, in @deprecate("Set the `con.default_fg` attribute instead.") -def console_set_default_foreground(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: +def console_set_default_foreground(con: tcod.console.Console, col: tuple[int, int, int]) -> None: """Change the default foreground color for a console. Args: @@ -1282,7 +1286,7 @@ def console_put_char( con: tcod.console.Console, x: int, y: int, - c: Union[int, str], + c: int | str, flag: int = BKGND_DEFAULT, ) -> None: """Draw the character c at x,y using the default colors and a blend mode. @@ -1302,9 +1306,9 @@ def console_put_char_ex( con: tcod.console.Console, x: int, y: int, - c: Union[int, str], - fore: Tuple[int, int, int], - back: Tuple[int, int, int], + c: int | str, + fore: tuple[int, int, int], + back: tuple[int, int, int], ) -> None: """Draw the character c at x,y using the colors fore and back. @@ -1326,7 +1330,7 @@ def console_set_char_background( con: tcod.console.Console, x: int, y: int, - col: Tuple[int, int, int], + col: tuple[int, int, int], flag: int = BKGND_SET, ) -> None: """Change the background color of x,y to col using a blend mode. @@ -1343,7 +1347,7 @@ def console_set_char_background( @deprecate("Directly access a consoles foreground color with `console.fg`") -def console_set_char_foreground(con: tcod.console.Console, x: int, y: int, col: Tuple[int, int, int]) -> None: +def console_set_char_foreground(con: tcod.console.Console, x: int, y: int, col: tuple[int, int, int]) -> None: """Change the foreground color of x,y to col. Args: @@ -1361,7 +1365,7 @@ def console_set_char_foreground(con: tcod.console.Console, x: int, y: int, col: @deprecate("Directly access a consoles characters with `console.ch`") -def console_set_char(con: tcod.console.Console, x: int, y: int, c: Union[int, str]) -> None: +def console_set_char(con: tcod.console.Console, x: int, y: int, c: int | str) -> None: """Change the character at x,y to c, keeping the current colors. Args: @@ -1414,7 +1418,7 @@ def console_set_alignment(con: tcod.console.Console, alignment: int) -> None: Args: con (Console): Any Console instance. - alignment (int): + alignment (int): The libtcod alignment constant. .. deprecated:: 8.5 Set :any:`Console.default_alignment` instead. @@ -1613,7 +1617,7 @@ def console_print_frame( @pending_deprecate() -def console_set_color_control(con: int, fore: Tuple[int, int, int], back: Tuple[int, int, int]) -> None: +def console_set_color_control(con: int, fore: tuple[int, int, int], back: tuple[int, int, int]) -> None: """Configure :term:`color controls`. Args: @@ -1679,27 +1683,30 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore -@deprecate("This function is not supported if contexts are being used.") -def console_set_fade(fade: int, fadingColor: Tuple[int, int, int]) -> None: - """ +@deprecate("This function is not supported if contexts are being used.", FutureWarning) +def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: + """Deprecated function. + .. deprecated:: 11.13 This function is not supported by contexts. """ lib.TCOD_console_set_fade(fade, fadingColor) -@deprecate("This function is not supported if contexts are being used.") +@deprecate("This function is not supported if contexts are being used.", FutureWarning) def console_get_fade() -> int: - """ + """Deprecated function. + .. deprecated:: 11.13 This function is not supported by contexts. """ return int(lib.TCOD_console_get_fade()) -@deprecate("This function is not supported if contexts are being used.") +@deprecate("This function is not supported if contexts are being used.", FutureWarning) def console_get_fading_color() -> Color: - """ + """Deprecated function. + .. deprecated:: 11.13 This function is not supported by contexts. """ @@ -1734,7 +1741,8 @@ def console_wait_for_keypress(flush: bool) -> Key: @deprecate("Use the tcod.event.get function to check for events.") def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: - """ + """Return a recently pressed key. + .. deprecated:: 9.3 Use the :any:`tcod.event.get` function to check for events. @@ -1749,9 +1757,10 @@ def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: return key -@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.") +@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.", FutureWarning) def console_is_key_pressed(key: int) -> bool: - """ + """Return True if a key is held. + .. deprecated:: 12.7 Use :any:`tcod.event.get_keyboard_state` to check if a key is held. """ @@ -1787,9 +1796,8 @@ def console_from_file(filename: str) -> tcod.console.Console: Other formats are not actively supported. """ - if not os.path.exists(filename): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(filename.encode("utf-8")))) + path = Path(filename).resolve(strict=True) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(bytes(path)))) @deprecate("Call the `Console.blit` method instead.") @@ -1814,7 +1822,7 @@ def console_blit( @deprecate("Pass the key color to `Console.blit` instead of calling this function.") -def console_set_key_color(con: tcod.console.Console, col: Tuple[int, int, int]) -> None: +def console_set_key_color(con: tcod.console.Console, col: tuple[int, int, int]) -> None: """Set a consoles blit transparent color. .. deprecated:: 8.5 @@ -1879,7 +1887,8 @@ def console_fill_foreground( You should assign to :any:`tcod.console.Console.fg` instead. """ if len(r) != len(g) or len(r) != len(b): - raise TypeError("R, G and B must all have the same size.") + msg = "R, G and B must all have the same size." + raise TypeError(msg) if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray): # numpy arrays, use numpy's ctypes functions r_ = np.ascontiguousarray(r, dtype=np.intc) @@ -1916,7 +1925,8 @@ def console_fill_background( You should assign to :any:`tcod.console.Console.bg` instead. """ if len(r) != len(g) or len(r) != len(b): - raise TypeError("R, G and B must all have the same size.") + msg = "R, G and B must all have the same size." + raise TypeError(msg) if isinstance(r, np.ndarray) and isinstance(g, np.ndarray) and isinstance(b, np.ndarray): # numpy arrays, use numpy's ctypes functions r_ = np.ascontiguousarray(r, dtype=np.intc) @@ -2015,19 +2025,17 @@ def console_save_xp(con: tcod.console.Console, filename: str, compress_level: in @deprecate("Use tcod.console.load_xp to load this file.") def console_from_xp(filename: str) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file.""" - if not os.path.exists(filename): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(filename.encode("utf-8")))) + path = Path(filename).resolve(strict=True) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(bytes(path)))) @deprecate("Use tcod.console.load_xp to load this file.") def console_list_load_xp( filename: str, -) -> Optional[List[tcod.console.Console]]: +) -> list[tcod.console.Console] | None: """Return a list of consoles from a REXPaint `.xp` file.""" - if not os.path.exists(filename): - raise RuntimeError("File not found:\n\t%s" % (os.path.realpath(filename),)) - tcod_list = lib.TCOD_console_list_from_xp(filename.encode("utf-8")) + path = Path(filename).resolve(strict=True) + tcod_list = lib.TCOD_console_list_from_xp(bytes(path)) if tcod_list == ffi.NULL: return None try: @@ -2064,6 +2072,7 @@ def path_new_using_map(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.AStar: m (Map): A Map instance. dcost (float): The path-finding cost of diagonal movement. Can be set to 0 to disable diagonal movement. + Returns: AStar: A new AStar instance. """ @@ -2087,6 +2096,7 @@ def path_new_using_function( userData (Any): dcost (float): A multiplier for the cost of diagonal movement. Can be set to 0 to disable diagonal movement. + Returns: AStar: A new AStar instance. """ @@ -2103,6 +2113,7 @@ def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool oy (int): Starting y position. dx (int): Destination x position. dy (int): Destination y position. + Returns: bool: True if a valid path was found. Otherwise False. """ @@ -2110,13 +2121,14 @@ def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool @pending_deprecate() -def path_get_origin(p: tcod.path.AStar) -> Tuple[int, int]: +def path_get_origin(p: tcod.path.AStar) -> tuple[int, int]: """Get the current origin position. This point moves when :any:`path_walk` returns the next x,y step. Args: p (AStar): An AStar instance. + Returns: Tuple[int, int]: An (x, y) point. """ @@ -2127,11 +2139,12 @@ def path_get_origin(p: tcod.path.AStar) -> Tuple[int, int]: @pending_deprecate() -def path_get_destination(p: tcod.path.AStar) -> Tuple[int, int]: +def path_get_destination(p: tcod.path.AStar) -> tuple[int, int]: """Get the current destination position. Args: p (AStar): An AStar instance. + Returns: Tuple[int, int]: An (x, y) point. """ @@ -2147,6 +2160,7 @@ def path_size(p: tcod.path.AStar) -> int: Args: p (AStar): An AStar instance. + Returns: int: Length of the path. """ @@ -2166,7 +2180,7 @@ def path_reverse(p: tcod.path.AStar) -> None: @pending_deprecate() -def path_get(p: tcod.path.AStar, idx: int) -> Tuple[int, int]: +def path_get(p: tcod.path.AStar, idx: int) -> tuple[int, int]: """Get a point on a path. Args: @@ -2185,6 +2199,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: Args: p (AStar): An AStar instance. + Returns: bool: True if a path is empty. Otherwise False. """ @@ -2192,7 +2207,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: @pending_deprecate() -def path_walk(p: tcod.path.AStar, recompute: bool) -> Union[Tuple[int, int], Tuple[None, None]]: +def path_walk(p: tcod.path.AStar, recompute: bool) -> tuple[int, int] | tuple[None, None]: """Return the next (x, y) point in a path, or (None, None) if it's empty. When ``recompute`` is True and a previously valid path reaches a point @@ -2201,6 +2216,7 @@ def path_walk(p: tcod.path.AStar, recompute: bool) -> Union[Tuple[int, int], Tup Args: p (AStar): An AStar instance. recompute (bool): Recompute the path automatically. + Returns: Union[Tuple[int, int], Tuple[None, None]]: A single (x, y) point, or (None, None) @@ -2262,7 +2278,7 @@ def dijkstra_reverse(p: tcod.path.Dijkstra) -> None: @pending_deprecate() -def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> Tuple[int, int]: +def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> tuple[int, int]: x = ffi.new("int *") y = ffi.new("int *") lib.TCOD_dijkstra_get(p._path_c, idx, x, y) @@ -2277,7 +2293,7 @@ def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool: @pending_deprecate() def dijkstra_path_walk( p: tcod.path.Dijkstra, -) -> Union[Tuple[int, int], Tuple[None, None]]: +) -> tuple[int, int] | tuple[None, None]: x = ffi.new("int *") y = ffi.new("int *") if lib.TCOD_dijkstra_path_walk(p._path_c, x, y): @@ -2301,7 +2317,8 @@ def _heightmap_cdata(array: NDArray[np.float32]) -> ffi.CData: if array.flags["F_CONTIGUOUS"]: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: - raise ValueError("array must be a contiguous segment.") + msg = "array must be a contiguous segment." + raise ValueError(msg) if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) height, width = array.shape @@ -2333,7 +2350,8 @@ def heightmap_new(w: int, h: int, order: str = "C") -> NDArray[np.float32]: elif order == "F": return np.zeros((w, h), np.float32, order="F") else: - raise ValueError("Invalid order parameter, should be 'C' or 'F'.") + msg = "Invalid order parameter, should be 'C' or 'F'." + raise ValueError(msg) @deprecate("Assign to heightmaps as a NumPy array instead.") @@ -2358,7 +2376,8 @@ def heightmap_set_value(hm: NDArray[np.float32], x: int, y: int, value: float) - ) hm[x, y] = value else: - raise ValueError("This array is not contiguous.") + msg = "This array is not contiguous." + raise ValueError(msg) @deprecate("Add a scalar to an array using `hm[:] += value`") @@ -2404,7 +2423,7 @@ def heightmap_clear(hm: NDArray[np.float32]) -> None: @deprecate("Clamp array values using `hm.clip(mi, ma)`") def heightmap_clamp(hm: NDArray[np.float32], mi: float, ma: float) -> None: - """Clamp all values on this heightmap between ``mi`` and ``ma`` + """Clamp all values on this heightmap between ``mi`` and ``ma``. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. @@ -2449,8 +2468,7 @@ def heightmap_lerp_hm( hm3: NDArray[np.float32], coef: float, ) -> None: - """Perform linear interpolation between two heightmaps storing the result - in ``hm3``. + """Perform linear interpolation between two heightmaps storing the result in ``hm3``. This is the same as doing ``hm3[:] = hm1[:] + (hm2[:] - hm1[:]) * coef`` @@ -2525,13 +2543,11 @@ def heightmap_add_hill(hm: NDArray[np.float32], x: float, y: float, radius: floa @pending_deprecate() def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None: - """ + """Dig a hill in a heightmap. - This function takes the highest value (if height > 0) or the lowest - (if height < 0) between the map and the hill. + This function takes the highest value (if height > 0) or the lowest (if height < 0) between the map and the hill. - It's main goal is to carve things in maps (like rivers) by digging hills - along a curve. + It's main goal is to carve things in maps (like rivers) by digging hills along a curve. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. @@ -2549,7 +2565,7 @@ def heightmap_rain_erosion( nbDrops: int, erosionCoef: float, sedimentationCoef: float, - rnd: Optional[tcod.random.Random] = None, + rnd: tcod.random.Random | None = None, ) -> None: """Simulate the effect of rain drops on the terrain, resulting in erosion. @@ -2582,8 +2598,7 @@ def heightmap_kernel_transform( minLevel: float, maxLevel: float, ) -> None: - """Apply a generic transformation on the map, so that each resulting cell - value is the weighted sum of several neighbor cells. + """Apply a generic transformation on the map, so that each resulting cell value is the weighted sum of several neighbor cells. This can be used to smooth/sharpen the map. @@ -2635,7 +2650,7 @@ def heightmap_add_voronoi( nbPoints: Any, nbCoef: int, coef: Sequence[float], - rnd: Optional[tcod.random.Random] = None, + rnd: tcod.random.Random | None = None, ) -> None: """Add values from a Voronoi diagram to the heightmap. @@ -2721,7 +2736,7 @@ def heightmap_scale_fbm( delta: float, scale: float, ) -> None: - """Multiply the heighmap values with FBM noise. + """Multiply the heightmap values with FBM noise. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. @@ -2755,8 +2770,8 @@ def heightmap_scale_fbm( @pending_deprecate() def heightmap_dig_bezier( hm: NDArray[np.float32], - px: Tuple[int, int, int, int], - py: Tuple[int, int, int, int], + px: tuple[int, int, int, int], + py: tuple[int, int, int, int], startRadius: float, startDepth: float, endRadius: float, @@ -2808,7 +2823,8 @@ def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: ) return hm[x, y] # type: ignore else: - raise ValueError("This array is not contiguous.") + msg = "This array is not contiguous." + raise ValueError(msg) @pending_deprecate() @@ -2842,7 +2858,7 @@ def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> Tuple[float, float, float]: +def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> tuple[float, float, float]: """Return the map normal at given coordinates. Args: @@ -2893,7 +2909,7 @@ def heightmap_has_land_on_border(hm: NDArray[np.float32], waterlevel: float) -> @deprecate("Use `hm.min()` and `hm.max()` instead.") -def heightmap_get_minmax(hm: NDArray[np.float32]) -> Tuple[float, float]: +def heightmap_get_minmax(hm: NDArray[np.float32]) -> tuple[float, float]: """Return the min and max values of this heightmap. Args: @@ -2928,7 +2944,7 @@ def image_new(width: int, height: int) -> tcod.image.Image: @pending_deprecate() -def image_clear(image: tcod.image.Image, col: Tuple[int, int, int]) -> None: +def image_clear(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.clear(col) @@ -2958,7 +2974,7 @@ def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: @pending_deprecate() -def image_set_key_color(image: tcod.image.Image, col: Tuple[int, int, int]) -> None: +def image_set_key_color(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.set_key_color(col) @@ -3008,22 +3024,22 @@ def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console @pending_deprecate() -def image_get_size(image: tcod.image.Image) -> Tuple[int, int]: +def image_get_size(image: tcod.image.Image) -> tuple[int, int]: return image.width, image.height @pending_deprecate() -def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> Tuple[int, int, int]: +def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> tuple[int, int, int]: return image.get_pixel(x, y) @pending_deprecate() -def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> Tuple[int, int, int]: +def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) @pending_deprecate() -def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: Tuple[int, int, int]) -> None: +def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: tuple[int, int, int]) -> None: image.put_pixel(x, y, col) @@ -3102,7 +3118,7 @@ def line_init(xo: int, yo: int, xd: int, yd: int) -> None: @deprecate("Use tcod.line_iter instead.") -def line_step() -> Union[Tuple[int, int], Tuple[None, None]]: +def line_step() -> tuple[int, int] | tuple[None, None]: """After calling line_init returns (x, y) points of the line. Once all points are exhausted this function will return (None, None) @@ -3156,8 +3172,8 @@ def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], b @deprecate("This function has been replaced by tcod.los.bresenham.") -def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: - """returns an Iterable +def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: + """Returns an Iterable over a Bresenham line. This Iterable does not include the origin point. @@ -3183,7 +3199,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[Tuple[int, int]]: @deprecate("This function has been replaced by tcod.los.bresenham.") -def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> Tuple[NDArray[np.intc], NDArray[np.intc]]: +def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. @@ -3281,8 +3297,7 @@ def map_compute_fov( @deprecate("Use map.fov to check for this property.") def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: - """Return True if the cell at x,y is lit by the last field-of-view - algorithm. + """Return True if the cell at x,y is lit by the last field-of-view algorithm. .. note:: This function is slow. @@ -3294,7 +3309,8 @@ def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: @deprecate("Use map.transparent to check for this property.") def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: - """ + """Return True is a map cell is transparent. + .. note:: This function is slow. .. deprecated:: 4.5 @@ -3305,7 +3321,8 @@ def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: @deprecate("Use map.walkable to check for this property.") def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: - """ + """Return True is a map cell is walkable. + .. note:: This function is slow. .. deprecated:: 4.5 @@ -3365,7 +3382,7 @@ def mouse_get_status() -> Mouse: @pending_deprecate() -def namegen_parse(filename: str, random: Optional[tcod.random.Random] = None) -> None: +def namegen_parse(filename: str, random: tcod.random.Random | None = None) -> None: lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) @@ -3380,7 +3397,7 @@ def namegen_generate_custom(name: str, rule: str) -> str: @pending_deprecate() -def namegen_get_sets() -> List[str]: +def namegen_get_sets() -> list[str]: sets = lib.TCOD_namegen_get_sets() try: lst = [] @@ -3401,7 +3418,7 @@ def noise_new( dim: int, h: float = NOISE_DEFAULT_HURST, l: float = NOISE_DEFAULT_LACUNARITY, # noqa: E741 - random: Optional[tcod.random.Random] = None, + random: tcod.random.Random | None = None, ) -> tcod.noise.Noise: """Return a new Noise instance. @@ -3499,7 +3516,7 @@ def noise_delete(n: tcod.noise.Noise) -> None: def _unpack_union(type_: int, union: Any) -> Any: - """Unpack items from parser new_property (value_converter)""" + """Unpack items from parser new_property (value_converter).""" if type_ == lib.TCOD_TYPE_BOOL: return bool(union.b) elif type_ == lib.TCOD_TYPE_CHAR: @@ -3692,7 +3709,7 @@ def random_new_from_seed(seed: Hashable, algo: int = RNG_CMWC) -> tcod.random.Ra @pending_deprecate() -def random_set_distribution(rnd: Optional[tcod.random.Random], dist: int) -> None: +def random_set_distribution(rnd: tcod.random.Random | None, dist: int) -> None: """Change the distribution mode of a random number generator. Args: @@ -3703,7 +3720,7 @@ def random_set_distribution(rnd: Optional[tcod.random.Random], dist: int) -> Non @pending_deprecate() -def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: +def random_get_int(rnd: tcod.random.Random | None, mi: int, ma: int) -> int: """Return a random integer in the range: ``mi`` <= n <= ``ma``. The result is affected by calls to :any:`random_set_distribution`. @@ -3720,7 +3737,7 @@ def random_get_int(rnd: Optional[tcod.random.Random], mi: int, ma: int) -> int: @pending_deprecate() -def random_get_float(rnd: Optional[tcod.random.Random], mi: float, ma: float) -> float: +def random_get_float(rnd: tcod.random.Random | None, mi: float, ma: float) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. The result is affected by calls to :any:`random_set_distribution`. @@ -3738,7 +3755,7 @@ def random_get_float(rnd: Optional[tcod.random.Random], mi: float, ma: float) -> @deprecate("Call tcod.random_get_float instead.") -def random_get_double(rnd: Optional[tcod.random.Random], mi: float, ma: float) -> float: +def random_get_double(rnd: tcod.random.Random | None, mi: float, ma: float) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 @@ -3749,7 +3766,7 @@ def random_get_double(rnd: Optional[tcod.random.Random], mi: float, ma: float) - @pending_deprecate() -def random_get_int_mean(rnd: Optional[tcod.random.Random], mi: int, ma: int, mean: int) -> int: +def random_get_int_mean(rnd: tcod.random.Random | None, mi: int, ma: int, mean: int) -> int: """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. The result is affected by calls to :any:`random_set_distribution`. @@ -3767,7 +3784,7 @@ def random_get_int_mean(rnd: Optional[tcod.random.Random], mi: int, ma: int, mea @pending_deprecate() -def random_get_float_mean(rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float) -> float: +def random_get_float_mean(rnd: tcod.random.Random | None, mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. The result is affected by calls to :any:`random_set_distribution`. @@ -3786,7 +3803,7 @@ def random_get_float_mean(rnd: Optional[tcod.random.Random], mi: float, ma: floa @deprecate("Call tcod.random_get_float_mean instead.") -def random_get_double_mean(rnd: Optional[tcod.random.Random], mi: float, ma: float, mean: float) -> float: +def random_get_double_mean(rnd: tcod.random.Random | None, mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. .. deprecated:: 2.0 @@ -3797,7 +3814,7 @@ def random_get_double_mean(rnd: Optional[tcod.random.Random], mi: float, ma: flo @deprecate("Use the standard library 'copy' module instead.") -def random_save(rnd: Optional[tcod.random.Random]) -> tcod.random.Random: +def random_save(rnd: tcod.random.Random | None) -> tcod.random.Random: """Return a copy of a random number generator. .. deprecated:: 8.4 @@ -3813,7 +3830,7 @@ def random_save(rnd: Optional[tcod.random.Random]) -> tcod.random.Random: @deprecate("This function is deprecated.") -def random_restore(rnd: Optional[tcod.random.Random], backup: tcod.random.Random) -> None: +def random_restore(rnd: tcod.random.Random | None, backup: tcod.random.Random) -> None: """Restore a random number generator from a backed up copy. Args: @@ -3990,7 +4007,7 @@ def sys_get_renderer() -> int: # easy screenshots @deprecate("This function is not supported if contexts are being used.") -def sys_save_screenshot(name: Optional[str] = None) -> None: +def sys_save_screenshot(name: str | None = None) -> None: """Save a screenshot to a file. By default this will automatically save screenshots in the working @@ -4006,7 +4023,7 @@ def sys_save_screenshot(name: Optional[str] = None) -> None: This function is not supported by contexts. Use :any:`Context.save_screenshot` instead. """ - lib.TCOD_sys_save_screenshot(_bytes(name) if name is not None else ffi.NULL) + lib.TCOD_sys_save_screenshot(bytes(Path(name)) if name is not None else ffi.NULL) # custom fullscreen resolution @@ -4032,7 +4049,7 @@ def sys_force_fullscreen_resolution(width: int, height: int) -> None: @deprecate("This function is deprecated, which monitor is detected is ambiguous.") -def sys_get_current_resolution() -> Tuple[int, int]: +def sys_get_current_resolution() -> tuple[int, int]: """Return a monitors pixel resolution as (width, height). .. deprecated:: 11.13 @@ -4045,8 +4062,8 @@ def sys_get_current_resolution() -> Tuple[int, int]: @deprecate("This function is not supported if contexts are being used.") -def sys_get_char_size() -> Tuple[int, int]: - """Return the current fonts character size as (width, height) +def sys_get_char_size() -> tuple[int, int]: + """Return the current fonts character size as (width, height). Returns: Tuple[int,int]: The current font glyph size in (width, height) @@ -4121,7 +4138,7 @@ def _pycall_sdl_hook(sdl_surface: Any) -> None: @deprecate("Use tcod.event.get to check for events.") -def sys_check_for_event(mask: int, k: Optional[Key], m: Optional[Mouse]) -> int: +def sys_check_for_event(mask: int, k: Key | None, m: Mouse | None) -> int: """Check for and return an event. Args: @@ -4138,7 +4155,7 @@ def sys_check_for_event(mask: int, k: Optional[Key], m: Optional[Mouse]) -> int: @deprecate("Use tcod.event.wait to wait for events.") -def sys_wait_for_event(mask: int, k: Optional[Key], m: Optional[Mouse], flush: bool) -> int: +def sys_wait_for_event(mask: int, k: Key | None, m: Mouse | None, flush: bool) -> int: """Wait for an event then return. If flush is True then the buffer will be cleared before waiting. Otherwise From e0233a7b0e4d2733809ae7fe75c511166e12d494 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 26 May 2023 23:54:45 -0700 Subject: [PATCH 0775/1101] Clean up the remaining auto-fixes for the tcod package and scripts. --- scripts/generate_charmap_table.py | 3 + scripts/get_release_description.py | 2 + tcod/_internal.py | 2 +- tcod/console.py | 5 +- tcod/loader.py | 7 +- tcod/los.py | 4 +- tcod/map.py | 33 +++--- tcod/noise.py | 66 ++++++------ tcod/path.py | 163 +++++++++++++++-------------- tcod/random.py | 8 +- tcod/render.py | 6 +- tcod/sdl/__init__.py | 20 ++-- tcod/sdl/audio.py | 46 ++++---- tcod/sdl/joystick.py | 38 +++---- tcod/sdl/mouse.py | 29 ++--- tcod/sdl/render.py | 89 ++++++++-------- tcod/sdl/sys.py | 6 +- tcod/sdl/video.py | 51 +++++---- tcod/tileset.py | 67 ++++++------ 19 files changed, 330 insertions(+), 315 deletions(-) diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 1f9f4639..2e2931b5 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -13,6 +13,8 @@ import tcod.tileset +# ruff: noqa: INP001 + def get_charmaps() -> Iterator[str]: """Return an iterator of the current character maps from tcod.tilest.""" @@ -53,6 +55,7 @@ def generate_table(charmap: Iterable[int]) -> str: def main() -> None: + """Main entry point.""" parser = argparse.ArgumentParser( description="Generate an RST table for a tcod character map.", ) diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 7d47d9f9..0a2dffb3 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -5,6 +5,8 @@ import re from pathlib import Path +# ruff: noqa: INP001 + TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n" RE_BODY = re.compile(rf".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) diff --git a/tcod/_internal.py b/tcod/_internal.py index 694c9aaf..a9d2ba35 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -168,7 +168,7 @@ def __exit__( class _CDataWrapper: """A generally deprecated CData wrapper class used by libtcodpy.""" - def __init__(self, *args: Any, **kwargs: Any): # noqa: ANN401 + def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 self.cdata = self._get_cdata_from_args(*args, **kwargs) if self.cdata is None: self.cdata = ffi.NULL diff --git a/tcod/console.py b/tcod/console.py index 77a75c0e..19114077 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1,5 +1,4 @@ -""" -Libtcod consoles are a strictly tile-based representation of text and color. +"""Libtcod consoles are a strictly tile-based representation of text and color. To render a console you need a tileset and a window to render to. See :ref:`getting-started` for info on how to set those up. """ @@ -117,7 +116,7 @@ def __init__( height: int, order: Literal["C", "F"] = "C", buffer: NDArray[Any] | None = None, - ): + ) -> None: self._key_color: tuple[int, int, int] | None = None self._order = tcod._internal.verify_order(order) if buffer is not None: diff --git a/tcod/loader.py b/tcod/loader.py index 1a296b12..0027fb8c 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -5,7 +5,7 @@ import platform import sys from pathlib import Path -from typing import Any # noqa: F401 +from typing import Any import cffi @@ -54,7 +54,7 @@ def get_sdl_version() -> str: os.environ["PATH"] = f"""{Path(__file__).parent / get_architecture()}{os.pathsep}{os.environ["PATH"]}""" -class _Mock(object): +class _Mock: """Mock object needed for ReadTheDocs.""" @staticmethod @@ -64,7 +64,6 @@ def def_extern() -> Any: def __getattr__(self, attr: str) -> None: """Return None on any attribute.""" - return None def __bool__(self) -> bool: """Allow checking for this mock object at import time.""" @@ -80,7 +79,7 @@ def __bool__(self) -> bool: lib = ffi = _Mock() else: verify_dependencies() - from tcod._libtcod import ffi, lib # type: ignore # noqa: F401 + from tcod._libtcod import ffi, lib # type: ignore __sdl_version__ = get_sdl_version() diff --git a/tcod/los.py b/tcod/los.py index 9cbd87da..d815504f 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -1,7 +1,7 @@ """This modules holds functions for NumPy-based line of sight algorithms.""" from __future__ import annotations -from typing import Any, Tuple +from typing import Any import numpy as np from numpy.typing import NDArray @@ -9,7 +9,7 @@ from tcod.loader import ffi, lib -def bresenham(start: Tuple[int, int], end: Tuple[int, int]) -> NDArray[np.intc]: +def bresenham(start: tuple[int, int], end: tuple[int, int]) -> NDArray[np.intc]: """Return a thin Bresenham line as a NumPy array of shape (length, 2). `start` and `end` are the endpoints of the line. diff --git a/tcod/map.py b/tcod/map.py index 8ccc3b1f..e33a0984 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -1,11 +1,8 @@ -"""libtcod map attributes and field-of-view functions. - - -""" +"""libtcod map attributes and field-of-view functions.""" from __future__ import annotations import warnings -from typing import Any, Tuple +from typing import Any import numpy as np from numpy.typing import ArrayLike, NDArray @@ -16,7 +13,7 @@ from tcod.loader import ffi, lib -class Map(object): +class Map: """A map containing libtcod attributes. .. versionchanged:: 4.1 @@ -77,7 +74,7 @@ def __init__( width: int, height: int, order: Literal["C", "F"] = "C", - ): + ) -> None: warnings.warn( "This class may perform poorly and is no longer needed.", DeprecationWarning, @@ -140,15 +137,15 @@ def compute_fov( """ if not (0 <= x < self.width and 0 <= y < self.height): warnings.warn( - "Index (%r, %r) is outside of this maps shape (%r, %r)." - "\nThis will raise an error in future versions." % (x, y, self.width, self.height), + "Index ({}, {}) is outside of this maps shape ({}, {})." + "\nThis will raise an error in future versions.".format(x, y, self.width, self.height), RuntimeWarning, stacklevel=2, ) lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm) - def __setstate__(self, state: Any) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: if "_Map__buffer" not in state: # deprecated # remove this check on major version update self.__buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_) @@ -157,12 +154,10 @@ def __setstate__(self, state: Any) -> None: self.__buffer[:, :, 2] = state["buffer"] & 0x04 del state["buffer"] state["_order"] = "F" - if "_order" not in state: # remove this check on major version update - raise RuntimeError("This Map was saved with a bad version of tdl.") self.__dict__.update(state) self.map_c = self.__as_cdata() - def __getstate__(self) -> Any: + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() del state["map_c"] return state @@ -170,7 +165,7 @@ def __getstate__(self) -> Any: def compute_fov( transparency: ArrayLike, - pov: Tuple[int, int], + pov: tuple[int, int], radius: int = 0, light_walls: bool = True, algorithm: int = tcod.constants.FOV_RESTRICTIVE, @@ -240,14 +235,12 @@ def compute_fov( if len(transparency.shape) != 2: raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape) if isinstance(pov, int): - raise TypeError( - "The tcod.map.compute_fov function has changed. The `x` and `y`" - " parameters should now be given as a single tuple." - ) + msg = "The tcod.map.compute_fov function has changed. The `x` and `y` parameters should now be given as a single tuple." + raise TypeError(msg) if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]): warnings.warn( - "Given pov index %r is outside the array of shape %r." - "\nThis will raise an error in future versions." % (pov, transparency.shape), + "Given pov index {!r} is outside the array of shape {!r}." + "\nThis will raise an error in future versions.".format(pov, transparency.shape), RuntimeWarning, stacklevel=2, ) diff --git a/tcod/noise.py b/tcod/noise.py index 94178b56..843f60b9 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -30,12 +30,12 @@ [ 92, 35, 33, 71, 90], [ 76, 54, 85, 144, 164], [ 63, 94, 159, 209, 203]], dtype=uint8) -""" # noqa: E501 +""" from __future__ import annotations import enum import warnings -from typing import Any, List, Optional, Sequence, Tuple, Union +from typing import Any, Sequence import numpy as np from numpy.typing import ArrayLike, NDArray @@ -43,7 +43,6 @@ import tcod.constants import tcod.random -from tcod._internal import deprecate from tcod.loader import ffi, lib @@ -92,14 +91,15 @@ def __getattr__(name: str) -> Implementation: if name in Implementation.__members__: warnings.warn( f"'tcod.noise.{name}' is deprecated," f" use 'tcod.noise.Implementation.{name}' instead.", - DeprecationWarning, + FutureWarning, stacklevel=2, ) return Implementation[name] - raise AttributeError(f"module {__name__} has no attribute {name}") + msg = f"module {__name__} has no attribute {name}" + raise AttributeError(msg) -class Noise(object): +class Noise: """A configurable noise sampler. The ``hurst`` exponent describes the raggedness of the resultant noise, @@ -130,10 +130,11 @@ def __init__( hurst: float = 0.5, lacunarity: float = 2.0, octaves: float = 4, - seed: Optional[Union[int, tcod.random.Random]] = None, - ): + seed: int | tcod.random.Random | None = None, + ) -> None: if not 0 < dimensions <= 4: - raise ValueError("dimensions must be in range 0 < n <= 4, got %r" % (dimensions,)) + msg = f"dimensions must be in range 0 < n <= 4, got {dimensions}" + raise ValueError(msg) self._seed = seed self._random = self.__rng_from_seed(seed) _random_c = self._random.random_c @@ -149,7 +150,7 @@ def __init__( self.implementation = implementation # sanity check @staticmethod - def __rng_from_seed(seed: Union[None, int, tcod.random.Random]) -> tcod.random.Random: + def __rng_from_seed(seed: None | int | tcod.random.Random) -> tcod.random.Random: if seed is None or isinstance(seed, int): return tcod.random.Random(seed=seed, algorithm=tcod.random.MERSENNE_TWISTER) return seed @@ -174,11 +175,6 @@ def __repr__(self) -> str: def dimensions(self) -> int: return int(self._tdl_noise_c.dimensions) - @property - @deprecate("This is a misspelling of 'dimensions'.", FutureWarning) - def dimentions(self) -> int: - return self.dimensions - @property def algorithm(self) -> int: noise_type = self.noise_c.noise_type @@ -195,7 +191,8 @@ def implementation(self) -> int: @implementation.setter def implementation(self, value: int) -> None: if not 0 <= value < 3: - raise ValueError("%r is not a valid implementation. " % (value,)) + msg = f"{value!r} is not a valid implementation. " + raise ValueError(msg) self._tdl_noise_c.implementation = value @property @@ -242,7 +239,8 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): if index.dtype.type == np.object_: - raise TypeError("Index arrays can not be of dtype np.object_.") + msg = "Index arrays can not be of dtype np.object_." + raise TypeError(msg) indexes[i] = np.ascontiguousarray(index, dtype=np.float32) c_input[i] = ffi.from_buffer("float*", indexes[i]) @@ -296,12 +294,12 @@ def sample_mgrid(self, mgrid: ArrayLike) -> NDArray[np.float32]: """ mgrid = np.ascontiguousarray(mgrid, np.float32) if mgrid.shape[0] != self.dimensions: - raise ValueError( - "mgrid.shape[0] must equal self.dimensions, " "%r[0] != %r" % (mgrid.shape, self.dimensions) - ) + msg = f"mgrid.shape[0] must equal self.dimensions, {mgrid.shape!r}[0] != {self.dimensions!r}" + raise ValueError(msg) out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray(mgrid.shape[1:], np.float32) if mgrid.shape[1:] != out.shape: - raise ValueError("mgrid.shape[1:] must equal out.shape, " "%r[1:] != %r" % (mgrid.shape, out.shape)) + msg = f"mgrid.shape[1:] must equal out.shape, {mgrid.shape!r}[1:] != {out.shape!r}" + raise ValueError(msg) lib.NoiseSampleMeshGrid( self._tdl_noise_c, out.size, @@ -323,8 +321,9 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: The ``dtype`` is `numpy.float32`. """ if len(ogrid) != self.dimensions: - raise ValueError("len(ogrid) must equal self.dimensions, " "%r != %r" % (len(ogrid), self.dimensions)) - ogrids: List[NDArray[np.float32]] = [np.ascontiguousarray(array, np.float32) for array in ogrid] + msg = f"len(ogrid) must equal self.dimensions, {len(ogrid)!r} != {self.dimensions!r}" + raise ValueError(msg) + ogrids: list[NDArray[np.float32]] = [np.ascontiguousarray(array, np.float32) for array in ogrid] out: np.ndarray[Any, np.dtype[np.float32]] = np.ndarray([array.size for array in ogrids], np.float32) lib.NoiseSampleOpenMeshGrid( self._tdl_noise_c, @@ -335,7 +334,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: ) return out - def __getstate__(self) -> Any: + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # Trigger a side effect of wavelet, so that copies will be synced. @@ -366,7 +365,7 @@ def __getstate__(self) -> Any: } return state - def __setstate__(self, state: Any) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: if isinstance(state, tuple): # deprecated format return self._setstate_old(state) # unpack wavelet tile data if it exists @@ -384,6 +383,7 @@ def __setstate__(self, state: Any) -> None: state["_tdl_noise_c"]["noise"] = state["noise_c"] state["_tdl_noise_c"] = ffi.new("TDLNoise*", state["_tdl_noise_c"]) self.__dict__.update(state) + return None def _setstate_old(self, state: Any) -> None: self._random = state[0] @@ -403,11 +403,11 @@ def _setstate_old(self, state: Any) -> None: def grid( - shape: Tuple[int, ...], - scale: Union[Tuple[float, ...], float], - origin: Optional[Tuple[int, ...]] = None, + shape: tuple[int, ...], + scale: tuple[float, ...] | float, + origin: tuple[int, ...] | None = None, indexing: Literal["ij", "xy"] = "xy", -) -> Tuple[NDArray[Any], ...]: +) -> tuple[NDArray[Any], ...]: """Generate a mesh-grid of sample points to use with noise sampling. Args: @@ -444,14 +444,16 @@ def grid( dtype=float32) .. versionadded:: 12.2 - """ # noqa: E501 + """ if isinstance(scale, float): scale = (scale,) * len(shape) if origin is None: origin = (0,) * len(shape) if len(shape) != len(scale): - raise TypeError("shape must have the same length as scale") + msg = "shape must have the same length as scale" + raise TypeError(msg) if len(shape) != len(origin): - raise TypeError("shape must have the same length as origin") + msg = "shape must have the same length as origin" + raise TypeError(msg) indexes = (np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin)) return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) diff --git a/tcod/path.py b/tcod/path.py index db42fba5..e7b648c5 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -20,13 +20,12 @@ import functools import itertools import warnings -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable import numpy as np from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal -import tcod.map # noqa: F401 from tcod._internal import _check from tcod.loader import ffi, lib @@ -65,7 +64,7 @@ def _get_path_cost_func( return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) # type: ignore -class _EdgeCostFunc(object): +class _EdgeCostFunc: """Generic edge-cost function factory. `userdata` is the custom userdata to send to the C call. @@ -75,16 +74,16 @@ class _EdgeCostFunc(object): _CALLBACK_P = lib._pycall_path_old - def __init__(self, userdata: Any, shape: Tuple[int, int]) -> None: + def __init__(self, userdata: Any, shape: tuple[int, int]) -> None: self._userdata = userdata self.shape = shape - def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: + def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: """Return (C callback, userdata handle, shape).""" return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape def __repr__(self) -> str: - return "%s(%r, shape=%r)" % ( + return "{}({!r}, shape={!r})".format( self.__class__.__name__, self._userdata, self.shape, @@ -109,10 +108,10 @@ class EdgeCostCallback(_EdgeCostFunc): def __init__( self, callback: Callable[[int, int, int, int], float], - shape: Tuple[int, int], - ): + shape: tuple[int, int], + ) -> None: self.callback = callback - super(EdgeCostCallback, self).__init__(callback, shape) + super().__init__(callback, shape) class NodeCostArray(np.ndarray): # type: ignore @@ -139,16 +138,15 @@ def __new__(cls, array: ArrayLike) -> NodeCostArray: return self def __repr__(self) -> str: - return "%s(%r)" % ( - self.__class__.__name__, - repr(self.view(np.ndarray)), - ) + return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})" - def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: + def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: if len(self.shape) != 2: - raise ValueError("Array must have a 2d shape, shape is %r" % (self.shape,)) + msg = f"Array must have a 2d shape, shape is {self.shape!r}" + raise ValueError(msg) if self.dtype.type not in self._C_ARRAY_CALLBACKS: - raise ValueError("dtype must be one of %r, dtype is %r" % (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type)) + msg = f"dtype must be one of {self._C_ARRAY_CALLBACKS.keys()!r}, dtype is {self.dtype.type!r}" + raise ValueError(msg) array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type] userdata = ffi.new( @@ -158,10 +156,10 @@ def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]: return callback, userdata, (self.shape[0], self.shape[1]) -class _PathFinder(object): +class _PathFinder: """A class sharing methods used by AStar and Dijkstra.""" - def __init__(self, cost: Any, diagonal: float = 1.41): + def __init__(self, cost: Any, diagonal: float = 1.41) -> None: self.cost = cost self.diagonal = diagonal self._path_c: Any = None @@ -198,11 +196,7 @@ def __init__(self, cost: Any, diagonal: float = 1.41): ) def __repr__(self) -> str: - return "%s(cost=%r, diagonal=%r)" % ( - self.__class__.__name__, - self.cost, - self.diagonal, - ) + return f"{self.__class__.__name__}(cost={self.cost!r}, diagonal={self.diagonal!r})" def __getstate__(self) -> Any: state = self.__dict__.copy() @@ -222,14 +216,15 @@ def __setstate__(self, state: Any) -> None: class AStar(_PathFinder): - """ + """The older libtcod A* pathfinder. + Args: cost (Union[tcod.map.Map, numpy.ndarray, Any]): diagonal (float): Multiplier for diagonal movement. A value of 0 will disable diagonal movement entirely. """ - def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> List[Tuple[int, int]]: + def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> list[tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible. Args: @@ -237,6 +232,7 @@ def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> List start_y (int): Starting Y position. goal_x (int): Destination X position. goal_y (int): Destination Y position. + Returns: List[Tuple[int, int]]: A list of points, or an empty list if there is no valid path. @@ -251,7 +247,8 @@ def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> List class Dijkstra(_PathFinder): - """ + """The older libtcod Dijkstra pathfinder. + Args: cost (Union[tcod.map.Map, numpy.ndarray, Any]): diagonal (float): Multiplier for diagonal movement. @@ -266,7 +263,7 @@ def set_goal(self, x: int, y: int) -> None: """Set the goal point and recompute the Dijkstra path-finder.""" lib.TCOD_dijkstra_compute(self._path_c, x, y) - def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: + def get_path(self, x: int, y: int) -> list[tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible.""" lib.TCOD_dijkstra_path_set(self._path_c, x, y) path = [] @@ -292,7 +289,7 @@ def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: def maxarray( - shape: Tuple[int, ...], + shape: tuple[int, ...], dtype: Any = np.int32, order: Literal["C", "F"] = "C", ) -> NDArray[Any]: @@ -314,10 +311,11 @@ def maxarray( return np.full(shape, np.iinfo(dtype).max, dtype, order) -def _export_dict(array: NDArray[Any]) -> Dict[str, Any]: +def _export_dict(array: NDArray[Any]) -> dict[str, Any]: """Convert a NumPy array into a format compatible with CFFI.""" if array.dtype.type not in _INT_TYPES: - raise TypeError("dtype was %s, but must be one of %s." % (array.dtype.type, tuple(_INT_TYPES.keys()))) + msg = f"dtype was {array.dtype.type}, but must be one of {tuple(_INT_TYPES.keys())}." + raise TypeError(msg) return { "type": _INT_TYPES[array.dtype.type], "ndim": array.ndim, @@ -332,7 +330,7 @@ def _export(array: NDArray[Any]) -> Any: return ffi.new("struct NArray*", _export_dict(array)) -def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: +def _compile_cost_edges(edge_map: Any) -> tuple[Any, int]: """Return an edge_cost array using an integer map.""" edge_map = np.array(edge_map, copy=True) if edge_map.ndim != 2: @@ -353,11 +351,11 @@ def _compile_cost_edges(edge_map: Any) -> Tuple[Any, int]: def dijkstra2d( distance: ArrayLike, cost: ArrayLike, - cardinal: Optional[int] = None, - diagonal: Optional[int] = None, + cardinal: int | None = None, + diagonal: int | None = None, *, edge_map: Any = None, - out: Optional[np.ndarray] = ..., # type: ignore + out: np.ndarray | None = ..., # type: ignore ) -> NDArray[Any]: """Return the computed distance of all nodes on a 2D Dijkstra grid. @@ -491,14 +489,17 @@ def dijkstra2d( out[...] = dist if dist.shape != out.shape: - raise TypeError("distance and output must have the same shape %r != %r" % (dist.shape, out.shape)) + msg = f"distance and output must have the same shape {dist.shape!r} != {out.shape!r}" + raise TypeError(msg) cost = np.asarray(cost) if dist.shape != cost.shape: - raise TypeError("output and cost must have the same shape %r != %r" % (out.shape, cost.shape)) + msg = f"output and cost must have the same shape {out.shape!r} != {cost.shape!r}" + raise TypeError(msg) c_dist = _export(out) if edge_map is not None: if cardinal is not None or diagonal is not None: - raise TypeError("`edge_map` can not be set at the same time as" " `cardinal` or `diagonal`.") + msg = "`edge_map` can not be set at the same time as `cardinal` or `diagonal`." + raise TypeError(msg) c_edges, n_edges = _compile_cost_edges(edge_map) _check(lib.dijkstra2d(c_dist, _export(cost), n_edges, c_edges)) else: @@ -510,7 +511,7 @@ def dijkstra2d( return out -def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: +def _compile_bool_edges(edge_map: ArrayLike) -> tuple[Any, int]: """Return an edge array using a boolean map.""" edge_map = np.array(edge_map, copy=True) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 @@ -522,9 +523,9 @@ def _compile_bool_edges(edge_map: ArrayLike) -> Tuple[Any, int]: def hillclimb2d( distance: ArrayLike, - start: Tuple[int, int], - cardinal: Optional[bool] = None, - diagonal: Optional[bool] = None, + start: tuple[int, int], + cardinal: bool | None = None, + diagonal: bool | None = None, *, edge_map: Any = None, ) -> NDArray[Any]: @@ -562,11 +563,13 @@ def hillclimb2d( x, y = start dist: NDArray[Any] = np.asarray(distance) if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]): - raise IndexError("Starting point %r not in shape %r" % (start, dist.shape)) + msg = f"Starting point {start!r} not in shape {dist.shape!r}" + raise IndexError(msg) c_dist = _export(dist) if edge_map is not None: if cardinal is not None or diagonal is not None: - raise TypeError("`edge_map` can not be set at the same time as" " `cardinal` or `diagonal`.") + msg = "`edge_map` can not be set at the same time as `cardinal` or `diagonal`." + raise TypeError(msg) c_edges, n_edges = _compile_bool_edges(edge_map) func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges, c_edges) else: @@ -578,7 +581,7 @@ def hillclimb2d( return path -def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: +def _world_array(shape: tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( @@ -592,7 +595,7 @@ def _world_array(shape: Tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: ) -def _as_hashable(obj: Optional[np.ndarray[Any, Any]]) -> Optional[Any]: +def _as_hashable(obj: np.ndarray[Any, Any] | None) -> Any | None: """Return NumPy arrays as a more hashable form.""" if obj is None: return obj @@ -661,18 +664,19 @@ class CustomGraph: Added the `order` parameter. """ - def __init__(self, shape: Tuple[int, ...], *, order: str = "C"): + def __init__(self, shape: tuple[int, ...], *, order: str = "C") -> None: self._shape = self._shape_c = tuple(shape) self._ndim = len(self._shape) self._order = order if self._order == "F": self._shape_c = self._shape_c[::-1] if not 0 < self._ndim <= 4: - raise TypeError("Graph dimensions must be 1 <= n <= 4.") - self._graph: Dict[Tuple[Any, ...], Dict[str, Any]] = {} - self._edge_rules_keep_alive: List[Any] = [] + msg = "Graph dimensions must be 1 <= n <= 4." + raise TypeError(msg) + self._graph: dict[tuple[Any, ...], dict[str, Any]] = {} + self._edge_rules_keep_alive: list[Any] = [] self._edge_rules_p: Any = None - self._heuristic: Optional[Tuple[int, int, int, int]] = None + self._heuristic: tuple[int, int, int, int] | None = None @property def ndim(self) -> int: @@ -680,17 +684,17 @@ def ndim(self) -> int: return self._ndim @property - def shape(self) -> Tuple[int, ...]: + def shape(self) -> tuple[int, ...]: """Return the shape of this graph.""" return self._shape def add_edge( self, - edge_dir: Tuple[int, ...], + edge_dir: tuple[int, ...], edge_cost: int = 1, *, cost: NDArray[Any], - condition: Optional[ArrayLike] = None, + condition: ArrayLike | None = None, ) -> None: """Add a single edge rule. @@ -742,20 +746,23 @@ def add_edge( but bidirectional edges are not a requirement for the graph. One directional edges such as pits can be added which will only allow movement outwards from the root nodes of the pathfinder. - """ # noqa: E501 + """ self._edge_rules_p = None edge_dir = tuple(edge_dir) cost = np.asarray(cost) if len(edge_dir) != self._ndim: raise TypeError("edge_dir must have exactly %i items, got %r" % (self._ndim, edge_dir)) if edge_cost <= 0: - raise ValueError("edge_cost must be greater than zero, got %r" % (edge_cost,)) + msg = f"edge_cost must be greater than zero, got {edge_cost!r}" + raise ValueError(msg) if cost.shape != self._shape: - raise TypeError("cost array must be shape %r, got %r" % (self._shape, cost.shape)) + msg = f"cost array must be shape {self._shape!r}, got {cost.shape!r}" + raise TypeError(msg) if condition is not None: condition = np.asarray(condition) if condition.shape != self._shape: - raise TypeError("condition array must be shape %r, got %r" % (self._shape, condition.shape)) + msg = f"condition array must be shape {self._shape!r}, got {condition.shape!r}" + raise TypeError(msg) if self._order == "F": # Inputs need to be converted to C. edge_dir = edge_dir[::-1] @@ -772,7 +779,7 @@ def add_edge( } if condition is not None: rule["condition"] = condition - edge = edge_dir + (edge_cost,) + edge = (*edge_dir, edge_cost) if edge not in rule["edge_list"]: rule["edge_list"].append(edge) @@ -781,7 +788,7 @@ def add_edges( *, edge_map: ArrayLike, cost: NDArray[Any], - condition: Optional[ArrayLike] = None, + condition: ArrayLike | None = None, ) -> None: """Add a rule with multiple edges. @@ -943,13 +950,15 @@ def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: that's because those nodes are only partially evaluated, but pathfinding to those nodes will work correctly as long as the heuristic isn't greedy. - """ # noqa: E501 + """ if 0 == cardinal == diagonal == z == w: self._heuristic = None if diagonal and cardinal > diagonal: - raise ValueError("Diagonal parameter can not be lower than cardinal.") + msg = "Diagonal parameter can not be lower than cardinal." + raise ValueError(msg) if cardinal < 0 or diagonal < 0 or z < 0 or w < 0: - raise ValueError("Parameters can not be set to negative values..") + msg = "Parameters can not be set to negative values." + raise ValueError(msg) self._heuristic = (cardinal, diagonal, z, w) def _compile_rules(self) -> Any: @@ -1022,12 +1031,14 @@ class SimpleGraph: .. versionadded:: 11.15 """ - def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1): + def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1) -> None: cost = np.asarray(cost) if cost.ndim != 2: - raise TypeError("The cost array must e 2 dimensional, array of shape %r given." % (cost.shape,)) + msg = f"The cost array must e 2 dimensional, array of shape {cost.shape!r} given." + raise TypeError(msg) if greed <= 0: - raise ValueError("Greed must be greater than zero, got %r" % (greed,)) + msg = f"Greed must be greater than zero, got {greed}" + raise ValueError(msg) edge_map = ( (diagonal, cardinal, diagonal), (cardinal, 0, cardinal), @@ -1046,11 +1057,11 @@ def ndim(self) -> int: return 2 @property - def shape(self) -> Tuple[int, int]: + def shape(self) -> tuple[int, int]: return self._shape @property - def _heuristic(self) -> Optional[Tuple[int, int, int, int]]: + def _heuristic(self) -> tuple[int, int, int, int] | None: return self._subgraph._heuristic def set_heuristic(self, *, cardinal: int, diagonal: int) -> None: @@ -1079,7 +1090,7 @@ class Pathfinder: .. versionadded:: 11.13 """ - def __init__(self, graph: Union[CustomGraph, SimpleGraph]): + def __init__(self, graph: CustomGraph | SimpleGraph) -> None: self._graph = graph self._order = graph._order self._frontier_p = ffi.gc(lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete) @@ -1087,7 +1098,7 @@ def __init__(self, graph: Union[CustomGraph, SimpleGraph]): self._travel = _world_array(self._graph._shape_c) self._distance_p = _export(self._distance) self._travel_p = _export(self._travel) - self._heuristic: Optional[Tuple[int, int, int, int, Tuple[int, ...]]] = None + self._heuristic: tuple[int, int, int, int, tuple[int, ...]] | None = None self._heuristic_p: Any = ffi.NULL @property @@ -1161,7 +1172,7 @@ def clear(self) -> None: self._travel = _world_array(self._graph._shape_c) lib.TCOD_frontier_clear(self._frontier_p) - def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: + def add_root(self, index: tuple[int, ...], value: int = 0) -> None: """Add a root node and insert it into the pathfinder frontier. `index` is the root point to insert. The length of `index` must match @@ -1179,7 +1190,7 @@ def add_root(self, index: Tuple[int, ...], value: int = 0) -> None: self._update_heuristic(None) lib.TCOD_frontier_push(self._frontier_p, index, value, value) - def _update_heuristic(self, goal_ij: Optional[Tuple[int, ...]]) -> bool: + def _update_heuristic(self, goal_ij: tuple[int, ...] | None) -> bool: """Update the active heuristic. Return True if the heuristic changed.""" if goal_ij is None: heuristic = None @@ -1212,7 +1223,7 @@ def rebuild_frontier(self) -> None: self._update_heuristic(None) _check(lib.rebuild_frontier_from_distance(self._frontier_p, self._distance_p)) - def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: + def resolve(self, goal: tuple[int, ...] | None = None) -> None: """Manually run the pathfinder algorithm. The :any:`path_from` and :any:`path_to` methods will automatically @@ -1270,7 +1281,7 @@ def resolve(self, goal: Optional[Tuple[int, ...]] = None) -> None: self._update_heuristic(goal) self._graph._resolve(self) - def path_from(self, index: Tuple[int, ...]) -> NDArray[Any]: + def path_from(self, index: tuple[int, ...]) -> NDArray[Any]: """Return the shortest path from `index` to the nearest root. The returned array is of shape `(length, ndim)` where `length` is the @@ -1303,7 +1314,7 @@ def path_from(self, index: Tuple[int, ...]) -> NDArray[Any]: >>> pf.path_from((4, 4))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. [] - """ # noqa: E501 + """ index = tuple(index) # Check for bad input. if len(index) != self._graph._ndim: raise TypeError("Index must be %i items, got %r" % (self._distance.ndim, index)) @@ -1322,7 +1333,7 @@ def path_from(self, index: Tuple[int, ...]) -> NDArray[Any]: ) return path[:, ::-1] if self._order == "F" else path - def path_to(self, index: Tuple[int, ...]) -> NDArray[Any]: + def path_to(self, index: tuple[int, ...]) -> NDArray[Any]: """Return the shortest path from the nearest root to `index`. See :any:`path_from`. @@ -1346,5 +1357,5 @@ def path_to(self, index: Tuple[int, ...]) -> NDArray[Any]: [[1, 1], [2, 2], [3, 3]] >>> pf.path_to((0, 0))[1:].tolist() # Exclude the starting point so that a blocked path is an empty list. [] - """ # noqa: E501 + """ return self.path_from(index)[::-1] diff --git a/tcod/random.py b/tcod/random.py index 013dd6ab..12169141 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -11,7 +11,7 @@ import os import random import warnings -from typing import Any, Hashable, Optional +from typing import Any, Hashable import tcod.constants from tcod.loader import ffi, lib @@ -21,7 +21,7 @@ MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC -class Random(object): +class Random: """The libtcod random number generator. `algorithm` defaults to Mersenne Twister, it can be one of: @@ -49,8 +49,8 @@ class Random(object): def __init__( self, algorithm: int = MERSENNE_TWISTER, - seed: Optional[Hashable] = None, - ): + seed: Hashable | None = None, + ) -> None: """Create a new instance using this algorithm and seed.""" if seed is None: seed = random.getrandbits(32) diff --git a/tcod/render.py b/tcod/render.py index e604d29f..71a98686 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -29,7 +29,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any from typing_extensions import Final @@ -72,8 +72,8 @@ def __init__(self, atlas: SDLTilesetAtlas) -> None: .. versionadded:: 13.7 """ self._renderer = atlas._renderer - self._cache_console: Optional[tcod.console.Console] = None - self._texture: Optional[tcod.sdl.render.Texture] = None + self._cache_console: tcod.console.Console | None = None + self._texture: tcod.sdl.render.Texture | None = None def render(self, console: tcod.console.Console) -> tcod.sdl.render.Texture: """Render a console to a cached Texture and then return the Texture. diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index e1cbde7a..84ee2d06 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -2,7 +2,7 @@ import logging from pkgutil import extend_path -from typing import Any, Callable, Tuple, TypeVar +from typing import Any, Callable, TypeVar from tcod.loader import ffi, lib @@ -51,34 +51,32 @@ def _check_p(result: Any) -> Any: lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) -def _compiled_version() -> Tuple[int, int, int]: +def _compiled_version() -> tuple[int, int, int]: return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) -def _linked_version() -> Tuple[int, int, int]: +def _linked_version() -> tuple[int, int, int]: sdl_version = ffi.new("SDL_version*") lib.SDL_GetVersion(sdl_version) return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) -def _version_at_least(required: Tuple[int, int, int]) -> None: +def _version_at_least(required: tuple[int, int, int]) -> None: """Raise an error if the compiled version is less than required. Used to guard recently defined SDL functions.""" if required <= _compiled_version(): return - raise RuntimeError( - f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" - ) + msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + raise RuntimeError(msg) -def _required_version(required: Tuple[int, int, int]) -> Callable[[T], T]: +def _required_version(required: tuple[int, int, int]) -> Callable[[T], T]: if not lib: # Read the docs mock object. return lambda x: x if required <= _compiled_version(): return lambda x: x def replacement(*_args: Any, **_kwargs: Any) -> Any: - raise RuntimeError( - f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" - ) + msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + raise RuntimeError(msg) return lambda x: replacement # type: ignore[return-value] diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 55b080c6..8234a2a6 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -47,7 +47,7 @@ import sys import threading import time -from typing import Any, Callable, Dict, Hashable, Iterator, List, Optional, Tuple, Union +from typing import Any, Callable, Hashable, Iterator import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray @@ -116,8 +116,9 @@ def convert_audio( in_array: NDArray[Any] = np.asarray(in_sound) if len(in_array.shape) == 1: in_array = in_array[:, np.newaxis] - if not len(in_array.shape) == 2: - raise TypeError(f"Expected a 1 or 2 ndim input, got {in_array.shape} instead.") + if len(in_array.shape) != 2: + msg = f"Expected a 1 or 2 ndim input, got {in_array.shape} instead." + raise TypeError(msg) cvt = ffi.new("SDL_AudioCVT*") in_channels = in_array.shape[1] in_format = _get_format(in_array.dtype) @@ -150,7 +151,7 @@ def __init__( device_id: int, capture: bool, spec: Any, # SDL_AudioSpec* - ): + ) -> None: assert device_id >= 0 assert ffi.typeof(spec) is ffi.typeof("SDL_AudioSpec*") assert spec @@ -172,20 +173,22 @@ def __init__( """The size of the audio buffer in samples.""" self.buffer_bytes: Final[int] = int(spec.size) """The size of the audio buffer in bytes.""" - self._handle: Optional[Any] = None + self._handle: Any | None = None self._callback: Callable[[AudioDevice, NDArray[Any]], None] = self.__default_callback @property def callback(self) -> Callable[[AudioDevice, NDArray[Any]], None]: """If the device was opened with a callback enabled, then you may get or set the callback with this attribute.""" if self._handle is None: - raise TypeError("This AudioDevice was opened without a callback.") + msg = "This AudioDevice was opened without a callback." + raise TypeError(msg) return self._callback @callback.setter def callback(self, new_callback: Callable[[AudioDevice, NDArray[Any]], None]) -> None: if self._handle is None: - raise TypeError("This AudioDevice was opened without a callback.") + msg = "This AudioDevice was opened without a callback." + raise TypeError(msg) self._callback = new_callback @property @@ -209,7 +212,8 @@ def paused(self, value: bool) -> None: def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]: if samples.dtype != self.format: - raise TypeError(f"Expected an array of dtype {self.format}, got {samples.dtype} instead.") + msg = f"Expected an array of dtype {self.format}, got {samples.dtype} instead." + raise TypeError(msg) return samples def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: @@ -220,7 +224,7 @@ def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: samples = samples[:, np.newaxis] return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) - def convert(self, sound: ArrayLike, rate: Optional[int] = None) -> NDArray[Any]: + def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[Any]: """Convert an audio sample into a format supported by this device. Returns the converted array. This might be a reference to the input array if no conversion was needed. @@ -290,7 +294,7 @@ def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: class _LoopSoundFunc: - def __init__(self, sound: NDArray[Any], loops: int, on_end: Optional[Callable[[Channel], None]]): + def __init__(self, sound: NDArray[Any], loops: int, on_end: Callable[[Channel], None] | None) -> None: self.sound = sound self.loops = loops self.on_end = on_end @@ -316,9 +320,9 @@ class Channel: def __init__(self) -> None: self._lock = threading.RLock() - self.volume: Union[float, Tuple[float, ...]] = 1.0 - self.sound_queue: List[NDArray[Any]] = [] - self.on_end_callback: Optional[Callable[[Channel], None]] = None + self.volume: float | tuple[float, ...] = 1.0 + self.sound_queue: list[NDArray[Any]] = [] + self.on_end_callback: Callable[[Channel], None] | None = None @property def busy(self) -> bool: @@ -329,9 +333,9 @@ def play( self, sound: ArrayLike, *, - volume: Union[float, Tuple[float, ...]] = 1.0, + volume: float | tuple[float, ...] = 1.0, loops: int = 0, - on_end: Optional[Callable[[Channel], None]] = None, + on_end: Callable[[Channel], None] | None = None, ) -> None: """Play an audio sample, stopping any audio currently playing on this channel. @@ -392,8 +396,8 @@ class BasicMixer(threading.Thread): .. versionadded:: 13.6 """ - def __init__(self, device: AudioDevice): - self.channels: Dict[Hashable, Channel] = {} + def __init__(self, device: AudioDevice) -> None: + self.channels: dict[Hashable, Channel] = {} assert device.format == np.float32 super().__init__(daemon=True) self.device = device @@ -445,9 +449,9 @@ def play( self, sound: ArrayLike, *, - volume: Union[float, Tuple[float, ...]] = 1.0, + volume: float | tuple[float, ...] = 1.0, loops: int = 0, - on_end: Optional[Callable[[Channel], None]] = None, + on_end: Callable[[Channel], None] | None = None, ) -> Channel: """Play a sound, return the channel the sound is playing on. @@ -525,7 +529,7 @@ class AllowedChanges(enum.IntFlag): def open( - name: Optional[str] = None, + name: str | None = None, capture: bool = False, *, frequency: int = 44100, @@ -534,7 +538,7 @@ def open( samples: int = 0, allowed_changes: AllowedChanges = AllowedChanges.NONE, paused: bool = False, - callback: Union[None, Literal[True], Callable[[AudioDevice, NDArray[Any]], None]] = None, + callback: None | Literal[True] | Callable[[AudioDevice, NDArray[Any]], None] = None, ) -> AudioDevice: """Open an audio device for playback or capture and return it. diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index dde74192..5d24d353 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -1,11 +1,11 @@ -"""SDL Joystick Support +"""SDL Joystick Support. .. versionadded:: 13.8 """ from __future__ import annotations import enum -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any from typing_extensions import Final, Literal @@ -13,7 +13,7 @@ from tcod.loader import ffi, lib from tcod.sdl import _check, _check_p -_HAT_DIRECTIONS: Dict[int, Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { +_HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { lib.SDL_HAT_CENTERED or 0: (0, 0), lib.SDL_HAT_UP or 0: (0, -1), lib.SDL_HAT_RIGHT or 0: (1, 0), @@ -122,7 +122,7 @@ class Joystick: https://wiki.libsdl.org/CategoryJoystick """ - def __init__(self, sdl_joystick_p: Any): + def __init__(self, sdl_joystick_p: Any) -> None: self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) @@ -173,7 +173,7 @@ def get_axis(self, axis: int) -> int: """Return the raw value of `axis` in the range -32768 to 32767.""" return int(lib.SDL_JoystickGetAxis(self.sdl_joystick_p, axis)) - def get_ball(self, ball: int) -> Tuple[int, int]: + def get_ball(self, ball: int) -> tuple[int, int]: """Return the values (delta_x, delta_y) of `ball` since the last poll.""" xy = ffi.new("int[2]") _check(lib.SDL_JoystickGetBall(ball, xy, xy + 1)) @@ -183,7 +183,7 @@ def get_button(self, button: int) -> bool: """Return True if `button` is currently held.""" return bool(lib.SDL_JoystickGetButton(self.sdl_joystick_p, button)) - def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: + def get_hat(self, hat: int) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: """Return the direction of `hat` as (x, y). With (-1, -1) being in the upper-left.""" return _HAT_DIRECTIONS[lib.SDL_JoystickGetHat(self.sdl_joystick_p, hat)] @@ -191,7 +191,7 @@ def get_hat(self, hat: int) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: class GameController: """A standard interface for an Xbox 360 style game controller.""" - def __init__(self, sdl_controller_p: Any): + def __init__(self, sdl_controller_p: Any) -> None: self.sdl_controller_p: Final = sdl_controller_p self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p)) """The :any:`Joystick` associated with this controller.""" @@ -228,32 +228,32 @@ def __hash__(self) -> int: # These could exist as convenience functions, but the get_X functions are probably better. @property def _left_x(self) -> int: - "Return the position of this axis. (-32768 to 32767)" + """Return the position of this axis (-32768 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTX)) @property def _left_y(self) -> int: - "Return the position of this axis. (-32768 to 32767)" + """Return the position of this axis (-32768 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTY)) @property def _right_x(self) -> int: - "Return the position of this axis. (-32768 to 32767)" + """Return the position of this axis (-32768 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTX)) @property def _right_y(self) -> int: - "Return the position of this axis. (-32768 to 32767)" + """Return the position of this axis (-32768 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTY)) @property def _trigger_left(self) -> int: - "Return the position of this trigger. (0 to 32767)" + """Return the position of this trigger (0 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) @property def _trigger_right(self) -> int: - "Return the position of this trigger. (0 to 32767)" + """Return the position of this trigger (0 to 32767).""" return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) @property @@ -312,7 +312,7 @@ def _right_shoulder(self) -> bool: return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) @property - def _dpad(self) -> Tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: + def _dpad(self) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: return ( lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT), @@ -362,12 +362,12 @@ def _get_number() -> int: return _check(lib.SDL_NumJoysticks()) -def get_joysticks() -> List[Joystick]: +def get_joysticks() -> list[Joystick]: """Return a list of all connected joystick devices.""" return [Joystick._open(i) for i in range(_get_number())] -def get_controllers() -> List[GameController]: +def get_controllers() -> list[GameController]: """Return a list of all connected game controllers. This ignores joysticks without a game controller mapping. @@ -375,7 +375,7 @@ def get_controllers() -> List[GameController]: return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGameController(i)] -def _get_all() -> List[Union[Joystick, GameController]]: +def _get_all() -> list[Joystick | GameController]: """Return a list of all connected joystick or controller devices. If the joystick has a controller mapping then it is returned as a :any:`GameController`. @@ -384,7 +384,7 @@ def _get_all() -> List[Union[Joystick, GameController]]: return [GameController._open(i) if lib.SDL_IsGameController(i) else Joystick._open(i) for i in range(_get_number())] -def joystick_event_state(new_state: Optional[bool] = None) -> bool: +def joystick_event_state(new_state: bool | None = None) -> bool: """Check or set joystick event polling. .. seealso:: @@ -394,7 +394,7 @@ def joystick_event_state(new_state: Optional[bool] = None) -> bool: return bool(_check(lib.SDL_JoystickEventState(_OPTIONS[new_state]))) -def controller_event_state(new_state: Optional[bool] = None) -> bool: +def controller_event_state(new_state: bool | None = None) -> bool: """Check or set game controller event polling. .. seealso:: diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index bce0b07f..aaffdeec 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -9,7 +9,7 @@ from __future__ import annotations import enum -from typing import Any, Optional, Tuple, Union +from typing import Any import numpy as np from numpy.typing import ArrayLike, NDArray @@ -23,11 +23,13 @@ class Cursor: """A cursor icon for use with :any:`set_cursor`.""" - def __init__(self, sdl_cursor_p: Any): + def __init__(self, sdl_cursor_p: Any) -> None: if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"): - raise TypeError(f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)}).") + msg = f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)})." + raise TypeError(msg) if not sdl_cursor_p: - raise TypeError("C pointer must not be null.") + msg = "C pointer must not be null." + raise TypeError(msg) self.p = sdl_cursor_p def __eq__(self, other: Any) -> bool: @@ -68,7 +70,7 @@ class SystemCursor(enum.IntEnum): """""" -def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: Tuple[int, int] = (0, 0)) -> Cursor: +def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: tuple[int, int] = (0, 0)) -> Cursor: """Return a new non-color Cursor from the provided parameters. Args: @@ -81,9 +83,11 @@ def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: Tuple[i https://wiki.libsdl.org/SDL_CreateCursor """ if len(data.shape) != 2: - raise TypeError("Data and mask arrays must be 2D.") + msg = "Data and mask arrays must be 2D." + raise TypeError(msg) if data.shape != mask.shape: - raise TypeError("Data and mask arrays must have the same shape.") + msg = "Data and mask arrays must have the same shape." + raise TypeError(msg) height, width = data.shape data_packed = np.packbits(data, axis=0, bitorder="big") mask_packed = np.packbits(mask, axis=0, bitorder="big") @@ -94,9 +98,8 @@ def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: Tuple[i ) -def new_color_cursor(pixels: ArrayLike, hot_xy: Tuple[int, int]) -> Cursor: - """ - Args: +def new_color_cursor(pixels: ArrayLike, hot_xy: tuple[int, int]) -> Cursor: + """Args: pixels: A row-major array of RGB or RGBA pixels. hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). @@ -116,7 +119,7 @@ def new_system_cursor(cursor: SystemCursor) -> Cursor: return Cursor._claim(lib.SDL_CreateSystemCursor(cursor)) -def set_cursor(cursor: Optional[Union[Cursor, SystemCursor]]) -> None: +def set_cursor(cursor: Cursor | SystemCursor | None) -> None: """Change the active cursor to the one provided. Args: @@ -134,7 +137,7 @@ def get_default_cursor() -> Cursor: return Cursor(_check_p(lib.SDL_GetDefaultCursor())) -def get_cursor() -> Optional[Cursor]: +def get_cursor() -> Cursor | None: """Return the active cursor, or None if these is no mouse.""" cursor_p = lib.SDL_GetCursor() return Cursor(cursor_p) if cursor_p else None @@ -214,7 +217,7 @@ def get_state() -> tcod.event.MouseState: return tcod.event.MouseState((xy[0], xy[1]), state=state) -def get_focus() -> Optional[tcod.sdl.video.Window]: +def get_focus() -> tcod.sdl.video.Window | None: """Return the window which currently has mouse focus.""" window_p = lib.SDL_GetMouseFocus() return tcod.sdl.video.Window(window_p) if window_p else None diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index d7e3226d..65cc6d41 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -5,7 +5,7 @@ from __future__ import annotations import enum -from typing import Any, Optional, Tuple, Union +from typing import Any import numpy as np from numpy.typing import NDArray @@ -168,14 +168,14 @@ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: def __eq__(self, other: Any) -> bool: return bool(self.p == getattr(other, "p", None)) - def _query(self) -> Tuple[int, int, int, int]: + def _query(self) -> tuple[int, int, int, int]: """Return (format, access, width, height).""" format = ffi.new("uint32_t*") buffer = ffi.new("int[3]") lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) return int(format[0]), int(buffer[0]), int(buffer[1]), int(buffer[2]) - def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None: + def update(self, pixels: NDArray[Any], rect: tuple[int, int, int, int] | None = None) -> None: """Update the pixel data of this texture. .. versionadded:: 13.5 @@ -214,14 +214,14 @@ def blend_mode(self, value: int) -> None: _check(lib.SDL_SetTextureBlendMode(self.p, value)) @property - def color_mod(self) -> Tuple[int, int, int]: + def color_mod(self) -> tuple[int, int, int]: """Texture RGB color modulate values, can be set.""" rgb = ffi.new("uint8_t[3]") _check(lib.SDL_GetTextureColorMod(self.p, rgb, rgb + 1, rgb + 2)) return int(rgb[0]), int(rgb[1]), int(rgb[2]) @color_mod.setter - def color_mod(self, rgb: Tuple[int, int, int]) -> None: + def color_mod(self, rgb: tuple[int, int, int]) -> None: _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2])) @@ -244,9 +244,11 @@ class Renderer: def __init__(self, sdl_renderer_p: Any) -> None: if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): - raise TypeError(f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)}).") + msg = f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)})." + raise TypeError(msg) if not sdl_renderer_p: - raise TypeError("C pointer must not be null.") + msg = "C pointer must not be null." + raise TypeError(msg) self.p = sdl_renderer_p def __eq__(self, other: Any) -> bool: @@ -255,10 +257,10 @@ def __eq__(self, other: Any) -> bool: def copy( self, texture: Texture, - source: Optional[Tuple[float, float, float, float]] = None, - dest: Optional[Tuple[float, float, float, float]] = None, + source: tuple[float, float, float, float] | None = None, + dest: tuple[float, float, float, float] | None = None, angle: float = 0, - center: Optional[Tuple[float, float]] = None, + center: tuple[float, float] | None = None, flip: RendererFlip = RendererFlip.NONE, ) -> None: """Copy a texture to the rendering target. @@ -297,9 +299,7 @@ def set_render_target(self, texture: Texture) -> _RestoreTargetContext: _check(lib.SDL_SetRenderTarget(self.p, texture.p)) return restore - def new_texture( - self, width: int, height: int, *, format: Optional[int] = None, access: Optional[int] = None - ) -> Texture: + def new_texture(self, width: int, height: int, *, format: int | None = None, access: int | None = None) -> Texture: """Allocate and return a new Texture for this renderer. Args: @@ -316,9 +316,7 @@ def new_texture( texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture) return Texture(texture_p, self.p) - def upload_texture( - self, pixels: NDArray[Any], *, format: Optional[int] = None, access: Optional[int] = None - ) -> Texture: + def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, access: int | None = None) -> Texture: """Return a new Texture from an array of pixels. Args: @@ -335,7 +333,8 @@ def upload_texture( elif pixels.shape[2] == 3: format = int(lib.SDL_PIXELFORMAT_RGB24) else: - raise TypeError(f"Can't determine the format required for an array of shape {pixels.shape}.") + msg = f"Can't determine the format required for an array of shape {pixels.shape}." + raise TypeError(msg) texture = self.new_texture(pixels.shape[1], pixels.shape[0], format=format, access=access) if not pixels[0].flags["C_CONTIGUOUS"]: @@ -346,7 +345,7 @@ def upload_texture( return texture @property - def draw_color(self) -> Tuple[int, int, int, int]: + def draw_color(self) -> tuple[int, int, int, int]: """Get or set the active RGBA draw color for this renderer. .. versionadded:: 13.5 @@ -356,7 +355,7 @@ def draw_color(self) -> Tuple[int, int, int, int]: return tuple(rgba) # type: ignore[return-value] @draw_color.setter - def draw_color(self, rgba: Tuple[int, int, int, int]) -> None: + def draw_color(self, rgba: tuple[int, int, int, int]) -> None: _check(lib.SDL_SetRenderDrawColor(self.p, *rgba)) @property @@ -374,7 +373,7 @@ def draw_blend_mode(self, value: int) -> None: _check(lib.SDL_SetRenderDrawBlendMode(self.p, value)) @property - def output_size(self) -> Tuple[int, int]: + def output_size(self) -> tuple[int, int]: """Get the (width, height) pixel resolution of the rendering context. .. seealso:: @@ -387,7 +386,7 @@ def output_size(self) -> Tuple[int, int]: return out[0], out[1] @property - def clip_rect(self) -> Optional[Tuple[int, int, int, int]]: + def clip_rect(self) -> tuple[int, int, int, int] | None: """Get or set the clipping rectangle of this renderer. Set to None to disable clipping. @@ -401,7 +400,7 @@ def clip_rect(self) -> Optional[Tuple[int, int, int, int]]: return rect.x, rect.y, rect.w, rect.h @clip_rect.setter - def clip_rect(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + def clip_rect(self, rect: tuple[int, int, int, int] | None) -> None: rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect) _check(lib.SDL_RenderSetClipRect(self.p, rect_p)) @@ -421,7 +420,7 @@ def integer_scaling(self, enable: bool) -> None: _check(lib.SDL_RenderSetIntegerScale(self.p, enable)) @property - def logical_size(self) -> Tuple[int, int]: + def logical_size(self) -> tuple[int, int]: """Get or set a device independent (width, height) resolution. Might be (0, 0) if a resolution was never assigned. @@ -436,11 +435,11 @@ def logical_size(self) -> Tuple[int, int]: return out[0], out[1] @logical_size.setter - def logical_size(self, size: Tuple[int, int]) -> None: + def logical_size(self, size: tuple[int, int]) -> None: _check(lib.SDL_RenderSetLogicalSize(self.p, *size)) @property - def scale(self) -> Tuple[float, float]: + def scale(self) -> tuple[float, float]: """Get or set an (x_scale, y_scale) multiplier for drawing. .. seealso:: @@ -453,11 +452,11 @@ def scale(self) -> Tuple[float, float]: return out[0], out[1] @scale.setter - def scale(self, scale: Tuple[float, float]) -> None: + def scale(self, scale: tuple[float, float]) -> None: _check(lib.SDL_RenderSetScale(self.p, *scale)) @property - def viewport(self) -> Optional[Tuple[int, int, int, int]]: + def viewport(self) -> tuple[int, int, int, int] | None: """Get or set the drawing area for the current rendering target. .. seealso:: @@ -470,7 +469,7 @@ def viewport(self) -> Optional[Tuple[int, int, int, int]]: return rect.x, rect.y, rect.w, rect.h @viewport.setter - def viewport(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + def viewport(self, rect: tuple[int, int, int, int] | None) -> None: _check(lib.SDL_RenderSetViewport(self.p, (rect,))) @_required_version((2, 0, 18)) @@ -554,35 +553,35 @@ def clear(self) -> None: """ _check(lib.SDL_RenderClear(self.p)) - def fill_rect(self, rect: Tuple[float, float, float, float]) -> None: + def fill_rect(self, rect: tuple[float, float, float, float]) -> None: """Fill a rectangle with :any:`draw_color`. .. versionadded:: 13.5 """ _check(lib.SDL_RenderFillRectF(self.p, (rect,))) - def draw_rect(self, rect: Tuple[float, float, float, float]) -> None: + def draw_rect(self, rect: tuple[float, float, float, float]) -> None: """Draw a rectangle outline. .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawRectF(self.p, (rect,))) - def draw_point(self, xy: Tuple[float, float]) -> None: + def draw_point(self, xy: tuple[float, float]) -> None: """Draw a point. .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawPointF(self.p, (xy,))) - def draw_line(self, start: Tuple[float, float], end: Tuple[float, float]) -> None: + def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> None: """Draw a single line. .. versionadded:: 13.5 """ _check(lib.SDL_RenderDrawLineF(self.p, *start, *end)) - def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: + def fill_rects(self, rects: NDArray[np.intc | np.float32]) -> None: """Fill multiple rectangles from an array. .. versionadded:: 13.5 @@ -595,9 +594,10 @@ def fill_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: elif rects.dtype == np.float32: _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) else: - raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.") + msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}." + raise TypeError(msg) - def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: + def draw_rects(self, rects: NDArray[np.intc | np.float32]) -> None: """Draw multiple outlined rectangles from an array. .. versionadded:: 13.5 @@ -610,9 +610,10 @@ def draw_rects(self, rects: NDArray[Union[np.intc, np.float32]]) -> None: elif rects.dtype == np.float32: _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) else: - raise TypeError(f"Array must be an np.intc or np.float32 type, got {rects.dtype}.") + msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}." + raise TypeError(msg) - def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None: + def draw_points(self, points: NDArray[np.intc | np.float32]) -> None: """Draw an array of points. .. versionadded:: 13.5 @@ -625,9 +626,10 @@ def draw_points(self, points: NDArray[Union[np.intc, np.float32]]) -> None: elif points.dtype == np.float32: _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) else: - raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.") + msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}." + raise TypeError(msg) - def draw_lines(self, points: NDArray[Union[np.intc, np.float32]]) -> None: + def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: """Draw a connected series of lines from an array. .. versionadded:: 13.5 @@ -640,16 +642,17 @@ def draw_lines(self, points: NDArray[Union[np.intc, np.float32]]) -> None: elif points.dtype == np.float32: _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1)) else: - raise TypeError(f"Array must be an np.intc or np.float32 type, got {points.dtype}.") + msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}." + raise TypeError(msg) @_required_version((2, 0, 18)) def geometry( self, - texture: Optional[Texture], + texture: Texture | None, xy: NDArray[np.float32], color: NDArray[np.uint8], uv: NDArray[np.float32], - indices: Optional[NDArray[Union[np.uint8, np.uint16, np.uint32]]] = None, + indices: NDArray[np.uint8 | np.uint16 | np.uint32] | None = None, ) -> None: """Render triangles from texture and vertex data. @@ -695,7 +698,7 @@ def geometry( def new_renderer( window: tcod.sdl.video.Window, *, - driver: Optional[int] = None, + driver: int | None = None, software: bool = False, vsync: bool = True, target_textures: bool = False, diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 7987081d..39bda95d 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -2,7 +2,7 @@ import enum import warnings -from typing import Any, Tuple +from typing import Any from tcod.loader import ffi, lib from tcod.sdl import _check, _get_error @@ -56,7 +56,7 @@ class _PowerState(enum.IntEnum): CHARGED = enum.auto() -def _get_power_info() -> Tuple[_PowerState, int, int]: +def _get_power_info() -> tuple[_PowerState, int, int]: buffer = ffi.new("int[2]") power_state = _PowerState(lib.SDL_GetPowerInfo(buffer, buffer + 1)) seconds_of_power = buffer[0] @@ -68,7 +68,7 @@ def _get_clipboard() -> str: """Return the text of the clipboard.""" text = str(ffi.string(lib.SDL_GetClipboardText()), encoding="utf-8") if not text: # Show the reason for an empty return, this should probably be logged instead. - warnings.warn(f"Return string is empty because: {_get_error()}") + warnings.warn(f"Return string is empty because: {_get_error()}", RuntimeWarning, stacklevel=2) return text diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 2ef4553e..4cf6a87b 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -10,7 +10,7 @@ import enum import sys -from typing import Any, Optional, Tuple +from typing import Any import numpy as np from numpy.typing import ArrayLike, NDArray @@ -98,9 +98,11 @@ class _TempSurface: def __init__(self, pixels: ArrayLike) -> None: self._array: NDArray[np.uint8] = np.ascontiguousarray(pixels, dtype=np.uint8) if len(self._array.shape) != 3: - raise TypeError(f"NumPy shape must be 3D [y, x, ch] (got {self._array.shape})") + msg = f"NumPy shape must be 3D [y, x, ch] (got {self._array.shape})" + raise TypeError(msg) if not (3 <= self._array.shape[2] <= 4): - raise TypeError(f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})") + msg = f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})" + raise TypeError(msg) self.p = ffi.gc( lib.SDL_CreateRGBSurfaceFrom( ffi.from_buffer("void*", self._array), @@ -122,11 +124,13 @@ class Window: def __init__(self, sdl_window_p: Any) -> None: if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): - raise TypeError( - "sdl_window_p must be %r type (was %r)." % (ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p)) + msg = "sdl_window_p must be {!r} type (was {!r}).".format( + ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p) ) + raise TypeError(msg) if not sdl_window_p: - raise TypeError("sdl_window_p can not be a null pointer.") + msg = "sdl_window_p can not be a null pointer." + raise TypeError(msg) self.p = sdl_window_p def __eq__(self, other: Any) -> bool: @@ -142,7 +146,7 @@ def set_icon(self, pixels: ArrayLike) -> None: lib.SDL_SetWindowIcon(self.p, surface.p) @property - def position(self) -> Tuple[int, int]: + def position(self) -> tuple[int, int]: """Get or set the (x, y) position of the window. This attribute can be set the move the window. @@ -153,12 +157,12 @@ def position(self) -> Tuple[int, int]: return xy[0], xy[1] @position.setter - def position(self, xy: Tuple[int, int]) -> None: + def position(self, xy: tuple[int, int]) -> None: x, y = xy lib.SDL_SetWindowPosition(self.p, x, y) @property - def size(self) -> Tuple[int, int]: + def size(self) -> tuple[int, int]: """Get or set the pixel (width, height) of the window client area. This attribute can be set to change the size of the window but the given size must be greater than (1, 1) or @@ -169,32 +173,33 @@ def size(self) -> Tuple[int, int]: return xy[0], xy[1] @size.setter - def size(self, xy: Tuple[int, int]) -> None: + def size(self, xy: tuple[int, int]) -> None: if any(i <= 0 for i in xy): - raise ValueError(f"Window size must be greater than zero, not {xy}") + msg = f"Window size must be greater than zero, not {xy}" + raise ValueError(msg) x, y = xy lib.SDL_SetWindowSize(self.p, x, y) @property - def min_size(self) -> Tuple[int, int]: + def min_size(self) -> tuple[int, int]: """Get or set this windows minimum client area.""" xy = ffi.new("int[2]") lib.SDL_GetWindowMinimumSize(self.p, xy, xy + 1) return xy[0], xy[1] @min_size.setter - def min_size(self, xy: Tuple[int, int]) -> None: + def min_size(self, xy: tuple[int, int]) -> None: lib.SDL_SetWindowMinimumSize(self.p, xy[0], xy[1]) @property - def max_size(self) -> Tuple[int, int]: + def max_size(self) -> tuple[int, int]: """Get or set this windows maximum client area.""" xy = ffi.new("int[2]") lib.SDL_GetWindowMaximumSize(self.p, xy, xy + 1) return xy[0], xy[1] @max_size.setter - def max_size(self, xy: Tuple[int, int]) -> None: + def max_size(self, xy: tuple[int, int]) -> None: lib.SDL_SetWindowMaximumSize(self.p, xy[0], xy[1]) @property @@ -242,7 +247,7 @@ def resizable(self, value: bool) -> None: lib.SDL_SetWindowResizable(self.p, value) @property - def border_size(self) -> Tuple[int, int, int, int]: + def border_size(self) -> tuple[int, int, int, int]: """Get the (top, left, bottom, right) size of the window decorations around the client area. If this fails or the window doesn't have decorations yet then the value will be (0, 0, 0, 0). @@ -283,7 +288,7 @@ def grab(self, value: bool) -> None: lib.SDL_SetWindowGrab(self.p, value) @property - def mouse_rect(self) -> Optional[Tuple[int, int, int, int]]: + def mouse_rect(self) -> tuple[int, int, int, int] | None: """Get or set the mouse confinement area when the window has mouse focus. Setting this will not automatically grab the cursor. @@ -295,7 +300,7 @@ def mouse_rect(self) -> Optional[Tuple[int, int, int, int]]: return (rect.x, rect.y, rect.w, rect.h) if rect else None @mouse_rect.setter - def mouse_rect(self, rect: Optional[Tuple[int, int, int, int]]) -> None: + def mouse_rect(self, rect: tuple[int, int, int, int] | None) -> None: _version_at_least((2, 0, 18)) _check(lib.SDL_SetWindowMouseRect(self.p, (rect,) if rect else ffi.NULL)) @@ -333,9 +338,9 @@ def new_window( width: int, height: int, *, - x: Optional[int] = None, - y: Optional[int] = None, - title: Optional[str] = None, + x: int | None = None, + y: int | None = None, + title: str | None = None, flags: int = 0, ) -> Window: """Initialize and return a new SDL Window. @@ -366,13 +371,13 @@ def new_window( return Window(_check_p(window_p)) -def get_grabbed_window() -> Optional[Window]: +def get_grabbed_window() -> Window | None: """Return the window which has input grab enabled, if any.""" sdl_window_p = lib.SDL_GetGrabbedWindow() return Window(sdl_window_p) if sdl_window_p else None -def screen_saver_allowed(allow: Optional[bool] = None) -> bool: +def screen_saver_allowed(allow: bool | None = None) -> bool: """Allow or prevent a screen saver from being displayed and return the current allowed status. If `allow` is `None` then only the current state is returned. diff --git a/tcod/tileset.py b/tcod/tileset.py index ac418f1b..9f13f691 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -15,7 +15,7 @@ import itertools from os import PathLike from pathlib import Path -from typing import Any, Iterable, Optional, Tuple, Union +from typing import Any, Iterable import numpy as np from numpy.typing import ArrayLike, NDArray @@ -42,7 +42,8 @@ def _claim(cls, cdata: Any) -> Tileset: """Return a new Tileset that owns the provided TCOD_Tileset* object.""" self = object.__new__(cls) if cdata == ffi.NULL: - raise RuntimeError("Tileset initialized with nullptr.") + msg = "Tileset initialized with nullptr." + raise RuntimeError(msg) self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete) return self @@ -63,7 +64,7 @@ def tile_height(self) -> int: return int(lib.TCOD_tileset_get_tile_height_(self._tileset_p)) @property - def tile_shape(self) -> Tuple[int, int]: + def tile_shape(self) -> tuple[int, int]: """Shape (height, width) of the tile in pixels.""" return self.tile_height, self.tile_width @@ -80,7 +81,7 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]: uint8. Note that most grey-scale tiles will only use the alpha channel and will usually have a solid white color channel. """ - tile: NDArray[np.uint8] = np.zeros(self.tile_shape + (4,), dtype=np.uint8) + tile: NDArray[np.uint8] = np.zeros((*self.tile_shape, 4), dtype=np.uint8) lib.TCOD_tileset_get_tile_( self._tileset_p, codepoint, @@ -88,7 +89,7 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]: ) return tile - def set_tile(self, codepoint: int, tile: Union[ArrayLike, NDArray[np.uint8]]) -> None: + def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: """Upload a tile into this array. Args: @@ -139,11 +140,11 @@ def set_tile(self, codepoint: int, tile: Union[ArrayLike, NDArray[np.uint8]]) -> """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: - full_tile: NDArray[np.uint8] = np.empty(self.tile_shape + (4,), dtype=np.uint8) + full_tile: NDArray[np.uint8] = np.empty((*self.tile_shape, 4), dtype=np.uint8) full_tile[:, :, :3] = 255 full_tile[:, :, 3] = tile return self.set_tile(codepoint, full_tile) - required = self.tile_shape + (4,) + required = (*self.tile_shape, 4) if tile.shape != required: note = "" if len(tile.shape) == 3 and tile.shape[2] == 3: @@ -151,12 +152,14 @@ def set_tile(self, codepoint: int, tile: Union[ArrayLike, NDArray[np.uint8]]) -> "\nNote: An RGB array is too ambiguous," " an alpha channel must be added to this array to divide the background/foreground areas." ) - raise ValueError(f"Tile shape must be {required} or {self.tile_shape}, got {tile.shape}.{note}") + msg = f"Tile shape must be {required} or {self.tile_shape}, got {tile.shape}.{note}" + raise ValueError(msg) lib.TCOD_tileset_set_tile_( self._tileset_p, codepoint, ffi.from_buffer("struct TCOD_ColorRGBA*", tile), ) + return None def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: """Render an RGBA array, using console with this tileset. @@ -170,7 +173,8 @@ def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: .. versionadded:: 11.9 """ if not console: - raise ValueError("'console' must not be the root console.") + msg = "'console' must not be the root console." + raise ValueError(msg) width = console.width * self.tile_width height = console.height * self.tile_height out: NDArray[np.uint8] = np.empty((height, width, 4), np.uint8) @@ -186,16 +190,15 @@ def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: ), lib.SDL_FreeSurface, ) - with surface_p: - with ffi.new("SDL_Surface**", surface_p) as surface_p_p: - _check( - lib.TCOD_tileset_render_to_surface( - self._tileset_p, - _console(console), - ffi.NULL, - surface_p_p, - ) + with surface_p, ffi.new("SDL_Surface**", surface_p) as surface_p_p: + _check( + lib.TCOD_tileset_render_to_surface( + self._tileset_p, + _console(console), + ffi.NULL, + surface_p_p, ) + ) return out def remap(self, codepoint: int, x: int, y: int = 0) -> None: @@ -256,7 +259,7 @@ def set_default(tileset: Tileset) -> None: lib.TCOD_set_default_tileset(tileset._tileset_p) -def load_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_height: int) -> Tileset: +def load_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: int) -> Tileset: """Return a new Tileset from a `.ttf` or `.otf` file. Same as :any:`set_truetype_font`, but returns a :any:`Tileset` instead. @@ -264,9 +267,7 @@ def load_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_he This function is provisional. The API may change. """ - path = Path(path) - if not path.exists(): - raise RuntimeError(f"File not found:\n\t{path.resolve()}") + path = Path(path).resolve(strict=True) cdata = lib.TCOD_load_truetype_font_(bytes(path), tile_width, tile_height) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) @@ -274,7 +275,7 @@ def load_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_he @deprecate("Accessing the default tileset is deprecated.") -def set_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_height: int) -> None: +def set_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: int) -> None: """Set the default tileset from a `.ttf` or `.otf` file. `path` is the file path for the font file. @@ -294,14 +295,12 @@ def set_truetype_font(path: Union[str, PathLike[str]], tile_width: int, tile_hei This function does not support contexts. Use :any:`load_truetype_font` instead. """ - path = Path(path) - if not path.exists(): - raise RuntimeError(f"File not found:\n\t{path.resolve()}") + path = Path(path).resolve(strict=True) if lib.TCOD_tileset_load_truetype_(bytes(path), tile_width, tile_height): raise RuntimeError(ffi.string(lib.TCOD_get_error())) -def load_bdf(path: Union[str, PathLike[str]]) -> Tileset: +def load_bdf(path: str | PathLike[str]) -> Tileset: """Return a new Tileset from a `.bdf` file. For the best results the font should be monospace, cell-based, and @@ -313,19 +312,15 @@ def load_bdf(path: Union[str, PathLike[str]]) -> Tileset: take effect when `tcod.console_init_root` is called. .. versionadded:: 11.10 - """ # noqa: E501 - path = Path(path) - if not path.exists(): - raise RuntimeError(f"File not found:\n\t{path.resolve()}") + """ + path = Path(path).resolve(strict=True) cdata = lib.TCOD_load_bdf(bytes(path)) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) return Tileset._claim(cdata) -def load_tilesheet( - path: Union[str, PathLike[str]], columns: int, rows: int, charmap: Optional[Iterable[int]] -) -> Tileset: +def load_tilesheet(path: str | PathLike[str], columns: int, rows: int, charmap: Iterable[int] | None) -> Tileset: """Return a new Tileset from a simple tilesheet image. `path` is the file path to a PNG file with the tileset. @@ -344,9 +339,7 @@ def load_tilesheet( .. versionadded:: 11.12 """ - path = Path(path) - if not path.exists(): - raise RuntimeError(f"File not found:\n\t{path.resolve()}") + path = Path(path).resolve(strict=True) mapping = [] if charmap is not None: mapping = list(itertools.islice(charmap, columns * rows)) From 26f24067302510a53d68b2da3da9238e106bff78 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 26 May 2023 23:57:31 -0700 Subject: [PATCH 0776/1101] Remove no-pep420 linter rule. --- pyproject.toml | 1 - scripts/generate_charmap_table.py | 2 -- scripts/get_release_description.py | 2 -- scripts/tag_release.py | 2 +- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4df86fb1..e1f8bead 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -158,7 +158,6 @@ select = [ "EXE", # flake8-executable "RET", # flake8-return "ICN", # flake8-import-conventions - "INP", # flake8-no-pep420 "PIE", # flake8-pie "PT", # flake8-pytest-style "SIM", # flake8-simplify diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 2e2931b5..0591cb9b 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -13,8 +13,6 @@ import tcod.tileset -# ruff: noqa: INP001 - def get_charmaps() -> Iterator[str]: """Return an iterator of the current character maps from tcod.tilest.""" diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 0a2dffb3..7d47d9f9 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -5,8 +5,6 @@ import re from pathlib import Path -# ruff: noqa: INP001 - TAG_BANNER = r"## \[[\w.]*\] - \d+-\d+-\d+\n" RE_BODY = re.compile(rf".*?{TAG_BANNER}(.*?){TAG_BANNER}", re.DOTALL) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 92e896fe..70b77fb7 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -10,7 +10,7 @@ import sys from pathlib import Path -# ruff: noqa: INP001, S603, S607 +# ruff: noqa: S603, S607 PROJECT_DIR = Path(__file__).parent.parent From f07e6847500cbbf42525e7387f622c346b7ea3d7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 00:25:49 -0700 Subject: [PATCH 0777/1101] Add PathLike support to more libtcodpy functions. Remove long deprecated bytes to Unicode conversion. --- CHANGELOG.md | 5 ++ tcod/image.py | 7 ++- tcod/libtcodpy.py | 130 +++++++++++++++++++++++++++++++--------------- 3 files changed, 99 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5730d8a..e280b173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Added PathLike support to more libtcodpy functions. + +### Removed +- `tcod.console_set_custom_font` can no longer take bytes. ## [15.0.3] - 2023-05-25 ### Deprecated diff --git a/tcod/image.py b/tcod/image.py index 85e315ea..ced114d7 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -288,13 +288,16 @@ def blit_2x( img_height, ) - def save_as(self, filename: str) -> None: + def save_as(self, filename: str | PathLike[str]) -> None: """Save the Image to a 32-bit .bmp or .png file. Args: filename (Text): File path to same this Image. + + .. versionchanged:: Unreleased + Added PathLike support. """ - lib.TCOD_image_save(self.image_c, filename.encode("utf-8")) + lib.TCOD_image_save(self.image_c, bytes(Path(filename))) @property def __array_interface__(self) -> dict[str, Any]: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index b06572ec..cb4d2c8a 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -5,8 +5,9 @@ import sys import threading import warnings +from os import PathLike from pathlib import Path -from typing import Any, AnyStr, Callable, Hashable, Iterable, Iterator, Sequence +from typing import Any, Callable, Hashable, Iterable, Iterator, Sequence import numpy as np from numpy.typing import NDArray @@ -954,7 +955,7 @@ def console_init_root( https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html""" ) def console_set_custom_font( - fontFile: AnyStr, + fontFile: str | PathLike[str], flags: int = FONT_LAYOUT_ASCII_INCOL, nb_char_horiz: int = 0, nb_char_vertic: int = 0, @@ -983,9 +984,12 @@ def console_set_custom_font( .. deprecated:: 11.13 Load fonts using :any:`tcod.tileset.load_tilesheet` instead. See :ref:`getting-started` for more info. + + .. versionchanged:: Unreleased + Added PathLike support. `fontFile` no longer takes bytes. """ - path = Path(_unicode(fontFile)).resolve(strict=True) - _check(lib.TCOD_console_set_custom_font(path, flags, nb_char_horiz, nb_char_vertic)) + fontFile = Path(fontFile).resolve(strict=True) + _check(lib.TCOD_console_set_custom_font(bytes(fontFile), flags, nb_char_horiz, nb_char_vertic)) @deprecate("Check `con.width` instead.") @@ -1780,7 +1784,7 @@ def console_new(w: int, h: int) -> tcod.console.Console: @deprecate("This loading method is no longer supported, use tcod.console_load_xp instead.") -def console_from_file(filename: str) -> tcod.console.Console: +def console_from_file(filename: str | PathLike[str]) -> tcod.console.Console: """Return a new console object from a filename. The file format is automatically determined. This can load REXPaint `.xp`, @@ -1795,9 +1799,12 @@ def console_from_file(filename: str) -> tcod.console.Console: Use :any:`tcod.console_load_xp` to load REXPaint consoles. Other formats are not actively supported. + + .. versionchanged:: Unreleased + Added PathLike support. """ - path = Path(filename).resolve(strict=True) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(bytes(path)))) + filename = Path(filename).resolve(strict=True) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(bytes(filename)))) @deprecate("Call the `Console.blit` method instead.") @@ -1966,76 +1973,106 @@ def console_fill_char(con: tcod.console.Console, arr: Sequence[int]) -> None: @deprecate("This format is not actively supported") -def console_load_asc(con: tcod.console.Console, filename: str) -> bool: +def console_load_asc(con: tcod.console.Console, filename: str | PathLike[str]) -> bool: """Update a console from a non-delimited ASCII `.asc` file. .. deprecated:: 12.7 This format is no longer supported. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return bool(lib.TCOD_console_load_asc(_console(con), filename.encode("utf-8"))) + filename = Path(filename).resolve(strict=True) + return bool(lib.TCOD_console_load_asc(_console(con), bytes(filename))) @deprecate("This format is not actively supported") -def console_save_asc(con: tcod.console.Console, filename: str) -> bool: +def console_save_asc(con: tcod.console.Console, filename: str | PathLike[str]) -> bool: """Save a console to a non-delimited ASCII `.asc` file. .. deprecated:: 12.7 This format is no longer supported. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return bool(lib.TCOD_console_save_asc(_console(con), filename.encode("utf-8"))) + return bool(lib.TCOD_console_save_asc(_console(con), bytes(Path(filename)))) @deprecate("This format is not actively supported") -def console_load_apf(con: tcod.console.Console, filename: str) -> bool: +def console_load_apf(con: tcod.console.Console, filename: str | PathLike[str]) -> bool: """Update a console from an ASCII Paint `.apf` file. .. deprecated:: 12.7 This format is no longer supported. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return bool(lib.TCOD_console_load_apf(_console(con), filename.encode("utf-8"))) + filename = Path(filename).resolve(strict=True) + return bool(lib.TCOD_console_load_apf(_console(con), bytes(filename))) @deprecate("This format is not actively supported") -def console_save_apf(con: tcod.console.Console, filename: str) -> bool: +def console_save_apf(con: tcod.console.Console, filename: str | PathLike[str]) -> bool: """Save a console to an ASCII Paint `.apf` file. .. deprecated:: 12.7 This format is no longer supported. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return bool(lib.TCOD_console_save_apf(_console(con), filename.encode("utf-8"))) + return bool(lib.TCOD_console_save_apf(_console(con), bytes(Path(filename)))) @deprecate("Use tcod.console.load_xp to load this file.") -def console_load_xp(con: tcod.console.Console, filename: str) -> bool: +def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> bool: """Update a console from a REXPaint `.xp` file. .. deprecated:: 11.18 Functions modifying console objects in-place are deprecated. Use :any:`tcod.console_from_xp` to load a Console from a file. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return bool(lib.TCOD_console_load_xp(_console(con), filename.encode("utf-8"))) + filename = Path(filename).resolve(strict=True) + return bool(lib.TCOD_console_load_xp(_console(con), bytes(filename))) @deprecate("Use tcod.console.save_xp to save this console.") -def console_save_xp(con: tcod.console.Console, filename: str, compress_level: int = 9) -> bool: - """Save a console to a REXPaint `.xp` file.""" - return bool(lib.TCOD_console_save_xp(_console(con), filename.encode("utf-8"), compress_level)) +def console_save_xp(con: tcod.console.Console, filename: str | PathLike[str], compress_level: int = 9) -> bool: + """Save a console to a REXPaint `.xp` file. + + .. versionchanged:: Unreleased + Added PathLike support. + """ + return bool(lib.TCOD_console_save_xp(_console(con), bytes(Path(filename)), compress_level)) @deprecate("Use tcod.console.load_xp to load this file.") -def console_from_xp(filename: str) -> tcod.console.Console: - """Return a single console from a REXPaint `.xp` file.""" - path = Path(filename).resolve(strict=True) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(bytes(path)))) +def console_from_xp(filename: str | PathLike[str]) -> tcod.console.Console: + """Return a single console from a REXPaint `.xp` file. + + .. versionchanged:: Unreleased + Added PathLike support. + """ + filename = Path(filename).resolve(strict=True) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(bytes(filename)))) @deprecate("Use tcod.console.load_xp to load this file.") def console_list_load_xp( - filename: str, + filename: str | PathLike[str], ) -> list[tcod.console.Console] | None: - """Return a list of consoles from a REXPaint `.xp` file.""" - path = Path(filename).resolve(strict=True) - tcod_list = lib.TCOD_console_list_from_xp(bytes(path)) + """Return a list of consoles from a REXPaint `.xp` file. + + .. versionchanged:: Unreleased + Added PathLike support. + """ + filename = Path(filename).resolve(strict=True) + tcod_list = lib.TCOD_console_list_from_xp(bytes(filename)) if tcod_list == ffi.NULL: return None try: @@ -2051,15 +2088,19 @@ def console_list_load_xp( @deprecate("Use tcod.console.save_xp to save these consoles.") def console_list_save_xp( console_list: Sequence[tcod.console.Console], - filename: str, + filename: str | PathLike[str], compress_level: int = 9, ) -> bool: - """Save a list of consoles to a REXPaint `.xp` file.""" + """Save a list of consoles to a REXPaint `.xp` file. + + .. versionchanged:: Unreleased + Added PathLike support. + """ tcod_list = lib.TCOD_list_new() try: for console in console_list: lib.TCOD_list_push(tcod_list, _console(console)) - return bool(lib.TCOD_console_list_save_xp(tcod_list, filename.encode("utf-8"), compress_level)) + return bool(lib.TCOD_console_list_save_xp(tcod_list, bytes(Path(filename)), compress_level)) finally: lib.TCOD_list_delete(tcod_list) @@ -2992,13 +3033,17 @@ def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: "This function may be removed in the future." " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio." ) -def image_load(filename: str) -> tcod.image.Image: +def image_load(filename: str | PathLike[str]) -> tcod.image.Image: """Load an image file into an Image instance and return it. Args: - filename (AnyStr): Path to a .bmp or .png image file. + filename: Path to a .bmp or .png image file. + + .. versionchanged:: Unreleased + Added PathLike support. """ - return tcod.image.Image._from_cdata(ffi.gc(lib.TCOD_image_load(_bytes(filename)), lib.TCOD_image_delete)) + filename = Path(filename).resolve(strict=True) + return tcod.image.Image._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(filename)), lib.TCOD_image_delete)) @pending_deprecate() @@ -3085,7 +3130,7 @@ def image_blit_2x( @pending_deprecate() -def image_save(image: tcod.image.Image, filename: str) -> None: +def image_save(image: tcod.image.Image, filename: str | PathLike[str]) -> None: image.save_as(filename) @@ -3382,8 +3427,8 @@ def mouse_get_status() -> Mouse: @pending_deprecate() -def namegen_parse(filename: str, random: tcod.random.Random | None = None) -> None: - lib.TCOD_namegen_parse(_bytes(filename), random or ffi.NULL) +def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | None = None) -> None: + lib.TCOD_namegen_parse(bytes(Path(filename)), random or ffi.NULL) @pending_deprecate() @@ -3583,10 +3628,10 @@ def _pycall_parser_error(msg: Any) -> None: @deprecate("Parser functions have been deprecated.") -def parser_run(parser: Any, filename: str, listener: Any = None) -> None: +def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) -> None: global _parser_listener if not listener: - lib.TCOD_parser_run(parser, _bytes(filename), ffi.NULL) + lib.TCOD_parser_run(parser, bytes(Path(filename)), ffi.NULL) return propagate_manager = _PropagateException() @@ -3605,7 +3650,7 @@ def parser_run(parser: Any, filename: str, listener: Any = None) -> None: with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, _bytes(filename), c_listener) + lib.TCOD_parser_run(parser, bytes(Path(filename)), c_listener) @deprecate("libtcod objects are deleted automatically.") @@ -4007,7 +4052,7 @@ def sys_get_renderer() -> int: # easy screenshots @deprecate("This function is not supported if contexts are being used.") -def sys_save_screenshot(name: str | None = None) -> None: +def sys_save_screenshot(name: str | PathLike[str] | None = None) -> None: """Save a screenshot to a file. By default this will automatically save screenshots in the working @@ -4022,6 +4067,9 @@ def sys_save_screenshot(name: str | None = None) -> None: .. deprecated:: 11.13 This function is not supported by contexts. Use :any:`Context.save_screenshot` instead. + + .. versionchanged:: Unreleased + Added PathLike support. """ lib.TCOD_sys_save_screenshot(bytes(Path(name)) if name is not None else ffi.NULL) From 684ae5386eaeadfb501d8369349b9b66024f2312 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 00:45:05 -0700 Subject: [PATCH 0778/1101] Cleanup pass of examples directory. Switch samples_tcod to use Path objects. --- .vscode/settings.json | 1 + examples/distribution/PyInstaller/main.py | 8 ++-- examples/distribution/cx_Freeze/main.py | 2 + examples/eventget.py | 6 +-- examples/framerate.py | 6 +-- examples/samples_tcod.py | 45 ++++++++++------------- examples/termbox/termbox.py | 17 +++++---- examples/termbox/termboxtest.py | 5 +-- examples/thread_jobs.py | 7 ++-- examples/ttf.py | 1 + 10 files changed, 48 insertions(+), 50 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ab94f02..18967232 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -145,6 +145,7 @@ "fmean", "fontx", "fonty", + "freetype", "frombuffer", "fullscreen", "fwidth", diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index b396d536..7f715351 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -3,20 +3,22 @@ # copyright and related or neighboring rights for the "hello world" PyInstaller # example script. This work is published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ -import os.path +"""PyInstaller main script example.""" import sys +from pathlib import Path import tcod WIDTH, HEIGHT = 80, 60 # The base directory, this is sys._MEIPASS when in one-file mode. -BASE_DIR = getattr(sys, "_MEIPASS", ".") +BASE_DIR = Path(getattr(sys, "_MEIPASS", ".")) -FONT_PATH = os.path.join(BASE_DIR, "data/terminal8x8_gs_ro.png") +FONT_PATH = BASE_DIR / "data/terminal8x8_gs_ro.png" def main() -> None: + """Entry point function.""" tileset = tcod.tileset.load_tilesheet(FONT_PATH, 16, 16, tcod.tileset.CHARMAP_CP437) with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index 6323407a..b1f51acb 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +"""cx_Freeze main script example.""" import tcod WIDTH, HEIGHT = 80, 60 @@ -6,6 +7,7 @@ def main() -> None: + """Entry point function.""" tileset = tcod.tileset.load_tilesheet("data/terminal8x8_gs_ro.png", 16, 16, tcod.tileset.CHARMAP_CP437) with tcod.context.new(columns=WIDTH, rows=HEIGHT, tileset=tileset) as context: while True: diff --git a/examples/eventget.py b/examples/eventget.py index 52e26c09..32a12658 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -3,8 +3,7 @@ # copyright and related or neighboring rights for this example. This work is # published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ -"""An demonstration of event handling using the tcod.event module. -""" +"""An demonstration of event handling using the tcod.event module.""" from typing import List, Set import tcod @@ -15,8 +14,7 @@ def main() -> None: - """Example program for tcod.event""" - + """Example program for tcod.event.""" event_log: List[str] = [] motion_desc = "" tcod.sdl.joystick.init() diff --git a/examples/framerate.py b/examples/framerate.py index 39ef6ee6..3a9e9f81 100755 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -3,8 +3,7 @@ # copyright and related or neighboring rights for this example. This work is # published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ -"""A system to control time since the original libtcod tools are deprecated. -""" +"""A system to control time since the original libtcod tools are deprecated.""" import statistics import time from collections import deque @@ -23,6 +22,7 @@ class Clock: """ def __init__(self) -> None: + """Initialize this object with empty data.""" self.last_time = time.perf_counter() # Last time this was synced. self.time_samples: Deque[float] = deque() # Delta time samples. self.max_samples = 64 # Number of fps samples to log. Can be changed. @@ -138,7 +138,7 @@ def main() -> None: context.convert_event(event) # Set tile coordinates for event. if isinstance(event, tcod.event.Quit): raise SystemExit() - elif isinstance(event, tcod.event.MouseWheel): + if isinstance(event, tcod.event.MouseWheel): desired_fps = max(1, desired_fps + event.y) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index bf0f7101..8b1d375d 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -7,12 +7,12 @@ import copy import math -import os import random import sys import time import warnings -from typing import Any, List +from pathlib import Path +from typing import Any import numpy as np from numpy.typing import NDArray @@ -21,19 +21,15 @@ import tcod.render import tcod.sdl.render +# ruff: noqa: S311 + if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. +DATA_DIR = Path(__file__).parent / "../libtcod/data" +"""Path of the samples data directory.""" -def get_data(path: str) -> str: - """Return the path to a resource in the libtcod data directory,""" - SCRIPT_DIR = os.path.dirname(__file__) - DATA_DIR = os.path.join(SCRIPT_DIR, "../libtcod/data") - assert os.path.exists(DATA_DIR), ( - "Data directory is missing," " did you forget to run `git submodule update --init`?" - ) - return os.path.join(DATA_DIR, path) - +assert DATA_DIR.exists(), "Data directory is missing, did you forget to run `git submodule update --init`?" WHITE = (255, 255, 255) GREY = (127, 127, 127) @@ -45,7 +41,7 @@ def get_data(path: str) -> str: SAMPLE_SCREEN_HEIGHT = 20 SAMPLE_SCREEN_X = 20 SAMPLE_SCREEN_Y = 10 -FONT = get_data("fonts/dejavu10x10_gs_tc.png") +FONT = DATA_DIR / "fonts/dejavu10x10_gs_tc.png" # Mutable global names. context: tcod.context.Context @@ -564,10 +560,9 @@ def draw_ui(self) -> None: 1, 1, "IJKL : move around\n" - "T : torch fx %s\n" - "W : light walls %s\n" - "+-: algo %s" - % ( + "T : torch fx {}\n" + "W : light walls {}\n" + "+-: algo {}".format( "on " if self.torch else "off", "on " if self.light_walls else "off", FOV_ALGO_NAMES[self.algo_num], @@ -1032,9 +1027,9 @@ class ImageSample(Sample): def __init__(self) -> None: self.name = "Image toolkit" - self.img = tcod.image_load(get_data("img/skull.png")) + self.img = tcod.image_load(DATA_DIR / "img/skull.png") self.img.set_key_color(BLACK) - self.circle = tcod.image_load(get_data("img/circle.png")) + self.circle = tcod.image_load(DATA_DIR / "img/circle.png") def on_draw(self) -> None: sample_console.clear() @@ -1068,7 +1063,7 @@ def __init__(self) -> None: self.motion = tcod.event.MouseMotion() self.mouse_left = self.mouse_middle = self.mouse_right = 0 - self.log: List[str] = [] + self.log: list[str] = [] def on_enter(self) -> None: tcod.mouse_move(320, 200) @@ -1141,15 +1136,15 @@ def __init__(self) -> None: self.curset = 0 self.delay = 0.0 - self.names: List[str] = [] - self.sets: List[str] = [] + self.names: list[str] = [] + self.sets: list[str] = [] def on_draw(self) -> None: if not self.sets: # parse all *.cfg files in data/namegen - for file in os.listdir(get_data("namegen")): - if file.find(".cfg") > 0: - tcod.namegen_parse(get_data(os.path.join("namegen", file))) + for file in (DATA_DIR / "namegen").iterdir(): + if file.suffix == ".cfg": + tcod.namegen_parse(file) # get the sets list self.sets = tcod.namegen_get_sets() print(self.sets) @@ -1254,7 +1249,7 @@ def on_enter(self) -> None: self.frac_t: float = RES_V - 1 self.abs_t: float = RES_V - 1 # light and current color of the tunnel texture - self.lights: List[Light] = [] + self.lights: list[Light] = [] self.tex_r = 0.0 self.tex_g = 0.0 self.tex_b = 0.0 diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index d97d9110..26ab2d7a 100755 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -1,5 +1,4 @@ -""" -Implementation of Termbox Python API in tdl. +"""Implementation of Termbox Python API in tdl. See README.md for details. """ @@ -18,10 +17,10 @@ class TermboxException(Exception): - def __init__(self, msg): + def __init__(self, msg) -> None: self.msg = msg - def __str__(self): + def __str__(self) -> str: return self.msg @@ -153,7 +152,7 @@ def __str__(self): class Event: - """Aggregate for Termbox Event structure""" + """Aggregate for Termbox Event structure.""" type = None ch = None @@ -169,10 +168,11 @@ def gettuple(self): class Termbox: - def __init__(self, width=132, height=60): + def __init__(self, width=132, height=60) -> None: global _instance if _instance: - raise TermboxException("It is possible to create only one instance of Termbox") + msg = "It is possible to create only one instance of Termbox" + raise TermboxException(msg) try: self.console = tdl.init(width, height) @@ -183,7 +183,7 @@ def __init__(self, width=132, height=60): _instance = self - def __del__(self): + def __del__(self) -> None: self.close() def __exit__(self, *args): # t, value, traceback): @@ -289,5 +289,6 @@ def poll_event(self): if e.type == "KEYDOWN": self.e.key = e.key return self.e.gettuple() + return None # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py index 9b43cfae..c0d57065 100755 --- a/examples/termbox/termboxtest.py +++ b/examples/termbox/termboxtest.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- encoding: utf-8 -*- import termbox @@ -17,8 +16,8 @@ def print_line(t, msg, y, fg, bg): t.change_cell(x + i, y, c, fg, bg) -class SelectBox(object): - def __init__(self, tb, choices, active=-1): +class SelectBox: + def __init__(self, tb, choices, active=-1) -> None: self.tb = tb self.active = active self.choices = choices diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index 82d88358..b935c91f 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -42,7 +42,7 @@ def test_fov_single(maps: List[tcod.map.Map]) -> None: def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: - for result in executor.map(test_fov, maps): + for _result in executor.map(test_fov, maps): pass @@ -57,7 +57,7 @@ def test_astar_single(maps: List[tcod.map.Map]) -> None: def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: - for result in executor.map(test_astar, maps): + for _result in executor.map(test_astar, maps): pass @@ -66,8 +66,7 @@ def run_test( single_func: Callable[[List[tcod.map.Map]], None], multi_func: Callable[[concurrent.futures.Executor, List[tcod.map.Map]], None], ) -> None: - """Run a function designed for a single thread and compare it to a threaded - version. + """Run a function designed for a single thread and compare it to a threaded version. This prints the results of these tests. """ diff --git a/examples/ttf.py b/examples/ttf.py index 7268109f..de134165 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -59,6 +59,7 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: def main() -> None: + """True-type font example script.""" console = tcod.Console(16, 12, order="F") with tcod.context.new( columns=console.width, From 74c7bd1bf13ed2d48dfaabcc625edaafbc34cfb5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 01:09:46 -0700 Subject: [PATCH 0779/1101] Fix missing imports in docstrings. These were being included in the module imports before, but I'd rather have them here or in conftest.py. --- tcod/path.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tcod/path.py b/tcod/path.py index e7b648c5..9e2439c8 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1131,6 +1131,7 @@ def traversal(self) -> NDArray[Any]: Example:: # This example demonstrates the purpose of the traversal array. + >>> import tcod.path >>> graph = tcod.path.SimpleGraph( ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, ... ) @@ -1240,6 +1241,7 @@ def resolve(self, goal: tuple[int, ...] | None = None) -> None: Example:: + >>> import tcod.path >>> graph = tcod.path.SimpleGraph( ... cost=np.ones((4, 4), np.int8), cardinal=2, diagonal=3, ... ) @@ -1300,6 +1302,7 @@ def path_from(self, index: tuple[int, ...]) -> NDArray[Any]: Example:: + >>> import tcod.path >>> cost = np.ones((5, 5), dtype=np.int8) >>> cost[:, 3:] = 0 >>> graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3) @@ -1344,6 +1347,7 @@ def path_to(self, index: tuple[int, ...]) -> NDArray[Any]: Example:: + >>> import tcod.path >>> graph = tcod.path.SimpleGraph( ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, ... ) From 3a9122731e2e9117c1e6e9f85f09e9679696f257 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 01:28:12 -0700 Subject: [PATCH 0780/1101] Remove setup.cfg, config moved to pyproject.toml --- pyproject.toml | 3 +++ setup.cfg | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index e1f8bead..81bab905 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,9 @@ Source = "https://github.com/libtcod/python-tcod" Tracker = "https://github.com/libtcod/python-tcod/issues" Forum = "https://github.com/libtcod/python-tcod/discussions" +[tool.distutils.bdist_wheel] +py-limited-api = "cp37" + [tool.setuptools_scm] write_to = "tcod/version.py" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9cc8e393..00000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -py-limited-api = cp36 - -[aliases] -test=pytest From 49ddf4c4527e921d41508b0a40a15390bad4b909 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 01:35:23 -0700 Subject: [PATCH 0781/1101] Update Codecov settings Allow comment, but only on coverage changes. --- codecov.yml => .codecov.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename codecov.yml => .codecov.yml (60%) diff --git a/codecov.yml b/.codecov.yml similarity index 60% rename from codecov.yml rename to .codecov.yml index dd31e005..79c5a87d 100644 --- a/codecov.yml +++ b/.codecov.yml @@ -3,6 +3,7 @@ coverage: project: default: target: auto - threshold: 0.1 + threshold: "0.1" base: auto -comment: off +comment: + require_changes: true From 1613480f5d8475fc515fbc8931ef25739c5fd1a7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 02:14:11 -0700 Subject: [PATCH 0782/1101] Fix or suppress all Ruff warnings in test scripts. --- pyproject.toml | 2 ++ tests/conftest.py | 15 +++++---- tests/test_console.py | 7 +++-- tests/test_libtcodpy.py | 70 +++++++++++++++++++---------------------- tests/test_noise.py | 13 +++++--- tests/test_parser.py | 15 +++++---- tests/test_random.py | 18 ++++++----- tests/test_sdl.py | 11 ++++--- tests/test_tcod.py | 33 ++++++++++--------- tests/test_testing.py | 10 ------ tests/test_tileset.py | 3 ++ 11 files changed, 103 insertions(+), 94 deletions(-) delete mode 100644 tests/test_testing.py diff --git a/pyproject.toml b/pyproject.toml index 81bab905..4c1c47a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,6 +174,8 @@ select = [ ignore = [ "E501", # line-too-long "S101", # assert + "S301", # suspicious-pickle-usage + "S311", # suspicious-non-cryptographic-random-usage "ANN101", # missing-type-self "ANN102", # missing-type-cls "D203", # one-blank-line-before-class diff --git a/tests/conftest.py b/tests/conftest.py index 0b3fb63b..163d0918 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,18 +1,21 @@ +"""Test directory configuration.""" import random import warnings -from typing import Any, Callable, Iterator, Union +from typing import Callable, Iterator, Union import pytest import tcod +# ruff: noqa: D103 -def pytest_addoption(parser: Any) -> None: + +def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") @pytest.fixture(scope="session", params=["SDL", "SDL2"]) -def session_console(request: Any) -> Iterator[tcod.console.Console]: +def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Console]: if request.config.getoption("--no-window"): pytest.skip("This test needs a rendering context.") FONT_FILE = "libtcod/terminal.png" @@ -27,7 +30,7 @@ def session_console(request: Any) -> Iterator[tcod.console.Console]: yield con -@pytest.fixture(scope="function") +@pytest.fixture() def console(session_console: tcod.console.Console) -> tcod.console.Console: console = session_console tcod.console_flush() @@ -81,6 +84,6 @@ def ch_latin1_str() -> str: "latin1_str", ] ) -def ch(request: Any) -> Callable[[], Union[int, str]]: - """Test with multiple types of ascii/latin1 characters""" +def ch(request: pytest.FixtureRequest) -> Callable[[], Union[int, str]]: + """Test with multiple types of ascii/latin1 characters.""" return globals()["ch_%s" % request.param]() # type: ignore diff --git a/tests/test_console.py b/tests/test_console.py index 790ddcfe..5ddc6f8e 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -1,12 +1,14 @@ +"""Tests for tcod.console.""" import pickle from pathlib import Path import numpy as np import pytest -from numpy import array import tcod +# ruff: noqa: D103 + @pytest.mark.filterwarnings("ignore:Directly access a consoles") @pytest.mark.filterwarnings("ignore:This function may be deprecated in the fu") @@ -95,7 +97,8 @@ def test_console_pickle_fortran() -> None: def test_console_repr() -> None: - array # Needed for eval. + from numpy import array # noqa: F401 # Used for eval + eval(repr(tcod.console.Console(10, 2))) diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 33cba999..5803c625 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,6 +1,7 @@ +"""Tests for the libtcodpy API.""" +from pathlib import Path from typing import Any, Callable, Iterator, List, Optional, Tuple, Union -import numpy import numpy as np import pytest from numpy.typing import NDArray @@ -8,6 +9,8 @@ import tcod import tcod as libtcodpy +# ruff: noqa: D103 + pytestmark = [ pytest.mark.filterwarnings("ignore::DeprecationWarning"), pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"), @@ -29,7 +32,7 @@ def test_credits(console: tcod.console.Console) -> None: libtcodpy.console_credits_reset() -def assert_char( +def assert_char( # noqa: PLR0913 console: tcod.console.Console, x: int, y: int, @@ -143,20 +146,20 @@ def test_console_blit(console: tcod.console.Console, offscreen: tcod.console.Con @pytest.mark.filterwarnings("ignore") -def test_console_asc_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmpdir: Any) -> None: +def test_console_asc_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmp_path: Path) -> None: libtcodpy.console_print(console, 0, 0, "test") - asc_file = tmpdir.join("test.asc").strpath + asc_file = tmp_path / "test.asc" assert libtcodpy.console_save_asc(console, asc_file) assert libtcodpy.console_load_asc(offscreen, asc_file) assertConsolesEqual(console, offscreen) @pytest.mark.filterwarnings("ignore") -def test_console_apf_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmpdir: Any) -> None: +def test_console_apf_read_write(console: tcod.console.Console, offscreen: tcod.console.Console, tmp_path: Path) -> None: libtcodpy.console_print(console, 0, 0, "test") - apf_file = tmpdir.join("test.apf").strpath + apf_file = tmp_path / "test.apf" assert libtcodpy.console_save_apf(console, apf_file) assert libtcodpy.console_load_apf(offscreen, apf_file) assertConsolesEqual(console, offscreen) @@ -176,14 +179,14 @@ def test_console_rexpaint_load_test_file(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") def test_console_rexpaint_save_load( console: tcod.console.Console, - tmpdir: Any, + tmp_path: Path, ch: int, fg: Tuple[int, int, int], bg: Tuple[int, int, int], ) -> None: libtcodpy.console_print(console, 0, 0, "test") libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) - xp_file = tmpdir.join("test.xp").strpath + xp_file = tmp_path / "test.xp" assert libtcodpy.console_save_xp(console, xp_file, 1) xp_console = libtcodpy.console_from_xp(xp_file) assert xp_console @@ -193,12 +196,12 @@ def test_console_rexpaint_save_load( @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmpdir: Any) -> None: +def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path: Path) -> None: con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) libtcodpy.console_print(con1, 0, 0, "hello") libtcodpy.console_print(con2, 0, 0, "world") - xp_file = tmpdir.join("test.xp").strpath + xp_file = tmp_path / "test.xp" assert libtcodpy.console_list_save_xp([con1, con2], xp_file, 1) loaded_consoles = libtcodpy.console_list_load_xp(xp_file) assert loaded_consoles @@ -252,7 +255,7 @@ def test_console_fill(console: tcod.console.Console) -> None: def test_console_fill_numpy(console: tcod.console.Console) -> None: width = libtcodpy.console_get_width(console) height = libtcodpy.console_get_height(console) - fill: NDArray[np.intc] = numpy.zeros((height, width), dtype=np.intc) + fill: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) for y in range(height): fill[y, :] = y % 256 @@ -261,9 +264,9 @@ def test_console_fill_numpy(console: tcod.console.Console) -> None: libtcodpy.console_fill_char(console, fill) # type: ignore # verify fill - bg: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) - fg: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) - ch: NDArray[np.intc] = numpy.zeros((height, width), dtype=numpy.intc) + bg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) + fg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) + ch: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) for y in range(height): for x in range(width): bg[y, x] = libtcodpy.console_get_char_background(console, x, y)[0] @@ -291,7 +294,7 @@ def test_console_buffer(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore:Console array attributes perform better") def test_console_buffer_error(console: tcod.console.Console) -> None: buffer = libtcodpy.ConsoleBuffer(0, 0) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r".*Destination console has an incorrect size."): buffer.blit(console) @@ -322,8 +325,8 @@ def test_sys_time(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_sys_screenshot(console: tcod.console.Console, tmpdir: Any) -> None: - libtcodpy.sys_save_screenshot(tmpdir.join("test.png").strpath) +def test_sys_screenshot(console: tcod.console.Console, tmp_path: Path) -> None: + libtcodpy.sys_save_screenshot(tmp_path / "test.png") @pytest.mark.filterwarnings("ignore") @@ -333,7 +336,7 @@ def test_sys_custom_render(console: tcod.console.Console) -> None: escape = [] - def sdl_callback(sdl_surface: Any) -> None: + def sdl_callback(sdl_surface: object) -> None: escape.append(True) libtcodpy.sys_register_SDL_renderer(sdl_callback) @@ -342,7 +345,7 @@ def sdl_callback(sdl_surface: Any) -> None: @pytest.mark.filterwarnings("ignore") -def test_image(console: tcod.console.Console, tmpdir: Any) -> None: +def test_image(console: tcod.console.Console, tmp_path: Path) -> None: img = libtcodpy.image_new(16, 16) libtcodpy.image_clear(img, (0, 0, 0)) libtcodpy.image_invert(img) @@ -360,7 +363,7 @@ def test_image(console: tcod.console.Console, tmpdir: Any) -> None: libtcodpy.image_blit(img, console, 0, 0, libtcodpy.BKGND_SET, 1, 1, 0) libtcodpy.image_blit_rect(img, console, 0, 0, 16, 16, libtcodpy.BKGND_SET) libtcodpy.image_blit_2x(img, console, 0, 0) - libtcodpy.image_save(img, tmpdir.join("test.png").strpath) + libtcodpy.image_save(img, tmp_path / "test.png") libtcodpy.image_delete(img) img = libtcodpy.image_from_console(console) @@ -385,14 +388,12 @@ def test_clipboard(console: tcod.console.Console, sample: str) -> None: # arguments to test with and the results expected from these arguments LINE_ARGS = (-5, 0, 5, 10) EXCLUSIVE_RESULTS = [(-4, 1), (-3, 2), (-2, 3), (-1, 4), (0, 5), (1, 6), (2, 7), (3, 8), (4, 9), (5, 10)] -INCLUSIVE_RESULTS = [(-5, 0)] + EXCLUSIVE_RESULTS +INCLUSIVE_RESULTS = [(-5, 0), *EXCLUSIVE_RESULTS] @pytest.mark.filterwarnings("ignore") def test_line_step() -> None: - """ - libtcodpy.line_init and libtcodpy.line_step - """ + """libtcodpy.line_init and libtcodpy.line_step.""" libtcodpy.line_init(*LINE_ARGS) for expected_xy in EXCLUSIVE_RESULTS: assert libtcodpy.line_step() == expected_xy @@ -401,9 +402,7 @@ def test_line_step() -> None: @pytest.mark.filterwarnings("ignore") def test_line() -> None: - """ - tests normal use, lazy evaluation, and error propagation - """ + """Tests normal use, lazy evaluation, and error propagation.""" # test normal results test_result: List[Tuple[int, int]] = [] @@ -427,17 +426,13 @@ def return_false(x: int, y: int) -> bool: @pytest.mark.filterwarnings("ignore") def test_line_iter() -> None: - """ - libtcodpy.line_iter - """ + """libtcodpy.line_iter.""" assert list(libtcodpy.line_iter(*LINE_ARGS)) == INCLUSIVE_RESULTS @pytest.mark.filterwarnings("ignore") def test_bsp() -> None: - """ - commented out statements work in libtcod-cffi - """ + """Commented out statements work in libtcod-cffi.""" bsp = libtcodpy.bsp_new_with_size(0, 0, 64, 64) repr(bsp) # test __repr__ on leaf libtcodpy.bsp_resize(bsp, 0, 0, 32, 32) @@ -479,7 +474,7 @@ def test_bsp() -> None: libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0) # cover bsp_traverse - def traverse(node: tcod.bsp.BSP, user_data: Any) -> None: + def traverse(node: tcod.bsp.BSP, user_data: object) -> None: return None libtcodpy.bsp_traverse_pre_order(bsp, traverse) @@ -498,9 +493,10 @@ def traverse(node: tcod.bsp.BSP, user_data: Any) -> None: @pytest.mark.filterwarnings("ignore") def test_map() -> None: - map = libtcodpy.map_new(16, 16) - assert libtcodpy.map_get_width(map) == 16 - assert libtcodpy.map_get_height(map) == 16 + WIDTH, HEIGHT = 13, 17 + map = libtcodpy.map_new(WIDTH, HEIGHT) + assert libtcodpy.map_get_width(map) == WIDTH + assert libtcodpy.map_get_height(map) == HEIGHT libtcodpy.map_copy(map, map) libtcodpy.map_clear(map) libtcodpy.map_set_properties(map, 0, 0, True, True) diff --git a/tests/test_noise.py b/tests/test_noise.py index 2067b852..6a653f06 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -1,3 +1,4 @@ +"""Tests for the tcod.noise module.""" import copy import pickle @@ -6,6 +7,8 @@ import tcod +# ruff: noqa: D103 + @pytest.mark.parametrize("implementation", tcod.noise.Implementation) @pytest.mark.parametrize("algorithm", tcod.noise.Algorithm) @@ -28,7 +31,7 @@ def test_noise_class( octaves=octaves, ) # cover attributes - assert noise.dimensions == 2 + assert noise.dimensions == 2 # noqa: PLR2004 noise.algorithm = noise.algorithm noise.implementation = noise.implementation noise.octaves = noise.octaves @@ -57,14 +60,14 @@ def test_noise_samples() -> None: def test_noise_errors() -> None: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"dimensions must be in range"): tcod.noise.Noise(0) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"-1 is not a valid implementation"): tcod.noise.Noise(1, implementation=-1) noise = tcod.noise.Noise(2) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"mgrid.shape\[0\] must equal self.dimensions"): noise.sample_mgrid(np.mgrid[:2, :2, :2]) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"len\(ogrid\) must equal self.dimensions"): noise.sample_ogrid(np.ogrid[:2, :2, :2]) with pytest.raises(IndexError): noise[0, 0, 0, 0, 0] diff --git a/tests/test_parser.py b/tests/test_parser.py index 9a1dad7f..ce200166 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,10 +1,13 @@ -import os +"""Test old libtcodpy parser.""" +from pathlib import Path from typing import Any import pytest import tcod as libtcod +# ruff: noqa: D103 + @pytest.mark.filterwarnings("ignore") def test_parser() -> None: @@ -27,7 +30,7 @@ def test_parser() -> None: # default listener print("***** Default listener *****") - libtcod.parser_run(parser, os.path.join("libtcod", "data", "cfg", "sample.cfg")) + libtcod.parser_run(parser, Path("libtcod/data/cfg/sample.cfg")) print("bool_field : ", libtcod.parser_get_bool_property(parser, "myStruct.bool_field")) print("char_field : ", libtcod.parser_get_char_property(parser, "myStruct.char_field")) print("int_field : ", libtcod.parser_get_int_property(parser, "myStruct.int_field")) @@ -46,7 +49,7 @@ def test_parser() -> None: print("***** Custom listener *****") class MyListener: - def new_struct(self, struct: Any, name: str) -> bool: + def new_struct(self, struct: Any, name: str) -> bool: # noqa: ANN401 print("new structure type", libtcod.struct_get_name(struct), " named ", name) return True @@ -54,7 +57,7 @@ def new_flag(self, name: str) -> bool: print("new flag named ", name) return True - def new_property(self, name: str, typ: int, value: Any) -> bool: + def new_property(self, name: str, typ: int, value: Any) -> bool: # noqa: ANN401 type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"] type_name = type_names[typ & 0xFF] if typ & libtcod.TYPE_LIST: @@ -62,7 +65,7 @@ def new_property(self, name: str, typ: int, value: Any) -> bool: print("new property named ", name, " type ", type_name, " value ", value) return True - def end_struct(self, struct: Any, name: str) -> bool: + def end_struct(self, struct: Any, name: str) -> bool: # noqa: ANN401 print("end structure type", libtcod.struct_get_name(struct), " named ", name) return True @@ -70,7 +73,7 @@ def error(self, msg: str) -> bool: print("error : ", msg) return True - libtcod.parser_run(parser, os.path.join("libtcod", "data", "cfg", "sample.cfg"), MyListener()) + libtcod.parser_run(parser, Path("libtcod/data/cfg/sample.cfg"), MyListener()) if __name__ == "__main__": diff --git a/tests/test_random.py b/tests/test_random.py index ce5875e3..c56c6678 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,14 +1,19 @@ +"""Test random number generators.""" import copy -import pathlib import pickle +from pathlib import Path import tcod +# ruff: noqa: D103 + +SCRIPT_DIR = Path(__file__).parent + def test_tcod_random() -> None: rand = tcod.random.Random(tcod.random.COMPLEMENTARY_MULTIPLY_WITH_CARRY) - assert 0 <= rand.randint(0, 100) <= 100 - assert 0 <= rand.uniform(0, 100) <= 100 + assert 0 <= rand.randint(0, 100) <= 100 # noqa: PLR2004 + assert 0 <= rand.uniform(0, 100) <= 100 # noqa: PLR2004 rand.guass(0, 1) rand.inverse_guass(0, 1) @@ -30,7 +35,6 @@ def test_tcod_random_pickle() -> None: def test_load_rng_v13_1() -> None: - with open(pathlib.Path(__file__).parent / "data/random_v13.pkl", "rb") as f: - rand: tcod.random.Random = pickle.load(f) - assert rand.randint(0, 0xFFFF) == 56422 - assert rand.randint(0, 0xFFFF) == 15795 + rand: tcod.random.Random = pickle.loads((SCRIPT_DIR / "data/random_v13.pkl").read_bytes()) + assert rand.randint(0, 0xFFFF) == 56422 # noqa: PLR2004 + assert rand.randint(0, 0xFFFF) == 15795 # noqa: PLR2004 diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 84dcf3a7..b7bf8571 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -1,3 +1,4 @@ +"""Test SDL specific features.""" import contextlib import sys @@ -9,6 +10,8 @@ import tcod.sdl.sys import tcod.sdl.video +# ruff: noqa: D103 + def test_sdl_window() -> None: assert tcod.sdl.video.get_grabbed_window() is None @@ -22,14 +25,14 @@ def test_sdl_window() -> None: assert window.title == sys.argv[0] window.title = "Title" assert window.title == "Title" - assert window.opacity == 1.0 + assert window.opacity == 1.0 # noqa: PLR2004 window.position = window.position window.fullscreen = window.fullscreen window.resizable = window.resizable window.size = window.size window.min_size = window.min_size window.max_size = window.max_size - window.border_size + window.border_size # noqa: B018 window.set_icon(np.zeros((32, 32, 3), dtype=np.uint8)) with pytest.raises(TypeError): window.set_icon(np.zeros((32, 32, 5), dtype=np.uint8)) @@ -84,9 +87,9 @@ def test_sdl_render_bad_types() -> None: def test_sdl_audio_device() -> None: with contextlib.closing(tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True)) as device: - assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 + assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 # noqa: PLR2004 assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape == (4, 2) - assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 + assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 # noqa: PLR2004 device.paused = False device.paused = True assert device.queued_samples == 0 diff --git a/tests/test_tcod.py b/tests/test_tcod.py index d3216e02..5c731f48 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -1,3 +1,4 @@ +"""Tests for newer tcod API.""" import copy import pickle from typing import Any, NoReturn @@ -8,15 +9,15 @@ import tcod +# ruff: noqa: D103 -def raise_Exception(*args: Any) -> NoReturn: - raise RuntimeError("testing exception") + +def raise_Exception(*args: object) -> NoReturn: + raise RuntimeError("testing exception") # noqa: TRY003, EM101 def test_line_error() -> None: - """ - test exception propagation - """ + """Test exception propagation.""" with pytest.raises(RuntimeError): tcod.line(0, 0, 10, 10, py_callback=raise_Exception) @@ -24,9 +25,7 @@ def test_line_error() -> None: @pytest.mark.filterwarnings("ignore:Iterate over nodes using") @pytest.mark.filterwarnings("ignore:Use pre_order method instead of walk.") def test_tcod_bsp() -> None: - """ - test tcod additions to BSP - """ + """Test tcod additions to BSP.""" bsp = tcod.bsp.BSP(0, 0, 32, 32) assert bsp.level == 0 @@ -45,7 +44,7 @@ def test_tcod_bsp() -> None: # test that operations on deep BSP nodes preserve depth sub_bsp = bsp.children[0] sub_bsp.split_recursive(3, 2, 2, 1, 1) - assert sub_bsp.children[0].level == 2 + assert sub_bsp.children[0].level == 2 # noqa: PLR2004 # cover find_node method assert bsp.find_node(0, 0) @@ -112,7 +111,7 @@ def test_color_class() -> None: assert tcod.white * 1 == tcod.white assert tcod.white * tcod.black == tcod.black assert tcod.white - tcod.white == tcod.black - assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) + assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) # noqa: RUF005 color = tcod.Color() color.r = 1 @@ -129,17 +128,17 @@ def test_path_numpy(dtype: DTypeLike) -> None: astar = tcod.path.AStar(map_np, 0) astar = pickle.loads(pickle.dumps(astar)) # test pickle astar = tcod.path.AStar(astar.cost, 0) # use existing cost attribute - assert len(astar.get_path(0, 0, 5, 5)) == 10 + assert len(astar.get_path(0, 0, 5, 5)) == 10 # noqa: PLR2004 dijkstra = tcod.path.Dijkstra(map_np, 0) dijkstra.set_goal(0, 0) - assert len(dijkstra.get_path(5, 5)) == 10 + assert len(dijkstra.get_path(5, 5)) == 10 # noqa: PLR2004 repr(dijkstra) # cover __repr__ methods # cover errors - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"Array must have a 2d shape, shape is \(3, 3, 3\)"): tcod.path.AStar(np.ones((3, 3, 3), dtype=dtype)) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"dtype must be one of dict_keys"): tcod.path.AStar(np.ones((2, 2), dtype=np.float64)) @@ -158,7 +157,7 @@ def test_key_repr() -> None: Key = tcod.Key key = Key(vk=1, c=2, shift=True) assert key.vk == 1 - assert key.c == 2 + assert key.c == 2 # noqa: PLR2004 assert key.shift key_copy = eval(repr(key)) assert key.vk == key_copy.vk @@ -193,8 +192,8 @@ def test_context() -> None: with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2) as context: console = tcod.Console(*context.recommended_console_size()) context.present(console) - context.sdl_window_p - context.renderer_type + assert context.sdl_window_p is not None + assert context.renderer_type >= 0 context.change_tileset(tcod.tileset.Tileset(16, 16)) context.pixel_to_tile(0, 0) context.pixel_to_subtile(0, 0) diff --git a/tests/test_testing.py b/tests/test_testing.py deleted file mode 100644 index 92604ae2..00000000 --- a/tests/test_testing.py +++ /dev/null @@ -1,10 +0,0 @@ -import os - -curdir = os.path.dirname(__file__) - -FONT_FILE = os.path.join(curdir, "data/fonts/consolas10x10_gs_tc.png") - -# def test_console(): -# libtcod.console_set_custom_font(FONT_FILE, libtcod.FONT_LAYOUT_TCOD) -# libtcod.console_init_root(40, 30, 'test', False, libtcod.RENDERER_SDL) -# libtcod.console_flush() diff --git a/tests/test_tileset.py b/tests/test_tileset.py index 36a3de58..c0c24c75 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -1,5 +1,8 @@ +"""Test for tcod.tileset module.""" import tcod +# ruff: noqa: D103 + def test_proc_block_elements() -> None: tileset = tcod.tileset.Tileset(8, 8) From 9cb25f75dc95046bffeb079c535a535e7266839d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 06:22:31 -0700 Subject: [PATCH 0783/1101] Skip DLL copy if it already exists at the destination. Should fix minor crashes when creating development installs. --- build_sdl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build_sdl.py b/build_sdl.py index 2b7c19e7..ef3dd4be 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -291,7 +291,10 @@ def get_cdef() -> str: library_dirs.append(str(SDL2_LIB_DIR)) SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BIT_SIZE]) SDL2_LIB_DEST.mkdir(exist_ok=True) - shutil.copy(SDL2_LIB_DIR / "SDL2.dll", SDL2_LIB_DEST) + SDL2_LIB_DEST_FILE = SDL2_LIB_DEST / "SDL2.dll" + SDL2_LIB_FILE = SDL2_LIB_DIR / "SDL2.dll" + if not SDL2_LIB_DEST_FILE.exists() or SDL2_LIB_FILE.read_bytes() != SDL2_LIB_DEST_FILE.read_bytes(): + shutil.copy(SDL2_LIB_FILE, SDL2_LIB_DEST_FILE) # Link to the SDL2 framework on MacOS. # Delocate will bundle the binaries in a later step. From c6967a8cffd198c6f9e3e7c35ec11290f527bf02 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 11:44:41 -0700 Subject: [PATCH 0784/1101] More libtcodpy deprecation. Update samples to work with namespaces a little better. Add missing SDL mouse feature. --- CHANGELOG.md | 7 +++ examples/samples_tcod.py | 108 ++++++++++++++++--------------- tcod/image.py | 5 ++ tcod/libtcodpy.py | 133 ++++++++++++++++++++++----------------- tcod/sdl/mouse.py | 21 ++++++- 5 files changed, 164 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e280b173..5831f695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,17 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ### Added - Added PathLike support to more libtcodpy functions. +- New `tcod.sdl.mouse.show` function for querying or setting mouse visibility. + +### Deprecated +- Deprecated the libtcodpy functions for images and noise generators. ### Removed - `tcod.console_set_custom_font` can no longer take bytes. +### Fixed +- Fix `tcod.sdl.mouse.warp_in_window` function. + ## [15.0.3] - 2023-05-25 ### Deprecated - Deprecated all libtcod color constants. Replace these with your own manually defined colors. diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 8b1d375d..35e1832d 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -18,7 +18,12 @@ from numpy.typing import NDArray import tcod +import tcod.constants +import tcod.event +import tcod.libtcodpy +import tcod.noise import tcod.render +import tcod.sdl.mouse import tcod.sdl.render # ruff: noqa: S311 @@ -48,7 +53,7 @@ tileset: tcod.tileset.Tileset console_render: tcod.render.SDLConsoleRender # Optional SDL renderer. sample_minimap: tcod.sdl.render.Texture # Optional minimap texture. -root_console = tcod.Console(80, 50, order="F") +root_console = tcod.console.Console(80, 50, order="F") sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F") cur_sample = 0 # Current selected sample. frame_times = [time.perf_counter()] @@ -191,7 +196,7 @@ def __init__(self) -> None: "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", fg=WHITE, bg=None, - alignment=tcod.CENTER, + alignment=tcod.constants.CENTER, ) def on_enter(self) -> None: @@ -245,8 +250,8 @@ class LineDrawingSample(Sample): def __init__(self) -> None: self.name = "Line drawing" - self.mk_flag = tcod.BKGND_SET - self.bk_flag = tcod.BKGND_SET + self.mk_flag = tcod.constants.BKGND_SET + self.bk_flag = tcod.constants.BKGND_SET self.bk = tcod.console.Console(sample_console.width, sample_console.height, order="F") # initialize the colored background @@ -291,7 +296,7 @@ def on_draw(self) -> None: yd = int(sample_console.height // 2 - sin_angle * sample_console.width // 2) # draw the line # in python the easiest way is to use the line iterator - for x, y in tcod.line_iter(xo, yo, xd, yd): + for x, y in tcod.los.bresenham((xo, yo), (xd, yd)).tolist(): if 0 <= x < sample_console.width and 0 <= y < sample_console.height: tcod.console_set_char_background(sample_console, x, y, LIGHT_BLUE, self.bk_flag) sample_console.print( @@ -359,10 +364,10 @@ def __init__(self) -> None: self.dy = 0.0 self.octaves = 4.0 self.zoom = 3.0 - self.hurst = tcod.NOISE_DEFAULT_HURST - self.lacunarity = tcod.NOISE_DEFAULT_LACUNARITY + self.hurst = tcod.libtcodpy.NOISE_DEFAULT_HURST + self.lacunarity = tcod.libtcodpy.NOISE_DEFAULT_LACUNARITY self.noise = self.get_noise() - self.img = tcod.image_new(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2) + self.img = tcod.image.Image(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2) @property def algorithm(self) -> int: @@ -537,7 +542,7 @@ def __init__(self) -> None: self.player_y = 10 self.torch = False self.light_walls = True - self.algo_num = tcod.FOV_SYMMETRIC_SHADOWCAST + self.algo_num = tcod.constants.FOV_SYMMETRIC_SHADOWCAST self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering. map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) @@ -668,17 +673,19 @@ def __init__(self) -> None: self.busy = 0.0 self.oldchar = " " - self.map = tcod.map_new(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + self.map = tcod.map.Map(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] == " ": # ground - tcod.map_set_properties(self.map, x, y, True, True) + self.map.walkable[y, x] = True + self.map.transparent[y, x] = True elif SAMPLE_MAP[x, y] == "=": # window - tcod.map_set_properties(self.map, x, y, True, False) - self.path = tcod.path_new_using_map(self.map) - self.dijkstra = tcod.dijkstra_new(self.map) + self.map.walkable[y, x] = False + self.map.transparent[y, x] = True + self.path = tcod.path.AStar(self.map) + self.dijkstra = tcod.path.Dijkstra(self.map) def on_enter(self) -> None: # we draw the foreground only the first time. @@ -901,43 +908,41 @@ def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: left, right = node.children node.x = min(left.x, right.x) node.y = min(left.y, right.y) - node.w = max(left.x + left.w, right.x + right.w) - node.x - node.h = max(left.y + left.h, right.y + right.h) - node.y + node.width = max(left.x + left.width, right.x + right.width) - node.x + node.height = max(left.y + left.height, right.y + right.height) - node.y # create a corridor between the two lower nodes if node.horizontal: # vertical corridor - if left.x + left.w - 1 < right.x or right.x + right.w - 1 < left.x: + if left.x + left.width - 1 < right.x or right.x + right.width - 1 < left.x: # no overlapping zone. we need a Z shaped corridor - x1 = random.randint(left.x, left.x + left.w - 1) - x2 = random.randint(right.x, right.x + right.w - 1) - y = random.randint(left.y + left.h, right.y) + x1 = random.randint(left.x, left.x + left.width - 1) + x2 = random.randint(right.x, right.x + right.width - 1) + y = random.randint(left.y + left.height, right.y) vline_up(bsp_map, x1, y - 1) hline(bsp_map, x1, y, x2) vline_down(bsp_map, x2, y + 1) else: # straight vertical corridor min_x = max(left.x, right.x) - max_x = min(left.x + left.w - 1, right.x + right.w - 1) + max_x = min(left.x + left.width - 1, right.x + right.width - 1) x = random.randint(min_x, max_x) vline_down(bsp_map, x, right.y) vline_up(bsp_map, x, right.y - 1) + elif left.y + left.height - 1 < right.y or right.y + right.height - 1 < left.y: # horizontal corridor + # no overlapping zone. we need a Z shaped corridor + y1 = random.randint(left.y, left.y + left.height - 1) + y2 = random.randint(right.y, right.y + right.height - 1) + x = random.randint(left.x + left.width, right.x) + hline_left(bsp_map, x - 1, y1) + vline(bsp_map, x, y1, y2) + hline_right(bsp_map, x + 1, y2) else: - # horizontal corridor - if left.y + left.h - 1 < right.y or right.y + right.h - 1 < left.y: - # no overlapping zone. we need a Z shaped corridor - y1 = random.randint(left.y, left.y + left.h - 1) - y2 = random.randint(right.y, right.y + right.h - 1) - x = random.randint(left.x + left.w, right.x) - hline_left(bsp_map, x - 1, y1) - vline(bsp_map, x, y1, y2) - hline_right(bsp_map, x + 1, y2) - else: - # straight horizontal corridor - min_y = max(left.y, right.y) - max_y = min(left.y + left.h - 1, right.y + right.h - 1) - y = random.randint(min_y, max_y) - hline_left(bsp_map, right.x - 1, y) - hline_right(bsp_map, right.x, y) + # straight horizontal corridor + min_y = max(left.y, right.y) + max_y = min(left.y + left.height - 1, right.y + right.height - 1) + y = random.randint(min_y, max_y) + hline_left(bsp_map, right.x - 1, y) + hline_right(bsp_map, right.x, y) class BSPSample(Sample): @@ -1027,9 +1032,9 @@ class ImageSample(Sample): def __init__(self) -> None: self.name = "Image toolkit" - self.img = tcod.image_load(DATA_DIR / "img/skull.png") + self.img = tcod.image.Image.from_file(DATA_DIR / "img/skull.png") self.img.set_key_color(BLACK) - self.circle = tcod.image_load(DATA_DIR / "img/circle.png") + self.circle = tcod.image.Image.from_file(DATA_DIR / "img/circle.png") def on_draw(self) -> None: sample_console.clear() @@ -1066,8 +1071,10 @@ def __init__(self) -> None: self.log: list[str] = [] def on_enter(self) -> None: - tcod.mouse_move(320, 200) - tcod.mouse_show_cursor(True) + sdl_window = context.sdl_window + if sdl_window: + tcod.sdl.mouse.warp_in_window(sdl_window, 320, 200) + tcod.sdl.mouse.show(True) def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: self.motion = event @@ -1123,9 +1130,9 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.N1: - tcod.mouse_show_cursor(False) + tcod.sdl.mouse.show(False) elif event.sym == tcod.event.KeySym.N2: - tcod.mouse_show_cursor(True) + tcod.sdl.mouse.show(True) else: super().ev_keydown(event) @@ -1215,7 +1222,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: xc = xc - HALF_W yc = yc - HALF_H -noise2d = tcod.noise_new(2, 0.5, 2.0) +noise2d = tcod.noise.Noise(2, hurst=0.5, lacunarity=2.0) if numpy_available: # the texture starts empty texture = np.zeros((RES_U, RES_V)) @@ -1359,11 +1366,11 @@ def on_draw(self) -> None: ############################################# RENDERER_KEYS = { - tcod.event.KeySym.F1: tcod.RENDERER_GLSL, - tcod.event.KeySym.F2: tcod.RENDERER_OPENGL, - tcod.event.KeySym.F3: tcod.RENDERER_SDL, - tcod.event.KeySym.F4: tcod.RENDERER_SDL2, - tcod.event.KeySym.F5: tcod.RENDERER_OPENGL2, + tcod.event.KeySym.F1: tcod.constants.RENDERER_GLSL, + tcod.event.KeySym.F2: tcod.constants.RENDERER_OPENGL, + tcod.event.KeySym.F3: tcod.constants.RENDERER_SDL, + tcod.event.KeySym.F4: tcod.constants.RENDERER_SDL2, + tcod.event.KeySym.F5: tcod.constants.RENDERER_OPENGL2, } RENDERER_NAMES = ( @@ -1406,7 +1413,6 @@ def init_context(renderer: int) -> None: columns=root_console.width, rows=root_console.height, title=f"python-tcod samples (python-tcod {tcod.__version__}, libtcod {libtcod_version})", - renderer=renderer, vsync=False, # VSync turned off since this is for benchmarking. tileset=tileset, ) @@ -1430,7 +1436,7 @@ def init_context(renderer: int) -> None: def main() -> None: global context, tileset tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) - init_context(tcod.RENDERER_SDL2) + init_context(tcod.constants.RENDERER_SDL2) try: SAMPLES[cur_sample].on_enter() diff --git a/tcod/image.py b/tcod/image.py index ced114d7..d1a98d88 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -65,6 +65,11 @@ def from_array(cls, array: ArrayLike) -> Image: image_array[...] = array return image + @classmethod + def from_file(cls, path: str | PathLike[str]) -> Image: + path = Path(path).resolve(strict=True) + return cls._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(path)), lib.TCOD_image_delete)) + def clear(self, color: tuple[int, int, int]) -> None: """Fill this entire Image with color. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index cb4d2c8a..d34de56c 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2979,59 +2979,60 @@ def heightmap_delete(hm: Any) -> None: """ -@pending_deprecate() +@deprecate("Use `tcod.image.Image(width, height)` instead.", FutureWarning) def image_new(width: int, height: int) -> tcod.image.Image: return tcod.image.Image(width, height) -@pending_deprecate() +@deprecate("Use the `image.clear()` method instead.", FutureWarning) def image_clear(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.clear(col) -@pending_deprecate() +@deprecate("Use the `image.invert()` method instead.", FutureWarning) def image_invert(image: tcod.image.Image) -> None: image.invert() -@pending_deprecate() +@deprecate("Use the `image.hflip()` method instead.", FutureWarning) def image_hflip(image: tcod.image.Image) -> None: image.hflip() -@pending_deprecate() +@deprecate("Use the `image.rotate90(n)` method instead.", FutureWarning) def image_rotate90(image: tcod.image.Image, num: int = 1) -> None: image.rotate90(num) -@pending_deprecate() +@deprecate("Use the `image.vflip()` method instead.", FutureWarning) def image_vflip(image: tcod.image.Image) -> None: image.vflip() -@pending_deprecate() +@deprecate("Use the `image.scale(new_width, new_height)` method instead.", FutureWarning) def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: image.scale(neww, newh) -@pending_deprecate() +@deprecate("Use the `image.image_set_key_color(rgb)` method instead.", FutureWarning) def image_set_key_color(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.set_key_color(col) -@pending_deprecate() +@deprecate("Use `np.asarray(image)[y, x, 3]` instead.", FutureWarning) def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int: return image.get_alpha(x, y) -@pending_deprecate() +@deprecate("Use the Numpy array interface to check alpha or color keys.", FutureWarning) def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) -@pending_deprecate( - "This function may be removed in the future." - " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio." +@deprecate( + "Call the classmethod `tcod.image.Image.from_file` instead to load images." + "\nIt's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", + FutureWarning, ) def image_load(filename: str | PathLike[str]) -> tcod.image.Image: """Load an image file into an Image instance and return it. @@ -3041,12 +3042,14 @@ def image_load(filename: str | PathLike[str]) -> tcod.image.Image: .. versionchanged:: Unreleased Added PathLike support. + + .. deprecated:: Unreleased + Use :any:`tcod.image.Image.from_file` instead. """ - filename = Path(filename).resolve(strict=True) - return tcod.image.Image._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(filename)), lib.TCOD_image_delete)) + return tcod.image.Image.from_file(filename) -@pending_deprecate() +@deprecate("Use `Tileset.render` instead of this function.", FutureWarning) def image_from_console(console: tcod.console.Console) -> tcod.image.Image: """Return an Image with a Consoles pixel data. @@ -3054,6 +3057,9 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: Args: console (Console): Any Console instance. + + .. deprecated:: Unreleased + :any:`Tileset.render` is a better alternative. """ return tcod.image.Image._from_cdata( ffi.gc( @@ -3063,32 +3069,37 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: ) -@pending_deprecate() +@deprecate("Use `Tileset.render` instead of this function.", FutureWarning) def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console) -> None: + """Update an image made with :any:`image_from_console`. + + .. deprecated:: Unreleased + This function is unnecessary, use :any:`Tileset.render` instead. + """ image.refresh_console(console) -@pending_deprecate() +@deprecate("Access an images size with `image.width` or `image.height`.", FutureWarning) def image_get_size(image: tcod.image.Image) -> tuple[int, int]: return image.width, image.height -@pending_deprecate() +@deprecate("Use `np.asarray(image)[y, x, :3]` instead.", FutureWarning) def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> tuple[int, int, int]: return image.get_pixel(x, y) -@pending_deprecate() +@deprecate("Use the `image.get_mipmap_pixel(...)` method instead.", FutureWarning) def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) -@pending_deprecate() +@deprecate("Use `np.asarray(image)[y, x, :3] = rgb` instead.", FutureWarning) def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: tuple[int, int, int]) -> None: image.put_pixel(x, y, col) -@pending_deprecate() +@deprecate("Use the `image.blit(...)` method instead.", FutureWarning) def image_blit( image: tcod.image.Image, console: tcod.console.Console, @@ -3102,7 +3113,7 @@ def image_blit( image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle) -@pending_deprecate() +@deprecate("Use the `image.blit_rect(...)` method instead.", FutureWarning) def image_blit_rect( image: tcod.image.Image, console: tcod.console.Console, @@ -3115,7 +3126,7 @@ def image_blit_rect( image.blit_rect(console, x, y, w, h, bkgnd_flag) -@pending_deprecate() +@deprecate("Use `Console.draw_semigraphics(image, ...)` instead.", FutureWarning) def image_blit_2x( image: tcod.image.Image, console: tcod.console.Console, @@ -3129,12 +3140,12 @@ def image_blit_2x( image.blit_2x(console, dx, dy, sx, sy, w, h) -@pending_deprecate() +@deprecate("Use the `image.save_as` method instead.", FutureWarning) def image_save(image: tcod.image.Image, filename: str | PathLike[str]) -> None: image.save_as(filename) -@deprecate("libtcod objects are deleted automatically.") +@deprecate("libtcod objects are deleted automatically.", FutureWarning) def image_delete(image: tcod.image.Image) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -3142,7 +3153,7 @@ def image_delete(image: tcod.image.Image) -> None: """ -@deprecate("Use tcod.line_iter instead.") +@deprecate("Use tcod.los.bresenham instead.", FutureWarning) def line_init(xo: int, yo: int, xd: int, yd: int) -> None: """Initialize a line whose points will be returned by `line_step`. @@ -3157,12 +3168,12 @@ def line_init(xo: int, yo: int, xd: int, yd: int) -> None: yd (int): Y destination point. .. deprecated:: 2.0 - Use `line_iter` instead. + This function was replaced by :any:`tcod.los.bresenham`. """ lib.TCOD_line_init(xo, yo, xd, yd) -@deprecate("Use tcod.line_iter instead.") +@deprecate("Use tcod.los.bresenham instead.", FutureWarning) def line_step() -> tuple[int, int] | tuple[None, None]: """After calling line_init returns (x, y) points of the line. @@ -3174,7 +3185,7 @@ def line_step() -> tuple[int, int] | tuple[None, None]: or (None, None) if there are no more points. .. deprecated:: 2.0 - Use `line_iter` instead. + This function was replaced by :any:`tcod.los.bresenham`. """ x = ffi.new("int *") y = ffi.new("int *") @@ -3184,7 +3195,7 @@ def line_step() -> tuple[int, int] | tuple[None, None]: return None, None -@deprecate("Use tcod.line_iter instead.") +@deprecate("Use tcod.los.bresenham instead.", FutureWarning) def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool]) -> bool: """Iterate over a line using a callback function. @@ -3206,7 +3217,7 @@ def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], b returning False or None, otherwise True. .. deprecated:: 2.0 - Use `line_iter` instead. + This function was replaced by :any:`tcod.los.bresenham`. """ for x, y in line_iter(xo, yo, xd, yd): if not py_callback(x, y): @@ -3216,7 +3227,7 @@ def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], b return False -@deprecate("This function has been replaced by tcod.los.bresenham.") +@deprecate("This function has been replaced by tcod.los.bresenham.", FutureWarning) def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: """Returns an Iterable over a Bresenham line. @@ -3243,7 +3254,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: yield (x[0], y[0]) -@deprecate("This function has been replaced by tcod.los.bresenham.") +@deprecate("This function has been replaced by tcod.los.bresenham.", FutureWarning) def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: """Return a NumPy index array following a Bresenham line. @@ -3274,7 +3285,7 @@ def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tu return i, j -@deprecate("Call tcod.map.Map(width, height) instead.") +@deprecate("Call tcod.map.Map(width, height) instead.", FutureWarning) def map_new(w: int, h: int) -> tcod.map.Map: """Return a :any:`tcod.map.Map` with a width and height. @@ -3285,7 +3296,7 @@ def map_new(w: int, h: int) -> tcod.map.Map: return tcod.map.Map(w, h) -@deprecate("Use Python's standard copy module instead.") +@deprecate("Use Python's standard copy module instead.", FutureWarning) def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: """Copy map data from `source` to `dest`. @@ -3298,7 +3309,7 @@ def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore -@deprecate("Set properties using the m.transparent and m.walkable arrays.") +@deprecate("Set properties using the m.transparent and m.walkable arrays.", FutureWarning) def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: """Set the properties of a single cell. @@ -3311,7 +3322,7 @@ def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: b lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk) -@deprecate("Clear maps using NumPy broadcast rules instead.") +@deprecate("Clear maps using NumPy broadcast rules instead.", FutureWarning) def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: """Change all map cells to a specific value. @@ -3323,7 +3334,7 @@ def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False m.walkable[:] = walkable -@deprecate("Call the map.compute_fov method instead.") +@deprecate("Call the map.compute_fov method instead.", FutureWarning) def map_compute_fov( m: tcod.map.Map, x: int, @@ -3340,7 +3351,7 @@ def map_compute_fov( m.compute_fov(x, y, radius, light_walls, algo) -@deprecate("Use map.fov to check for this property.") +@deprecate("Use map.fov to check for this property.", FutureWarning) def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: """Return True if the cell at x,y is lit by the last field-of-view algorithm. @@ -3352,7 +3363,7 @@ def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_in_fov(m.map_c, x, y)) -@deprecate("Use map.transparent to check for this property.") +@deprecate("Use map.transparent to check for this property.", FutureWarning) def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: """Return True is a map cell is transparent. @@ -3364,7 +3375,7 @@ def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_transparent(m.map_c, x, y)) -@deprecate("Use map.walkable to check for this property.") +@deprecate("Use map.walkable to check for this property.", FutureWarning) def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: """Return True is a map cell is walkable. @@ -3376,7 +3387,7 @@ def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_walkable(m.map_c, x, y)) -@deprecate("libtcod objects are deleted automatically.") +@deprecate("libtcod objects are deleted automatically.", FutureWarning) def map_delete(m: tcod.map.Map) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -3384,7 +3395,7 @@ def map_delete(m: tcod.map.Map) -> None: """ -@deprecate("Check the map.width attribute instead.") +@deprecate("Check the map.width attribute instead.", FutureWarning) def map_get_width(map: tcod.map.Map) -> int: """Return the width of a map. @@ -3394,7 +3405,7 @@ def map_get_width(map: tcod.map.Map) -> int: return map.width -@deprecate("Check the map.height attribute instead.") +@deprecate("Check the map.height attribute instead.", FutureWarning) def map_get_height(map: tcod.map.Map) -> int: """Return the height of a map. @@ -3404,24 +3415,32 @@ def map_get_height(map: tcod.map.Map) -> int: return map.height -@pending_deprecate() +@deprecate("Use `tcod.sdl.mouse.show(visible)` instead.", FutureWarning) def mouse_show_cursor(visible: bool) -> None: - """Change the visibility of the mouse cursor.""" + """Change the visibility of the mouse cursor. + + .. deprecated:: Unreleased + Use :any:`tcod.sdl.mouse.show` instead. + """ lib.TCOD_mouse_show_cursor(visible) -@pending_deprecate() +@deprecate("Use `is_visible = tcod.sdl.mouse.show()` instead.", FutureWarning) def mouse_is_cursor_visible() -> bool: - """Return True if the mouse cursor is visible.""" + """Return True if the mouse cursor is visible. + + .. deprecated:: Unreleased + Use :any:`tcod.sdl.mouse.show` instead. + """ return bool(lib.TCOD_mouse_is_cursor_visible()) -@pending_deprecate() +@deprecate("Use `tcod.sdl.mouse.warp_in_window` instead.", FutureWarning) def mouse_move(x: int, y: int) -> None: lib.TCOD_mouse_move(x, y) -@deprecate("Use tcod.event.get_mouse_state() instead.") +@deprecate("Use tcod.event.get_mouse_state() instead.", FutureWarning) def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) @@ -3458,7 +3477,7 @@ def namegen_destroy() -> None: lib.TCOD_namegen_destroy() -@pending_deprecate() +@deprecate("Use `tcod.noise.Noise(dimensions, hurst=, lacunarity=)` instead.", FutureWarning) def noise_new( dim: int, h: float = NOISE_DEFAULT_HURST, @@ -3479,7 +3498,7 @@ def noise_new( return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random) -@pending_deprecate() +@deprecate("Use `noise.algorithm = x` instead.", FutureWarning) def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: """Set a Noise objects default noise algorithm. @@ -3489,7 +3508,7 @@ def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: n.algorithm = typ -@pending_deprecate() +@deprecate("Use `value = noise[x]` instead.", FutureWarning) def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) -> float: """Return the noise value sampled from the ``f`` coordinate. @@ -3509,7 +3528,7 @@ def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) return float(lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ)) -@pending_deprecate() +@deprecate("Configure a Noise instance for FBM and then sample it like normal.", FutureWarning) def noise_get_fbm( n: tcod.noise.Noise, f: Sequence[float], @@ -3530,7 +3549,7 @@ def noise_get_fbm( return float(lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) -@pending_deprecate() +@deprecate("Configure a Noise instance for FBM and then sample it like normal.", FutureWarning) def noise_get_turbulence( n: tcod.noise.Noise, f: Sequence[float], @@ -3551,7 +3570,7 @@ def noise_get_turbulence( return float(lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) -@deprecate("libtcod objects are deleted automatically.") +@deprecate("libtcod objects are deleted automatically.", FutureWarning) def noise_delete(n: tcod.noise.Noise) -> None: # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index aaffdeec..cfb0be3f 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -99,7 +99,9 @@ def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: tuple[i def new_color_cursor(pixels: ArrayLike, hot_xy: tuple[int, int]) -> Cursor: - """Args: + """Create a new color cursor. + + Args: pixels: A row-major array of RGB or RGBA pixels. hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). @@ -230,4 +232,19 @@ def warp_global(x: int, y: int) -> None: def warp_in_window(window: tcod.sdl.video.Window, x: int, y: int) -> None: """Move the mouse cursor to a position within a window.""" - _check(lib.SDL_WarpMouseInWindow(window.p, x, y)) + lib.SDL_WarpMouseInWindow(window.p, x, y) + + +def show(visible: bool | None = None) -> bool: + """Optionally show or hide the mouse cursor then return the state of the cursor. + + Args: + visible: If None then only return the current state. Otherwise set the mouse visibility. + + Returns: + True if the cursor is visible. + + .. versionadded:: Unreleased + """ + _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_DISABLE, True: lib.SDL_ENABLE} + return _check(lib.SDL_ShowCursor(_OPTIONS[visible])) == int(lib.SDL_ENABLE) From 7d994eeb1bbdac92c301295679ce19395adb14fd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 18:44:36 -0700 Subject: [PATCH 0785/1101] Add missing docstring to Image.from_file class method. --- CHANGELOG.md | 1 + tcod/image.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5831f695..1a0c7312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Added - Added PathLike support to more libtcodpy functions. - New `tcod.sdl.mouse.show` function for querying or setting mouse visibility. +- New class method `tcod.image.Image.from_file` to load images with. This replaces `tcod.image_load`. ### Deprecated - Deprecated the libtcodpy functions for images and noise generators. diff --git a/tcod/image.py b/tcod/image.py index d1a98d88..927be5da 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -67,6 +67,10 @@ def from_array(cls, array: ArrayLike) -> Image: @classmethod def from_file(cls, path: str | PathLike[str]) -> Image: + """Return a new Image loaded from the given `path`. + + .. versionadded:: Unreleased + """ path = Path(path).resolve(strict=True) return cls._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(path)), lib.TCOD_image_delete)) From 37ec7c0ef139e2ac134d35f305a1d98f7f45a217 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 18:49:46 -0700 Subject: [PATCH 0786/1101] Fixed many typos in the tcod.sdl package. --- .vscode/settings.json | 26 ++++++++++++++++++++++++++ tcod/sdl/audio.py | 28 ++++++++++++++-------------- tcod/sdl/mouse.py | 2 +- tcod/sdl/sys.py | 4 ++-- tcod/sdl/video.py | 2 +- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 18967232..8eb6e93e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,6 +57,8 @@ "bfade", "bgcolor", "bitmask", + "bitorder", + "BITSIZE", "BKGND", "Blit", "blits", @@ -123,6 +125,7 @@ "DISPLAYSWITCH", "dlopen", "Doryen", + "DPAD", "DTEEE", "DTEEN", "DTEES", @@ -149,6 +152,8 @@ "frombuffer", "fullscreen", "fwidth", + "GAMECONTROLLER", + "gamepad", "genindex", "GFORCE", "GLES", @@ -220,12 +225,18 @@ "LDFLAGS", "LEFTBRACE", "LEFTBRACKET", + "LEFTDOWN", "LEFTPAREN", + "LEFTSHOULDER", + "LEFTSTICK", + "LEFTUP", + "LEFTX", "lerp", "LGUI", "libsdl", "libtcod", "libtcodpy", + "linspace", "liskin", "LMASK", "lmeta", @@ -289,8 +300,10 @@ "onefile", "OPENGL", "OPER", + "packbits", "PAGEDOWN", "PAGEUP", + "PATCHLEVEL", "pathfinding", "pathlib", "pcpp", @@ -329,7 +342,12 @@ "RGUI", "RIGHTBRACE", "RIGHTBRACKET", + "RIGHTDOWN", "RIGHTPAREN", + "RIGHTSHOULDER", + "RIGHTSTICK", + "RIGHTUP", + "RIGHTX", "RMASK", "rmeta", "roguelike", @@ -354,18 +372,21 @@ "servernum", "setuptools", "SHADOWCAST", + "SIZEALL", "SIZENESW", "SIZENS", "SIZENWSE", "SIZEWE", "SMILIE", "snprintf", + "soundfile", "stdeb", "struct", "structs", "SUBP", "SYSREQ", "tablefmt", + "TARGETTEXTURE", "tcod", "tcoddoc", "TCODK", @@ -383,6 +404,9 @@ "toctree", "todos", "tolist", + "touchpad", + "TRIGGERLEFT", + "TRIGGERRIGHT", "tris", "truetype", "typestr", @@ -405,6 +429,8 @@ "voronoi", "VRAM", "vsync", + "VULKAN", + "WAITARROW", "WASD", "waterlevel", "windowclose", diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 8234a2a6..69ca5143 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,11 +1,11 @@ """SDL2 audio playback and recording tools. -This module includes SDL's low-level audio API and a naive implentation of an SDL mixer. +This module includes SDL's low-level audio API and a naive implementation of an SDL mixer. If you have experience with audio mixing then you might be better off writing your own mixer or modifying the existing one which was written using Python/Numpy. This module is designed to integrate with the wider Python ecosystem. -It leaves the loading to sound samples to other libaries like +It leaves the loading to sound samples to other libraries like `SoundFile `_. Example:: @@ -17,9 +17,9 @@ import tcod.sdl.audio device = tcod.sdl.audio.open() # Open the default output device. - sound, samplerate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile. - converted = device.convert(sound, samplerate) # Convert this sample to the format expected by the device. - device.queue_audio(converted) # Play audio syncroniously by appending it to the device buffer. + sound, sample_rate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile. + converted = device.convert(sound, sample_rate) # Convert this sample to the format expected by the device. + device.queue_audio(converted) # Play audio synchronously by appending it to the device buffer. while device.queued_samples: # Wait until device is done playing. time.sleep(0.001) @@ -33,8 +33,8 @@ import tcod.sdl.audio mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) # Setup BasicMixer with the default audio output. - sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. - sound = mixer.device.convert(sound, samplerate) # Convert this sample to the format expected by the device. + sound, sample_rate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. + sound = mixer.device.convert(sound, sample_rate) # Convert this sample to the format expected by the device. channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread. while channel.busy: # Wait until the sample is done playing. time.sleep(0.001) @@ -59,7 +59,7 @@ def _get_format(format: DTypeLike) -> int: - """Return a SDL_AudioFormat bitfield from a NumPy dtype.""" + """Return a SDL_AudioFormat bit-field from a NumPy dtype.""" dt: Any = np.dtype(format) assert dt.fields is None bitsize = dt.itemsize * 8 @@ -83,7 +83,7 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: """Return a dtype from a SDL_AudioFormat.""" bitsize = format & lib.SDL_AUDIO_MASK_BITSIZE assert bitsize % 8 == 0 - bytesize = bitsize // 8 + byte_size = bitsize // 8 byteorder = ">" if format & lib.SDL_AUDIO_MASK_ENDIAN else "<" if format & lib.SDL_AUDIO_MASK_DATATYPE: kind = "f" @@ -91,7 +91,7 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: kind = "i" else: kind = "u" - return np.dtype(f"{byteorder}{kind}{bytesize}") + return np.dtype(f"{byteorder}{kind}{byte_size}") def convert_audio( @@ -103,8 +103,8 @@ def convert_audio( Args: in_sound: The input ArrayLike sound sample. Input format and channels are derived from the array. - in_rate: The samplerate of the input array. - out_rate: The samplerate of the output array. + in_rate: The sample-rate of the input array. + out_rate: The sample-rate of the output array. out_format: The output format of the converted array. out_channels: The number of audio channels of the output array. @@ -142,7 +142,7 @@ class AudioDevice: Open new audio devices using :any:`tcod.sdl.audio.open`. - When you use this object directly the audio passed to :any:`queue_audio` is always played syncroniously. + When you use this object directly the audio passed to :any:`queue_audio` is always played synchronously. For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. """ @@ -231,7 +231,7 @@ def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[Any]: Args: sound: An ArrayLike sound sample. - rate: The samplerate of the input array. + rate: The sample-rate of the input array. If None is given then it's assumed to be the same as the device. .. versionadded:: 13.6 diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index cfb0be3f..3498c7d8 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -148,7 +148,7 @@ def get_cursor() -> Cursor | None: def capture(enable: bool) -> None: """Enable or disable mouse capture to track the mouse outside of a window. - It is highly reccomended to read the related remarks section in the SDL docs before using this. + It is highly recommended to read the related remarks section in the SDL docs before using this. Example:: diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 39bda95d..f2b65fb4 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -60,8 +60,8 @@ def _get_power_info() -> tuple[_PowerState, int, int]: buffer = ffi.new("int[2]") power_state = _PowerState(lib.SDL_GetPowerInfo(buffer, buffer + 1)) seconds_of_power = buffer[0] - percenage = buffer[1] - return power_state, seconds_of_power, percenage + percentage = buffer[1] + return power_state, seconds_of_power, percentage def _get_clipboard() -> str: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 4cf6a87b..0a4ad7a0 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -262,7 +262,7 @@ def border_size(self) -> tuple[int, int, int, int]: @property def opacity(self) -> float: - """Get or set this windows opacity. 0.0 is fully transarpent and 1.0 is fully opaque. + """Get or set this windows opacity. 0.0 is fully transparent and 1.0 is fully opaque. Will error if you try to set this and opacity isn't supported. """ From eaf4571d137d8569001422a7fc8a799eb96ddf72 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 20:32:28 -0700 Subject: [PATCH 0787/1101] Fix audio device callback. Closes #128 Allow audio conversions of floating types other than float32. Setup AudioDevice as a context manager and add a `__repr__` method. --- CHANGELOG.md | 6 +++++ tcod/sdl/audio.py | 60 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0c7312..ba10dc37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Added PathLike support to more libtcodpy functions. - New `tcod.sdl.mouse.show` function for querying or setting mouse visibility. - New class method `tcod.image.Image.from_file` to load images with. This replaces `tcod.image_load`. +- `tcod.sdl.audio.AudioDevice` is now a context manager. + +### Changed +- SDL audio conversion will now pass unconvertible floating types as float32 instead of raising. ### Deprecated - Deprecated the libtcodpy functions for images and noise generators. @@ -17,6 +21,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - Fix `tcod.sdl.mouse.warp_in_window` function. +- Fix `TypeError: '_AudioCallbackUserdata' object is not callable` when using an SDL audio device callback. + [#128](https://github.com/libtcod/python-tcod/issues/128) ## [15.0.3] - 2023-05-25 ### Deprecated diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 69ca5143..b995e336 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -47,11 +47,12 @@ import sys import threading import time +from types import TracebackType from typing import Any, Callable, Hashable, Iterator import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray -from typing_extensions import Final, Literal +from typing_extensions import Final, Literal, Self import tcod.sdl.sys from tcod.loader import ffi, lib @@ -110,6 +111,9 @@ def convert_audio( .. versionadded:: 13.6 + .. versionchanged:: Unreleased + Now converts floating types to `np.float32` when SDL doesn't support the specific format. + .. seealso:: :any:`AudioDevice.convert` """ @@ -123,8 +127,26 @@ def convert_audio( in_channels = in_array.shape[1] in_format = _get_format(in_array.dtype) out_sdl_format = _get_format(out_format) - if _check(lib.SDL_BuildAudioCVT(cvt, in_format, in_channels, in_rate, out_sdl_format, out_channels, out_rate)) == 0: - return in_array # No conversion needed. + try: + if ( + _check(lib.SDL_BuildAudioCVT(cvt, in_format, in_channels, in_rate, out_sdl_format, out_channels, out_rate)) + == 0 + ): + return in_array # No conversion needed. + except RuntimeError as exc: + if ( # SDL now only supports float32, but later versions may add more support for more formats. + exc.args[0] == "Invalid source format" + and np.issubdtype(in_array.dtype, np.floating) + and in_array.dtype != np.float32 + ): + return convert_audio( # Try again with float32 + in_array.astype(np.float32), + in_rate, + out_rate=out_rate, + out_format=out_format, + out_channels=out_channels, + ) + raise # Upload to the SDL_AudioCVT buffer. cvt.len = in_array.itemsize * in_array.size out_buffer = cvt.buf = ffi.new("uint8_t[]", cvt.len * cvt.len_mult) @@ -144,6 +166,9 @@ class AudioDevice: When you use this object directly the audio passed to :any:`queue_audio` is always played synchronously. For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. + + .. versionchanged:: Unreleased + Can now be used as a context which will close the device on exit. """ def __init__( @@ -176,6 +201,23 @@ def __init__( self._handle: Any | None = None self._callback: Callable[[AudioDevice, NDArray[Any]], None] = self.__default_callback + def __repr__(self) -> str: + """Return a representation of this device.""" + items = [ + f"{self.__class__.__name__}(device_id={self.device_id})", + f"frequency={self.frequency}", + f"is_capture={self.is_capture}", + f"format={self.format}", + f"channels={self.channels}", + f"buffer_samples={self.buffer_samples}", + f"buffer_bytes={self.buffer_bytes}", + ] + if self.silence: + items.append(f"silence={self.silence}") + if self._handle is not None: + items.append(f"callback={self._callback}") + return f"""<{" ".join(items)}>""" + @property def callback(self) -> Callable[[AudioDevice, NDArray[Any]], None]: """If the device was opened with a callback enabled, then you may get or set the callback with this attribute.""" @@ -288,6 +330,16 @@ def close(self) -> None: lib.SDL_CloseAudioDevice(self.device_id) del self.device_id + def __enter__(self) -> Self: + """Return self and enter a managed context.""" + return self + + def __exit__( + self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> None: + """Close the device when exiting the context.""" + self.close() + @staticmethod def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: stream[...] = device.silence @@ -487,7 +539,7 @@ class _AudioCallbackUserdata: @ffi.def_extern() # type: ignore def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: """Handle audio device callbacks.""" - data: _AudioCallbackUserdata = ffi.from_handle(userdata)() + data: _AudioCallbackUserdata = ffi.from_handle(userdata) device = data.device buffer = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) device._callback(device, buffer) From 98531c5fee187633fc84640c844ac1bfb0e88c0a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 27 May 2023 20:53:44 -0700 Subject: [PATCH 0788/1101] Prepare 16.0.0 release. --- CHANGELOG.md | 4 +++- tcod/image.py | 4 ++-- tcod/libtcodpy.py | 36 ++++++++++++++++++------------------ tcod/sdl/audio.py | 4 ++-- tcod/sdl/mouse.py | 2 +- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba10dc37..5db385e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.0.0] - 2023-05-27 ### Added - Added PathLike support to more libtcodpy functions. - New `tcod.sdl.mouse.show` function for querying or setting mouse visibility. @@ -17,7 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Deprecated the libtcodpy functions for images and noise generators. ### Removed -- `tcod.console_set_custom_font` can no longer take bytes. +- `tcod.console_set_custom_font` can no longer take bytes as the file path. ### Fixed - Fix `tcod.sdl.mouse.warp_in_window` function. diff --git a/tcod/image.py b/tcod/image.py index 927be5da..700c6290 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -69,7 +69,7 @@ def from_array(cls, array: ArrayLike) -> Image: def from_file(cls, path: str | PathLike[str]) -> Image: """Return a new Image loaded from the given `path`. - .. versionadded:: Unreleased + .. versionadded:: 16.0 """ path = Path(path).resolve(strict=True) return cls._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(path)), lib.TCOD_image_delete)) @@ -303,7 +303,7 @@ def save_as(self, filename: str | PathLike[str]) -> None: Args: filename (Text): File path to same this Image. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ lib.TCOD_image_save(self.image_c, bytes(Path(filename))) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d34de56c..6bb1c762 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -985,7 +985,7 @@ def console_set_custom_font( Load fonts using :any:`tcod.tileset.load_tilesheet` instead. See :ref:`getting-started` for more info. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. `fontFile` no longer takes bytes. """ fontFile = Path(fontFile).resolve(strict=True) @@ -1800,7 +1800,7 @@ def console_from_file(filename: str | PathLike[str]) -> tcod.console.Console: Other formats are not actively supported. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -1979,7 +1979,7 @@ def console_load_asc(con: tcod.console.Console, filename: str | PathLike[str]) - .. deprecated:: 12.7 This format is no longer supported. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -1993,7 +1993,7 @@ def console_save_asc(con: tcod.console.Console, filename: str | PathLike[str]) - .. deprecated:: 12.7 This format is no longer supported. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ return bool(lib.TCOD_console_save_asc(_console(con), bytes(Path(filename)))) @@ -2006,7 +2006,7 @@ def console_load_apf(con: tcod.console.Console, filename: str | PathLike[str]) - .. deprecated:: 12.7 This format is no longer supported. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -2020,7 +2020,7 @@ def console_save_apf(con: tcod.console.Console, filename: str | PathLike[str]) - .. deprecated:: 12.7 This format is no longer supported. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ return bool(lib.TCOD_console_save_apf(_console(con), bytes(Path(filename)))) @@ -2034,7 +2034,7 @@ def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> Functions modifying console objects in-place are deprecated. Use :any:`tcod.console_from_xp` to load a Console from a file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -2045,7 +2045,7 @@ def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> def console_save_xp(con: tcod.console.Console, filename: str | PathLike[str], compress_level: int = 9) -> bool: """Save a console to a REXPaint `.xp` file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ return bool(lib.TCOD_console_save_xp(_console(con), bytes(Path(filename)), compress_level)) @@ -2055,7 +2055,7 @@ def console_save_xp(con: tcod.console.Console, filename: str | PathLike[str], co def console_from_xp(filename: str | PathLike[str]) -> tcod.console.Console: """Return a single console from a REXPaint `.xp` file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -2068,7 +2068,7 @@ def console_list_load_xp( ) -> list[tcod.console.Console] | None: """Return a list of consoles from a REXPaint `.xp` file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ filename = Path(filename).resolve(strict=True) @@ -2093,7 +2093,7 @@ def console_list_save_xp( ) -> bool: """Save a list of consoles to a REXPaint `.xp` file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ tcod_list = lib.TCOD_list_new() @@ -3040,10 +3040,10 @@ def image_load(filename: str | PathLike[str]) -> tcod.image.Image: Args: filename: Path to a .bmp or .png image file. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. - .. deprecated:: Unreleased + .. deprecated:: 16.0 Use :any:`tcod.image.Image.from_file` instead. """ return tcod.image.Image.from_file(filename) @@ -3058,7 +3058,7 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: Args: console (Console): Any Console instance. - .. deprecated:: Unreleased + .. deprecated:: 16.0 :any:`Tileset.render` is a better alternative. """ return tcod.image.Image._from_cdata( @@ -3073,7 +3073,7 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console) -> None: """Update an image made with :any:`image_from_console`. - .. deprecated:: Unreleased + .. deprecated:: 16.0 This function is unnecessary, use :any:`Tileset.render` instead. """ image.refresh_console(console) @@ -3419,7 +3419,7 @@ def map_get_height(map: tcod.map.Map) -> int: def mouse_show_cursor(visible: bool) -> None: """Change the visibility of the mouse cursor. - .. deprecated:: Unreleased + .. deprecated:: 16.0 Use :any:`tcod.sdl.mouse.show` instead. """ lib.TCOD_mouse_show_cursor(visible) @@ -3429,7 +3429,7 @@ def mouse_show_cursor(visible: bool) -> None: def mouse_is_cursor_visible() -> bool: """Return True if the mouse cursor is visible. - .. deprecated:: Unreleased + .. deprecated:: 16.0 Use :any:`tcod.sdl.mouse.show` instead. """ return bool(lib.TCOD_mouse_is_cursor_visible()) @@ -4087,7 +4087,7 @@ def sys_save_screenshot(name: str | PathLike[str] | None = None) -> None: This function is not supported by contexts. Use :any:`Context.save_screenshot` instead. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Added PathLike support. """ lib.TCOD_sys_save_screenshot(bytes(Path(name)) if name is not None else ffi.NULL) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index b995e336..d1f221df 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -111,7 +111,7 @@ def convert_audio( .. versionadded:: 13.6 - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Now converts floating types to `np.float32` when SDL doesn't support the specific format. .. seealso:: @@ -167,7 +167,7 @@ class AudioDevice: When you use this object directly the audio passed to :any:`queue_audio` is always played synchronously. For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. - .. versionchanged:: Unreleased + .. versionchanged:: 16.0 Can now be used as a context which will close the device on exit. """ diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 3498c7d8..0de8ddbc 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -244,7 +244,7 @@ def show(visible: bool | None = None) -> bool: Returns: True if the cursor is visible. - .. versionadded:: Unreleased + .. versionadded:: 16.0 """ _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_DISABLE, True: lib.SDL_ENABLE} return _check(lib.SDL_ShowCursor(_OPTIONS[visible])) == int(lib.SDL_ENABLE) From 93114e2df88c36251d0b5b54486e921668914564 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 May 2023 03:52:32 -0700 Subject: [PATCH 0789/1101] Fix multiple issues with tcod.sdl.audio Adds much needed testing of the tcod.sdl.audio module. Convert some asserts into real errors. Handle exceptions raised in audio callback as unraisable. --- .vscode/settings.json | 1 + CHANGELOG.md | 4 ++ tcod/sdl/audio.py | 67 +++++++++++++++++++--- tests/test_sdl.py | 14 ----- tests/test_sdl_audio.py | 121 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 tests/test_sdl_audio.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 8eb6e93e..6ddcc5ef 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -412,6 +412,7 @@ "typestr", "undoc", "Unifont", + "unraisable", "unraisablehook", "unraiseable", "upscaling", diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db385e8..166c2af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- `AudioDevice.stopped` was inverted. +- Fixed the audio mixer stop and fadeout methods. +- Exceptions raised in the audio mixer callback no longer cause a messy crash, they now go to `sys.unraisablehook`. ## [16.0.0] - 2023-05-27 ### Added diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index d1f221df..c909aa73 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -47,6 +47,7 @@ import sys import threading import time +from dataclasses import dataclass from types import TracebackType from typing import Any, Callable, Hashable, Iterator @@ -65,7 +66,9 @@ def _get_format(format: DTypeLike) -> int: assert dt.fields is None bitsize = dt.itemsize * 8 assert 0 < bitsize <= lib.SDL_AUDIO_MASK_BITSIZE - assert dt.str[1] in "uif" + if not dt.str[1] in "uif": + msg = f"Unexpected dtype: {dt}" + raise TypeError(msg) is_signed = dt.str[1] != "u" is_float = dt.str[1] == "f" byteorder = dt.byteorder @@ -81,7 +84,21 @@ def _get_format(format: DTypeLike) -> int: def _dtype_from_format(format: int) -> np.dtype[Any]: - """Return a dtype from a SDL_AudioFormat.""" + """Return a dtype from a SDL_AudioFormat. + + >>> _dtype_from_format(tcod.lib.AUDIO_F32LSB) + dtype('float32') + >>> _dtype_from_format(tcod.lib.AUDIO_F32MSB) + dtype('>f4') + >>> _dtype_from_format(tcod.lib.AUDIO_S16LSB) + dtype('int16') + >>> _dtype_from_format(tcod.lib.AUDIO_S16MSB) + dtype('>i2') + >>> _dtype_from_format(tcod.lib.AUDIO_U16LSB) + dtype('uint16') + >>> _dtype_from_format(tcod.lib.AUDIO_U16MSB) + dtype('>u2') + """ bitsize = format & lib.SDL_AUDIO_MASK_BITSIZE assert bitsize % 8 == 0 byte_size = bitsize // 8 @@ -203,6 +220,8 @@ def __init__( def __repr__(self) -> str: """Return a representation of this device.""" + if self.stopped: + return f"<{self.__class__.__name__}() stopped=True>" items = [ f"{self.__class__.__name__}(device_id={self.device_id})", f"frequency={self.frequency}", @@ -211,7 +230,9 @@ def __repr__(self) -> str: f"channels={self.channels}", f"buffer_samples={self.buffer_samples}", f"buffer_bytes={self.buffer_bytes}", + f"paused={self.paused}", ] + if self.silence: items.append(f"silence={self.silence}") if self._handle is not None: @@ -241,7 +262,9 @@ def _sample_size(self) -> int: @property def stopped(self) -> bool: """Is True if the device has failed or was closed.""" - return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) != lib.SDL_AUDIO_STOPPED) + if not hasattr(self, "device_id"): + return True + return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) == lib.SDL_AUDIO_STOPPED) @property def paused(self) -> bool: @@ -404,7 +427,9 @@ def play( def _verify_audio_sample(self, sample: ArrayLike) -> NDArray[Any]: """Verify an audio sample is valid and return it as a Numpy array.""" array: NDArray[Any] = np.asarray(sample) - assert array.dtype == self.mixer.device.format + if array.dtype != self.mixer.device.format: + msg = f"Audio sample must be dtype={self.mixer.device.format}, input was dtype={array.dtype}" + raise TypeError(msg) if len(array.shape) == 1: array = array[:, np.newaxis] return array @@ -434,7 +459,7 @@ def fadeout(self, time: float) -> None: time_samples = round(time * self.mixer.device.frequency) + 1 buffer: NDArray[np.float32] = np.zeros((time_samples, self.mixer.device.channels), np.float32) self._on_mix(buffer) - buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:] + buffer *= np.linspace(1.0, 0.0, time_samples + 1, endpoint=False)[1:, np.newaxis] self.sound_queue[:] = [buffer] def stop(self) -> None: @@ -536,13 +561,41 @@ class _AudioCallbackUserdata: device: AudioDevice +@dataclass +class _UnraisableHookArgs: + exc_type: type[BaseException] + exc_value: BaseException | None + exc_traceback: TracebackType | None + err_msg: str | None + object: object + + +class _ProtectedContext: + def __init__(self, obj: object = None) -> None: + self.obj = obj + + def __enter__(self) -> None: + pass + + def __exit__( + self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool: + if exc_type is None: + return False + if sys.version_info < (3, 8): + return False + sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] + return True + + @ffi.def_extern() # type: ignore -def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: +def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: # noqa: ANN401 """Handle audio device callbacks.""" data: _AudioCallbackUserdata = ffi.from_handle(userdata) device = data.device buffer = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) - device._callback(device, buffer) + with _ProtectedContext(device): + device._callback(device, buffer) def _get_devices(capture: bool) -> Iterator[str]: diff --git a/tests/test_sdl.py b/tests/test_sdl.py index b7bf8571..fa2ac29a 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -1,11 +1,9 @@ """Test SDL specific features.""" -import contextlib import sys import numpy as np import pytest -import tcod.sdl.audio import tcod.sdl.render import tcod.sdl.sys import tcod.sdl.video @@ -83,15 +81,3 @@ def test_sdl_render_bad_types() -> None: tcod.sdl.render.Renderer(tcod.ffi.cast("SDL_Renderer*", tcod.ffi.NULL)) with pytest.raises(TypeError): tcod.sdl.render.Renderer(tcod.ffi.new("SDL_Rect*")) - - -def test_sdl_audio_device() -> None: - with contextlib.closing(tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True)) as device: - assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 # noqa: PLR2004 - assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape == (4, 2) - assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 # noqa: PLR2004 - device.paused = False - device.paused = True - assert device.queued_samples == 0 - with contextlib.closing(tcod.sdl.audio.BasicMixer(device)) as mixer: - assert mixer diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py new file mode 100644 index 00000000..90af5fd2 --- /dev/null +++ b/tests/test_sdl_audio.py @@ -0,0 +1,121 @@ +"""Test tcod.sdl.audio module.""" +import contextlib +import sys +import time +from typing import Any + +import numpy as np +import pytest +from numpy.typing import NDArray + +import tcod.sdl.audio + +# ruff: noqa: D103 + + +def test_devices() -> None: + list(tcod.sdl.audio.get_devices()) + list(tcod.sdl.audio.get_capture_devices()) + + +def test_audio_device() -> None: + with tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True) as device: + assert not device.stopped + assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 # noqa: PLR2004 + assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape == (4, 2) + assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 # noqa: PLR2004 + assert device.paused is True + device.paused = False + assert device.paused is False + device.paused = True + assert device.queued_samples == 0 + with pytest.raises(TypeError): + device.callback # noqa: B018 + with pytest.raises(TypeError): + device.callback = lambda _device, _stream: None + with contextlib.closing(tcod.sdl.audio.BasicMixer(device)) as mixer: + assert mixer.daemon + assert mixer.play(np.zeros(4, np.float32)).busy + mixer.play(np.zeros(0, np.float32)) + mixer.play(np.full(1, 0.01, np.float32), on_end=lambda _: None) + mixer.play(np.full(1, 0.01, np.float32), loops=2, on_end=lambda _: None) + mixer.play(np.full(4, 0.01, np.float32), loops=2).stop() + mixer.play(np.full(100000, 0.01, np.float32)) + with pytest.raises(TypeError, match=r".*must be dtype=float32.*was dtype=int32"): + mixer.play(np.zeros(1, np.int32)) + time.sleep(0.001) + mixer.stop() + + +def test_audio_capture() -> None: + with tcod.sdl.audio.open(capture=True) as device: + assert not device.stopped + assert isinstance(device.dequeue_audio(), np.ndarray) + + +def test_audio_device_repr() -> None: + with tcod.sdl.audio.open(format=np.uint16, paused=True, callback=True) as device: + assert "silence=" in repr(device) + assert "callback=" in repr(device) + assert "stopped=" in repr(device) + + +def test_convert_bad_shape() -> None: + with pytest.raises(TypeError): + tcod.sdl.audio.convert_audio( + np.zeros((1, 1, 1), np.float32), 8000, out_rate=8000, out_format=np.float32, out_channels=1 + ) + + +def test_convert_bad_type() -> None: + with pytest.raises(TypeError, match=r".*bool"): + tcod.sdl.audio.convert_audio(np.zeros(8, bool), 8000, out_rate=8000, out_format=np.float32, out_channels=1) + with pytest.raises(RuntimeError, match=r"Invalid source format"): + tcod.sdl.audio.convert_audio(np.zeros(8, np.int64), 8000, out_rate=8000, out_format=np.float32, out_channels=1) + + +def test_convert_float64() -> None: + np.testing.assert_array_equal( + tcod.sdl.audio.convert_audio( + np.ones(8, np.float64), 8000, out_rate=8000, out_format=np.float32, out_channels=1 + ), + np.ones((8, 1), np.float32), + ) + + +def test_audio_callback() -> None: + class CheckCalled: + was_called: bool = False + + def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None: + self.was_called = True + assert isinstance(device, tcod.sdl.audio.AudioDevice) + assert isinstance(stream, np.ndarray) + assert len(stream.shape) == 2 # noqa: PLR2004 + + check_called = CheckCalled() + with tcod.sdl.audio.open(callback=check_called, paused=False) as device: + device.callback = device.callback + while not check_called.was_called: + time.sleep(0.001) + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs sys.unraisablehook support") +@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") +def test_audio_callback_unraisable() -> None: + """Test unraisable error in audio callback. + + This can't be checked with pytest very well, so at least make sure this doesn't crash. + """ + + class CheckCalled: + was_called: bool = False + + def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None: + self.was_called = True + raise Exception("Test unraisable error") # noqa + + check_called = CheckCalled() + with tcod.sdl.audio.open(callback=check_called, paused=False): + while not check_called.was_called: + time.sleep(0.001) From 499059247f69ab3f55168db068ccb5a3e428e551 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 May 2023 04:36:37 -0700 Subject: [PATCH 0790/1101] Run tests in CI with a timeout. --- .github/workflows/python-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9161d910..3c571b40 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -136,7 +136,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pytest pytest-cov pytest-benchmark build + pip install pytest pytest-cov pytest-benchmark pytest-timeout build if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package run: | @@ -147,11 +147,11 @@ jobs: - name: Test with pytest if: runner.os == 'Windows' run: | - pytest --cov-report=xml + pytest --cov-report=xml --timeout=30 - name: Test with pytest (Xvfb) if: always() && runner.os != 'Windows' run: | - xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml + xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=30 - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log From 66a0bd91c9ab03b7dcd75f29e5fe24d45e783476 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 May 2023 04:52:34 -0700 Subject: [PATCH 0791/1101] Xfail tests when audio devices are missing. --- tests/test_sdl_audio.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index 90af5fd2..6441bda1 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -13,11 +13,20 @@ # ruff: noqa: D103 +needs_audio_device = pytest.mark.xfail( + not list(tcod.sdl.audio.get_devices()), reason="This test requires an audio device" +) +needs_audio_capture = pytest.mark.xfail( + not list(tcod.sdl.audio.get_capture_devices()), reason="This test requires an audio capture device" +) + + def test_devices() -> None: list(tcod.sdl.audio.get_devices()) list(tcod.sdl.audio.get_capture_devices()) +@needs_audio_device def test_audio_device() -> None: with tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True) as device: assert not device.stopped @@ -47,14 +56,17 @@ def test_audio_device() -> None: mixer.stop() +@needs_audio_capture def test_audio_capture() -> None: with tcod.sdl.audio.open(capture=True) as device: assert not device.stopped assert isinstance(device.dequeue_audio(), np.ndarray) +@needs_audio_device def test_audio_device_repr() -> None: with tcod.sdl.audio.open(format=np.uint16, paused=True, callback=True) as device: + assert not device.stopped assert "silence=" in repr(device) assert "callback=" in repr(device) assert "stopped=" in repr(device) @@ -83,6 +95,7 @@ def test_convert_float64() -> None: ) +@needs_audio_device def test_audio_callback() -> None: class CheckCalled: was_called: bool = False @@ -95,6 +108,7 @@ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> check_called = CheckCalled() with tcod.sdl.audio.open(callback=check_called, paused=False) as device: + assert not device.stopped device.callback = device.callback while not check_called.was_called: time.sleep(0.001) @@ -102,6 +116,7 @@ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> @pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs sys.unraisablehook support") @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") +@needs_audio_device def test_audio_callback_unraisable() -> None: """Test unraisable error in audio callback. @@ -116,6 +131,7 @@ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> raise Exception("Test unraisable error") # noqa check_called = CheckCalled() - with tcod.sdl.audio.open(callback=check_called, paused=False): + with tcod.sdl.audio.open(callback=check_called, paused=False) as device: + assert not device.stopped while not check_called.was_called: time.sleep(0.001) From 07bf13c21ff3903a5fc4a5ae548047799d4faa6f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 28 May 2023 05:06:50 -0700 Subject: [PATCH 0792/1101] Prepare 16.0.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166c2af1..872d4904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.0.1] - 2023-05-28 ### Fixed - `AudioDevice.stopped` was inverted. - Fixed the audio mixer stop and fadeout methods. From 35144454d26852e3992b6ad29f638b9e40f7b6a9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 29 May 2023 06:37:43 -0700 Subject: [PATCH 0793/1101] Remove unnecessary files from source distributions. Right now GitHub is the main place to track the source and the sources uploaded to PyPI are just to build with. I don't need to upload fonts, tools, or tool configs to PyPI. --- MANIFEST.in | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index dd211a1d..9fc63cae 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,23 @@ -include *.py *.cfg *.txt *.rst *.toml +global-exclude .* +prune .* + +include *.py +include *.txt +include *.rst +include *.toml +include *.md recursive-include tcod *.py *.c *.h +prune libtcod recursive-include libtcod/src *.glsl* *.c *.h -include libtcod/*.txt libtcod/*.md tests/data/*.pkl +include libtcod/*.txt +include libtcod/*.md + +prune docs +prune examples +prune fonts +prune scripts +prune tests -exclude tcod/*/SDL2.dll +global-exclude *.dll From ffd21bba370ae8362257e9607271d4b76dec4cb6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 30 May 2023 23:31:21 -0700 Subject: [PATCH 0794/1101] Convert examples and testing to be more namespace friendly. --- .github/workflows/python-package.yml | 6 +++--- docs/installation.rst | 4 ++-- docs/tcod/getting-started.rst | 10 +++++++--- examples/distribution/PyInstaller/main.py | 5 ++++- examples/distribution/cx_Freeze/main.py | 5 ++++- examples/eventget.py | 3 ++- examples/sdl-hello-world.py | 2 +- examples/thread_jobs.py | 2 +- examples/ttf.py | 7 +++++-- tcod/console.py | 10 ++++++---- tcod/context.py | 4 ++-- tcod/event.py | 2 +- tcod/render.py | 2 +- tests/test_console.py | 22 +++++++++++++--------- tests/test_noise.py | 2 +- tests/test_random.py | 2 +- tests/test_tcod.py | 3 ++- tests/test_tileset.py | 2 +- 18 files changed, 57 insertions(+), 36 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3c571b40..587ef2d5 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -191,7 +191,7 @@ jobs: pip install tcod-*.tar.gz - name: Confirm package import run: | - python -c "import tcod" + python -c "import tcod.context" linux-wheels: needs: [black, isort, mypy] @@ -232,7 +232,7 @@ jobs: yum-config-manager --enable epel && yum install -y SDL2-devel CIBW_BEFORE_TEST: pip install numpy - CIBW_TEST_COMMAND: python -c "import tcod" + CIBW_TEST_COMMAND: python -c "import tcod.context" # Skip test on emulated architectures CIBW_TEST_SKIP: "*_aarch64" - name: Archive wheel @@ -267,7 +267,7 @@ jobs: CIBW_ARCHS_MACOS: x86_64 arm64 universal2 CIBW_BEFORE_BUILD_MACOS: pip install --upgrade delocate CIBW_BEFORE_TEST: pip install numpy - CIBW_TEST_COMMAND: python -c "import tcod" + CIBW_TEST_COMMAND: python -c "import tcod.context" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" - name: Archive wheel uses: actions/upload-artifact@v3 diff --git a/docs/installation.rst b/docs/installation.rst index 109601cc..4b8e04ce 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -50,9 +50,9 @@ You can then verify that ``tcod`` is importable from the Python interpreter:: >python - >>> import tcod + >>> import tcod.context -If ``import tcod`` doesn't throw an ``ImportError`` then ``tcod`` has been installed correctly to your system libraries. +If ``import tcod.context`` doesn't throw an ``ImportError`` then ``tcod`` has been installed correctly to your system libraries. Some IDE's such as PyCharm will create a virtual environment which will ignore your system libraries and require tcod to be installed again in that new environment. diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 4954fe6a..b277f864 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -23,7 +23,10 @@ Example:: #!/usr/bin/env python3 # Make sure 'dejavu10x10_gs_tc.png' is in the same directory as this script. - import tcod + import tcod.console + import tcod.context + import tcod.event + import tcod.tileset WIDTH, HEIGHT = 80, 60 # Console width and height in tiles. @@ -35,7 +38,7 @@ Example:: "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD, ) # Create the main console. - console = tcod.Console(WIDTH, HEIGHT, order="F") + console = tcod.console.Console(WIDTH, HEIGHT, order="F") # Create a window based on this console and tileset. with tcod.context.new( # New window for a console of size columns×rows. columns=console.width, rows=console.height, tileset=tileset, @@ -87,7 +90,8 @@ clearing the console every frame and replacing it only on resizing the window. Example:: #!/usr/bin/env python3 - import tcod + import tcod.context + import tcod.event WIDTH, HEIGHT = 720, 480 # Window pixel resolution (when not maximized.) FLAGS = tcod.context.SDL_WINDOW_RESIZABLE | tcod.context.SDL_WINDOW_MAXIMIZED diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index 7f715351..e1c6090d 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -7,7 +7,10 @@ import sys from pathlib import Path -import tcod +import tcod.console +import tcod.context +import tcod.event +import tcod.tileset WIDTH, HEIGHT = 80, 60 diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index b1f51acb..8f608af7 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 """cx_Freeze main script example.""" -import tcod +import tcod.console +import tcod.context +import tcod.event +import tcod.tileset WIDTH, HEIGHT = 80, 60 console = None diff --git a/examples/eventget.py b/examples/eventget.py index 32a12658..774ad3b1 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -6,7 +6,8 @@ """An demonstration of event handling using the tcod.event module.""" from typing import List, Set -import tcod +import tcod.context +import tcod.event import tcod.sdl.joystick import tcod.sdl.sys diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index d0ae93c0..b9c07547 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -4,7 +4,7 @@ import numpy as np from PIL import Image, ImageDraw, ImageFont # type: ignore # pip install Pillow -import tcod +import tcod.event import tcod.sdl.render import tcod.sdl.video diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index b935c91f..d6fa54fd 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -20,7 +20,7 @@ import timeit from typing import Callable, List, Tuple -import tcod +import tcod.map THREADS = multiprocessing.cpu_count() diff --git a/examples/ttf.py b/examples/ttf.py index de134165..e1c585d5 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -14,7 +14,10 @@ import numpy as np from numpy.typing import NDArray -import tcod +import tcod.console +import tcod.context +import tcod.event +import tcod.tileset FONT = "VeraMono.ttf" @@ -60,7 +63,7 @@ def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: def main() -> None: """True-type font example script.""" - console = tcod.Console(16, 12, order="F") + console = tcod.console.Console(16, 12, order="F") with tcod.context.new( columns=console.width, rows=console.height, diff --git a/tcod/console.py b/tcod/console.py index 19114077..f14f8bca 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1087,7 +1087,7 @@ def draw_frame( Example:: - >>> console = tcod.Console(12, 6) + >>> console = tcod.console.Console(12, 6) >>> console.draw_frame(x=0, y=0, width=3, height=3) >>> console.draw_frame(x=3, y=0, width=3, height=3, decoration="╔═╗║ ║╚═╝") >>> console.draw_frame(x=6, y=0, width=3, height=3, decoration="123456789") @@ -1272,7 +1272,8 @@ def load_xp(path: str | PathLike[str], order: Literal["C", "F"] = "C") -> tuple[ Example:: import numpy as np - import tcod + import tcod.console + import tcod.tileset path = "example.xp" # REXPaint file with one layer. @@ -1321,9 +1322,10 @@ def save_xp( Example:: import numpy as np - import tcod + import tcod.console + import tcod.tileset - console = tcod.Console(80, 24) # Example console. + console = tcod.console.Console(80, 24) # Example console. # Convert from Unicode to REXPaint's encoding. # Required to load this console correctly in the REXPaint tool. diff --git a/tcod/context.py b/tcod/context.py index b8f359d4..019097c2 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -58,7 +58,7 @@ from typing_extensions import Literal -import tcod +import tcod.console import tcod.event import tcod.render import tcod.sdl.render @@ -483,7 +483,7 @@ def new( sdl_window_flags: int | None = None, title: str | None = None, argv: Iterable[str] | None = None, - console: tcod.Console | None = None, + console: tcod.console.Console | None = None, ) -> Context: """Create a new context with the desired pixel size. diff --git a/tcod/event.py b/tcod/event.py index c415cb34..1379c40c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1253,7 +1253,7 @@ def wait(timeout: float | None = None) -> Iterator[Any]: context: tcod.context.Context # Context object initialized earlier. while True: # Main game-loop. - console: tcod.Console # Console used for rendering. + console: tcod.console.Console # Console used for rendering. ... # Render the frame to `console` and then: context.present(console) # Show the console to the display. # The ordering to draw first before waiting for events is important. diff --git a/tcod/render.py b/tcod/render.py index 71a98686..184d8abd 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -7,7 +7,7 @@ Example:: tileset = tcod.tileset.load_tilesheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD) - console = tcod.Console(20, 8) + console = tcod.console.Console(20, 8) console.print(0, 0, "Hello World") sdl_window = tcod.sdl.video.new_window( console.width * tileset.tile_width, diff --git a/tests/test_console.py b/tests/test_console.py index 5ddc6f8e..a5af3d31 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -6,23 +6,24 @@ import pytest import tcod +import tcod.console # ruff: noqa: D103 -@pytest.mark.filterwarnings("ignore:Directly access a consoles") -@pytest.mark.filterwarnings("ignore:This function may be deprecated in the fu") def test_array_read_write() -> None: console = tcod.console.Console(width=12, height=10) FG = (255, 254, 253) BG = (1, 2, 3) CH = ord("&") - tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) + with pytest.warns(): + tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) assert console.ch[0, 0] == CH assert tuple(console.fg[0, 0]) == FG assert tuple(console.bg[0, 0]) == BG - tcod.console_put_char_ex(console, 1, 2, CH, FG, BG) + with pytest.warns(): + tcod.console_put_char_ex(console, 1, 2, CH, FG, BG) assert console.ch[2, 1] == CH assert tuple(console.fg[2, 1]) == FG assert tuple(console.bg[2, 1]) == BG @@ -37,9 +38,12 @@ def test_array_read_write() -> None: console.fg[1, ::2] = FG console.bg[...] = BG - assert tcod.console_get_char(console, 2, 1) == CH - assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG - assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG + with pytest.warns(): + assert tcod.console_get_char(console, 2, 1) == CH + with pytest.warns(): + assert tuple(tcod.console_get_char_foreground(console, 2, 1)) == FG + with pytest.warns(): + assert tuple(tcod.console_get_char_background(console, 2, 1)) == BG @pytest.mark.filterwarnings("ignore") @@ -137,7 +141,7 @@ def test_console_semigraphics() -> None: def test_rexpaint(tmp_path: Path) -> None: xp_path = tmp_path / "test.xp" - consoles = tcod.Console(80, 24, order="F"), tcod.Console(8, 8, order="F") + consoles = tcod.console.Console(80, 24, order="F"), tcod.console.Console(8, 8, order="F") tcod.console.save_xp(xp_path, consoles, compress_level=0) loaded = tcod.console.load_xp(xp_path, order="F") assert len(consoles) == len(loaded) @@ -149,7 +153,7 @@ def test_rexpaint(tmp_path: Path) -> None: def test_draw_frame() -> None: - console = tcod.Console(3, 3, order="C") + console = tcod.console.Console(3, 3, order="C") with pytest.raises(TypeError): console.draw_frame(0, 0, 3, 3, title="test", decoration="123456789") with pytest.raises(TypeError): diff --git a/tests/test_noise.py b/tests/test_noise.py index 6a653f06..0f64d5fe 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -5,7 +5,7 @@ import numpy as np import pytest -import tcod +import tcod.noise # ruff: noqa: D103 diff --git a/tests/test_random.py b/tests/test_random.py index c56c6678..e6b64e1e 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -3,7 +3,7 @@ import pickle from pathlib import Path -import tcod +import tcod.random # ruff: noqa: D103 diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 5c731f48..b736e787 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -8,6 +8,7 @@ from numpy.typing import DTypeLike, NDArray import tcod +import tcod.console # ruff: noqa: D103 @@ -190,7 +191,7 @@ def test_context() -> None: pass WIDTH, HEIGHT = 16, 4 with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2) as context: - console = tcod.Console(*context.recommended_console_size()) + console = tcod.console.Console(*context.recommended_console_size()) context.present(console) assert context.sdl_window_p is not None assert context.renderer_type >= 0 diff --git a/tests/test_tileset.py b/tests/test_tileset.py index c0c24c75..aba88b98 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -1,5 +1,5 @@ """Test for tcod.tileset module.""" -import tcod +import tcod.tileset # ruff: noqa: D103 From af6d8ea6b827e96cbd9a859e369d3f985d54c11f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 30 May 2023 23:45:27 -0700 Subject: [PATCH 0795/1101] Fix readme typo. --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 48fc5209..0594efe1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,7 +1,7 @@ This directory contains a few example scripts for using python-tcod. `samples_tcod.py` is the mail example which uses most of the newer API. This -can be compared to `samokes_libtcodpy.py` which mostly uses deprecated +can be compared to `samples_libtcodpy.py` which mostly uses deprecated functions from the old API. Examples in the `distribution/` folder show how to distribute projects made From b28789cfa8ac5dcc4f5b2d244e5c5f484eaa47f2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 May 2023 23:10:04 -0700 Subject: [PATCH 0796/1101] Give a better error when a context is used after it's closed. --- tcod/context.py | 32 +++++++++++++++++++++----------- tests/test_tcod.py | 2 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 019097c2..79eb7df1 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -176,8 +176,18 @@ def __init__(self, context_p: Any) -> None: @classmethod def _claim(cls, context_p: Any) -> Context: + """Return a new instance wrapping a context pointer.""" return cls(ffi.gc(context_p, lib.TCOD_context_delete)) + @property + def _p(self) -> Any: # noqa: ANN401 + """Return the context pointer or raise if it is missing.""" + try: + return self._context_p + except AttributeError: + msg = "This context has been closed can no longer be used." + raise RuntimeError(msg) from None + def __enter__(self) -> Context: """Enter this context which will close on exiting.""" return self @@ -236,18 +246,18 @@ def present( "align_y": align[1], }, ) - _check(lib.TCOD_context_present(self._context_p, console.console_c, viewport_args)) + _check(lib.TCOD_context_present(self._p, console.console_c, viewport_args)) def pixel_to_tile(self, x: int, y: int) -> tuple[int, int]: """Convert window pixel coordinates to tile coordinates.""" with ffi.new("int[2]", (x, y)) as xy: - _check(lib.TCOD_context_screen_pixel_to_tile_i(self._context_p, xy, xy + 1)) + _check(lib.TCOD_context_screen_pixel_to_tile_i(self._p, xy, xy + 1)) return xy[0], xy[1] def pixel_to_subtile(self, x: int, y: int) -> tuple[float, float]: """Convert window pixel coordinates to sub-tile coordinates.""" with ffi.new("double[2]", (x, y)) as xy: - _check(lib.TCOD_context_screen_pixel_to_tile_d(self._context_p, xy, xy + 1)) + _check(lib.TCOD_context_screen_pixel_to_tile_d(self._p, xy, xy + 1)) return xy[0], xy[1] def convert_event(self, event: _Event) -> _Event: @@ -286,7 +296,7 @@ def convert_event(self, event: _Event) -> _Event: def save_screenshot(self, path: str | None = None) -> None: """Save a screen-shot to the given file path.""" c_path = path.encode("utf-8") if path is not None else ffi.NULL - _check(lib.TCOD_context_save_screenshot(self._context_p, c_path)) + _check(lib.TCOD_context_save_screenshot(self._p, c_path)) def change_tileset(self, tileset: tcod.tileset.Tileset | None) -> None: """Change the active tileset used by this context. @@ -300,7 +310,7 @@ def change_tileset(self, tileset: tcod.tileset.Tileset | None) -> None: Using this method only one tileset is active per-frame. See :any:`tcod.render` if you want to renderer with multiple tilesets in a single frame. """ - _check(lib.TCOD_context_change_tileset(self._context_p, _handle_tileset(tileset))) + _check(lib.TCOD_context_change_tileset(self._p, _handle_tileset(tileset))) def new_console( self, @@ -359,7 +369,7 @@ def new_console( if magnification < 0: raise ValueError("Magnification must be greater than zero. (Got %f)" % magnification) size = ffi.new("int[2]") - _check(lib.TCOD_context_recommended_console_size(self._context_p, magnification, size, size + 1)) + _check(lib.TCOD_context_recommended_console_size(self._p, magnification, size, size + 1)) width, height = max(min_columns, size[0]), max(min_rows, size[1]) return tcod.console.Console(width, height, order=order) @@ -371,13 +381,13 @@ def recommended_console_size(self, min_columns: int = 1, min_rows: int = 1) -> t If result is only used to create a new console then you may want to call :any:`Context.new_console` instead. """ with ffi.new("int[2]") as size: - _check(lib.TCOD_context_recommended_console_size(self._context_p, 1.0, size, size + 1)) + _check(lib.TCOD_context_recommended_console_size(self._p, 1.0, size, size + 1)) return max(min_columns, size[0]), max(min_rows, size[1]) @property def renderer_type(self) -> int: """Return the libtcod renderer type used by this context.""" - return _check(lib.TCOD_context_get_renderer_type(self._context_p)) + return _check(lib.TCOD_context_get_renderer_type(self._p)) @property def sdl_window_p(self) -> Any: @@ -407,7 +417,7 @@ def toggle_fullscreen(context: tcod.context.Context) -> None: ) ''' - return lib.TCOD_context_get_sdl_window(self._context_p) + return lib.TCOD_context_get_sdl_window(self._p) @property def sdl_window(self) -> tcod.sdl.video.Window | None: @@ -439,7 +449,7 @@ def sdl_renderer(self) -> tcod.sdl.render.Renderer | None: .. versionadded:: 13.4 """ - p = lib.TCOD_context_get_sdl_renderer(self._context_p) + p = lib.TCOD_context_get_sdl_renderer(self._p) return tcod.sdl.render.Renderer(p) if p else None @property @@ -448,7 +458,7 @@ def sdl_atlas(self) -> tcod.render.SDLTilesetAtlas | None: .. versionadded:: 13.5 """ - if self._context_p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2): + if self._p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2): return None context_data = ffi.cast("struct TCOD_RendererSDL2*", self._context_p.contextdata_) return tcod.render.SDLTilesetAtlas._from_ref(context_data.renderer, context_data.atlas) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index b736e787..0f9ddf15 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -198,3 +198,5 @@ def test_context() -> None: context.change_tileset(tcod.tileset.Tileset(16, 16)) context.pixel_to_tile(0, 0) context.pixel_to_subtile(0, 0) + with pytest.raises(RuntimeError, match=".*context has been closed"): + context.present(console) From 21c93c12129927ad5df479bcde6da1403cbf4bcc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 May 2023 23:29:57 -0700 Subject: [PATCH 0797/1101] Increase test timeout. Now that hanging issues are fixed the timeout should be far more relaxed. --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 587ef2d5..95e391c2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -147,11 +147,11 @@ jobs: - name: Test with pytest if: runner.os == 'Windows' run: | - pytest --cov-report=xml --timeout=30 + pytest --cov-report=xml --timeout=300 - name: Test with pytest (Xvfb) if: always() && runner.os != 'Windows' run: | - xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=30 + xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300 - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log From fc2ba4f888c880ff7dfe8fe901dcbe8eae52dfd3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 31 May 2023 23:35:03 -0700 Subject: [PATCH 0798/1101] Handle warnings better in deprecated tests. Remove outdated doctest which was fully deprecated. --- tcod/console.py | 12 +++++++----- tcod/libtcodpy.py | 13 ------------- tests/test_console.py | 7 ++++--- tests/test_deprecated.py | 4 +++- tests/test_tcod.py | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index f14f8bca..f5147e23 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -304,10 +304,11 @@ def rgba(self) -> NDArray[Any]: Example: >>> con = tcod.console.Console(10, 2) + >>> WHITE, BLACK = (255, 255, 255), (0, 0, 0) >>> con.rgba[0, 0] = ( ... ord("X"), - ... (*tcod.white, 255), - ... (*tcod.black, 255), + ... (*WHITE, 255), + ... (*BLACK, 255), ... ) >>> con.rgba[0, 0] (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) @@ -328,10 +329,11 @@ def rgb(self) -> NDArray[Any]: Example: >>> con = tcod.console.Console(10, 2) - >>> con.rgb[0, 0] = ord("@"), tcod.yellow, tcod.black + >>> BLUE, YELLOW, BLACK = (0, 0, 255), (255, 255, 0), (0, 0, 0) + >>> con.rgb[0, 0] = ord("@"), YELLOW, BLACK >>> con.rgb[0, 0] (64, [255, 255, 0], [0, 0, 0]) - >>> con.rgb["bg"] = tcod.blue + >>> con.rgb["bg"] = BLUE >>> con.rgb[0, 0] (64, [255, 255, 0], [ 0, 0, 255]) @@ -916,7 +918,7 @@ def __repr__(self) -> str: self.width, self.height, self._order, - self.tiles, + self.rgba, ) def __str__(self) -> str: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 6bb1c762..4a258081 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3260,19 +3260,6 @@ def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tu If `inclusive` is true then the start point is included in the result. - Example: - >>> where = tcod.line_where(1, 0, 3, 4) - >>> where - (array([1, 1, 2, 2, 3]...), array([0, 1, 2, 3, 4]...)) - >>> array = np.zeros((5, 5), dtype=np.int32) - >>> array[where] = np.arange(len(where[0])) + 1 - >>> array - array([[0, 0, 0, 0, 0], - [1, 2, 0, 0, 0], - [0, 0, 3, 4, 0], - [0, 0, 0, 0, 5], - [0, 0, 0, 0, 0]]...) - .. versionadded:: 4.6 .. deprecated:: 11.14 diff --git a/tests/test_console.py b/tests/test_console.py index a5af3d31..f8fa5d91 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -106,11 +106,11 @@ def test_console_repr() -> None: eval(repr(tcod.console.Console(10, 2))) -@pytest.mark.filterwarnings("ignore") def test_console_str() -> None: console = tcod.console.Console(10, 2) console.ch[:] = ord(".") - console.print_(0, 0, "Test") + with pytest.warns(): + console.print_(0, 0, "Test") assert str(console) == ("") @@ -161,4 +161,5 @@ def test_draw_frame() -> None: console.draw_frame(0, 0, 3, 3, decoration=(49, 50, 51, 52, 53, 54, 55, 56, 57)) assert console.ch.tolist() == [[49, 50, 51], [52, 53, 54], [55, 56, 57]] - console.draw_frame(0, 0, 3, 3, title="T") + with pytest.warns(): + console.draw_frame(0, 0, 3, 3, title="T") diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 425cafb1..ec4894c9 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -3,11 +3,13 @@ import pytest -import libtcodpy import tcod import tcod.event import tcod.libtcodpy +with pytest.warns(): + import libtcodpy + # ruff: noqa: D103 diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 0f9ddf15..33e50195 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -19,7 +19,7 @@ def raise_Exception(*args: object) -> NoReturn: def test_line_error() -> None: """Test exception propagation.""" - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError), pytest.warns(): tcod.line(0, 0, 10, 10, py_callback=raise_Exception) From 9e1291de40cb211fe68dca653b39dffa41f33541 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 1 Jun 2023 00:30:05 -0700 Subject: [PATCH 0799/1101] Backport deprecated line_where test. So that this is still covered since the doctest has been removed. --- tests/test_deprecated.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index ec4894c9..9d871ba7 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -1,6 +1,7 @@ """Test deprecated features.""" from __future__ import annotations +import numpy as np import pytest import tcod @@ -29,3 +30,9 @@ def test_deprecate_key_constants() -> None: _ = tcod.event.K_1 with pytest.raises(FutureWarning, match=r".*Scancode.N1"): _ = tcod.event.SCANCODE_1 + + +def test_line_where() -> None: + with pytest.warns(): + where = tcod.libtcodpy.line_where(1, 0, 3, 4) + np.testing.assert_array_equal(where, [[1, 1, 2, 2, 3], [0, 1, 2, 3, 4]]) From 3bed09fa4d395f89d4fed63c0cfa12189362fbee Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 2 Jun 2023 07:37:17 -0700 Subject: [PATCH 0800/1101] Fix joystick removal access. Joysticks can not be accessed by their instance id during removal. This change stores them in a weak dictionary so that they may be accessed that way. --- CHANGELOG.md | 2 ++ tcod/sdl/joystick.py | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 872d4904..c23fd3dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- Joystick/controller device events would raise `RuntimeError` when accessed after removal. ## [16.0.1] - 2023-05-28 ### Fixed diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 5d24d353..5e2390ec 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -5,7 +5,8 @@ from __future__ import annotations import enum -from typing import Any +from typing import Any, ClassVar +from weakref import WeakValueDictionary from typing_extensions import Final, Literal @@ -122,6 +123,9 @@ class Joystick: https://wiki.libsdl.org/CategoryJoystick """ + _by_instance_id: ClassVar[WeakValueDictionary[int, Joystick]] = WeakValueDictionary() + """Currently opened joysticks.""" + def __init__(self, sdl_joystick_p: Any) -> None: self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" @@ -142,6 +146,8 @@ def __init__(self, sdl_joystick_p: Any) -> None: self._keep_alive: Any = None """The owner of this objects memory if this object does not own itself.""" + self._by_instance_id[self.id] = self + @classmethod def _open(cls, device_index: int) -> Joystick: tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) @@ -150,7 +156,7 @@ def _open(cls, device_index: int) -> Joystick: @classmethod def _from_instance_id(cls, instance_id: int) -> Joystick: - return cls(_check_p(ffi.gc(lib.SDL_JoystickFromInstanceID(instance_id), lib.SDL_JoystickClose))) + return cls._by_instance_id[instance_id] def __eq__(self, other: object) -> bool: if isinstance(other, Joystick): @@ -163,7 +169,7 @@ def __hash__(self) -> int: def _get_guid(self) -> str: guid_str = ffi.new("char[33]") lib.SDL_JoystickGetGUIDString(lib.SDL_JoystickGetGUID(self.sdl_joystick_p), guid_str, len(guid_str)) - return str(tcod.ffi.string(guid_str), encoding="utf-8") + return str(ffi.string(guid_str), encoding="ascii") def get_current_power(self) -> Power: """Return the power level/state of this joystick. See :any:`Power`.""" @@ -191,11 +197,15 @@ def get_hat(self, hat: int) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: class GameController: """A standard interface for an Xbox 360 style game controller.""" + _by_instance_id: ClassVar[WeakValueDictionary[int, GameController]] = WeakValueDictionary() + """Currently opened controllers.""" + def __init__(self, sdl_controller_p: Any) -> None: self.sdl_controller_p: Final = sdl_controller_p self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p)) """The :any:`Joystick` associated with this controller.""" self.joystick._keep_alive = self.sdl_controller_p # This objects real owner needs to be kept alive. + self._by_instance_id[self.joystick.id] = self @classmethod def _open(cls, joystick_index: int) -> GameController: @@ -203,7 +213,7 @@ def _open(cls, joystick_index: int) -> GameController: @classmethod def _from_instance_id(cls, instance_id: int) -> GameController: - return cls(_check_p(ffi.gc(lib.SDL_GameControllerFromInstanceID(instance_id), lib.SDL_GameControllerClose))) + return cls._by_instance_id[instance_id] def get_button(self, button: ControllerButton) -> bool: """Return True if `button` is currently held.""" From c061679b18cfaac162c576ef5d6513cf123eee75 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 2 Jun 2023 08:08:18 -0700 Subject: [PATCH 0801/1101] Prepare 16.0.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c23fd3dc..c9f0e36e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.0.2] - 2023-06-02 ### Fixed - Joystick/controller device events would raise `RuntimeError` when accessed after removal. From 49ebc65ddf0fb0292f667dca9d43b7d5fe1ca7bd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 3 Jun 2023 00:11:55 -0700 Subject: [PATCH 0802/1101] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dc89cbfd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. A minimal code example is recommended. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Windows 11] + - Python version [e.g. Python 3.11, PyPy 3.9] + - tcod version [e.g. 16.0.2] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From ce0ea6408dda735c731c405d4153a02267f6e8a5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 3 Jun 2023 08:08:28 -0700 Subject: [PATCH 0803/1101] Add more logging for libtcod and SDL. Move protected context closer to tcod.sdl internals. --- tcod/cdef.h | 3 +++ tcod/loader.py | 17 +++++++++++++++ tcod/sdl/__init__.py | 52 +++++++++++++++++++++++++++++++++++++++++--- tcod/sdl/audio.py | 30 +------------------------ 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/tcod/cdef.h b/tcod/cdef.h index 03db6cbc..aecccc9c 100644 --- a/tcod/cdef.h +++ b/tcod/cdef.h @@ -17,4 +17,7 @@ float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void* user_data); void _pycall_sdl_hook(struct SDL_Surface*); void _pycall_cli_output(void* userdata, const char* output); + +// Libtcod log watch function. +void _libtcod_log_watcher(const TCOD_LogMessage* message, void* userdata); } diff --git a/tcod/loader.py b/tcod/loader.py index 0027fb8c..36e6d17b 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -1,6 +1,7 @@ """This module handles loading of the libtcod cffi API.""" from __future__ import annotations +import logging import os import platform import sys @@ -9,6 +10,8 @@ import cffi +logger = logging.getLogger("tcod") + __sdl_version__ = "" ffi_check = cffi.FFI() @@ -83,4 +86,18 @@ def __bool__(self) -> bool: __sdl_version__ = get_sdl_version() + +@ffi.def_extern() # type: ignore +def _libtcod_log_watcher(message: Any, userdata: None) -> None: + text = str(ffi.string(message.message), encoding="utf-8") + source = str(ffi.string(message.source), encoding="utf-8") + level = int(message.level) + lineno = int(message.lineno) + logger.log(level, "%s:%d:%s", source, lineno, text) + + +if lib: + lib.TCOD_set_log_callback(lib._libtcod_log_watcher, ffi.NULL) + lib.TCOD_set_log_level(0) + __all__ = ["ffi", "lib"] diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 84ee2d06..b99ad675 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -1,7 +1,10 @@ from __future__ import annotations import logging +import sys as _sys +from dataclasses import dataclass from pkgutil import extend_path +from types import TracebackType from typing import Any, Callable, TypeVar from tcod.loader import ffi, lib @@ -10,7 +13,7 @@ T = TypeVar("T") -logger = logging.getLogger(__name__) +logger = logging.getLogger("tcod.sdl") _LOG_PRIORITY = { 1: logging.DEBUG, # SDL_LOG_PRIORITY_VERBOSE @@ -21,11 +24,52 @@ 6: logging.CRITICAL, # SDL_LOG_PRIORITY_CRITICAL } +_LOG_CATEGORY = { + int(lib.SDL_LOG_CATEGORY_APPLICATION): "APPLICATION", + int(lib.SDL_LOG_CATEGORY_ERROR): "ERROR", + int(lib.SDL_LOG_CATEGORY_ASSERT): "ASSERT", + int(lib.SDL_LOG_CATEGORY_SYSTEM): "SYSTEM", + int(lib.SDL_LOG_CATEGORY_AUDIO): "AUDIO", + int(lib.SDL_LOG_CATEGORY_VIDEO): "VIDEO", + int(lib.SDL_LOG_CATEGORY_RENDER): "RENDER", + int(lib.SDL_LOG_CATEGORY_INPUT): "INPUT", + int(lib.SDL_LOG_CATEGORY_TEST): "TEST", + int(lib.SDL_LOG_CATEGORY_CUSTOM): "", +} + + +@dataclass +class _UnraisableHookArgs: + exc_type: type[BaseException] + exc_value: BaseException | None + exc_traceback: TracebackType | None + err_msg: str | None + object: object + + +class _ProtectedContext: + def __init__(self, obj: object = None) -> None: + self.obj = obj + + def __enter__(self) -> None: + pass + + def __exit__( + self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool: + if exc_type is None: + return False + if _sys.version_info < (3, 8): + return False + _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] + return True + @ffi.def_extern() # type: ignore -def _sdl_log_output_function(_userdata: Any, category: int, priority: int, message: Any) -> None: +def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 """Pass logs sent by SDL to Python's logging system.""" - logger.log(_LOG_PRIORITY.get(priority, 0), "%i:%s", category, ffi.string(message).decode("utf-8")) + message = str(ffi.string(message_p), encoding="utf-8") + logger.log(_LOG_PRIORITY.get(priority, 0), "%s:%s", _LOG_CATEGORY.get(category, ""), message) def _get_error() -> str: @@ -49,6 +93,8 @@ def _check_p(result: Any) -> Any: if lib._sdl_log_output_function: lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) + if __debug__: + lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) def _compiled_version() -> tuple[int, int, int]: diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index c909aa73..88d9323a 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -47,7 +47,6 @@ import sys import threading import time -from dataclasses import dataclass from types import TracebackType from typing import Any, Callable, Hashable, Iterator @@ -57,7 +56,7 @@ import tcod.sdl.sys from tcod.loader import ffi, lib -from tcod.sdl import _check, _get_error +from tcod.sdl import _check, _get_error, _ProtectedContext def _get_format(format: DTypeLike) -> int: @@ -561,33 +560,6 @@ class _AudioCallbackUserdata: device: AudioDevice -@dataclass -class _UnraisableHookArgs: - exc_type: type[BaseException] - exc_value: BaseException | None - exc_traceback: TracebackType | None - err_msg: str | None - object: object - - -class _ProtectedContext: - def __init__(self, obj: object = None) -> None: - self.obj = obj - - def __enter__(self) -> None: - pass - - def __exit__( - self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool: - if exc_type is None: - return False - if sys.version_info < (3, 8): - return False - sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] - return True - - @ffi.def_extern() # type: ignore def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: # noqa: ANN401 """Handle audio device callbacks.""" From 01c9bb631c215e262334a246b36253d1f29c072f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 3 Jun 2023 08:51:01 -0700 Subject: [PATCH 0804/1101] Move tcod.sdl internals to a sub-module. To keep things out of a potential namespace. --- tcod/sdl/__init__.py | 126 +----------------------------------------- tcod/sdl/_internal.py | 126 ++++++++++++++++++++++++++++++++++++++++++ tcod/sdl/audio.py | 2 +- tcod/sdl/joystick.py | 2 +- tcod/sdl/mouse.py | 2 +- tcod/sdl/render.py | 2 +- tcod/sdl/sys.py | 2 +- tcod/sdl/video.py | 2 +- 8 files changed, 133 insertions(+), 131 deletions(-) create mode 100644 tcod/sdl/_internal.py diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index b99ad675..8133948b 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -1,128 +1,4 @@ -from __future__ import annotations - -import logging -import sys as _sys -from dataclasses import dataclass +"""tcod.sdl package.""" from pkgutil import extend_path -from types import TracebackType -from typing import Any, Callable, TypeVar - -from tcod.loader import ffi, lib __path__ = extend_path(__path__, __name__) - -T = TypeVar("T") - -logger = logging.getLogger("tcod.sdl") - -_LOG_PRIORITY = { - 1: logging.DEBUG, # SDL_LOG_PRIORITY_VERBOSE - 2: logging.DEBUG, # SDL_LOG_PRIORITY_DEBUG - 3: logging.INFO, # SDL_LOG_PRIORITY_INFO - 4: logging.WARNING, # SDL_LOG_PRIORITY_WARN - 5: logging.ERROR, # SDL_LOG_PRIORITY_ERROR - 6: logging.CRITICAL, # SDL_LOG_PRIORITY_CRITICAL -} - -_LOG_CATEGORY = { - int(lib.SDL_LOG_CATEGORY_APPLICATION): "APPLICATION", - int(lib.SDL_LOG_CATEGORY_ERROR): "ERROR", - int(lib.SDL_LOG_CATEGORY_ASSERT): "ASSERT", - int(lib.SDL_LOG_CATEGORY_SYSTEM): "SYSTEM", - int(lib.SDL_LOG_CATEGORY_AUDIO): "AUDIO", - int(lib.SDL_LOG_CATEGORY_VIDEO): "VIDEO", - int(lib.SDL_LOG_CATEGORY_RENDER): "RENDER", - int(lib.SDL_LOG_CATEGORY_INPUT): "INPUT", - int(lib.SDL_LOG_CATEGORY_TEST): "TEST", - int(lib.SDL_LOG_CATEGORY_CUSTOM): "", -} - - -@dataclass -class _UnraisableHookArgs: - exc_type: type[BaseException] - exc_value: BaseException | None - exc_traceback: TracebackType | None - err_msg: str | None - object: object - - -class _ProtectedContext: - def __init__(self, obj: object = None) -> None: - self.obj = obj - - def __enter__(self) -> None: - pass - - def __exit__( - self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None - ) -> bool: - if exc_type is None: - return False - if _sys.version_info < (3, 8): - return False - _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] - return True - - -@ffi.def_extern() # type: ignore -def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 - """Pass logs sent by SDL to Python's logging system.""" - message = str(ffi.string(message_p), encoding="utf-8") - logger.log(_LOG_PRIORITY.get(priority, 0), "%s:%s", _LOG_CATEGORY.get(category, ""), message) - - -def _get_error() -> str: - """Return a message from SDL_GetError as a Unicode string.""" - return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") - - -def _check(result: int) -> int: - """Check if an SDL function returned without errors, and raise an exception if it did.""" - if result < 0: - raise RuntimeError(_get_error()) - return result - - -def _check_p(result: Any) -> Any: - """Check if an SDL function returned NULL, and raise an exception if it did.""" - if not result: - raise RuntimeError(_get_error()) - return result - - -if lib._sdl_log_output_function: - lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) - if __debug__: - lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) - - -def _compiled_version() -> tuple[int, int, int]: - return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) - - -def _linked_version() -> tuple[int, int, int]: - sdl_version = ffi.new("SDL_version*") - lib.SDL_GetVersion(sdl_version) - return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) - - -def _version_at_least(required: tuple[int, int, int]) -> None: - """Raise an error if the compiled version is less than required. Used to guard recently defined SDL functions.""" - if required <= _compiled_version(): - return - msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" - raise RuntimeError(msg) - - -def _required_version(required: tuple[int, int, int]) -> Callable[[T], T]: - if not lib: # Read the docs mock object. - return lambda x: x - if required <= _compiled_version(): - return lambda x: x - - def replacement(*_args: Any, **_kwargs: Any) -> Any: - msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" - raise RuntimeError(msg) - - return lambda x: replacement # type: ignore[return-value] diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py new file mode 100644 index 00000000..79d59339 --- /dev/null +++ b/tcod/sdl/_internal.py @@ -0,0 +1,126 @@ +"""tcod.sdl private functions.""" +from __future__ import annotations + +import logging +import sys as _sys +from dataclasses import dataclass +from types import TracebackType +from typing import Any, Callable, TypeVar + +from tcod.loader import ffi, lib + +T = TypeVar("T") + +logger = logging.getLogger("tcod.sdl") + +_LOG_PRIORITY = { + 1: logging.DEBUG, # SDL_LOG_PRIORITY_VERBOSE + 2: logging.DEBUG, # SDL_LOG_PRIORITY_DEBUG + 3: logging.INFO, # SDL_LOG_PRIORITY_INFO + 4: logging.WARNING, # SDL_LOG_PRIORITY_WARN + 5: logging.ERROR, # SDL_LOG_PRIORITY_ERROR + 6: logging.CRITICAL, # SDL_LOG_PRIORITY_CRITICAL +} + +_LOG_CATEGORY = { + int(lib.SDL_LOG_CATEGORY_APPLICATION): "APPLICATION", + int(lib.SDL_LOG_CATEGORY_ERROR): "ERROR", + int(lib.SDL_LOG_CATEGORY_ASSERT): "ASSERT", + int(lib.SDL_LOG_CATEGORY_SYSTEM): "SYSTEM", + int(lib.SDL_LOG_CATEGORY_AUDIO): "AUDIO", + int(lib.SDL_LOG_CATEGORY_VIDEO): "VIDEO", + int(lib.SDL_LOG_CATEGORY_RENDER): "RENDER", + int(lib.SDL_LOG_CATEGORY_INPUT): "INPUT", + int(lib.SDL_LOG_CATEGORY_TEST): "TEST", + int(lib.SDL_LOG_CATEGORY_CUSTOM): "", +} + + +@dataclass +class _UnraisableHookArgs: + exc_type: type[BaseException] + exc_value: BaseException | None + exc_traceback: TracebackType | None + err_msg: str | None + object: object + + +class _ProtectedContext: + def __init__(self, obj: object = None) -> None: + self.obj = obj + + def __enter__(self) -> None: + pass + + def __exit__( + self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + ) -> bool: + if exc_type is None: + return False + if _sys.version_info < (3, 8): + return False + _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] + return True + + +@ffi.def_extern() # type: ignore +def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 + """Pass logs sent by SDL to Python's logging system.""" + message = str(ffi.string(message_p), encoding="utf-8") + logger.log(_LOG_PRIORITY.get(priority, 0), "%s:%s", _LOG_CATEGORY.get(category, ""), message) + + +def _get_error() -> str: + """Return a message from SDL_GetError as a Unicode string.""" + return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") + + +def _check(result: int) -> int: + """Check if an SDL function returned without errors, and raise an exception if it did.""" + if result < 0: + raise RuntimeError(_get_error()) + return result + + +def _check_p(result: Any) -> Any: + """Check if an SDL function returned NULL, and raise an exception if it did.""" + if not result: + raise RuntimeError(_get_error()) + return result + + +if lib._sdl_log_output_function: + lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) + if __debug__: + lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) + + +def _compiled_version() -> tuple[int, int, int]: + return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) + + +def _linked_version() -> tuple[int, int, int]: + sdl_version = ffi.new("SDL_version*") + lib.SDL_GetVersion(sdl_version) + return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) + + +def _version_at_least(required: tuple[int, int, int]) -> None: + """Raise an error if the compiled version is less than required. Used to guard recently defined SDL functions.""" + if required <= _compiled_version(): + return + msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + raise RuntimeError(msg) + + +def _required_version(required: tuple[int, int, int]) -> Callable[[T], T]: + if not lib: # Read the docs mock object. + return lambda x: x + if required <= _compiled_version(): + return lambda x: x + + def replacement(*_args: Any, **_kwargs: Any) -> Any: + msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" + raise RuntimeError(msg) + + return lambda x: replacement # type: ignore[return-value] diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 88d9323a..f8261097 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -56,7 +56,7 @@ import tcod.sdl.sys from tcod.loader import ffi, lib -from tcod.sdl import _check, _get_error, _ProtectedContext +from tcod.sdl._internal import _check, _get_error, _ProtectedContext def _get_format(format: DTypeLike) -> int: diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 5e2390ec..2a00d0cc 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -12,7 +12,7 @@ import tcod.sdl.sys from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p +from tcod.sdl._internal import _check, _check_p _HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { lib.SDL_HAT_CENTERED or 0: (0, 0), diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 0de8ddbc..5c7543a5 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -17,7 +17,7 @@ import tcod.event import tcod.sdl.video from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p +from tcod.sdl._internal import _check, _check_p class Cursor: diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 65cc6d41..06962f93 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -13,7 +13,7 @@ import tcod.sdl.video from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p, _required_version +from tcod.sdl._internal import _check, _check_p, _required_version class TextureAccess(enum.IntEnum): diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index f2b65fb4..b0cbd08b 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -5,7 +5,7 @@ from typing import Any from tcod.loader import ffi, lib -from tcod.sdl import _check, _get_error +from tcod.sdl._internal import _check, _get_error class Subsystem(enum.IntFlag): diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 0a4ad7a0..21af4ecc 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -16,7 +16,7 @@ from numpy.typing import ArrayLike, NDArray from tcod.loader import ffi, lib -from tcod.sdl import _check, _check_p, _required_version, _version_at_least +from tcod.sdl._internal import _check, _check_p, _required_version, _version_at_least __all__ = ( "WindowFlags", From d45b5a0894a82085d7cd48fa68587ffcc85084ce Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 3 Jun 2023 23:04:15 -0700 Subject: [PATCH 0805/1101] Test Sphinx doc generation. Need to detect issues early so that docs don't break on main. --- .github/workflows/python-package.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 95e391c2..38ad5cb1 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -163,6 +163,31 @@ jobs: path: dist/*.whl retention-days: 7 + test-docs: + needs: [black, isort, mypy] + runs-on: ubuntu-latest + steps: + - name: Install APT dependencies + run: | + sudo apt-get update + sudo apt-get install libsdl2-dev + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Install package + run: pip install -e . + env: + TDL_BUILD: DEBUG + - name: Test doc generation + working-directory: docs + run: python -m sphinx -T -E -W --keep-going . _build/html + isolated: # Test installing the package from source. needs: [black, isort, mypy, sdist] runs-on: ${{ matrix.os }} From 64cb4384d7acd10b6c8a70ef98291490c3c6e43f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 3 Jun 2023 23:38:15 -0700 Subject: [PATCH 0806/1101] Remove obsolete mocks needed for doc generation. The new doc generator can install tcod, so these mocks now just get in the way of maintenance. --- build_libtcod.py | 2 +- pyproject.toml | 3 ++ tcod/__init__.py | 6 +--- tcod/loader.py | 37 ++++---------------- tcod/sdl/_internal.py | 11 +++--- tcod/sdl/joystick.py | 78 +++++++++++++++++++++---------------------- tcod/sdl/sys.py | 2 +- tcod/sdl/video.py | 44 ++++++++++++------------ 8 files changed, 78 insertions(+), 105 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 3f5d2772..8f34ac3f 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -248,7 +248,7 @@ def find_sdl_attrs(prefix: str) -> Iterator[tuple[str, int | str | Any]]: `prefix` is used to filter out which names to copy. """ - from tcod._libtcod import lib # type: ignore + from tcod._libtcod import lib if prefix.startswith("SDL_"): name_starts_at = 4 diff --git a/pyproject.toml b/pyproject.toml index 4c1c47a0..75279cba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,6 +141,9 @@ ignore_missing_imports = true module = "tcod.version" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "tcod._libtcod" +ignore_missing_imports = true [tool.ruff] # https://beta.ruff.rs/docs/rules/ diff --git a/tcod/__init__.py b/tcod/__init__.py index 0082d521..22d183b4 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -18,11 +18,7 @@ from tcod.constants import * # noqa: F403 from tcod.libtcodpy import * # noqa: F403 from tcod.loader import __sdl_version__, ffi, lib - -try: - from tcod.version import __version__ -except ImportError: # Gets imported without version.py by ReadTheDocs - __version__ = "" +from tcod.version import __version__ def __getattr__(name: str, stacklevel: int = 1) -> color.Color: diff --git a/tcod/loader.py b/tcod/loader.py index 36e6d17b..de6c2efe 100644 --- a/tcod/loader.py +++ b/tcod/loader.py @@ -57,34 +57,10 @@ def get_sdl_version() -> str: os.environ["PATH"] = f"""{Path(__file__).parent / get_architecture()}{os.pathsep}{os.environ["PATH"]}""" -class _Mock: - """Mock object needed for ReadTheDocs.""" +verify_dependencies() +from tcod._libtcod import ffi, lib # noqa - @staticmethod - def def_extern() -> Any: - """Pass def_extern call silently.""" - return lambda func: func - - def __getattr__(self, attr: str) -> None: - """Return None on any attribute.""" - - def __bool__(self) -> bool: - """Allow checking for this mock object at import time.""" - return False - - -lib: Any = None -ffi: Any = None - -if os.environ.get("READTHEDOCS"): - # Mock the lib and ffi objects needed to compile docs for readthedocs.io - # Allows an import without building the cffi module first. - lib = ffi = _Mock() -else: - verify_dependencies() - from tcod._libtcod import ffi, lib # type: ignore - - __sdl_version__ = get_sdl_version() +__sdl_version__ = get_sdl_version() @ffi.def_extern() # type: ignore @@ -96,8 +72,7 @@ def _libtcod_log_watcher(message: Any, userdata: None) -> None: logger.log(level, "%s:%d:%s", source, lineno, text) -if lib: - lib.TCOD_set_log_callback(lib._libtcod_log_watcher, ffi.NULL) - lib.TCOD_set_log_level(0) +lib.TCOD_set_log_callback(lib._libtcod_log_watcher, ffi.NULL) +lib.TCOD_set_log_level(0) -__all__ = ["ffi", "lib"] +__all__ = ["ffi", "lib", "__sdl_version__"] diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 79d59339..27435c6d 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -89,12 +89,6 @@ def _check_p(result: Any) -> Any: return result -if lib._sdl_log_output_function: - lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) - if __debug__: - lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) - - def _compiled_version() -> tuple[int, int, int]: return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) @@ -124,3 +118,8 @@ def replacement(*_args: Any, **_kwargs: Any) -> Any: raise RuntimeError(msg) return lambda x: replacement # type: ignore[return-value] + + +lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) +if __debug__: + lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 2a00d0cc..c68ff682 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -15,69 +15,69 @@ from tcod.sdl._internal import _check, _check_p _HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { - lib.SDL_HAT_CENTERED or 0: (0, 0), - lib.SDL_HAT_UP or 0: (0, -1), - lib.SDL_HAT_RIGHT or 0: (1, 0), - lib.SDL_HAT_DOWN or 0: (0, 1), - lib.SDL_HAT_LEFT or 0: (-1, 0), - lib.SDL_HAT_RIGHTUP or 0: (1, -1), - lib.SDL_HAT_RIGHTDOWN or 0: (1, 1), - lib.SDL_HAT_LEFTUP or 0: (-1, -1), - lib.SDL_HAT_LEFTDOWN or 0: (-1, 1), + int(lib.SDL_HAT_CENTERED): (0, 0), + int(lib.SDL_HAT_UP): (0, -1), + int(lib.SDL_HAT_RIGHT): (1, 0), + int(lib.SDL_HAT_DOWN): (0, 1), + int(lib.SDL_HAT_LEFT): (-1, 0), + int(lib.SDL_HAT_RIGHTUP): (1, -1), + int(lib.SDL_HAT_RIGHTDOWN): (1, 1), + int(lib.SDL_HAT_LEFTUP): (-1, -1), + int(lib.SDL_HAT_LEFTDOWN): (-1, 1), } class ControllerAxis(enum.IntEnum): """The standard axes for a game controller.""" - INVALID = lib.SDL_CONTROLLER_AXIS_INVALID or -1 - LEFTX = lib.SDL_CONTROLLER_AXIS_LEFTX or 0 + INVALID = int(lib.SDL_CONTROLLER_AXIS_INVALID) + LEFTX = int(lib.SDL_CONTROLLER_AXIS_LEFTX) """""" - LEFTY = lib.SDL_CONTROLLER_AXIS_LEFTY or 1 + LEFTY = int(lib.SDL_CONTROLLER_AXIS_LEFTY) """""" - RIGHTX = lib.SDL_CONTROLLER_AXIS_RIGHTX or 2 + RIGHTX = int(lib.SDL_CONTROLLER_AXIS_RIGHTX) """""" - RIGHTY = lib.SDL_CONTROLLER_AXIS_RIGHTY or 3 + RIGHTY = int(lib.SDL_CONTROLLER_AXIS_RIGHTY) """""" - TRIGGERLEFT = lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT or 4 + TRIGGERLEFT = int(lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT) """""" - TRIGGERRIGHT = lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT or 5 + TRIGGERRIGHT = int(lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT) """""" class ControllerButton(enum.IntEnum): """The standard buttons for a game controller.""" - INVALID = lib.SDL_CONTROLLER_BUTTON_INVALID or -1 - A = lib.SDL_CONTROLLER_BUTTON_A or 0 + INVALID = int(lib.SDL_CONTROLLER_BUTTON_INVALID) + A = int(lib.SDL_CONTROLLER_BUTTON_A) """""" - B = lib.SDL_CONTROLLER_BUTTON_B or 1 + B = int(lib.SDL_CONTROLLER_BUTTON_B) """""" - X = lib.SDL_CONTROLLER_BUTTON_X or 2 + X = int(lib.SDL_CONTROLLER_BUTTON_X) """""" - Y = lib.SDL_CONTROLLER_BUTTON_Y or 3 + Y = int(lib.SDL_CONTROLLER_BUTTON_Y) """""" - BACK = lib.SDL_CONTROLLER_BUTTON_BACK or 4 + BACK = int(lib.SDL_CONTROLLER_BUTTON_BACK) """""" - GUIDE = lib.SDL_CONTROLLER_BUTTON_GUIDE or 5 + GUIDE = int(lib.SDL_CONTROLLER_BUTTON_GUIDE) """""" - START = lib.SDL_CONTROLLER_BUTTON_START or 6 + START = int(lib.SDL_CONTROLLER_BUTTON_START) """""" - LEFTSTICK = lib.SDL_CONTROLLER_BUTTON_LEFTSTICK or 7 + LEFTSTICK = int(lib.SDL_CONTROLLER_BUTTON_LEFTSTICK) """""" - RIGHTSTICK = lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK or 8 + RIGHTSTICK = int(lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK) """""" - LEFTSHOULDER = lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER or 9 + LEFTSHOULDER = int(lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) """""" - RIGHTSHOULDER = lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER or 10 + RIGHTSHOULDER = int(lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) """""" - DPAD_UP = lib.SDL_CONTROLLER_BUTTON_DPAD_UP or 11 + DPAD_UP = int(lib.SDL_CONTROLLER_BUTTON_DPAD_UP) """""" - DPAD_DOWN = lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN or 12 + DPAD_DOWN = int(lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN) """""" - DPAD_LEFT = lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT or 13 + DPAD_LEFT = int(lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT) """""" - DPAD_RIGHT = lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT or 14 + DPAD_RIGHT = int(lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) """""" MISC1 = 15 """""" @@ -100,19 +100,19 @@ class Power(enum.IntEnum): :any:`Joystick.get_current_power` """ - UNKNOWN = lib.SDL_JOYSTICK_POWER_UNKNOWN or -1 + UNKNOWN = int(lib.SDL_JOYSTICK_POWER_UNKNOWN) """Power state is unknown.""" - EMPTY = lib.SDL_JOYSTICK_POWER_EMPTY or 0 + EMPTY = int(lib.SDL_JOYSTICK_POWER_EMPTY) """<= 5% power.""" - LOW = lib.SDL_JOYSTICK_POWER_LOW or 1 + LOW = int(lib.SDL_JOYSTICK_POWER_LOW) """<= 20% power.""" - MEDIUM = lib.SDL_JOYSTICK_POWER_MEDIUM or 2 + MEDIUM = int(lib.SDL_JOYSTICK_POWER_MEDIUM) """<= 70% power.""" - FULL = lib.SDL_JOYSTICK_POWER_FULL or 3 + FULL = int(lib.SDL_JOYSTICK_POWER_FULL) """<= 100% power.""" - WIRED = lib.SDL_JOYSTICK_POWER_WIRED or 4 + WIRED = int(lib.SDL_JOYSTICK_POWER_WIRED) """""" - MAX = lib.SDL_JOYSTICK_POWER_MAX or 5 + MAX = int(lib.SDL_JOYSTICK_POWER_MAX) """""" diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index b0cbd08b..15ba4fbf 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -17,7 +17,7 @@ class Subsystem(enum.IntFlag): GAMECONTROLLER = 0x00002000 EVENTS = 0x00004000 SENSOR = 0x00008000 - EVERYTHING = lib.SDL_INIT_EVERYTHING or 0 + EVERYTHING = int(lib.SDL_INIT_EVERYTHING) def init(flags: int = Subsystem.EVERYTHING) -> None: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 21af4ecc..7881a706 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -35,49 +35,49 @@ class WindowFlags(enum.IntFlag): https://wiki.libsdl.org/SDL_WindowFlags """ - FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN or 0 + FULLSCREEN = int(lib.SDL_WINDOW_FULLSCREEN) """""" - FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP or 0 + FULLSCREEN_DESKTOP = int(lib.SDL_WINDOW_FULLSCREEN_DESKTOP) """""" - OPENGL = lib.SDL_WINDOW_OPENGL or 0 + OPENGL = int(lib.SDL_WINDOW_OPENGL) """""" - SHOWN = lib.SDL_WINDOW_SHOWN or 0 + SHOWN = int(lib.SDL_WINDOW_SHOWN) """""" - HIDDEN = lib.SDL_WINDOW_HIDDEN or 0 + HIDDEN = int(lib.SDL_WINDOW_HIDDEN) """""" - BORDERLESS = lib.SDL_WINDOW_BORDERLESS or 0 + BORDERLESS = int(lib.SDL_WINDOW_BORDERLESS) """""" - RESIZABLE = lib.SDL_WINDOW_RESIZABLE or 0 + RESIZABLE = int(lib.SDL_WINDOW_RESIZABLE) """""" - MINIMIZED = lib.SDL_WINDOW_MINIMIZED or 0 + MINIMIZED = int(lib.SDL_WINDOW_MINIMIZED) """""" - MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED or 0 + MAXIMIZED = int(lib.SDL_WINDOW_MAXIMIZED) """""" - MOUSE_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED or 0 + MOUSE_GRABBED = int(lib.SDL_WINDOW_INPUT_GRABBED) """""" - INPUT_FOCUS = lib.SDL_WINDOW_INPUT_FOCUS or 0 + INPUT_FOCUS = int(lib.SDL_WINDOW_INPUT_FOCUS) """""" - MOUSE_FOCUS = lib.SDL_WINDOW_MOUSE_FOCUS or 0 + MOUSE_FOCUS = int(lib.SDL_WINDOW_MOUSE_FOCUS) """""" - FOREIGN = lib.SDL_WINDOW_FOREIGN or 0 + FOREIGN = int(lib.SDL_WINDOW_FOREIGN) """""" - ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI or 0 + ALLOW_HIGHDPI = int(lib.SDL_WINDOW_ALLOW_HIGHDPI) """""" - MOUSE_CAPTURE = lib.SDL_WINDOW_MOUSE_CAPTURE or 0 + MOUSE_CAPTURE = int(lib.SDL_WINDOW_MOUSE_CAPTURE) """""" - ALWAYS_ON_TOP = lib.SDL_WINDOW_ALWAYS_ON_TOP or 0 + ALWAYS_ON_TOP = int(lib.SDL_WINDOW_ALWAYS_ON_TOP) """""" - SKIP_TASKBAR = lib.SDL_WINDOW_SKIP_TASKBAR or 0 + SKIP_TASKBAR = int(lib.SDL_WINDOW_SKIP_TASKBAR) """""" - UTILITY = lib.SDL_WINDOW_UTILITY or 0 + UTILITY = int(lib.SDL_WINDOW_UTILITY) """""" - TOOLTIP = lib.SDL_WINDOW_TOOLTIP or 0 + TOOLTIP = int(lib.SDL_WINDOW_TOOLTIP) """""" - POPUP_MENU = lib.SDL_WINDOW_POPUP_MENU or 0 + POPUP_MENU = int(lib.SDL_WINDOW_POPUP_MENU) """""" - VULKAN = lib.SDL_WINDOW_VULKAN or 0 + VULKAN = int(lib.SDL_WINDOW_VULKAN) """""" - METAL = getattr(lib, "SDL_WINDOW_METAL", None) or 0x20000000 # SDL >= 2.0.14 + METAL = int(getattr(lib, "SDL_WINDOW_METAL", 0x20000000)) # SDL >= 2.0.14 """""" From a14058137efa08c27a89c9a75ea01536337b8446 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 00:03:50 -0700 Subject: [PATCH 0807/1101] Rename tcod.loader to tcod.cffi. Should be a better name since this is where the cffi API is exported from. --- tcod/__init__.py | 2 +- tcod/_internal.py | 2 +- tcod/bsp.py | 2 +- tcod/{loader.py => cffi.py} | 2 +- tcod/color.py | 2 +- tcod/console.py | 2 +- tcod/context.py | 2 +- tcod/event.py | 2 +- tcod/image.py | 2 +- tcod/libtcodpy.py | 2 +- tcod/los.py | 2 +- tcod/map.py | 2 +- tcod/noise.py | 2 +- tcod/path.py | 2 +- tcod/random.py | 2 +- tcod/render.py | 2 +- tcod/sdl/_internal.py | 2 +- tcod/sdl/audio.py | 2 +- tcod/sdl/joystick.py | 2 +- tcod/sdl/mouse.py | 2 +- tcod/sdl/render.py | 2 +- tcod/sdl/sys.py | 2 +- tcod/sdl/video.py | 2 +- tcod/tileset.py | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) rename tcod/{loader.py => cffi.py} (96%) diff --git a/tcod/__init__.py b/tcod/__init__.py index 22d183b4..fd461833 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -14,10 +14,10 @@ __path__ = extend_path(__path__, __name__) from tcod import bsp, color, console, constants, context, event, image, los, map, noise, path, random, tileset +from tcod.cffi import __sdl_version__, ffi, lib from tcod.console import Console from tcod.constants import * # noqa: F403 from tcod.libtcodpy import * # noqa: F403 -from tcod.loader import __sdl_version__, ffi, lib from tcod.version import __version__ diff --git a/tcod/_internal.py b/tcod/_internal.py index a9d2ba35..cd92a45c 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -10,7 +10,7 @@ from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) diff --git a/tcod/bsp.py b/tcod/bsp.py index 8734c340..a53b8f67 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -30,7 +30,7 @@ import tcod.random from tcod._internal import deprecate -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class BSP: diff --git a/tcod/loader.py b/tcod/cffi.py similarity index 96% rename from tcod/loader.py rename to tcod/cffi.py index de6c2efe..d733abea 100644 --- a/tcod/loader.py +++ b/tcod/cffi.py @@ -64,7 +64,7 @@ def get_sdl_version() -> str: @ffi.def_extern() # type: ignore -def _libtcod_log_watcher(message: Any, userdata: None) -> None: +def _libtcod_log_watcher(message: Any, userdata: None) -> None: # noqa: ANN401 text = str(ffi.string(message.message), encoding="utf-8") source = str(ffi.string(message.source), encoding="utf-8") level = int(message.level) diff --git a/tcod/color.py b/tcod/color.py index f5fed4c2..dfd97b8c 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -5,7 +5,7 @@ from typing import Any, List from tcod._internal import deprecate -from tcod.loader import lib +from tcod.cffi import lib class Color(List[int]): diff --git a/tcod/console.py b/tcod/console.py index f5147e23..d8c0540b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -16,7 +16,7 @@ import tcod._internal import tcod.constants from tcod._internal import _check, deprecate -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib def _fmt(string: str) -> bytes: diff --git a/tcod/context.py b/tcod/context.py index 79eb7df1..abfd3967 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -65,7 +65,7 @@ import tcod.sdl.video import tcod.tileset from tcod._internal import _check, _check_warn, pending_deprecate -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib __all__ = ( "Context", diff --git a/tcod/event.py b/tcod/event.py index 1379c40c..c8846d2c 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -91,9 +91,9 @@ import tcod.event_constants import tcod.sdl.joystick +from tcod.cffi import ffi, lib from tcod.event_constants import * # noqa: F403 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT -from tcod.loader import ffi, lib from tcod.sdl.joystick import _HAT_DIRECTIONS T = TypeVar("T") diff --git a/tcod/image.py b/tcod/image.py index 700c6290..dc147a54 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -19,7 +19,7 @@ import tcod.console from tcod._internal import _console, deprecate -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class Image: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 4a258081..4372abc9 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -36,6 +36,7 @@ deprecate, pending_deprecate, ) +from tcod.cffi import ffi, lib from tcod.color import Color from tcod.constants import * # noqa: F403 from tcod.constants import ( @@ -49,7 +50,6 @@ KEY_RELEASED, NOISE_DEFAULT, ) -from tcod.loader import ffi, lib # ruff: noqa: ANN401 PLR0913 # Functions are too deprecated to make changes. diff --git a/tcod/los.py b/tcod/los.py index d815504f..d6cd1362 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -6,7 +6,7 @@ import numpy as np from numpy.typing import NDArray -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib def bresenham(start: tuple[int, int], end: tuple[int, int]) -> NDArray[np.intc]: diff --git a/tcod/map.py b/tcod/map.py index e33a0984..820c981d 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -10,7 +10,7 @@ import tcod._internal import tcod.constants -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class Map: diff --git a/tcod/noise.py b/tcod/noise.py index 843f60b9..92b7636e 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -43,7 +43,7 @@ import tcod.constants import tcod.random -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class Algorithm(enum.IntEnum): diff --git a/tcod/path.py b/tcod/path.py index 9e2439c8..c25e9348 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -27,7 +27,7 @@ from typing_extensions import Literal from tcod._internal import _check -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib @ffi.def_extern() # type: ignore diff --git a/tcod/random.py b/tcod/random.py index 12169141..b574cab8 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -14,7 +14,7 @@ from typing import Any, Hashable import tcod.constants -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib MERSENNE_TWISTER = tcod.constants.RNG_MT COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC diff --git a/tcod/render.py b/tcod/render.py index 184d8abd..d01d8129 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -37,7 +37,7 @@ import tcod.sdl.render import tcod.tileset from tcod._internal import _check, _check_p -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class SDLTilesetAtlas: diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 27435c6d..00dc868c 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -7,7 +7,7 @@ from types import TracebackType from typing import Any, Callable, TypeVar -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib T = TypeVar("T") diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index f8261097..e8af0fee 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -55,7 +55,7 @@ from typing_extensions import Final, Literal, Self import tcod.sdl.sys -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _get_error, _ProtectedContext diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index c68ff682..8ab8637f 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -11,7 +11,7 @@ from typing_extensions import Final, Literal import tcod.sdl.sys -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p _HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 5c7543a5..6a93955a 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -16,7 +16,7 @@ import tcod.event import tcod.sdl.video -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 06962f93..612cb972 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -12,7 +12,7 @@ from typing_extensions import Final, Literal import tcod.sdl.video -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p, _required_version diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 15ba4fbf..b2a71a66 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -4,7 +4,7 @@ import warnings from typing import Any -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _get_error diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 7881a706..64b8cf20 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -15,7 +15,7 @@ import numpy as np from numpy.typing import ArrayLike, NDArray -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p, _required_version, _version_at_least __all__ = ( diff --git a/tcod/tileset.py b/tcod/tileset.py index 9f13f691..533b9c99 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -22,7 +22,7 @@ import tcod.console from tcod._internal import _check, _console, _raise_tcod_error, deprecate -from tcod.loader import ffi, lib +from tcod.cffi import ffi, lib class Tileset: From b63753711843222011a9d2062b5fa8174265dab8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 02:31:57 -0700 Subject: [PATCH 0808/1101] Deprecate implicit libtcodpy and constants. These are needed to move away from the `tcod` module, so that it can eventually be converted to an implicit namespace. --- CHANGELOG.md | 5 + build_libtcod.py | 1 - examples/samples_tcod.py | 53 ++-- libtcodpy.py | 4 +- tcod/__init__.py | 579 +-------------------------------------- tcod/libtcodpy.py | 16 +- tcod/tcod.py | 89 ++++++ tests/test_deprecated.py | 29 +- tests/test_libtcodpy.py | 4 +- 9 files changed, 158 insertions(+), 622 deletions(-) create mode 100644 tcod/tcod.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f0e36e..6b424a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Deprecated +- Deprecated using `tcod` as an implicit alias for `libtcodpy`. + You should use `from tcod import libtcodpy` if you want to access this module. +- Deprecated constants being held in `tcod`, get these from `tcod.constants` instead. +- Deprecated `tcod.Console` which should be accessed from `tcod.console.Console` instead. ## [16.0.2] - 2023-06-02 ### Fixed diff --git a/build_libtcod.py b/build_libtcod.py index 8f34ac3f..b912ecc1 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -366,7 +366,6 @@ def write_library_constants() -> None: all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") - update_module_all(Path("tcod/__init__.py"), all_names_merged) update_module_all(Path("tcod/libtcodpy.py"), all_names_merged) with Path("tcod/event_constants.py").open("w", encoding="utf-8") as f: diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 35e1832d..1e23a5c4 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -25,6 +25,7 @@ import tcod.render import tcod.sdl.mouse import tcod.sdl.render +from tcod import libtcodpy # ruff: noqa: S311 @@ -746,76 +747,76 @@ def on_draw(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] != "#": - tcod.console_set_char_background( + libtcodpy.console_set_char_background( sample_console, x, y, - tcod.color_lerp( # type: ignore + libtcodpy.color_lerp( # type: ignore LIGHT_GROUND, DARK_GROUND, - 0.9 * tcod.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, + 0.9 * libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, ), tcod.BKGND_SET, ) - for i in range(tcod.dijkstra_size(self.dijkstra)): - x, y = tcod.dijkstra_get(self.dijkstra, i) - tcod.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET) + for i in range(libtcodpy.dijkstra_size(self.dijkstra)): + x, y = libtcodpy.dijkstra_get(self.dijkstra, i) + libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.constants.BKGND_SET) # move the creature self.busy -= frame_length[-1] if self.busy <= 0.0: self.busy = 0.2 if self.using_astar: - if not tcod.path_is_empty(self.path): - tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) - self.px, self.py = tcod.path_walk(self.path, True) # type: ignore - tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) + if not libtcodpy.path_is_empty(self.path): + libtcodpy.console_put_char(sample_console, self.px, self.py, " ", tcod.constants.BKGND_NONE) + self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore + libtcodpy.console_put_char(sample_console, self.px, self.py, "@", tcod.constants.BKGND_NONE) else: - if not tcod.dijkstra_is_empty(self.dijkstra): - tcod.console_put_char(sample_console, self.px, self.py, " ", tcod.BKGND_NONE) - self.px, self.py = tcod.dijkstra_path_walk(self.dijkstra) # type: ignore - tcod.console_put_char(sample_console, self.px, self.py, "@", tcod.BKGND_NONE) + if not libtcodpy.dijkstra_is_empty(self.dijkstra): + libtcodpy.console_put_char(sample_console, self.px, self.py, " ", tcod.constants.BKGND_NONE) + self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore + libtcodpy.console_put_char(sample_console, self.px, self.py, "@", tcod.constants.BKGND_NONE) self.recalculate = True def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.i and self.dy > 0: # destination move north - tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) self.dy -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.k and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) self.dy += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.j and self.dx > 0: # destination move west - tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) self.dx -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.l and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) self.dx += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.TAB: self.using_astar = not self.using_astar if self.using_astar: - tcod.console_print(sample_console, 1, 4, "Using : A* ") + libtcodpy.console_print(sample_console, 1, 4, "Using : A* ") else: - tcod.console_print(sample_console, 1, 4, "Using : Dijkstra") + libtcodpy.console_print(sample_console, 1, 4, "Using : Dijkstra") self.recalculate = True else: super().ev_keydown(event) @@ -824,11 +825,11 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my): - tcod.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) self.dx = mx self.dy = my self.oldchar = sample_console.ch[self.dx, self.dy] - tcod.console_put_char(sample_console, self.dx, self.dy, "+", tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True diff --git a/libtcodpy.py b/libtcodpy.py index 63e2e0b0..7947f394 100644 --- a/libtcodpy.py +++ b/libtcodpy.py @@ -1,7 +1,7 @@ -"""Module alias for tcod.""" +"""Deprecated module alias for tcod.libtcodpy, use 'import tcod as libtcodpy' instead.""" import warnings -from tcod import * # noqa: F4 +from tcod.libtcodpy import * # noqa: F403 from tcod.libtcodpy import __getattr__ # noqa: F401 warnings.warn( diff --git a/tcod/__init__.py b/tcod/__init__.py index fd461833..b8ea0d25 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -8,34 +8,16 @@ """ from __future__ import annotations -import warnings from pkgutil import extend_path __path__ = extend_path(__path__, __name__) -from tcod import bsp, color, console, constants, context, event, image, los, map, noise, path, random, tileset +from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset from tcod.cffi import __sdl_version__, ffi, lib -from tcod.console import Console -from tcod.constants import * # noqa: F403 -from tcod.libtcodpy import * # noqa: F403 +from tcod.tcod import __getattr__ # noqa: F401 from tcod.version import __version__ - -def __getattr__(name: str, stacklevel: int = 1) -> color.Color: - """Mark access to color constants as deprecated.""" - value: color.Color | None = getattr(constants, name, None) - if value is None: - msg = f"module {__name__!r} has no attribute {name!r}" - raise AttributeError(msg) - warnings.warn( - f"Color constants will be removed from future releases.\nReplace `tcod.{name}` with `{tuple(value)}`.", - FutureWarning, - stacklevel=stacklevel + 1, - ) - return value - - -__all__ = [ # noqa: F405 +__all__ = [ "__version__", "__sdl_version__", "lib", @@ -54,559 +36,4 @@ def __getattr__(name: str, stacklevel: int = 1) -> color.Color: "random", "tileset", "Console", - # --- From libtcodpy.py --- - "Color", - "Bsp", - "NB_FOV_ALGORITHMS", - "NOISE_DEFAULT_HURST", - "NOISE_DEFAULT_LACUNARITY", - "ConsoleBuffer", - "Dice", - "Key", - "Mouse", - "FOV_PERMISSIVE", - "BKGND_ALPHA", - "BKGND_ADDALPHA", - "bsp_new_with_size", - "bsp_split_once", - "bsp_split_recursive", - "bsp_resize", - "bsp_left", - "bsp_right", - "bsp_father", - "bsp_is_leaf", - "bsp_contains", - "bsp_find_node", - "bsp_traverse_pre_order", - "bsp_traverse_in_order", - "bsp_traverse_post_order", - "bsp_traverse_level_order", - "bsp_traverse_inverted_level_order", - "bsp_remove_sons", - "bsp_delete", - "color_lerp", - "color_set_hsv", - "color_get_hsv", - "color_scale_HSV", - "color_gen_map", - "console_init_root", - "console_set_custom_font", - "console_get_width", - "console_get_height", - "console_map_ascii_code_to_font", - "console_map_ascii_codes_to_font", - "console_map_string_to_font", - "console_is_fullscreen", - "console_set_fullscreen", - "console_is_window_closed", - "console_has_mouse_focus", - "console_is_active", - "console_set_window_title", - "console_credits", - "console_credits_reset", - "console_credits_render", - "console_flush", - "console_set_default_background", - "console_set_default_foreground", - "console_clear", - "console_put_char", - "console_put_char_ex", - "console_set_char_background", - "console_set_char_foreground", - "console_set_char", - "console_set_background_flag", - "console_get_background_flag", - "console_set_alignment", - "console_get_alignment", - "console_print", - "console_print_ex", - "console_print_rect", - "console_print_rect_ex", - "console_get_height_rect", - "console_rect", - "console_hline", - "console_vline", - "console_print_frame", - "console_set_color_control", - "console_get_default_background", - "console_get_default_foreground", - "console_get_char_background", - "console_get_char_foreground", - "console_get_char", - "console_set_fade", - "console_get_fade", - "console_get_fading_color", - "console_wait_for_keypress", - "console_check_for_keypress", - "console_is_key_pressed", - "console_new", - "console_from_file", - "console_blit", - "console_set_key_color", - "console_delete", - "console_fill_foreground", - "console_fill_background", - "console_fill_char", - "console_load_asc", - "console_save_asc", - "console_load_apf", - "console_save_apf", - "console_load_xp", - "console_save_xp", - "console_from_xp", - "console_list_load_xp", - "console_list_save_xp", - "path_new_using_map", - "path_new_using_function", - "path_compute", - "path_get_origin", - "path_get_destination", - "path_size", - "path_reverse", - "path_get", - "path_is_empty", - "path_walk", - "path_delete", - "dijkstra_new", - "dijkstra_new_using_function", - "dijkstra_compute", - "dijkstra_path_set", - "dijkstra_get_distance", - "dijkstra_size", - "dijkstra_reverse", - "dijkstra_get", - "dijkstra_is_empty", - "dijkstra_path_walk", - "dijkstra_delete", - "heightmap_new", - "heightmap_set_value", - "heightmap_add", - "heightmap_scale", - "heightmap_clear", - "heightmap_clamp", - "heightmap_copy", - "heightmap_normalize", - "heightmap_lerp_hm", - "heightmap_add_hm", - "heightmap_multiply_hm", - "heightmap_add_hill", - "heightmap_dig_hill", - "heightmap_rain_erosion", - "heightmap_kernel_transform", - "heightmap_add_voronoi", - "heightmap_add_fbm", - "heightmap_scale_fbm", - "heightmap_dig_bezier", - "heightmap_get_value", - "heightmap_get_interpolated_value", - "heightmap_get_slope", - "heightmap_get_normal", - "heightmap_count_cells", - "heightmap_has_land_on_border", - "heightmap_get_minmax", - "heightmap_delete", - "image_new", - "image_clear", - "image_invert", - "image_hflip", - "image_rotate90", - "image_vflip", - "image_scale", - "image_set_key_color", - "image_get_alpha", - "image_is_pixel_transparent", - "image_load", - "image_from_console", - "image_refresh_console", - "image_get_size", - "image_get_pixel", - "image_get_mipmap_pixel", - "image_put_pixel", - "image_blit", - "image_blit_rect", - "image_blit_2x", - "image_save", - "image_delete", - "line_init", - "line_step", - "line", - "line_iter", - "line_where", - "map_new", - "map_copy", - "map_set_properties", - "map_clear", - "map_compute_fov", - "map_is_in_fov", - "map_is_transparent", - "map_is_walkable", - "map_delete", - "map_get_width", - "map_get_height", - "mouse_show_cursor", - "mouse_is_cursor_visible", - "mouse_move", - "mouse_get_status", - "namegen_parse", - "namegen_generate", - "namegen_generate_custom", - "namegen_get_sets", - "namegen_destroy", - "noise_new", - "noise_set_type", - "noise_get", - "noise_get_fbm", - "noise_get_turbulence", - "noise_delete", - "parser_new", - "parser_new_struct", - "parser_run", - "parser_delete", - "parser_get_bool_property", - "parser_get_int_property", - "parser_get_char_property", - "parser_get_float_property", - "parser_get_string_property", - "parser_get_color_property", - "parser_get_dice_property", - "parser_get_list_property", - "random_get_instance", - "random_new", - "random_new_from_seed", - "random_set_distribution", - "random_get_int", - "random_get_float", - "random_get_double", - "random_get_int_mean", - "random_get_float_mean", - "random_get_double_mean", - "random_save", - "random_restore", - "random_delete", - "struct_add_flag", - "struct_add_property", - "struct_add_value_list", - "struct_add_list_property", - "struct_add_structure", - "struct_get_name", - "struct_is_mandatory", - "struct_get_type", - "sys_set_fps", - "sys_get_fps", - "sys_get_last_frame_length", - "sys_sleep_milli", - "sys_elapsed_milli", - "sys_elapsed_seconds", - "sys_set_renderer", - "sys_get_renderer", - "sys_save_screenshot", - "sys_force_fullscreen_resolution", - "sys_get_current_resolution", - "sys_get_char_size", - "sys_update_char", - "sys_register_SDL_renderer", - "sys_check_for_event", - "sys_wait_for_event", - "sys_clipboard_set", - "sys_clipboard_get", - # --- From constants.py --- - "FOV_BASIC", - "FOV_DIAMOND", - "FOV_PERMISSIVE_0", - "FOV_PERMISSIVE_1", - "FOV_PERMISSIVE_2", - "FOV_PERMISSIVE_3", - "FOV_PERMISSIVE_4", - "FOV_PERMISSIVE_5", - "FOV_PERMISSIVE_6", - "FOV_PERMISSIVE_7", - "FOV_PERMISSIVE_8", - "FOV_RESTRICTIVE", - "FOV_SHADOW", - "FOV_SYMMETRIC_SHADOWCAST", - "KEY_0", - "KEY_1", - "KEY_2", - "KEY_3", - "KEY_4", - "KEY_5", - "KEY_6", - "KEY_7", - "KEY_8", - "KEY_9", - "KEY_ALT", - "KEY_APPS", - "KEY_BACKSPACE", - "KEY_CAPSLOCK", - "KEY_CHAR", - "KEY_CONTROL", - "KEY_DELETE", - "KEY_DOWN", - "KEY_END", - "KEY_ENTER", - "KEY_ESCAPE", - "KEY_F1", - "KEY_F10", - "KEY_F11", - "KEY_F12", - "KEY_F2", - "KEY_F3", - "KEY_F4", - "KEY_F5", - "KEY_F6", - "KEY_F7", - "KEY_F8", - "KEY_F9", - "KEY_HOME", - "KEY_INSERT", - "KEY_KP0", - "KEY_KP1", - "KEY_KP2", - "KEY_KP3", - "KEY_KP4", - "KEY_KP5", - "KEY_KP6", - "KEY_KP7", - "KEY_KP8", - "KEY_KP9", - "KEY_KPADD", - "KEY_KPDEC", - "KEY_KPDIV", - "KEY_KPENTER", - "KEY_KPMUL", - "KEY_KPSUB", - "KEY_LEFT", - "KEY_LWIN", - "KEY_NONE", - "KEY_NUMLOCK", - "KEY_PAGEDOWN", - "KEY_PAGEUP", - "KEY_PAUSE", - "KEY_PRINTSCREEN", - "KEY_RIGHT", - "KEY_RWIN", - "KEY_SCROLLLOCK", - "KEY_SHIFT", - "KEY_SPACE", - "KEY_TAB", - "KEY_TEXT", - "KEY_UP", - "BKGND_ADD", - "BKGND_ADDA", - "BKGND_ALPH", - "BKGND_BURN", - "BKGND_COLOR_BURN", - "BKGND_COLOR_DODGE", - "BKGND_DARKEN", - "BKGND_DEFAULT", - "BKGND_LIGHTEN", - "BKGND_MULTIPLY", - "BKGND_NONE", - "BKGND_OVERLAY", - "BKGND_SCREEN", - "BKGND_SET", - "CENTER", - "CHAR_ARROW2_E", - "CHAR_ARROW2_N", - "CHAR_ARROW2_S", - "CHAR_ARROW2_W", - "CHAR_ARROW_E", - "CHAR_ARROW_N", - "CHAR_ARROW_S", - "CHAR_ARROW_W", - "CHAR_BLOCK1", - "CHAR_BLOCK2", - "CHAR_BLOCK3", - "CHAR_BULLET", - "CHAR_BULLET_INV", - "CHAR_BULLET_SQUARE", - "CHAR_CENT", - "CHAR_CHECKBOX_SET", - "CHAR_CHECKBOX_UNSET", - "CHAR_CLUB", - "CHAR_COPYRIGHT", - "CHAR_CROSS", - "CHAR_CURRENCY", - "CHAR_DARROW_H", - "CHAR_DARROW_V", - "CHAR_DCROSS", - "CHAR_DHLINE", - "CHAR_DIAMOND", - "CHAR_DIVISION", - "CHAR_DNE", - "CHAR_DNW", - "CHAR_DSE", - "CHAR_DSW", - "CHAR_DTEEE", - "CHAR_DTEEN", - "CHAR_DTEES", - "CHAR_DTEEW", - "CHAR_DVLINE", - "CHAR_EXCLAM_DOUBLE", - "CHAR_FEMALE", - "CHAR_FUNCTION", - "CHAR_GRADE", - "CHAR_HALF", - "CHAR_HEART", - "CHAR_HLINE", - "CHAR_LIGHT", - "CHAR_MALE", - "CHAR_MULTIPLICATION", - "CHAR_NE", - "CHAR_NOTE", - "CHAR_NOTE_DOUBLE", - "CHAR_NW", - "CHAR_ONE_QUARTER", - "CHAR_PILCROW", - "CHAR_POUND", - "CHAR_POW1", - "CHAR_POW2", - "CHAR_POW3", - "CHAR_RADIO_SET", - "CHAR_RADIO_UNSET", - "CHAR_RESERVED", - "CHAR_SE", - "CHAR_SECTION", - "CHAR_SMILIE", - "CHAR_SMILIE_INV", - "CHAR_SPADE", - "CHAR_SUBP_DIAG", - "CHAR_SUBP_E", - "CHAR_SUBP_N", - "CHAR_SUBP_NE", - "CHAR_SUBP_NW", - "CHAR_SUBP_SE", - "CHAR_SUBP_SW", - "CHAR_SW", - "CHAR_TEEE", - "CHAR_TEEN", - "CHAR_TEES", - "CHAR_TEEW", - "CHAR_THREE_QUARTERS", - "CHAR_UMLAUT", - "CHAR_VLINE", - "CHAR_YEN", - "COLCTRL_1", - "COLCTRL_2", - "COLCTRL_3", - "COLCTRL_4", - "COLCTRL_5", - "COLCTRL_BACK_RGB", - "COLCTRL_FORE_RGB", - "COLCTRL_NUMBER", - "COLCTRL_STOP", - "COLOR_AMBER", - "COLOR_AZURE", - "COLOR_BLUE", - "COLOR_CHARTREUSE", - "COLOR_CRIMSON", - "COLOR_CYAN", - "COLOR_DARK", - "COLOR_DARKER", - "COLOR_DARKEST", - "COLOR_DESATURATED", - "COLOR_FLAME", - "COLOR_FUCHSIA", - "COLOR_GREEN", - "COLOR_HAN", - "COLOR_LEVELS", - "COLOR_LIGHT", - "COLOR_LIGHTER", - "COLOR_LIGHTEST", - "COLOR_LIME", - "COLOR_MAGENTA", - "COLOR_NB", - "COLOR_NORMAL", - "COLOR_ORANGE", - "COLOR_PINK", - "COLOR_PURPLE", - "COLOR_RED", - "COLOR_SEA", - "COLOR_SKY", - "COLOR_TURQUOISE", - "COLOR_VIOLET", - "COLOR_YELLOW", - "DISTRIBUTION_GAUSSIAN", - "DISTRIBUTION_GAUSSIAN_INVERSE", - "DISTRIBUTION_GAUSSIAN_RANGE", - "DISTRIBUTION_GAUSSIAN_RANGE_INVERSE", - "DISTRIBUTION_LINEAR", - "EVENT_ANY", - "EVENT_FINGER", - "EVENT_FINGER_MOVE", - "EVENT_FINGER_PRESS", - "EVENT_FINGER_RELEASE", - "EVENT_KEY", - "EVENT_KEY_PRESS", - "EVENT_KEY_RELEASE", - "EVENT_MOUSE", - "EVENT_MOUSE_MOVE", - "EVENT_MOUSE_PRESS", - "EVENT_MOUSE_RELEASE", - "EVENT_NONE", - "FONT_LAYOUT_ASCII_INCOL", - "FONT_LAYOUT_ASCII_INROW", - "FONT_LAYOUT_CP437", - "FONT_LAYOUT_TCOD", - "FONT_TYPE_GRAYSCALE", - "FONT_TYPE_GREYSCALE", - "KEY_PRESSED", - "KEY_RELEASED", - "LEFT", - "NB_RENDERERS", - "NOISE_DEFAULT", - "NOISE_PERLIN", - "NOISE_SIMPLEX", - "NOISE_WAVELET", - "RENDERER_GLSL", - "RENDERER_OPENGL", - "RENDERER_OPENGL2", - "RENDERER_SDL", - "RENDERER_SDL2", - "RENDERER_XTERM", - "RIGHT", - "RNG_CMWC", - "RNG_MT", - "TYPE_BOOL", - "TYPE_CHAR", - "TYPE_COLOR", - "TYPE_CUSTOM00", - "TYPE_CUSTOM01", - "TYPE_CUSTOM02", - "TYPE_CUSTOM03", - "TYPE_CUSTOM04", - "TYPE_CUSTOM05", - "TYPE_CUSTOM06", - "TYPE_CUSTOM07", - "TYPE_CUSTOM08", - "TYPE_CUSTOM09", - "TYPE_CUSTOM10", - "TYPE_CUSTOM11", - "TYPE_CUSTOM12", - "TYPE_CUSTOM13", - "TYPE_CUSTOM14", - "TYPE_CUSTOM15", - "TYPE_DICE", - "TYPE_FLOAT", - "TYPE_INT", - "TYPE_LIST", - "TYPE_NONE", - "TYPE_STRING", - "TYPE_VALUELIST00", - "TYPE_VALUELIST01", - "TYPE_VALUELIST02", - "TYPE_VALUELIST03", - "TYPE_VALUELIST04", - "TYPE_VALUELIST05", - "TYPE_VALUELIST06", - "TYPE_VALUELIST07", - "TYPE_VALUELIST08", - "TYPE_VALUELIST09", - "TYPE_VALUELIST10", - "TYPE_VALUELIST11", - "TYPE_VALUELIST12", - "TYPE_VALUELIST13", - "TYPE_VALUELIST14", - "TYPE_VALUELIST15", - # --- End constants.py --- ] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 4372abc9..fc2f9c9f 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -15,6 +15,7 @@ import tcod.bsp import tcod.console +import tcod.constants import tcod.image import tcod.los import tcod.map @@ -4274,11 +4275,16 @@ def _atexit_verify() -> None: def __getattr__(name: str) -> Color: """Mark access to color constants as deprecated.""" - try: - return tcod.__getattr__(name, stacklevel=2) # type: ignore[call-arg] - except AttributeError: - msg = f"module {__name__!r} has no attribute {name!r}" - raise AttributeError(msg) from None + value: object = getattr(tcod.constants, name, None) + if isinstance(value, Color): + warnings.warn( + f"Color constants will be removed from future releases.\nReplace 'tcod.{name}' with '{tuple(value)}'.", + FutureWarning, + stacklevel=2, + ) + return value + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) from None __all__ = [ # noqa: F405 diff --git a/tcod/tcod.py b/tcod/tcod.py new file mode 100644 index 00000000..b5691729 --- /dev/null +++ b/tcod/tcod.py @@ -0,0 +1,89 @@ +"""The fast Python port of libtcod. + +This module can be used as a drop in replacement for the official libtcodpy module. + +Bring any issues or feature requests to GitHub: https://github.com/libtcod/python-tcod + +Read the documentation online: https://python-tcod.readthedocs.io/en/latest/ +""" +from __future__ import annotations + +import warnings +from typing import Any + +from tcod import ( + bsp, + color, + console, + constants, + context, + event, + image, + libtcodpy, + los, + map, + noise, + path, + random, + tileset, +) +from tcod.version import __version__ + + +def __getattr__(name: str, stacklevel: int = 1) -> Any: # noqa: ANN401 + """Mark access to color constants as deprecated.""" + if name == "Console": + warnings.warn( + "tcod.Console is deprecated.\nReplace 'tcod.Console' with 'tcod.console.Console'", + FutureWarning, + stacklevel=stacklevel + 1, + ) + return console.Console + value: Any = getattr(constants, name, None) + if isinstance(value, color.Color): + warnings.warn( + f"Color constants will be removed from future releases.\nReplace 'tcod.{name}' with '{tuple(value)}'", + FutureWarning, + stacklevel=stacklevel + 1, + ) + return value + if value is not None: + warnings.warn( + "Soon the 'tcod' module will no longer hold constants directly." + "\nAdd 'import tcod.constants' if you haven't already." + f"\nReplace 'tcod.{name}' with 'tcod.constants.{name}'", + FutureWarning, + stacklevel=stacklevel + 1, + ) + return value + value = getattr(libtcodpy, name, None) + if value is not None: + warnings.warn( + "Soon the 'tcod' module will no longer be an implicit reference to 'libtcodpy'." + "\nAdd 'from tcod import libtcodpy' if you haven't already." + f"\nReplace 'tcod.{name}' with 'libtcodpy.{name}'", + FutureWarning, + stacklevel=stacklevel + 1, + ) + return value + + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) + + +__all__ = [ + "__version__", + "bsp", + "color", + "console", + "context", + "event", + "tileset", + "image", + "los", + "map", + "noise", + "path", + "random", + "tileset", +] diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 9d871ba7..f841cad6 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -5,6 +5,7 @@ import pytest import tcod +import tcod.constants import tcod.event import tcod.libtcodpy @@ -14,21 +15,29 @@ # ruff: noqa: D103 -@pytest.mark.filterwarnings("error") def test_deprecate_color() -> None: - with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): - _ = tcod.black - with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): - _ = tcod.libtcodpy.black - with pytest.raises(FutureWarning, match=r".*\(0, 0, 0\)"): - _ = libtcodpy.black + with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"): + assert tcod.black is tcod.constants.black + with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"): + assert tcod.libtcodpy.black is tcod.constants.black + with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"): + assert libtcodpy.black is tcod.constants.black + + +def test_constants() -> None: + with pytest.warns(match=r"tcod.constants.RENDERER_SDL2"): + assert tcod.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2 + + +def test_implicit_libtcodpy() -> None: + with pytest.warns(match=r"libtcodpy.console_init_root"): + assert tcod.console_init_root is tcod.libtcodpy.console_init_root -@pytest.mark.filterwarnings("error") def test_deprecate_key_constants() -> None: - with pytest.raises(FutureWarning, match=r".*KeySym.N1"): + with pytest.warns(FutureWarning, match=r"KeySym.N1"): _ = tcod.event.K_1 - with pytest.raises(FutureWarning, match=r".*Scancode.N1"): + with pytest.warns(FutureWarning, match=r"Scancode.N1"): _ = tcod.event.SCANCODE_1 diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 5803c625..9eb151bb 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -7,7 +7,7 @@ from numpy.typing import NDArray import tcod -import tcod as libtcodpy +from tcod import libtcodpy # ruff: noqa: D103 @@ -17,7 +17,7 @@ ] -def test_console_behavior(console: tcod.Console) -> None: +def test_console_behavior(console: tcod.console.Console) -> None: assert not console From 63862f913a12cd4a3c859f30bb8d58640dd7f92a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 02:40:25 -0700 Subject: [PATCH 0809/1101] Enable Ruff auto-fixes. --- .vscode/settings.json | 3 ++- docs/conf.py | 6 +----- pyproject.toml | 1 + tcod/sdl/audio.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ddcc5ef..2e3629c7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,8 @@ ], "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.fixAll": true, + "source.organizeImports": false }, "files.trimFinalNewlines": true, "files.insertFinalNewline": true, diff --git a/docs/conf.py b/docs/conf.py index de4337ee..26efffad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # tdl documentation build configuration file, created by # sphinx-quickstart on Fri Nov 25 12:49:46 2016. # @@ -68,9 +66,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -git_describe = subprocess.run( - ["git", "describe", "--abbrev=0"], stdout=subprocess.PIPE, universal_newlines=True, check=True -) +git_describe = subprocess.run(["git", "describe", "--abbrev=0"], stdout=subprocess.PIPE, text=True, check=True) release = git_describe.stdout.strip() assert release print("release version: %r" % release) diff --git a/pyproject.toml b/pyproject.toml index 75279cba..447540a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -188,6 +188,7 @@ ignore = [ "D408", # section-underline-after-name "D409", # section-underline-matches-section-length ] +extend-exclude = ["libtcod"] # Ignore submodule line-length = 120 [tool.ruff.pydocstyle] diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index e8af0fee..8b9addab 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -65,7 +65,7 @@ def _get_format(format: DTypeLike) -> int: assert dt.fields is None bitsize = dt.itemsize * 8 assert 0 < bitsize <= lib.SDL_AUDIO_MASK_BITSIZE - if not dt.str[1] in "uif": + if dt.str[1] not in "uif": msg = f"Unexpected dtype: {dt}" raise TypeError(msg) is_signed = dt.str[1] != "u" From 627f05dd96461656aac834fda73852be99393209 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 02:47:23 -0700 Subject: [PATCH 0810/1101] Test that libtcodpy constants are correct. --- tests/test_deprecated.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index f841cad6..26090162 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -27,6 +27,7 @@ def test_deprecate_color() -> None: def test_constants() -> None: with pytest.warns(match=r"tcod.constants.RENDERER_SDL2"): assert tcod.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2 + assert tcod.libtcodpy.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2 def test_implicit_libtcodpy() -> None: From 0b311d2171b47acfc009bc1e425896c4f3913e31 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 03:15:31 -0700 Subject: [PATCH 0811/1101] Prefer tcod.libtcodpy over tcod.constants. libtcodpy is more clearly deprecated, the loose constants in tcod.constants are not recommended that's less clear when they're used. --- CHANGELOG.md | 2 +- examples/samples_tcod.py | 148 +++++++++++++++++++-------------------- tcod/tcod.py | 4 +- tests/test_deprecated.py | 2 +- 4 files changed, 77 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b424a99..b7e383df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Deprecated - Deprecated using `tcod` as an implicit alias for `libtcodpy`. You should use `from tcod import libtcodpy` if you want to access this module. -- Deprecated constants being held in `tcod`, get these from `tcod.constants` instead. +- Deprecated constants being held in `tcod`, get these from `tcod.libtcodpy` instead. - Deprecated `tcod.Console` which should be accessed from `tcod.console.Console` instead. ## [16.0.2] - 2023-06-02 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 1e23a5c4..43b53d92 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -17,10 +17,8 @@ import numpy as np from numpy.typing import NDArray -import tcod -import tcod.constants +import tcod.cffi import tcod.event -import tcod.libtcodpy import tcod.noise import tcod.render import tcod.sdl.mouse @@ -82,14 +80,14 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: SAMPLES[cur_sample].on_enter() draw_samples_menu() elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.KMOD_LALT: - tcod.console_set_fullscreen(not tcod.console_is_fullscreen()) + libtcodpy.console_set_fullscreen(not libtcodpy.console_is_fullscreen()) elif event.sym == tcod.event.KeySym.PRINTSCREEN or event.sym == tcod.event.KeySym.p: print("screenshot") if event.mod & tcod.event.KMOD_LALT: - tcod.console_save_apf(root_console, "samples.apf") + libtcodpy.console_save_apf(root_console, "samples.apf") print("apf") else: - tcod.sys_save_screenshot() + libtcodpy.sys_save_screenshot() print("png") elif event.sym == tcod.event.KeySym.ESCAPE: raise SystemExit() @@ -197,7 +195,7 @@ def __init__(self) -> None: "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", fg=WHITE, bg=None, - alignment=tcod.constants.CENTER, + alignment=libtcodpy.CENTER, ) def on_enter(self) -> None: @@ -251,8 +249,8 @@ class LineDrawingSample(Sample): def __init__(self) -> None: self.name = "Line drawing" - self.mk_flag = tcod.constants.BKGND_SET - self.bk_flag = tcod.constants.BKGND_SET + self.mk_flag = libtcodpy.BKGND_SET + self.bk_flag = libtcodpy.BKGND_SET self.bk = tcod.console.Console(sample_console.width, sample_console.height, order="F") # initialize the colored background @@ -264,30 +262,30 @@ def __init__(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): self.bk_flag += 1 - if (self.bk_flag & 0xFF) > tcod.BKGND_ALPH: - self.bk_flag = tcod.BKGND_NONE + if (self.bk_flag & 0xFF) > libtcodpy.BKGND_ALPH: + self.bk_flag = libtcodpy.BKGND_NONE else: super().ev_keydown(event) def on_draw(self) -> None: alpha = 0.0 - if (self.bk_flag & 0xFF) == tcod.BKGND_ALPH: + if (self.bk_flag & 0xFF) == libtcodpy.BKGND_ALPH: # for the alpha mode, update alpha every frame alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 - self.bk_flag = tcod.BKGND_ALPHA(int(alpha)) - elif (self.bk_flag & 0xFF) == tcod.BKGND_ADDA: + self.bk_flag = libtcodpy.BKGND_ALPHA(int(alpha)) + elif (self.bk_flag & 0xFF) == libtcodpy.BKGND_ADDA: # for the add alpha mode, update alpha every frame alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 - self.bk_flag = tcod.BKGND_ADDALPHA(int(alpha)) + self.bk_flag = libtcodpy.BKGND_ADDALPHA(int(alpha)) self.bk.blit(sample_console) rect_y = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0)) for x in range(sample_console.width): value = x * 255 // sample_console.width col = (value, value, value) - tcod.console_set_char_background(sample_console, x, rect_y, col, self.bk_flag) - tcod.console_set_char_background(sample_console, x, rect_y + 1, col, self.bk_flag) - tcod.console_set_char_background(sample_console, x, rect_y + 2, col, self.bk_flag) + libtcodpy.console_set_char_background(sample_console, x, rect_y, col, self.bk_flag) + libtcodpy.console_set_char_background(sample_console, x, rect_y + 1, col, self.bk_flag) + libtcodpy.console_set_char_background(sample_console, x, rect_y + 2, col, self.bk_flag) angle = time.time() * 2.0 cos_angle = math.cos(angle) sin_angle = math.sin(angle) @@ -365,8 +363,8 @@ def __init__(self) -> None: self.dy = 0.0 self.octaves = 4.0 self.zoom = 3.0 - self.hurst = tcod.libtcodpy.NOISE_DEFAULT_HURST - self.lacunarity = tcod.libtcodpy.NOISE_DEFAULT_LACUNARITY + self.hurst = libtcodpy.NOISE_DEFAULT_HURST + self.lacunarity = libtcodpy.NOISE_DEFAULT_LACUNARITY self.noise = self.get_noise() self.img = tcod.image.Image(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2) @@ -415,7 +413,7 @@ def on_draw(self) -> None: ch=0, fg=None, bg=GREY, - bg_blend=tcod.BKGND_MULTIPLY, + bg_blend=libtcodpy.BKGND_MULTIPLY, ) sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] = ( sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] * GREY / 255 @@ -543,7 +541,7 @@ def __init__(self) -> None: self.player_y = 10 self.torch = False self.light_walls = True - self.algo_num = tcod.constants.FOV_SYMMETRIC_SHADOWCAST + self.algo_num = libtcodpy.FOV_SYMMETRIC_SHADOWCAST self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering. map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) @@ -710,39 +708,39 @@ def on_enter(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] == "=": - tcod.console_put_char(sample_console, x, y, tcod.CHAR_DHLINE, tcod.BKGND_NONE) + libtcodpy.console_put_char(sample_console, x, y, libtcodpy.CHAR_DHLINE, libtcodpy.BKGND_NONE) self.recalculate = True def on_draw(self) -> None: if self.recalculate: if self.using_astar: - tcod.path_compute(self.path, self.px, self.py, self.dx, self.dy) + libtcodpy.path_compute(self.path, self.px, self.py, self.dx, self.dy) else: self.dijkstra_dist = 0.0 # compute dijkstra grid (distance from px,py) - tcod.dijkstra_compute(self.dijkstra, self.px, self.py) + libtcodpy.dijkstra_compute(self.dijkstra, self.px, self.py) # get the maximum distance (needed for rendering) for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - d = tcod.dijkstra_get_distance(self.dijkstra, x, y) + d = libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) if d > self.dijkstra_dist: self.dijkstra_dist = d # compute path from px,py to dx,dy - tcod.dijkstra_path_set(self.dijkstra, self.dx, self.dy) + libtcodpy.dijkstra_path_set(self.dijkstra, self.dx, self.dy) self.recalculate = False self.busy = 0.2 # draw the dungeon for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): if SAMPLE_MAP[x, y] == "#": - tcod.console_set_char_background(sample_console, x, y, DARK_WALL, tcod.BKGND_SET) + libtcodpy.console_set_char_background(sample_console, x, y, DARK_WALL, libtcodpy.BKGND_SET) else: - tcod.console_set_char_background(sample_console, x, y, DARK_GROUND, tcod.BKGND_SET) + libtcodpy.console_set_char_background(sample_console, x, y, DARK_GROUND, libtcodpy.BKGND_SET) # draw the path if self.using_astar: - for i in range(tcod.path_size(self.path)): - x, y = tcod.path_get(self.path, i) - tcod.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.BKGND_SET) + for i in range(libtcodpy.path_size(self.path)): + x, y = libtcodpy.path_get(self.path, i) + libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, libtcodpy.BKGND_SET) else: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -756,11 +754,11 @@ def on_draw(self) -> None: DARK_GROUND, 0.9 * libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, ), - tcod.BKGND_SET, + libtcodpy.BKGND_SET, ) for i in range(libtcodpy.dijkstra_size(self.dijkstra)): x, y = libtcodpy.dijkstra_get(self.dijkstra, i) - libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, tcod.constants.BKGND_SET) + libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, libtcodpy.BKGND_SET) # move the creature self.busy -= frame_length[-1] @@ -768,47 +766,47 @@ def on_draw(self) -> None: self.busy = 0.2 if self.using_astar: if not libtcodpy.path_is_empty(self.path): - libtcodpy.console_put_char(sample_console, self.px, self.py, " ", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore - libtcodpy.console_put_char(sample_console, self.px, self.py, "@", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) else: if not libtcodpy.dijkstra_is_empty(self.dijkstra): - libtcodpy.console_put_char(sample_console, self.px, self.py, " ", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore - libtcodpy.console_put_char(sample_console, self.px, self.py, "@", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) self.recalculate = True def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.i and self.dy > 0: # destination move north - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) self.dy -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.k and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) self.dy += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.j and self.dx > 0: # destination move west - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) self.dx -= 1 self.oldchar = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.l and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) self.dx += 1 self.oldchar = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.TAB: @@ -825,11 +823,11 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my): - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) self.dx = mx self.dy = my self.oldchar = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", tcod.constants.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True @@ -997,7 +995,7 @@ def on_draw(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): color = DARK_GROUND if self.bsp_map[x][y] else DARK_WALL - tcod.console_set_char_background(sample_console, x, y, color, tcod.BKGND_SET) + libtcodpy.console_set_char_background(sample_console, x, y, color, libtcodpy.BKGND_SET) def ev_keydown(self, event: tcod.event.KeyDown) -> None: global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size @@ -1048,19 +1046,19 @@ def on_draw(self) -> None: # split the color channels of circle.png # the red channel sample_console.draw_rect(0, 3, 15, 15, 0, None, (255, 0, 0)) - self.circle.blit_rect(sample_console, 0, 3, -1, -1, tcod.BKGND_MULTIPLY) + self.circle.blit_rect(sample_console, 0, 3, -1, -1, libtcodpy.BKGND_MULTIPLY) # the green channel sample_console.draw_rect(15, 3, 15, 15, 0, None, (0, 255, 0)) - self.circle.blit_rect(sample_console, 15, 3, -1, -1, tcod.BKGND_MULTIPLY) + self.circle.blit_rect(sample_console, 15, 3, -1, -1, libtcodpy.BKGND_MULTIPLY) # the blue channel sample_console.draw_rect(30, 3, 15, 15, 0, None, (0, 0, 255)) - self.circle.blit_rect(sample_console, 30, 3, -1, -1, tcod.BKGND_MULTIPLY) + self.circle.blit_rect(sample_console, 30, 3, -1, -1, libtcodpy.BKGND_MULTIPLY) else: # render circle.png with normal blitting - self.circle.blit_rect(sample_console, 0, 3, -1, -1, tcod.BKGND_SET) - self.circle.blit_rect(sample_console, 15, 3, -1, -1, tcod.BKGND_SET) - self.circle.blit_rect(sample_console, 30, 3, -1, -1, tcod.BKGND_SET) - self.img.blit(sample_console, x, y, tcod.BKGND_SET, scalex, scaley, angle) + self.circle.blit_rect(sample_console, 0, 3, -1, -1, libtcodpy.BKGND_SET) + self.circle.blit_rect(sample_console, 15, 3, -1, -1, libtcodpy.BKGND_SET) + self.circle.blit_rect(sample_console, 30, 3, -1, -1, libtcodpy.BKGND_SET) + self.img.blit(sample_console, x, y, libtcodpy.BKGND_SET, scalex, scaley, angle) class MouseSample(Sample): @@ -1152,9 +1150,9 @@ def on_draw(self) -> None: # parse all *.cfg files in data/namegen for file in (DATA_DIR / "namegen").iterdir(): if file.suffix == ".cfg": - tcod.namegen_parse(file) + libtcodpy.namegen_parse(file) # get the sets list - self.sets = tcod.namegen_get_sets() + self.sets = libtcodpy.namegen_get_sets() print(self.sets) while len(self.names) > 15: self.names.pop(0) @@ -1173,12 +1171,12 @@ def on_draw(self) -> None: self.names[i], fg=WHITE, bg=None, - alignment=tcod.RIGHT, + alignment=libtcodpy.RIGHT, ) self.delay += frame_length[-1] if self.delay > 0.5: self.delay -= 0.5 - self.names.append(tcod.namegen_generate(self.sets[self.curset])) + self.names.append(libtcodpy.namegen_generate(self.sets[self.curset])) def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.EQUALS: @@ -1367,11 +1365,11 @@ def on_draw(self) -> None: ############################################# RENDERER_KEYS = { - tcod.event.KeySym.F1: tcod.constants.RENDERER_GLSL, - tcod.event.KeySym.F2: tcod.constants.RENDERER_OPENGL, - tcod.event.KeySym.F3: tcod.constants.RENDERER_SDL, - tcod.event.KeySym.F4: tcod.constants.RENDERER_SDL2, - tcod.event.KeySym.F5: tcod.constants.RENDERER_OPENGL2, + tcod.event.KeySym.F1: libtcodpy.RENDERER_GLSL, + tcod.event.KeySym.F2: libtcodpy.RENDERER_OPENGL, + tcod.event.KeySym.F3: libtcodpy.RENDERER_SDL, + tcod.event.KeySym.F4: libtcodpy.RENDERER_SDL2, + tcod.event.KeySym.F5: libtcodpy.RENDERER_OPENGL2, } RENDERER_NAMES = ( @@ -1406,9 +1404,9 @@ def init_context(renderer: int) -> None: if "context" in globals(): context.close() libtcod_version = "%i.%i.%i" % ( - tcod.lib.TCOD_MAJOR_VERSION, - tcod.lib.TCOD_MINOR_VERSION, - tcod.lib.TCOD_PATCHLEVEL, + tcod.cffi.lib.TCOD_MAJOR_VERSION, + tcod.cffi.lib.TCOD_MINOR_VERSION, + tcod.cffi.lib.TCOD_PATCHLEVEL, ) context = tcod.context.new( columns=root_console.width, @@ -1429,7 +1427,7 @@ def init_context(renderer: int) -> None: sample_minimap = context.sdl_renderer.new_texture( SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, - format=tcod.lib.SDL_PIXELFORMAT_RGB24, + format=tcod.cffi.lib.SDL_PIXELFORMAT_RGB24, access=tcod.sdl.render.TextureAccess.STREAMING, # Updated every frame. ) @@ -1437,7 +1435,7 @@ def init_context(renderer: int) -> None: def main() -> None: global context, tileset tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) - init_context(tcod.constants.RENDERER_SDL2) + init_context(libtcodpy.RENDERER_SDL2) try: SAMPLES[cur_sample].on_enter() @@ -1522,7 +1520,7 @@ def draw_samples_menu() -> None: " %s" % sample.name.ljust(19), fg, bg, - alignment=tcod.LEFT, + alignment=libtcodpy.LEFT, ) @@ -1536,21 +1534,21 @@ def draw_stats() -> None: 46, "last frame :%5.1f ms (%4d fps)" % (frame_length[-1] * 1000.0, fps), fg=GREY, - alignment=tcod.RIGHT, + alignment=libtcodpy.RIGHT, ) root_console.print( 79, 47, "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), fg=GREY, - alignment=tcod.RIGHT, + alignment=libtcodpy.RIGHT, ) def draw_renderer_menu() -> None: root_console.print( 42, - 46 - (tcod.NB_RENDERERS + 1), + 46 - (libtcodpy.NB_RENDERERS + 1), "Renderer :", fg=GREY, bg=BLACK, @@ -1562,7 +1560,7 @@ def draw_renderer_menu() -> None: else: fg = GREY bg = BLACK - root_console.print(42, 46 - tcod.NB_RENDERERS + i, name, fg, bg) + root_console.print(42, 46 - libtcodpy.NB_RENDERERS + i, name, fg, bg) if __name__ == "__main__": diff --git a/tcod/tcod.py b/tcod/tcod.py index b5691729..6fab4ff4 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -50,8 +50,8 @@ def __getattr__(name: str, stacklevel: int = 1) -> Any: # noqa: ANN401 if value is not None: warnings.warn( "Soon the 'tcod' module will no longer hold constants directly." - "\nAdd 'import tcod.constants' if you haven't already." - f"\nReplace 'tcod.{name}' with 'tcod.constants.{name}'", + "\nAdd 'from tcod import libtcodpy' if you haven't already." + f"\nReplace 'tcod.{name}' with 'libtcodpy.{name}'", FutureWarning, stacklevel=stacklevel + 1, ) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 26090162..e1ee18c7 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -25,7 +25,7 @@ def test_deprecate_color() -> None: def test_constants() -> None: - with pytest.warns(match=r"tcod.constants.RENDERER_SDL2"): + with pytest.warns(match=r"libtcodpy.RENDERER_SDL2"): assert tcod.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2 assert tcod.libtcodpy.RENDERER_SDL2 is tcod.constants.RENDERER_SDL2 From a9345246acb0b73b4a46b2f5c8c3224b086db013 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 03:58:07 -0700 Subject: [PATCH 0812/1101] Prepare 16.0.3 release. --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e383df..c84d0f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,15 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.0.3] - 2023-06-04 +### Changed +- Enabled logging for libtcod and SDL. + ### Deprecated - Deprecated using `tcod` as an implicit alias for `libtcodpy`. You should use `from tcod import libtcodpy` if you want to access this module. -- Deprecated constants being held in `tcod`, get these from `tcod.libtcodpy` instead. +- Deprecated constants being held directly in `tcod`, get these from `tcod.libtcodpy` instead. - Deprecated `tcod.Console` which should be accessed from `tcod.console.Console` instead. ## [16.0.2] - 2023-06-02 From a3901f4cfb9007bf1930fc5a9f4eedaf2f58c24c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 Jun 2023 05:57:16 -0700 Subject: [PATCH 0813/1101] Update to libtcod 1.24.0 --- CHANGELOG.md | 2 ++ libtcod | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84d0f8a..330fa126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- Using `libtcod 1.24.0`. ## [16.0.3] - 2023-06-04 ### Changed diff --git a/libtcod b/libtcod index 168ab8ce..747b2e9d 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 168ab8ce054f84087e04595a54ad02093c31a5a9 +Subproject commit 747b2e9db06fca0f1281ba0ca6de173fbcb32eec From 7e14b3165db84bc79459ac05558116cd6c7f90a3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 5 Jun 2023 03:11:28 -0700 Subject: [PATCH 0814/1101] Update tcod.sdl.render equality functions and add missing docs. --- tcod/sdl/render.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 612cb972..b5489f18 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -149,6 +149,7 @@ class Texture: """ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: + """Encapsulate an SDL_Texture pointer. This function is private.""" self.p = sdl_texture_p self._sdl_renderer_p = sdl_renderer_p # Keep alive. query = self._query() @@ -165,8 +166,11 @@ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: self.height: Final[int] = query[3] """Texture pixel height, read only.""" - def __eq__(self, other: Any) -> bool: - return bool(self.p == getattr(other, "p", None)) + def __eq__(self, other: object) -> bool: + """Return True if compared to the same texture.""" + if isinstance(other, Texture): + return bool(self.p == other.p) + return NotImplemented def _query(self) -> tuple[int, int, int, int]: """Return (format, access, width, height).""" @@ -243,6 +247,7 @@ class Renderer: """SDL Renderer.""" def __init__(self, sdl_renderer_p: Any) -> None: + """Encapsulate an SDL_Renderer pointer. This function is private.""" if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): msg = f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)})." raise TypeError(msg) @@ -251,8 +256,11 @@ def __init__(self, sdl_renderer_p: Any) -> None: raise TypeError(msg) self.p = sdl_renderer_p - def __eq__(self, other: Any) -> bool: - return bool(self.p == getattr(other, "p", None)) + def __eq__(self, other: object) -> bool: + """Return True if compared to the same renderer.""" + if isinstance(other, Renderer): + return bool(self.p == other.p) + return NotImplemented def copy( self, From a78f13329c9a32506170e0e01f3e57c71e744c02 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 16 Jun 2023 06:49:24 -0700 Subject: [PATCH 0815/1101] Change Sphinx html theme to Furo. Update page titles to work better with the new theme. Fix deprecated features and warnings. --- .vscode/settings.json | 18 ++++++++++++++++++ docs/_static/css/custom.css | 3 --- docs/conf.py | 22 +++++++++++++++------- docs/libtcodpy.rst | 4 ++-- docs/requirements.txt | 4 ++-- docs/sdl/audio.rst | 4 ++-- docs/sdl/joystick.rst | 4 ++-- docs/sdl/mouse.rst | 4 ++-- docs/sdl/render.rst | 4 ++-- docs/sdl/video.rst | 4 ++-- docs/tcod/bsp.rst | 4 ++-- docs/tcod/console.rst | 4 ++-- docs/tcod/context.rst | 4 ++-- docs/tcod/event.rst | 4 ++-- docs/tcod/image.rst | 4 ++-- docs/tcod/los.rst | 4 ++-- docs/tcod/map.rst | 4 ++-- docs/tcod/noise.rst | 4 ++-- docs/tcod/path.rst | 4 ++-- docs/tcod/random.rst | 4 ++-- docs/tcod/render.rst | 4 ++-- docs/tcod/tileset.rst | 4 ++-- 22 files changed, 71 insertions(+), 48 deletions(-) delete mode 100644 docs/_static/css/custom.css diff --git a/.vscode/settings.json b/.vscode/settings.json index 2e3629c7..e96ff1cf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -121,10 +121,12 @@ "deque", "desaturated", "DESATURATED", + "detailmenu", "devel", "DHLINE", "DISPLAYSWITCH", "dlopen", + "documentclass", "Doryen", "DPAD", "DTEEE", @@ -152,6 +154,7 @@ "freetype", "frombuffer", "fullscreen", + "furo", "fwidth", "GAMECONTROLLER", "gamepad", @@ -161,6 +164,7 @@ "globaltoc", "GLSL", "greyscale", + "groupwise", "guass", "heapify", "heightmap", @@ -173,6 +177,7 @@ "horiz", "howto", "htbp", + "htmlhelp", "htmlzip", "IBEAM", "ifdef", @@ -182,6 +187,7 @@ "imageio", "INCOL", "INROW", + "intersphinx", "isinstance", "isort", "itemsize", @@ -233,6 +239,7 @@ "LEFTUP", "LEFTX", "lerp", + "letterpaper", "LGUI", "libsdl", "libtcod", @@ -268,6 +275,7 @@ "mipmaps", "MMASK", "modindex", + "moduleauthor", "MOUSEBUTTONDOWN", "MOUSEBUTTONUP", "MOUSEMOTION", @@ -300,10 +308,13 @@ "ogrids", "onefile", "OPENGL", + "opensearch", "OPER", "packbits", "PAGEDOWN", + "pagerefs", "PAGEUP", + "papersize", "PATCHLEVEL", "pathfinding", "pathlib", @@ -313,6 +324,7 @@ "pilmode", "PIXELFORMAT", "PLUSMINUS", + "pointsize", "PRESENTVSYNC", "PRINTF", "printn", @@ -369,6 +381,8 @@ "SDLCALL", "sdlevent", "SDLK", + "searchbox", + "sectionauthor", "seealso", "servernum", "setuptools", @@ -381,6 +395,9 @@ "SMILIE", "snprintf", "soundfile", + "sourcelink", + "sphinxstrong", + "sphinxtitleref", "stdeb", "struct", "structs", @@ -425,6 +442,7 @@ "vertic", "VERTICALBAR", "vflip", + "viewcode", "vline", "VOLUMEDOWN", "VOLUMEUP", diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css deleted file mode 100644 index 1af4c40b..00000000 --- a/docs/_static/css/custom.css +++ /dev/null @@ -1,3 +0,0 @@ -.wy-nav-content { - max-width: 60em -} diff --git a/docs/conf.py b/docs/conf.py index 26efffad..0e5f24ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +"""Sphinx config file.""" # tdl documentation build configuration file, created by # sphinx-quickstart on Fri Nov 25 12:49:46 2016. # @@ -14,13 +15,15 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +from __future__ import annotations + import os import re import subprocess import sys -from typing import Dict +from pathlib import Path -sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, str(Path("..").resolve(strict=True))) # -- General configuration ------------------------------------------------ @@ -66,7 +69,12 @@ # built documents. # # The full version, including alpha/beta/rc tags. -git_describe = subprocess.run(["git", "describe", "--abbrev=0"], stdout=subprocess.PIPE, text=True, check=True) +git_describe = subprocess.run( + ["git", "describe", "--abbrev=0"], # noqa: S603, S607 + stdout=subprocess.PIPE, + text=True, + check=True, +) release = git_describe.stdout.strip() assert release print("release version: %r" % release) @@ -143,7 +151,7 @@ # a list of builtin themes. # -html_theme = "sphinx_rtd_theme" +html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -263,7 +271,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements: Dict[str, str] = { +latex_elements: dict[str, str] = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). @@ -374,8 +382,8 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - "https://docs.python.org/3/": None, - "https://numpy.org/doc/stable/": None, + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), } os.environ["READTHEDOCS"] = "True" diff --git a/docs/libtcodpy.rst b/docs/libtcodpy.rst index 1b6073d0..ee4dcfe6 100644 --- a/docs/libtcodpy.rst +++ b/docs/libtcodpy.rst @@ -1,5 +1,5 @@ -libtcodpy - Old API Functions -============================= +Old API Functions ``libtcodpy`` +=============================== This is all the functions included since the start of the Python port. This collection is often called :term:`libtcodpy`, the name of the original diff --git a/docs/requirements.txt b/docs/requirements.txt index f25589d5..4cb56294 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx>=5.0.2,!=5.1.0,<6.1 # https://github.com/readthedocs/sphinx_rtd_theme/issues/1463 -sphinx_rtd_theme>=1.2.1 +sphinx>=7.0.1 +furo>=2023.5.20 diff --git a/docs/sdl/audio.rst b/docs/sdl/audio.rst index 702bd470..6eb14a1f 100644 --- a/docs/sdl/audio.rst +++ b/docs/sdl/audio.rst @@ -1,5 +1,5 @@ -tcod.sdl.audio - SDL Audio -========================== +SDL Audio ``tcod.sdl.audio`` +============================ .. automodule:: tcod.sdl.audio :members: diff --git a/docs/sdl/joystick.rst b/docs/sdl/joystick.rst index e1b22ca2..60c7c26e 100644 --- a/docs/sdl/joystick.rst +++ b/docs/sdl/joystick.rst @@ -1,5 +1,5 @@ -tcod.sdl.joystick - SDL Joystick Support -======================================== +SDL Joystick Support ``tcod.sdl.joystick`` +========================================== .. automodule:: tcod.sdl.joystick :members: diff --git a/docs/sdl/mouse.rst b/docs/sdl/mouse.rst index 932f4d9f..ab73d06c 100644 --- a/docs/sdl/mouse.rst +++ b/docs/sdl/mouse.rst @@ -1,5 +1,5 @@ -tcod.sdl.mouse - SDL Mouse Functions -==================================== +SDL Mouse Functions ``tcod.sdl.mouse`` +====================================== .. automodule:: tcod.sdl.mouse :members: diff --git a/docs/sdl/render.rst b/docs/sdl/render.rst index 0dc462cd..f5922b1f 100644 --- a/docs/sdl/render.rst +++ b/docs/sdl/render.rst @@ -1,5 +1,5 @@ -tcod.sdl.render - SDL Rendering -=============================== +SDL Rendering ``tcod.sdl.render`` +================================= .. automodule:: tcod.sdl.render :members: diff --git a/docs/sdl/video.rst b/docs/sdl/video.rst index 56d43408..25dffb17 100644 --- a/docs/sdl/video.rst +++ b/docs/sdl/video.rst @@ -1,5 +1,5 @@ -tcod.sdl.video - SDL Window and Display API -=========================================== +SDL Window and Display API ``tcod.sdl.video`` +============================================= .. automodule:: tcod.sdl.video :members: diff --git a/docs/tcod/bsp.rst b/docs/tcod/bsp.rst index b0859baa..14ddcdcd 100644 --- a/docs/tcod/bsp.rst +++ b/docs/tcod/bsp.rst @@ -1,5 +1,5 @@ -tcod.bsp - Binary Space Partitioning -==================================== +Binary Space Partitioning ``tcod.bsp`` +====================================== .. automodule:: tcod.bsp :members: diff --git a/docs/tcod/console.rst b/docs/tcod/console.rst index 6078f274..04ad7672 100644 --- a/docs/tcod/console.rst +++ b/docs/tcod/console.rst @@ -1,5 +1,5 @@ -tcod.console - Tile Drawing/Printing -==================================== +Tile Drawing/Printing ``tcod.console`` +====================================== .. automodule:: tcod.console :members: diff --git a/docs/tcod/context.rst b/docs/tcod/context.rst index 46ed6178..308f9a19 100644 --- a/docs/tcod/context.rst +++ b/docs/tcod/context.rst @@ -1,5 +1,5 @@ -tcod.context - Window Management -================================ +Window Management ``tcod.context`` +================================== .. automodule:: tcod.context :members: diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 88776e5b..10aed249 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -1,5 +1,5 @@ -tcod.event - SDL2 Event Handling -================================ +SDL2 Event Handling ``tcod.event`` +================================== .. automodule:: tcod.event :members: diff --git a/docs/tcod/image.rst b/docs/tcod/image.rst index 58706d3f..0d4aaecc 100644 --- a/docs/tcod/image.rst +++ b/docs/tcod/image.rst @@ -1,5 +1,5 @@ -tcod.image - Image Handling -=========================== +Image Handling ``tcod.image`` +============================= .. automodule:: tcod.image :members: diff --git a/docs/tcod/los.rst b/docs/tcod/los.rst index 73a501a1..bb82265a 100644 --- a/docs/tcod/los.rst +++ b/docs/tcod/los.rst @@ -1,5 +1,5 @@ -tcod.los - Line of Sight -======================== +Line of Sight ``tcod.los`` +========================== .. automodule:: tcod.los :members: diff --git a/docs/tcod/map.rst b/docs/tcod/map.rst index 08efaf86..1a02a9a0 100644 --- a/docs/tcod/map.rst +++ b/docs/tcod/map.rst @@ -1,5 +1,5 @@ -tcod.map - Field of View -======================== +Field of View ``tcod.map`` +========================== .. automodule:: tcod.map :members: diff --git a/docs/tcod/noise.rst b/docs/tcod/noise.rst index 03a18984..59c23165 100644 --- a/docs/tcod/noise.rst +++ b/docs/tcod/noise.rst @@ -1,5 +1,5 @@ -tcod.noise - Noise Map Generators -================================= +Noise Map Generators ``tcod.noise`` +=================================== .. automodule:: tcod.noise :members: diff --git a/docs/tcod/path.rst b/docs/tcod/path.rst index 75c1dcb1..7d5f3a79 100644 --- a/docs/tcod/path.rst +++ b/docs/tcod/path.rst @@ -1,5 +1,5 @@ -tcod.path - Pathfinding -======================= +Pathfinding ``tcod.path`` +========================= .. automodule:: tcod.path :members: diff --git a/docs/tcod/random.rst b/docs/tcod/random.rst index 3e4d08b0..2d7c035d 100644 --- a/docs/tcod/random.rst +++ b/docs/tcod/random.rst @@ -1,5 +1,5 @@ -tcod.random - Random Number Generators -====================================== +Random Number Generators ``tcod.random`` +======================================== .. automodule:: tcod.random :members: diff --git a/docs/tcod/render.rst b/docs/tcod/render.rst index 605d52f2..547a6d81 100644 --- a/docs/tcod/render.rst +++ b/docs/tcod/render.rst @@ -1,5 +1,5 @@ -tcod.render - Console Rendering Extension -========================================= +Console Rendering Extension ``tcod.render`` +=========================================== .. automodule:: tcod.render :members: diff --git a/docs/tcod/tileset.rst b/docs/tcod/tileset.rst index 47d0b0e8..57b2cc3c 100644 --- a/docs/tcod/tileset.rst +++ b/docs/tcod/tileset.rst @@ -1,5 +1,5 @@ -tcod.tileset - Font Loading Functions -===================================== +Font Loading Functions ``tcod.tileset`` +======================================= .. automodule:: tcod.tileset :members: From e95698a8e6c48e779345783e48b2a753335778e6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 16 Jun 2023 07:03:29 -0700 Subject: [PATCH 0816/1101] Update libtcodpy docs to be less deprecated. Should no longer get these from the `tcod` module. Update some deprecation warnings to FutureWarning. --- docs/conf.py | 4 +- docs/glossary.rst | 2 +- docs/libtcodpy.rst | 560 ++++++++++++++++++++++---------------------- fonts/X11/README.md | 2 +- tcod/console.py | 28 +-- tcod/context.py | 39 +-- tcod/event.py | 6 +- tcod/image.py | 6 +- tcod/libtcodpy.py | 30 +-- tcod/tileset.py | 4 +- 10 files changed, 329 insertions(+), 352 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0e5f24ab..090c191f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -185,9 +185,9 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +# html_static_path = ["_static"] -html_css_files = ["css/custom.css"] +# html_css_files = ["css/custom.css"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/docs/glossary.rst b/docs/glossary.rst index 3cfb22a7..89ece9b3 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -72,4 +72,4 @@ Glossary color control color controls Libtcod's old system which assigns colors to specific codepoints. - See :any:`tcod.COLCTRL_STOP`, :any:`tcod.COLCTRL_FORE_RGB`, and :any:`tcod.COLCTRL_BACK_RGB` for examples. + See :any:`libtcodpy.COLCTRL_STOP`, :any:`libtcodpy.COLCTRL_FORE_RGB`, and :any:`libtcodpy.COLCTRL_BACK_RGB` for examples. diff --git a/docs/libtcodpy.rst b/docs/libtcodpy.rst index ee4dcfe6..9dbeb3f5 100644 --- a/docs/libtcodpy.rst +++ b/docs/libtcodpy.rst @@ -5,6 +5,8 @@ This is all the functions included since the start of the Python port. This collection is often called :term:`libtcodpy`, the name of the original Python port. These functions are reproduced by python-tcod in their entirely. +Use ``from tcod import libtcodpy`` to access this module. + **A large majority of these functions are deprecated and will be removed in the future. In general this entire section should be avoided whenever possible.** @@ -14,158 +16,158 @@ modern API. bsp --- -.. autofunction:: tcod.bsp_new_with_size -.. autofunction:: tcod.bsp_split_once -.. autofunction:: tcod.bsp_split_recursive -.. autofunction:: tcod.bsp_resize -.. autofunction:: tcod.bsp_left -.. autofunction:: tcod.bsp_right -.. autofunction:: tcod.bsp_father -.. autofunction:: tcod.bsp_is_leaf -.. autofunction:: tcod.bsp_contains -.. autofunction:: tcod.bsp_find_node -.. autofunction:: tcod.bsp_traverse_pre_order -.. autofunction:: tcod.bsp_traverse_in_order -.. autofunction:: tcod.bsp_traverse_post_order -.. autofunction:: tcod.bsp_traverse_level_order -.. autofunction:: tcod.bsp_traverse_inverted_level_order -.. autofunction:: tcod.bsp_remove_sons -.. autofunction:: tcod.bsp_delete +.. autofunction:: libtcodpy.bsp_new_with_size +.. autofunction:: libtcodpy.bsp_split_once +.. autofunction:: libtcodpy.bsp_split_recursive +.. autofunction:: libtcodpy.bsp_resize +.. autofunction:: libtcodpy.bsp_left +.. autofunction:: libtcodpy.bsp_right +.. autofunction:: libtcodpy.bsp_father +.. autofunction:: libtcodpy.bsp_is_leaf +.. autofunction:: libtcodpy.bsp_contains +.. autofunction:: libtcodpy.bsp_find_node +.. autofunction:: libtcodpy.bsp_traverse_pre_order +.. autofunction:: libtcodpy.bsp_traverse_in_order +.. autofunction:: libtcodpy.bsp_traverse_post_order +.. autofunction:: libtcodpy.bsp_traverse_level_order +.. autofunction:: libtcodpy.bsp_traverse_inverted_level_order +.. autofunction:: libtcodpy.bsp_remove_sons +.. autofunction:: libtcodpy.bsp_delete color ----- -.. autoclass:: tcod.Color +.. autoclass:: libtcodpy.Color :member-order: bysource :members: -.. autofunction:: tcod.color_lerp -.. autofunction:: tcod.color_set_hsv -.. autofunction:: tcod.color_get_hsv -.. autofunction:: tcod.color_scale_HSV -.. autofunction:: tcod.color_gen_map +.. autofunction:: libtcodpy.color_lerp +.. autofunction:: libtcodpy.color_set_hsv +.. autofunction:: libtcodpy.color_get_hsv +.. autofunction:: libtcodpy.color_scale_HSV +.. autofunction:: libtcodpy.color_gen_map color controls ~~~~~~~~~~~~~~ Libtcod color control constants. These can be inserted into Python strings with the ``%c`` format specifier as shown below. -.. data:: tcod.COLCTRL_1 +.. data:: libtcodpy.COLCTRL_1 - These can be configured with :any:`tcod.console_set_color_control`. - However, it is recommended to use :any:`tcod.COLCTRL_FORE_RGB` and :any:`tcod.COLCTRL_BACK_RGB` instead. + These can be configured with :any:`libtcodpy.console_set_color_control`. + However, it is recommended to use :any:`libtcodpy.COLCTRL_FORE_RGB` and :any:`libtcodpy.COLCTRL_BACK_RGB` instead. -.. data:: tcod.COLCTRL_2 -.. data:: tcod.COLCTRL_3 -.. data:: tcod.COLCTRL_4 -.. data:: tcod.COLCTRL_5 +.. data:: libtcodpy.COLCTRL_2 +.. data:: libtcodpy.COLCTRL_3 +.. data:: libtcodpy.COLCTRL_4 +.. data:: libtcodpy.COLCTRL_5 -.. data:: tcod.COLCTRL_STOP +.. data:: libtcodpy.COLCTRL_STOP When this control character is inserted into a string the foreground and background colors will be reset for the remaining characters of the string. >>> import tcod - >>> reset_color = f"{tcod.COLCTRL_STOP:c}" + >>> reset_color = f"{libtcodpy.COLCTRL_STOP:c}" -.. data:: tcod.COLCTRL_FORE_RGB +.. data:: libtcodpy.COLCTRL_FORE_RGB Sets the foreground color to the next 3 Unicode characters for the remaining characters. >>> fg = (255, 255, 255) - >>> change_fg = f"{tcod.COLCTRL_FORE_RGB:c}{fg[0]:c}{fg[1]:c}{fg[2]:c}" - >>> string = f"Old color {change_fg}new color{tcod.COLCTRL_STOP:c} old color." + >>> change_fg = f"{libtcodpy.COLCTRL_FORE_RGB:c}{fg[0]:c}{fg[1]:c}{fg[2]:c}" + >>> string = f"Old color {change_fg}new color{libtcodpy.COLCTRL_STOP:c} old color." -.. data:: tcod.COLCTRL_BACK_RGB +.. data:: libtcodpy.COLCTRL_BACK_RGB Sets the background color to the next 3 Unicode characters for the remaining characters. >>> from typing import Tuple >>> def change_colors(fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> str: ... """Return the control codes to change the foreground and background colors.""" - ... return "%c%c%c%c%c%c%c%c" % (tcod.COLCTRL_FORE_RGB, *fg, tcod.COLCTRL_BACK_RGB, *bg) + ... return "%c%c%c%c%c%c%c%c" % (libtcodpy.COLCTRL_FORE_RGB, *fg, libtcodpy.COLCTRL_BACK_RGB, *bg) >>> string = f"Old {change_colors(fg=(255, 255, 255), bg=(0, 0, 255))}new" console ------- -.. autofunction:: tcod.console_set_custom_font -.. autofunction:: tcod.console_init_root -.. autofunction:: tcod.console_flush - -.. autofunction:: tcod.console_blit -.. autofunction:: tcod.console_check_for_keypress -.. autofunction:: tcod.console_clear -.. autofunction:: tcod.console_credits -.. autofunction:: tcod.console_credits_render -.. autofunction:: tcod.console_credits_reset -.. autofunction:: tcod.console_delete -.. autofunction:: tcod.console_fill_background -.. autofunction:: tcod.console_fill_char -.. autofunction:: tcod.console_fill_foreground -.. autofunction:: tcod.console_from_file -.. autofunction:: tcod.console_from_xp -.. autofunction:: tcod.console_get_alignment -.. autofunction:: tcod.console_get_background_flag -.. autofunction:: tcod.console_get_char -.. autofunction:: tcod.console_get_char_background -.. autofunction:: tcod.console_get_char_foreground -.. autofunction:: tcod.console_get_default_background -.. autofunction:: tcod.console_get_default_foreground -.. autofunction:: tcod.console_get_fade -.. autofunction:: tcod.console_get_fading_color -.. autofunction:: tcod.console_get_height -.. autofunction:: tcod.console_get_height_rect -.. autofunction:: tcod.console_get_width -.. autofunction:: tcod.console_hline -.. autofunction:: tcod.console_is_fullscreen -.. autofunction:: tcod.console_is_key_pressed -.. autofunction:: tcod.console_is_window_closed -.. autofunction:: tcod.console_load_apf -.. autofunction:: tcod.console_load_asc -.. autofunction:: tcod.console_load_xp -.. autofunction:: tcod.console_list_load_xp -.. autofunction:: tcod.console_list_save_xp -.. autofunction:: tcod.console_map_ascii_code_to_font -.. autofunction:: tcod.console_map_ascii_codes_to_font -.. autofunction:: tcod.console_map_string_to_font -.. autofunction:: tcod.console_new -.. autofunction:: tcod.console_print -.. autofunction:: tcod.console_print_ex -.. autofunction:: tcod.console_print_frame -.. autofunction:: tcod.console_print_rect -.. autofunction:: tcod.console_print_rect_ex -.. autofunction:: tcod.console_put_char -.. autofunction:: tcod.console_put_char_ex -.. autofunction:: tcod.console_rect -.. autofunction:: tcod.console_save_apf -.. autofunction:: tcod.console_save_asc -.. autofunction:: tcod.console_save_xp -.. autofunction:: tcod.console_set_alignment -.. autofunction:: tcod.console_set_background_flag -.. autofunction:: tcod.console_set_char -.. autofunction:: tcod.console_set_char_background -.. autofunction:: tcod.console_set_char_foreground -.. autofunction:: tcod.console_set_color_control -.. autofunction:: tcod.console_set_default_background -.. autofunction:: tcod.console_set_default_foreground -.. autofunction:: tcod.console_set_fade -.. autofunction:: tcod.console_set_fullscreen -.. autofunction:: tcod.console_set_key_color -.. autofunction:: tcod.console_set_window_title -.. autofunction:: tcod.console_vline -.. autofunction:: tcod.console_wait_for_keypress - -.. autoclass: tcod.ConsoleBuffer +.. autofunction:: libtcodpy.console_set_custom_font +.. autofunction:: libtcodpy.console_init_root +.. autofunction:: libtcodpy.console_flush + +.. autofunction:: libtcodpy.console_blit +.. autofunction:: libtcodpy.console_check_for_keypress +.. autofunction:: libtcodpy.console_clear +.. autofunction:: libtcodpy.console_credits +.. autofunction:: libtcodpy.console_credits_render +.. autofunction:: libtcodpy.console_credits_reset +.. autofunction:: libtcodpy.console_delete +.. autofunction:: libtcodpy.console_fill_background +.. autofunction:: libtcodpy.console_fill_char +.. autofunction:: libtcodpy.console_fill_foreground +.. autofunction:: libtcodpy.console_from_file +.. autofunction:: libtcodpy.console_from_xp +.. autofunction:: libtcodpy.console_get_alignment +.. autofunction:: libtcodpy.console_get_background_flag +.. autofunction:: libtcodpy.console_get_char +.. autofunction:: libtcodpy.console_get_char_background +.. autofunction:: libtcodpy.console_get_char_foreground +.. autofunction:: libtcodpy.console_get_default_background +.. autofunction:: libtcodpy.console_get_default_foreground +.. autofunction:: libtcodpy.console_get_fade +.. autofunction:: libtcodpy.console_get_fading_color +.. autofunction:: libtcodpy.console_get_height +.. autofunction:: libtcodpy.console_get_height_rect +.. autofunction:: libtcodpy.console_get_width +.. autofunction:: libtcodpy.console_hline +.. autofunction:: libtcodpy.console_is_fullscreen +.. autofunction:: libtcodpy.console_is_key_pressed +.. autofunction:: libtcodpy.console_is_window_closed +.. autofunction:: libtcodpy.console_load_apf +.. autofunction:: libtcodpy.console_load_asc +.. autofunction:: libtcodpy.console_load_xp +.. autofunction:: libtcodpy.console_list_load_xp +.. autofunction:: libtcodpy.console_list_save_xp +.. autofunction:: libtcodpy.console_map_ascii_code_to_font +.. autofunction:: libtcodpy.console_map_ascii_codes_to_font +.. autofunction:: libtcodpy.console_map_string_to_font +.. autofunction:: libtcodpy.console_new +.. autofunction:: libtcodpy.console_print +.. autofunction:: libtcodpy.console_print_ex +.. autofunction:: libtcodpy.console_print_frame +.. autofunction:: libtcodpy.console_print_rect +.. autofunction:: libtcodpy.console_print_rect_ex +.. autofunction:: libtcodpy.console_put_char +.. autofunction:: libtcodpy.console_put_char_ex +.. autofunction:: libtcodpy.console_rect +.. autofunction:: libtcodpy.console_save_apf +.. autofunction:: libtcodpy.console_save_asc +.. autofunction:: libtcodpy.console_save_xp +.. autofunction:: libtcodpy.console_set_alignment +.. autofunction:: libtcodpy.console_set_background_flag +.. autofunction:: libtcodpy.console_set_char +.. autofunction:: libtcodpy.console_set_char_background +.. autofunction:: libtcodpy.console_set_char_foreground +.. autofunction:: libtcodpy.console_set_color_control +.. autofunction:: libtcodpy.console_set_default_background +.. autofunction:: libtcodpy.console_set_default_foreground +.. autofunction:: libtcodpy.console_set_fade +.. autofunction:: libtcodpy.console_set_fullscreen +.. autofunction:: libtcodpy.console_set_key_color +.. autofunction:: libtcodpy.console_set_window_title +.. autofunction:: libtcodpy.console_vline +.. autofunction:: libtcodpy.console_wait_for_keypress + +.. autoclass: libtcodpy.ConsoleBuffer :members: Event ----- -.. autoclass:: tcod.Key() +.. autoclass:: libtcodpy.Key() :members: -.. autoclass:: tcod.Mouse() +.. autoclass:: libtcodpy.Mouse() :members: .. _event types: @@ -173,236 +175,236 @@ Event Event Types ~~~~~~~~~~~ -.. data:: tcod.EVENT_NONE -.. data:: tcod.EVENT_KEY_PRESS -.. data:: tcod.EVENT_KEY_RELEASE -.. data:: tcod.EVENT_KEY +.. data:: libtcodpy.EVENT_NONE +.. data:: libtcodpy.EVENT_KEY_PRESS +.. data:: libtcodpy.EVENT_KEY_RELEASE +.. data:: libtcodpy.EVENT_KEY - Same as ``tcod.EVENT_KEY_PRESS | tcod.EVENT_KEY_RELEASE`` + Same as ``libtcodpy.EVENT_KEY_PRESS | libtcodpy.EVENT_KEY_RELEASE`` -.. data:: tcod.EVENT_MOUSE_MOVE -.. data:: tcod.EVENT_MOUSE_PRESS -.. data:: tcod.EVENT_MOUSE_RELEASE -.. data:: tcod.EVENT_MOUSE +.. data:: libtcodpy.EVENT_MOUSE_MOVE +.. data:: libtcodpy.EVENT_MOUSE_PRESS +.. data:: libtcodpy.EVENT_MOUSE_RELEASE +.. data:: libtcodpy.EVENT_MOUSE - Same as ``tcod.EVENT_MOUSE_MOVE | tcod.EVENT_MOUSE_PRESS | tcod.EVENT_MOUSE_RELEASE`` + Same as ``libtcodpy.EVENT_MOUSE_MOVE | libtcodpy.EVENT_MOUSE_PRESS | libtcodpy.EVENT_MOUSE_RELEASE`` -.. data:: tcod.EVENT_FINGER_MOVE -.. data:: tcod.EVENT_FINGER_PRESS -.. data:: tcod.EVENT_FINGER_RELEASE -.. data:: tcod.EVENT_FINGER +.. data:: libtcodpy.EVENT_FINGER_MOVE +.. data:: libtcodpy.EVENT_FINGER_PRESS +.. data:: libtcodpy.EVENT_FINGER_RELEASE +.. data:: libtcodpy.EVENT_FINGER - Same as ``tcod.EVENT_FINGER_MOVE | tcod.EVENT_FINGER_PRESS | tcod.EVENT_FINGER_RELEASE`` + Same as ``libtcodpy.EVENT_FINGER_MOVE | libtcodpy.EVENT_FINGER_PRESS | libtcodpy.EVENT_FINGER_RELEASE`` -.. data:: tcod.EVENT_ANY +.. data:: libtcodpy.EVENT_ANY - Same as ``tcod.EVENT_KEY | tcod.EVENT_MOUSE | tcod.EVENT_FINGER`` + Same as ``libtcodpy.EVENT_KEY | libtcodpy.EVENT_MOUSE | libtcodpy.EVENT_FINGER`` sys --- -.. autofunction:: tcod.sys_set_fps -.. autofunction:: tcod.sys_get_fps -.. autofunction:: tcod.sys_get_last_frame_length -.. autofunction:: tcod.sys_sleep_milli -.. autofunction:: tcod.sys_elapsed_milli -.. autofunction:: tcod.sys_elapsed_seconds -.. autofunction:: tcod.sys_set_renderer -.. autofunction:: tcod.sys_get_renderer -.. autofunction:: tcod.sys_save_screenshot -.. autofunction:: tcod.sys_force_fullscreen_resolution -.. autofunction:: tcod.sys_get_current_resolution -.. autofunction:: tcod.sys_get_char_size -.. autofunction:: tcod.sys_update_char -.. autofunction:: tcod.sys_register_SDL_renderer -.. autofunction:: tcod.sys_check_for_event -.. autofunction:: tcod.sys_wait_for_event +.. autofunction:: libtcodpy.sys_set_fps +.. autofunction:: libtcodpy.sys_get_fps +.. autofunction:: libtcodpy.sys_get_last_frame_length +.. autofunction:: libtcodpy.sys_sleep_milli +.. autofunction:: libtcodpy.sys_elapsed_milli +.. autofunction:: libtcodpy.sys_elapsed_seconds +.. autofunction:: libtcodpy.sys_set_renderer +.. autofunction:: libtcodpy.sys_get_renderer +.. autofunction:: libtcodpy.sys_save_screenshot +.. autofunction:: libtcodpy.sys_force_fullscreen_resolution +.. autofunction:: libtcodpy.sys_get_current_resolution +.. autofunction:: libtcodpy.sys_get_char_size +.. autofunction:: libtcodpy.sys_update_char +.. autofunction:: libtcodpy.sys_register_SDL_renderer +.. autofunction:: libtcodpy.sys_check_for_event +.. autofunction:: libtcodpy.sys_wait_for_event pathfinding ----------- -.. autofunction:: tcod.dijkstra_compute -.. autofunction:: tcod.dijkstra_delete -.. autofunction:: tcod.dijkstra_get -.. autofunction:: tcod.dijkstra_get_distance -.. autofunction:: tcod.dijkstra_is_empty -.. autofunction:: tcod.dijkstra_new -.. autofunction:: tcod.dijkstra_new_using_function -.. autofunction:: tcod.dijkstra_path_set -.. autofunction:: tcod.dijkstra_path_walk -.. autofunction:: tcod.dijkstra_reverse -.. autofunction:: tcod.dijkstra_size - -.. autofunction:: tcod.path_compute -.. autofunction:: tcod.path_delete -.. autofunction:: tcod.path_get -.. autofunction:: tcod.path_get_destination -.. autofunction:: tcod.path_get_origin -.. autofunction:: tcod.path_is_empty -.. autofunction:: tcod.path_new_using_function -.. autofunction:: tcod.path_new_using_map -.. autofunction:: tcod.path_reverse -.. autofunction:: tcod.path_size -.. autofunction:: tcod.path_walk +.. autofunction:: libtcodpy.dijkstra_compute +.. autofunction:: libtcodpy.dijkstra_delete +.. autofunction:: libtcodpy.dijkstra_get +.. autofunction:: libtcodpy.dijkstra_get_distance +.. autofunction:: libtcodpy.dijkstra_is_empty +.. autofunction:: libtcodpy.dijkstra_new +.. autofunction:: libtcodpy.dijkstra_new_using_function +.. autofunction:: libtcodpy.dijkstra_path_set +.. autofunction:: libtcodpy.dijkstra_path_walk +.. autofunction:: libtcodpy.dijkstra_reverse +.. autofunction:: libtcodpy.dijkstra_size + +.. autofunction:: libtcodpy.path_compute +.. autofunction:: libtcodpy.path_delete +.. autofunction:: libtcodpy.path_get +.. autofunction:: libtcodpy.path_get_destination +.. autofunction:: libtcodpy.path_get_origin +.. autofunction:: libtcodpy.path_is_empty +.. autofunction:: libtcodpy.path_new_using_function +.. autofunction:: libtcodpy.path_new_using_map +.. autofunction:: libtcodpy.path_reverse +.. autofunction:: libtcodpy.path_size +.. autofunction:: libtcodpy.path_walk heightmap --------- -.. autofunction:: tcod.heightmap_add -.. autofunction:: tcod.heightmap_add_fbm -.. autofunction:: tcod.heightmap_add_hill -.. autofunction:: tcod.heightmap_add_hm -.. autofunction:: tcod.heightmap_add_voronoi -.. autofunction:: tcod.heightmap_clamp -.. autofunction:: tcod.heightmap_clear -.. autofunction:: tcod.heightmap_copy -.. autofunction:: tcod.heightmap_count_cells -.. autofunction:: tcod.heightmap_delete -.. autofunction:: tcod.heightmap_dig_bezier -.. autofunction:: tcod.heightmap_dig_hill -.. autofunction:: tcod.heightmap_get_interpolated_value -.. autofunction:: tcod.heightmap_get_minmax -.. autofunction:: tcod.heightmap_get_normal -.. autofunction:: tcod.heightmap_get_slope -.. autofunction:: tcod.heightmap_get_value -.. autofunction:: tcod.heightmap_has_land_on_border -.. autofunction:: tcod.heightmap_kernel_transform -.. autofunction:: tcod.heightmap_lerp_hm -.. autofunction:: tcod.heightmap_multiply_hm -.. autofunction:: tcod.heightmap_new -.. autofunction:: tcod.heightmap_normalize -.. autofunction:: tcod.heightmap_rain_erosion -.. autofunction:: tcod.heightmap_scale -.. autofunction:: tcod.heightmap_scale_fbm -.. autofunction:: tcod.heightmap_set_value +.. autofunction:: libtcodpy.heightmap_add +.. autofunction:: libtcodpy.heightmap_add_fbm +.. autofunction:: libtcodpy.heightmap_add_hill +.. autofunction:: libtcodpy.heightmap_add_hm +.. autofunction:: libtcodpy.heightmap_add_voronoi +.. autofunction:: libtcodpy.heightmap_clamp +.. autofunction:: libtcodpy.heightmap_clear +.. autofunction:: libtcodpy.heightmap_copy +.. autofunction:: libtcodpy.heightmap_count_cells +.. autofunction:: libtcodpy.heightmap_delete +.. autofunction:: libtcodpy.heightmap_dig_bezier +.. autofunction:: libtcodpy.heightmap_dig_hill +.. autofunction:: libtcodpy.heightmap_get_interpolated_value +.. autofunction:: libtcodpy.heightmap_get_minmax +.. autofunction:: libtcodpy.heightmap_get_normal +.. autofunction:: libtcodpy.heightmap_get_slope +.. autofunction:: libtcodpy.heightmap_get_value +.. autofunction:: libtcodpy.heightmap_has_land_on_border +.. autofunction:: libtcodpy.heightmap_kernel_transform +.. autofunction:: libtcodpy.heightmap_lerp_hm +.. autofunction:: libtcodpy.heightmap_multiply_hm +.. autofunction:: libtcodpy.heightmap_new +.. autofunction:: libtcodpy.heightmap_normalize +.. autofunction:: libtcodpy.heightmap_rain_erosion +.. autofunction:: libtcodpy.heightmap_scale +.. autofunction:: libtcodpy.heightmap_scale_fbm +.. autofunction:: libtcodpy.heightmap_set_value image ----- -.. autofunction:: tcod.image_load -.. autofunction:: tcod.image_from_console - -.. autofunction:: tcod.image_blit -.. autofunction:: tcod.image_blit_2x -.. autofunction:: tcod.image_blit_rect -.. autofunction:: tcod.image_clear -.. autofunction:: tcod.image_delete -.. autofunction:: tcod.image_get_alpha -.. autofunction:: tcod.image_get_mipmap_pixel -.. autofunction:: tcod.image_get_pixel -.. autofunction:: tcod.image_get_size -.. autofunction:: tcod.image_hflip -.. autofunction:: tcod.image_invert -.. autofunction:: tcod.image_is_pixel_transparent -.. autofunction:: tcod.image_new -.. autofunction:: tcod.image_put_pixel -.. autofunction:: tcod.image_refresh_console -.. autofunction:: tcod.image_rotate90 -.. autofunction:: tcod.image_save -.. autofunction:: tcod.image_scale -.. autofunction:: tcod.image_set_key_color -.. autofunction:: tcod.image_vflip +.. autofunction:: libtcodpy.image_load +.. autofunction:: libtcodpy.image_from_console + +.. autofunction:: libtcodpy.image_blit +.. autofunction:: libtcodpy.image_blit_2x +.. autofunction:: libtcodpy.image_blit_rect +.. autofunction:: libtcodpy.image_clear +.. autofunction:: libtcodpy.image_delete +.. autofunction:: libtcodpy.image_get_alpha +.. autofunction:: libtcodpy.image_get_mipmap_pixel +.. autofunction:: libtcodpy.image_get_pixel +.. autofunction:: libtcodpy.image_get_size +.. autofunction:: libtcodpy.image_hflip +.. autofunction:: libtcodpy.image_invert +.. autofunction:: libtcodpy.image_is_pixel_transparent +.. autofunction:: libtcodpy.image_new +.. autofunction:: libtcodpy.image_put_pixel +.. autofunction:: libtcodpy.image_refresh_console +.. autofunction:: libtcodpy.image_rotate90 +.. autofunction:: libtcodpy.image_save +.. autofunction:: libtcodpy.image_scale +.. autofunction:: libtcodpy.image_set_key_color +.. autofunction:: libtcodpy.image_vflip line ---- -.. autofunction:: tcod.line_init -.. autofunction:: tcod.line_step -.. autofunction:: tcod.line -.. autofunction:: tcod.line_iter -.. autofunction:: tcod.line_where +.. autofunction:: libtcodpy.line_init +.. autofunction:: libtcodpy.line_step +.. autofunction:: libtcodpy.line +.. autofunction:: libtcodpy.line_iter +.. autofunction:: libtcodpy.line_where map --- -.. autofunction:: tcod.map_clear -.. autofunction:: tcod.map_compute_fov -.. autofunction:: tcod.map_copy -.. autofunction:: tcod.map_delete -.. autofunction:: tcod.map_get_height -.. autofunction:: tcod.map_get_width -.. autofunction:: tcod.map_is_in_fov -.. autofunction:: tcod.map_is_transparent -.. autofunction:: tcod.map_is_walkable -.. autofunction:: tcod.map_new -.. autofunction:: tcod.map_set_properties +.. autofunction:: libtcodpy.map_clear +.. autofunction:: libtcodpy.map_compute_fov +.. autofunction:: libtcodpy.map_copy +.. autofunction:: libtcodpy.map_delete +.. autofunction:: libtcodpy.map_get_height +.. autofunction:: libtcodpy.map_get_width +.. autofunction:: libtcodpy.map_is_in_fov +.. autofunction:: libtcodpy.map_is_transparent +.. autofunction:: libtcodpy.map_is_walkable +.. autofunction:: libtcodpy.map_new +.. autofunction:: libtcodpy.map_set_properties mouse ----- -.. autofunction:: tcod.mouse_get_status -.. autofunction:: tcod.mouse_is_cursor_visible -.. autofunction:: tcod.mouse_move -.. autofunction:: tcod.mouse_show_cursor +.. autofunction:: libtcodpy.mouse_get_status +.. autofunction:: libtcodpy.mouse_is_cursor_visible +.. autofunction:: libtcodpy.mouse_move +.. autofunction:: libtcodpy.mouse_show_cursor namegen ------- -.. autofunction:: tcod.namegen_destroy -.. autofunction:: tcod.namegen_generate -.. autofunction:: tcod.namegen_generate_custom -.. autofunction:: tcod.namegen_get_sets -.. autofunction:: tcod.namegen_parse +.. autofunction:: libtcodpy.namegen_destroy +.. autofunction:: libtcodpy.namegen_generate +.. autofunction:: libtcodpy.namegen_generate_custom +.. autofunction:: libtcodpy.namegen_get_sets +.. autofunction:: libtcodpy.namegen_parse noise ----- -.. autofunction:: tcod.noise_delete -.. autofunction:: tcod.noise_get -.. autofunction:: tcod.noise_get_fbm -.. autofunction:: tcod.noise_get_turbulence -.. autofunction:: tcod.noise_new -.. autofunction:: tcod.noise_set_type +.. autofunction:: libtcodpy.noise_delete +.. autofunction:: libtcodpy.noise_get +.. autofunction:: libtcodpy.noise_get_fbm +.. autofunction:: libtcodpy.noise_get_turbulence +.. autofunction:: libtcodpy.noise_new +.. autofunction:: libtcodpy.noise_set_type parser ------ -.. autofunction:: tcod.parser_delete -.. autofunction:: tcod.parser_get_bool_property -.. autofunction:: tcod.parser_get_char_property -.. autofunction:: tcod.parser_get_color_property -.. autofunction:: tcod.parser_get_dice_property -.. autofunction:: tcod.parser_get_float_property -.. autofunction:: tcod.parser_get_int_property -.. autofunction:: tcod.parser_get_list_property -.. autofunction:: tcod.parser_get_string_property -.. autofunction:: tcod.parser_new -.. autofunction:: tcod.parser_new_struct -.. autofunction:: tcod.parser_run +.. autofunction:: libtcodpy.parser_delete +.. autofunction:: libtcodpy.parser_get_bool_property +.. autofunction:: libtcodpy.parser_get_char_property +.. autofunction:: libtcodpy.parser_get_color_property +.. autofunction:: libtcodpy.parser_get_dice_property +.. autofunction:: libtcodpy.parser_get_float_property +.. autofunction:: libtcodpy.parser_get_int_property +.. autofunction:: libtcodpy.parser_get_list_property +.. autofunction:: libtcodpy.parser_get_string_property +.. autofunction:: libtcodpy.parser_new +.. autofunction:: libtcodpy.parser_new_struct +.. autofunction:: libtcodpy.parser_run random ------ -.. autofunction:: tcod.random_delete -.. autofunction:: tcod.random_get_double -.. autofunction:: tcod.random_get_double_mean -.. autofunction:: tcod.random_get_float -.. autofunction:: tcod.random_get_float_mean -.. autofunction:: tcod.random_get_instance -.. autofunction:: tcod.random_get_int -.. autofunction:: tcod.random_get_int_mean -.. autofunction:: tcod.random_new -.. autofunction:: tcod.random_new_from_seed -.. autofunction:: tcod.random_restore -.. autofunction:: tcod.random_save -.. autofunction:: tcod.random_set_distribution +.. autofunction:: libtcodpy.random_delete +.. autofunction:: libtcodpy.random_get_double +.. autofunction:: libtcodpy.random_get_double_mean +.. autofunction:: libtcodpy.random_get_float +.. autofunction:: libtcodpy.random_get_float_mean +.. autofunction:: libtcodpy.random_get_instance +.. autofunction:: libtcodpy.random_get_int +.. autofunction:: libtcodpy.random_get_int_mean +.. autofunction:: libtcodpy.random_new +.. autofunction:: libtcodpy.random_new_from_seed +.. autofunction:: libtcodpy.random_restore +.. autofunction:: libtcodpy.random_save +.. autofunction:: libtcodpy.random_set_distribution struct ------ -.. autofunction:: tcod.struct_add_flag -.. autofunction:: tcod.struct_add_list_property -.. autofunction:: tcod.struct_add_property -.. autofunction:: tcod.struct_add_structure -.. autofunction:: tcod.struct_add_value_list -.. autofunction:: tcod.struct_get_name -.. autofunction:: tcod.struct_get_type -.. autofunction:: tcod.struct_is_mandatory +.. autofunction:: libtcodpy.struct_add_flag +.. autofunction:: libtcodpy.struct_add_list_property +.. autofunction:: libtcodpy.struct_add_property +.. autofunction:: libtcodpy.struct_add_structure +.. autofunction:: libtcodpy.struct_add_value_list +.. autofunction:: libtcodpy.struct_get_name +.. autofunction:: libtcodpy.struct_get_type +.. autofunction:: libtcodpy.struct_is_mandatory other ----- -.. autoclass:: tcod.ConsoleBuffer +.. autoclass:: libtcodpy.ConsoleBuffer :members: -.. autoclass:: tcod.Dice(nb_dices=0, nb_faces=0, multiplier=0, addsub=0) +.. autoclass:: libtcodpy.Dice(nb_dices=0, nb_faces=0, multiplier=0, addsub=0) :members: diff --git a/fonts/X11/README.md b/fonts/X11/README.md index 621dac8d..52921145 100644 --- a/fonts/X11/README.md +++ b/fonts/X11/README.md @@ -9,6 +9,6 @@ import tcod.tileset tcod.tileset.set_default(tcod.tileset.load_bdf("file_to_load.bdf")) # Start python-tcod normally. -with tcod.console_init_root(...) as root_console: +with libtcodpy.console_init_root(...) as root_console: ... ``` diff --git a/tcod/console.py b/tcod/console.py index d8c0540b..36cc0aba 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -244,7 +244,7 @@ def ch(self) -> NDArray[np.intc]: return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] @property - @deprecate("This attribute has been renamed to `rgba`.") + @deprecate("This attribute has been renamed to `rgba`.", category=FutureWarning) def tiles(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -260,7 +260,7 @@ def tiles(self) -> NDArray[Any]: return self.rgba @property - @deprecate("This attribute has been renamed to `rgba`.") + @deprecate("This attribute has been renamed to `rgba`.", category=FutureWarning) def buffer(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -272,7 +272,7 @@ def buffer(self) -> NDArray[Any]: return self.rgba @property - @deprecate("This attribute has been renamed to `rgb`.") + @deprecate("This attribute has been renamed to `rgb`.", category=FutureWarning) def tiles_rgb(self) -> NDArray[Any]: """An array of this consoles data without the alpha channel. @@ -284,7 +284,7 @@ def tiles_rgb(self) -> NDArray[Any]: return self.rgb @property - @deprecate("This attribute has been renamed to `rgb`.") + @deprecate("This attribute has been renamed to `rgb`.", category=FutureWarning) def tiles2(self) -> NDArray[Any]: """This name is deprecated in favour of :any:`rgb`. @@ -348,7 +348,7 @@ def default_bg(self) -> tuple[int, int, int]: return color.r, color.g, color.b @default_bg.setter - @deprecate("Console defaults have been deprecated.") + @deprecate("Console defaults have been deprecated.", category=FutureWarning) def default_bg(self, color: tuple[int, int, int]) -> None: self._console_data.back = color @@ -359,7 +359,7 @@ def default_fg(self) -> tuple[int, int, int]: return color.r, color.g, color.b @default_fg.setter - @deprecate("Console defaults have been deprecated.") + @deprecate("Console defaults have been deprecated.", category=FutureWarning) def default_fg(self, color: tuple[int, int, int]) -> None: self._console_data.fore = color @@ -369,7 +369,7 @@ def default_bg_blend(self) -> int: return self._console_data.bkgnd_flag # type: ignore @default_bg_blend.setter - @deprecate("Console defaults have been deprecated.") + @deprecate("Console defaults have been deprecated.", category=FutureWarning) def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @@ -379,7 +379,7 @@ def default_alignment(self) -> int: return self._console_data.alignment # type: ignore @default_alignment.setter - @deprecate("Console defaults have been deprecated.") + @deprecate("Console defaults have been deprecated.", category=FutureWarning) def default_alignment(self, value: int) -> None: self._console_data.alignment = value @@ -838,14 +838,14 @@ def __enter__(self) -> Console: """Return this console in a managed context. When the root console is used as a context, the graphical window will - close once the context is left as if :any:`tcod.console_delete` was + close once the context is left as if :any:`libtcodpy.console_delete` was called on it. This is useful for some Python IDE's like IDLE, where the window would not be closed on its own otherwise. .. seealso:: - :any:`tcod.console_init_root` + :any:`libtcodpy.console_init_root` """ if self.console_c != ffi.NULL: msg = "Only the root console has a context." @@ -856,7 +856,7 @@ def close(self) -> None: """Close the active window managed by libtcod. This must only be called on the root console, which is returned from - :any:`tcod.console_init_root`. + :any:`libtcodpy.console_init_root`. .. versionadded:: 11.11 """ @@ -1226,7 +1226,7 @@ def get_height_rect(width: int, string: str) -> int: return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) -@deprecate("This function does not support contexts.") +@deprecate("This function does not support contexts.", category=FutureWarning) def recommended_size() -> tuple[int, int]: """Return the recommended size of a console for the current active window. @@ -1238,8 +1238,8 @@ def recommended_size() -> tuple[int, int]: .. versionadded:: 11.8 .. seealso:: - :any:`tcod.console_init_root` - :any:`tcod.console_flush` + :any:`libtcodpy.console_init_root` + :any:`libtcodpy.console_flush` .. deprecated:: 11.13 This function does not support contexts. diff --git a/tcod/context.py b/tcod/context.py index abfd3967..20f749db 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -3,47 +3,22 @@ See :ref:`getting-started` for beginner examples on how to use this module. :any:`Context`'s are intended to replace several libtcod functions such as -:any:`tcod.console_init_root`, :any:`tcod.console_flush`, +:any:`libtcodpy.console_init_root`, :any:`libtcodpy.console_flush`, :any:`tcod.console.recommended_size`, and many other functions which rely on hidden global objects within libtcod. If you begin using contexts then most of these functions will no longer work properly. -Instead of calling :any:`tcod.console_init_root` you can call +Instead of calling :any:`libtcodpy.console_init_root` you can call :any:`tcod.context.new` with different keywords depending on how you plan to setup the size of the console. You should use :any:`tcod.tileset` to load the font for a context. .. note:: - If you use contexts then you should expect the following functions to no - longer be available, because these functions rely on a global console, - tileset, or some other kind of global state: - - :any:`tcod.console_init_root`, - :any:`tcod.console_set_custom_font`, - :any:`tcod.console_flush`, - :any:`tcod.console_is_fullscreen`, - :any:`tcod.console_is_window_closed`, - :any:`tcod.console_set_fade`, - :any:`tcod.console_set_fullscreen`, - :any:`tcod.console_set_window_title`, - :any:`tcod.sys_set_fps`, - :any:`tcod.sys_get_last_frame_length`, - :any:`tcod.sys_set_renderer`, - :any:`tcod.sys_save_screenshot`, - :any:`tcod.sys_force_fullscreen_resolution`, - :any:`tcod.sys_get_current_resolution`, - :any:`tcod.sys_register_SDL_renderer`, - :any:`tcod.console_map_ascii_code_to_font`, - :any:`tcod.sys_get_char_size`, - :any:`tcod.sys_update_char`, - :any:`tcod.console.recommended_size`, - :any:`tcod.tileset.get_default`, - :any:`tcod.tileset.set_default`. - - Some event functions can no longer return tile coordinates for the mouse: - :any:`tcod.sys_check_for_event`, - :any:`tcod.sys_wait_for_event`, - :any:`tcod.mouse_get_status`. + If you use contexts then expect deprecated functions from ``libtcodpy`` to no longer work correctly. + Those functions rely on a global console or tileset which doesn't exists with contexts. + Also ``libtcodpy`` event functions will no longer return tile coordinates for the mouse. + + New programs not using ``libtcodpy`` can ignore this warning. .. versionadded:: 11.12 """ diff --git a/tcod/event.py b/tcod/event.py index c8846d2c..364245f3 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1365,10 +1365,10 @@ def cmd_quit(self) -> None: raise SystemExit() - root_console = tcod.console_init_root(80, 60) + root_console = libtcodpy.console_init_root(80, 60) state = State() while True: - tcod.console_flush() + libtcodpy.console_flush() for event in tcod.event.wait(): state.dispatch(event) ''' @@ -1440,7 +1440,7 @@ def ev_windowhidden(self, event: tcod.event.WindowEvent) -> T | None: def ev_windowexposed(self, event: tcod.event.WindowEvent) -> T | None: """Called when a window is exposed, and needs to be refreshed. - This usually means a call to :any:`tcod.console_flush` is necessary. + This usually means a call to :any:`libtcodpy.console_flush` is necessary. """ def ev_windowmoved(self, event: tcod.event.WindowMoved) -> T | None: diff --git a/tcod/image.py b/tcod/image.py index dc147a54..b404361a 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -136,12 +136,12 @@ def get_alpha(self, x: int, y: int) -> int: return lib.TCOD_image_get_alpha(self.image_c, x, y) # type: ignore def refresh_console(self, console: tcod.console.Console) -> None: - """Update an Image created with :any:`tcod.image_from_console`. + """Update an Image created with :any:`libtcodpy.image_from_console`. The console used with this function should have the same width and - height as the Console given to :any:`tcod.image_from_console`. + height as the Console given to :any:`libtcodpy.image_from_console`. The font width and height must also be the same as when - :any:`tcod.image_from_console` was called. + :any:`libtcodpy.image_from_console` was called. Args: console (Console): A Console with a pixel width and height diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index fc2f9c9f..88086c1c 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -906,7 +906,7 @@ def console_init_root( .. code-block:: python - with tcod.console_init_root(80, 50, vsync=True) as root_console: + with libtcodpy.console_init_root(80, 50, vsync=True) as root_console: ... # Put your game loop here. ... # Window closes at the end of the above block. @@ -963,20 +963,20 @@ def console_set_custom_font( ) -> None: """Load the custom font file at `fontFile`. - Call this before function before calling :any:`tcod.console_init_root`. + Call this before function before calling :any:`libtcodpy.console_init_root`. Flags can be a mix of the following: - * tcod.FONT_LAYOUT_ASCII_INCOL: + * libtcodpy.FONT_LAYOUT_ASCII_INCOL: Decode tileset raw in column-major order. - * tcod.FONT_LAYOUT_ASCII_INROW: + * libtcodpy.FONT_LAYOUT_ASCII_INROW: Decode tileset raw in row-major order. - * tcod.FONT_TYPE_GREYSCALE: + * libtcodpy.FONT_TYPE_GREYSCALE: Force tileset to be read as greyscale. - * tcod.FONT_TYPE_GRAYSCALE - * tcod.FONT_LAYOUT_TCOD: + * libtcodpy.FONT_TYPE_GRAYSCALE + * libtcodpy.FONT_LAYOUT_TCOD: Unique layout used by libtcod. - * tcod.FONT_LAYOUT_CP437: + * libtcodpy.FONT_LAYOUT_CP437: Decode a row-major Code Page 437 tileset into Unicode. `nb_char_horiz` and `nb_char_vertic` are the columns and rows of the font @@ -1212,7 +1212,7 @@ def console_flush( `clear_color` can now be an RGB tuple. .. seealso:: - :any:`tcod.console_init_root` + :any:`libtcodpy.console_init_root` :any:`tcod.console.recommended_size` .. deprecated:: 11.13 @@ -1797,7 +1797,7 @@ def console_from_file(filename: str | PathLike[str]) -> tcod.console.Console: Returns: A new :any`Console` instance. .. deprecated:: 12.7 - Use :any:`tcod.console_load_xp` to load REXPaint consoles. + Use :any:`libtcodpy.console_load_xp` to load REXPaint consoles. Other formats are not actively supported. @@ -1863,7 +1863,7 @@ def console_delete(con: tcod.console.Console) -> None: "Instead of this call you should use Console.close," " or use a with statement to ensure the root console closes," " for example:" - "\n with tcod.console_init_root(...) as root_console:" + "\n with libtcodpy.console_init_root(...) as root_console:" "\n ...", DeprecationWarning, stacklevel=2, @@ -2033,7 +2033,7 @@ def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> .. deprecated:: 11.18 Functions modifying console objects in-place are deprecated. - Use :any:`tcod.console_from_xp` to load a Console from a file. + Use :any:`libtcodpy.console_from_xp` to load a Console from a file. .. versionchanged:: 16.0 Added PathLike support. @@ -3969,7 +3969,7 @@ def sys_get_fps() -> int: """Return the current frames per second. This the actual frame rate, not the frame limit set by - :any:`tcod.sys_set_fps`. + :any:`libtcodpy.sys_set_fps`. This number is updated every second. @@ -4145,7 +4145,7 @@ def sys_update_char( """Dynamically update the current font with img. All cells using this asciiCode will be updated - at the next call to :any:`tcod.console_flush`. + at the next call to :any:`libtcodpy.console_flush`. Args: asciiCode (int): Ascii code corresponding to the character to update. @@ -4174,7 +4174,7 @@ def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: The callback will receive a CData `void*` pointer to an `SDL_Surface*` struct. - The callback is called on every call to :any:`tcod.console_flush`. + The callback is called on every call to :any:`libtcodpy.console_flush`. Args: callback Callable[[CData], None]: diff --git a/tcod/tileset.py b/tcod/tileset.py index 533b9c99..b53b0149 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -284,7 +284,7 @@ def set_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: i tileset. The font will be scaled to fit the given `tile_height` and `tile_width`. - This function must be called before :any:`tcod.console_init_root`. Once + This function must be called before :any:`libtcodpy.console_init_root`. Once the root console is setup you may call this function again to change the font. The tileset can be changed but the window will not be resized automatically. @@ -309,7 +309,7 @@ def load_bdf(path: str | PathLike[str]) -> Tileset: package. Pass the returned Tileset to :any:`tcod.tileset.set_default` and it will - take effect when `tcod.console_init_root` is called. + take effect when `libtcodpy.console_init_root` is called. .. versionadded:: 11.10 """ From 0c1146753910a5463554daa854a33ec642ebe31b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jun 2023 18:55:05 -0700 Subject: [PATCH 0817/1101] Drop Python 3.8 from Mypy tests. Newer versions of NumPy don't support type-hints for this version. --- pyproject.toml | 2 +- tcod/path.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 447540a4..2fbc6ebb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,7 +107,7 @@ filterwarnings = [ [tool.mypy] files = ["."] -python_version = 3.8 +python_version = 3.9 warn_unused_configs = true show_error_codes = true disallow_subclassing_any = true diff --git a/tcod/path.py b/tcod/path.py index c25e9348..84d54138 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -330,7 +330,7 @@ def _export(array: NDArray[Any]) -> Any: return ffi.new("struct NArray*", _export_dict(array)) -def _compile_cost_edges(edge_map: Any) -> tuple[Any, int]: +def _compile_cost_edges(edge_map: ArrayLike) -> tuple[NDArray[np.intc], int]: """Return an edge_cost array using an integer map.""" edge_map = np.array(edge_map, copy=True) if edge_map.ndim != 2: From c8a704c9c2ea65c0b91f7a466c14fb94b43c814f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 19 Jun 2023 09:44:27 -0700 Subject: [PATCH 0818/1101] Fix wrong literal annotations for WindowResized. --- CHANGELOG.md | 3 +++ docs/tcod/getting-started.rst | 2 +- examples/eventget.py | 2 +- examples/ttf.py | 2 +- tcod/event.py | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330fa126..846a7fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Changed - Using `libtcod 1.24.0`. +### Fixed +- `WindowResized` literal annotations were in the wrong case. + ## [16.0.3] - 2023-06-04 ### Changed - Enabled logging for libtcod and SDL. diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index b277f864..c7634149 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -112,7 +112,7 @@ Example:: print(event) # Print event names and attributes. if isinstance(event, tcod.event.Quit): raise SystemExit() - elif isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED": + elif isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": pass # The next call to context.new_console may return a different size. diff --git a/examples/eventget.py b/examples/eventget.py index 774ad3b1..0895e90f 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -41,7 +41,7 @@ def main() -> None: print(repr(event)) if isinstance(event, tcod.event.Quit): raise SystemExit() - if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWRESIZED": + if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": console = context.new_console() if isinstance(event, tcod.event.ControllerDevice): if event.type == "CONTROLLERDEVICEADDED": diff --git a/examples/ttf.py b/examples/ttf.py index e1c585d5..da412b4b 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -81,7 +81,7 @@ def main() -> None: for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): raise SystemExit() - if isinstance(event, tcod.event.WindowResized) and event.type == "WINDOWSIZECHANGED": + if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": # Resize the Tileset to match the new screen size. context.change_tileset( load_ttf( diff --git a/tcod/event.py b/tcod/event.py index 364245f3..0991db76 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -827,8 +827,8 @@ class WindowResized(WindowEvent): height (int): The current height of the window. """ - type: Final[Literal["WINDOWRESIZED", "WINDOWSIZECHANGED"]] # type: ignore[assignment,misc] - """WINDOWRESIZED" or "WINDOWSIZECHANGED""" + type: Final[Literal["WindowResized", "WindowSizeChanged"]] # type: ignore[misc] + """WindowResized" or "WindowSizeChanged""" def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) From a7372fbc4bb5b47596ea2b51693d2ead8a21ef1a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 19 Jun 2023 10:43:35 -0700 Subject: [PATCH 0819/1101] Suppress internal warnings for deprecated tile attributes. --- tcod/context.py | 5 +++-- tcod/event.py | 16 +++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/tcod/context.py b/tcod/context.py index 20f749db..4b2d33e3 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -256,15 +256,16 @@ def convert_event(self, event: _Event) -> _Event: event_copy = copy.copy(event) if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion)) - event_copy.position = event.tile = tcod.event.Point(*self.pixel_to_tile(*event.position)) + event_copy.position = event._tile = tcod.event.Point(*self.pixel_to_tile(*event.position)) if isinstance(event, tcod.event.MouseMotion): assert isinstance(event_copy, tcod.event.MouseMotion) + assert event._tile is not None prev_tile = self.pixel_to_tile( event.position[0] - event.motion[0], event.position[1] - event.motion[1], ) event_copy.motion = event.tile_motion = tcod.event.Point( - event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1] + event._tile[0] - prev_tile[0], event._tile[1] - prev_tile[1] ) return event_copy diff --git a/tcod/event.py b/tcod/event.py index 0991db76..29671e03 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -412,7 +412,7 @@ def __init__( ) -> None: super().__init__() self.position = Point(*position) - self.__tile = Point(*tile) if tile is not None else None + self._tile = Point(*tile) if tile is not None else None self.state = state @property @@ -426,11 +426,6 @@ def pixel(self) -> Point: @pixel.setter def pixel(self, value: Point) -> None: - warnings.warn( - "The mouse.pixel attribute is deprecated. Use mouse.position instead.", - DeprecationWarning, - stacklevel=2, - ) self.position = value @property @@ -440,16 +435,11 @@ def tile(self) -> Point: DeprecationWarning, stacklevel=2, ) - return _verify_tile_coordinates(self.__tile) + return _verify_tile_coordinates(self._tile) @tile.setter def tile(self, xy: tuple[int, int]) -> None: - warnings.warn( - "The mouse.tile attribute is deprecated. Use mouse.position of the event returned by context.convert_event instead.", - DeprecationWarning, - stacklevel=2, - ) - self.__tile = Point(*xy) + self._tile = Point(*xy) def __repr__(self) -> str: return ("tcod.event.{}(position={!r}, tile={!r}, state={})").format( From 8e7dd8b6b7431a39d8a89f3febe5cfe629e456b6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 19 Jun 2023 11:38:59 -0700 Subject: [PATCH 0820/1101] Add mouse button/mask enums. Continuing to replace loose constants with enums. --- CHANGELOG.md | 6 +++ tcod/event.py | 113 +++++++++++++++++++++++---------------- tests/test_deprecated.py | 7 +++ 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 846a7fa9..54d8a268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,15 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Added +- Added the enums `tcod.event.MouseButton` and `tcod.event.MouseButtonMask`. + ### Changed - Using `libtcod 1.24.0`. +### Deprecated +- Mouse button and mask constants have been replaced by enums. + ### Fixed - `WindowResized` literal annotations were in the wrong case. diff --git a/tcod/event.py b/tcod/event.py index 29671e03..b76bb985 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -240,46 +240,48 @@ class Modifier(enum.IntFlag): """Alt graph.""" -# manually define names for SDL macros -BUTTON_LEFT = 1 -BUTTON_MIDDLE = 2 -BUTTON_RIGHT = 3 -BUTTON_X1 = 4 -BUTTON_X2 = 5 -BUTTON_LMASK = 0x1 -BUTTON_MMASK = 0x2 -BUTTON_RMASK = 0x4 -BUTTON_X1MASK = 0x8 -BUTTON_X2MASK = 0x10 - -# reverse tables are used to get the tcod.event name from the value. -_REVERSE_BUTTON_TABLE = { - BUTTON_LEFT: "BUTTON_LEFT", - BUTTON_MIDDLE: "BUTTON_MIDDLE", - BUTTON_RIGHT: "BUTTON_RIGHT", - BUTTON_X1: "BUTTON_X1", - BUTTON_X2: "BUTTON_X2", -} +class MouseButton(enum.IntEnum): + """An enum for mouse buttons. -_REVERSE_BUTTON_MASK_TABLE = { - BUTTON_LMASK: "BUTTON_LMASK", - BUTTON_MMASK: "BUTTON_MMASK", - BUTTON_RMASK: "BUTTON_RMASK", - BUTTON_X1MASK: "BUTTON_X1MASK", - BUTTON_X2MASK: "BUTTON_X2MASK", -} + .. versionadded:: Unreleased + """ + + LEFT = 1 + """Left mouse button.""" + MIDDLE = 2 + """Middle mouse button.""" + RIGHT = 3 + """Right mouse button.""" + X1 = 4 + """Back mouse button.""" + X2 = 5 + """Forward mouse button.""" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" -_REVERSE_BUTTON_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_TABLE) -_REVERSE_BUTTON_MASK_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_BUTTON_MASK_TABLE) +class MouseButtonMask(enum.IntFlag): + """A mask enum for held mouse buttons. -_REVERSE_MOD_TABLE = tcod.event_constants._REVERSE_MOD_TABLE.copy() -del _REVERSE_MOD_TABLE[KMOD_SHIFT] -del _REVERSE_MOD_TABLE[KMOD_CTRL] -del _REVERSE_MOD_TABLE[KMOD_ALT] -del _REVERSE_MOD_TABLE[KMOD_GUI] + .. versionadded:: Unreleased + """ -_REVERSE_MOD_TABLE_PREFIX = _ConstantsWithPrefix(_REVERSE_MOD_TABLE) + LEFT = 0x1 + """Left mouse button is held.""" + MIDDLE = 0x2 + """Middle mouse button is held.""" + RIGHT = 0x4 + """Right mouse button is held.""" + X1 = 0x8 + """Back mouse button is held.""" + X2 = 0x10 + """Forward mouse button is held.""" + + def __repr__(self) -> str: + if self == 0: + return f"{self.__class__.__name__}(0)" + return "|".join(f"{self.__class__.__name__}.{self.__class__(bit).name}" for bit in self.__class__ if bit & self) class Event: @@ -361,11 +363,11 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.{}(scancode={!r}, sym={!r}, mod={}{})".format( + return "tcod.event.{}(scancode={!r}, sym={!r}, mod={!r}{})".format( self.__class__.__name__, self.scancode, self.sym, - _describe_bitmask(self.mod, _REVERSE_MOD_TABLE_PREFIX), + self.mod, ", repeat=True" if self.repeat else "", ) @@ -446,7 +448,7 @@ def __repr__(self) -> str: self.__class__.__name__, tuple(self.position), tuple(self.tile), - _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), + MouseButtonMask(self.state), ) def __str__(self) -> str: @@ -454,7 +456,7 @@ def __str__(self) -> str: super().__str__().strip("<>"), *self.position, *self.tile, - _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE), + MouseButtonMask(self.state), ) @@ -552,13 +554,13 @@ def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: return self def __repr__(self) -> str: - return ("tcod.event.{}(position={!r}, motion={!r}, tile={!r}, tile_motion={!r}, state={})").format( + return ("tcod.event.{}(position={!r}, motion={!r}, tile={!r}, tile_motion={!r}, state={!r})").format( self.__class__.__name__, tuple(self.position), tuple(self.motion), tuple(self.tile), tuple(self.tile_motion), - _describe_bitmask(self.state, _REVERSE_BUTTON_MASK_TABLE_PREFIX), + MouseButtonMask(self.state), ) def __str__(self) -> str: @@ -619,19 +621,19 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.{}(position={!r}, tile={!r}, button={})".format( + return "tcod.event.{}(position={!r}, tile={!r}, button={!r})".format( self.__class__.__name__, tuple(self.position), tuple(self.tile), - _REVERSE_BUTTON_TABLE_PREFIX[self.button], + MouseButton(self.button), ) def __str__(self) -> str: - return " str: def __getattr__(name: str) -> int: """Migrate deprecated access of event constants.""" + if name.startswith("BUTTON_"): + replacement = { + "BUTTON_LEFT": MouseButton.LEFT, + "BUTTON_MIDDLE": MouseButton.MIDDLE, + "BUTTON_RIGHT": MouseButton.RIGHT, + "BUTTON_X1": MouseButton.X1, + "BUTTON_X2": MouseButton.X2, + "BUTTON_LMASK": MouseButtonMask.LEFT, + "BUTTON_MMASK": MouseButtonMask.MIDDLE, + "BUTTON_RMASK": MouseButtonMask.RIGHT, + "BUTTON_X1MASK": MouseButtonMask.X1, + "BUTTON_X2MASK": MouseButtonMask.X2, + }[name] + warnings.warn( + "Key constants have been replaced with enums.\n" + f"'tcod.event.{name}' should be replaced with 'tcod.event.{replacement!r}'", + FutureWarning, + stacklevel=2, + ) + return replacement + value: int | None = getattr(tcod.event_constants, name, None) if not value: msg = f"module {__name__!r} has no attribute {name!r}" diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index e1ee18c7..502d9ea9 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -42,6 +42,13 @@ def test_deprecate_key_constants() -> None: _ = tcod.event.SCANCODE_1 +def test_deprecate_mouse_constants() -> None: + with pytest.warns(FutureWarning, match=r"MouseButton.LEFT"): + _ = tcod.event.BUTTON_LEFT + with pytest.warns(FutureWarning, match=r"MouseButtonMask.LEFT"): + _ = tcod.event.BUTTON_LMASK + + def test_line_where() -> None: with pytest.warns(): where = tcod.libtcodpy.line_where(1, 0, 3, 4) From 16a8f8b0302846b0898f5777d6a539c6db400e29 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Jun 2023 22:08:07 -0700 Subject: [PATCH 0821/1101] Fix minor type error. Comparing self to `0` caused a minor panic in Mypy. --- tcod/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index b76bb985..77845ff9 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -279,7 +279,7 @@ class MouseButtonMask(enum.IntFlag): """Forward mouse button is held.""" def __repr__(self) -> str: - if self == 0: + if self.value == 0: return f"{self.__class__.__name__}(0)" return "|".join(f"{self.__class__.__name__}.{self.__class__(bit).name}" for bit in self.__class__ if bit & self) From 7ffb785f4d15250571f2945a273342659464b96c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Jun 2023 14:59:12 -0700 Subject: [PATCH 0822/1101] Prepare 16.1.0 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54d8a268..8fac3613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.1.0] - 2023-06-23 ### Added - Added the enums `tcod.event.MouseButton` and `tcod.event.MouseButtonMask`. diff --git a/tcod/event.py b/tcod/event.py index 77845ff9..d808436b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -243,7 +243,7 @@ class Modifier(enum.IntFlag): class MouseButton(enum.IntEnum): """An enum for mouse buttons. - .. versionadded:: Unreleased + .. versionadded:: 16.1 """ LEFT = 1 @@ -264,7 +264,7 @@ def __repr__(self) -> str: class MouseButtonMask(enum.IntFlag): """A mask enum for held mouse buttons. - .. versionadded:: Unreleased + .. versionadded:: 16.1 """ LEFT = 0x1 From 30aede9e1691056ae54278d2bb25d50279c69158 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jun 2023 18:13:26 -0700 Subject: [PATCH 0823/1101] Prevent documented literals from being broken by word wrapping. --- docs/_static/css/custom.css | 3 +++ docs/conf.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 docs/_static/css/custom.css diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 00000000..e14001c0 --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,3 @@ +code.literal { + white-space: nowrap +} diff --git a/docs/conf.py b/docs/conf.py index 090c191f..0e5f24ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -185,9 +185,9 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] +html_static_path = ["_static"] -# html_css_files = ["css/custom.css"] +html_css_files = ["css/custom.css"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied From 258a233edfab979a97b6aaa988262cc418a0377c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 18 Jun 2023 18:18:29 -0700 Subject: [PATCH 0824/1101] Add tutorial part 1. Initial draft of the tutorial. Not sure how to continue but I need this uploaded now. --- .vscode/settings.json | 2 + docs/index.rst | 7 ++ docs/tutorial/part-01.rst | 255 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 docs/tutorial/part-01.rst diff --git a/.vscode/settings.json b/.vscode/settings.json index e96ff1cf..7a02db40 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -110,6 +110,7 @@ "CURRENCYSUBUNIT", "CURRENCYUNIT", "currentmodule", + "dataclasses", "datas", "DBLAMPERSAND", "DBLVERTICALBAR", @@ -423,6 +424,7 @@ "todos", "tolist", "touchpad", + "traceback", "TRIGGERLEFT", "TRIGGERRIGHT", "tris", diff --git a/docs/index.rst b/docs/index.rst index c9760f9f..50a87f4a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,13 @@ Contents: changelog faq +.. toctree:: + :maxdepth: 2 + :caption: Tutorial + :glob: + + tutorial/part-* + .. toctree:: :maxdepth: 2 :caption: python-tcod API diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst new file mode 100644 index 00000000..cbae1101 --- /dev/null +++ b/docs/tutorial/part-01.rst @@ -0,0 +1,255 @@ +Part 1 +============================================================================== + +Initial script +------------------------------------------------------------------------------ + +First start with a modern top-level script: + +.. code-block:: python + + #!/usr/bin/env python3 + """This script is invoked to start the program.""" + from __future__ import annotations # PEP 563 + + + def main() -> None: + """Main entry point function.""" + pass # Nothing yet + + + if __name__ == "__main__": # Top-level code environment + main() + +The first line is a `shebang `_ which allows direct execution of the script and will hint certain Python launchers which version to use. +If you always invoke Python directly then you do not need this line. + +The triple-quoted string is a `docstring `_. +The one near the top documents the purpose for the module. +The one in ``main`` documents that function. + +``from __future__ import annotations`` tells Python to use `Postponed Evaluation of Annotations `_. +This is required for specific type-hints, such as a class using itself in its own annotations. +This will also speed up the initialization of code which uses type-hints. + +``def main() -> None:`` has no significance other than convention. +Because this function returns nothing it is annotated with ``-> None``. + +``if __name__ == "__main__":`` checks for the `Top-level code environment `_. +This prevents tools from accidentally launching the script when they just want to import it. +This is the only required boilerplate, everything else is optional. + +Loading a tileset and opening a window +------------------------------------------------------------------------------ + +From here it is time to setup a ``tcod`` program. +Download `Alloy_curses_12x12.png `_ and place this file in your projects ``data/`` directory. +This tileset is from the `Dwarf Fortress tileset repository `_ and you may choose to use any other tileset from there as long is you keep track of the filename yourself. + +Load the tileset with :any:`tcod.tileset.load_tilesheet` and then pass it to :any:`tcod.context.new`. +These functions are part of modules which have not been imported yet, so new imports need to be added. +:any:`tcod.context.new` returns a :any:`Context` which is used with the ``with`` statement. + +.. code-block:: python + :emphasize-lines: 2,3,8-12 + + ... + import tcod.context # Add these imports + import tcod.tileset + + + def main() -> None: + """Load a tileset and open a window using it, this window will immediately close.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + with tcod.context.new(tileset=tileset) as context: + pass # The window will stay open for the duration of this block + ... + +If an import fails that means you do not have ``tcod`` installed on the Python environment you just used to run the script. +If you use an IDE then make sure the Python environment it is using is correct and then run ``pip install tcod`` from the shell terminal within that IDE. + +If you run this script now then a window will open and then immediately close. +If that happens without seeing a traceback in your terminal then the script is correct. + +Configuring an event loop +------------------------------------------------------------------------------ + +The next step is to keep the window open until the user closes it. + +Since nothing is displayed yet a :any:`Console` should be created with ``"Hello World"`` printed to it. +The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. + +To actually display the console to the window the :any:`Context.present` method must be called with the console. +Be sure to check the additional a parameters of :any:`Context.present`, you can keep aspect or enforce integer scaling. + +Events are checked by iterating over all pending events. +If your game is strictly turn-based then you should use :any:`tcod.event.wait`. +If your game is real-time or has real-time animations then it should use :any:`tcod.event.get` instead. + +Test if an event is for closing the window with ``isinstance(event, tcod.event.Quit)``. +If this is True then you should exit the function, either with ``return``, or with :any:`sys.exit`, or with ``raise SystemExit``. + +.. code-block:: python + :emphasize-lines: 2,3,11-18 + + ... + import tcod.console + import tcod.event + + + def main() -> None: + """Show "Hello World" until the window is closed.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + console = tcod.console.Console(80, 50) + console.print(0, 0, "Hello World") # Test text by printing "Hello World" to the console + with tcod.context.new(console=console, tileset=tileset) as context: + while True: # Main loop + context.present(console) # Render the console to the window and show it + for event in tcod.event.wait(): # Event loop, blocks until pending events exist + if isinstance(event, tcod.event.Quit): + raise SystemExit() + ... + +If you run this then you get a window saying ``"Hello World"``. +The window can be resized and the console will be stretched to fit the new resolution. + +An example game state +------------------------------------------------------------------------------ + +What exists now is not very interactive. +The next step is to change state based on user input. + +Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with ``pip install attrs``. +Alternatively you can use :any:`dataclasses`, but this tutorial uses ``attrs`` since it has a more modern implementation. + +Start by adding an ``attrs`` class called ``ExampleState``. +This a normal class with the ``@attrs.define(eq=False)`` decorator added. + +This class should hold coordinates for the player. +It should also have a ``on_draw`` method which takes :any:`tcod.console.Console` as a parameter and marks the player position on it. +The parameters for ``on_draw`` are ``self`` because this is an instance method and ``console: tcod.console.Console``. +``on_draw`` returns nothing, so be sure to add ``-> None``. + +:any:`Console.print` is the simplest way to draw the player because other options would require bounds-checking. + +If ``tcod.console.Console`` is too verbose then you can add ``from tcod.console import Console`` so that you can use just ``Console`` instead. + +.. code-block:: python + + ... + import attrs + + + @attrs.define(eq=False) + class ExampleState: + """Example state with a hard-coded player position.""" + + player_x: int + """Player X position, left-most position is zero.""" + player_y: int + """Player Y position, top-most position is zero.""" + + def on_draw(self, console: tcod.console.Console) -> None: + """Draw the player glyph.""" + console.print(self.player_x, self.player_y, "@") + ... + +Now remove the ``console.print(0, 0, "Hello World")`` line from ``main``. + +Before the context is made create a new ``ExampleState`` with player coordinates on the screen. +Each :any:`Console` has ``.width`` and ``.height`` attributes which you can divide by 2 to get a centered coordinate for the player. +Use Python's floor division operator ``//`` so that the resulting type is ``int``. + +Modify the drawing routine so that the console is cleared, then passed to ``ExampleState.on_draw``, then passed to :any:`Context.present`. + +.. code-block:: python + :emphasize-lines: 9,12-14 + + ... + def main() -> None: + """Run ExampleState.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + tcod.tileset.procedural_block_elements(tileset=tileset) + console = tcod.console.Console(80, 50) + state = ExampleState(player_x=console.width // 2, player_y=console.height // 2) + with tcod.context.new(console=console, tileset=tileset) as context: + while True: + console.clear() # Clear the console before any drawing + state.on_draw(console) # Draw the current state + context.present(console) # Display the console on the window + for event in tcod.event.wait(): + if isinstance(event, tcod.event.Quit): + raise SystemExit() + ... + +Now if you run the script you'll see ``@``. + +This code is sensitive to typing. +If you wrote ``player_x=console.width / 2`` instead of ``player_x=console.width // 2`` (note the number of slashes) then ``player_x`` will be assigned as a float instead of an int. +If ``player_x`` is a float then :any:`Console.print` will raise a TypeError. +In this case the incorrect code is when ``ExampleState`` is created with an invalid type and not the print function call. +Running ``mypy`` on your code will show you this type error at the correct position. +Your IDE should also complain about a bad type if setup correctly. + +The next step is to move the player on events. +A new method will be added to the ``ExampleState`` for this called ``on_event``. +``on_event`` takes a ``self`` and a :any:`tcod.event.Event` parameter and returns nothing. + +Events are best handled using Python's `Structural Pattern Matching `_. +Consider reading `Python's Structural Pattern Matching Tutorial `_. + +Begin matching with ``match event:``. +The equivalent to ``if isinstance(event, tcod.event.Quit):`` is ``case tcod.event.Quit():``. +Keyboard keys can be checked with ``case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):``. +Make a case for each arrow key: ``LEFT`` ``RIGHT`` ``UP`` ``DOWN`` and move the player in the direction of that key. +See :any:`KeySym` for a list of all keys. + +.. code-block:: python + + ... + @attrs.define(eq=False) + class ExampleState: + ... + + def on_event(self, event: tcod.event.Event) -> None: + """Move the player on events and handle exiting. Movement is hard-coded.""" + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT): + self.player_x -= 1 + case tcod.event.KeyDown(sym=tcod.event.KeySym.RIGHT): + self.player_x += 1 + case tcod.event.KeyDown(sym=tcod.event.KeySym.UP): + self.player_y -= 1 + case tcod.event.KeyDown(sym=tcod.event.KeySym.DOWN): + self.player_y += 1 + ... + +Now replace the event handling code in ``main`` to defer to the states ``on_event`` method. + +.. code-block:: python + :emphasize-lines: 11 + + ... + def main() -> None: + ... + state = ExampleState(player_x=console.width // 2, player_y=console.height // 2) + with tcod.context.new(console=console, tileset=tileset) as context: + while True: + console.clear() + state.on_draw(console) + context.present(console) + for event in tcod.event.wait(): + state.on_event(event) # Pass events to the state + ... + +Now when you run this script you have a player character you can move around with the arrow keys before closing the window. + +You can review the part-1 source code `here `_. From a7a037b86518e481cb2febc8b5bd1d201f2f6c93 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Jun 2023 22:01:02 -0700 Subject: [PATCH 0825/1101] Remove tutorial options and explanations. Needed to make the tutorial more useful to learners. --- docs/tutorial/part-00.rst | 38 +++++++++++++++++++++ docs/tutorial/part-01.rst | 70 +++++++++++---------------------------- 2 files changed, 58 insertions(+), 50 deletions(-) create mode 100644 docs/tutorial/part-00.rst diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst new file mode 100644 index 00000000..9cc4b7cd --- /dev/null +++ b/docs/tutorial/part-00.rst @@ -0,0 +1,38 @@ +Part 0 - Setting up a project +############################################################################## + +Starting tools +============================================================================== + +The IDE used for this tutorial is `Visual Studio Code `_ (not to be mistaken for Visual Studio). + +Git will be used for version control. +`Follow the instructions here `_. + +Python 3.11 was used to make this tutorial. +`Get the latest version of Python here `_. +If there exists a version of Python later then 3.11 then install that version instead. + + +First script +============================================================================== + +First start with a modern top-level script. +Create a script in the project root folder called ``main.py`` which checks ``if __name__ == "__main__":`` and calls a ``main`` function. + +.. code-block:: python + + def main() -> None: + print("Hello World!") + + + if __name__ == "__main__": + main() + +In VSCode on the left sidebar is a **Run and Debug** tab. +On this tab select **create a launch.json** file. +This will prompt about what kind of program to launch. +Pick ``Python``, then ``Module``, then when asked for the module name type ``main``. +From now on the ``F5`` key will launch ``main.py`` in debug mode. + +Run the script now and ``"Hello World!"`` should be visible in the terminal output. diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index cbae1101..00c9eded 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -1,50 +1,29 @@ -Part 1 -============================================================================== +Part 1 - Moving a player around the screen +############################################################################## Initial script ------------------------------------------------------------------------------- +============================================================================== -First start with a modern top-level script: +First start with a modern top-level script. .. code-block:: python - #!/usr/bin/env python3 - """This script is invoked to start the program.""" - from __future__ import annotations # PEP 563 - - def main() -> None: - """Main entry point function.""" - pass # Nothing yet + ... - if __name__ == "__main__": # Top-level code environment + if __name__ == "__main__": main() -The first line is a `shebang `_ which allows direct execution of the script and will hint certain Python launchers which version to use. -If you always invoke Python directly then you do not need this line. - -The triple-quoted string is a `docstring `_. -The one near the top documents the purpose for the module. -The one in ``main`` documents that function. - -``from __future__ import annotations`` tells Python to use `Postponed Evaluation of Annotations `_. -This is required for specific type-hints, such as a class using itself in its own annotations. -This will also speed up the initialization of code which uses type-hints. - -``def main() -> None:`` has no significance other than convention. -Because this function returns nothing it is annotated with ``-> None``. - -``if __name__ == "__main__":`` checks for the `Top-level code environment `_. -This prevents tools from accidentally launching the script when they just want to import it. -This is the only required boilerplate, everything else is optional. +You will replace body of the ``main`` function in the following section. Loading a tileset and opening a window ------------------------------------------------------------------------------- +============================================================================== From here it is time to setup a ``tcod`` program. Download `Alloy_curses_12x12.png `_ and place this file in your projects ``data/`` directory. -This tileset is from the `Dwarf Fortress tileset repository `_ and you may choose to use any other tileset from there as long is you keep track of the filename yourself. +This tileset is from the `Dwarf Fortress tileset repository `_. +These kinds of tilesets are always loaded with ``columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437``. Load the tileset with :any:`tcod.tileset.load_tilesheet` and then pass it to :any:`tcod.context.new`. These functions are part of modules which have not been imported yet, so new imports need to be added. @@ -74,22 +53,22 @@ If you run this script now then a window will open and then immediately close. If that happens without seeing a traceback in your terminal then the script is correct. Configuring an event loop ------------------------------------------------------------------------------- +============================================================================== The next step is to keep the window open until the user closes it. Since nothing is displayed yet a :any:`Console` should be created with ``"Hello World"`` printed to it. The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. -To actually display the console to the window the :any:`Context.present` method must be called with the console. -Be sure to check the additional a parameters of :any:`Context.present`, you can keep aspect or enforce integer scaling. +Begin the main game loop with a ``while True:`` statement. -Events are checked by iterating over all pending events. -If your game is strictly turn-based then you should use :any:`tcod.event.wait`. -If your game is real-time or has real-time animations then it should use :any:`tcod.event.get` instead. +To actually display the console to the window the :any:`Context.present` method must be called with the console as a parameter. +Do this first in the game loop before handing events. -Test if an event is for closing the window with ``isinstance(event, tcod.event.Quit)``. -If this is True then you should exit the function, either with ``return``, or with :any:`sys.exit`, or with ``raise SystemExit``. +Events are checked by iterating over all pending events with :any:`tcod.event.wait`. +Use the code ``for event in tcod.event.wait():`` to begin handing events. +Test if an event is for closing the window with ``if isinstance(event, tcod.event.Quit):``. +If this is True then you should exit the function with ``raise SystemExit``. .. code-block:: python :emphasize-lines: 2,3,11-18 @@ -118,13 +97,12 @@ If you run this then you get a window saying ``"Hello World"``. The window can be resized and the console will be stretched to fit the new resolution. An example game state ------------------------------------------------------------------------------- +============================================================================== What exists now is not very interactive. The next step is to change state based on user input. Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with ``pip install attrs``. -Alternatively you can use :any:`dataclasses`, but this tutorial uses ``attrs`` since it has a more modern implementation. Start by adding an ``attrs`` class called ``ExampleState``. This a normal class with the ``@attrs.define(eq=False)`` decorator added. @@ -135,8 +113,7 @@ The parameters for ``on_draw`` are ``self`` because this is an instance method a ``on_draw`` returns nothing, so be sure to add ``-> None``. :any:`Console.print` is the simplest way to draw the player because other options would require bounds-checking. - -If ``tcod.console.Console`` is too verbose then you can add ``from tcod.console import Console`` so that you can use just ``Console`` instead. +Call this method using the players current coordinates and the ``"@"`` character. .. code-block:: python @@ -190,13 +167,6 @@ Modify the drawing routine so that the console is cleared, then passed to ``Exam Now if you run the script you'll see ``@``. -This code is sensitive to typing. -If you wrote ``player_x=console.width / 2`` instead of ``player_x=console.width // 2`` (note the number of slashes) then ``player_x`` will be assigned as a float instead of an int. -If ``player_x`` is a float then :any:`Console.print` will raise a TypeError. -In this case the incorrect code is when ``ExampleState`` is created with an invalid type and not the print function call. -Running ``mypy`` on your code will show you this type error at the correct position. -Your IDE should also complain about a bad type if setup correctly. - The next step is to move the player on events. A new method will be added to the ``ExampleState`` for this called ``on_event``. ``on_event`` takes a ``self`` and a :any:`tcod.event.Event` parameter and returns nothing. From 3797027db0bc17410a7d91883c40fe5acffbabca Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 22 Jun 2023 09:27:38 -0700 Subject: [PATCH 0826/1101] Update doc roles, add inline code highlighting. --- docs/conf.py | 3 ++- docs/prolog.rst | 6 ++++++ docs/tutorial/part-00.rst | 8 +++++--- docs/tutorial/part-01.rst | 42 ++++++++++++++++++++------------------- 4 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 docs/prolog.rst diff --git a/docs/conf.py b/docs/conf.py index 0e5f24ab..2fece512 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -378,7 +378,8 @@ napoleon_use_param = True napoleon_use_rtype = True -rst_epilog = ".. include:: /epilog.rst" +rst_prolog = ".. include:: /prolog.rst" # Added to the beginning of every source file. +rst_epilog = ".. include:: /epilog.rst" # Added to the end of every source file. # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { diff --git a/docs/prolog.rst b/docs/prolog.rst new file mode 100644 index 00000000..cdcd4c22 --- /dev/null +++ b/docs/prolog.rst @@ -0,0 +1,6 @@ +.. Add inline code roles to all sources +.. role:: python(code) + :language: python + +.. role:: shell(code) + :language: shell diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst index 9cc4b7cd..97c229ab 100644 --- a/docs/tutorial/part-00.rst +++ b/docs/tutorial/part-00.rst @@ -1,3 +1,5 @@ +.. _part-0: + Part 0 - Setting up a project ############################################################################## @@ -18,7 +20,7 @@ First script ============================================================================== First start with a modern top-level script. -Create a script in the project root folder called ``main.py`` which checks ``if __name__ == "__main__":`` and calls a ``main`` function. +Create a script in the project root folder called ``main.py`` which checks :python:`if __name__ == "__main__":` and calls a ``main`` function. .. code-block:: python @@ -33,6 +35,6 @@ In VSCode on the left sidebar is a **Run and Debug** tab. On this tab select **create a launch.json** file. This will prompt about what kind of program to launch. Pick ``Python``, then ``Module``, then when asked for the module name type ``main``. -From now on the ``F5`` key will launch ``main.py`` in debug mode. +From now on the :kbd:`F5` key will launch ``main.py`` in debug mode. -Run the script now and ``"Hello World!"`` should be visible in the terminal output. +Run the script now and ``Hello World!`` should be visible in the terminal output. diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index 00c9eded..ca78ab9c 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -1,3 +1,5 @@ +.. _part-1: + Part 1 - Moving a player around the screen ############################################################################## @@ -23,7 +25,7 @@ Loading a tileset and opening a window From here it is time to setup a ``tcod`` program. Download `Alloy_curses_12x12.png `_ and place this file in your projects ``data/`` directory. This tileset is from the `Dwarf Fortress tileset repository `_. -These kinds of tilesets are always loaded with ``columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437``. +These kinds of tilesets are always loaded with :python:`columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437`. Load the tileset with :any:`tcod.tileset.load_tilesheet` and then pass it to :any:`tcod.context.new`. These functions are part of modules which have not been imported yet, so new imports need to be added. @@ -47,7 +49,7 @@ These functions are part of modules which have not been imported yet, so new imp ... If an import fails that means you do not have ``tcod`` installed on the Python environment you just used to run the script. -If you use an IDE then make sure the Python environment it is using is correct and then run ``pip install tcod`` from the shell terminal within that IDE. +If you use an IDE then make sure the Python environment it is using is correct and then run :shell:`pip install tcod` from the shell terminal within that IDE. If you run this script now then a window will open and then immediately close. If that happens without seeing a traceback in your terminal then the script is correct. @@ -57,18 +59,18 @@ Configuring an event loop The next step is to keep the window open until the user closes it. -Since nothing is displayed yet a :any:`Console` should be created with ``"Hello World"`` printed to it. +Since nothing is displayed yet a :any:`Console` should be created with :python:`"Hello World"` printed to it. The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. -Begin the main game loop with a ``while True:`` statement. +Begin the main game loop with a :python:`while True:` statement. To actually display the console to the window the :any:`Context.present` method must be called with the console as a parameter. Do this first in the game loop before handing events. Events are checked by iterating over all pending events with :any:`tcod.event.wait`. -Use the code ``for event in tcod.event.wait():`` to begin handing events. -Test if an event is for closing the window with ``if isinstance(event, tcod.event.Quit):``. -If this is True then you should exit the function with ``raise SystemExit``. +Use the code :python:`for event in tcod.event.wait():` to begin handing events. +Test if an event is for closing the window with :python:`if isinstance(event, tcod.event.Quit):`. +If this is True then you should exit the function with :python:`raise SystemExit`. .. code-block:: python :emphasize-lines: 2,3,11-18 @@ -93,7 +95,7 @@ If this is True then you should exit the function with ``raise SystemExit``. raise SystemExit() ... -If you run this then you get a window saying ``"Hello World"``. +If you run this then you get a window saying :python:`"Hello World"`. The window can be resized and the console will be stretched to fit the new resolution. An example game state @@ -102,18 +104,18 @@ An example game state What exists now is not very interactive. The next step is to change state based on user input. -Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with ``pip install attrs``. +Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with :shell:`pip install attrs`. Start by adding an ``attrs`` class called ``ExampleState``. -This a normal class with the ``@attrs.define(eq=False)`` decorator added. +This a normal class with the :python:`@attrs.define(eq=False)` decorator added. This class should hold coordinates for the player. It should also have a ``on_draw`` method which takes :any:`tcod.console.Console` as a parameter and marks the player position on it. -The parameters for ``on_draw`` are ``self`` because this is an instance method and ``console: tcod.console.Console``. -``on_draw`` returns nothing, so be sure to add ``-> None``. +The parameters for ``on_draw`` are ``self`` because this is an instance method and :python:`console: tcod.console.Console`. +``on_draw`` returns nothing, so be sure to add :python:`-> None`. :any:`Console.print` is the simplest way to draw the player because other options would require bounds-checking. -Call this method using the players current coordinates and the ``"@"`` character. +Call this method using the players current coordinates and the :python:`"@"` character. .. code-block:: python @@ -135,13 +137,13 @@ Call this method using the players current coordinates and the ``"@"`` character console.print(self.player_x, self.player_y, "@") ... -Now remove the ``console.print(0, 0, "Hello World")`` line from ``main``. +Now remove the :python:`console.print(0, 0, "Hello World")` line from ``main``. Before the context is made create a new ``ExampleState`` with player coordinates on the screen. -Each :any:`Console` has ``.width`` and ``.height`` attributes which you can divide by 2 to get a centered coordinate for the player. -Use Python's floor division operator ``//`` so that the resulting type is ``int``. +Each :any:`Console` has :python:`.width` and :python:`.height` attributes which you can divide by 2 to get a centered coordinate for the player. +Use Python's floor division operator :python:`//` so that the resulting type is :python:`int`. -Modify the drawing routine so that the console is cleared, then passed to ``ExampleState.on_draw``, then passed to :any:`Context.present`. +Modify the drawing routine so that the console is cleared, then passed to :python:`ExampleState.on_draw`, then passed to :any:`Context.present`. .. code-block:: python :emphasize-lines: 9,12-14 @@ -174,9 +176,9 @@ A new method will be added to the ``ExampleState`` for this called ``on_event``. Events are best handled using Python's `Structural Pattern Matching `_. Consider reading `Python's Structural Pattern Matching Tutorial `_. -Begin matching with ``match event:``. -The equivalent to ``if isinstance(event, tcod.event.Quit):`` is ``case tcod.event.Quit():``. -Keyboard keys can be checked with ``case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):``. +Begin matching with :python:`match event:`. +The equivalent to :python:`if isinstance(event, tcod.event.Quit):` is :python:`case tcod.event.Quit():`. +Keyboard keys can be checked with :python:`case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):`. Make a case for each arrow key: ``LEFT`` ``RIGHT`` ``UP`` ``DOWN`` and move the player in the direction of that key. See :any:`KeySym` for a list of all keys. From 1092576f2d92bf7b0c4085d12d37e507d15202b6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Jun 2023 15:29:07 -0700 Subject: [PATCH 0827/1101] Add in-progress notice to the tutorial articles. --- docs/tutorial/notice.rst | 6 ++++++ docs/tutorial/part-00.rst | 2 ++ docs/tutorial/part-01.rst | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 docs/tutorial/notice.rst diff --git a/docs/tutorial/notice.rst b/docs/tutorial/notice.rst new file mode 100644 index 00000000..47bd3d6b --- /dev/null +++ b/docs/tutorial/notice.rst @@ -0,0 +1,6 @@ +.. note:: + This tutorial is still a work-in-progress. + `The resources being used are tracked here `_. + Feel free to discuss this tutorial on the `Github Discussions`_ forum. + +.. _Github Discussions: https://github.com/libtcod/python-tcod/discussions diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst index 97c229ab..1783740a 100644 --- a/docs/tutorial/part-00.rst +++ b/docs/tutorial/part-00.rst @@ -3,6 +3,8 @@ Part 0 - Setting up a project ############################################################################## +.. include:: notice.rst + Starting tools ============================================================================== diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index ca78ab9c..fac8d4e6 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -3,6 +3,8 @@ Part 1 - Moving a player around the screen ############################################################################## +.. include:: notice.rst + Initial script ============================================================================== From 322e0db8eacc72cd661af724ebfd4309e5782700 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Jun 2023 16:16:55 -0700 Subject: [PATCH 0828/1101] Test footnotes. These could be useful for out of the way justifications of tutorial decisions. --- docs/tutorial/part-00.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst index 1783740a..cb97db3f 100644 --- a/docs/tutorial/part-00.rst +++ b/docs/tutorial/part-00.rst @@ -8,7 +8,7 @@ Part 0 - Setting up a project Starting tools ============================================================================== -The IDE used for this tutorial is `Visual Studio Code `_ (not to be mistaken for Visual Studio). +The IDE used for this tutorial is `Visual Studio Code `_ [#vscode]_ (not to be mistaken for Visual Studio). Git will be used for version control. `Follow the instructions here `_. @@ -40,3 +40,8 @@ Pick ``Python``, then ``Module``, then when asked for the module name type ``mai From now on the :kbd:`F5` key will launch ``main.py`` in debug mode. Run the script now and ``Hello World!`` should be visible in the terminal output. + +.. rubric:: Footnotes + +.. [#vscode] Alternatives like `PyCharm `_ were considered, + but VSCode works the best with Git projects since workspace settings are portable and can be committed without issues. From 6436a59bd9ecf0e5250c94a3fb4a4038dd1208f3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 24 Jun 2023 16:39:24 -0700 Subject: [PATCH 0829/1101] Tutorial: Print events and justify more decisions. --- docs/tutorial/part-01.rst | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index fac8d4e6..103ab99b 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -9,6 +9,7 @@ Initial script ============================================================================== First start with a modern top-level script. +You should have ``main.py`` script from :ref:`part-0`: .. code-block:: python @@ -25,7 +26,7 @@ Loading a tileset and opening a window ============================================================================== From here it is time to setup a ``tcod`` program. -Download `Alloy_curses_12x12.png `_ and place this file in your projects ``data/`` directory. +Download `Alloy_curses_12x12.png `_ [#tileset]_ and place this file in your projects ``data/`` directory. This tileset is from the `Dwarf Fortress tileset repository `_. These kinds of tilesets are always loaded with :python:`columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437`. @@ -62,7 +63,7 @@ Configuring an event loop The next step is to keep the window open until the user closes it. Since nothing is displayed yet a :any:`Console` should be created with :python:`"Hello World"` printed to it. -The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. +The size of the console can be used as a reference to create the context by adding the console to :any:`tcod.context.new`. [#init_context]_ Begin the main game loop with a :python:`while True:` statement. @@ -71,11 +72,13 @@ Do this first in the game loop before handing events. Events are checked by iterating over all pending events with :any:`tcod.event.wait`. Use the code :python:`for event in tcod.event.wait():` to begin handing events. -Test if an event is for closing the window with :python:`if isinstance(event, tcod.event.Quit):`. -If this is True then you should exit the function with :python:`raise SystemExit`. + +In the event loop start with the line :python:`print(event)` so that all events can be viewed from the program output. +Then test if an event is for closing the window with :python:`if isinstance(event, tcod.event.Quit):`. +If this is True then you should exit the function with :python:`raise SystemExit()`. [#why_raise]_ .. code-block:: python - :emphasize-lines: 2,3,11-18 + :emphasize-lines: 2,3,11-19 ... import tcod.console @@ -93,12 +96,14 @@ If this is True then you should exit the function with :python:`raise SystemExit while True: # Main loop context.present(console) # Render the console to the window and show it for event in tcod.event.wait(): # Event loop, blocks until pending events exist + print(event) if isinstance(event, tcod.event.Quit): raise SystemExit() ... If you run this then you get a window saying :python:`"Hello World"`. The window can be resized and the console will be stretched to fit the new resolution. +When you do anything such as press a key or interact with the window the event for that action will be printed to the program output. An example game state ============================================================================== @@ -165,6 +170,7 @@ Modify the drawing routine so that the console is cleared, then passed to :pytho state.on_draw(console) # Draw the current state context.present(console) # Display the console on the window for event in tcod.event.wait(): + print(event) if isinstance(event, tcod.event.Quit): raise SystemExit() ... @@ -182,6 +188,7 @@ Begin matching with :python:`match event:`. The equivalent to :python:`if isinstance(event, tcod.event.Quit):` is :python:`case tcod.event.Quit():`. Keyboard keys can be checked with :python:`case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT):`. Make a case for each arrow key: ``LEFT`` ``RIGHT`` ``UP`` ``DOWN`` and move the player in the direction of that key. +Since events are printed you can check the :any:`KeySym` of a key by pressing that key and looking at the printed output. See :any:`KeySym` for a list of all keys. .. code-block:: python @@ -209,7 +216,7 @@ See :any:`KeySym` for a list of all keys. Now replace the event handling code in ``main`` to defer to the states ``on_event`` method. .. code-block:: python - :emphasize-lines: 11 + :emphasize-lines: 12 ... def main() -> None: @@ -221,9 +228,21 @@ Now replace the event handling code in ``main`` to defer to the states ``on_even state.on_draw(console) context.present(console) for event in tcod.event.wait(): + print(event) state.on_event(event) # Pass events to the state ... Now when you run this script you have a player character you can move around with the arrow keys before closing the window. You can review the part-1 source code `here `_. + +.. rubric:: Footnotes + +.. [#tileset] The choice of tileset came down to what looked nice while also being square. + Other options such as using a BDF font were considered, but in the end this tutorial won't go too much into Unicode. + +.. [#init_context] This tutorial follows the setup for a fixed-size console. + The alternatives shown in :ref:`getting-started` are outside the scope of this tutorial. + +.. [#why_raise] You could use :python:`return` here to exit the ``main`` function and end the program, but :python:`raise SystemExit()` is used because it will close the program from anywhere. + :python:`raise SystemExit()` is also more useful to teach than :any:`sys.exit`. From f62949f4bf6af25b341aaf0a6c9cc99d5b973bfc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 Jun 2023 09:29:18 -0700 Subject: [PATCH 0830/1101] Update tutorial notice. --- docs/tutorial/notice.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/notice.rst b/docs/tutorial/notice.rst index 47bd3d6b..707baf09 100644 --- a/docs/tutorial/notice.rst +++ b/docs/tutorial/notice.rst @@ -1,6 +1,6 @@ .. note:: This tutorial is still a work-in-progress. `The resources being used are tracked here `_. - Feel free to discuss this tutorial on the `Github Discussions`_ forum. + Feel free to discuss this tutorial or share your progress on the `Github Discussions`_ forum. .. _Github Discussions: https://github.com/libtcod/python-tcod/discussions From 75d59e1e73b9863e51e93f2a95e72a137aae918b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 Jun 2023 09:30:25 -0700 Subject: [PATCH 0831/1101] Enable all additional formats for Sphinx. --- .readthedocs.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 390d6115..81bb11ae 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -20,8 +20,7 @@ sphinx: fail_on_warning: true # If using Sphinx, optionally build your docs in additional formats such as PDF -# formats: -# - pdf +formats: all # Optionally declare the Python requirements required to build your docs python: From 1a6d9bea0167dd1769764dfbfd0668cfc7c659c8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 Jun 2023 23:27:40 -0700 Subject: [PATCH 0832/1101] Add `__slots__` to EventDispatch. This class is meant to be inherited, the subclass should be allowed to use `__slots__`. --- CHANGELOG.md | 2 ++ tcod/event.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fac3613..b812ee71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- Added an empty `__slots__` to `EventDispatch`. ## [16.1.0] - 2023-06-23 ### Added diff --git a/tcod/event.py b/tcod/event.py index d808436b..16eaf498 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1365,6 +1365,8 @@ def cmd_quit(self) -> None: state.dispatch(event) ''' + __slots__ = () + def dispatch(self, event: Any) -> T | None: """Send an event to an `ev_*` method. From a7923ed728993ac409241b85a8e5105f6ae75b26 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 28 Jun 2023 08:59:48 -0700 Subject: [PATCH 0833/1101] Fix comments on Ruff noqa directives. --- build_sdl.py | 3 ++- tcod/libtcodpy.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index ef3dd4be..cc66626b 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -16,7 +16,8 @@ import pcpp # type: ignore import requests -# ruff: noqa: S603, S607 # This script calls a lot of programs. +# This script calls a lot of programs. +# ruff: noqa: S603, S607 BIT_SIZE, LINKAGE = platform.architecture() diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 88086c1c..1e026ce3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -52,7 +52,8 @@ NOISE_DEFAULT, ) -# ruff: noqa: ANN401 PLR0913 # Functions are too deprecated to make changes. +# Functions are too deprecated to make changes. +# ruff: noqa: ANN401 PLR0913 Bsp = tcod.bsp.BSP From d20e1388f1464c38b372902773f4644795e044d6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 28 Jun 2023 11:57:10 -0700 Subject: [PATCH 0834/1101] Drop end-of-life Python 3.7. --- .github/workflows/python-package.yml | 10 +++++----- CHANGELOG.md | 3 +++ README.rst | 2 +- pyproject.toml | 9 ++++----- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 38ad5cb1..1b1ecec6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -105,14 +105,14 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - python-version: ["3.7", "3.8", "3.9", "pypy-3.7"] + python-version: ["3.8", "3.9", "pypy-3.8"] architecture: ["x64"] include: - os: "windows-latest" - python-version: "3.7" + python-version: "3.8" architecture: "x86" - os: "windows-latest" - python-version: "pypy-3.7" + python-version: "pypy-3.8" architecture: "x86" fail-fast: false @@ -224,7 +224,7 @@ jobs: strategy: matrix: arch: ["x86_64", "aarch64"] - build: ["cp37-manylinux*", "pp37-manylinux*"] + build: ["cp38-manylinux*", "pp38-manylinux*"] steps: - uses: actions/checkout@v3 with: @@ -273,7 +273,7 @@ jobs: strategy: fail-fast: true matrix: - python: ["cp38-*_universal2", "cp38-*_x86_64", "cp38-*_arm64", "pp37-*"] + python: ["cp38-*_universal2", "cp38-*_x86_64", "cp38-*_arm64", "pp38-*"] steps: - uses: actions/checkout@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index b812ee71..2d66d040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Changed - Added an empty `__slots__` to `EventDispatch`. +### Removed +- Dropped support for Python 3.7. + ## [16.1.0] - 2023-06-23 ### Added - Added the enums `tcod.event.MouseButton` and `tcod.event.MouseButtonMask`. diff --git a/README.rst b/README.rst index f84370ee..82aad962 100755 --- a/README.rst +++ b/README.rst @@ -46,7 +46,7 @@ For the most part it's just:: ============== Requirements ============== -* Python 3.7+ +* Python 3.8+ * Windows, Linux, or MacOS X 10.9+. * On Linux, requires libsdl2 (2.0.10+). diff --git a/pyproject.toml b/pyproject.toml index 2fbc6ebb..325c0baa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dynamic = ["version"] description = "The official Python port of libtcod." authors = [{ name = "Kyle Benesch", email = "4b796c65+tcod@gmail.com" }] readme = "README.rst" -requires-python = ">=3.7" +requires-python = ">=3.8" license = { text = "Simplified BSD License" } dependencies = [ "cffi>=1.15", @@ -45,7 +45,6 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -70,18 +69,18 @@ Tracker = "https://github.com/libtcod/python-tcod/issues" Forum = "https://github.com/libtcod/python-tcod/discussions" [tool.distutils.bdist_wheel] -py-limited-api = "cp37" +py-limited-api = "cp38" [tool.setuptools_scm] write_to = "tcod/version.py" [tool.black] line-length = 120 -target-version = ["py37"] +target-version = ["py38"] [tool.isort] profile = "black" -py_version = "37" +py_version = "38" skip_gitignore = true line_length = 120 From e39c25d7d5da9ec262ede9c28970469d98bbea01 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 29 Jun 2023 13:35:06 -0700 Subject: [PATCH 0835/1101] Reorganize tutorial TOC into a sub-tree. --- docs/index.rst | 5 ++--- docs/tutorial/index.rst | 10 ++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 docs/tutorial/index.rst diff --git a/docs/index.rst b/docs/index.rst index 50a87f4a..afc353a7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,10 +22,9 @@ Contents: .. toctree:: :maxdepth: 2 - :caption: Tutorial - :glob: + :caption: How To - tutorial/part-* + tutorial/index .. toctree:: :maxdepth: 2 diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst new file mode 100644 index 00000000..2277ab62 --- /dev/null +++ b/docs/tutorial/index.rst @@ -0,0 +1,10 @@ +Tutorial +############################################################################## + +.. include:: notice.rst + +.. toctree:: + :maxdepth: 1 + :glob: + + part-* From abdb1698f2ad189eb46fe340ce1566d37d2c41c7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 10 Jul 2023 00:01:00 -0700 Subject: [PATCH 0836/1101] Update SDL to 2.28.1 The latest version fixes SDL's rendering error. Closes #131 --- CHANGELOG.md | 5 +++++ build_sdl.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d66d040..67303d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ### Changed - Added an empty `__slots__` to `EventDispatch`. +- Bundle `SDL 2.28.1` on Windows and MacOS. + +### Fixed +- Fixed "SDL failed to get a vertex buffer for this Direct3D 9 rendering batch!" + https://github.com/libtcod/python-tcod/issues/131 ### Removed - Dropped support for Python 3.7. diff --git a/build_sdl.py b/build_sdl.py index cc66626b..b47b426c 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -26,7 +26,7 @@ # The SDL2 version to parse and export symbols from. SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") # The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.26.0") +SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.28.1") # Used to remove excessive newlines in debug outputs. From c7936eb31e95874ba7153d92097539173812b458 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 10 Jul 2023 00:10:32 -0700 Subject: [PATCH 0837/1101] Fix more samples deprecations --- examples/samples_tcod.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 43b53d92..58d35a15 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -6,6 +6,7 @@ from __future__ import annotations import copy +import importlib.metadata import math import random import sys @@ -18,6 +19,7 @@ from numpy.typing import NDArray import tcod.cffi +import tcod.context import tcod.event import tcod.noise import tcod.render @@ -160,8 +162,8 @@ def print_banner(self) -> None: string="The Doryen library uses 24 bits colors, for both " "background and foreground.", fg=WHITE, bg=GREY, - bg_blend=tcod.BKGND_MULTIPLY, - alignment=tcod.CENTER, + bg_blend=libtcodpy.BKGND_MULTIPLY, + alignment=libtcodpy.CENTER, ) @@ -297,7 +299,7 @@ def on_draw(self) -> None: # in python the easiest way is to use the line iterator for x, y in tcod.los.bresenham((xo, yo), (xd, yd)).tolist(): if 0 <= x < sample_console.width and 0 <= y < sample_console.height: - tcod.console_set_char_background(sample_console, x, y, LIGHT_BLUE, self.bk_flag) + libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_BLUE, self.bk_flag) sample_console.print( 2, 2, @@ -581,8 +583,8 @@ def on_draw(self) -> None: self.draw_ui() sample_console.print(self.player_x, self.player_y, "@") # Draw windows. - sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL - sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = BLACK + sample_console.rgb["ch"][SAMPLE_MAP == "="] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL + sample_console.rgb["fg"][SAMPLE_MAP == "="] = BLACK # Get a 2D boolean array of visible cells. fov = tcod.map.compute_fov( @@ -625,7 +627,7 @@ def on_draw(self) -> None: dark_bg: NDArray[np.float16] = self.dark_map_bg.astype(np.float16) # Linear interpolation between colors. - sample_console.tiles_rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] + sample_console.rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] else: sample_console.bg[...] = np.where(fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg) @@ -1411,7 +1413,7 @@ def init_context(renderer: int) -> None: context = tcod.context.new( columns=root_console.width, rows=root_console.height, - title=f"python-tcod samples (python-tcod {tcod.__version__}, libtcod {libtcod_version})", + title=f"""python-tcod samples (python-tcod {importlib.metadata.version("tcod")}, libtcod {libtcod_version})""", vsync=False, # VSync turned off since this is for benchmarking. tileset=tileset, ) From e597ceb8b5a63a62f2c99fdeffaab16adad2be0b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 10 Jul 2023 01:06:50 -0700 Subject: [PATCH 0838/1101] Prepare 16.1.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67303d41..ad769aaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.1.1] - 2023-07-10 ### Changed - Added an empty `__slots__` to `EventDispatch`. - Bundle `SDL 2.28.1` on Windows and MacOS. From 9c29b1a60fbe019f409f9abdb47fc5f1dfde0da4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 11 Jul 2023 18:31:23 -0700 Subject: [PATCH 0839/1101] Update tutorial part 1 --- docs/tutorial/part-01.rst | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index 103ab99b..028fc40a 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -5,6 +5,10 @@ Part 1 - Moving a player around the screen .. include:: notice.rst +In part 1 you will become familiar with the initialization, rendering, and event system of tcod. +This will be done as a series of small implementations. +It is recommend to save your progress after each section is finished and tested. + Initial script ============================================================================== @@ -29,10 +33,16 @@ From here it is time to setup a ``tcod`` program. Download `Alloy_curses_12x12.png `_ [#tileset]_ and place this file in your projects ``data/`` directory. This tileset is from the `Dwarf Fortress tileset repository `_. These kinds of tilesets are always loaded with :python:`columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437`. +Use the string :python:`"data/Alloy_curses_12x12.png"` to refer to the path of the tileset. [#why_not_pathlib]_ + +Load the tileset with :any:`tcod.tileset.load_tilesheet`. +Then pass the tileset to :any:`tcod.context.new`, you only need to provide the ``tileset`` parameter. -Load the tileset with :any:`tcod.tileset.load_tilesheet` and then pass it to :any:`tcod.context.new`. -These functions are part of modules which have not been imported yet, so new imports need to be added. -:any:`tcod.context.new` returns a :any:`Context` which is used with the ``with`` statement. +:any:`tcod.context.new` returns a :any:`Context` which will be used with Python's :python:`with` statement. +We want to keep the name of the context, so use the syntax: :python:`with tcod.context.new(tileset=tileset) as context:`. +The new block can't be empty, so add :python:`pass` to the with statement body. + +These functions are part of modules which have not been imported yet, so new imports for ``tcod.context`` and ``tcod.tileset`` must be added to the top of the script. .. code-block:: python :emphasize-lines: 2,3,8-12 @@ -54,7 +64,7 @@ These functions are part of modules which have not been imported yet, so new imp If an import fails that means you do not have ``tcod`` installed on the Python environment you just used to run the script. If you use an IDE then make sure the Python environment it is using is correct and then run :shell:`pip install tcod` from the shell terminal within that IDE. -If you run this script now then a window will open and then immediately close. +There is no game loop, so if you run this script now then a window will open and then immediately close. If that happens without seeing a traceback in your terminal then the script is correct. Configuring an event loop @@ -241,6 +251,11 @@ You can review the part-1 source code `here Date: Wed, 20 Sep 2023 20:01:47 -0700 Subject: [PATCH 0840/1101] Fix gauss function typos --- CHANGELOG.md | 2 ++ tcod/random.py | 19 +++++++++++++++++-- tests/test_deprecated.py | 9 +++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad769aaf..0057b82f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Changed +- Renamed `gauss` methods to fix typos. ## [16.1.1] - 2023-07-10 ### Changed diff --git a/tcod/random.py b/tcod/random.py index b574cab8..c9e54286 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -14,6 +14,7 @@ from typing import Any, Hashable import tcod.constants +from tcod._internal import deprecate from tcod.cffi import ffi, lib MERSENNE_TWISTER = tcod.constants.RNG_MT @@ -110,7 +111,7 @@ def uniform(self, low: float, high: float) -> float: """ return float(lib.TCOD_random_get_double(self.random_c, low, high)) - def guass(self, mu: float, sigma: float) -> float: + def gauss(self, mu: float, sigma: float) -> float: """Return a random number using Gaussian distribution. Args: @@ -119,10 +120,17 @@ def guass(self, mu: float, sigma: float) -> float: Returns: float: A random float. + + .. versionchanged:: Unreleased + Renamed from `guass` to `gauss`. """ return float(lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma)) - def inverse_guass(self, mu: float, sigma: float) -> float: + @deprecate("This is a typo, rename this to 'gauss'", category=FutureWarning) + def guass(self, mu: float, sigma: float) -> float: # noqa: D102 + return self.gauss(mu, sigma) + + def inverse_gauss(self, mu: float, sigma: float) -> float: """Return a random Gaussian number using the Box-Muller transform. Args: @@ -131,9 +139,16 @@ def inverse_guass(self, mu: float, sigma: float) -> float: Returns: float: A random float. + + .. versionchanged:: Unreleased + Renamed from `inverse_guass` to `inverse_gauss`. """ return float(lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma)) + @deprecate("This is a typo, rename this to 'inverse_gauss'", category=FutureWarning) + def inverse_guass(self, mu: float, sigma: float) -> float: # noqa: D102 + return self.inverse_gauss(mu, sigma) + def __getstate__(self) -> Any: """Pack the self.random_c attribute into a portable state.""" state = self.__dict__.copy() diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 502d9ea9..356de7d3 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -8,6 +8,7 @@ import tcod.constants import tcod.event import tcod.libtcodpy +import tcod.random with pytest.warns(): import libtcodpy @@ -53,3 +54,11 @@ def test_line_where() -> None: with pytest.warns(): where = tcod.libtcodpy.line_where(1, 0, 3, 4) np.testing.assert_array_equal(where, [[1, 1, 2, 2, 3], [0, 1, 2, 3, 4]]) + + +def test_gauss_typo() -> None: + rng = tcod.random.Random() + with pytest.warns(FutureWarning, match=r"gauss"): + rng.guass(1, 1) + with pytest.warns(FutureWarning, match=r"inverse_gauss"): + rng.inverse_guass(1, 1) From 422ccee4ba9b68ff33b5573c21c36923e5dd563b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Sep 2023 20:06:06 -0700 Subject: [PATCH 0841/1101] Update docs --- tcod/console.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index 36cc0aba..a014b8af 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1,4 +1,6 @@ -"""Libtcod consoles are a strictly tile-based representation of text and color. +"""Libtcod tile-based Consoles and printing functions. + +Libtcod consoles are a strictly tile-based representation of colored glyphs/tiles. To render a console you need a tileset and a window to render to. See :ref:`getting-started` for info on how to set those up. """ From bf7f1c105ab5501738a85dbbf9394cd4873ded0e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Sep 2023 20:06:37 -0700 Subject: [PATCH 0842/1101] Remove outdated Mypy ignore --- tcod/sdl/_internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 00dc868c..28b47460 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -59,7 +59,7 @@ def __exit__( return False if _sys.version_info < (3, 8): return False - _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) # type: ignore[arg-type] + _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) return True From deeeeca45cfba004283faf9c8b789c32f819cbe1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Sep 2023 20:10:47 -0700 Subject: [PATCH 0843/1101] Replace constant class var with an immutable type --- tcod/console.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index a014b8af..c8786703 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -443,24 +443,28 @@ def put_char( """ lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend) - __ALIGNMENT_LOOKUP = {0: "tcod.LEFT", 1: "tcod.RIGHT", 2: "tcod.CENTER"} - - __BG_BLEND_LOOKUP = { - 0: "tcod.BKGND_NONE", - 1: "tcod.BKGND_SET", - 2: "tcod.BKGND_MULTIPLY", - 3: "tcod.BKGND_LIGHTEN", - 4: "tcod.BKGND_DARKEN", - 5: "tcod.BKGND_SCREEN", - 6: "tcod.BKGND_COLOR_DODGE", - 7: "tcod.BKGND_COLOR_BURN", - 8: "tcod.BKGND_ADD", - 9: "tcod.BKGND_ADDA", - 10: "tcod.BKGND_BURN", - 11: "tcod.BKGND_OVERLAY", - 12: "tcod.BKGND_ALPH", - 13: "tcod.BKGND_DEFAULT", - } + __ALIGNMENT_LOOKUP = ( + "tcod.LEFT", + "tcod.RIGHT", + "tcod.CENTER", + ) + + __BG_BLEND_LOOKUP = ( + "tcod.BKGND_NONE", + "tcod.BKGND_SET", + "tcod.BKGND_MULTIPLY", + "tcod.BKGND_LIGHTEN", + "tcod.BKGND_DARKEN", + "tcod.BKGND_SCREEN", + "tcod.BKGND_COLOR_DODGE", + "tcod.BKGND_COLOR_BURN", + "tcod.BKGND_ADD", + "tcod.BKGND_ADDA", + "tcod.BKGND_BURN", + "tcod.BKGND_OVERLAY", + "tcod.BKGND_ALPH", + "tcod.BKGND_DEFAULT", + ) def __deprecate_defaults( # noqa: C901, PLR0912 self, From 7b75e019b19417badc9b21747c534fd2147ded94 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Sep 2023 20:18:27 -0700 Subject: [PATCH 0844/1101] Upgrade cibuildwheel --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1b1ecec6..784fef9a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -242,7 +242,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install cibuildwheel==2.3.1 + pip install cibuildwheel==2.16.0 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse From 688fc663ca08138afb8efed192b620bbf3f3bee1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 20 Sep 2023 20:46:39 -0700 Subject: [PATCH 0845/1101] Prepare 16.2.0 release. --- CHANGELOG.md | 2 ++ tcod/random.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0057b82f..284d5da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.2.0] - 2023-09-20 ### Changed - Renamed `gauss` methods to fix typos. diff --git a/tcod/random.py b/tcod/random.py index c9e54286..b6042d8a 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -121,7 +121,7 @@ def gauss(self, mu: float, sigma: float) -> float: Returns: float: A random float. - .. versionchanged:: Unreleased + .. versionchanged:: 16.2 Renamed from `guass` to `gauss`. """ return float(lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma)) @@ -140,7 +140,7 @@ def inverse_gauss(self, mu: float, sigma: float) -> float: Returns: float: A random float. - .. versionchanged:: Unreleased + .. versionchanged:: 16.2 Renamed from `inverse_guass` to `inverse_gauss`. """ return float(lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma)) From 2eb854dd7107aabbd5d5252e664871c660d7ca7d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 24 Sep 2023 22:39:13 -0700 Subject: [PATCH 0846/1101] Use the C locale when passing file paths to libtcod Fixes encoding errors which prevented existing files from loading correctly. --- CHANGELOG.md | 2 ++ tcod/_internal.py | 13 +++++++++++++ tcod/console.py | 8 ++++---- tcod/image.py | 8 ++++---- tcod/libtcodpy.py | 31 ++++++++++++++++--------------- tcod/tileset.py | 10 +++++----- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 284d5da0..e5c77fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- Fixed errors loading files where their paths are non-ASCII and the C locale is not UTF-8. ## [16.2.0] - 2023-09-20 ### Changed diff --git a/tcod/_internal.py b/tcod/_internal.py index cd92a45c..56ac7c50 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -2,7 +2,10 @@ from __future__ import annotations import functools +import locale +import sys import warnings +from pathlib import Path from types import TracebackType from typing import Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar, cast @@ -126,6 +129,16 @@ def _fmt(string: str, stacklevel: int = 2) -> bytes: return string.encode("utf-8").replace(b"%", b"%%") +def _path_encode(path: Path) -> bytes: + """Return a bytes file path for the current C locale.""" + try: + return str(path).encode(locale.getlocale()[1] or "utf-8") + except UnicodeEncodeError as exc: + if sys.version_info >= (3, 11): + exc.add_note("""Consider calling 'locale.setlocale(locale.LC_CTYPES, ".UTF8")' to support Unicode paths.""") + raise + + class _PropagateException: """Context manager designed to propagate exceptions outside of a cffi callback context. diff --git a/tcod/console.py b/tcod/console.py index c8786703..af0b3324 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -17,7 +17,7 @@ import tcod._internal import tcod.constants -from tcod._internal import _check, deprecate +from tcod._internal import _check, _path_encode, deprecate from tcod.cffi import ffi, lib @@ -1301,9 +1301,9 @@ def load_xp(path: str | PathLike[str], order: Literal["C", "F"] = "C") -> tuple[ console.rgba[is_transparent] = (ord(" "), (0,), (0,)) """ path = Path(path).resolve(strict=True) - layers = _check(tcod.lib.TCOD_load_xp(bytes(path), 0, ffi.NULL)) + layers = _check(tcod.lib.TCOD_load_xp(_path_encode(path), 0, ffi.NULL)) consoles = ffi.new("TCOD_Console*[]", layers) - _check(tcod.lib.TCOD_load_xp(bytes(path), layers, consoles)) + _check(tcod.lib.TCOD_load_xp(_path_encode(path), layers, consoles)) return tuple(Console._from_cdata(console_p, order=order) for console_p in consoles) @@ -1364,7 +1364,7 @@ def save_xp( tcod.lib.TCOD_save_xp( len(consoles_c), consoles_c, - bytes(path), + _path_encode(path), compress_level, ) ) diff --git a/tcod/image.py b/tcod/image.py index b404361a..826ea33e 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -18,7 +18,7 @@ from numpy.typing import ArrayLike, NDArray import tcod.console -from tcod._internal import _console, deprecate +from tcod._internal import _console, _path_encode, deprecate from tcod.cffi import ffi, lib @@ -72,7 +72,7 @@ def from_file(cls, path: str | PathLike[str]) -> Image: .. versionadded:: 16.0 """ path = Path(path).resolve(strict=True) - return cls._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(path)), lib.TCOD_image_delete)) + return cls._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(path)), lib.TCOD_image_delete)) def clear(self, color: tuple[int, int, int]) -> None: """Fill this entire Image with color. @@ -306,7 +306,7 @@ def save_as(self, filename: str | PathLike[str]) -> None: .. versionchanged:: 16.0 Added PathLike support. """ - lib.TCOD_image_save(self.image_c, bytes(Path(filename))) + lib.TCOD_image_save(self.image_c, _path_encode(Path(filename))) @property def __array_interface__(self) -> dict[str, Any]: @@ -364,7 +364,7 @@ def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: .. versionadded:: 11.4 """ - image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(bytes(Path(filename))), lib.TCOD_image_delete)) + image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(Path(filename))), lib.TCOD_image_delete)) array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 1e026ce3..a9162eb3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -31,6 +31,7 @@ _console, _fmt, _int, + _path_encode, _PropagateException, _unicode, _unpack_char_p, @@ -991,7 +992,7 @@ def console_set_custom_font( Added PathLike support. `fontFile` no longer takes bytes. """ fontFile = Path(fontFile).resolve(strict=True) - _check(lib.TCOD_console_set_custom_font(bytes(fontFile), flags, nb_char_horiz, nb_char_vertic)) + _check(lib.TCOD_console_set_custom_font(_path_encode(fontFile), flags, nb_char_horiz, nb_char_vertic)) @deprecate("Check `con.width` instead.") @@ -1806,7 +1807,7 @@ def console_from_file(filename: str | PathLike[str]) -> tcod.console.Console: Added PathLike support. """ filename = Path(filename).resolve(strict=True) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(bytes(filename)))) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_file(_path_encode(filename)))) @deprecate("Call the `Console.blit` method instead.") @@ -1985,7 +1986,7 @@ def console_load_asc(con: tcod.console.Console, filename: str | PathLike[str]) - Added PathLike support. """ filename = Path(filename).resolve(strict=True) - return bool(lib.TCOD_console_load_asc(_console(con), bytes(filename))) + return bool(lib.TCOD_console_load_asc(_console(con), _path_encode(filename))) @deprecate("This format is not actively supported") @@ -1998,7 +1999,7 @@ def console_save_asc(con: tcod.console.Console, filename: str | PathLike[str]) - .. versionchanged:: 16.0 Added PathLike support. """ - return bool(lib.TCOD_console_save_asc(_console(con), bytes(Path(filename)))) + return bool(lib.TCOD_console_save_asc(_console(con), _path_encode(Path(filename)))) @deprecate("This format is not actively supported") @@ -2012,7 +2013,7 @@ def console_load_apf(con: tcod.console.Console, filename: str | PathLike[str]) - Added PathLike support. """ filename = Path(filename).resolve(strict=True) - return bool(lib.TCOD_console_load_apf(_console(con), bytes(filename))) + return bool(lib.TCOD_console_load_apf(_console(con), _path_encode(filename))) @deprecate("This format is not actively supported") @@ -2025,7 +2026,7 @@ def console_save_apf(con: tcod.console.Console, filename: str | PathLike[str]) - .. versionchanged:: 16.0 Added PathLike support. """ - return bool(lib.TCOD_console_save_apf(_console(con), bytes(Path(filename)))) + return bool(lib.TCOD_console_save_apf(_console(con), _path_encode(Path(filename)))) @deprecate("Use tcod.console.load_xp to load this file.") @@ -2040,7 +2041,7 @@ def console_load_xp(con: tcod.console.Console, filename: str | PathLike[str]) -> Added PathLike support. """ filename = Path(filename).resolve(strict=True) - return bool(lib.TCOD_console_load_xp(_console(con), bytes(filename))) + return bool(lib.TCOD_console_load_xp(_console(con), _path_encode(filename))) @deprecate("Use tcod.console.save_xp to save this console.") @@ -2050,7 +2051,7 @@ def console_save_xp(con: tcod.console.Console, filename: str | PathLike[str], co .. versionchanged:: 16.0 Added PathLike support. """ - return bool(lib.TCOD_console_save_xp(_console(con), bytes(Path(filename)), compress_level)) + return bool(lib.TCOD_console_save_xp(_console(con), _path_encode(Path(filename)), compress_level)) @deprecate("Use tcod.console.load_xp to load this file.") @@ -2061,7 +2062,7 @@ def console_from_xp(filename: str | PathLike[str]) -> tcod.console.Console: Added PathLike support. """ filename = Path(filename).resolve(strict=True) - return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(bytes(filename)))) + return tcod.console.Console._from_cdata(_check_p(lib.TCOD_console_from_xp(_path_encode(filename)))) @deprecate("Use tcod.console.load_xp to load this file.") @@ -2074,7 +2075,7 @@ def console_list_load_xp( Added PathLike support. """ filename = Path(filename).resolve(strict=True) - tcod_list = lib.TCOD_console_list_from_xp(bytes(filename)) + tcod_list = lib.TCOD_console_list_from_xp(_path_encode(filename)) if tcod_list == ffi.NULL: return None try: @@ -2102,7 +2103,7 @@ def console_list_save_xp( try: for console in console_list: lib.TCOD_list_push(tcod_list, _console(console)) - return bool(lib.TCOD_console_list_save_xp(tcod_list, bytes(Path(filename)), compress_level)) + return bool(lib.TCOD_console_list_save_xp(tcod_list, _path_encode(Path(filename)), compress_level)) finally: lib.TCOD_list_delete(tcod_list) @@ -3436,7 +3437,7 @@ def mouse_get_status() -> Mouse: @pending_deprecate() def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | None = None) -> None: - lib.TCOD_namegen_parse(bytes(Path(filename)), random or ffi.NULL) + lib.TCOD_namegen_parse(_path_encode(Path(filename)), random or ffi.NULL) @pending_deprecate() @@ -3639,7 +3640,7 @@ def _pycall_parser_error(msg: Any) -> None: def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) -> None: global _parser_listener if not listener: - lib.TCOD_parser_run(parser, bytes(Path(filename)), ffi.NULL) + lib.TCOD_parser_run(parser, _path_encode(Path(filename)), ffi.NULL) return propagate_manager = _PropagateException() @@ -3658,7 +3659,7 @@ def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, bytes(Path(filename)), c_listener) + lib.TCOD_parser_run(parser, _path_encode(Path(filename)), c_listener) @deprecate("libtcod objects are deleted automatically.") @@ -4079,7 +4080,7 @@ def sys_save_screenshot(name: str | PathLike[str] | None = None) -> None: .. versionchanged:: 16.0 Added PathLike support. """ - lib.TCOD_sys_save_screenshot(bytes(Path(name)) if name is not None else ffi.NULL) + lib.TCOD_sys_save_screenshot(_path_encode(Path(name)) if name is not None else ffi.NULL) # custom fullscreen resolution diff --git a/tcod/tileset.py b/tcod/tileset.py index b53b0149..1ebf2122 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -21,7 +21,7 @@ from numpy.typing import ArrayLike, NDArray import tcod.console -from tcod._internal import _check, _console, _raise_tcod_error, deprecate +from tcod._internal import _check, _console, _path_encode, _raise_tcod_error, deprecate from tcod.cffi import ffi, lib @@ -268,7 +268,7 @@ def load_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: This function is provisional. The API may change. """ path = Path(path).resolve(strict=True) - cdata = lib.TCOD_load_truetype_font_(bytes(path), tile_width, tile_height) + cdata = lib.TCOD_load_truetype_font_(_path_encode(path), tile_width, tile_height) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error())) return Tileset._claim(cdata) @@ -296,7 +296,7 @@ def set_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: i Use :any:`load_truetype_font` instead. """ path = Path(path).resolve(strict=True) - if lib.TCOD_tileset_load_truetype_(bytes(path), tile_width, tile_height): + if lib.TCOD_tileset_load_truetype_(_path_encode(path), tile_width, tile_height): raise RuntimeError(ffi.string(lib.TCOD_get_error())) @@ -314,7 +314,7 @@ def load_bdf(path: str | PathLike[str]) -> Tileset: .. versionadded:: 11.10 """ path = Path(path).resolve(strict=True) - cdata = lib.TCOD_load_bdf(bytes(path)) + cdata = lib.TCOD_load_bdf(_path_encode(path)) if not cdata: raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode()) return Tileset._claim(cdata) @@ -343,7 +343,7 @@ def load_tilesheet(path: str | PathLike[str], columns: int, rows: int, charmap: mapping = [] if charmap is not None: mapping = list(itertools.islice(charmap, columns * rows)) - cdata = lib.TCOD_tileset_load(bytes(path), columns, rows, len(mapping), mapping) + cdata = lib.TCOD_tileset_load(_path_encode(path), columns, rows, len(mapping), mapping) if not cdata: _raise_tcod_error() return Tileset._claim(cdata) From b032d80bcfe1557ea5f8a9ffdf9e65320c4981d4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 24 Sep 2023 23:07:44 -0700 Subject: [PATCH 0847/1101] Prepare 16.2.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c77fa4..720afbd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + +## [16.2.1] - 2023-09-24 ### Fixed - Fixed errors loading files where their paths are non-ASCII and the C locale is not UTF-8. From c8520307e77f439cee48b70b71c010ab545c6132 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Sep 2023 01:08:02 -0700 Subject: [PATCH 0848/1101] Ignore locale outside of Windows The C calls I use are okay with UTF-8 on Unix platforms. --- CHANGELOG.md | 4 +++- tcod/_internal.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 720afbd4..2de3647a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] +### Fixed +- Ignore the locale when encoding file paths outside of Windows. ## [16.2.1] - 2023-09-24 ### Fixed -- Fixed errors loading files where their paths are non-ASCII and the C locale is not UTF-8. +- Fixed errors loading files on Windows where their paths are non-ASCII and the locale is not UTF-8. ## [16.2.0] - 2023-09-20 ### Changed diff --git a/tcod/_internal.py b/tcod/_internal.py index 56ac7c50..1bf5e211 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -130,9 +130,11 @@ def _fmt(string: str, stacklevel: int = 2) -> bytes: def _path_encode(path: Path) -> bytes: - """Return a bytes file path for the current C locale.""" + """Return a bytes file path for the current locale when on Windows, uses fsdecode for other platforms.""" + if sys.platform != "win32": + return bytes(path) # Sane and expected behavior for converting Path into bytes try: - return str(path).encode(locale.getlocale()[1] or "utf-8") + return str(path).encode(locale.getlocale()[1] or "utf-8") # Stay classy, Windows except UnicodeEncodeError as exc: if sys.version_info >= (3, 11): exc.add_note("""Consider calling 'locale.setlocale(locale.LC_CTYPES, ".UTF8")' to support Unicode paths.""") From 05b6e53a85615677a44ac283e2e8714132088018 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 05:35:18 +0000 Subject: [PATCH 0849/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.3.0 → 23.9.1](https://github.com/psf/black/compare/23.3.0...23.9.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54a9a88e..fb79db5e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/pycqa/isort From 3632ec6b854deea624077b14e6fad1624c50f7b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:05:18 +0000 Subject: [PATCH 0850/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb79db5e..abf69940 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer From 58c553cb097ce92041cab6f56bc66dd19ef80b83 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:00:56 +0000 Subject: [PATCH 0851/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.9.1 → 23.10.0](https://github.com/psf/black/compare/23.9.1...23.10.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abf69940..f49c6c76 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.0 hooks: - id: black - repo: https://github.com/pycqa/isort From d841fe7f86856bebb1f0290d4d7cdede632e7212 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Oct 2023 22:49:08 -0700 Subject: [PATCH 0852/1101] Apply Ruff auto fixes --- examples/samples_tcod.py | 2 +- examples/termbox/termbox.py | 2 +- tcod/event.py | 18 ++++-------------- tcod/map.py | 8 ++++---- tcod/path.py | 6 +----- tcod/render.py | 4 +--- tcod/sdl/audio.py | 4 ++-- tcod/sdl/joystick.py | 4 ++-- tcod/sdl/render.py | 4 ++-- 9 files changed, 18 insertions(+), 34 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 58d35a15..a53f81c3 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1295,7 +1295,7 @@ def on_draw(self) -> None: texture = np.roll(texture, -int_t, 1) # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): - for u in range(0, RES_U): + for u in range(RES_U): tex_v = (v + int_abs_t) / float(RES_V) texture[u, v] = tcod.noise_get_fbm(noise2d, [u / float(RES_U), tex_v], 32.0) + tcod.noise_get_fbm( noise2d, [1 - u / float(RES_U), tex_v], 32.0 diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index 26ab2d7a..714a28a5 100755 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -264,7 +264,7 @@ def peek_event(self, timeout=0): else: uch = None """ - pass # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) + # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) def poll_event(self): """Wait for an event and return it. diff --git a/tcod/event.py b/tcod/event.py index 16eaf498..41623a93 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -83,11 +83,11 @@ import enum import warnings -from typing import Any, Callable, Generic, Iterator, Mapping, NamedTuple, TypeVar +from typing import Any, Callable, Final, Generic, Iterator, Mapping, NamedTuple, TypeVar import numpy as np from numpy.typing import NDArray -from typing_extensions import Final, Literal +from typing_extensions import Literal import tcod.event_constants import tcod.sdl.joystick @@ -796,12 +796,7 @@ def __init__(self, x: int, y: int) -> None: self.y = y def __repr__(self) -> str: - return "tcod.event.{}(type={!r}, x={!r}, y={!r})".format( - self.__class__.__name__, - self.type, - self.x, - self.y, - ) + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, x={self.x!r}, y={self.y!r})" def __str__(self) -> str: return "<{}, x={!r}, y={!r})".format( @@ -828,12 +823,7 @@ def __init__(self, type: str, width: int, height: int) -> None: self.height = height def __repr__(self) -> str: - return "tcod.event.{}(type={!r}, width={!r}, height={!r})".format( - self.__class__.__name__, - self.type, - self.width, - self.height, - ) + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, width={self.width!r}, height={self.height!r})" def __str__(self) -> str: return "<{}, width={!r}, height={!r})".format( diff --git a/tcod/map.py b/tcod/map.py index 820c981d..8cd6ea68 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -137,8 +137,8 @@ def compute_fov( """ if not (0 <= x < self.width and 0 <= y < self.height): warnings.warn( - "Index ({}, {}) is outside of this maps shape ({}, {})." - "\nThis will raise an error in future versions.".format(x, y, self.width, self.height), + f"Index ({x}, {y}) is outside of this maps shape ({self.width}, {self.height})." + "\nThis will raise an error in future versions.", RuntimeWarning, stacklevel=2, ) @@ -239,8 +239,8 @@ def compute_fov( raise TypeError(msg) if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]): warnings.warn( - "Given pov index {!r} is outside the array of shape {!r}." - "\nThis will raise an error in future versions.".format(pov, transparency.shape), + f"Given pov index {pov!r} is outside the array of shape {transparency.shape!r}." + "\nThis will raise an error in future versions.", RuntimeWarning, stacklevel=2, ) diff --git a/tcod/path.py b/tcod/path.py index 84d54138..09f6d985 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -83,11 +83,7 @@ def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: return self._CALLBACK_P, ffi.new_handle(self._userdata), self.shape def __repr__(self) -> str: - return "{}({!r}, shape={!r})".format( - self.__class__.__name__, - self._userdata, - self.shape, - ) + return f"{self.__class__.__name__}({self._userdata!r}, shape={self.shape!r})" class EdgeCostCallback(_EdgeCostFunc): diff --git a/tcod/render.py b/tcod/render.py index d01d8129..8825d076 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -29,9 +29,7 @@ from __future__ import annotations -from typing import Any - -from typing_extensions import Final +from typing import Any, Final import tcod.console import tcod.sdl.render diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 8b9addab..b623c58d 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -48,11 +48,11 @@ import threading import time from types import TracebackType -from typing import Any, Callable, Hashable, Iterator +from typing import Any, Callable, Final, Hashable, Iterator import numpy as np from numpy.typing import ArrayLike, DTypeLike, NDArray -from typing_extensions import Final, Literal, Self +from typing_extensions import Literal, Self import tcod.sdl.sys from tcod.cffi import ffi, lib diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 8ab8637f..2e918202 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -5,10 +5,10 @@ from __future__ import annotations import enum -from typing import Any, ClassVar +from typing import Any, ClassVar, Final from weakref import WeakValueDictionary -from typing_extensions import Final, Literal +from typing_extensions import Literal import tcod.sdl.sys from tcod.cffi import ffi, lib diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index b5489f18..cb365215 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -5,11 +5,11 @@ from __future__ import annotations import enum -from typing import Any +from typing import Any, Final import numpy as np from numpy.typing import NDArray -from typing_extensions import Final, Literal +from typing_extensions import Literal import tcod.sdl.video from tcod.cffi import ffi, lib From 95e605a7c633c6d1c50a5966d642d550746e7563 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Oct 2023 23:22:55 -0700 Subject: [PATCH 0853/1101] Fix or ignore several Ruff issues --- build_libtcod.py | 4 ++-- examples/termbox/termbox.py | 2 ++ examples/termbox/termboxtest.py | 4 +++- examples/thread_jobs.py | 12 +++++----- tcod/color.py | 12 +++++----- tcod/libtcodpy.py | 42 ++++++++++++++++++--------------- 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index b912ecc1..59447e9f 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -9,7 +9,7 @@ import re import sys from pathlib import Path -from typing import Any, Iterable, Iterator +from typing import Any, ClassVar, Iterable, Iterator from cffi import FFI @@ -45,7 +45,7 @@ class ParsedHeader: """ # Class dictionary of all parsed headers. - all_headers: dict[Path, ParsedHeader] = {} + all_headers: ClassVar[dict[Path, ParsedHeader]] = {} def __init__(self, path: Path) -> None: """Initialize and organize a header file.""" diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py index 714a28a5..4634c990 100755 --- a/examples/termbox/termbox.py +++ b/examples/termbox/termbox.py @@ -15,6 +15,8 @@ [ ] not all keys/events are mapped """ +# ruff: noqa + class TermboxException(Exception): def __init__(self, msg) -> None: diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py index c0d57065..696be1ce 100755 --- a/examples/termbox/termboxtest.py +++ b/examples/termbox/termboxtest.py @@ -1,7 +1,9 @@ -#!/usr/bin/python +#!/usr/bin/env python import termbox +# ruff: noqa + spaceord = ord(" ") diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index d6fa54fd..f4154f9a 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -31,32 +31,32 @@ REPEAT = 10 # Number to times to run a test. Only the fastest result is shown. -def test_fov(map_: tcod.map.Map) -> tcod.map.Map: +def test_fov(map_: tcod.map.Map) -> tcod.map.Map: # noqa: D103 map_.compute_fov(MAP_WIDTH // 2, MAP_HEIGHT // 2) return map_ -def test_fov_single(maps: List[tcod.map.Map]) -> None: +def test_fov_single(maps: List[tcod.map.Map]) -> None: # noqa: D103 for map_ in maps: test_fov(map_) -def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: +def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: # noqa: D103 for _result in executor.map(test_fov, maps): pass -def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: +def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: # noqa: D103 astar = tcod.path.AStar(map_) return astar.get_path(0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1) -def test_astar_single(maps: List[tcod.map.Map]) -> None: +def test_astar_single(maps: List[tcod.map.Map]) -> None: # noqa: D103 for map_ in maps: test_astar(map_) -def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: +def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: # noqa: D103 for _result in executor.map(test_astar, maps): pass diff --git a/tcod/color.py b/tcod/color.py index dfd97b8c..37312413 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -17,7 +17,7 @@ class Color(List[int]): b (int): Blue value, from 0 to 255. """ - def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> None: + def __init__(self, r: int = 0, g: int = 0, b: int = 0) -> None: # noqa: D107 list.__setitem__(self, slice(None), (r & 0xFF, g & 0xFF, b & 0xFF)) @property @@ -82,13 +82,13 @@ def __getitem__(self, index: Any) -> Any: # noqa: ANN401 return super().__getitem__(index) @deprecate("This class will not be mutable in the future.", FutureWarning) - def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401 + def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401, D105 if isinstance(index, str): super().__setitem__("rgb".index(index), value) else: super().__setitem__(index, value) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: """Compare equality between colors. Also compares with standard sequences such as 3-item tuples or lists. @@ -99,7 +99,7 @@ def __eq__(self, other: Any) -> bool: return False @deprecate("Use NumPy instead for color math operations.", FutureWarning) - def __add__(self, other: Any) -> Color: # type: ignore[override] + def __add__(self, other: object) -> Color: # type: ignore[override] """Add two colors together. .. deprecated:: 9.2 @@ -108,7 +108,7 @@ def __add__(self, other: Any) -> Color: # type: ignore[override] return Color._new_from_cdata(lib.TCOD_color_add(self, other)) @deprecate("Use NumPy instead for color math operations.", FutureWarning) - def __sub__(self, other: Any) -> Color: + def __sub__(self, other: object) -> Color: """Subtract one color from another. .. deprecated:: 9.2 @@ -117,7 +117,7 @@ def __sub__(self, other: Any) -> Color: return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) @deprecate("Use NumPy instead for color math operations.", FutureWarning) - def __mul__(self, other: Any) -> Color: + def __mul__(self, other: object) -> Color: """Multiply with a scaler or another color. .. deprecated:: 9.2 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index a9162eb3..d3db588c 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -54,7 +54,7 @@ ) # Functions are too deprecated to make changes. -# ruff: noqa: ANN401 PLR0913 +# ruff: noqa: ANN401 PLR0913 D102 D103 D105 D107 Bsp = tcod.bsp.BSP @@ -1477,6 +1477,9 @@ def console_print_ex( con (Console): Any Console instance. x (int): Character x position from the left. y (int): Character y position from the top. + flag: Blending mode to use. + alignment: The libtcod alignment constant. + fmt: A unicode or bytes string, optionally using color codes. .. deprecated:: 8.5 Use :any:`Console.print_` instead. @@ -1726,8 +1729,7 @@ def console_wait_for_keypress(flush: bool) -> Key: """Block until the user presses a key, then returns a new Key. Args: - flush bool: If True then the event queue is cleared before waiting - for the next event. + flush: If True then the event queue is cleared before waiting for the next event. Returns: Key: A new Key instance. @@ -2136,8 +2138,8 @@ def path_new_using_function( Args: w (int): Clipping width. h (int): Clipping height. - func (Callable[[int, int, int, int, Any], float]): - userData (Any): + func: Callback function with the format: `f(origin_x, origin_y, dest_x, dest_y, userData) -> float` + userData (Any): An object passed to the callback. dcost (float): A multiplier for the cost of diagonal movement. Can be set to 0 to disable diagonal movement. @@ -2485,6 +2487,7 @@ def heightmap_copy(hm1: NDArray[np.float32], hm2: NDArray[np.float32]) -> None: """Copy the heightmap ``hm1`` to ``hm2``. Args: + hm: A numpy.ndarray formatted for heightmap functions. hm1 (numpy.ndarray): The source heightmap. hm2 (numpy.ndarray): The destination heightmap. @@ -2499,6 +2502,7 @@ def heightmap_normalize(hm: NDArray[np.float32], mi: float = 0.0, ma: float = 1. """Normalize heightmap values between ``mi`` and ``ma``. Args: + hm: A numpy.ndarray formatted for heightmap functions. mi (float): The lowest value after normalization. ma (float): The highest value after normalization. """ @@ -2944,7 +2948,7 @@ def heightmap_has_land_on_border(hm: NDArray[np.float32], waterlevel: float) -> Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. - waterLevel (float): The water level to use. + waterlevel (float): The water level to use. Returns: bool: True if the map edges are below ``waterlevel``, otherwise False. @@ -3493,6 +3497,7 @@ def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: """Set a Noise objects default noise algorithm. Args: + n: Noise object. typ (int): Any NOISE_* constant. """ n.algorithm = typ @@ -3531,7 +3536,7 @@ def noise_get_fbm( n (Noise): A Noise instance. f (Sequence[float]): The point to sample the noise from. typ (int): The noise algorithm to use. - octaves (float): The level of level. Should be more than 1. + oc (float): The level of level. Should be more than 1. Returns: float: The sampled noise value. @@ -3552,7 +3557,7 @@ def noise_get_turbulence( n (Noise): A Noise instance. f (Sequence[float]): The point to sample the noise from. typ (int): The noise algorithm to use. - octaves (float): The level of level. Should be more than 1. + oc (float): The level of level. Should be more than 1. Returns: float: The sampled noise value. @@ -3781,8 +3786,8 @@ def random_get_int(rnd: tcod.random.Random | None, mi: int, ma: int) -> int: Args: rnd (Optional[Random]): A Random instance, or None to use the default. - low (int): The lower bound of the random range, inclusive. - high (int): The upper bound of the random range, inclusive. + mi (int): The lower bound of the random range, inclusive. + ma (int): The upper bound of the random range, inclusive. Returns: int: A random integer in the range ``mi`` <= n <= ``ma``. @@ -3798,8 +3803,8 @@ def random_get_float(rnd: tcod.random.Random | None, mi: float, ma: float) -> fl Args: rnd (Optional[Random]): A Random instance, or None to use the default. - low (float): The lower bound of the random range, inclusive. - high (float): The upper bound of the random range, inclusive. + mi (float): The lower bound of the random range, inclusive. + ma (float): The upper bound of the random range, inclusive. Returns: float: A random double precision float @@ -3827,8 +3832,8 @@ def random_get_int_mean(rnd: tcod.random.Random | None, mi: int, ma: int, mean: Args: rnd (Optional[Random]): A Random instance, or None to use the default. - low (int): The lower bound of the random range, inclusive. - high (int): The upper bound of the random range, inclusive. + mi (int): The lower bound of the random range, inclusive. + ma (int): The upper bound of the random range, inclusive. mean (int): The mean return value. Returns: @@ -3845,8 +3850,8 @@ def random_get_float_mean(rnd: tcod.random.Random | None, mi: float, ma: float, Args: rnd (Optional[Random]): A Random instance, or None to use the default. - low (float): The lower bound of the random range, inclusive. - high (float): The upper bound of the random range, inclusive. + mi (float): The lower bound of the random range, inclusive. + ma (float): The upper bound of the random range, inclusive. mean (float): The mean return value. Returns: @@ -4071,7 +4076,7 @@ def sys_save_screenshot(name: str | PathLike[str] | None = None) -> None: screenshot000.png, screenshot001.png, etc. Whichever is available first. Args: - file Optional[AnyStr]: File path to save screenshot. + name: File path to save screenshot. .. deprecated:: 11.13 This function is not supported by contexts. @@ -4179,8 +4184,7 @@ def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: The callback is called on every call to :any:`libtcodpy.console_flush`. Args: - callback Callable[[CData], None]: - A function which takes a single argument. + callback: A function which takes a single argument. .. deprecated:: 11.13 This function is not supported by contexts. From 086684f92253237d2ab1bbe4d7ebbeb0f2bfdb04 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Oct 2023 23:37:32 -0700 Subject: [PATCH 0854/1101] Switch linters and formatters to Ruff --- .github/workflows/python-package.yml | 36 ++++++++++------------------ .pre-commit-config.yaml | 12 ++++------ .vscode/extensions.json | 2 +- .vscode/settings.json | 3 +-- build_sdl.py | 6 ++++- examples/samples_tcod.py | 5 +--- pyproject.toml | 2 ++ 7 files changed, 28 insertions(+), 38 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 784fef9a..5fa48608 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,26 +16,16 @@ env: git-depth: 0 # Depth to search for tags. jobs: - black: + ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install Black - run: pip install black - - name: Run Black - run: black --check --diff examples/ scripts/ tcod/ tests/ *.py - - isort: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install isort - run: pip install isort - - name: isort - uses: liskin/gh-problem-matcher-wrap@v2 - with: - linters: isort - run: isort scripts/ tcod/ tests/ examples/ --check --diff + - name: Install Ruff + run: pip install ruff + - name: Ruff Check + run: ruff check . --fix-only --exit-non-zero-on-fix --output-format=github + - name: Ruff Format + run: ruff format . --check mypy: runs-on: ubuntu-latest @@ -76,7 +66,7 @@ jobs: # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: - needs: [black, isort, mypy] + needs: [ruff, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -100,7 +90,7 @@ jobs: SDL_VERSION: ${{ matrix.sdl-version }} build: - needs: [black, isort, mypy] + needs: [ruff, mypy] runs-on: ${{ matrix.os }} strategy: matrix: @@ -164,7 +154,7 @@ jobs: retention-days: 7 test-docs: - needs: [black, isort, mypy] + needs: [ruff, mypy] runs-on: ubuntu-latest steps: - name: Install APT dependencies @@ -189,7 +179,7 @@ jobs: run: python -m sphinx -T -E -W --keep-going . _build/html isolated: # Test installing the package from source. - needs: [black, isort, mypy, sdist] + needs: [ruff, mypy, sdist] runs-on: ${{ matrix.os }} strategy: matrix: @@ -219,7 +209,7 @@ jobs: python -c "import tcod.context" linux-wheels: - needs: [black, isort, mypy] + needs: [ruff, mypy] runs-on: "ubuntu-latest" strategy: matrix: @@ -268,7 +258,7 @@ jobs: retention-days: 7 build-macos: - needs: [black, isort, mypy] + needs: [ruff, mypy] runs-on: "macos-11" strategy: fail-fast: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f49c6c76..a591e078 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,13 +14,11 @@ repos: - id: debug-statements - id: fix-byte-order-marker - id: detect-private-key - - repo: https://github.com/psf/black - rev: 23.10.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.2 hooks: - - id: black - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort + - id: ruff + args: [--fix-only, --exit-non-zero-on-fix] + - id: ruff-format default_language_version: python: python3.11 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d589600c..15ad54f7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,13 +6,13 @@ "austin.code-gnu-global", "editorconfig.editorconfig", "ms-python.python", - "ms-python.black-formatter", "ms-python.vscode-pylance", "ms-vscode.cpptools", "redhat.vscode-yaml", "streetsidesoftware.code-spell-checker", "tamasfe.even-better-toml", "xaver.clang-format", + "charliermarsh.ruff" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a02db40..aeeb53d8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,7 +18,6 @@ "--follow-imports=silent", "--show-column-numbers" ], - "python.formatting.provider": "none", "files.associations": { "*.spec": "python", }, @@ -483,7 +482,7 @@ "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" + "editor.defaultFormatter": "charliermarsh.ruff" }, "cSpell.enableFiletypes": [ "github-actions-workflow" diff --git a/build_sdl.py b/build_sdl.py index b47b426c..0c46df14 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -183,7 +183,11 @@ def _should_track_define(self, tokens: list[Any]) -> bool: ) def on_directive_handle( - self, directive: Any, tokens: list[Any], if_passthru: bool, preceding_tokens: list[Any] # noqa: ANN401 + self, + directive: Any, # noqa: ANN401 + tokens: list[Any], + if_passthru: bool, + preceding_tokens: list[Any], ) -> Any: # noqa: ANN401 """Catch and store definitions.""" if directive.value == "define" and self._should_track_define(tokens): diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index a53f81c3..7f65a817 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -565,10 +565,7 @@ def draw_ui(self) -> None: sample_console.print( 1, 1, - "IJKL : move around\n" - "T : torch fx {}\n" - "W : light walls {}\n" - "+-: algo {}".format( + "IJKL : move around\nT : torch fx {}\nW : light walls {}\n+-: algo {}".format( "on " if self.torch else "off", "on " if self.light_walls else "off", FOV_ALGO_NAMES[self.algo_num], diff --git a/pyproject.toml b/pyproject.toml index 325c0baa..3edd28ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -186,6 +186,8 @@ ignore = [ "D407", # dashed-underline-after-section "D408", # section-underline-after-name "D409", # section-underline-matches-section-length + "D206", # indent-with-spaces + "W191", # tab-indentation ] extend-exclude = ["libtcod"] # Ignore submodule line-length = 120 From 027f52229b5004807580a01883a1c11a795e34c0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 25 Oct 2023 00:13:30 -0700 Subject: [PATCH 0855/1101] Convert isolated tests to use Tox Remove setuptools upper bound Skip more tests requiring SDL windows --- .github/workflows/python-package.yml | 22 ++++++++++------------ pyproject.toml | 4 ++-- tests/conftest.py | 9 +++++++++ tests/test_sdl.py | 6 +++--- tests/test_tcod.py | 2 +- tox.ini | 17 +++++++++++++++++ 6 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 tox.ini diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5fa48608..fdd34ead 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -178,35 +178,33 @@ jobs: working-directory: docs run: python -m sphinx -T -E -W --keep-going . _build/html - isolated: # Test installing the package from source. - needs: [ruff, mypy, sdist] + tox: + needs: [ruff] runs-on: ${{ matrix.os }} strategy: matrix: os: ["ubuntu-latest", "windows-latest"] steps: + - uses: actions/checkout@v3 + with: + fetch-depth: ${{ env.git-depth }} + - name: Checkout submodules + run: git submodule update --init --depth 1 - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.x - name: Install Python dependencies run: | - python -m pip install --upgrade pip - pip install wheel + python -m pip install --upgrade pip tox - name: Install APT dependencies if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install libsdl2-dev - - uses: actions/download-artifact@v3 - with: - name: sdist - - name: Build package in isolation - run: | - pip install tcod-*.tar.gz - - name: Confirm package import + - name: Run tox run: | - python -c "import tcod.context" + tox -vv linux-wheels: needs: [ruff, mypy] diff --git a/pyproject.toml b/pyproject.toml index 3edd28ce..94f1ddc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [build-system] requires = [ - # Newer versions of setuptools break editable installs + # setuptools >=64.0.0 might break editable installs # https://github.com/pypa/setuptools/issues/3548 - "setuptools >=61.0.0, <64.0.0", + "setuptools >=61.0.0", "setuptools_scm[toml]>=6.2", "wheel>=0.37.1", "cffi>=1.15", diff --git a/tests/conftest.py b/tests/conftest.py index 163d0918..83e2dc8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,15 @@ def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") +@pytest.fixture() +def uses_window(request: pytest.FixtureRequest) -> Iterator[None]: + """Marks tests which require a rendering context.""" + if request.config.getoption("--no-window"): + pytest.skip("This test needs a rendering context.") + yield None + return + + @pytest.fixture(scope="session", params=["SDL", "SDL2"]) def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Console]: if request.config.getoption("--no-window"): diff --git a/tests/test_sdl.py b/tests/test_sdl.py index fa2ac29a..10cdbb50 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -11,7 +11,7 @@ # ruff: noqa: D103 -def test_sdl_window() -> None: +def test_sdl_window(uses_window: None) -> None: assert tcod.sdl.video.get_grabbed_window() is None window = tcod.sdl.video.new_window(1, 1) window.raise_window() @@ -47,14 +47,14 @@ def test_sdl_window_bad_types() -> None: tcod.sdl.video.Window(tcod.ffi.new("SDL_Rect*")) -def test_sdl_screen_saver() -> None: +def test_sdl_screen_saver(uses_window: None) -> None: tcod.sdl.sys.init() assert tcod.sdl.video.screen_saver_allowed(False) is False assert tcod.sdl.video.screen_saver_allowed(True) is True assert tcod.sdl.video.screen_saver_allowed() is True -def test_sdl_render() -> None: +def test_sdl_render(uses_window: None) -> None: window = tcod.sdl.video.new_window(1, 1) render = tcod.sdl.render.new_renderer(window, software=True, vsync=False, target_textures=True) render.present() diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 33e50195..38b1d5c2 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -186,7 +186,7 @@ def test_recommended_size(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_context() -> None: +def test_context(uses_window: None) -> None: with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): pass WIDTH, HEIGHT = 16, 4 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..a9bf7fbf --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +isolated_build = True +env_list = + py311 +minversion = 4.4.11 + +[testenv] +description = run the tests with pytest +package = wheel +wheel_build_env = .pkg +deps = + pytest>=6 + pytest-cov + pytest-benchmark + pytest-timeout +commands = + pytest --no-window {tty:--color=yes} {posargs} From b399f48289c0aed48b2e5c0668a4f0752d348f9b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 25 Oct 2023 16:23:40 -0700 Subject: [PATCH 0856/1101] Refactor tutorial More verbose code examples Include future annotations Explain procedural block elements function call --- docs/tutorial/part-00.rst | 4 ++ docs/tutorial/part-01.rst | 87 ++++++++++++++++++++++++++++++--------- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/docs/tutorial/part-00.rst b/docs/tutorial/part-00.rst index cb97db3f..a00a7fe9 100644 --- a/docs/tutorial/part-00.rst +++ b/docs/tutorial/part-00.rst @@ -23,9 +23,13 @@ First script First start with a modern top-level script. Create a script in the project root folder called ``main.py`` which checks :python:`if __name__ == "__main__":` and calls a ``main`` function. +Any modern script using type-hinting will also have :python:`from __future__ import annotations` near the top. .. code-block:: python + from __future__ import annotations + + def main() -> None: print("Hello World!") diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index 028fc40a..4f1827f5 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -17,6 +17,9 @@ You should have ``main.py`` script from :ref:`part-0`: .. code-block:: python + from __future__ import annotations + + def main() -> None: ... @@ -36,6 +39,7 @@ These kinds of tilesets are always loaded with :python:`columns=16, rows=16, cha Use the string :python:`"data/Alloy_curses_12x12.png"` to refer to the path of the tileset. [#why_not_pathlib]_ Load the tileset with :any:`tcod.tileset.load_tilesheet`. +Pass the tileset to :any:`tcod.tileset.procedural_block_elements` which will fill in most `Block Elements `_ missing from `Code Page 437 `_. Then pass the tileset to :any:`tcod.context.new`, you only need to provide the ``tileset`` parameter. :any:`tcod.context.new` returns a :any:`Context` which will be used with Python's :python:`with` statement. @@ -45,9 +49,10 @@ The new block can't be empty, so add :python:`pass` to the with statement body. These functions are part of modules which have not been imported yet, so new imports for ``tcod.context`` and ``tcod.tileset`` must be added to the top of the script. .. code-block:: python - :emphasize-lines: 2,3,8-12 + :emphasize-lines: 3,4,8-14 + + from __future__ import annotations - ... import tcod.context # Add these imports import tcod.tileset @@ -57,9 +62,13 @@ These functions are part of modules which have not been imported yet, so new imp tileset = tcod.tileset.load_tilesheet( "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 ) + tcod.tileset.procedural_block_elements(tileset=tileset) with tcod.context.new(tileset=tileset) as context: pass # The window will stay open for the duration of this block - ... + + + if __name__ == "__main__": + main() If an import fails that means you do not have ``tcod`` installed on the Python environment you just used to run the script. If you use an IDE then make sure the Python environment it is using is correct and then run :shell:`pip install tcod` from the shell terminal within that IDE. @@ -88,11 +97,14 @@ Then test if an event is for closing the window with :python:`if isinstance(even If this is True then you should exit the function with :python:`raise SystemExit()`. [#why_raise]_ .. code-block:: python - :emphasize-lines: 2,3,11-19 + :emphasize-lines: 3,5,15-23 + + from __future__ import annotations - ... import tcod.console + import tcod.context import tcod.event + import tcod.tileset def main() -> None: @@ -100,6 +112,7 @@ If this is True then you should exit the function with :python:`raise SystemExit tileset = tcod.tileset.load_tilesheet( "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 ) + tcod.tileset.procedural_block_elements(tileset=tileset) console = tcod.console.Console(80, 50) console.print(0, 0, "Hello World") # Test text by printing "Hello World" to the console with tcod.context.new(console=console, tileset=tileset) as context: @@ -109,7 +122,10 @@ If this is True then you should exit the function with :python:`raise SystemExit print(event) if isinstance(event, tcod.event.Quit): raise SystemExit() - ... + + + if __name__ == "__main__": + main() If you run this then you get a window saying :python:`"Hello World"`. The window can be resized and the console will be stretched to fit the new resolution. @@ -135,9 +151,15 @@ The parameters for ``on_draw`` are ``self`` because this is an instance method a Call this method using the players current coordinates and the :python:`"@"` character. .. code-block:: python + :emphasize-lines: 3,10-21 + + from __future__ import annotations - ... import attrs + import tcod.console + import tcod.context + import tcod.event + import tcod.tileset @attrs.define(eq=False) @@ -152,6 +174,7 @@ Call this method using the players current coordinates and the :python:`"@"` cha def on_draw(self, console: tcod.console.Console) -> None: """Draw the player glyph.""" console.print(self.player_x, self.player_y, "@") + ... Now remove the :python:`console.print(0, 0, "Hello World")` line from ``main``. @@ -183,7 +206,10 @@ Modify the drawing routine so that the console is cleared, then passed to :pytho print(event) if isinstance(event, tcod.event.Quit): raise SystemExit() - ... + + + if __name__ == "__main__": + main() Now if you run the script you'll see ``@``. @@ -201,12 +227,33 @@ Make a case for each arrow key: ``LEFT`` ``RIGHT`` ``UP`` ``DOWN`` and move the Since events are printed you can check the :any:`KeySym` of a key by pressing that key and looking at the printed output. See :any:`KeySym` for a list of all keys. +Finally replace the event handling code in ``main`` to defer to the states ``on_event`` method. +The full script so far is: + .. code-block:: python + :emphasize-lines: 23-35,53 + + from __future__ import annotations + + import attrs + import tcod.console + import tcod.context + import tcod.event + import tcod.tileset + - ... @attrs.define(eq=False) class ExampleState: - ... + """Example state with a hard-coded player position.""" + + player_x: int + """Player X position, left-most position is zero.""" + player_y: int + """Player Y position, top-most position is zero.""" + + def on_draw(self, console: tcod.console.Console) -> None: + """Draw the player glyph.""" + console.print(self.player_x, self.player_y, "@") def on_event(self, event: tcod.event.Event) -> None: """Move the player on events and handle exiting. Movement is hard-coded.""" @@ -221,16 +268,15 @@ See :any:`KeySym` for a list of all keys. self.player_y -= 1 case tcod.event.KeyDown(sym=tcod.event.KeySym.DOWN): self.player_y += 1 - ... - -Now replace the event handling code in ``main`` to defer to the states ``on_event`` method. -.. code-block:: python - :emphasize-lines: 12 - ... def main() -> None: - ... + """Run ExampleState.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + tcod.tileset.procedural_block_elements(tileset=tileset) + console = tcod.console.Console(80, 50) state = ExampleState(player_x=console.width // 2, player_y=console.height // 2) with tcod.context.new(console=console, tileset=tileset) as context: while True: @@ -240,7 +286,10 @@ Now replace the event handling code in ``main`` to defer to the states ``on_even for event in tcod.event.wait(): print(event) state.on_event(event) # Pass events to the state - ... + + + if __name__ == "__main__": + main() Now when you run this script you have a player character you can move around with the arrow keys before closing the window. @@ -253,7 +302,7 @@ You can review the part-1 source code `here Date: Wed, 25 Oct 2023 15:15:55 -0700 Subject: [PATCH 0857/1101] Add SDL audio device example --- examples/audio_tone.py | 48 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 49 insertions(+) create mode 100755 examples/audio_tone.py diff --git a/examples/audio_tone.py b/examples/audio_tone.py new file mode 100755 index 00000000..dc24d3ec --- /dev/null +++ b/examples/audio_tone.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Shows how to use tcod.sdl.audio to play a custom-made audio stream. + +Opens an audio device using SDL and plays a square wave for 1 second. +""" +import math +import time +from typing import Any + +import attrs +import numpy as np +from numpy.typing import NDArray +from scipy import signal # type: ignore + +import tcod.sdl.audio + +VOLUME = 10 ** (-12 / 10) # -12dB, square waves can be loud + + +@attrs.define +class PullWave: + """Square wave stream generator for an SDL audio device in pull mode.""" + + time: float = 0.0 + + def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None: + """Stream a square wave to SDL on demand. + + This function must run faster than the stream duration. + Numpy is used to keep performance within these limits. + """ + sample_rate = device.frequency + n_samples = device.buffer_samples + duration = n_samples / sample_rate + print(f"{duration=} {self.time=}") + + t = np.linspace(self.time, self.time + duration, n_samples, endpoint=False) + self.time += duration + wave = signal.square(t * (math.tau * 440)).astype(np.float32) + wave *= VOLUME + + stream[:] = device.convert(wave) + + +if __name__ == "__main__": + with tcod.sdl.audio.open(callback=PullWave()) as device: + print(device) + time.sleep(1) diff --git a/requirements.txt b/requirements.txt index ee1ea8a0..eb2f7d65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +attrs>=23.1.0 cffi>=1.15 numpy>=1.21.4 pycparser>=2.14 From 32005bb43364979f566a9d8442fc584ea16a35b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:48:44 +0000 Subject: [PATCH 0858/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.2 → v0.1.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.2...v0.1.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a591e078..a0778f86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.2 + rev: v0.1.3 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 326f92945a28bce6d5d6b8f722d8a0e8cfe2aa00 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 30 Oct 2023 14:44:24 -0700 Subject: [PATCH 0859/1101] Have Tox use any Python 3 version --- .github/workflows/python-package.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index fdd34ead..8eeac91c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -193,7 +193,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.x + python-version: "3.x" - name: Install Python dependencies run: | python -m pip install --upgrade pip tox diff --git a/tox.ini b/tox.ini index a9bf7fbf..784e5f57 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] isolated_build = True env_list = - py311 + py3 minversion = 4.4.11 [testenv] From 6eb6d6d90b525c1764543c551a9fc5cb19923f25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:41:41 +0000 Subject: [PATCH 0860/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.3 → v0.1.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.3...v0.1.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0778f86..b58a31b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.3 + rev: v0.1.4 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 04664d305ac11e4d52074b10470399ab52bd3cc5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 10 Nov 2023 14:36:49 -0800 Subject: [PATCH 0861/1101] Remove unused type ignores New Mypy version --- tcod/libtcodpy.py | 2 +- tcod/sdl/render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index d3db588c..e6649497 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -2920,7 +2920,7 @@ def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel """ cn = ffi.new("float[3]") lib.TCOD_heightmap_get_normal(_heightmap_cdata(hm), x, y, cn, waterLevel) - return tuple(cn) # type: ignore + return tuple(cn) @deprecate("This function is deprecated, see documentation.") diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index cb365215..b1dafe8c 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -360,7 +360,7 @@ def draw_color(self) -> tuple[int, int, int, int]: """ rgba = ffi.new("uint8_t[4]") _check(lib.SDL_GetRenderDrawColor(self.p, rgba, rgba + 1, rgba + 2, rgba + 3)) - return tuple(rgba) # type: ignore[return-value] + return tuple(rgba) @draw_color.setter def draw_color(self, rgba: tuple[int, int, int, int]) -> None: From 7ef79333f32273c0d35c6b9c5b26feda53667393 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:05:16 +0000 Subject: [PATCH 0862/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b58a31b1..e8feb5a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.5 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 5788303ea960d11d31fee5d2844a02a7e1957955 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 19:38:11 +0000 Subject: [PATCH 0863/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.5 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.5...v0.1.6) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8feb5a7..ff7a3bd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.6 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 1ea1db28008029d2165b0705ed32b8aef8c65cd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Dec 2023 00:44:12 +0000 Subject: [PATCH 0864/1101] Bump pyinstaller in /examples/distribution/PyInstaller Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 4.3 to 5.13.1. - [Release notes](https://github.com/pyinstaller/pyinstaller/releases) - [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst) - [Commits](https://github.com/pyinstaller/pyinstaller/compare/v4.3...v5.13.1) --- updated-dependencies: - dependency-name: pyinstaller dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- examples/distribution/PyInstaller/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt index f28443fd..390bb9dd 100644 --- a/examples/distribution/PyInstaller/requirements.txt +++ b/examples/distribution/PyInstaller/requirements.txt @@ -1,3 +1,3 @@ -tcod==12.2.0 -pyinstaller==4.3 -pypiwin32; sys_platform=="win32" +tcod==12.2.0 +pyinstaller==5.13.1 +pypiwin32; sys_platform=="win32" From 0c3035c5f472713b72c013c7a3e53806c3e8d1d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:00:05 +0000 Subject: [PATCH 0865/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.7) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff7a3bd4..2b5e7322 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.7 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 36bd2ec9e33f9d26f64f0ac56930cc209032211b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 20:06:55 +0000 Subject: [PATCH 0866/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.8) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b5e7322..538602ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.8 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 53926e18521dd8539157eb3cb3f57dd4e85ecf61 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 19:50:08 +0000 Subject: [PATCH 0867/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.8 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.8...v0.1.9) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 538602ff..b944cbb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.1.9 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From f919219178b8bdb7c134bc288409428990fd855e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:52:24 +0000 Subject: [PATCH 0868/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.11) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b944cbb4..63cc2295 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.1.11 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From d9fdb067436d5729c929a9aeb2b942c74572d030 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 19:26:35 +0000 Subject: [PATCH 0869/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.13) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 63cc2295..b3619574 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.11 + rev: v0.1.13 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From b738d1ee848d1ce0307267cd46b18b5040f745bb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 20:58:18 -0800 Subject: [PATCH 0870/1101] Update Python version for pre-commit. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3619574..9f04c539 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,4 +21,4 @@ repos: args: [--fix-only, --exit-non-zero-on-fix] - id: ruff-format default_language_version: - python: python3.11 + python: python3.12 From ea62af615419a136960ccea3dc43f26809ffb4de Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 20:58:58 -0800 Subject: [PATCH 0871/1101] Modernize VSCode settings --- .vscode/settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index aeeb53d8..67f05c37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,8 @@ ], "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": false + "source.fixAll": "always", + "source.organizeImports": "never" }, "files.trimFinalNewlines": true, "files.insertFinalNewline": true, From 390fe35c00faff657c3aee78dfbbaa04e461c3a2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 21:05:24 -0800 Subject: [PATCH 0872/1101] Initialize SDL joystick subsystems lazily Should fix performance issues caused by calling functions such as get_controller every frame. --- CHANGELOG.md | 1 + tcod/sdl/joystick.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de3647a..0abccdd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] ### Fixed - Ignore the locale when encoding file paths outside of Windows. +- Fix performance when calling joystick functions. ## [16.2.1] - 2023-09-24 ### Fixed diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 2e918202..62241dc5 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -363,7 +363,10 @@ def _touchpad(self) -> bool: def init() -> None: """Initialize SDL's joystick and game controller subsystems.""" - tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER) + CONTROLLER_SYSTEMS = tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER + if tcod.sdl.sys.Subsystem(lib.SDL_WasInit(CONTROLLER_SYSTEMS)) == CONTROLLER_SYSTEMS: + return # Already initialized + tcod.sdl.sys.init(CONTROLLER_SYSTEMS) def _get_number() -> int: From 7904d0fd2f93ae6968db450fa5a5c08074aa5b89 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 21:12:53 -0800 Subject: [PATCH 0873/1101] Apply auto-formatting on markdown files from VSCode --- CHANGELOG.md | 797 ++++++++++++++++++++++++++++++------- CONTRIBUTING.md | 2 +- examples/README.md | 4 +- examples/termbox/README.md | 26 +- 4 files changed, 667 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abccdd6..cb802f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1294 +1,1790 @@ # Changelog + Changes relevant to the users of python-tcod are documented here. This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`. ## [Unreleased] + ### Fixed + - Ignore the locale when encoding file paths outside of Windows. - Fix performance when calling joystick functions. ## [16.2.1] - 2023-09-24 + ### Fixed + - Fixed errors loading files on Windows where their paths are non-ASCII and the locale is not UTF-8. ## [16.2.0] - 2023-09-20 + ### Changed + - Renamed `gauss` methods to fix typos. ## [16.1.1] - 2023-07-10 + ### Changed + - Added an empty `__slots__` to `EventDispatch`. - Bundle `SDL 2.28.1` on Windows and MacOS. ### Fixed + - Fixed "SDL failed to get a vertex buffer for this Direct3D 9 rendering batch!" https://github.com/libtcod/python-tcod/issues/131 ### Removed + - Dropped support for Python 3.7. ## [16.1.0] - 2023-06-23 + ### Added + - Added the enums `tcod.event.MouseButton` and `tcod.event.MouseButtonMask`. ### Changed + - Using `libtcod 1.24.0`. ### Deprecated + - Mouse button and mask constants have been replaced by enums. ### Fixed + - `WindowResized` literal annotations were in the wrong case. ## [16.0.3] - 2023-06-04 + ### Changed + - Enabled logging for libtcod and SDL. ### Deprecated + - Deprecated using `tcod` as an implicit alias for `libtcodpy`. You should use `from tcod import libtcodpy` if you want to access this module. - Deprecated constants being held directly in `tcod`, get these from `tcod.libtcodpy` instead. - Deprecated `tcod.Console` which should be accessed from `tcod.console.Console` instead. ## [16.0.2] - 2023-06-02 + ### Fixed + - Joystick/controller device events would raise `RuntimeError` when accessed after removal. ## [16.0.1] - 2023-05-28 + ### Fixed + - `AudioDevice.stopped` was inverted. - Fixed the audio mixer stop and fadeout methods. - Exceptions raised in the audio mixer callback no longer cause a messy crash, they now go to `sys.unraisablehook`. ## [16.0.0] - 2023-05-27 + ### Added + - Added PathLike support to more libtcodpy functions. - New `tcod.sdl.mouse.show` function for querying or setting mouse visibility. -- New class method `tcod.image.Image.from_file` to load images with. This replaces `tcod.image_load`. +- New class method `tcod.image.Image.from_file` to load images with. This replaces `tcod.image_load`. - `tcod.sdl.audio.AudioDevice` is now a context manager. ### Changed + - SDL audio conversion will now pass unconvertible floating types as float32 instead of raising. ### Deprecated + - Deprecated the libtcodpy functions for images and noise generators. ### Removed + - `tcod.console_set_custom_font` can no longer take bytes as the file path. ### Fixed + - Fix `tcod.sdl.mouse.warp_in_window` function. - Fix `TypeError: '_AudioCallbackUserdata' object is not callable` when using an SDL audio device callback. [#128](https://github.com/libtcod/python-tcod/issues/128) ## [15.0.3] - 2023-05-25 + ### Deprecated -- Deprecated all libtcod color constants. Replace these with your own manually defined colors. + +- Deprecated all libtcod color constants. Replace these with your own manually defined colors. Using a color will tell you the color values of the deprecated color in the warning. -- Deprecated older scancode and keysym constants. These were replaced with the Scancode and KeySym enums. +- Deprecated older scancode and keysym constants. These were replaced with the Scancode and KeySym enums. ### Fixed + - DLL loader could fail to load `SDL2.dll` when other tcod namespace packages were installed. ## [15.0.1] - 2023-03-30 + ### Added + - Added support for `tcod.sdl` namespace packages. ### Fixed -- ``Renderer.read_pixels`` method was completely broken. + +- `Renderer.read_pixels` method was completely broken. ## [15.0.0] - 2023-01-04 + ### Changed + - Modified the letter case of window event types to match their type annotations. - This may cause regressions. Run Mypy to check for ``[comparison-overlap]`` errors. -- Mouse event attributes have been changed ``.pixel -> .position`` and ``.pixel_motion -> .motion``. + This may cause regressions. Run Mypy to check for `[comparison-overlap]` errors. +- Mouse event attributes have been changed `.pixel -> .position` and `.pixel_motion -> .motion`. - `Context.convert_event` now returns copies of events with mouse coordinates converted into tile positions. ### Deprecated + - Mouse event pixel and tile attributes have been deprecated. ## [14.0.0] - 2022-12-09 + ### Added + - Added explicit support for namespace packages. ### Changed + - Using `libtcod 1.23.1`. - Bundle `SDL 2.26.0` on Windows and MacOS. - Code Page 437: Character 0x7F is now assigned to 0x2302 (HOUSE). -- Forced all renderers to ``RENDERER_SDL2`` to fix rare graphical artifacts with OpenGL. +- Forced all renderers to `RENDERER_SDL2` to fix rare graphical artifacts with OpenGL. ### Deprecated + - The `renderer` parameter of new contexts is now deprecated. ## [13.8.1] - 2022-09-23 + ### Fixed + - `EventDispatch` was missing new event names. ## [13.8.0] - 2022-09-22 + ### Added + - Ported SDL2 joystick handing as `tcod.sdl.joystick`. - New joystick related events. ### Changed + - Using `libtcod 1.22.3`. - Bundle `SDL 2.24.0` on Windows and MacOS. ### Deprecated + - Renderers other than `tcod.RENDERER_SDL2` are now discouraged. ### Fixed + - Fixed double present bug in non-context flush functions. This was affecting performance and also caused a screen flicker whenever the global fade color was active. - Fixed the parsing of SDL 2.24.0 headers on Windows. ## [13.7.0] - 2022-08-07 + ### Added + - You can new use `SDLConsoleRender.atlas` to access the `SDLTilesetAtlas` used to create it. [#121](https://github.com/libtcod/python-tcod/issues/121) ### Fixed -- Fixed the parsing of SDL 2.0.22 headers. Specifically `SDL_FLT_EPSILON`. + +- Fixed the parsing of SDL 2.0.22 headers. Specifically `SDL_FLT_EPSILON`. ## [13.6.2] - 2022-05-02 + ### Fixed + - SDL renderers were ignoring tiles where only the background red channel was changed. ## [13.6.1] - 2022-03-29 + ### Changed + - The SDL2 renderer has had a major performance update when compiled with SDL 2.0.18. - SDL2 is now the default renderer to avoid rare issues with the OpenGL 2 renderer. ## [13.6.0] - 2022-02-19 + ### Added -- `BasicMixer` and `Channel` classes added to `tcod.sdl.audio`. These handle simple audio mixing. + +- `BasicMixer` and `Channel` classes added to `tcod.sdl.audio`. These handle simple audio mixing. - `AudioDevice.convert` added to handle simple conversions to the active devices format. - `tcod.sdl.audio.convert_audio` added to handle any other conversions needed. ## [13.5.0] - 2022-02-11 + ### Added -- `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet. + +- `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet. - `tcod.sdl.mouse`, for SDL mouse and cursor handing. - `Context.sdl_atlas`, which provides the relevant `SDLTilesetAtlas` when one is being used by the context. - Several missing features were added to `tcod.sdl.render`. - `Window.mouse_rect` added to SDL windows to set the mouse confinement area. + ### Changed + - `Texture.access` and `Texture.blend_mode` properties now return enum instances. You can still set `blend_mode` with `int` but Mypy will complain. ## [13.4.0] - 2022-02-04 + ### Added + - Adds `sdl_window` and `sdl_renderer` properties to tcod contexts. - Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback. - Adds the `tcod.sdl.video` module to handle SDL windows. - Adds the `tcod.sdl.render` module to handle SDL renderers. - Adds the `tcod.render` module which gives more control over the rendering of consoles and tilesets. + ### Fixed + - Fixed handling of non-Path PathLike parameters and filepath encodings. ## [13.3.0] - 2022-01-07 + ### Added + - New experimental renderer `tcod.context.RENDERER_XTERM`. + ### Changed + - Using `libtcod 1.20.1`. + ### Fixed + - Functions accepting `Path`-like parameters now accept the more correct `os.PathLike` type. - BDF files with blank lines no longer fail to load with an "Unknown keyword" error. ## [13.2.0] - 2021-12-24 + ### Added + - New `console` parameter in `tcod.context.new` which sets parameters from an existing Console. ### Changed + - Using `libtcod 1.20.0`. ### Fixed + - Fixed segfault when an OpenGL2 context fails to load. - Gaussian number generation no longer affects the results of unrelated RNG's. - Gaussian number generation is now reentrant and thread-safe. - Fixed potential crash in PNG image loading. ## [13.1.0] - 2021-10-22 + ### Added + - Added the `tcod.tileset.procedural_block_elements` function. ### Removed + - Python 3.6 is no longer supported. ## [13.0.0] - 2021-09-20 + ### Changed + - Console print and drawing functions now always use absolute coordinates for negative numbers. ## [12.7.3] - 2021-08-13 + ### Deprecated + - `tcod.console_is_key_pressed` was replaced with `tcod.event.get_keyboard_state`. - `tcod.console_from_file` is deprecated. - The `.asc` and `.apf` formats are no longer actively supported. ### Fixed + - Fixed the parsing of SDL 2.0.16 headers. ## [12.7.2] - 2021-07-01 + ### Fixed -- *Scancode* and *KeySym* enums no longer crash when SDL returns an unexpected value. + +- _Scancode_ and _KeySym_ enums no longer crash when SDL returns an unexpected value. ## [12.7.1] - 2021-06-30 + ### Added + - Started uploading wheels for ARM64 macOS. ## [12.7.0] - 2021-06-29 + ### Added -- *tcod.image* and *tcod.tileset* now support *pathlib*. + +- _tcod.image_ and _tcod.tileset_ now support _pathlib_. ### Fixed + - Wheels for 32-bit Windows now deploy again. ## [12.6.2] - 2021-06-15 + ### Fixed + - Git is no longer required to install from source. ## [12.6.1] - 2021-06-09 + ### Fixed + - Fixed version mismatch when building from sources. ## [12.6.0] - 2021-06-09 + ### Added -- Added the *decoration* parameter to *Console.draw_frame*. - You may use this parameter to designate custom glyphs as the frame border. + +- Added the _decoration_ parameter to _Console.draw_frame_. + You may use this parameter to designate custom glyphs as the frame border. ### Deprecated + - The handling of negative indexes given to console drawing and printing - functions will be changed to be used as absolute coordinates in the future. + functions will be changed to be used as absolute coordinates in the future. ## [12.5.1] - 2021-05-30 + ### Fixed + - The setup script should no longer fail silently when cffi is unavailable. ## [12.5.0] - 2021-05-21 + ### Changed + - `KeyboardEvent`'s '`scancode`, `sym`, and `mod` attributes now use their respective enums. ## [12.4.0] - 2021-05-21 + ### Added + - Added modernized REXPaint saving/loading functions. - - `tcod.console.load_xp` - - `tcod.console.save_xp` + - `tcod.console.load_xp` + - `tcod.console.save_xp` ### Changed + - Using `libtcod 1.18.1`. - `tcod.event.KeySym` and `tcod.event.Scancode` can now be hashed. ## [12.3.2] - 2021-05-15 + ### Changed + - Using `libtcod 1.17.1`. ### Fixed + - Fixed regression with loading PNG images. ## [12.3.1] - 2021-05-13 + ### Fixed + - Fix Windows deployment. ## [12.3.0] - 2021-05-13 + ### Added + - New keyboard enums: - - `tcod.event.KeySym` - - `tcod.event.Scancode` - - `tcod.event.Modifier` + - `tcod.event.KeySym` + - `tcod.event.Scancode` + - `tcod.event.Modifier` - New functions: - - `tcod.event.get_keyboard_state` - - `tcod.event.get_modifier_state` + - `tcod.event.get_keyboard_state` + - `tcod.event.get_modifier_state` - Added `tcod.console.rgb_graphic` and `tcod.console.rgba_graphic` dtypes. - Another name for the Console array attributes: `Console.rgb` and `Console.rgba`. ### Changed + - Using `libtcod 1.17.0`. ### Deprecated + - `Console_tiles_rgb` is being renamed to `Console.rgb`. - `Console_tiles` being renamed to `Console.rgba`. ### Fixed + - Contexts now give a more useful error when pickled. - Fixed regressions with `tcod.console_print_frame` and `Console.print_frame` - when given empty strings as the banner. + when given empty strings as the banner. ## [12.2.0] - 2021-04-09 + ### Added + - Added `tcod.noise.Algorithm` and `tcod.noise.Implementation` enums. - Added `tcod.noise.grid` helper function. ### Deprecated + - The non-enum noise implementation names have been deprecated. ### Fixed + - Indexing Noise classes now works with the FBM implementation. ## [12.1.0] - 2021-04-01 + ### Added + - Added package-level PyInstaller hook. ### Changed + - Using `libtcod 1.16.7`. - `tcod.path.dijkstra2d` now returns the output and accepts an `out` parameter. ### Deprecated -- In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given. + +- In the future `tcod.path.dijkstra2d` will no longer modify the input by default. Until then an `out` parameter must be given. ### Fixed + - Fixed crashes from loading tilesets with non-square tile sizes. - Tilesets with a size of 0 should no longer crash when used. - Prevent division by zero from recommended-console-size functions. ## [12.0.0] - 2021-03-05 + ### Added + - Now includes PyInstaller hooks within the package itself. ### Deprecated + - The Random class will now warn if the seed it's given will not used - deterministically. It will no longer accept non-integer seeds in the future. + deterministically. It will no longer accept non-integer seeds in the future. ### Changed + - Now bundles SDL 2.0.14 for MacOS. - `tcod.event` can now detect and will warn about uninitialized tile - attributes on mouse events. + attributes on mouse events. ### Removed + - Python 3.5 is no longer supported. - The `tdl` module has been dropped. ## [11.19.3] - 2021-01-07 + ### Fixed + - Some wheels had broken version metadata. ## [11.19.2] - 2020-12-30 + ### Changed + - Now bundles SDL 2.0.10 for MacOS and SDL 2.0.14 for Windows. ### Fixed + - MacOS wheels were failing to bundle dependencies for SDL2. ## [11.19.1] - 2020-12-29 + ### Fixed + - MacOS wheels failed to deploy for the previous version. ## [11.19.0] - 2020-12-29 + ### Added + - Added the important `order` parameter to `Context.new_console`. ## [11.18.3] - 2020-12-28 + ### Changed + - Now bundles SDL 2.0.14 for Windows/MacOS. ### Deprecated + - Support for Python 3.5 will be dropped. - `tcod.console_load_xp` has been deprecated, `tcod.console_from_xp` can load - these files without modifying an existing console. + these files without modifying an existing console. ### Fixed + - `tcod.console_from_xp` now has better error handling (instead of crashing.) - Can now compile with SDL 2.0.14 headers. ## [11.18.2] - 2020-12-03 + ### Fixed + - Fixed missing `tcod.FOV_SYMMETRIC_SHADOWCAST` constant. -- Fixed regression in `tcod.sys_get_current_resolution` behavior. This - function now returns the monitor resolution as was previously expected. +- Fixed regression in `tcod.sys_get_current_resolution` behavior. This + function now returns the monitor resolution as was previously expected. ## [11.18.1] - 2020-11-30 + ### Fixed + - Code points from the Private Use Area will now print correctly. ## [11.18.0] - 2020-11-13 + ### Added + - New context method `Context.new_console`. ### Changed + - Using `libtcod 1.16.0-alpha.15`. ## [11.17.0] - 2020-10-30 + ### Added + - New FOV implementation: `tcod.FOV_SYMMETRIC_SHADOWCAST`. ### Changed + - Using `libtcod 1.16.0-alpha.14`. ## [11.16.1] - 2020-10-28 + ### Deprecated + - Changed context deprecations to PendingDeprecationWarning to reduce mass - panic from tutorial followers. + panic from tutorial followers. ### Fixed + - Fixed garbled titles and crashing on some platforms. ## [11.16.0] - 2020-10-23 + ### Added + - Added `tcod.context.new` function. - Contexts now support a CLI. - You can now provide the window x,y position when making contexts. - `tcod.noise.Noise` instances can now be indexed to generate noise maps. ### Changed + - Using `libtcod 1.16.0-alpha.13`. - The OpenGL 2 renderer can now use `SDL_HINT_RENDER_SCALE_QUALITY` to - determine the tileset upscaling filter. + determine the tileset upscaling filter. - Improved performance of the FOV_BASIC algorithm. ### Deprecated + - `tcod.context.new_window` and `tcod.context.new_terminal` have been replaced - by `tcod.context.new`. + by `tcod.context.new`. ### Fixed + - Pathfinders will now work with boolean arrays. - Console blits now ignore alpha compositing which would result in division by - zero. + zero. - `tcod.console_is_key_pressed` should work even if libtcod events are ignored. - The `TCOD_RENDERER` and `TCOD_VSYNC` environment variables should work now. - `FOV_PERMISSIVE` algorithm is now reentrant. ## [11.15.3] - 2020-07-30 + ### Fixed + - `tcod.tileset.Tileset.remap`, codepoint and index were swapped. ## [11.15.2] - 2020-07-27 + ### Fixed + - `tcod.path.dijkstra2d`, fixed corrupted output with int8 arrays. ## [11.15.1] - 2020-07-26 + ### Changed + - `tcod.event.EventDispatch` now uses the absolute names for event type hints - so that IDE's can better auto-complete method overrides. + so that IDE's can better auto-complete method overrides. ### Fixed + - Fixed libtcodpy heightmap data alignment issues on non-square maps. ## [11.15.0] - 2020-06-29 + ### Added + - `tcod.path.SimpleGraph` for pathfinding on simple 2D arrays. ### Changed + - `tcod.path.CustomGraph` now accepts an `order` parameter. ## [11.14.0] - 2020-06-23 + ### Added + - New `tcod.los` module for NumPy-based line-of-sight algorithms. - Includes `tcod.los.bresenham`. + Includes `tcod.los.bresenham`. ### Deprecated + - `tcod.line_where` and `tcod.line_iter` have been deprecated. ## [11.13.6] - 2020-06-19 + ### Deprecated + - `console_init_root` and `console_set_custom_font` have been replaced by the - modern API. + modern API. - All functions which handle SDL windows without a context are deprecated. - All functions which modify a globally active tileset are deprecated. - `tcod.map.Map` is deprecated, NumPy arrays should be passed to functions - directly instead of through this class. + directly instead of through this class. ## [11.13.5] - 2020-06-15 + ### Fixed + - Install requirements will no longer try to downgrade `cffi`. ## [11.13.4] - 2020-06-15 ## [11.13.3] - 2020-06-13 + ### Fixed + - `cffi` requirement has been updated to version `1.13.0`. - The older versions raise TypeError's. + The older versions raise TypeError's. ## [11.13.2] - 2020-06-12 + ### Fixed + - SDL related errors during package installation are now more readable. ## [11.13.1] - 2020-05-30 + ### Fixed + - `tcod.event.EventDispatch`: `ev_*` methods now allow `Optional[T]` return - types. + types. ## [11.13.0] - 2020-05-22 + ### Added + - `tcod.path`: New `Pathfinder` and `CustomGraph` classes. ### Changed + - Added `edge_map` parameter to `tcod.path.dijkstra2d` and - `tcod.path.hillclimb2d`. + `tcod.path.hillclimb2d`. ### Fixed + - tcod.console_init_root` and context initializing functions were not - raising exceptions on failure. + raising exceptions on failure. ## [11.12.1] - 2020-05-02 + ### Fixed + - Prevent adding non-existent 2nd halves to potential double-wide charterers. ## [11.12.0] - 2020-04-30 + ### Added -- Added `tcod.context` module. You now have more options for making libtcod - controlled contexts. + +- Added `tcod.context` module. You now have more options for making libtcod + controlled contexts. - `tcod.tileset.load_tilesheet`: Load a simple tilesheet as a Tileset. - `Tileset.remap`: Reassign codepoints to tiles on a Tileset. - `tcod.tileset.CHARMAP_CP437`: Character mapping for `load_tilesheet`. - `tcod.tileset.CHARMAP_TCOD`: Older libtcod layout. ### Changed + - `EventDispatch.dispatch` can now return the values returned by the `ev_*` - methods. The class is now generic to support type checking these values. + methods. The class is now generic to support type checking these values. - Event mouse coordinates are now strictly int types. - Submodules are now implicitly imported. ## [11.11.4] - 2020-04-26 + ### Changed + - Using `libtcod 1.16.0-alpha.10`. ### Fixed + - Fixed characters being dropped when color codes were used. ## [11.11.3] - 2020-04-24 + ### Changed + - Using `libtcod 1.16.0-alpha.9`. ### Fixed + - `FOV_DIAMOND` and `FOV_RESTRICTIVE` algorithms are now reentrant. - [libtcod#48](https://github.com/libtcod/libtcod/pull/48) + [libtcod#48](https://github.com/libtcod/libtcod/pull/48) - The `TCOD_VSYNC` environment variable was being ignored. ## [11.11.2] - 2020-04-22 ## [11.11.1] - 2020-04-03 + ### Changed + - Using `libtcod 1.16.0-alpha.8`. ### Fixed + - Changing the active tileset now redraws tiles correctly on the next frame. ## [11.11.0] - 2020-04-02 + ### Added + - Added `Console.close` as a more obvious way to close the active window of a - root console. + root console. ### Changed + - GCC is no longer needed to compile the library on Windows. - Using `libtcod 1.16.0-alpha.7`. - `tcod.console_flush` will now accept an RGB tuple as a `clear_color`. ### Fixed + - Changing the active tileset will now properly show it on the next render. ## [11.10.0] - 2020-03-26 + ### Added + - Added `tcod.tileset.load_bdf`, you can now load BDF fonts. - `tcod.tileset.set_default` and `tcod.tileset.get_default` are now stable. ### Changed + - Using `libtcod 1.16.0-alpha.6`. ### Deprecated + - The `snap_to_integer` parameter in `tcod.console_flush` has been deprecated - since it can cause minor scaling issues which don't exist when using - `integer_scaling` instead. + since it can cause minor scaling issues which don't exist when using + `integer_scaling` instead. ## [11.9.2] - 2020-03-17 + ### Fixed + - Fixed segfault after the Tileset returned by `tcod.tileset.get_default` goes - out of scope. + out of scope. ## [11.9.1] - 2020-02-28 + ### Changed + - Using `libtcod 1.16.0-alpha.5`. - Mouse tile coordinates are now always zero before the first call to - `tcod.console_flush`. + `tcod.console_flush`. ## [11.9.0] - 2020-02-22 + ### Added + - New method `Tileset.render` renders an RGBA NumPy array from a tileset and - a console. + a console. ## [11.8.2] - 2020-02-22 + ### Fixed + - Prevent KeyError when representing unusual keyboard symbol constants. ## [11.8.1] - 2020-02-22 + ### Changed + - Using `libtcod 1.16.0-alpha.4`. ### Fixed + - Mouse tile coordinates are now correct on any resized window. ## [11.8.0] - 2020-02-21 + ### Added + - Added `tcod.console.recommended_size` for when you want to change your main - console size at runtime. + console size at runtime. - Added `Console.tiles_rgb` as a replacement for `Console.tiles2`. ### Changed + - Using `libtcod 1.16.0-alpha.3`. - Added parameters to `tcod.console_flush`, you can now manually provide a - console and adjust how it is presented. + console and adjust how it is presented. ### Deprecated + - `Console.tiles2` is deprecated in favour of `Console.tiles_rgb`. - `Console.buffer` is now deprecated in favour of `Console.tiles`, instead of - the other way around. + the other way around. ### Fixed + - Fixed keyboard state and mouse state functions losing state when events were - flushed. + flushed. ## [11.7.2] - 2020-02-16 + ### Fixed + - Fixed regression in `tcod.console_clear`. ## [11.7.1] - 2020-02-16 + ### Fixed + - Fixed regression in `Console.draw_frame`. - The wavelet noise generator now excludes -1.0f and 1.0f as return values. - Fixed console fading color regression. ## [11.7.0] - 2020-02-14 + ### Changed + - Using `libtcod 1.16.0-alpha.2`. - When a renderer fails to load it will now fallback to a different one. - The order is: OPENGL2 -> OPENGL -> SDL2. + The order is: OPENGL2 -> OPENGL -> SDL2. - The default renderer is now SDL2. - The SDL and OPENGL renderers are no longer deprecated, but they now point to - slightly different backward compatible implementations. + slightly different backward compatible implementations. ### Deprecated + - The use of `libtcod.cfg` and `terminal.png` is deprecated. ### Fixed + - `tcod.sys_update_char` now works with the newer renderers. - Fixed buffer overflow in name generator. - `tcod.image_from_console` now works with the newer renderers. - New renderers now auto-load fonts from `libtcod.cfg` or `terminal.png`. ## [11.6.0] - 2019-12-05 + ### Changed + - Console blit operations now perform per-cell alpha transparency. ## [11.5.1] - 2019-11-23 + ### Fixed + - Python 3.8 wheels failed to deploy. ## [11.5.0] - 2019-11-22 + ### Changed + - Quarter block elements are now rendered using Unicode instead of a custom - encoding. + encoding. ### Fixed + - `OPENGL` and `GLSL` renderers were not properly clearing space characters. ## [11.4.1] - 2019-10-15 + ### Added + - Uploaded Python 3.8 wheels to PyPI. ## [11.4.0] - 2019-09-20 + ### Added + - Added `__array_interface__` to the Image class. - Added `Console.draw_semigraphics` as a replacement for blit_2x functions. - `draw_semigraphics` can handle array-like objects. + `draw_semigraphics` can handle array-like objects. - `Image.from_array` class method creates an Image from an array-like object. - `tcod.image.load` loads a PNG file as an RGBA array. ### Changed + - `Console.tiles` is now named `Console.buffer`. ## [11.3.0] - 2019-09-06 + ### Added + - New attribute `Console.tiles2` is similar to `Console.tiles` but without an - alpha channel. + alpha channel. ## [11.2.2] - 2019-08-25 + ### Fixed + - Fixed a regression preventing PyInstaller distributions from loading SDL2. ## [11.2.1] - 2019-08-25 ## [11.2.0] - 2019-08-24 + ### Added + - `tcod.path.dijkstra2d`: Computes Dijkstra from an arbitrary initial state. - `tcod.path.hillclimb2d`: Returns a path from a distance array. - `tcod.path.maxarray`: Creates arrays filled with maximum finite values. ### Fixed + - Changing the tiles of an active tileset on OPENGL2 will no longer leave - temporary artifact tiles. + temporary artifact tiles. - It's now harder to accidentally import tcod's internal modules. ## [11.1.2] - 2019-08-02 + ### Changed + - Now bundles SDL 2.0.10 for Windows/MacOS. ### Fixed + - Can now parse SDL 2.0.10 headers during installation without crashing. ## [11.1.1] - 2019-08-01 + ### Deprecated + - Using an out-of-bounds index for field-of-view operations now raises a - warning, which will later become an error. + warning, which will later become an error. ### Fixed + - Changing the tiles of an active tileset will now work correctly. ## [11.1.0] - 2019-07-05 + ### Added + - You can now set the `TCOD_RENDERER` and `TCOD_VSYNC` environment variables to - force specific options to be used. - Example: ``TCOD_RENDERER=sdl2 TCOD_VSYNC=1`` + force specific options to be used. + Example: `TCOD_RENDERER=sdl2 TCOD_VSYNC=1` ### Changed + - `tcod.sys_set_renderer` now raises an exception if it fails. ### Fixed + - `tcod.console_map_ascii_code_to_font` functions will now work when called - before `tcod.console_init_root`. + before `tcod.console_init_root`. ## [11.0.2] - 2019-06-21 + ### Changed + - You no longer need OpenGL to build python-tcod. ## [11.0.1] - 2019-06-21 + ### Changed + - Better runtime checks for Windows dependencies should now give distinct - errors depending on if the issue is SDL2 or missing redistributables. + errors depending on if the issue is SDL2 or missing redistributables. ### Fixed + - Changed NumPy type hints from `np.array` to `np.ndarray` which should - resolve issues. + resolve issues. ## [11.0.0] - 2019-06-14 + ### Changed + - `tcod.map.compute_fov` now takes a 2-item tuple instead of separate `x` and - `y` parameters. This causes less confusion over how axes are aligned. + `y` parameters. This causes less confusion over how axes are aligned. ## [10.1.1] - 2019-06-02 + ### Changed + - Better string representations for `tcod.event.Event` subclasses. ### Fixed + - Fixed regressions in text alignment for non-rectangle print functions. ## [10.1.0] - 2019-05-24 + ### Added + - `tcod.console_init_root` now has an optional `vsync` parameter. ## [10.0.5] - 2019-05-17 + ### Fixed + - Fixed shader compilation issues in the OPENGL2 renderer. - Fallback fonts should fail less on Linux. ## [10.0.4] - 2019-05-17 + ### Changed + - Now depends on cffi 0.12 or later. ### Fixed + - `tcod.console_init_root` and `tcod.console_set_custom_font` will raise - exceptions instead of terminating. + exceptions instead of terminating. - Fixed issues preventing `tcod.event` from working on 32-bit Windows. ## [10.0.3] - 2019-05-10 + ### Fixed + - Corrected bounding box issues with the `Console.print_box` method. ## [10.0.2] - 2019-04-26 + ### Fixed + - Resolved Color warnings when importing tcod. - When compiling, fixed a name conflict with endianness macros on FreeBSD. ## [10.0.1] - 2019-04-19 + ### Fixed + - Fixed horizontal alignment for TrueType fonts. - Fixed taking screenshots with the older SDL renderer. ## [10.0.0] - 2019-03-29 + ### Added + - New `Console.tiles` array attribute. + ### Changed + - `Console.DTYPE` changed to add alpha to its color types. + ### Fixed + - Console printing was ignoring color codes at the beginning of a string. ## [9.3.0] - 2019-03-15 + ### Added + - The SDL2/OPENGL2 renderers can potentially use a fall-back font when none - are provided. + are provided. - New function `tcod.event.get_mouse_state`. - New function `tcod.map.compute_fov` lets you get a visibility array directly - from a transparency array. + from a transparency array. + ### Deprecated + - The following functions and classes have been deprecated. - - `tcod.Key` - - `tcod.Mouse` - - `tcod.mouse_get_status` - - `tcod.console_is_window_closed` - - `tcod.console_check_for_keypress` - - `tcod.console_wait_for_keypress` - - `tcod.console_delete` - - `tcod.sys_check_for_event` - - `tcod.sys_wait_for_event` + - `tcod.Key` + - `tcod.Mouse` + - `tcod.mouse_get_status` + - `tcod.console_is_window_closed` + - `tcod.console_check_for_keypress` + - `tcod.console_wait_for_keypress` + - `tcod.console_delete` + - `tcod.sys_check_for_event` + - `tcod.sys_wait_for_event` - The SDL, OPENGL, and GLSL renderers have been deprecated. - Many libtcodpy functions have been marked with PendingDeprecationWarning's. + ### Fixed + - To be more compatible with libtcodpy `tcod.console_init_root` will default - to the SDL render, but will raise warnings when an old renderer is used. + to the SDL render, but will raise warnings when an old renderer is used. ## [9.2.5] - 2019-03-04 + ### Fixed + - Fixed `tcod.namegen_generate_custom`. ## [9.2.4] - 2019-03-02 + ### Fixed + - The `tcod` package is has been marked as typed and will now work with MyPy. ## [9.2.3] - 2019-03-01 + ### Deprecated + - The behavior for negative indexes on the new print functions may change in - the future. + the future. - Methods and functionality preventing `tcod.Color` from behaving like a tuple - have been deprecated. + have been deprecated. ## [9.2.2] - 2019-02-26 + ### Fixed + - `Console.print_box` wasn't setting the background color by default. ## [9.2.1] - 2019-02-25 + ### Fixed + - `tcod.sys_get_char_size` fixed on the new renderers. ## [9.2.0] - 2019-02-24 + ### Added + - New `tcod.console.get_height_rect` function, which can be used to get the - height of a print call without an existing console. + height of a print call without an existing console. - New `tcod.tileset` module, with a `set_truetype_font` function. + ### Fixed + - The new print methods now handle alignment according to how they were - documented. + documented. - `SDL2` and `OPENGL2` now support screenshots. - Windows and MacOS builds now restrict exported SDL2 symbols to only - SDL 2.0.5; This will avoid hard to debug import errors when the wrong - version of SDL is dynamically linked. + SDL 2.0.5; This will avoid hard to debug import errors when the wrong + version of SDL is dynamically linked. - The root console now starts with a white foreground. ## [9.1.0] - 2019-02-23 + ### Added + - Added the `tcod.random.MULTIPLY_WITH_CARRY` constant. + ### Changed + - The overhead for warnings has been reduced when running Python with the - optimize `-O` flag. + optimize `-O` flag. - `tcod.random.Random` now provides a default algorithm. ## [9.0.0] - 2019-02-17 + ### Changed + - New console methods now default to an `fg` and `bg` of None instead of - white-on-black. + white-on-black. ## [8.5.0] - 2019-02-15 + ### Added + - `tcod.console.Console` now supports `str` and `repr`. - Added new Console methods which are independent from the console defaults. - You can now give an array when initializing a `tcod.console.Console` - instance. + instance. - `Console.clear` can now take `ch`, `fg`, and `bg` parameters. + ### Changed + - Updated libtcod to 1.10.6 - Printing generates more compact layouts. + ### Deprecated + - Most libtcodpy console functions have been replaced by the tcod.console - module. -- Deprecated the `set_key_color` functions. You can pass key colors to - `Console.blit` instead. + module. +- Deprecated the `set_key_color` functions. You can pass key colors to + `Console.blit` instead. - `Console.clear` should be given the colors to clear with as parameters, - rather than by using `default_fg` or `default_bg`. + rather than by using `default_fg` or `default_bg`. - Most functions which depend on console default values have been deprecated. - The new deprecation warnings will give details on how to make default values - explicit. + The new deprecation warnings will give details on how to make default values + explicit. + ### Fixed + - `tcod.console.Console.blit` was ignoring the key color set by - `Console.set_key_color`. + `Console.set_key_color`. - The `SDL2` and `OPENGL2` renders can now large numbers of tiles. ## [8.4.3] - 2019-02-06 + ### Changed + - Updated libtcod to 1.10.5 - The SDL2/OPENGL2 renderers will now auto-detect a custom fonts key-color. ## [8.4.2] - 2019-02-05 + ### Deprecated + - The tdl module has been deprecated. - The libtcodpy parser functions have been deprecated. + ### Fixed + - `tcod.image_is_pixel_transparent` and `tcod.image_get_alpha` now return - values. + values. - `Console.print_frame` was clearing tiles outside if its bounds. - The `FONT_LAYOUT_CP437` layout was incorrect. ## [8.4.1] - 2019-02-01 + ### Fixed + - Window event types were not upper-case. - Fixed regression where libtcodpy mouse wheel events unset mouse coordinates. ## [8.4.0] - 2019-01-31 + ### Added + - Added tcod.event module, based off of the sdlevent.py shim. + ### Changed + - Updated libtcod to 1.10.3 + ### Fixed + - Fixed libtcodpy `struct_add_value_list` function. - Use correct math for tile-based delta in mouse events. - New renderers now support tile-based mouse coordinates. - SDL2 renderer will now properly refresh after the window is resized. ## [8.3.2] - 2018-12-28 + ### Fixed + - Fixed rare access violations for some functions which took strings as - parameters, such as `tcod.console_init_root`. + parameters, such as `tcod.console_init_root`. ## [8.3.1] - 2018-12-28 + ### Fixed + - libtcodpy key and mouse functions will no longer accept the wrong types. - The `new_struct` method was not being called for libtcodpy's custom parsers. ## [8.3.0] - 2018-12-08 + ### Added + - Added BSP traversal methods in tcod.bsp for parity with libtcodpy. + ### Deprecated + - Already deprecated bsp functions are now even more deprecated. ## [8.2.0] - 2018-11-27 + ### Added + - New layout `tcod.FONT_LAYOUT_CP437`. + ### Changed + - Updated libtcod to 1.10.2 - `tcod.console_print_frame` and `Console.print_frame` now support Unicode - strings. + strings. + ### Deprecated + - Deprecated using bytes strings for all printing functions. + ### Fixed + - Console objects are now initialized with spaces. This fixes some blit - operations. + operations. - Unicode code-points above U+FFFF will now work on all platforms. ## [8.1.1] - 2018-11-16 + ### Fixed + - Printing a frame with an empty string no longer displays a title bar. ## [8.1.0] - 2018-11-15 + ### Changed + - Heightmap functions now support 'F_CONTIGUOUS' arrays. - `tcod.heightmap_new` now has an `order` parameter. - Updated SDL to 2.0.9 + ### Deprecated + - Deprecated heightmap functions which sample noise grids, this can be done - using the `Noise.sample_ogrid` method. + using the `Noise.sample_ogrid` method. ## [8.0.0] - 2018-11-02 + ### Changed + - The default renderer can now be anything if not set manually. - Better error message for when a font file isn't found. ## [7.0.1] - 2018-10-27 + ### Fixed + - Building from source was failing because `console_2tris.glsl*` was missing - from source distributions. + from source distributions. ## [7.0.0] - 2018-10-25 + ### Added + - New `RENDERER_SDL2` and `RENDERER_OPENGL2` renderers. + ### Changed + - Updated libtcod to 1.9.0 - Now requires SDL 2.0.5, which is not trivially installable on - Ubuntu 16.04 LTS. + Ubuntu 16.04 LTS. + ### Removed + - Dropped support for Python versions before 3.5 - Dropped support for MacOS versions before 10.9 Mavericks. ## [6.0.7] - 2018-10-24 + ### Fixed + - The root console no longer loses track of buffers and console defaults on a - renderer change. + renderer change. ## [6.0.6] - 2018-10-01 + ### Fixed + - Replaced missing wheels for older and 32-bit versions of MacOS. ## [6.0.5] - 2018-09-28 + ### Fixed + - Resolved CDefError error during source installs. ## [6.0.4] - 2018-09-11 + ### Fixed + - tcod.Key right-hand modifiers are now set independently at initialization, - instead of mirroring the left-hand modifier value. + instead of mirroring the left-hand modifier value. ## [6.0.3] - 2018-09-05 + ### Fixed + - tcod.Key and tcod.Mouse no longer ignore initiation parameters. ## [6.0.2] - 2018-08-28 + ### Fixed + - Fixed color constants missing at build-time. ## [6.0.1] - 2018-08-24 + ### Fixed + - Source distributions were missing C++ source files. ## [6.0.0] - 2018-08-23 + ### Changed + - Project renamed to tcod on PyPI. + ### Deprecated + - Passing bytes strings to libtcodpy print functions is deprecated. + ### Fixed + - Fixed libtcodpy print functions not accepting bytes strings. - libtcod constants are now generated at build-time fixing static analysis - tools. + tools. ## [5.0.1] - 2018-07-08 + ### Fixed + - tdl.event no longer crashes with StopIteration on Python 3.7 ## [5.0.0] - 2018-07-05 + ### Changed + - tcod.path: all classes now use `shape` instead of `width` and `height`. - tcod.path now respects NumPy array shape, instead of assuming that arrays - need to be transposed from C memory order. From now on `x` and `y` mean - 1st and 2nd axis. This doesn't affect non-NumPy code. + need to be transposed from C memory order. From now on `x` and `y` mean + 1st and 2nd axis. This doesn't affect non-NumPy code. - tcod.path now has full support of non-contiguous memory. ## [4.6.1] - 2018-06-30 + ### Added + - New function `tcod.line_where` for indexing NumPy arrays using a Bresenham - line. + line. + ### Deprecated + - Python 2.7 support will be dropped in the near future. ## [4.5.2] - 2018-06-29 + ### Added + - New wheels for Python3.7 on Windows. + ### Fixed + - Arrays from `tcod.heightmap_new` are now properly zeroed out. ## [4.5.1] - 2018-06-23 + ### Deprecated + - Deprecated all libtcodpy map functions. + ### Fixed + - `tcod.map_copy` could break the `tcod.map.Map` class. - `tcod.map_clear` `transparent` and `walkable` parameters were reversed. - When multiple SDL2 headers were installed, the wrong ones would be used when - the library is built. + the library is built. - Fails to build via pip unless Numpy is installed first. ## [4.5.0] - 2018-06-12 + ### Changed + - Updated libtcod to v1.7.0 - Updated SDL to v2.0.8 - Error messages when failing to create an SDL window should be a less vague. - You no longer need to initialize libtcod before you can print to an - off-screen console. + off-screen console. + ### Fixed + - Avoid crashes if the root console has a character code higher than expected. + ### Removed + - No more debug output when loading fonts. ## [4.4.0] - 2018-05-02 + ### Added -- Added the libtcodpy module as an alias for tcod. Actual use of it is - deprecated, it exists primarily for backward compatibility. + +- Added the libtcodpy module as an alias for tcod. Actual use of it is + deprecated, it exists primarily for backward compatibility. - Adding missing libtcodpy functions `console_has_mouse_focus` and - `console_is_active`. + `console_is_active`. + ### Changed + - Updated libtcod to v1.6.6 ## [4.3.2] - 2018-03-18 + ### Deprecated + - Deprecated the use of falsy console parameters with libtcodpy functions. + ### Fixed + - Fixed libtcodpy image functions not supporting falsy console parameters. - Fixed tdl `Window.get_char` method. (Kaczor2704) ## [4.3.1] - 2018-03-07 + ### Fixed + - Fixed cffi.api.FFIError "unsupported expression: expected a simple numeric - constant" error when building on platforms with an older cffi module and - newer SDL headers. + constant" error when building on platforms with an older cffi module and + newer SDL headers. - tcod/tdl Map and Console objects were not saving stride data when pickled. ## [4.3.0] - 2018-02-01 + ### Added + - You can now set the numpy memory order on tcod.console.Console, - tcod.map.Map, and tdl.map.Map objects well as from the - tcod.console_init_root function. + tcod.map.Map, and tdl.map.Map objects well as from the + tcod.console_init_root function. + ### Changed + - The `console_init_root` `title` parameter is now optional. + ### Fixed + - OpenGL renderer alpha blending is now consistent with all other render - modes. + modes. ## [4.2.3] - 2018-01-06 + ### Fixed + - Fixed setup.py regression that could prevent building outside of the git - repository. + repository. ## [4.2.2] - 2018-01-06 + ### Fixed + - The Windows dynamic linker will now prefer the bundled version of SDL. - This fixes: - "ImportError: DLL load failed: The specified procedure could not be found." + This fixes: + "ImportError: DLL load failed: The specified procedure could not be found." - `key.c` is no longer set when `key.vk == KEY_TEXT`, this fixes a regression - which was causing events to be heard twice in the libtcod/Python tutorial. + which was causing events to be heard twice in the libtcod/Python tutorial. ## [4.2.0] - 2018-01-02 + ### Changed + - Updated libtcod backend to v1.6.4 - Updated SDL to v2.0.7 for Windows/MacOS. + ### Removed + - Source distributions no longer include tests, examples, or fonts. - [Find these on GitHub.](https://github.com/libtcod/python-tcod) + [Find these on GitHub.](https://github.com/libtcod/python-tcod) + ### Fixed + - Fixed "final link failed: Nonrepresentable section on output" error - when compiling for Linux. + when compiling for Linux. - `tcod.console_init_root` defaults to the SDL renderer, other renderers - cause issues with mouse movement events. + cause issues with mouse movement events. ## [4.1.1] - 2017-11-02 + ### Fixed + - Fixed `ConsoleBuffer.blit` regression. - Console defaults corrected, the root console's blend mode and alignment is - the default value for newly made Console's. + the default value for newly made Console's. - You can give a byte string as a filename to load parsers. ## [4.1.0] - 2017-07-19 + ### Added + - tdl Map class can now be pickled. + ### Changed + - Added protection to the `transparent`, `walkable`, and `fov` - attributes in tcod and tdl Map classes, to prevent them from being - accidentally overridden. + attributes in tcod and tdl Map classes, to prevent them from being + accidentally overridden. - tcod and tdl Map classes now use numpy arrays as their attributes. ## [4.0.1] - 2017-07-12 + ### Fixed + - tdl: Fixed NameError in `set_fps`. ## [4.0.0] - 2017-07-08 + ### Changed + - tcod.bsp: `BSP.split_recursive` parameter `random` is now `seed`. - tcod.console: `Console.blit` parameters have been rearranged. - Most of the parameters are now optional. + Most of the parameters are now optional. - tcod.noise: `Noise.__init__` parameter `rand` is now named `seed`. - tdl: Changed `set_fps` parameter name to `fps`. + ### Fixed + - tcod.bsp: Corrected spelling of max_vertical_ratio. ## [3.2.0] - 2017-07-04 + ### Changed + - Merged libtcod-cffi dependency with TDL. + ### Fixed + - Fixed boolean related crashes with Key 'text' events. -- tdl.noise: Fixed crash when given a negative seed. As well as cases - where an instance could lose its seed being pickled. +- tdl.noise: Fixed crash when given a negative seed. As well as cases + where an instance could lose its seed being pickled. ## [3.1.0] - 2017-05-28 + ### Added + - You can now pass tdl Console instances as parameters to libtcod-cffi - functions expecting a tcod Console. + functions expecting a tcod Console. + ### Changed + - Dependencies updated: `libtcod-cffi>=2.5.0,<3` - The `Console.tcod_console` attribute is being renamed to - `Console.console_c`. + `Console.console_c`. + ### Deprecated + - The tdl.noise and tdl.map modules will be deprecated in the future. + ### Fixed + - Resolved crash-on-exit issues for Windows platforms. ## [3.0.2] - 2017-04-13 + ### Changed + - Dependencies updated: `libtcod-cffi>=2.4.3,<3` - You can now create Console instances before a call to `tdl.init`. + ### Removed + - Dropped support for Python 3.3 + ### Fixed + - Resolved issues with MacOS builds. - 'OpenGL' and 'GLSL' renderers work again. ## [3.0.1] - 2017-03-22 + ### Changed + - `KeyEvent`'s with `text` now have all their modifier keys set to False. + ### Fixed + - Undefined behavior in text events caused crashes on 32-bit builds. ## [3.0.0] - 2017-03-21 + ### Added + - `KeyEvent` supports libtcod text and meta keys. + ### Changed + - `KeyEvent` parameters have been moved. - This version requires `libtcod-cffi>=2.3.0`. + ### Deprecated + - `KeyEvent` camel capped attribute names are deprecated. + ### Fixed + - Crashes with key-codes undefined by libtcod. - `tdl.map` typedef issues with libtcod-cffi. - ## [2.0.1] - 2017-02-22 + ### Fixed + - `tdl.init` renderer was defaulted to OpenGL which is not supported in the - current version of libtcod. + current version of libtcod. ## [2.0.0] - 2017-02-15 + ### Changed + - Dependencies updated, tdl now requires libtcod-cffi 2.x.x - Some event behaviors have changed with SDL2, event keys might be different - than what you expect. + than what you expect. + ### Removed + - Key repeat functions were removed from SDL2. - `set_key_repeat` is now stubbed, and does nothing. + `set_key_repeat` is now stubbed, and does nothing. ## [1.6.0] - 2016-11-18 + - Console.blit methods can now take fg_alpha and bg_alpha parameters. ## [1.5.3] - 2016-06-04 + - set_font no longer crashes when loading a file without the implied font size in its name ## [1.5.2] - 2016-03-11 + - Fixed non-square Map instances ## [1.5.1] - 2015-12-20 + - Fixed errors with Unicode and non-Unicode literals on Python 2 - Fixed attribute error in compute_fov ## [1.5.0] - 2015-07-13 + - python-tdl distributions are now universal builds - New Map class - map.bresenham now returns a list - This release will require libtcod-cffi v0.2.3 or later ## [1.4.0] - 2015-06-22 + - The DLL's have been moved into another library which you can find at https://github.com/HexDecimal/libtcod-cffi You can use this library to have some raw access to libtcod if you want. Plus it can be used alongside TDL. - The libtcod console objects in Console instances have been made public. -- Added tdl.event.wait function. This function can called with a timeout and +- Added tdl.event.wait function. This function can called with a timeout and can automatically call tdl.flush. ## [1.3.1] - 2015-06-19 + - Fixed pathfinding regressions. ## [1.3.0] - 2015-06-19 -- Updated backend to use python-cffi instead of ctypes. This gives decent + +- Updated backend to use python-cffi instead of ctypes. This gives decent boost to speed in CPython and a drastic to boost in speed in PyPy. ## [1.2.0] - 2015-06-06 -- The set_colors method now changes the default colors used by the draw_* - methods. You can use Python's Ellipsis to explicitly select default colors + +- The set*colors method now changes the default colors used by the draw*\* + methods. You can use Python's Ellipsis to explicitly select default colors this way. - Functions and Methods renamed to match Python's style-guide PEP 8, the old function names still exist and are depreciated. - The fgcolor and bgcolor parameters have been shortened to fg and bg. ## [1.1.7] - 2015-03-19 + - Noise generator now seeds properly. - The OS event queue will now be handled during a call to tdl.flush. This prevents a common newbie programmer hang where events are handled @@ -1296,11 +1792,13 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Fixed a major bug that would cause a crash in later versions of Python 3 ## [1.1.6] - 2014-06-27 + - Fixed a race condition when importing on some platforms. - Fixed a type issue with quickFOV on Linux. - Added a bresenham function to the tdl.map module. ## [1.1.5] - 2013-11-10 + - A for loop can iterate over all coordinates of a Console. - drawStr can be configured to scroll or raise an error. - You can now configure or disable key repeating with tdl.event.setKeyRepeat @@ -1308,6 +1806,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - setColors method fixed. ## [1.1.4] - 2013-03-06 + - Merged the Typewriter and MetaConsole classes, You now have a virtual cursor with Console and Window objects. - Fixed the clear method on the Window class. @@ -1318,11 +1817,13 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Fixed event.keyWait, and now converts window closed events into Alt+F4. ## [1.1.3] - 2012-12-17 + - Some of the setFont parameters were incorrectly labeled and documented. - setFont can auto-detect tilesets if the font sizes are in the filenames. - Added some X11 unicode tilesets, including Unifont. ## [1.1.2] - 2012-12-13 + - Window title now defaults to the running scripts filename. - Fixed incorrect deltaTime for App.update - App will no longer call tdl.flush on its own, you'll need to call this @@ -1331,6 +1832,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - clear method now defaults to black on black. ## [1.1.1] - 2012-12-05 + - Map submodule added with AStar class and quickFOV function. - New Typewriter class. - Most console functions can use Python-style negative indexes now. @@ -1338,6 +1840,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Rectangle geometry is less strict. ## [1.1.0] - 2012-10-04 + - KeyEvent.keyname is now KeyEvent.key - MouseButtonEvent.button now behaves like KeyEvent.keyname does. - event.App class added. @@ -1345,20 +1848,24 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - KeyEvent.ctrl is now KeyEvent.control ## [1.0.8] - 2010-04-07 + - No longer works in Python 2.5 but now works in 3.x and has been partly tested. - Many bug fixes. ## [1.0.5] - 2010-04-06 + - Got rid of setuptools dependency, this will make it much more compatible with Python 3.x - Fixed a typo with the MacOS library import. ## [1.0.4] - 2010-04-06 -- All constant colors (C_*) have been removed, they may be put back in later. + +- All constant colors (C\_\*) have been removed, they may be put back in later. - Made some type assertion failures show the value they received to help in - general debugging. Still working on it. + general debugging. Still working on it. - Added MacOS and 64-bit Linux support. ## [1.0.0] - 2009-01-31 + - First public release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4f7d5ea..846d492c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ pre-commit install ## Building python-tcod To work with the tcod source, your environment must be set up to build -Python C extensions. You'll also need `cpp` installed for +Python C extensions. You'll also need `cpp` installed for use with pycparser. ### Windows diff --git a/examples/README.md b/examples/README.md index 0594efe1..8f13fa79 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ This directory contains a few example scripts for using python-tcod. -`samples_tcod.py` is the mail example which uses most of the newer API. This +`samples_tcod.py` is the mail example which uses most of the newer API. This can be compared to `samples_libtcodpy.py` which mostly uses deprecated functions from the old API. @@ -8,5 +8,5 @@ Examples in the `distribution/` folder show how to distribute projects made using python-tcod. Examples in the `experimental/` folder show off features that might later be -added the python-tcod API. You can use those features by copying those modules +added the python-tcod API. You can use those features by copying those modules into your own project. diff --git a/examples/termbox/README.md b/examples/termbox/README.md index 3ec77f56..36f13c20 100644 --- a/examples/termbox/README.md +++ b/examples/termbox/README.md @@ -4,7 +4,6 @@ The code here are modified files from [termbox repository](https://github.com/nsf/termbox/), so please consult it for the license and other info. - The code consists of two part - `termbox.py` module with API, translation of official binding form the description below into `tld`: @@ -14,7 +13,6 @@ And the example `termboxtest.py` which is copied verbatim from: https://github.com/nsf/termbox/blob/b20c0a11/test_termboxmodule.py - ### API Mapping Notes Notes taken while mapping the Termbox class: @@ -41,19 +39,19 @@ Notes taken while mapping the Termbox class: - init... - tdl doesn't allow to resize window (or rather libtcod) - tb works in existing terminal window and queries it rather than making own + init... + tdl doesn't allow to resize window (or rather libtcod) + tb works in existing terminal window and queries it rather than making own - colors... - tdl uses RGB values - tb uses it own constants + colors... + tdl uses RGB values + tb uses it own constants - event... - tb returns event one by one - tdl return an event iterator + event... + tb returns event one by one + tdl return an event iterator - tb Event tdl Event - .type .type - EVENT_KEY KEYDOWN + tb Event tdl Event + .type .type + EVENT_KEY KEYDOWN From 1c8064cc70b5eb73fe93fdf8e753f5800c88866c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 21:15:24 -0800 Subject: [PATCH 0874/1101] Remove outdated version block --- tcod/sdl/_internal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 28b47460..58b7b030 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -57,8 +57,6 @@ def __exit__( ) -> bool: if exc_type is None: return False - if _sys.version_info < (3, 8): - return False _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) return True From 70ec2cc03fe8ec70f7bf14b9eff4c2acefda01ff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 21:18:43 -0800 Subject: [PATCH 0875/1101] Add Python 3.12 to supported versions Fix string type in pyproject.toml --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94f1ddc2..b4c0490a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", @@ -106,7 +107,7 @@ filterwarnings = [ [tool.mypy] files = ["."] -python_version = 3.9 +python_version = "3.9" warn_unused_configs = true show_error_codes = true disallow_subclassing_any = true From 392b0ce5f5eaf0dfad47d272b5202d95d8d4c44c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 22:20:16 -0800 Subject: [PATCH 0876/1101] Refactoring pass, fix or suppress warnings Fix critical issues with samples_tcod.py --- .vscode/settings.json | 3 +++ examples/samples_tcod.py | 50 ++++++++++++++++++++-------------------- tcod/_internal.py | 20 ++++++++-------- tcod/bsp.py | 4 ++-- tcod/console.py | 32 ++++++++++++------------- tcod/context.py | 22 +++++++++--------- tcod/image.py | 10 ++++---- tcod/libtcodpy.py | 37 ++++++++++++++--------------- tcod/map.py | 6 ++--- tcod/noise.py | 10 ++++---- tcod/path.py | 45 ++++++++++++++++++------------------ tcod/random.py | 6 ++--- tcod/render.py | 2 +- tcod/sdl/_internal.py | 6 ++--- tcod/sdl/audio.py | 8 +++---- tcod/sdl/joystick.py | 4 ++-- tcod/sdl/mouse.py | 4 ++-- tcod/sdl/render.py | 42 ++++++++++++++++----------------- tcod/sdl/sys.py | 3 +-- tcod/sdl/video.py | 10 ++++---- tcod/tileset.py | 6 ++--- tests/test_console.py | 2 +- tests/test_libtcodpy.py | 4 ++-- tests/test_tcod.py | 4 ++-- 24 files changed, 170 insertions(+), 170 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 67f05c37..1ea25a26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "ALTERASE", "arange", "ARCHS", + "arctan", "asarray", "ascontiguousarray", "astar", @@ -190,6 +191,7 @@ "intersphinx", "isinstance", "isort", + "issubdtype", "itemsize", "itleref", "ivar", @@ -338,6 +340,7 @@ "pypiwin", "pypy", "pytest", + "PYTHONHASHSEED", "PYTHONOPTIMIZE", "Pyup", "quickstart", diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 7f65a817..197a1bc9 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -118,7 +118,7 @@ def on_draw(self) -> None: self.slide_corner_colors() self.interpolate_corner_colors() self.darken_background_characters() - self.randomize_sample_conole() + self.randomize_sample_console() self.print_banner() def slide_corner_colors(self) -> None: @@ -143,7 +143,7 @@ def darken_background_characters(self) -> None: sample_console.fg[:] = sample_console.bg[:] sample_console.fg[:] //= 2 - def randomize_sample_conole(self) -> None: + def randomize_sample_console(self) -> None: # randomize sample console characters sample_console.ch[:] = np.random.randint( low=ord("a"), @@ -406,7 +406,7 @@ def on_draw(self) -> None: rect_h = 13 if self.implementation == tcod.noise.Implementation.SIMPLE: rect_h = 10 - sample_console.draw_semigraphics(self.img) + sample_console.draw_semigraphics(np.asarray(self.img)) sample_console.draw_rect( 2, 2, @@ -669,7 +669,7 @@ def __init__(self) -> None: self.using_astar = True self.recalculate = False self.busy = 0.0 - self.oldchar = " " + self.old_char = " " self.map = tcod.map.Map(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) for y in range(SAMPLE_SCREEN_HEIGHT): @@ -778,33 +778,33 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.i and self.dy > 0: # destination move north - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) self.dy -= 1 - self.oldchar = sample_console.ch[self.dx, self.dy] + self.old_char = sample_console.ch[self.dx, self.dy] libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.k and self.dy < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) self.dy += 1 - self.oldchar = sample_console.ch[self.dx, self.dy] + self.old_char = sample_console.ch[self.dx, self.dy] libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.j and self.dx > 0: # destination move west - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) self.dx -= 1 - self.oldchar = sample_console.ch[self.dx, self.dy] + self.old_char = sample_console.ch[self.dx, self.dy] libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True elif event.sym == tcod.event.KeySym.l and self.dx < SAMPLE_SCREEN_WIDTH - 1: # destination move east - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) self.dx += 1 - self.oldchar = sample_console.ch[self.dx, self.dy] + self.old_char = sample_console.ch[self.dx, self.dy] libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True @@ -822,10 +822,10 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my): - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.oldchar, libtcodpy.BKGND_NONE) + libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) self.dx = mx self.dy = my - self.oldchar = sample_console.ch[self.dx, self.dy] + self.old_char = sample_console.ch[self.dx, self.dy] libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) if SAMPLE_MAP[self.dx, self.dy] == " ": self.recalculate = True @@ -1139,7 +1139,7 @@ class NameGeneratorSample(Sample): def __init__(self) -> None: self.name = "Name generator" - self.curset = 0 + self.current_set = 0 self.delay = 0.0 self.names: list[str] = [] self.sets: list[str] = [] @@ -1159,7 +1159,7 @@ def on_draw(self) -> None: sample_console.print( 1, 1, - "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.curset], + "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.current_set], fg=WHITE, bg=None, ) @@ -1175,18 +1175,18 @@ def on_draw(self) -> None: self.delay += frame_length[-1] if self.delay > 0.5: self.delay -= 0.5 - self.names.append(libtcodpy.namegen_generate(self.sets[self.curset])) + self.names.append(libtcodpy.namegen_generate(self.sets[self.current_set])) def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.EQUALS: - self.curset += 1 + self.current_set += 1 self.names.append("======") elif event.sym == tcod.event.KeySym.MINUS: - self.curset -= 1 + self.current_set -= 1 self.names.append("======") else: super().ev_keydown(event) - self.curset %= len(self.sets) + self.current_set %= len(self.sets) ############################################# @@ -1294,9 +1294,9 @@ def on_draw(self) -> None: for v in range(RES_V - int_t, RES_V): for u in range(RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u, v] = tcod.noise_get_fbm(noise2d, [u / float(RES_U), tex_v], 32.0) + tcod.noise_get_fbm( - noise2d, [1 - u / float(RES_U), tex_v], 32.0 - ) + texture[u, v] = libtcodpy.noise_get_fbm( + noise2d, [u / float(RES_U), tex_v], 32.0 + ) + libtcodpy.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0) # squared distance from center, # clipped to sensible minimum and maximum values @@ -1324,9 +1324,9 @@ def on_draw(self) -> None: y = random.uniform(-0.5, 0.5) strength = random.uniform(MIN_LIGHT_STRENGTH, 1.0) - color = tcod.Color(0, 0, 0) # create bright colors with random hue + color = libtcodpy.Color(0, 0, 0) # create bright colors with random hue hue = random.uniform(0, 360) - tcod.color_set_hsv(color, hue, 0.5, strength) + libtcodpy.color_set_hsv(color, hue, 0.5, strength) self.lights.append(Light(x, y, TEX_STRETCH, color.r, color.g, color.b, strength)) # eliminate lights that are going to be out of view diff --git a/tcod/_internal.py b/tcod/_internal.py index 1bf5e211..ef6305bf 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -7,7 +7,7 @@ import warnings from pathlib import Path from types import TracebackType -from typing import Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar, cast +from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar, cast import numpy as np from numpy.typing import ArrayLike, NDArray @@ -15,6 +15,9 @@ from tcod.cffi import ffi, lib +if TYPE_CHECKING: + import tcod.image + FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) T = TypeVar("T") @@ -198,18 +201,17 @@ def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 def __hash__(self) -> int: return hash(self.cdata) - def __eq__(self, other: Any) -> Any: - try: - return self.cdata == other.cdata - except AttributeError: + def __eq__(self, other: object) -> bool: + if not isinstance(other, _CDataWrapper): return NotImplemented + return bool(self.cdata == other.cdata) - def __getattr__(self, attr: str) -> Any: + def __getattr__(self, attr: str) -> Any: # noqa: ANN401 if "cdata" in self.__dict__: return getattr(self.__dict__["cdata"], attr) raise AttributeError(attr) - def __setattr__(self, attr: str, value: Any) -> None: + def __setattr__(self, attr: str, value: Any) -> None: # noqa: ANN401 if hasattr(self, "cdata") and hasattr(self.cdata, attr): setattr(self.cdata, attr, value) else: @@ -240,7 +242,7 @@ def __init__(self, array: ArrayLike) -> None: """Initialize an image from the given array. May copy or reference the array.""" self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape - if depth != 3: + if depth != 3: # noqa: PLR2004 msg = f"Array must have RGB channels. Shape is: {self._array.shape!r}" raise TypeError(msg) self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) @@ -265,7 +267,7 @@ def __init__(self, array: ArrayLike) -> None: ) -def _as_image(image: Any) -> TempImage: +def _as_image(image: ArrayLike | tcod.image.Image) -> TempImage | tcod.image.Image: """Convert this input into an Image-like object.""" if hasattr(image, "image_c"): return image # type: ignore diff --git a/tcod/bsp.py b/tcod/bsp.py index a53b8f67..cfb6c021 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -88,7 +88,7 @@ def h(self) -> int: # noqa: D102 def h(self, value: int) -> None: self.height = value - def _as_cdata(self) -> Any: + def _as_cdata(self) -> Any: # noqa: ANN401 cdata = ffi.gc( lib.TCOD_bsp_new_with_size(self.x, self.y, self.width, self.height), lib.TCOD_bsp_delete, @@ -115,7 +115,7 @@ def __repr__(self) -> str: status, ) - def _unpack_bsp_tree(self, cdata: Any) -> None: + def _unpack_bsp_tree(self, cdata: Any) -> None: # noqa: ANN401 self.x = cdata.x self.y = cdata.y self.width = cdata.w diff --git a/tcod/console.py b/tcod/console.py index af0b3324..3512c893 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -12,7 +12,7 @@ from typing import Any, Iterable import numpy as np -from numpy.typing import NDArray +from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod._internal @@ -168,7 +168,7 @@ def _get_root(cls, order: Literal["C", "F"] | None = None) -> Console: This function will also update an already active root console. """ - global _root_console + global _root_console # noqa: PLW0603 if _root_console is None: _root_console = object.__new__(cls) self: Console = _root_console @@ -180,7 +180,7 @@ def _get_root(cls, order: Literal["C", "F"] | None = None) -> Console: def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: """Setup numpy arrays over libtcod data buffers.""" - global _root_console + global _root_console # noqa: PLW0603 self._key_color = None if self.console_c == ffi.NULL: _root_console = self @@ -517,7 +517,7 @@ def __deprecate_defaults( # noqa: C901, PLR0912 stacklevel=3, ) - def print_( + def print_( # noqa: PLR0913 self, x: int, y: int, @@ -544,7 +544,7 @@ def print_( alignment = self.default_alignment if alignment is None else alignment lib.TCOD_console_printf_ex(self.console_c, x, y, bg_blend, alignment, _fmt(string)) - def print_rect( + def print_rect( # noqa: PLR0913 self, x: int, y: int, @@ -594,7 +594,7 @@ def print_rect( ) ) - def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: + def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: # noqa: PLR0913 """Return the height of this text word-wrapped into this rectangle. Args: @@ -610,7 +610,7 @@ def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) string_ = string.encode("utf-8") return int(lib.TCOD_console_get_height_rect_n(self.console_c, x, y, width, height, len(string_), string_)) - def rect( + def rect( # noqa: PLR0913 self, x: int, y: int, @@ -696,7 +696,7 @@ def vline( self.__deprecate_defaults("draw_rect", bg_blend) lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) - def print_frame( + def print_frame( # noqa: PLR0913 self, x: int, y: int, @@ -738,7 +738,7 @@ def print_frame( string = _fmt(string) if string else ffi.NULL _check(lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, clear, bg_blend, string)) - def blit( + def blit( # noqa: PLR0913 self, dest: Console, dest_x: int = 0, @@ -871,7 +871,7 @@ def close(self) -> None: raise NotImplementedError(msg) lib.TCOD_console_delete(self.console_c) - def __exit__(self, *args: Any) -> None: + def __exit__(self, *_: object) -> None: """Close the graphical window on exit. Some tcod functions may have undefined behavior after this point. @@ -931,7 +931,7 @@ def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" return "<%s>" % "\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) - def print( + def print( # noqa: PLR0913 self, x: int, y: int, @@ -980,7 +980,7 @@ def print( alignment, ) - def print_box( + def print_box( # noqa: PLR0913 self, x: int, y: int, @@ -1039,7 +1039,7 @@ def print_box( ) ) - def draw_frame( + def draw_frame( # noqa: PLR0913 self, x: int, y: int, @@ -1138,7 +1138,7 @@ def draw_frame( ) return decoration_ = [ord(c) for c in decoration] if isinstance(decoration, str) else decoration - if len(decoration_) != 9: + if len(decoration_) != 9: # noqa: PLR2004 msg = f"Decoration must have a length of 9 (len(decoration) is {len(decoration_)}.)" raise TypeError(msg) _check( @@ -1156,7 +1156,7 @@ def draw_frame( ) ) - def draw_rect( + def draw_rect( # noqa: PLR0913 self, x: int, y: int, @@ -1204,7 +1204,7 @@ def draw_rect( bg_blend, ) - def draw_semigraphics(self, pixels: Any, x: int = 0, y: int = 0) -> None: + def draw_semigraphics(self, pixels: ArrayLike | tcod.image.Image, x: int = 0, y: int = 0) -> None: """Draw a block of 2x2 semi-graphics into this console. `pixels` is an Image or an array-like object. It will be down-sampled diff --git a/tcod/context.py b/tcod/context.py index 4b2d33e3..8d5e5259 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -124,12 +124,12 @@ """ -def _handle_tileset(tileset: tcod.tileset.Tileset | None) -> Any: +def _handle_tileset(tileset: tcod.tileset.Tileset | None) -> Any: # noqa: ANN401 """Get the TCOD_Tileset pointer from a Tileset or return a NULL pointer.""" return tileset._tileset_p if tileset else ffi.NULL -def _handle_title(title: str | None) -> Any: +def _handle_title(title: str | None) -> Any: # noqa: ANN401 """Return title as a CFFI string. If title is None then return a decent default title is returned. @@ -145,12 +145,12 @@ class Context: Use :any:`tcod.context.new` to create a new context. """ - def __init__(self, context_p: Any) -> None: + def __init__(self, context_p: Any) -> None: # noqa: ANN401 """Create a context from a cffi pointer.""" self._context_p = context_p @classmethod - def _claim(cls, context_p: Any) -> Context: + def _claim(cls, context_p: Any) -> Context: # noqa: ANN401 """Return a new instance wrapping a context pointer.""" return cls(ffi.gc(context_p, lib.TCOD_context_delete)) @@ -176,11 +176,11 @@ def close(self) -> None: ffi.release(self._context_p) del self._context_p - def __exit__(self, *args: Any) -> None: + def __exit__(self, *_: object) -> None: """Automatically close on the context on exit.""" self.close() - def present( + def present( # noqa: PLR0913 self, console: tcod.console.Console, *, @@ -366,7 +366,7 @@ def renderer_type(self) -> int: return _check(lib.TCOD_context_get_renderer_type(self._p)) @property - def sdl_window_p(self) -> Any: + def sdl_window_p(self) -> Any: # noqa: ANN401 '''A cffi `SDL_Window*` pointer. This pointer might be NULL. This pointer will become invalid if the context is closed or goes out @@ -446,7 +446,7 @@ def __reduce__(self) -> NoReturn: @ffi.def_extern() # type: ignore -def _pycall_cli_output(catch_reference: Any, output: Any) -> None: +def _pycall_cli_output(catch_reference: Any, output: Any) -> None: # noqa: ANN401 """Callback for the libtcod context CLI. Catches the CLI output. @@ -455,7 +455,7 @@ def _pycall_cli_output(catch_reference: Any, output: Any) -> None: catch.append(ffi.string(output).decode("utf-8")) -def new( +def new( # noqa: PLR0913 *, x: int | None = None, y: int | None = None, @@ -573,7 +573,7 @@ def new( @pending_deprecate("Call tcod.context.new with width and height as keyword parameters.") -def new_window( +def new_window( # noqa: PLR0913 width: int, height: int, *, @@ -600,7 +600,7 @@ def new_window( @pending_deprecate("Call tcod.context.new with columns and rows as keyword parameters.") -def new_terminal( +def new_terminal( # noqa: PLR0913 columns: int, rows: int, *, diff --git a/tcod/image.py b/tcod/image.py index 826ea33e..9480c08d 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -40,7 +40,7 @@ def __init__(self, width: int, height: int) -> None: self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete) @classmethod - def _from_cdata(cls, cdata: Any) -> Image: + def _from_cdata(cls, cdata: Any) -> Image: # noqa: ANN401 self: Image = object.__new__(cls) self.image_c = cdata self.width, self.height = self._get_size() @@ -207,7 +207,7 @@ def put_pixel(self, x: int, y: int, color: tuple[int, int, int]) -> None: """ lib.TCOD_image_put_pixel(self.image_c, x, y, color) - def blit( + def blit( # noqa: PLR0913 self, console: tcod.console.Console, x: float, @@ -242,7 +242,7 @@ def blit( angle, ) - def blit_rect( + def blit_rect( # noqa: PLR0913 self, console: tcod.console.Console, x: int, @@ -263,7 +263,7 @@ def blit_rect( """ lib.TCOD_image_blit_rect(self.image_c, _console(console), x, y, width, height, bg_blend) - def blit_2x( + def blit_2x( # noqa: PLR0913 self, console: tcod.console.Console, dest_x: int, @@ -367,7 +367,7 @@ def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(Path(filename))), lib.TCOD_image_delete)) array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape - if depth == 3: + if depth == 3: # noqa: PLR2004 array = np.concatenate( ( array, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index e6649497..d6c7e730 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1226,7 +1226,7 @@ def console_flush( DeprecationWarning, stacklevel=2, ) - if len(clear_color) == 3: + if len(clear_color) == 3: # noqa: PLR2004 clear_color = clear_color[0], clear_color[1], clear_color[2], 255 options = { "keep_aspect": keep_aspect, @@ -2393,11 +2393,10 @@ def heightmap_new(w: int, h: int, order: str = "C") -> NDArray[np.float32]: """ if order == "C": return np.zeros((h, w), np.float32, order="C") - elif order == "F": + if order == "F": return np.zeros((w, h), np.float32, order="F") - else: - msg = "Invalid order parameter, should be 'C' or 'F'." - raise ValueError(msg) + msg = "Invalid order parameter, should be 'C' or 'F'." + raise ValueError(msg) @deprecate("Assign to heightmaps as a NumPy array instead.") @@ -2863,16 +2862,15 @@ def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: stacklevel=2, ) return hm[y, x] # type: ignore - elif hm.flags["F_CONTIGUOUS"]: + if hm.flags["F_CONTIGUOUS"]: warnings.warn( "Get a value from this heightmap with hm[x,y]", DeprecationWarning, stacklevel=2, ) return hm[x, y] # type: ignore - else: - msg = "This array is not contiguous." - raise ValueError(msg) + msg = "This array is not contiguous." + raise ValueError(msg) @pending_deprecate() @@ -3574,26 +3572,25 @@ def noise_delete(n: tcod.noise.Noise) -> None: """ -def _unpack_union(type_: int, union: Any) -> Any: +def _unpack_union(type_: int, union: Any) -> Any: # noqa: PLR0911 """Unpack items from parser new_property (value_converter).""" if type_ == lib.TCOD_TYPE_BOOL: return bool(union.b) - elif type_ == lib.TCOD_TYPE_CHAR: + if type_ == lib.TCOD_TYPE_CHAR: return union.c.decode("latin-1") - elif type_ == lib.TCOD_TYPE_INT: + if type_ == lib.TCOD_TYPE_INT: return union.i - elif type_ == lib.TCOD_TYPE_FLOAT: + if type_ == lib.TCOD_TYPE_FLOAT: return union.f - elif type_ == lib.TCOD_TYPE_STRING or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00: + if type_ == lib.TCOD_TYPE_STRING or lib.TCOD_TYPE_VALUELIST15 >= type_ >= lib.TCOD_TYPE_VALUELIST00: return _unpack_char_p(union.s) - elif type_ == lib.TCOD_TYPE_COLOR: + if type_ == lib.TCOD_TYPE_COLOR: return Color._new_from_cdata(union.col) - elif type_ == lib.TCOD_TYPE_DICE: + if type_ == lib.TCOD_TYPE_DICE: return Dice(union.dice) - elif type_ & lib.TCOD_TYPE_LIST: + if type_ & lib.TCOD_TYPE_LIST: return _convert_TCODList(union.list, type_ & 0xFF) - else: - raise RuntimeError("Unknown libtcod type: %i" % type_) + raise RuntimeError("Unknown libtcod type: %i" % type_) def _convert_TCODList(c_list: Any, type_: int) -> Any: @@ -3643,7 +3640,7 @@ def _pycall_parser_error(msg: Any) -> None: @deprecate("Parser functions have been deprecated.") def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) -> None: - global _parser_listener + global _parser_listener # noqa: PLW0603 if not listener: lib.TCOD_parser_run(parser, _path_encode(Path(filename)), ffi.NULL) return diff --git a/tcod/map.py b/tcod/map.py index 8cd6ea68..b339cee1 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -87,7 +87,7 @@ def __init__( self.__buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_) self.map_c = self.__as_cdata() - def __as_cdata(self) -> Any: + def __as_cdata(self) -> Any: # noqa: ANN401 return ffi.new( "struct TCOD_Map*", ( @@ -113,7 +113,7 @@ def fov(self) -> NDArray[np.bool_]: buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer - def compute_fov( + def compute_fov( # noqa: PLR0913 self, x: int, y: int, @@ -232,7 +232,7 @@ def compute_fov( conditions. """ transparency = np.asarray(transparency) - if len(transparency.shape) != 2: + if len(transparency.shape) != 2: # noqa: PLR2004 raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape) if isinstance(pov, int): msg = "The tcod.map.compute_fov function has changed. The `x` and `y` parameters should now be given as a single tuple." diff --git a/tcod/noise.py b/tcod/noise.py index 92b7636e..66cba1d1 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -122,7 +122,7 @@ class Noise: noise_c (CData): A cffi pointer to a TCOD_noise_t object. """ - def __init__( + def __init__( # noqa: PLR0913 self, dimensions: int, algorithm: int = Algorithm.SIMPLEX, @@ -132,7 +132,7 @@ def __init__( octaves: float = 4, seed: int | tcod.random.Random | None = None, ) -> None: - if not 0 < dimensions <= 4: + if not 0 < dimensions <= 4: # noqa: PLR2004 msg = f"dimensions must be in range 0 < n <= 4, got {dimensions}" raise ValueError(msg) self._seed = seed @@ -190,7 +190,7 @@ def implementation(self) -> int: @implementation.setter def implementation(self, value: int) -> None: - if not 0 <= value < 3: + if not 0 <= value < 3: # noqa: PLR2004 msg = f"{value!r} is not a valid implementation. " raise ValueError(msg) self._tdl_noise_c.implementation = value @@ -336,7 +336,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() - if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: + if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # noqa: PLR2004 # Trigger a side effect of wavelet, so that copies will be synced. saved_algo = self.algorithm self.algorithm = tcod.constants.NOISE_WAVELET @@ -385,7 +385,7 @@ def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) return None - def _setstate_old(self, state: Any) -> None: + def _setstate_old(self, state: tuple[Any, ...]) -> None: self._random = state[0] self.noise_c = ffi.new("struct TCOD_Noise*") self.noise_c.ndim = state[3] diff --git a/tcod/path.py b/tcod/path.py index 09f6d985..f708a072 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -20,10 +20,10 @@ import functools import itertools import warnings -from typing import Any, Callable +from typing import Any, Callable, Final import numpy as np -from numpy.typing import ArrayLike, NDArray +from numpy.typing import ArrayLike, DTypeLike, NDArray from typing_extensions import Literal from tcod._internal import _check @@ -31,26 +31,26 @@ @ffi.def_extern() # type: ignore -def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: +def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """Libtcodpy style callback, needs to preserve the old userData issue.""" func, userData = ffi.from_handle(handle) return func(x1, y1, x2, y2, userData) # type: ignore @ffi.def_extern() # type: ignore -def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: +def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """Does less and should run faster, just calls the handle function.""" return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore @ffi.def_extern() # type: ignore -def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: +def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function dest comes first to match up with a dest only call.""" return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore @ffi.def_extern() # type: ignore -def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: +def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function which samples the dest coordinate only.""" return ffi.from_handle(handle)(x2, y2) # type: ignore @@ -74,7 +74,7 @@ class _EdgeCostFunc: _CALLBACK_P = lib._pycall_path_old - def __init__(self, userdata: Any, shape: tuple[int, int]) -> None: + def __init__(self, userdata: object, shape: tuple[int, int]) -> None: self._userdata = userdata self.shape = shape @@ -117,7 +117,7 @@ class NodeCostArray(np.ndarray): # type: ignore A cost of 0 means the node is blocking. """ - _C_ARRAY_CALLBACKS = { + _C_ARRAY_CALLBACKS: Final = { np.float32: ("float*", _get_path_cost_func("PathCostArrayFloat32")), np.bool_: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")), np.int8: ("int8_t*", _get_path_cost_func("PathCostArrayInt8")), @@ -130,14 +130,13 @@ class NodeCostArray(np.ndarray): # type: ignore def __new__(cls, array: ArrayLike) -> NodeCostArray: """Validate a numpy array and setup a C callback.""" - self = np.asarray(array).view(cls) - return self + return np.asarray(array).view(cls) def __repr__(self) -> str: return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})" def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: - if len(self.shape) != 2: + if len(self.shape) != 2: # noqa: PLR2004 msg = f"Array must have a 2d shape, shape is {self.shape!r}" raise ValueError(msg) if self.dtype.type not in self._C_ARRAY_CALLBACKS: @@ -194,7 +193,7 @@ def __init__(self, cost: Any, diagonal: float = 1.41) -> None: def __repr__(self) -> str: return f"{self.__class__.__name__}(cost={self.cost!r}, diagonal={self.diagonal!r})" - def __getstate__(self) -> Any: + def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() del state["_path_c"] del state["shape"] @@ -202,7 +201,7 @@ def __getstate__(self) -> Any: del state["_userdata"] return state - def __setstate__(self, state: Any) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) self.__init__(self.cost, self.diagonal) # type: ignore @@ -286,7 +285,7 @@ def get_path(self, x: int, y: int) -> list[tuple[int, int]]: def maxarray( shape: tuple[int, ...], - dtype: Any = np.int32, + dtype: DTypeLike = np.int32, order: Literal["C", "F"] = "C", ) -> NDArray[Any]: """Return a new array filled with the maximum finite value for `dtype`. @@ -321,7 +320,7 @@ def _export_dict(array: NDArray[Any]) -> dict[str, Any]: } -def _export(array: NDArray[Any]) -> Any: +def _export(array: NDArray[Any]) -> Any: # noqa: ANN401 """Convert a NumPy array into a cffi object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -329,7 +328,7 @@ def _export(array: NDArray[Any]) -> Any: def _compile_cost_edges(edge_map: ArrayLike) -> tuple[NDArray[np.intc], int]: """Return an edge_cost array using an integer map.""" edge_map = np.array(edge_map, copy=True) - if edge_map.ndim != 2: + if edge_map.ndim != 2: # noqa: PLR2004 raise ValueError("edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 edge_map[edge_center] = 0 @@ -344,13 +343,13 @@ def _compile_cost_edges(edge_map: ArrayLike) -> tuple[NDArray[np.intc], int]: return c_edges, len(edge_array) -def dijkstra2d( +def dijkstra2d( # noqa: PLR0913 distance: ArrayLike, cost: ArrayLike, cardinal: int | None = None, diagonal: int | None = None, *, - edge_map: Any = None, + edge_map: ArrayLike | None = None, out: np.ndarray | None = ..., # type: ignore ) -> NDArray[Any]: """Return the computed distance of all nodes on a 2D Dijkstra grid. @@ -523,7 +522,7 @@ def hillclimb2d( cardinal: bool | None = None, diagonal: bool | None = None, *, - edge_map: Any = None, + edge_map: ArrayLike | None = None, ) -> NDArray[Any]: """Return a path on a grid from `start` to the lowest point. @@ -577,7 +576,7 @@ def hillclimb2d( return path -def _world_array(shape: tuple[int, ...], dtype: Any = np.int32) -> NDArray[Any]: +def _world_array(shape: tuple[int, ...], dtype: DTypeLike = np.int32) -> NDArray[Any]: """Return an array where ``ij == arr[ij]``.""" return np.ascontiguousarray( np.transpose( @@ -666,7 +665,7 @@ def __init__(self, shape: tuple[int, ...], *, order: str = "C") -> None: self._order = order if self._order == "F": self._shape_c = self._shape_c[::-1] - if not 0 < self._ndim <= 4: + if not 0 < self._ndim <= 4: # noqa: PLR2004 msg = "Graph dimensions must be 1 <= n <= 4." raise TypeError(msg) self._graph: dict[tuple[Any, ...], dict[str, Any]] = {} @@ -957,7 +956,7 @@ def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: raise ValueError(msg) self._heuristic = (cardinal, diagonal, z, w) - def _compile_rules(self) -> Any: + def _compile_rules(self) -> Any: # noqa: ANN401 """Compile this graph into a C struct array.""" if not self._edge_rules_p: self._edge_rules_keep_alive = [] @@ -1029,7 +1028,7 @@ class SimpleGraph: def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1) -> None: cost = np.asarray(cost) - if cost.ndim != 2: + if cost.ndim != 2: # noqa: PLR2004 msg = f"The cost array must e 2 dimensional, array of shape {cost.shape!r} given." raise TypeError(msg) if greed <= 0: diff --git a/tcod/random.py b/tcod/random.py index b6042d8a..148331df 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -81,7 +81,7 @@ def __init__( ) @classmethod - def _new_from_cdata(cls, cdata: Any) -> Random: + def _new_from_cdata(cls, cdata: Any) -> Random: # noqa: ANN401 """Return a new instance encapsulating this cdata.""" self: Random = object.__new__(cls) self.random_c = cdata @@ -149,7 +149,7 @@ def inverse_gauss(self, mu: float, sigma: float) -> float: def inverse_guass(self, mu: float, sigma: float) -> float: # noqa: D102 return self.inverse_gauss(mu, sigma) - def __getstate__(self) -> Any: + def __getstate__(self) -> dict[str, Any]: """Pack the self.random_c attribute into a portable state.""" state = self.__dict__.copy() state["random_c"] = { @@ -165,7 +165,7 @@ def __getstate__(self) -> Any: } return state - def __setstate__(self, state: Any) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: """Create a new cdata object with the stored parameters.""" if "algo" in state["random_c"]: # Handle old/deprecated format. Covert to libtcod's new union type. diff --git a/tcod/render.py b/tcod/render.py index 8825d076..80558dfb 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -50,7 +50,7 @@ def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Til ) @classmethod - def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas: + def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas: # noqa: ANN401 self = object.__new__(cls) # Ignore Final reassignment type errors since this is an alternative constructor. # This could be a sign that the current constructor was badly implemented. diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 58b7b030..761ca9a1 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -5,7 +5,7 @@ import sys as _sys from dataclasses import dataclass from types import TracebackType -from typing import Any, Callable, TypeVar +from typing import Any, Callable, NoReturn, TypeVar from tcod.cffi import ffi, lib @@ -80,7 +80,7 @@ def _check(result: int) -> int: return result -def _check_p(result: Any) -> Any: +def _check_p(result: Any) -> Any: # noqa: ANN401 """Check if an SDL function returned NULL, and raise an exception if it did.""" if not result: raise RuntimeError(_get_error()) @@ -111,7 +111,7 @@ def _required_version(required: tuple[int, int, int]) -> Callable[[T], T]: if required <= _compiled_version(): return lambda x: x - def replacement(*_args: Any, **_kwargs: Any) -> Any: + def replacement(*_args: object, **_kwargs: object) -> NoReturn: msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" raise RuntimeError(msg) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index b623c58d..8ed3fd38 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -136,7 +136,7 @@ def convert_audio( in_array: NDArray[Any] = np.asarray(in_sound) if len(in_array.shape) == 1: in_array = in_array[:, np.newaxis] - if len(in_array.shape) != 2: + if len(in_array.shape) != 2: # noqa: PLR2004 msg = f"Expected a 1 or 2 ndim input, got {in_array.shape} instead." raise TypeError(msg) cvt = ffi.new("SDL_AudioCVT*") @@ -191,7 +191,7 @@ def __init__( self, device_id: int, capture: bool, - spec: Any, # SDL_AudioSpec* + spec: Any, # SDL_AudioSpec* # noqa: ANN401 ) -> None: assert device_id >= 0 assert ffi.typeof(spec) is ffi.typeof("SDL_AudioSpec*") @@ -284,7 +284,7 @@ def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: if isinstance(samples_, np.ndarray): samples_ = self._verify_array_format(samples_) samples: NDArray[Any] = np.asarray(samples_, dtype=self.format) - if len(samples.shape) < 2: + if len(samples.shape) < 2: # noqa: PLR2004 samples = samples[:, np.newaxis] return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) @@ -605,7 +605,7 @@ class AllowedChanges(enum.IntFlag): """""" -def open( +def open( # noqa: PLR0913 name: str | None = None, capture: bool = False, *, diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 62241dc5..b65d7339 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -126,7 +126,7 @@ class Joystick: _by_instance_id: ClassVar[WeakValueDictionary[int, Joystick]] = WeakValueDictionary() """Currently opened joysticks.""" - def __init__(self, sdl_joystick_p: Any) -> None: + def __init__(self, sdl_joystick_p: Any) -> None: # noqa: ANN401 self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) @@ -200,7 +200,7 @@ class GameController: _by_instance_id: ClassVar[WeakValueDictionary[int, GameController]] = WeakValueDictionary() """Currently opened controllers.""" - def __init__(self, sdl_controller_p: Any) -> None: + def __init__(self, sdl_controller_p: Any) -> None: # noqa: ANN401 self.sdl_controller_p: Final = sdl_controller_p self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p)) """The :any:`Joystick` associated with this controller.""" diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 6a93955a..d212afb6 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -23,7 +23,7 @@ class Cursor: """A cursor icon for use with :any:`set_cursor`.""" - def __init__(self, sdl_cursor_p: Any) -> None: + def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"): msg = f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)})." raise TypeError(msg) @@ -82,7 +82,7 @@ def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: tuple[i :any:`set_cursor` https://wiki.libsdl.org/SDL_CreateCursor """ - if len(data.shape) != 2: + if len(data.shape) != 2: # noqa: PLR2004 msg = "Data and mask arrays must be 2D." raise TypeError(msg) if data.shape != mask.shape: diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index b1dafe8c..2e879c4d 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -115,7 +115,7 @@ class BlendMode(enum.IntEnum): """""" -def compose_blend_mode( +def compose_blend_mode( # noqa: PLR0913 source_color_factor: BlendFactor, dest_color_factor: BlendFactor, color_operation: BlendOperation, @@ -239,7 +239,7 @@ def __init__(self, renderer: Renderer) -> None: def __enter__(self) -> None: pass - def __exit__(self, *_: Any) -> None: + def __exit__(self, *_: object) -> None: _check(lib.SDL_SetRenderTarget(self.renderer.p, self.old_texture_p)) @@ -262,7 +262,7 @@ def __eq__(self, other: object) -> bool: return bool(self.p == other.p) return NotImplemented - def copy( + def copy( # noqa: PLR0913 self, texture: Texture, source: tuple[float, float, float, float] | None = None, @@ -334,11 +334,11 @@ def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, acc See :any:`TextureAccess` for more options. """ if format is None: - assert len(pixels.shape) == 3 + assert len(pixels.shape) == 3 # noqa: PLR2004 assert pixels.dtype == np.uint8 - if pixels.shape[2] == 4: + if pixels.shape[2] == 4: # noqa: PLR2004 format = int(lib.SDL_PIXELFORMAT_RGBA32) - elif pixels.shape[2] == 3: + elif pixels.shape[2] == 3: # noqa: PLR2004 format = int(lib.SDL_PIXELFORMAT_RGB24) else: msg = f"Can't determine the format required for an array of shape {pixels.shape}." @@ -594,8 +594,8 @@ def fill_rects(self, rects: NDArray[np.intc | np.float32]) -> None: .. versionadded:: 13.5 """ - assert len(rects.shape) == 2 - assert rects.shape[1] == 4 + assert len(rects.shape) == 2 # noqa: PLR2004 + assert rects.shape[1] == 4 # noqa: PLR2004 rects = np.ascontiguousarray(rects) if rects.dtype == np.intc: _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) @@ -610,8 +610,8 @@ def draw_rects(self, rects: NDArray[np.intc | np.float32]) -> None: .. versionadded:: 13.5 """ - assert len(rects.shape) == 2 - assert rects.shape[1] == 4 + assert len(rects.shape) == 2 # noqa: PLR2004 + assert rects.shape[1] == 4 # noqa: PLR2004 rects = np.ascontiguousarray(rects) if rects.dtype == np.intc: _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) @@ -626,8 +626,8 @@ def draw_points(self, points: NDArray[np.intc | np.float32]) -> None: .. versionadded:: 13.5 """ - assert len(points.shape) == 2 - assert points.shape[1] == 2 + assert len(points.shape) == 2 # noqa: PLR2004 + assert points.shape[1] == 2 # noqa: PLR2004 points = np.ascontiguousarray(points) if points.dtype == np.intc: _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) @@ -642,8 +642,8 @@ def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: .. versionadded:: 13.5 """ - assert len(points.shape) == 2 - assert points.shape[1] == 2 + assert len(points.shape) == 2 # noqa: PLR2004 + assert points.shape[1] == 2 # noqa: PLR2004 points = np.ascontiguousarray(points) if points.dtype == np.intc: _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) @@ -654,7 +654,7 @@ def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: raise TypeError(msg) @_required_version((2, 0, 18)) - def geometry( + def geometry( # noqa: PLR0913 self, texture: Texture | None, xy: NDArray[np.float32], @@ -667,18 +667,18 @@ def geometry( .. versionadded:: 13.5 """ assert xy.dtype == np.float32 - assert len(xy.shape) == 2 - assert xy.shape[1] == 2 + assert len(xy.shape) == 2 # noqa: PLR2004 + assert xy.shape[1] == 2 # noqa: PLR2004 assert xy[0].flags.c_contiguous assert color.dtype == np.uint8 - assert len(color.shape) == 2 - assert color.shape[1] == 4 + assert len(color.shape) == 2 # noqa: PLR2004 + assert color.shape[1] == 4 # noqa: PLR2004 assert color[0].flags.c_contiguous assert uv.dtype == np.float32 - assert len(uv.shape) == 2 - assert uv.shape[1] == 2 + assert len(uv.shape) == 2 # noqa: PLR2004 + assert uv.shape[1] == 2 # noqa: PLR2004 assert uv[0].flags.c_contiguous if indices is not None: assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index b2a71a66..dff4d4c0 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -2,7 +2,6 @@ import enum import warnings -from typing import Any from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _get_error @@ -44,7 +43,7 @@ def __del__(self) -> None: def __enter__(self) -> _ScopeInit: return self - def __exit__(self, *args: Any) -> None: + def __exit__(self, *_: object) -> None: self.close() diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 64b8cf20..41fec458 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -97,10 +97,10 @@ class _TempSurface: def __init__(self, pixels: ArrayLike) -> None: self._array: NDArray[np.uint8] = np.ascontiguousarray(pixels, dtype=np.uint8) - if len(self._array.shape) != 3: + if len(self._array.shape) != 3: # noqa: PLR2004 msg = f"NumPy shape must be 3D [y, x, ch] (got {self._array.shape})" raise TypeError(msg) - if not (3 <= self._array.shape[2] <= 4): + if not (3 <= self._array.shape[2] <= 4): # noqa: PLR2004 msg = f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})" raise TypeError(msg) self.p = ffi.gc( @@ -113,7 +113,7 @@ def __init__(self, pixels: ArrayLike) -> None: 0x000000FF, 0x0000FF00, 0x00FF0000, - 0xFF000000 if self._array.shape[2] == 4 else 0, + 0xFF000000 if self._array.shape[2] == 4 else 0, # noqa: PLR2004 ), lib.SDL_FreeSurface, ) @@ -122,7 +122,7 @@ def __init__(self, pixels: ArrayLike) -> None: class Window: """An SDL2 Window object.""" - def __init__(self, sdl_window_p: Any) -> None: + def __init__(self, sdl_window_p: Any) -> None: # noqa: ANN401 if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): msg = "sdl_window_p must be {!r} type (was {!r}).".format( ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p) @@ -334,7 +334,7 @@ def hide(self) -> None: lib.SDL_HideWindow(self.p) -def new_window( +def new_window( # noqa: PLR0913 width: int, height: int, *, diff --git a/tcod/tileset.py b/tcod/tileset.py index 1ebf2122..4f1aeef8 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -38,7 +38,7 @@ def __init__(self, tile_width: int, tile_height: int) -> None: ) @classmethod - def _claim(cls, cdata: Any) -> Tileset: + def _claim(cls, cdata: Any) -> Tileset: # noqa: ANN401 """Return a new Tileset that owns the provided TCOD_Tileset* object.""" self = object.__new__(cls) if cdata == ffi.NULL: @@ -48,7 +48,7 @@ def _claim(cls, cdata: Any) -> Tileset: return self @classmethod - def _from_ref(cls, tileset_p: Any) -> Tileset: + def _from_ref(cls, tileset_p: Any) -> Tileset: # noqa: ANN401 self = object.__new__(cls) self._tileset_p = tileset_p return self @@ -147,7 +147,7 @@ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: required = (*self.tile_shape, 4) if tile.shape != required: note = "" - if len(tile.shape) == 3 and tile.shape[2] == 3: + if len(tile.shape) == 3 and tile.shape[2] == 3: # noqa: PLR2004 note = ( "\nNote: An RGB array is too ambiguous," " an alpha channel must be added to this array to divide the background/foreground areas." diff --git a/tests/test_console.py b/tests/test_console.py index f8fa5d91..cd6df8f7 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -103,7 +103,7 @@ def test_console_pickle_fortran() -> None: def test_console_repr() -> None: from numpy import array # noqa: F401 # Used for eval - eval(repr(tcod.console.Console(10, 2))) + eval(repr(tcod.console.Console(10, 2))) # noqa: S307 def test_console_str() -> None: diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 9eb151bb..50c8be7e 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -518,7 +518,7 @@ def test_color() -> None: color_a["b"] = color_a["b"] assert list(color_a) == [0, 3, 2] - assert color_a == color_a + assert color_a == color_a # noqa: PLR0124 color_b = libtcodpy.Color(255, 255, 255) assert color_a != color_b @@ -532,7 +532,7 @@ def test_color() -> None: def test_color_repr() -> None: Color = libtcodpy.Color col = Color(0, 1, 2) - assert eval(repr(col)) == col + assert eval(repr(col)) == col # noqa: S307 @pytest.mark.filterwarnings("ignore") diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 38b1d5c2..aee98c0c 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -160,7 +160,7 @@ def test_key_repr() -> None: assert key.vk == 1 assert key.c == 2 # noqa: PLR2004 assert key.shift - key_copy = eval(repr(key)) + key_copy = eval(repr(key)) # noqa: S307 assert key.vk == key_copy.vk assert key.c == key_copy.c assert key.shift == key_copy.shift @@ -169,7 +169,7 @@ def test_key_repr() -> None: def test_mouse_repr() -> None: Mouse = tcod.Mouse mouse = Mouse(x=1, lbutton=True) - mouse_copy = eval(repr(mouse)) + mouse_copy = eval(repr(mouse)) # noqa: S307 assert mouse.x == mouse_copy.x assert mouse.lbutton == mouse_copy.lbutton From 153ce40b129669cec7fb38068f5dda610b427ccd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 22:29:48 -0800 Subject: [PATCH 0877/1101] Update Python shebangs --- build_libtcod.py | 2 +- build_sdl.py | 2 +- docs/tcod/getting-started.rst | 4 ++-- examples/audio_tone.py | 2 +- examples/cavegen.py | 2 +- examples/distribution/PyInstaller/main.py | 2 +- examples/distribution/cx_Freeze/main.py | 2 +- examples/distribution/cx_Freeze/setup.py | 2 +- examples/eventget.py | 2 +- examples/framerate.py | 2 +- examples/samples_tcod.py | 2 +- examples/thread_jobs.py | 2 +- examples/ttf.py | 2 +- scripts/generate_charmap_table.py | 2 +- scripts/get_release_description.py | 2 +- scripts/tag_release.py | 2 +- setup.py | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 59447e9f..00b6036f 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Parse and compile libtcod and SDL sources for CFFI.""" from __future__ import annotations diff --git a/build_sdl.py b/build_sdl.py index 0c46df14..56fa87de 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Build script to parse SDL headers and generate CFFI bindings.""" from __future__ import annotations diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index c7634149..7d5ec175 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -21,7 +21,7 @@ integer increments. Example:: - #!/usr/bin/env python3 + #!/usr/bin/env python # Make sure 'dejavu10x10_gs_tc.png' is in the same directory as this script. import tcod.console import tcod.context @@ -89,7 +89,7 @@ clearing the console every frame and replacing it only on resizing the window. Example:: - #!/usr/bin/env python3 + #!/usr/bin/env python import tcod.context import tcod.event diff --git a/examples/audio_tone.py b/examples/audio_tone.py index dc24d3ec..052f8a39 100755 --- a/examples/audio_tone.py +++ b/examples/audio_tone.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Shows how to use tcod.sdl.audio to play a custom-made audio stream. Opens an audio device using SDL and plays a square wave for 1 second. diff --git a/examples/cavegen.py b/examples/cavegen.py index eedf0575..4ae22ac3 100755 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """A basic cellular automata cave generation example using SciPy. http://www.roguebasin.com/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index e1c6090d..51e31e79 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights for the "hello world" PyInstaller # example script. This work is published from: United States. diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index 8f608af7..0c85ca83 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """cx_Freeze main script example.""" import tcod.console import tcod.context diff --git a/examples/distribution/cx_Freeze/setup.py b/examples/distribution/cx_Freeze/setup.py index 446a0f42..50a588db 100755 --- a/examples/distribution/cx_Freeze/setup.py +++ b/examples/distribution/cx_Freeze/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python import sys from cx_Freeze import Executable, setup # type: ignore diff --git a/examples/eventget.py b/examples/eventget.py index 0895e90f..0af84d04 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights for this example. This work is # published from: United States. diff --git a/examples/framerate.py b/examples/framerate.py index 3a9e9f81..a51f83ff 100755 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights for this example. This work is # published from: United States. diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 197a1bc9..f68fed86 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """This code demonstrates various usages of python-tcod.""" # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index f4154f9a..fa99a465 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights for this example. This work is # published from: United States. diff --git a/examples/ttf.py b/examples/ttf.py index da412b4b..fb0336cb 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """A TrueType Font example using the FreeType library. You will need to get this external library from PyPI: diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 0591cb9b..8154b5b8 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """This script is used to generate the tables for `charmap-reference.rst`. Uses the tabulate module from PyPI. diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 7d47d9f9..547f8ca6 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Print the description used for GitHub Releases.""" from __future__ import annotations diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 70b77fb7..2d528306 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Automate tagged releases of this project.""" from __future__ import annotations diff --git a/setup.py b/setup.py index dc2139e7..4027fbae 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """Python-tcod setup script.""" from __future__ import annotations From 3e02e28489f0de3555f1cb18bb40d79bea22728c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 22:49:09 -0800 Subject: [PATCH 0878/1101] Clean up warnings in scripts --- scripts/generate_charmap_table.py | 6 +++--- scripts/tag_release.py | 8 +++----- setup.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 8154b5b8..c4137e8a 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -14,8 +14,8 @@ import tcod.tileset -def get_charmaps() -> Iterator[str]: - """Return an iterator of the current character maps from tcod.tilest.""" +def get_character_maps() -> Iterator[str]: + """Return an iterator of the current character maps from tcod.tileset.""" for name in dir(tcod.tileset): if name.startswith("CHARMAP_"): yield name[len("CHARMAP_") :].lower() @@ -60,7 +60,7 @@ def main() -> None: parser.add_argument( "charmap", action="store", - choices=list(get_charmaps()), + choices=list(get_character_maps()), type=str, help="which character map to generate a table from", ) diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 2d528306..7b946513 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -34,11 +34,9 @@ def parse_changelog(args: argparse.Namespace) -> tuple[str, str]: ) assert match header, changes, tail = match.groups() - tagged = "\n## [{}] - {}\n{}".format( - args.tag, - datetime.date.today().isoformat(), # Local timezone is fine, probably. # noqa: DTZ011 - changes, - ) + + iso_date = datetime.datetime.now(tz=datetime.timezone.utc).date().isoformat() + tagged = f"\n## [{args.tag}] - {iso_date}\n{changes}" if args.verbose: print("--- Tagged section:") print(tagged) diff --git a/setup.py b/setup.py index 4027fbae..6e17d0f8 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ def check_sdl_version() -> None: return needed_version = "{}.{}.{}".format(*SDL_VERSION_NEEDED) try: - sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() + sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() # noqa: S603, S607 except FileNotFoundError as exc: msg = ( f"libsdl2-dev or equivalent must be installed on your system and must be at least version {needed_version}." From 5dcbcd42c14464f0f1e1680bde42438d597502e1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Jan 2024 22:54:42 -0800 Subject: [PATCH 0879/1101] Prepare 16.2.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb802f94..acd852e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [16.2.2] - 2024-01-16 + ### Fixed - Ignore the locale when encoding file paths outside of Windows. From ede0886eb04bf3c8bf4ca98e6d2925b96ad67e1a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:33:20 +0000 Subject: [PATCH 0880/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.13 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.13...v0.1.14) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f04c539..3bdb74e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.1.14 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 60ea79c9c6db6bed69bf699aa8530741fc9bd1d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:40:04 +0000 Subject: [PATCH 0881/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3bdb74e6..520d663a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 + rev: v0.2.0 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 5726f0db22150633a47cdc70761cdee4aa0eb72b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:40:12 +0000 Subject: [PATCH 0882/1101] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- build_libtcod.py | 2 +- examples/samples_tcod.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 00b6036f..56161182 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -15,7 +15,7 @@ sys.path.append(str(Path(__file__).parent)) # Allow importing local modules. -import build_sdl # noqa: E402 +import build_sdl Py_LIMITED_API = 0x03060000 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index f68fed86..7baf6af2 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -768,12 +768,11 @@ def on_draw(self) -> None: libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) - else: - if not libtcodpy.dijkstra_is_empty(self.dijkstra): - libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) - self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore - libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) - self.recalculate = True + elif not libtcodpy.dijkstra_is_empty(self.dijkstra): + libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) + self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore + libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) + self.recalculate = True def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym == tcod.event.KeySym.i and self.dy > 0: From 9101ca0e397b476f7b6d8a8bc6b946bd439d7fe0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 23:11:12 +0000 Subject: [PATCH 0883/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 520d663a..447a431b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.2.1 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 98cc42411376b331fd1374c3aab7e868daecdfe2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:30:42 +0000 Subject: [PATCH 0884/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 447a431b..7badadae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From bcbd672c404164f5bc8116d0bf8d0a0f18ab1fcb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 23:30:50 +0000 Subject: [PATCH 0885/1101] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_sdl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 10cdbb50..709d3647 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -23,7 +23,7 @@ def test_sdl_window(uses_window: None) -> None: assert window.title == sys.argv[0] window.title = "Title" assert window.title == "Title" - assert window.opacity == 1.0 # noqa: PLR2004 + assert window.opacity == 1.0 window.position = window.position window.fullscreen = window.fullscreen window.resizable = window.resizable From 63e98d9acaa4bfb8b53243918c4bbca859049ed4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Feb 2024 09:29:52 -0800 Subject: [PATCH 0886/1101] Tutorial part 2 --- .vscode/settings.json | 5 + docs/conf.py | 7 +- docs/epilog.rst | 3 + docs/tutorial/index.rst | 6 + docs/tutorial/part-02.rst | 701 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 719 insertions(+), 3 deletions(-) create mode 100644 docs/tutorial/part-02.rst diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ea25a26..62ea12d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -127,6 +127,7 @@ "DHLINE", "DISPLAYSWITCH", "dlopen", + "docstrings", "documentclass", "Doryen", "DPAD", @@ -136,6 +137,7 @@ "DTEEW", "dtype", "dtypes", + "dunder", "DVLINE", "elif", "endianness", @@ -148,6 +150,7 @@ "ffade", "fgcolor", "fheight", + "Flecs", "flto", "fmean", "fontx", @@ -332,6 +335,7 @@ "printn", "PRINTSCREEN", "propname", + "pushdown", "pycall", "pycparser", "pyinstaller", @@ -346,6 +350,7 @@ "quickstart", "QUOTEDBL", "RALT", + "randint", "randomizer", "rbutton", "RCTRL", diff --git a/docs/conf.py b/docs/conf.py index 2fece512..a323f906 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -103,7 +103,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "/epilog.rst"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "epilog.rst", "prolog.rst"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -378,13 +378,14 @@ napoleon_use_param = True napoleon_use_rtype = True -rst_prolog = ".. include:: /prolog.rst" # Added to the beginning of every source file. -rst_epilog = ".. include:: /epilog.rst" # Added to the end of every source file. +rst_prolog = Path("prolog.rst").read_text(encoding="utf-8") # Added to the beginning of every source file. +rst_epilog = Path("epilog.rst").read_text(encoding="utf-8") # Added to the end of every source file. # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "numpy": ("https://numpy.org/doc/stable/", None), + "tcod-ecs": ("https://python-tcod-ecs.readthedocs.io/en/latest/", None), } os.environ["READTHEDOCS"] = "True" diff --git a/docs/epilog.rst b/docs/epilog.rst index e69de29b..c3ee806c 100644 --- a/docs/epilog.rst +++ b/docs/epilog.rst @@ -0,0 +1,3 @@ + +.. _tcod-ecs: https://github.com/HexDecimal/python-tcod-ecs +.. _Flecs: https://github.com/SanderMertens/flecs diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 2277ab62..b43a8971 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -3,6 +3,12 @@ Tutorial .. include:: notice.rst +.. note:: + This a Python tutorial reliant on a Modern ECS implementation. + In this case `tcod-ecs`_ will be used. + Most other Python ECS libraries do not support entity relationships and arbitrary tags required by this tutorial. + If you wish to use this tutorial with another language you may need a Modern ECS implementation on par with `Flecs`_. + .. toctree:: :maxdepth: 1 :glob: diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst new file mode 100644 index 00000000..460e5016 --- /dev/null +++ b/docs/tutorial/part-02.rst @@ -0,0 +1,701 @@ +.. _part-2: + +Part 2 - Entities +############################################################################## + +.. include:: notice.rst + +In part 2 entities will be added and the state system will be refactored to be more generic. +This part will also begin to split logic into multiple Python modules using a namespace called ``game``. + +Entities will be handled with an ECS implementation, in this case: `tcod-ecs`_. +``tcod-ecs`` is a standalone package and is installed separately from ``tcod``. +Use :shell:`pip install tcod-ecs` to install this package. + +Namespace package +============================================================================== + +Create a new folder called ``game`` and inside the folder create a new python file named ``__init__.py``. +``game/__init__.py`` only needs a docstring describing that it is a namespace package: + +.. code-block:: python + + """Game namespace package.""" + +This package will be used to organize new modules. + +State protocol +============================================================================== + +To have more states than ``ExampleState`` one must use an abstract type which can be used to refer to any state. +In this case a `Protocol`_ will be used, called ``State``. + +Create a new module: ``game/state.py``. +In this module add the class :python:`class State(Protocol):`. +``Protocol`` is from Python's ``typing`` module. +``State`` should have the ``on_event`` and ``on_draw`` methods from ``ExampleState`` but these methods will be empty other than the docstrings describing what they are for. +These methods refer to types from ``tcod`` and those types will need to be imported. +``State`` should also have :python:`__slots__ = ()` [#slots]_ in case the class is used for a subclass. + +``game/state.py`` should look like this: + +.. code-block:: python + + """Base classes for states.""" + from __future__ import annotations + + from typing import Protocol + + import tcod.console + import tcod.event + + + class State(Protocol): + """An abstract game state.""" + + __slots__ = () + + def on_event(self, event: tcod.event.Event) -> None: + """Called on events.""" + + def on_draw(self, console: tcod.console.Console) -> None: + """Called when the state is being drawn.""" + +The ``ExampleState`` class does not need to be updated since it is already a structural subtype of ``State``. +Note that subclasses of ``State`` will never be in same module as ``State``, this will be the same for all abstract classes. + +Organizing globals +============================================================================== + +There are a few variables which will need to be accessible from multiple modules. +Any global variables which might be assigned from other modules will need to a tracked and handled with care. + +Create a new module: ``g.py`` [#g]_. +This module is exceptional and will be placed at the top-level instead of in the ``game`` folder. + +``console`` and ``context`` from ``main.py`` will now be annotated in ``g.py``. +These will not be assigned here, only annotated with a type-hint. + +A new global will be added: :python:`states: list[game.state.State] = []`. +States are implemented as a list/stack to support `pushdown automata `_. +Representing states as a stack makes it easier to implement popup windows, menus, and other "history aware" states. + +Finally :python:`world: tcod.ecs.Registry` will be added to hold the ECS scope. + +It is important to document all variables placed in this module with docstrings. + +.. code-block:: python + + """This module stores globally mutable variables used by this program.""" + from __future__ import annotations + + import tcod.console + import tcod.context + import tcod.ecs + + import game.state + + console: tcod.console.Console + """The main console.""" + + context: tcod.context.Context + """The window managed by tcod.""" + + states: list[game.state.State] = [] + """A stack of states with the last item being the active state.""" + + world: tcod.ecs.Registry + """The active ECS registry and current session.""" + +Now other modules can :python:`import g` to access global variables. + +Ideally you should not overuse this module for too many things. +When a variables can either be taken as a function parameter or accessed as a global then passing as a parameter is always preferable. + +State functions +============================================================================== + +Create a new module: ``game/state_tools.py``. +This module will handle events and rendering of the global state. + +In this module add the function :python:`def main_draw() -> None:`. +This will hold the "clear, draw, present" logic from the ``main`` function which will be moved to this function. +Render the active state with :python:`g.states[-1].on_draw(g.console)`. +If ``g.states`` is empty then this function should immediately :python:`return` instead of doing anything. +Empty containers in Python are :python:`False` when checked for truthiness. + +Next the function :python:`def main_loop() -> None:` is created. +The :python:`while` loop from ``main`` will be moved to this function. +The while loop will be replaced by :python:`while g.states:` so that this function will exit if no state exists. +Drawing will be replaced by a call to ``main_draw``. +Events in the for-loop will be passed to the active state :python:`g.states[-1].on_event(event)`. +Any states ``on_event`` method could potentially change the state so ``g.states`` must be checked to be non-empty for every handled event. + +.. code-block:: python + + """State handling functions.""" + from __future__ import annotations + + import tcod.console + + import g + + + def main_draw() -> None: + """Render and present the active state.""" + if not g.states: + return + g.console.clear() + g.states[-1].on_draw(g.console) + g.context.present(g.console) + + + def main_loop() -> None: + """Run the active state forever.""" + while g.states: + main_draw() + for event in tcod.event.wait(): + if g.states: + g.states[-1].on_event(event) + +Now ``main.py`` can be edited to use the global variables and the new game loop. + +Add :python:`import g` and :python:`import game.state_tools`. +Replace references to ``console`` with ``g.console``. +Replace references to ``context`` with ``g.context``. + +States are initialed by assigning a list with the initial state to ``g.states``. +The previous game loop is replaced by a call to :python:`game.state_tools.main_loop()`. + +.. code-block:: python + :emphasize-lines: 3-4,12-15 + + ... + + import g + import game.state_tools + + def main() -> None: + """Entry point function.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + tcod.tileset.procedural_block_elements(tileset=tileset) + g.console = tcod.console.Console(80, 50) + g.states = [ExampleState(player_x=console.width // 2, player_y=console.height // 2)] + with tcod.context.new(console=g.console, tileset=tileset) as g.context: + game.state_tools.main_loop() + ... + +After this you can test the game. +There should be no visible differences from before. + + +ECS tags +============================================================================== + +Create ``game/tags.py``. +This will hold some sentinel values to be used as tags for ``tcod-ecs``. +These tags can be anything that's both unique and unchanging, in this case Python strings are used. + +For example :python:`IsPlayer: Final = "IsPlayer"` will tag an object as being controlled by the player. +The name is ``IsPlayer`` and string is the same as the name. +The ``Final`` annotation clarifies that this a constant. +Sentinel values for ``tcod-ecs`` are named like classes, similar to names like :python:`None` or :python:`False`. + +Repeat this for ``IsActor`` and ``IsItem`` tags. +The ``game/tags.py`` module should look like this: + +.. code-block:: python + + """Collection of common tags.""" + from __future__ import annotations + + from typing import Final + + IsPlayer: Final = "IsPlayer" + """Entity is the player.""" + + IsActor: Final = "IsActor" + """Entity is an actor.""" + + IsItem: Final = "IsItem" + """Entity is an item.""" + +ECS components +============================================================================== + +Next is a new ``game/components.py`` module. +This will hold the components for the graphics and position of entities. + +Start by adding an import for ``attrs``. +The ability to easily design small classes which are frozen/immutable is important for working with ``tcod-ecs``. + +The first component will be a ``Position`` class. +This class will be decorated with :python:`@attrs.define(frozen=True)`. +For attributes this class will have :python:`x: int` and :python:`y: int`. + +It will be common to add vectors to a ``Position`` with code such as :python:`new_pos: Position = Position(0, 0) + (0, 1)`. +Create the dunder method :python:`def __add__(self, direction: tuple[int, int]) -> Self:` to allow this syntax. +Unpack the input with :python:`x, y = direction`. +:python:`self.__class__` is the current class so :python:`self.__class__(self.x + x, self.y + y)` will create a new instance with the direction added to the previous values. + +The new class will look like this: + +.. code-block:: python + + @attrs.define(frozen=True) + class Position: + """An entities position.""" + + x: int + y: int + + def __add__(self, direction: tuple[int, int]) -> Self: + """Add a vector to this position.""" + x, y = direction + return self.__class__(self.x + x, self.y + y) + +Because ``Position`` is immutable, ``tcod-ecs`` is able to reliably track changes to this component. +Normally you can only query entities by which components they have. +A callback can be registered with ``tcod-ecs`` to mirror component values as tags. +This allows querying an entity by its exact position. + +Add :python:`import tcod.ecs.callbacks` and :python:`from tcod.ecs import Entity`. +Then create the new function :python:`def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None:` decorated with :python:`@tcod.ecs.callbacks.register_component_changed(component=Position)`. +This function is called when the ``Position`` component is either added, removed, or modified by assignment. +The goal of this function is to mirror the current position to the :class:`set`-like attribute ``entity.tags``. + +:python:`if old == new:` then a position was assigned its own value or an equivalent value. +The cost of discarding and adding the same value can sometimes be high so this case should be guarded and ignored. +:python:`if old is not None:` then the value tracked by ``entity.tags`` is outdated and must be removed. +:python:`if new is not None:` then ``new`` is the up-to-date value to be tracked by ``entity.tags``. + +The function should look like this: + +.. code-block:: python + + @tcod.ecs.callbacks.register_component_changed(component=Position) + def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None: + """Mirror position components as a tag.""" + if old == new: # New position is equivalent to its previous value + return # Ignore and return + if old is not None: # Position component removed or changed + entity.tags.discard(old) # Remove old position from tags + if new is not None: # Position component added or changed + entity.tags.add(new) # Add new position to tags + +Next is the ``Graphic`` component. +This will have the attributes :python:`ch: int = ord("!")` and :python:`fg: tuple[int, int, int] = (255, 255, 255)`. +By default all new components should be marked as frozen. + +.. code-block:: python + + @attrs.define(frozen=True) + class Graphic: + """An entities icon and color.""" + + ch: int = ord("!") + fg: tuple[int, int, int] = (255, 255, 255) + +One last component: ``Gold``. +Define this as :python:`Gold: Final = ("Gold", int)`. +``(name, type)`` is tcod-ecs specific syntax to handle multiple components sharing the same type. + +.. code-block:: python + + Gold: Final = ("Gold", int) + """Amount of gold.""" + +That was the last component. +The ``game/components.py`` module should look like this: + +.. code-block:: python + + """Collection of common components.""" + from __future__ import annotations + + from typing import Final, Self + + import attrs + import tcod.ecs.callbacks + from tcod.ecs import Entity + + + @attrs.define(frozen=True) + class Position: + """An entities position.""" + + x: int + y: int + + def __add__(self, direction: tuple[int, int]) -> Self: + """Add a vector to this position.""" + x, y = direction + return self.__class__(self.x + x, self.y + y) + + + @tcod.ecs.callbacks.register_component_changed(component=Position) + def on_position_changed(entity: Entity, old: Position | None, new: Position | None) -> None: + """Mirror position components as a tag.""" + if old == new: + return + if old is not None: + entity.tags.discard(old) + if new is not None: + entity.tags.add(new) + + + @attrs.define(frozen=True) + class Graphic: + """An entities icon and color.""" + + ch: int = ord("!") + fg: tuple[int, int, int] = (255, 255, 255) + + + Gold: Final = ("Gold", int) + """Amount of gold.""" + +ECS entities and registry +============================================================================== + +Now it is time to create entities. +To do that you need to create the ECS registry. + +Make a new script called ``game/world_tools.py``. +This module will be used to create the ECS registry. + +Random numbers from :mod:`random` will be used. +In this case we want to use ``Random`` as a component so add :python:`from random import Random`. +Get the registry with :python:`from tcod.ecs import Registry`. +Collect all our components and tags with :python:`from game.components import Gold, Graphic, Position` and :python:`from game.tags import IsActor, IsItem, IsPlayer`. + +This module will have one function: :python:`def new_world() -> Registry:`. +Think of the ECS registry as containing the world since this is how it will be used. +Start this function with :python:`world = Registry()`. + +Entities are referenced with the syntax :python:`world[unique_id]`. +If the same ``unique_id`` is used then you will access the same entity. +:python:`new_entity = world[object()]` is the syntax to spawn new entities because ``object()`` is always unique. +Whenever a global entity is needed then :python:`world[None]` will be used. + +Create an instance of :python:`Random()` and assign it to both :python:`world[None].components[Random]` and ``rng``. +This can done on one line with :python:`rng = world[None].components[Random] = Random()`. + +Next create the player entity with :python:`player = world[object()]`. +Assign the following components to the new player entity: :python:`player.components[Position] = Position(5, 5)`, :python:`player.components[Graphic] = Graphic(ord("@"))`, and :python:`player.components[Gold] = 0`. +Then update the players tags with :python:`player.tags |= {IsPlayer, IsActor}`. + +To add some variety we will scatter gold randomly across the world. +Start a for-loop with :python:`for _ in range(10):` then create a ``gold`` entity in this loop. + +The ``Random`` instance ``rng`` has access to functions from Python's random module such as :any:`random.randint`. +Set ``Position`` to :python:`Position(rng.randint(0, 20), rng.randint(0, 20))`. +Set ``Graphic`` to :python:`Graphic(ord("$"), fg=(255, 255, 0))`. +Set ``Gold`` to :python:`rng.randint(1, 10)`. +Then add ``IsItem`` as a tag. + +Once the for-loop exits then :python:`return world`. +Make sure :python:`return` has the correct indentation and is not part of the for-loop or else you will only spawn one gold. + +``game/world_tools.py`` should look like this: + +.. code-block:: python + + """Functions for working with worlds.""" + from __future__ import annotations + + from random import Random + + from tcod.ecs import Registry + + from game.components import Gold, Graphic, Position + from game.tags import IsActor, IsItem, IsPlayer + + + def new_world() -> Registry: + """Return a freshly generated world.""" + world = Registry() + + rng = world[None].components[Random] = Random() + + player = world[object()] + player.components[Position] = Position(5, 5) + player.components[Graphic] = Graphic(ord("@")) + player.components[Gold] = 0 + player.tags |= {IsPlayer, IsActor} + + for _ in range(10): + gold = world[object()] + gold.components[Position] = Position(rng.randint(0, 20), rng.randint(0, 20)) + gold.components[Graphic] = Graphic(ord("$"), fg=(255, 255, 0)) + gold.components[Gold] = rng.randint(1, 10) + gold.tags |= {IsItem} + + return world + +New in-game state +============================================================================== + +Now there is a new ECS world but the example state does not know how to render it. +A new state needs to be made which is aware of the new entities. + +Create a new script called ``game/states.py``. +``states`` is for derived classes, ``state`` is for the abstract class. +New states will be created in this module and this module will be allowed to import many first party modules without issues. + +Before adding a new state it is time to add a more complete set of directional keys. +These will be added as a dictionary and can be reused anytime we want to know how a key translates to a direction. +Use :python:`from tcod.event import KeySym` to make ``KeySym`` enums easier to write. +Then add the following: + +.. code-block:: python + + DIRECTION_KEYS: Final = { + # Arrow keys + KeySym.LEFT: (-1, 0), + KeySym.RIGHT: (1, 0), + KeySym.UP: (0, -1), + KeySym.DOWN: (0, 1), + # Arrow key diagonals + KeySym.HOME: (-1, -1), + KeySym.END: (-1, 1), + KeySym.PAGEUP: (1, -1), + KeySym.PAGEDOWN: (1, 1), + # Keypad + KeySym.KP_4: (-1, 0), + KeySym.KP_6: (1, 0), + KeySym.KP_8: (0, -1), + KeySym.KP_2: (0, 1), + KeySym.KP_7: (-1, -1), + KeySym.KP_1: (-1, 1), + KeySym.KP_9: (1, -1), + KeySym.KP_3: (1, 1), + # VI keys + KeySym.h: (-1, 0), + KeySym.l: (1, 0), + KeySym.k: (0, -1), + KeySym.j: (0, 1), + KeySym.y: (-1, -1), + KeySym.b: (-1, 1), + KeySym.u: (1, -1), + KeySym.n: (1, 1), + } + +Create a new :python:`class InGame:` decorated with :python:`@attrs.define(eq=False)`. +States will always use ``g.world`` to access the ECS registry. +States prefer ``console`` as a parameter over the global ``g.console`` so always use ``console`` when it exists. + +.. code-block:: python + + @attrs.define(eq=False) + class InGame: + """Primary in-game state.""" + ... + +Create an ``on_event`` method matching the ``State`` protocol. +Copying these methods from ``State`` or ``ExampleState`` should be enough. + +Now to do an tcod-ecs query to fetch the player entity. +In tcod-ecs queries most often start with :python:`g.world.Q.all_of(components=[], tags=[])`. +Which components and tags are asked for will narrow down the returned set of entities to only those matching the requirements. +The query to fetch player entities is :python:`g.world.Q.all_of(tags=[IsPlayer])`. +We expect only one player so the result will be unpacked into a single name: :python:`(player,) = g.world.Q.all_of(tags=[IsPlayer])`. + +Next is to handle the event. +Handling :python:`case tcod.event.Quit():` is the same as before: :python:`raise SystemExit()`. + +The case for direction keys will now be done in a single case: :python:`case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:`. +``sym=sym`` assigns from the event attribute to a local name. +The left side is the ``event.sym`` attribute and right side is the local name ``sym`` being assigned to. +The case also has a condition which must pass for this branch to be taken and in this case we ensure that only keys from the ``DIRECTION_KEYS`` dictionary are valid ``sym``'s. + +Inside this branch moving the player is simple. +Access the ``(x, y)`` vector with :python:`DIRECTION_KEYS[sym]` and use ``+=`` to add it to the players current ``Position`` component. +This triggers the earlier written ``__add__`` dunder method and ``on_position_changed`` callback. + +Now that the player has moved it would be a good time to interact with the gold entities. +The query to see if the player has stepped on gold is to check for whichever entities have a ``Gold`` component, an ``IsItem`` tag, and the players current position as a tag. +The query for this is :python:`g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):`. + +We will iterate over whatever matches this query using a :python:`for gold in ...:` loop. +Add the entities ``Gold`` component to the player. +Keep in mind that ``Gold`` is treated like an ``int`` so its usage is predictable. +Now print the current amount of gold using :python:`print(f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g")`. +Then use :python:`gold.clear()` at the end to remove all components and tags from the gold entity which will effectively delete it. + +.. code-block:: python + + ... + def on_event(self, event: tcod.event.Event) -> None: + """Handle events for the in-game state.""" + (player,) = g.world.Q.all_of(tags=[IsPlayer]) + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS: + player.components[Position] += DIRECTION_KEYS[sym] + # Auto pickup gold + for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]): + player.components[Gold] += gold.components[Gold] + print(f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g") + gold.clear() + ... + +Now start with the ``on_draw`` method. +Any entity with both a ``Position`` and a ``Graphic`` is drawable. +Iterate over these entities with :python:`for entity in g.world.Q.all_of(components=[Position, Graphic]):`. +Accessing components can be slow in a loop, so assign components to local names before using them (:python:`pos = entity.components[Position]` and :python:`graphic = entity.components[Graphic]`). + +Check if a components position is in the bounds of the console. +:python:`0 <= pos.x < console.width and 0 <= pos.y < console.height` tells if the position is in bounds. +Instead of nesting this method further, this check should be a guard using :python:`if not (...):` and :python:`continue`. + +Draw the graphic by assigning it to the consoles Numpy array directly with :python:`console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg`. +``console.rgb`` is a ``ch,fg,bg`` array and :python:`[["ch", "fg"]]` narrows it down to only ``ch,fg``. +The array is in C row-major memory order so you access it with yx (or ij) ordering. + +.. code-block:: python + + ... + def on_draw(self, console: tcod.console.Console) -> None: + """Draw the standard screen.""" + for entity in g.world.Q.all_of(components=[Position, Graphic]): + pos = entity.components[Position] + if not (0 <= pos.x < console.width and 0 <= pos.y < console.height): + continue + graphic = entity.components[Graphic] + console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg + +``game/states.py`` should now look like this: + +.. code-block:: python + + """A collection of game states.""" + from __future__ import annotations + + from typing import Final + + import attrs + import tcod.console + import tcod.event + from tcod.event import KeySym + + import g + from game.components import Gold, Graphic, Position + from game.tags import IsItem, IsPlayer + + DIRECTION_KEYS: Final = { + # Arrow keys + KeySym.LEFT: (-1, 0), + KeySym.RIGHT: (1, 0), + KeySym.UP: (0, -1), + KeySym.DOWN: (0, 1), + # Arrow key diagonals + KeySym.HOME: (-1, -1), + KeySym.END: (-1, 1), + KeySym.PAGEUP: (1, -1), + KeySym.PAGEDOWN: (1, 1), + # Keypad + KeySym.KP_4: (-1, 0), + KeySym.KP_6: (1, 0), + KeySym.KP_8: (0, -1), + KeySym.KP_2: (0, 1), + KeySym.KP_7: (-1, -1), + KeySym.KP_1: (-1, 1), + KeySym.KP_9: (1, -1), + KeySym.KP_3: (1, 1), + # VI keys + KeySym.h: (-1, 0), + KeySym.l: (1, 0), + KeySym.k: (0, -1), + KeySym.j: (0, 1), + KeySym.y: (-1, -1), + KeySym.b: (-1, 1), + KeySym.u: (1, -1), + KeySym.n: (1, 1), + } + + + @attrs.define(eq=False) + class InGame: + """Primary in-game state.""" + + def on_event(self, event: tcod.event.Event) -> None: + """Handle events for the in-game state.""" + (player,) = g.world.Q.all_of(tags=[IsPlayer]) + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS: + player.components[Position] += DIRECTION_KEYS[sym] + # Auto pickup gold + for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]): + player.components[Gold] += gold.components[Gold] + print(f"Picked up ${gold.components[Gold]}, total: ${player.components[Gold]}") + gold.clear() + + def on_draw(self, console: tcod.console.Console) -> None: + """Draw the standard screen.""" + for entity in g.world.Q.all_of(components=[Position, Graphic]): + pos = entity.components[Position] + if not (0 <= pos.x < console.width and 0 <= pos.y < console.height): + continue + graphic = entity.components[Graphic] + console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg + +Back to ``main.py``. +At this point you should know which imports to add and which are no longed needed. +``ExampleState`` should be removed. +``g.state`` will be initialized with :python:`[game.states.InGame()]` instead. +Add :python:`g.world = game.world_tools.new_world()`. + +``main.py`` will look like this: + +.. code-block:: python + :emphasize-lines: 5-12,22-23 + + #!/usr/bin/env python3 + """Main entry-point module. This script is used to start the program.""" + from __future__ import annotations + + import tcod.console + import tcod.context + import tcod.tileset + + import g + import game.state_tools + import game.states + import game.world_tools + + + def main() -> None: + """Entry point function.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + tcod.tileset.procedural_block_elements(tileset=tileset) + g.console = tcod.console.Console(80, 50) + g.states = [game.states.InGame()] + g.world = game.world_tools.new_world() + with tcod.context.new(console=g.console, tileset=tileset) as g.context: + game.state_tools.main_loop() + + + if __name__ == "__main__": + main() + +Now you can play a simple game where you wander around collecting gold. + +You can review the part-2 source code `here `_. + +.. rubric:: Footnotes + +.. [#slots] This is done to prevent subclasses from requiring a ``__dict__`` attribute. + If you are still wondering what ``__slots__`` is then `the Python docs have a detailed explanation `_. + +.. [#g] ``global``, ``globals``, and ``glob`` were already taken by keywords, built-ins, and the standard library. + The alternatives are to either put this in the ``game`` namespace or to add an underscore such as ``globals_.py``. + +.. _Protocol: https://mypy.readthedocs.io/en/stable/protocols.html From 336aa0a428180804b572695196a54dc7eb7e1669 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 20 Feb 2024 09:37:34 -0800 Subject: [PATCH 0887/1101] conf.py should always open files relative to script Fixes tests importing this script from other places --- docs/conf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a323f906..f66b990d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,6 +25,8 @@ sys.path.insert(0, str(Path("..").resolve(strict=True))) +THIS_DIR = Path(__file__).parent + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -378,8 +380,8 @@ napoleon_use_param = True napoleon_use_rtype = True -rst_prolog = Path("prolog.rst").read_text(encoding="utf-8") # Added to the beginning of every source file. -rst_epilog = Path("epilog.rst").read_text(encoding="utf-8") # Added to the end of every source file. +rst_prolog = (THIS_DIR / "prolog.rst").read_text(encoding="utf-8") # Added to the beginning of every source file. +rst_epilog = (THIS_DIR / "epilog.rst").read_text(encoding="utf-8") # Added to the end of every source file. # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { From f1c6e5041f477ec84491248f44b44e12715f2a28 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 24 Feb 2024 05:43:23 -0800 Subject: [PATCH 0888/1101] Refactor part 2 and split state changes into part 3 --- docs/tutorial/part-02.rst | 226 +++++++++++--------------------------- docs/tutorial/part-03.rst | 145 ++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 160 deletions(-) create mode 100644 docs/tutorial/part-03.rst diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst index 460e5016..432f4d16 100644 --- a/docs/tutorial/part-02.rst +++ b/docs/tutorial/part-02.rst @@ -5,7 +5,7 @@ Part 2 - Entities .. include:: notice.rst -In part 2 entities will be added and the state system will be refactored to be more generic. +In part 2 entities will be added and a new state will be created to handle them. This part will also begin to split logic into multiple Python modules using a namespace called ``game``. Entities will be handled with an ECS implementation, in this case: `tcod-ecs`_. @@ -24,46 +24,6 @@ Create a new folder called ``game`` and inside the folder create a new python fi This package will be used to organize new modules. -State protocol -============================================================================== - -To have more states than ``ExampleState`` one must use an abstract type which can be used to refer to any state. -In this case a `Protocol`_ will be used, called ``State``. - -Create a new module: ``game/state.py``. -In this module add the class :python:`class State(Protocol):`. -``Protocol`` is from Python's ``typing`` module. -``State`` should have the ``on_event`` and ``on_draw`` methods from ``ExampleState`` but these methods will be empty other than the docstrings describing what they are for. -These methods refer to types from ``tcod`` and those types will need to be imported. -``State`` should also have :python:`__slots__ = ()` [#slots]_ in case the class is used for a subclass. - -``game/state.py`` should look like this: - -.. code-block:: python - - """Base classes for states.""" - from __future__ import annotations - - from typing import Protocol - - import tcod.console - import tcod.event - - - class State(Protocol): - """An abstract game state.""" - - __slots__ = () - - def on_event(self, event: tcod.event.Event) -> None: - """Called on events.""" - - def on_draw(self, console: tcod.console.Console) -> None: - """Called when the state is being drawn.""" - -The ``ExampleState`` class does not need to be updated since it is already a structural subtype of ``State``. -Note that subclasses of ``State`` will never be in same module as ``State``, this will be the same for all abstract classes. - Organizing globals ============================================================================== @@ -73,14 +33,15 @@ Any global variables which might be assigned from other modules will need to a t Create a new module: ``g.py`` [#g]_. This module is exceptional and will be placed at the top-level instead of in the ``game`` folder. -``console`` and ``context`` from ``main.py`` will now be annotated in ``g.py``. -These will not be assigned here, only annotated with a type-hint. +In ``g.py`` import ``tcod.context`` and ``tcod.ecs``. + +``context`` from ``main.py`` will now be annotated in ``g.py`` by adding the line :python:`context: tcod.context.Context` by itself. +Notice that is this only a type-hinted name and nothing is assigned to it. +This means that type-checking will assume the variable always exists but using it before it is assigned will crash at run-time. -A new global will be added: :python:`states: list[game.state.State] = []`. -States are implemented as a list/stack to support `pushdown automata `_. -Representing states as a stack makes it easier to implement popup windows, menus, and other "history aware" states. +``main.py`` should add :python:`import g` and replace the variables named ``context`` with ``g.context``. -Finally :python:`world: tcod.ecs.Registry` will be added to hold the ECS scope. +Then add the :python:`world: tcod.ecs.Registry` global to hold the ECS scope. It is important to document all variables placed in this module with docstrings. @@ -89,107 +50,17 @@ It is important to document all variables placed in this module with docstrings. """This module stores globally mutable variables used by this program.""" from __future__ import annotations - import tcod.console import tcod.context import tcod.ecs - import game.state - - console: tcod.console.Console - """The main console.""" - context: tcod.context.Context """The window managed by tcod.""" - states: list[game.state.State] = [] - """A stack of states with the last item being the active state.""" - world: tcod.ecs.Registry """The active ECS registry and current session.""" -Now other modules can :python:`import g` to access global variables. - Ideally you should not overuse this module for too many things. -When a variables can either be taken as a function parameter or accessed as a global then passing as a parameter is always preferable. - -State functions -============================================================================== - -Create a new module: ``game/state_tools.py``. -This module will handle events and rendering of the global state. - -In this module add the function :python:`def main_draw() -> None:`. -This will hold the "clear, draw, present" logic from the ``main`` function which will be moved to this function. -Render the active state with :python:`g.states[-1].on_draw(g.console)`. -If ``g.states`` is empty then this function should immediately :python:`return` instead of doing anything. -Empty containers in Python are :python:`False` when checked for truthiness. - -Next the function :python:`def main_loop() -> None:` is created. -The :python:`while` loop from ``main`` will be moved to this function. -The while loop will be replaced by :python:`while g.states:` so that this function will exit if no state exists. -Drawing will be replaced by a call to ``main_draw``. -Events in the for-loop will be passed to the active state :python:`g.states[-1].on_event(event)`. -Any states ``on_event`` method could potentially change the state so ``g.states`` must be checked to be non-empty for every handled event. - -.. code-block:: python - - """State handling functions.""" - from __future__ import annotations - - import tcod.console - - import g - - - def main_draw() -> None: - """Render and present the active state.""" - if not g.states: - return - g.console.clear() - g.states[-1].on_draw(g.console) - g.context.present(g.console) - - - def main_loop() -> None: - """Run the active state forever.""" - while g.states: - main_draw() - for event in tcod.event.wait(): - if g.states: - g.states[-1].on_event(event) - -Now ``main.py`` can be edited to use the global variables and the new game loop. - -Add :python:`import g` and :python:`import game.state_tools`. -Replace references to ``console`` with ``g.console``. -Replace references to ``context`` with ``g.context``. - -States are initialed by assigning a list with the initial state to ``g.states``. -The previous game loop is replaced by a call to :python:`game.state_tools.main_loop()`. - -.. code-block:: python - :emphasize-lines: 3-4,12-15 - - ... - - import g - import game.state_tools - - def main() -> None: - """Entry point function.""" - tileset = tcod.tileset.load_tilesheet( - "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 - ) - tcod.tileset.procedural_block_elements(tileset=tileset) - g.console = tcod.console.Console(80, 50) - g.states = [ExampleState(player_x=console.width // 2, player_y=console.height // 2)] - with tcod.context.new(console=g.console, tileset=tileset) as g.context: - game.state_tools.main_loop() - ... - -After this you can test the game. -There should be no visible differences from before. - +When a variable can either be taken as a function parameter or accessed as a global then passing as a parameter is always preferable. ECS tags ============================================================================== @@ -435,7 +306,7 @@ Make sure :python:`return` has the correct indentation and is not part of the fo return world -New in-game state +New InGame state ============================================================================== Now there is a new ECS world but the example state does not know how to render it. @@ -485,7 +356,6 @@ Then add the following: Create a new :python:`class InGame:` decorated with :python:`@attrs.define(eq=False)`. States will always use ``g.world`` to access the ECS registry. -States prefer ``console`` as a parameter over the global ``g.console`` so always use ``console`` when it exists. .. code-block:: python @@ -494,8 +364,8 @@ States prefer ``console`` as a parameter over the global ``g.console`` so always """Primary in-game state.""" ... -Create an ``on_event`` method matching the ``State`` protocol. -Copying these methods from ``State`` or ``ExampleState`` should be enough. +Create an ``on_event`` and ``on_draw`` method matching the ``ExampleState`` class. +Copying ``ExampleState`` and modifying it should be enough since this wil replace ``ExampleState``. Now to do an tcod-ecs query to fetch the player entity. In tcod-ecs queries most often start with :python:`g.world.Q.all_of(components=[], tags=[])`. @@ -520,9 +390,13 @@ The query to see if the player has stepped on gold is to check for whichever ent The query for this is :python:`g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]):`. We will iterate over whatever matches this query using a :python:`for gold in ...:` loop. -Add the entities ``Gold`` component to the player. +Add the entities ``Gold`` component to the players similar component. Keep in mind that ``Gold`` is treated like an ``int`` so its usage is predictable. -Now print the current amount of gold using :python:`print(f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g")`. + +Format the added and total of gold using a Python f-string_: :python:`text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g"`. +Store ``text`` globally in the ECS registry with :python:`g.world[None].components[("Text", str)] = text`. +This is done as two lines to avoid creating a line with an excessive length. + Then use :python:`gold.clear()` at the end to remove all components and tags from the gold entity which will effectively delete it. .. code-block:: python @@ -539,7 +413,8 @@ Then use :python:`gold.clear()` at the end to remove all components and tags fro # Auto pickup gold for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]): player.components[Gold] += gold.components[Gold] - print(f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g") + text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g" + g.world[None].components[str] = text gold.clear() ... @@ -556,6 +431,17 @@ Draw the graphic by assigning it to the consoles Numpy array directly with :pyth ``console.rgb`` is a ``ch,fg,bg`` array and :python:`[["ch", "fg"]]` narrows it down to only ``ch,fg``. The array is in C row-major memory order so you access it with yx (or ij) ordering. +That ends the entity rendering loop. +Next is to print the ``("Text", str)`` component if it exists. +A normal access will raise ``KeyError`` if the component is accessed before being assigned. +This case will be handled by the ``.get`` method of the ``Entity.components`` attribute. +:python:`g.world[None].components.get(("Text", str))` will return :python:`None` instead of raising ``KeyError``. +Assigning this result to ``text`` and then checking :python:`if text:` will ensure that ``text`` within the branch is not None and that the string is not empty. +We will not use ``text`` outside of the branch, so an assignment expression can be used here to check and assign the name at the same time with :python:`if text := g.world[None].components.get(("Text", str)):`. + +In this branch you will print ``text`` to the bottom of the console with a white foreground and black background. +The call to do this is :python:`console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))`. + .. code-block:: python ... @@ -568,6 +454,12 @@ The array is in C row-major memory order so you access it with yx (or ij) orderi graphic = entity.components[Graphic] console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg + if text := g.world[None].components.get(("Text", str)): + console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0)) + +Verify the indentation of the ``if`` branch is correct. +It should be at the same level as the ``for`` loop and not inside of it. + ``game/states.py`` should now look like this: .. code-block:: python @@ -633,7 +525,8 @@ The array is in C row-major memory order so you access it with yx (or ij) orderi # Auto pickup gold for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]): player.components[Gold] += gold.components[Gold] - print(f"Picked up ${gold.components[Gold]}, total: ${player.components[Gold]}") + text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g" + g.world[None].components[("Text", str)] = text gold.clear() def on_draw(self, console: tcod.console.Console) -> None: @@ -645,16 +538,26 @@ The array is in C row-major memory order so you access it with yx (or ij) orderi graphic = entity.components[Graphic] console.rgb[["ch", "fg"]][pos.y, pos.x] = graphic.ch, graphic.fg + if text := g.world[None].components.get(("Text", str)): + console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0)) + +Main script update +============================================================================== + Back to ``main.py``. -At this point you should know which imports to add and which are no longed needed. -``ExampleState`` should be removed. -``g.state`` will be initialized with :python:`[game.states.InGame()]` instead. -Add :python:`g.world = game.world_tools.new_world()`. +At this point you should know to import the modules needed. + +The ``ExampleState`` class is obsolete and will be removed. +``state`` will be created with :python:`game.states.InGame()` instead. + +If you have not replaced ``context`` with ``g.context`` yet then do it now. + +Add :python:`g.world = game.world_tools.new_world()` before the main loop. ``main.py`` will look like this: .. code-block:: python - :emphasize-lines: 5-12,22-23 + :emphasize-lines: 10-12,22-24,28 #!/usr/bin/env python3 """Main entry-point module. This script is used to start the program.""" @@ -662,10 +565,10 @@ Add :python:`g.world = game.world_tools.new_world()`. import tcod.console import tcod.context + import tcod.event import tcod.tileset import g - import game.state_tools import game.states import game.world_tools @@ -676,11 +579,17 @@ Add :python:`g.world = game.world_tools.new_world()`. "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 ) tcod.tileset.procedural_block_elements(tileset=tileset) - g.console = tcod.console.Console(80, 50) - g.states = [game.states.InGame()] + console = tcod.console.Console(80, 50) + state = game.states.InGame() g.world = game.world_tools.new_world() - with tcod.context.new(console=g.console, tileset=tileset) as g.context: - game.state_tools.main_loop() + with tcod.context.new(console=console, tileset=tileset) as g.context: + while True: # Main loop + console.clear() # Clear the console before any drawing + state.on_draw(console) # Draw the current state + g.context.present(console) # Render the console to the window and show it + for event in tcod.event.wait(): # Event loop, blocks until pending events exist + print(event) + state.on_event(event) # Dispatch events to the state if __name__ == "__main__": @@ -692,10 +601,7 @@ You can review the part-2 source code `here `_. - .. [#g] ``global``, ``globals``, and ``glob`` were already taken by keywords, built-ins, and the standard library. The alternatives are to either put this in the ``game`` namespace or to add an underscore such as ``globals_.py``. -.. _Protocol: https://mypy.readthedocs.io/en/stable/protocols.html +.. _f-string: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals diff --git a/docs/tutorial/part-03.rst b/docs/tutorial/part-03.rst new file mode 100644 index 00000000..c3918521 --- /dev/null +++ b/docs/tutorial/part-03.rst @@ -0,0 +1,145 @@ +.. _part-3: + +Part 3 - UI State +############################################################################## + +.. include:: notice.rst + +.. warning:: + + **This part is still a draft and is being worked on. + Sections here will be incorrect as these examples were hastily moved from an earlier part.** + + +State protocol +============================================================================== + +To have more states than ``ExampleState`` one must use an abstract type which can be used to refer to any state. +In this case a `Protocol`_ will be used, called ``State``. + +Create a new module: ``game/state.py``. +In this module add the class :python:`class State(Protocol):`. +``Protocol`` is from Python's ``typing`` module. +``State`` should have the ``on_event`` and ``on_draw`` methods from ``ExampleState`` but these methods will be empty other than the docstrings describing what they are for. +These methods refer to types from ``tcod`` and those types will need to be imported. +``State`` should also have :python:`__slots__ = ()` [#slots]_ in case the class is used for a subclass. + +``game/state.py`` should look like this: + +.. code-block:: python + + """Base classes for states.""" + from __future__ import annotations + + from typing import Protocol + + import tcod.console + import tcod.event + + + class State(Protocol): + """An abstract game state.""" + + __slots__ = () + + def on_event(self, event: tcod.event.Event) -> None: + """Called on events.""" + + def on_draw(self, console: tcod.console.Console) -> None: + """Called when the state is being drawn.""" + +The ``InGame`` class does not need to be updated since it is already a structural subtype of ``State``. +Note that subclasses of ``State`` will never be in same module as ``State``, this will be the same for all abstract classes. + +State globals +============================================================================== + +A new global will be added: :python:`states: list[game.state.State] = []`. +States are implemented as a list/stack to support `pushdown automata `_. +Representing states as a stack makes it easier to implement popup windows, menus, and other "history aware" states. + +State functions +============================================================================== + +Create a new module: ``game/state_tools.py``. +This module will handle events and rendering of the global state. + +In this module add the function :python:`def main_draw() -> None:`. +This will hold the "clear, draw, present" logic from the ``main`` function which will be moved to this function. +Render the active state with :python:`g.states[-1].on_draw(g.console)`. +If ``g.states`` is empty then this function should immediately :python:`return` instead of doing anything. +Empty containers in Python are :python:`False` when checked for truthiness. + +Next the function :python:`def main_loop() -> None:` is created. +The :python:`while` loop from ``main`` will be moved to this function. +The while loop will be replaced by :python:`while g.states:` so that this function will exit if no state exists. +Drawing will be replaced by a call to ``main_draw``. +Events in the for-loop will be passed to the active state :python:`g.states[-1].on_event(event)`. +Any states ``on_event`` method could potentially change the state so ``g.states`` must be checked to be non-empty for every handled event. + +.. code-block:: python + + """State handling functions.""" + from __future__ import annotations + + import tcod.console + + import g + + + def main_draw() -> None: + """Render and present the active state.""" + if not g.states: + return + g.console.clear() + g.states[-1].on_draw(g.console) + g.context.present(g.console) + + + def main_loop() -> None: + """Run the active state forever.""" + while g.states: + main_draw() + for event in tcod.event.wait(): + if g.states: + g.states[-1].on_event(event) + +Now ``main.py`` can be edited to use the global variables and the new game loop. + +Add :python:`import g` and :python:`import game.state_tools`. +Replace references to ``console`` with ``g.console``. +Replace references to ``context`` with ``g.context``. + +States are initialed by assigning a list with the initial state to ``g.states``. +The previous game loop is replaced by a call to :python:`game.state_tools.main_loop()`. + +.. code-block:: python + :emphasize-lines: 3-4,12-15 + + ... + + import g + import game.state_tools + + def main() -> None: + """Entry point function.""" + tileset = tcod.tileset.load_tilesheet( + "data/Alloy_curses_12x12.png", columns=16, rows=16, charmap=tcod.tileset.CHARMAP_CP437 + ) + tcod.tileset.procedural_block_elements(tileset=tileset) + g.console = tcod.console.Console(80, 50) + g.states = [ExampleState(player_x=console.width // 2, player_y=console.height // 2)] + with tcod.context.new(console=g.console, tileset=tileset) as g.context: + game.state_tools.main_loop() + ... + +After this you can test the game. +There should be no visible differences from before. + + +.. rubric:: Footnotes + +.. [#slots] This is done to prevent subclasses from requiring a ``__dict__`` attribute. + If you are still wondering what ``__slots__`` is then `the Python docs have a detailed explanation `_. + +.. _Protocol: https://mypy.readthedocs.io/en/stable/protocols.html From 4c0feb51e1e51dddb90ddc509fcc75ebf91b5c7d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 23:45:34 +0000 Subject: [PATCH 0889/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.2 → v0.3.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.2...v0.3.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7badadae..718837ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.3.2 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 396f279204b0f4b972cb856d5f8c8a35034f857e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 23:45:41 +0000 Subject: [PATCH 0890/1101] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- build_libtcod.py | 1 + build_sdl.py | 1 + examples/audio_tone.py | 1 + examples/cavegen.py | 1 + examples/distribution/PyInstaller/main.py | 1 + examples/distribution/cx_Freeze/main.py | 1 + examples/eventget.py | 1 + examples/framerate.py | 1 + examples/samples_tcod.py | 1 + examples/sdl-hello-world.py | 1 + examples/thread_jobs.py | 1 + examples/ttf.py | 1 + libtcodpy.py | 1 + scripts/generate_charmap_table.py | 1 + scripts/get_release_description.py | 1 + scripts/tag_release.py | 1 + setup.py | 1 + tcod/__init__.py | 1 + tcod/__pyinstaller/__init__.py | 1 + tcod/__pyinstaller/hook-tcod.py | 1 + tcod/_internal.py | 1 + tcod/bsp.py | 1 + tcod/cffi.py | 1 + tcod/color.py | 1 + tcod/console.py | 1 + tcod/constants.py | 1 + tcod/context.py | 1 + tcod/event.py | 24 ++++------------------- tcod/image.py | 1 + tcod/libtcodpy.py | 9 ++------- tcod/los.py | 1 + tcod/map.py | 1 + tcod/noise.py | 1 + tcod/path.py | 1 + tcod/random.py | 1 + tcod/sdl/__init__.py | 1 + tcod/sdl/_internal.py | 1 + tcod/sdl/audio.py | 1 + tcod/sdl/joystick.py | 1 + tcod/sdl/mouse.py | 1 + tcod/sdl/render.py | 1 + tcod/sdl/video.py | 1 + tcod/tcod.py | 1 + tcod/tileset.py | 1 + tests/conftest.py | 1 + tests/test_console.py | 1 + tests/test_deprecated.py | 1 + tests/test_libtcodpy.py | 1 + tests/test_noise.py | 1 + tests/test_parser.py | 1 + tests/test_random.py | 1 + tests/test_sdl.py | 1 + tests/test_sdl_audio.py | 1 + tests/test_tcod.py | 1 + tests/test_tileset.py | 1 + 55 files changed, 59 insertions(+), 27 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 56161182..2ffe6aad 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Parse and compile libtcod and SDL sources for CFFI.""" + from __future__ import annotations import contextlib diff --git a/build_sdl.py b/build_sdl.py index 56fa87de..629c98f4 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Build script to parse SDL headers and generate CFFI bindings.""" + from __future__ import annotations import io diff --git a/examples/audio_tone.py b/examples/audio_tone.py index 052f8a39..22f21133 100755 --- a/examples/audio_tone.py +++ b/examples/audio_tone.py @@ -3,6 +3,7 @@ Opens an audio device using SDL and plays a square wave for 1 second. """ + import math import time from typing import Any diff --git a/examples/cavegen.py b/examples/cavegen.py index 4ae22ac3..8b95d3fc 100755 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -6,6 +6,7 @@ This will print the result to the console, so be sure to run this from the command line. """ + from typing import Any import numpy as np diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index 51e31e79..b2767fed 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -4,6 +4,7 @@ # example script. This work is published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ """PyInstaller main script example.""" + import sys from pathlib import Path diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index 0c85ca83..79846377 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """cx_Freeze main script example.""" + import tcod.console import tcod.context import tcod.event diff --git a/examples/eventget.py b/examples/eventget.py index 0af84d04..ada61668 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -4,6 +4,7 @@ # published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ """An demonstration of event handling using the tcod.event module.""" + from typing import List, Set import tcod.context diff --git a/examples/framerate.py b/examples/framerate.py index a51f83ff..25a1dd3f 100755 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -4,6 +4,7 @@ # published from: United States. # https://creativecommons.org/publicdomain/zero/1.0/ """A system to control time since the original libtcod tools are deprecated.""" + import statistics import time from collections import deque diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 7baf6af2..d2a8cb86 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """This code demonstrates various usages of python-tcod.""" + # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to these samples. # https://creativecommons.org/publicdomain/zero/1.0/ diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index b9c07547..4e314805 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -1,4 +1,5 @@ """Hello world using tcod's SDL API and using Pillow for the TTF rendering.""" + from pathlib import Path import numpy as np diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index fa99a465..0c616bba 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -13,6 +13,7 @@ Typically the field-of-view tasks run good but not great, and the path-finding tasks run poorly. """ + import concurrent.futures import multiprocessing import platform diff --git a/examples/ttf.py b/examples/ttf.py index fb0336cb..3e13a837 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -5,6 +5,7 @@ pip install freetype-py """ + # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to this example script. # https://creativecommons.org/publicdomain/zero/1.0/ diff --git a/libtcodpy.py b/libtcodpy.py index 7947f394..76317e99 100644 --- a/libtcodpy.py +++ b/libtcodpy.py @@ -1,4 +1,5 @@ """Deprecated module alias for tcod.libtcodpy, use 'import tcod as libtcodpy' instead.""" + import warnings from tcod.libtcodpy import * # noqa: F403 diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index c4137e8a..50b6a779 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -3,6 +3,7 @@ Uses the tabulate module from PyPI. """ + from __future__ import annotations import argparse diff --git a/scripts/get_release_description.py b/scripts/get_release_description.py index 547f8ca6..889feced 100755 --- a/scripts/get_release_description.py +++ b/scripts/get_release_description.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Print the description used for GitHub Releases.""" + from __future__ import annotations import re diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 7b946513..2155aaae 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Automate tagged releases of this project.""" + from __future__ import annotations import argparse diff --git a/setup.py b/setup.py index 6e17d0f8..29f9cb3d 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Python-tcod setup script.""" + from __future__ import annotations import platform diff --git a/tcod/__init__.py b/tcod/__init__.py index b8ea0d25..63335ebc 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -6,6 +6,7 @@ Read the documentation online: https://python-tcod.readthedocs.io/en/latest/ """ + from __future__ import annotations from pkgutil import extend_path diff --git a/tcod/__pyinstaller/__init__.py b/tcod/__pyinstaller/__init__.py index 7224bb10..2b67c1c0 100644 --- a/tcod/__pyinstaller/__init__.py +++ b/tcod/__pyinstaller/__init__.py @@ -1,4 +1,5 @@ """PyInstaller entry point for tcod.""" + from __future__ import annotations from pathlib import Path diff --git a/tcod/__pyinstaller/hook-tcod.py b/tcod/__pyinstaller/hook-tcod.py index 9790d583..b0cabf38 100644 --- a/tcod/__pyinstaller/hook-tcod.py +++ b/tcod/__pyinstaller/hook-tcod.py @@ -5,6 +5,7 @@ If this hook is ever modified then the contributed hook needs to be removed from: https://github.com/pyinstaller/pyinstaller-hooks-contrib """ + from PyInstaller.utils.hooks import collect_dynamic_libs # type: ignore hiddenimports = ["_cffi_backend"] diff --git a/tcod/_internal.py b/tcod/_internal.py index ef6305bf..cebe249e 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -1,4 +1,5 @@ """Internal helper functions used by the rest of the library.""" + from __future__ import annotations import functools diff --git a/tcod/bsp.py b/tcod/bsp.py index cfb6c021..030bb04b 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -24,6 +24,7 @@ else: print('Dig a room for %s.' % node) """ + from __future__ import annotations from typing import Any, Iterator diff --git a/tcod/cffi.py b/tcod/cffi.py index d733abea..19a597b6 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -1,4 +1,5 @@ """This module handles loading of the libtcod cffi API.""" + from __future__ import annotations import logging diff --git a/tcod/color.py b/tcod/color.py index 37312413..ef75bfb8 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -1,4 +1,5 @@ """Old libtcod color management.""" + from __future__ import annotations import warnings diff --git a/tcod/console.py b/tcod/console.py index 3512c893..5cd80c3b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -4,6 +4,7 @@ To render a console you need a tileset and a window to render to. See :ref:`getting-started` for info on how to set those up. """ + from __future__ import annotations import warnings diff --git a/tcod/constants.py b/tcod/constants.py index f0a596dc..98f1ef1f 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -2,6 +2,7 @@ This module is auto-generated by `build_libtcod.py`. """ + from tcod.color import Color FOV_BASIC = 0 diff --git a/tcod/context.py b/tcod/context.py index 8d5e5259..f22c78de 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -22,6 +22,7 @@ .. versionadded:: 11.12 """ + from __future__ import annotations import copy diff --git a/tcod/event.py b/tcod/event.py index 41623a93..cb48b047 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -79,6 +79,7 @@ .. versionadded:: 8.4 """ + from __future__ import annotations import enum @@ -444,12 +445,7 @@ def tile(self, xy: tuple[int, int]) -> None: self._tile = Point(*xy) def __repr__(self) -> str: - return ("tcod.event.{}(position={!r}, tile={!r}, state={})").format( - self.__class__.__name__, - tuple(self.position), - tuple(self.tile), - MouseButtonMask(self.state), - ) + return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self.tile)!r}, state={MouseButtonMask(self.state)})" def __str__(self) -> str: return ("<%s, position=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( @@ -554,14 +550,7 @@ def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: return self def __repr__(self) -> str: - return ("tcod.event.{}(position={!r}, motion={!r}, tile={!r}, tile_motion={!r}, state={!r})").format( - self.__class__.__name__, - tuple(self.position), - tuple(self.motion), - tuple(self.tile), - tuple(self.tile_motion), - MouseButtonMask(self.state), - ) + return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, motion={tuple(self.motion)!r}, tile={tuple(self.tile)!r}, tile_motion={tuple(self.tile_motion)!r}, state={MouseButtonMask(self.state)!r})" def __str__(self) -> str: return ("<%s, motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>") % ( @@ -621,12 +610,7 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: return self def __repr__(self) -> str: - return "tcod.event.{}(position={!r}, tile={!r}, button={!r})".format( - self.__class__.__name__, - tuple(self.position), - tuple(self.tile), - MouseButton(self.button), - ) + return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self.tile)!r}, button={MouseButton(self.button)!r})" def __str__(self) -> str: return " str: ) def __repr__(self) -> str: - return "{}(nb_dices={!r},nb_faces={!r},multiplier={!r},addsub={!r})".format( - self.__class__.__name__, - self.nb_dices, - self.nb_faces, - self.multiplier, - self.addsub, - ) + return f"{self.__class__.__name__}(nb_dices={self.nb_dices!r},nb_faces={self.nb_faces!r},multiplier={self.multiplier!r},addsub={self.addsub!r})" # reverse lookup table for KEY_X attributes, used by Key.__repr__ diff --git a/tcod/los.py b/tcod/los.py index d6cd1362..6d30d0b8 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -1,4 +1,5 @@ """This modules holds functions for NumPy-based line of sight algorithms.""" + from __future__ import annotations from typing import Any diff --git a/tcod/map.py b/tcod/map.py index b339cee1..fc3c24cf 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -1,4 +1,5 @@ """libtcod map attributes and field-of-view functions.""" + from __future__ import annotations import warnings diff --git a/tcod/noise.py b/tcod/noise.py index 66cba1d1..22f5d9c2 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -31,6 +31,7 @@ [ 76, 54, 85, 144, 164], [ 63, 94, 159, 209, 203]], dtype=uint8) """ + from __future__ import annotations import enum diff --git a/tcod/path.py b/tcod/path.py index f708a072..5765f233 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -15,6 +15,7 @@ All path-finding functions now respect the NumPy array shape (if a NumPy array is used.) """ + from __future__ import annotations import functools diff --git a/tcod/random.py b/tcod/random.py index 148331df..73121578 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -6,6 +6,7 @@ However, you will need to use these generators to get deterministic results from the :any:`Noise` and :any:`BSP` classes. """ + from __future__ import annotations import os diff --git a/tcod/sdl/__init__.py b/tcod/sdl/__init__.py index 8133948b..22e82845 100644 --- a/tcod/sdl/__init__.py +++ b/tcod/sdl/__init__.py @@ -1,4 +1,5 @@ """tcod.sdl package.""" + from pkgutil import extend_path __path__ = extend_path(__path__, __name__) diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 761ca9a1..8ea5e13f 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -1,4 +1,5 @@ """tcod.sdl private functions.""" + from __future__ import annotations import logging diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 8ed3fd38..33356132 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -41,6 +41,7 @@ .. versionadded:: 13.5 """ + from __future__ import annotations import enum diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index b65d7339..d5c66713 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -2,6 +2,7 @@ .. versionadded:: 13.8 """ + from __future__ import annotations import enum diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index d212afb6..fefd71d7 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -6,6 +6,7 @@ .. versionadded:: 13.5 """ + from __future__ import annotations import enum diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 2e879c4d..c57d2242 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -2,6 +2,7 @@ .. versionadded:: 13.4 """ + from __future__ import annotations import enum diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 41fec458..53a8e2db 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -6,6 +6,7 @@ .. versionadded:: 13.4 """ + from __future__ import annotations import enum diff --git a/tcod/tcod.py b/tcod/tcod.py index 6fab4ff4..8181da79 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -6,6 +6,7 @@ Read the documentation online: https://python-tcod.readthedocs.io/en/latest/ """ + from __future__ import annotations import warnings diff --git a/tcod/tileset.py b/tcod/tileset.py index 4f1aeef8..846aed32 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -10,6 +10,7 @@ `_ while continuing to use python-tcod's pathfinding and field-of-view algorithms. """ + from __future__ import annotations import itertools diff --git a/tests/conftest.py b/tests/conftest.py index 83e2dc8e..2998b4ae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ """Test directory configuration.""" + import random import warnings from typing import Callable, Iterator, Union diff --git a/tests/test_console.py b/tests/test_console.py index cd6df8f7..e329fe6f 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -1,4 +1,5 @@ """Tests for tcod.console.""" + import pickle from pathlib import Path diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 356de7d3..869c2b36 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -1,4 +1,5 @@ """Test deprecated features.""" + from __future__ import annotations import numpy as np diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 50c8be7e..676ab2bc 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,4 +1,5 @@ """Tests for the libtcodpy API.""" + from pathlib import Path from typing import Any, Callable, Iterator, List, Optional, Tuple, Union diff --git a/tests/test_noise.py b/tests/test_noise.py index 0f64d5fe..72149062 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -1,4 +1,5 @@ """Tests for the tcod.noise module.""" + import copy import pickle diff --git a/tests/test_parser.py b/tests/test_parser.py index ce200166..07f736a1 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,4 +1,5 @@ """Test old libtcodpy parser.""" + from pathlib import Path from typing import Any diff --git a/tests/test_random.py b/tests/test_random.py index e6b64e1e..5bed9d95 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,4 +1,5 @@ """Test random number generators.""" + import copy import pickle from pathlib import Path diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 709d3647..f22b7a72 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -1,4 +1,5 @@ """Test SDL specific features.""" + import sys import numpy as np diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index 6441bda1..f7e55093 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -1,4 +1,5 @@ """Test tcod.sdl.audio module.""" + import contextlib import sys import time diff --git a/tests/test_tcod.py b/tests/test_tcod.py index aee98c0c..d15b145d 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -1,4 +1,5 @@ """Tests for newer tcod API.""" + import copy import pickle from typing import Any, NoReturn diff --git a/tests/test_tileset.py b/tests/test_tileset.py index aba88b98..dd272fe7 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -1,4 +1,5 @@ """Test for tcod.tileset module.""" + import tcod.tileset # ruff: noqa: D103 From c7d71e58f7b80a6ba5cd8ce77d0e38846d4e72e1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 12 Mar 2024 22:16:20 -0700 Subject: [PATCH 0891/1101] Fix pyproject Ruff deprecations --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b4c0490a..97ee6190 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,7 +146,10 @@ module = "tcod._libtcod" ignore_missing_imports = true [tool.ruff] -# https://beta.ruff.rs/docs/rules/ +extend-exclude = ["libtcod"] # Ignore submodule +line-length = 120 + +[tool.ruff.lint] # https://docs.astral.sh/ruff/rules/ select = [ "C90", # mccabe "E", # pycodestyle @@ -190,9 +193,6 @@ ignore = [ "D206", # indent-with-spaces "W191", # tab-indentation ] -extend-exclude = ["libtcod"] # Ignore submodule -line-length = 120 -[tool.ruff.pydocstyle] -# Use Google-style docstrings. +[tool.ruff.lint.pydocstyle] # https://docs.astral.sh/ruff/settings/#lintpydocstyle convention = "google" From 5df804c91fc221805bfb59aee7a12f7571bbb91b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:14:04 +0000 Subject: [PATCH 0892/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.2 → v0.3.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.2...v0.3.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 718837ab..02c3b670 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.2 + rev: v0.3.3 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 6fce3dd4fbd852db21d22e7e39972ab9bf3ef08a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:00:31 +0000 Subject: [PATCH 0893/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.3 → v0.3.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.3...v0.3.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02c3b670..2f877f6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.3 + rev: v0.3.4 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 40c0c865c37960e6f063812df49306dccbf997cb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Mar 2024 14:59:44 -0700 Subject: [PATCH 0894/1101] Reformat yml --- .github/workflows/python-package.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8eeac91c..9f625fb2 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -63,7 +63,6 @@ jobs: path: dist/tcod-*.tar.gz retention-days: 7 - # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: needs: [ruff, mypy] @@ -290,15 +289,15 @@ jobs: retention-days: 7 publish: - needs: [sdist, build, build-macos, linux-wheels] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - environment: - name: release - url: https://pypi.org/p/tcod - permissions: - id-token: write - steps: + needs: [sdist, build, build-macos, linux-wheels] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + environment: + name: release + url: https://pypi.org/p/tcod + permissions: + id-token: write + steps: - uses: actions/download-artifact@v3 with: name: sdist From 729448991b222dd0d4301fd1a113937a9c7f3a1e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 25 Mar 2024 15:00:38 -0700 Subject: [PATCH 0895/1101] Set correct version on MacOS wheels --- .github/workflows/python-package.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9f625fb2..844f451a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -273,7 +273,7 @@ jobs: # Downloads SDL2 for the later step. run: python3 build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.12.3 + uses: pypa/cibuildwheel@v2.17.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 @@ -281,6 +281,7 @@ jobs: CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod.context" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" + MACOSX_DEPLOYMENT_TARGET: "10.11" - name: Archive wheel uses: actions/upload-artifact@v3 with: From f4d3b1b8cca991cd788574802da9b002bf0dd9db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:14:16 +0000 Subject: [PATCH 0896/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.4 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.3.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f877f6e..40c8dba0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.3.5 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From b813146e38030fc696451f269b16f2057b9052d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:14:23 +0000 Subject: [PATCH 0897/1101] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tcod/event.py | 2 +- tcod/libtcodpy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index cb48b047..0dc40dbe 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -2805,7 +2805,7 @@ def __getattr__(name: str) -> int: return value -__all__ = [ # noqa: F405 +__all__ = [ "Modifier", "Point", "BUTTON_LEFT", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index dd23de45..13c9e5e3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4285,7 +4285,7 @@ def __getattr__(name: str) -> Color: raise AttributeError(msg) from None -__all__ = [ # noqa: F405 +__all__ = [ "Color", "Bsp", "NB_FOV_ALGORITHMS", From 8d39359f38854e0c298f7c0a590f3738445b8f1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:33:30 +0000 Subject: [PATCH 0898/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40c8dba0..e8b671c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer From 9c510f0130a1cc6ad8665466045c3fef9be23823 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 21:07:48 +0000 Subject: [PATCH 0899/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.4.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8b671c1..8411e3fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.4.3 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From f166816d5ee1396a528d33be1883f7e226b491a9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 9 May 2024 00:15:39 -0700 Subject: [PATCH 0900/1101] Skip checking events when the SDL event subsystem is offline. Fixes a known access violation. --- CHANGELOG.md | 4 ++++ tcod/event.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd852e6..10bed531 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- Fixed access violation when events are polled before SDL is initialized. + ## [16.2.2] - 2024-01-16 ### Fixed diff --git a/tcod/event.py b/tcod/event.py index 0dc40dbe..c4592855 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -92,6 +92,7 @@ import tcod.event_constants import tcod.sdl.joystick +import tcod.sdl.sys from tcod.cffi import ffi, lib from tcod.event_constants import * # noqa: F403 from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT @@ -1196,6 +1197,13 @@ def get() -> Iterator[Any]: It is also safe to call this function inside of a loop that is already handling events (the event iterator is reentrant.) """ + if not lib.SDL_WasInit(tcod.sdl.sys.Subsystem.EVENTS): + warnings.warn( + "Events polled before SDL was initialized.", + RuntimeWarning, + stacklevel=1, + ) + return sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): if sdl_event.type in _SDL_TO_CLASS_TABLE: From ffa1a27690031db205b06cb8dd9f373713232bc1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 9 May 2024 00:31:08 -0700 Subject: [PATCH 0901/1101] Update Pillow code in example New version of Pillow has type hints and breaking changes. --- .vscode/settings.json | 1 + examples/sdl-hello-world.py | 7 ++++--- requirements.txt | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 62ea12d3..a6b3de95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -163,6 +163,7 @@ "GAMECONTROLLER", "gamepad", "genindex", + "getbbox", "GFORCE", "GLES", "globaltoc", diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index 4e314805..6afd02ab 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -3,7 +3,7 @@ from pathlib import Path import numpy as np -from PIL import Image, ImageDraw, ImageFont # type: ignore # pip install Pillow +from PIL import Image, ImageDraw, ImageFont # pip install Pillow import tcod.event import tcod.sdl.render @@ -15,8 +15,9 @@ def render_text(renderer: tcod.sdl.render.Renderer, text: str) -> tcod.sdl.render.Texture: """Render text, upload it to VRAM, then return it as an SDL Texture.""" - # Use Pillow normally to render the font. This code is standard. - width, height = font.getsize(text) + # Use Pillow to render the font. + _left, _top, right, bottom = font.getbbox(text) + width, height = right, bottom image = Image.new("RGBA", (width, height)) draw = ImageDraw.Draw(image) draw.text((0, 0), text, font=font) diff --git a/requirements.txt b/requirements.txt index eb2f7d65..a94c3862 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ requests>=2.28.1 setuptools==65.5.1 types-cffi types-requests +types-Pillow types-setuptools types-tabulate typing_extensions From 0684c0830409f9057a084c1057ba04447e147c8a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 9 May 2024 00:37:48 -0700 Subject: [PATCH 0902/1101] Update deprecated VSCode configuration --- .vscode/launch.json | 14 +++++++------- .vscode/settings.json | 8 +------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0f237387..a4d5d9e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,24 +6,24 @@ "configurations": [ { "name": "Python: Current File", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${file}", - "console": "internalConsole", + "console": "integratedTerminal", }, { // Run the Python samples. // tcod will be built and installed in editalbe mode. "name": "Python: Python samples", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/examples/samples_tcod.py", "cwd": "${workspaceFolder}/examples", - "console": "internalConsole", + "console": "integratedTerminal", }, { "name": "Python: Run tests", - "type": "python", + "type": "debugpy", "request": "launch", "module": "pytest", "preLaunchTask": "develop python-tcod", @@ -31,7 +31,7 @@ { "name": "Documentation: Launch Chrome", "request": "launch", - "type": "pwa-chrome", + "type": "chrome", "url": "file://${workspaceFolder}/docs/_build/html/index.html", "webRoot": "${workspaceFolder}", "preLaunchTask": "build documentation", @@ -39,7 +39,7 @@ { "name": "Documentation: Launch Edge", "request": "launch", - "type": "pwa-msedge", + "type": "msedge", "url": "file://${workspaceFolder}/docs/_build/html/index.html", "webRoot": "${workspaceFolder}", "preLaunchTask": "build documentation", diff --git a/.vscode/settings.json b/.vscode/settings.json index a6b3de95..39cb6ad5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,16 +8,10 @@ "source.fixAll": "always", "source.organizeImports": "never" }, + "cmake.configureOnOpen": false, "files.trimFinalNewlines": true, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, - "python.linting.enabled": true, - "python.linting.flake8Enabled": false, - "python.linting.mypyEnabled": true, - "python.linting.mypyArgs": [ - "--follow-imports=silent", - "--show-column-numbers" - ], "files.associations": { "*.spec": "python", }, From 61ce951b390a0a53f84ad3d22810a2017bc48c07 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 9 May 2024 00:52:57 -0700 Subject: [PATCH 0903/1101] Silence several Ruff errors --- tcod/event.py | 17 +++++++---------- tcod/libtcodpy.py | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index c4592855..5d7e9aef 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -174,16 +174,11 @@ def _verify_tile_coordinates(xy: Point | None) -> Point: return Point(0, 0) -_is_sdl_video_initialized = False - - def _init_sdl_video() -> None: """Keyboard layout stuff needs SDL to be initialized first.""" - global _is_sdl_video_initialized - if _is_sdl_video_initialized: + if lib.SDL_WasInit(lib.SDL_INIT_VIDEO): return lib.SDL_InitSubSystem(lib.SDL_INIT_VIDEO) - _is_sdl_video_initialized = True class Modifier(enum.IntFlag): @@ -744,7 +739,7 @@ def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}(type={self.type!r})" - __WINDOW_TYPES = { + __WINDOW_TYPES: Final = { lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", lib.SDL_WINDOWEVENT_HIDDEN: "WindowHidden", lib.SDL_WINDOWEVENT_EXPOSED: "WindowExposed", @@ -1592,7 +1587,9 @@ def handle_events(event: tcod.event.Event) -> None: .. versionadded:: 13.4 """ if callback in _event_watch_handles: - warnings.warn(f"{callback} is already an active event watcher, nothing was added.", RuntimeWarning) + warnings.warn( + f"{callback} is already an active event watcher, nothing was added.", RuntimeWarning, stacklevel=2 + ) return callback handle = _event_watch_handles[callback] = ffi.new_handle(callback) lib.SDL_AddEventWatch(lib._sdl_event_watcher, handle) @@ -1609,7 +1606,7 @@ def remove_watch(callback: Callable[[Event], None]) -> None: .. versionadded:: 13.4 """ if callback not in _event_watch_handles: - warnings.warn(f"{callback} is not an active event watcher, nothing was removed.", RuntimeWarning) + warnings.warn(f"{callback} is not an active event watcher, nothing was removed.", RuntimeWarning, stacklevel=2) return handle = _event_watch_handles[callback] lib.SDL_DelEventWatch(lib._sdl_event_watcher, handle) @@ -2813,7 +2810,7 @@ def __getattr__(name: str) -> int: return value -__all__ = [ +__all__ = [ # noqa: F405 "Modifier", "Point", "BUTTON_LEFT", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 13c9e5e3..dd23de45 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4285,7 +4285,7 @@ def __getattr__(name: str) -> Color: raise AttributeError(msg) from None -__all__ = [ +__all__ = [ # noqa: F405 "Color", "Bsp", "NB_FOV_ALGORITHMS", From 835ca64e344126de3e966f3f5cf1c18b944b68ab Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Jun 2024 16:48:07 -0700 Subject: [PATCH 0904/1101] Docs: add newline between module docstrings and future imports --- docs/tutorial/part-02.rst | 6 ++++++ docs/tutorial/part-03.rst | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst index 432f4d16..c939f88a 100644 --- a/docs/tutorial/part-02.rst +++ b/docs/tutorial/part-02.rst @@ -48,6 +48,7 @@ It is important to document all variables placed in this module with docstrings. .. code-block:: python """This module stores globally mutable variables used by this program.""" + from __future__ import annotations import tcod.context @@ -80,6 +81,7 @@ The ``game/tags.py`` module should look like this: .. code-block:: python """Collection of common tags.""" + from __future__ import annotations from typing import Final @@ -184,6 +186,7 @@ The ``game/components.py`` module should look like this: .. code-block:: python """Collection of common components.""" + from __future__ import annotations from typing import Final, Self @@ -275,6 +278,7 @@ Make sure :python:`return` has the correct indentation and is not part of the fo .. code-block:: python """Functions for working with worlds.""" + from __future__ import annotations from random import Random @@ -465,6 +469,7 @@ It should be at the same level as the ``for`` loop and not inside of it. .. code-block:: python """A collection of game states.""" + from __future__ import annotations from typing import Final @@ -561,6 +566,7 @@ Add :python:`g.world = game.world_tools.new_world()` before the main loop. #!/usr/bin/env python3 """Main entry-point module. This script is used to start the program.""" + from __future__ import annotations import tcod.console diff --git a/docs/tutorial/part-03.rst b/docs/tutorial/part-03.rst index c3918521..a70aadeb 100644 --- a/docs/tutorial/part-03.rst +++ b/docs/tutorial/part-03.rst @@ -29,6 +29,7 @@ These methods refer to types from ``tcod`` and those types will need to be impor .. code-block:: python """Base classes for states.""" + from __future__ import annotations from typing import Protocol @@ -80,6 +81,7 @@ Any states ``on_event`` method could potentially change the state so ``g.states` .. code-block:: python """State handling functions.""" + from __future__ import annotations import tcod.console From 69fd5fa8e34f3aa31a0e96a67925ea43a07527c3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Jun 2024 17:21:31 -0700 Subject: [PATCH 0905/1101] Fix tests for newer Numpy versions New versions of Numpy return different types than before --- tcod/console.py | 12 ++++++------ tcod/map.py | 2 +- tcod/noise.py | 2 +- tcod/path.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 5cd80c3b..c6767b59 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -313,8 +313,8 @@ def rgba(self) -> NDArray[Any]: ... (*WHITE, 255), ... (*BLACK, 255), ... ) - >>> con.rgba[0, 0] - (88, [255, 255, 255, 255], [ 0, 0, 0, 255]) + >>> print(f"{con.rgba[0, 0]=}") + con.rgba[0, 0]=...(88, [255, 255, 255, 255], [ 0, 0, 0, 255])... .. versionadded:: 12.3 """ @@ -334,11 +334,11 @@ def rgb(self) -> NDArray[Any]: >>> con = tcod.console.Console(10, 2) >>> BLUE, YELLOW, BLACK = (0, 0, 255), (255, 255, 0), (0, 0, 0) >>> con.rgb[0, 0] = ord("@"), YELLOW, BLACK - >>> con.rgb[0, 0] - (64, [255, 255, 0], [0, 0, 0]) + >>> print(f"{con.rgb[0, 0]=}") + con.rgb[0, 0]=...(64, [255, 255, 0], [0, 0, 0])... >>> con.rgb["bg"] = BLUE - >>> con.rgb[0, 0] - (64, [255, 255, 0], [ 0, 0, 255]) + >>> print(f"{con.rgb[0, 0]=}") + con.rgb[0, 0]=...(64, [255, 255, 0], [ 0, 0, 255])... .. versionadded:: 12.3 """ diff --git a/tcod/map.py b/tcod/map.py index fc3c24cf..68f4a04b 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -61,7 +61,7 @@ class Map: [ True, True, True], [False, True, True], [False, False, True]]...) - >>> m.fov[3,1] + >>> m.fov.item(3, 1) False .. deprecated:: 11.13 diff --git a/tcod/noise.py b/tcod/noise.py index 22f5d9c2..8d9dc3ff 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -236,7 +236,7 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: raise IndexError( "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) ) - indexes = np.broadcast_arrays(*indexes) + indexes = list(np.broadcast_arrays(*indexes)) c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): if index.dtype.type == np.object_: diff --git a/tcod/path.py b/tcod/path.py index 5765f233..8d9fc5db 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1141,7 +1141,7 @@ def traversal(self) -> NDArray[Any]: >>> i, j = (3, 3) # Starting index. >>> path = [(i, j)] # List of nodes from the start to the root. >>> while not (pf.traversal[i, j] == (i, j)).all(): - ... i, j = pf.traversal[i, j] + ... i, j = pf.traversal[i, j].tolist() ... path.append((i, j)) >>> path # Slower. [(3, 3), (2, 2), (1, 1), (0, 0)] From 2d2775f1164689b12fa905e849dfc107ef1e7893 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 25 Jun 2024 21:23:30 -0700 Subject: [PATCH 0906/1101] Update tutorial part 3 --- .vscode/settings.json | 1 + docs/tutorial/part-01.rst | 6 +- docs/tutorial/part-02.rst | 6 +- docs/tutorial/part-03.rst | 336 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 331 insertions(+), 18 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 39cb6ad5..73a2b81e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -186,6 +186,7 @@ "imageio", "INCOL", "INROW", + "interactable", "intersphinx", "isinstance", "isort", diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index 4f1827f5..d4216982 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -140,7 +140,7 @@ The next step is to change state based on user input. Like ``tcod`` you'll need to install ``attrs`` with Pip, such as with :shell:`pip install attrs`. Start by adding an ``attrs`` class called ``ExampleState``. -This a normal class with the :python:`@attrs.define(eq=False)` decorator added. +This a normal class with the :python:`@attrs.define()` decorator added. This class should hold coordinates for the player. It should also have a ``on_draw`` method which takes :any:`tcod.console.Console` as a parameter and marks the player position on it. @@ -162,7 +162,7 @@ Call this method using the players current coordinates and the :python:`"@"` cha import tcod.tileset - @attrs.define(eq=False) + @attrs.define() class ExampleState: """Example state with a hard-coded player position.""" @@ -242,7 +242,7 @@ The full script so far is: import tcod.tileset - @attrs.define(eq=False) + @attrs.define() class ExampleState: """Example state with a hard-coded player position.""" diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst index c939f88a..90b20625 100644 --- a/docs/tutorial/part-02.rst +++ b/docs/tutorial/part-02.rst @@ -358,12 +358,12 @@ Then add the following: KeySym.n: (1, 1), } -Create a new :python:`class InGame:` decorated with :python:`@attrs.define(eq=False)`. +Create a new :python:`class InGame:` decorated with :python:`@attrs.define()`. States will always use ``g.world`` to access the ECS registry. .. code-block:: python - @attrs.define(eq=False) + @attrs.define() class InGame: """Primary in-game state.""" ... @@ -515,7 +515,7 @@ It should be at the same level as the ``for`` loop and not inside of it. } - @attrs.define(eq=False) + @attrs.define() class InGame: """Primary in-game state.""" diff --git a/docs/tutorial/part-03.rst b/docs/tutorial/part-03.rst index a70aadeb..25b811e8 100644 --- a/docs/tutorial/part-03.rst +++ b/docs/tutorial/part-03.rst @@ -24,6 +24,16 @@ In this module add the class :python:`class State(Protocol):`. These methods refer to types from ``tcod`` and those types will need to be imported. ``State`` should also have :python:`__slots__ = ()` [#slots]_ in case the class is used for a subclass. +Now add a few small classes using :python:`@attrs.define()`: +A ``Push`` class with a :python:`state: State` attribute. +A ``Pop`` class with no attributes. +A ``Reset`` class with a :python:`state: State` attribute. + +Then add a :python:`StateResult: TypeAlias = "Push | Pop | Reset | None"`. +This is a type which combines all of the previous classes. + +Edit ``State``'s ``on_event`` method to return ``StateResult``. + ``game/state.py`` should look like this: .. code-block:: python @@ -32,8 +42,9 @@ These methods refer to types from ``tcod`` and those types will need to be impor from __future__ import annotations - from typing import Protocol + from typing import Protocol, TypeAlias + import attrs import tcod.console import tcod.event @@ -43,21 +54,73 @@ These methods refer to types from ``tcod`` and those types will need to be impor __slots__ = () - def on_event(self, event: tcod.event.Event) -> None: + def on_event(self, event: tcod.event.Event) -> StateResult: """Called on events.""" def on_draw(self, console: tcod.console.Console) -> None: """Called when the state is being drawn.""" + + @attrs.define() + class Push: + """Push a new state on top of the stack.""" + + state: State + + + @attrs.define() + class Pop: + """Remove the current state from the stack.""" + + + @attrs.define() + class Reset: + """Replace the entire stack with a new state.""" + + state: State + + + StateResult: TypeAlias = "Push | Pop | Reset | None" + """Union of state results.""" + The ``InGame`` class does not need to be updated since it is already a structural subtype of ``State``. Note that subclasses of ``State`` will never be in same module as ``State``, this will be the same for all abstract classes. -State globals +New globals ============================================================================== A new global will be added: :python:`states: list[game.state.State] = []`. States are implemented as a list/stack to support `pushdown automata `_. -Representing states as a stack makes it easier to implement popup windows, menus, and other "history aware" states. +Representing states as a stack makes it easier to implement popup windows, sub-menus, and other prompts. + +The ``console`` variable from ``main.py`` will be moved to ``g.py``. +Add :python:`console: tcod.console.Console` and replace all references to ``console`` in ``main.py`` with ``g.console``. + +.. code-block:: python + :emphasize-lines: 9,17-21 + + """This module stores globally mutable variables used by this program.""" + + from __future__ import annotations + + import tcod.console + import tcod.context + import tcod.ecs + + import game.state + + context: tcod.context.Context + """The window managed by tcod.""" + + world: tcod.ecs.Registry + """The active ECS registry and current session.""" + + states: list[game.state.State] = [] + """A stack of states with the last item being the active state.""" + + console: tcod.console.Console + """The current main console.""" + State functions ============================================================================== @@ -71,12 +134,39 @@ Render the active state with :python:`g.states[-1].on_draw(g.console)`. If ``g.states`` is empty then this function should immediately :python:`return` instead of doing anything. Empty containers in Python are :python:`False` when checked for truthiness. -Next the function :python:`def main_loop() -> None:` is created. +Next is to handle the ``StateResult`` type. +Start by adding the :python:`def apply_state_result(result: StateResult) -> None:` function. +This function will :python:`match result:` to decide on what to do. + +:python:`case Push(state=state):` should append ``state`` to ``g.states``. + +:python:`case Pop():` should simply call :python:`g.states.pop()`. + +:python:`case Reset(state=state):` should call :python:`apply_state_result(Pop())` until ``g.state`` is empty then call :python:`apply_state_result(Push(state))`. + +:python:`case None:` should be handled by explicitly ignoring it. + +:python:`case _:` handles anything else and should invoke :python:`raise TypeError(result)` since no other types are expected. + +Now the function :python:`def main_loop() -> None:` is created. The :python:`while` loop from ``main`` will be moved to this function. The while loop will be replaced by :python:`while g.states:` so that this function will exit if no state exists. Drawing will be replaced by a call to ``main_draw``. -Events in the for-loop will be passed to the active state :python:`g.states[-1].on_event(event)`. -Any states ``on_event`` method could potentially change the state so ``g.states`` must be checked to be non-empty for every handled event. +Events with mouse coordinates should be converted to tiles using :python:`tile_event = g.context.convert_event(event)` before being passed to a state. +:python:`apply_state_result(g.states[-1].on_event(tile_event))` will pass the event and handle the return result at the same time. +``g.states`` must be checked to be non-empty inside the event handing for-loop because ``apply_state_result`` could cause ``g.states`` to become empty. + +Next is the utility function :python:`def get_previous_state(state: State) -> State | None:`. +Get the index of ``state`` in ``g.states`` by identity [#identity]_ using :python:`current_index = next(index for index, value in enumerate(g.states) if value is state)`. +Return the previous state if :python:`current_index > 0` or else return None using :python:`return g.states[current_index - 1] if current_index > 0 else None`. + +Next is :python:`def draw_previous_state(state: State, console: tcod.console.Console, dim: bool = True) -> None:`. +Call ``get_previous_state`` to get the previous state and return early if the result is :python:`None`. +Then call the previous states :python:`State.on_draw` method as normal. +Afterwards test :python:`dim and state is g.states[-1]` to see if the console should be dimmed. +If it should be dimmed then reduce the color values of the console with :python:`console.rgb["fg"] //= 4` and :python:`console.rgb["bg"] //= 4`. +This is used to indicate that any graphics behind the active state are non-interactable. + .. code-block:: python @@ -87,6 +177,7 @@ Any states ``on_event`` method could potentially change the state so ``g.states` import tcod.console import g + from game.state import Pop, Push, Reset, StateResult def main_draw() -> None: @@ -98,30 +189,246 @@ Any states ``on_event`` method could potentially change the state so ``g.states` g.context.present(g.console) + def apply_state_result(result: StateResult) -> None: + """Apply a StateResult to `g.states`.""" + match result: + case Push(state=state): + g.states.append(state) + case Pop(): + g.states.pop() + case Reset(state=state): + while g.states: + apply_state_result(Pop()) + apply_state_result(Push(state)) + case None: + pass + case _: + raise TypeError(result) + + def main_loop() -> None: """Run the active state forever.""" while g.states: main_draw() for event in tcod.event.wait(): + tile_event = g.context.convert_event(event) if g.states: - g.states[-1].on_event(event) + apply_state_result(g.states[-1].on_event(tile_event)) + + + def get_previous_state(state: State) -> State | None: + """Return the state before `state` in the stack if it exists.""" + current_index = next(index for index, value in enumerate(g.states) if value is state) + return g.states[current_index - 1] if current_index > 0 else None + + + def draw_previous_state(state: State, console: tcod.console.Console, dim: bool = True) -> None: + """Draw previous states, optionally dimming all but the active state.""" + prev_state = get_previous_state(state) + if prev_state is None: + return + prev_state.on_draw(console) + if dim and state is g.states[-1]: + console.rgb["fg"] //= 4 + console.rgb["bg"] //= 4 + +Menus +============================================================================== + +.. code-block:: python + + """Menu UI classes.""" + + from __future__ import annotations + + from collections.abc import Callable + from typing import Protocol + + import attrs + import tcod.console + import tcod.event + from tcod.event import KeySym + + import game.state_tools + from game.constants import DIRECTION_KEYS + from game.state import Pop, State, StateResult + + + class MenuItem(Protocol): + """Menu item protocol.""" + + __slots__ = () + + def on_event(self, event: tcod.event.Event) -> StateResult: + """Handle events passed to the menu item.""" + + def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None: + """Draw is item at the given position.""" + + + @attrs.define() + class SelectItem(MenuItem): + """Clickable menu item.""" + + label: str + callback: Callable[[], StateResult] + + def on_event(self, event: tcod.event.Event) -> StateResult: + """Handle events selecting this item.""" + match event: + case tcod.event.KeyDown(sym=sym) if sym in {KeySym.RETURN, KeySym.RETURN2, KeySym.KP_ENTER}: + return self.callback() + case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT): + return self.callback() + case _: + return None + + def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None: + """Render this items label.""" + console.print(x, y, self.label, fg=(255, 255, 255), bg=(64, 64, 64) if highlight else (0, 0, 0)) + + + @attrs.define() + class ListMenu(State): + """Simple list menu state.""" + + items: tuple[MenuItem, ...] + selected: int | None = 0 + x: int = 0 + y: int = 0 + + def on_event(self, event: tcod.event.Event) -> StateResult: + """Handle events for menus.""" + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS: + dx, dy = DIRECTION_KEYS[sym] + if dx != 0 or dy == 0: + return self.activate_selected(event) + if self.selected is not None: + self.selected += dy + self.selected %= len(self.items) + else: + self.selected = 0 if dy == 1 else len(self.items) - 1 + return None + case tcod.event.MouseMotion(position=(_, y)): + y -= self.y + self.selected = y if 0 <= y < len(self.items) else None + return None + case tcod.event.KeyDown(sym=KeySym.ESCAPE): + return self.on_cancel() + case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT): + return self.on_cancel() + case _: + return self.activate_selected(event) + + def activate_selected(self, event: tcod.event.Event) -> StateResult: + """Call the selected menu items callback.""" + if self.selected is not None: + return self.items[self.selected].on_event(event) + return None + + def on_cancel(self) -> StateResult: + """Handle escape or right click being pressed on menus.""" + return Pop() + + def on_draw(self, console: tcod.console.Console) -> None: + """Render the menu.""" + game.state_tools.draw_previous_state(self, console) + for i, item in enumerate(self.items): + item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected) + +Update states +============================================================================== + +.. code-block:: python + + class MainMenu(game.menus.ListMenu): + """Main/escape menu.""" + + __slots__ = () + + def __init__(self) -> None: + """Initialize the main menu.""" + items = [ + game.menus.SelectItem("New game", self.new_game), + game.menus.SelectItem("Quit", self.quit), + ] + if hasattr(g, "world"): + items.insert(0, game.menus.SelectItem("Continue", self.continue_)) + + super().__init__( + items=tuple(items), + selected=0, + x=5, + y=5, + ) + + @staticmethod + def continue_() -> StateResult: + """Return to the game.""" + return Reset(InGame()) + + @staticmethod + def new_game() -> StateResult: + """Begin a new game.""" + g.world = game.world_tools.new_world() + return Reset(InGame()) + + @staticmethod + def quit() -> StateResult: + """Close the program.""" + raise SystemExit() + +.. code-block:: python + :emphasize-lines: 2,5,19-23 + + @attrs.define() + class InGame(State): + """Primary in-game state.""" + + def on_event(self, event: tcod.event.Event) -> StateResult: + """Handle events for the in-game state.""" + (player,) = g.world.Q.all_of(tags=[IsPlayer]) + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS: + player.components[Position] += DIRECTION_KEYS[sym] + # Auto pickup gold + for gold in g.world.Q.all_of(components=[Gold], tags=[player.components[Position], IsItem]): + player.components[Gold] += gold.components[Gold] + text = f"Picked up {gold.components[Gold]}g, total: {player.components[Gold]}g" + g.world[None].components[("Text", str)] = text + gold.clear() + return None + case tcod.event.KeyDown(sym=KeySym.ESCAPE): + return Push(MainMenu()) + case _: + return None + + ... + +Update main.py +============================================================================== Now ``main.py`` can be edited to use the global variables and the new game loop. Add :python:`import g` and :python:`import game.state_tools`. Replace references to ``console`` with ``g.console``. -Replace references to ``context`` with ``g.context``. States are initialed by assigning a list with the initial state to ``g.states``. The previous game loop is replaced by a call to :python:`game.state_tools.main_loop()`. .. code-block:: python - :emphasize-lines: 3-4,12-15 + :emphasize-lines: 3-4,13-16 ... import g import game.state_tools + import game.states def main() -> None: """Entry point function.""" @@ -130,7 +437,7 @@ The previous game loop is replaced by a call to :python:`game.state_tools.main_l ) tcod.tileset.procedural_block_elements(tileset=tileset) g.console = tcod.console.Console(80, 50) - g.states = [ExampleState(player_x=console.width // 2, player_y=console.height // 2)] + g.states = [game.states.MainMenu()] with tcod.context.new(console=g.console, tileset=tileset) as g.context: game.state_tools.main_loop() ... @@ -138,10 +445,15 @@ The previous game loop is replaced by a call to :python:`game.state_tools.main_l After this you can test the game. There should be no visible differences from before. +You can review the part-3 source code `here `_. .. rubric:: Footnotes .. [#slots] This is done to prevent subclasses from requiring a ``__dict__`` attribute. - If you are still wondering what ``__slots__`` is then `the Python docs have a detailed explanation `_. + See :any:`slots` for a detailed explanation of what they are. + +.. [#identity] See :any:`is`. + Since ``State`` classes use ``attrs`` they might compare equal when they're not the same object. + This means :python:`list.index` won't work for this case. .. _Protocol: https://mypy.readthedocs.io/en/stable/protocols.html From 070cb120cdb99e8f2478b95b76c77b927a301143 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 26 Jun 2024 00:33:19 -0700 Subject: [PATCH 0907/1101] Tutorial: finish part 2 backports DIRECTION_KEYS moved to constants.py module --- docs/tutorial/part-02.rst | 55 +++++++++++---------------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/docs/tutorial/part-02.rst b/docs/tutorial/part-02.rst index 90b20625..fa4467a9 100644 --- a/docs/tutorial/part-02.rst +++ b/docs/tutorial/part-02.rst @@ -316,17 +316,21 @@ New InGame state Now there is a new ECS world but the example state does not know how to render it. A new state needs to be made which is aware of the new entities. -Create a new script called ``game/states.py``. -``states`` is for derived classes, ``state`` is for the abstract class. -New states will be created in this module and this module will be allowed to import many first party modules without issues. - Before adding a new state it is time to add a more complete set of directional keys. -These will be added as a dictionary and can be reused anytime we want to know how a key translates to a direction. +Create a new module called ``game/constants.py``. +Keys will be mapped to direction using a dictionary which can be reused anytime we want to know how a key translates to a direction. Use :python:`from tcod.event import KeySym` to make ``KeySym`` enums easier to write. -Then add the following: + +``game/constants.py`` should look like this: .. code-block:: python + """Global constants are stored here.""" + + from typing import Final + + from tcod.event import KeySym + DIRECTION_KEYS: Final = { # Arrow keys KeySym.LEFT: (-1, 0), @@ -358,6 +362,10 @@ Then add the following: KeySym.n: (1, 1), } +Create a new module called ``game/states.py``. +``states`` is for derived classes, ``state`` is for the abstract class. +New states will be created in this module and this module will be allowed to import many first party modules without issues. + Create a new :python:`class InGame:` decorated with :python:`@attrs.define()`. States will always use ``g.world`` to access the ECS registry. @@ -472,48 +480,15 @@ It should be at the same level as the ``for`` loop and not inside of it. from __future__ import annotations - from typing import Final - import attrs import tcod.console import tcod.event - from tcod.event import KeySym import g from game.components import Gold, Graphic, Position + from game.constants import DIRECTION_KEYS from game.tags import IsItem, IsPlayer - DIRECTION_KEYS: Final = { - # Arrow keys - KeySym.LEFT: (-1, 0), - KeySym.RIGHT: (1, 0), - KeySym.UP: (0, -1), - KeySym.DOWN: (0, 1), - # Arrow key diagonals - KeySym.HOME: (-1, -1), - KeySym.END: (-1, 1), - KeySym.PAGEUP: (1, -1), - KeySym.PAGEDOWN: (1, 1), - # Keypad - KeySym.KP_4: (-1, 0), - KeySym.KP_6: (1, 0), - KeySym.KP_8: (0, -1), - KeySym.KP_2: (0, 1), - KeySym.KP_7: (-1, -1), - KeySym.KP_1: (-1, 1), - KeySym.KP_9: (1, -1), - KeySym.KP_3: (1, 1), - # VI keys - KeySym.h: (-1, 0), - KeySym.l: (1, 0), - KeySym.k: (0, -1), - KeySym.j: (0, 1), - KeySym.y: (-1, -1), - KeySym.b: (-1, 1), - KeySym.u: (1, -1), - KeySym.n: (1, 1), - } - @attrs.define() class InGame: From 6a396e6c1a647cefa210b503b2604eb9dc815153 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 26 Jun 2024 01:15:20 -0700 Subject: [PATCH 0908/1101] Update CI workflows --- .github/workflows/python-package.yml | 98 ++++++++++++++++------------ .github/workflows/release-on-tag.yml | 2 +- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 844f451a..0f545dcc 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,7 +19,7 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Ruff run: pip install ruff - name: Ruff Check @@ -30,13 +30,13 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install typing dependencies run: pip install mypy pytest -r requirements.txt - name: Mypy - uses: liskin/gh-problem-matcher-wrap@v2 + uses: liskin/gh-problem-matcher-wrap@v3 with: linters: mypy run: mypy --show-column-numbers @@ -48,7 +48,7 @@ jobs: run: sudo apt-get update - name: Install APT dependencies run: sudo apt-get install libsdl2-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -57,11 +57,12 @@ jobs: run: pip install build - name: Build source distribution run: python -m build --sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: sdist path: dist/tcod-*.tar.gz retention-days: 7 + compression-level: 0 # This makes sure that the latest versions of the SDL headers parse correctly. parse_sdl: @@ -73,12 +74,12 @@ jobs: sdl-version: ["2.0.14", "2.0.16"] fail-fast: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install build dependencies @@ -94,7 +95,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - python-version: ["3.8", "3.9", "pypy-3.8"] + python-version: ["3.8", "pypy-3.8"] architecture: ["x64"] include: - os: "windows-latest" @@ -106,14 +107,14 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -144,13 +145,16 @@ jobs: - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log - - uses: codecov/codecov-action@v3 - - uses: actions/upload-artifact@v3 + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - uses: actions/upload-artifact@v4 if: runner.os == 'Windows' with: - name: wheels-windows + name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }} path: dist/*.whl retention-days: 7 + compression-level: 0 test-docs: needs: [ruff, mypy] @@ -160,7 +164,7 @@ jobs: run: | sudo apt-get update sudo apt-get install libsdl2-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -184,13 +188,13 @@ jobs: matrix: os: ["ubuntu-latest", "windows-latest"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --depth 1 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install Python dependencies @@ -212,18 +216,20 @@ jobs: matrix: arch: ["x86_64", "aarch64"] build: ["cp38-manylinux*", "pp38-manylinux*"] + env: + BUILD_DESC: steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Set up QEMU if: ${{ matrix.arch == 'aarch64' }} - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install Python dependencies @@ -247,33 +253,44 @@ jobs: CIBW_TEST_COMMAND: python -c "import tcod.context" # Skip test on emulated architectures CIBW_TEST_SKIP: "*_aarch64" + - name: Remove asterisk from label + run: | + BUILD_DESC=${{ matrix.build }} + BUILD_DESC=${BUILD_DESC//\*} + echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: wheels-linux + name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }} path: wheelhouse/*.whl retention-days: 7 + compression-level: 0 build-macos: needs: [ruff, mypy] - runs-on: "macos-11" + runs-on: "macos-14" strategy: fail-fast: true matrix: - python: ["cp38-*_universal2", "cp38-*_x86_64", "cp38-*_arm64", "pp38-*"] + python: ["cp38-*_universal2", "cp38-*_x86_64", "pp38-*"] + env: + PYTHON_DESC: steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" - name: Install Python dependencies - run: pip3 install -r requirements.txt + run: pip install -r requirements.txt - name: Prepare package # Downloads SDL2 for the later step. - run: python3 build_sdl.py + run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.17.0 + uses: pypa/cibuildwheel@v2.19.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 @@ -282,12 +299,18 @@ jobs: CIBW_TEST_COMMAND: python -c "import tcod.context" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" MACOSX_DEPLOYMENT_TARGET: "10.11" + - name: Remove asterisk from label + run: | + PYTHON_DESC=${{ matrix.python }} + PYTHON_DESC=${PYTHON_DESC//\*/X} + echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: wheels-macos + name: wheels-macos-${{ env.PYTHON_DESC }} path: wheelhouse/*.whl retention-days: 7 + compression-level: 0 publish: needs: [sdist, build, build-macos, linux-wheels] @@ -295,26 +318,19 @@ jobs: if: startsWith(github.ref, 'refs/tags/') environment: name: release - url: https://pypi.org/p/tcod + url: https://pypi.org/project/tcod/ permissions: id-token: write steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: sdist path: dist/ - - uses: actions/download-artifact@v3 - with: - name: wheels-windows - path: dist/ - - uses: actions/download-artifact@v3 - with: - name: wheels-macos - path: dist/ - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: wheels-linux + pattern: wheels-* path: dist/ + merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 95cf02d3..2171ddbf 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Generate body run: | scripts/get_release_description.py | tee release_body.md From b92d433e81fd9b2a14f25cdab6107095a67eae84 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 26 Jun 2024 03:17:36 -0700 Subject: [PATCH 0909/1101] Add spelling --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 73a2b81e..07db160c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ "*.spec": "python", }, "cSpell.words": [ + "aarch", "ADDA", "ADDALPHA", "addressof", @@ -135,6 +136,7 @@ "DVLINE", "elif", "endianness", + "epel", "epub", "EQUALSAS", "errorvf", @@ -165,6 +167,7 @@ "greyscale", "groupwise", "guass", + "hasattr", "heapify", "heightmap", "heightmaps", @@ -402,6 +405,7 @@ "sourcelink", "sphinxstrong", "sphinxtitleref", + "staticmethod", "stdeb", "struct", "structs", From 9164b223dc6c7d9b8b8add7d68ad0c775bb1e968 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:57:24 +0000 Subject: [PATCH 0910/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.5.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8411e3fa..d075967a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.3 + rev: v0.5.1 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From efbd2641ca0824d2bde6a1cc9b70644c01dc30ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:57:31 +0000 Subject: [PATCH 0911/1101] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f66b990d..d4edf824 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,7 +72,7 @@ # # The full version, including alpha/beta/rc tags. git_describe = subprocess.run( - ["git", "describe", "--abbrev=0"], # noqa: S603, S607 + ["git", "describe", "--abbrev=0"], # noqa: S607 stdout=subprocess.PIPE, text=True, check=True, From a1c84ccd615f1630bac538bfe20bf5e85444fdd4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 9 Jul 2024 06:49:08 -0700 Subject: [PATCH 0912/1101] Set pre-commit update schedule --- .pre-commit-config.yaml | 4 ++-- .vscode/settings.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d075967a..03143e06 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +ci: + autoupdate_schedule: quarterly repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -20,5 +22,3 @@ repos: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] - id: ruff-format -default_language_version: - python: python3.12 diff --git a/.vscode/settings.json b/.vscode/settings.json index 07db160c..fc7c18f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -46,6 +46,7 @@ "autofunction", "autogenerated", "automodule", + "autoupdate", "backlinks", "bdist", "Benesch", From b2de7747c7b7f75d25f026dea4dec538f55351d5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 9 Jul 2024 07:01:20 -0700 Subject: [PATCH 0913/1101] Update cibuildwheel --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0f545dcc..bfb4b7e4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -235,7 +235,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install cibuildwheel==2.16.0 + pip install cibuildwheel==2.19.2 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -290,7 +290,7 @@ jobs: # Downloads SDL2 for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.19.1 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 From ce2027e209ebdb784c4e95d7926d3a74aff9c95e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:29:12 +0000 Subject: [PATCH 0914/1101] Bump setuptools from 65.5.1 to 70.0.0 Bumps [setuptools](https://github.com/pypa/setuptools) from 65.5.1 to 70.0.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v65.5.1...v70.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index a94c3862..03ad70a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -attrs>=23.1.0 -cffi>=1.15 -numpy>=1.21.4 -pycparser>=2.14 -requests>=2.28.1 -setuptools==65.5.1 -types-cffi -types-requests -types-Pillow -types-setuptools -types-tabulate -typing_extensions -pcpp==1.30 +attrs>=23.1.0 +cffi>=1.15 +numpy>=1.21.4 +pycparser>=2.14 +requests>=2.28.1 +setuptools==70.0.0 +types-cffi +types-requests +types-Pillow +types-setuptools +types-tabulate +typing_extensions +pcpp==1.30 From 793b54151ed69a91e9277a7bddd35e9426cad92c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 15:09:50 -0700 Subject: [PATCH 0915/1101] Fix image load NULL dereference and verify the existence of more files --- CHANGELOG.md | 2 ++ tcod/image.py | 9 ++++++++- tcod/libtcodpy.py | 7 ++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bed531..2c82939c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - Fixed access violation when events are polled before SDL is initialized. +- Fixed access violation when libtcod images fail to load. +- Verify input files exist when calling `libtcodpy.parser_run`, `libtcodpy.namegen_parse`, `tcod.image.load`. ## [16.2.2] - 2024-01-16 diff --git a/tcod/image.py b/tcod/image.py index 38fe3e4d..7b2ee13d 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -39,10 +39,16 @@ def __init__(self, width: int, height: int) -> None: """Initialize a blank image.""" self.width, self.height = width, height self.image_c = ffi.gc(lib.TCOD_image_new(width, height), lib.TCOD_image_delete) + if self.image_c == ffi.NULL: + msg = "Failed to allocate image." + raise MemoryError(msg) @classmethod def _from_cdata(cls, cdata: Any) -> Image: # noqa: ANN401 self: Image = object.__new__(cls) + if cdata == ffi.NULL: + msg = "Pointer must not be NULL." + raise RuntimeError(msg) self.image_c = cdata self.width, self.height = self._get_size() return self @@ -365,7 +371,8 @@ def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: .. versionadded:: 11.4 """ - image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(Path(filename))), lib.TCOD_image_delete)) + filename = Path(filename).resolve(strict=True) + image = Image._from_cdata(ffi.gc(lib.TCOD_image_load(_path_encode(filename)), lib.TCOD_image_delete)) array: NDArray[np.uint8] = np.asarray(image, dtype=np.uint8) height, width, depth = array.shape if depth == 3: # noqa: PLR2004 diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index dd23de45..004053d8 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3434,7 +3434,7 @@ def mouse_get_status() -> Mouse: @pending_deprecate() def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | None = None) -> None: - lib.TCOD_namegen_parse(_path_encode(Path(filename)), random or ffi.NULL) + lib.TCOD_namegen_parse(_path_encode(Path(filename).resolve(strict=True)), random or ffi.NULL) @pending_deprecate() @@ -3636,8 +3636,9 @@ def _pycall_parser_error(msg: Any) -> None: @deprecate("Parser functions have been deprecated.") def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) -> None: global _parser_listener # noqa: PLW0603 + filename = Path(filename).resolve(strict=True) if not listener: - lib.TCOD_parser_run(parser, _path_encode(Path(filename)), ffi.NULL) + lib.TCOD_parser_run(parser, _path_encode(filename), ffi.NULL) return propagate_manager = _PropagateException() @@ -3656,7 +3657,7 @@ def parser_run(parser: Any, filename: str | PathLike[str], listener: Any = None) with _parser_callback_lock: _parser_listener = listener with propagate_manager: - lib.TCOD_parser_run(parser, _path_encode(Path(filename)), c_listener) + lib.TCOD_parser_run(parser, _path_encode(filename), c_listener) @deprecate("libtcod objects are deleted automatically.") From 49dea990349cb51be2a8c81322c53bdaed8cae33 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 15:20:12 -0700 Subject: [PATCH 0916/1101] pre-commit update --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03143e06..a312e948 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.1 + rev: v0.5.2 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 59ec79731507eec5d50dbada3ee766c4d3312621 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 15:28:06 -0700 Subject: [PATCH 0917/1101] Prepare 16.2.3 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c82939c..9b5a2551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [16.2.3] - 2024-07-16 + ### Fixed - Fixed access violation when events are polled before SDL is initialized. From 350b6c7ec5c516e704c2dc69d4e7836ef3707bb8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 16:12:12 -0700 Subject: [PATCH 0918/1101] Update PyPI deployment Clean up ref_type check and add specific release URL --- .github/workflows/python-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bfb4b7e4..38d468c9 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -315,10 +315,10 @@ jobs: publish: needs: [sdist, build, build-macos, linux-wheels] runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') + if: github.ref_type == 'tag' environment: - name: release - url: https://pypi.org/project/tcod/ + name: pypi + url: https://pypi.org/project/tcod/${{ github.ref_name }} permissions: id-token: write steps: From 98093c1cda7d958c2c2b629e82b98b1954ce2776 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 16:15:15 -0700 Subject: [PATCH 0919/1101] Skip macOS x86_64 wheel Redundant with the universal2 releases --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 38d468c9..0417777c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -272,7 +272,7 @@ jobs: strategy: fail-fast: true matrix: - python: ["cp38-*_universal2", "cp38-*_x86_64", "pp38-*"] + python: ["cp38-*_universal2", "pp38-*"] env: PYTHON_DESC: steps: From 3bc0ce0b16787dcd613c66d9f647a9bdd7a7483e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 16:15:27 -0700 Subject: [PATCH 0920/1101] Clean up minor formatting issues with the tutorial --- docs/tutorial/part-01.rst | 12 ++++++------ docs/tutorial/part-02.rst | 8 +++++--- docs/tutorial/part-03.rst | 6 +++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/tutorial/part-01.rst b/docs/tutorial/part-01.rst index d4216982..b18b7531 100644 --- a/docs/tutorial/part-01.rst +++ b/docs/tutorial/part-01.rst @@ -94,7 +94,7 @@ Use the code :python:`for event in tcod.event.wait():` to begin handing events. In the event loop start with the line :python:`print(event)` so that all events can be viewed from the program output. Then test if an event is for closing the window with :python:`if isinstance(event, tcod.event.Quit):`. -If this is True then you should exit the function with :python:`raise SystemExit()`. [#why_raise]_ +If this is True then you should exit the function with :python:`raise SystemExit`. [#why_raise]_ .. code-block:: python :emphasize-lines: 3,5,15-23 @@ -121,7 +121,7 @@ If this is True then you should exit the function with :python:`raise SystemExit for event in tcod.event.wait(): # Event loop, blocks until pending events exist print(event) if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if __name__ == "__main__": @@ -205,7 +205,7 @@ Modify the drawing routine so that the console is cleared, then passed to :pytho for event in tcod.event.wait(): print(event) if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if __name__ == "__main__": @@ -259,7 +259,7 @@ The full script so far is: """Move the player on events and handle exiting. Movement is hard-coded.""" match event: case tcod.event.Quit(): - raise SystemExit() + raise SystemExit case tcod.event.KeyDown(sym=tcod.event.KeySym.LEFT): self.player_x -= 1 case tcod.event.KeyDown(sym=tcod.event.KeySym.RIGHT): @@ -308,5 +308,5 @@ You can review the part-1 source code `here StateResult: """Close the program.""" - raise SystemExit() + raise SystemExit .. code-block:: python :emphasize-lines: 2,5,19-23 @@ -393,7 +393,7 @@ Update states (player,) = g.world.Q.all_of(tags=[IsPlayer]) match event: case tcod.event.Quit(): - raise SystemExit() + raise SystemExit case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS: player.components[Position] += DIRECTION_KEYS[sym] # Auto pickup gold From 4b25bc4543b0dfb5d66509547ffa9fd401ed4384 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 16:50:29 -0700 Subject: [PATCH 0921/1101] Fix Ruff warnings, update Mypy strategy to use local version --- .vscode/settings.json | 1 + tcod/console.py | 3 ++- tcod/context.py | 3 ++- tcod/libtcodpy.py | 15 ++++++++------- tcod/map.py | 4 +++- tcod/noise.py | 1 + tcod/path.py | 10 ++++++---- tcod/render.py | 2 ++ tcod/tileset.py | 11 +++++++---- tests/test_libtcodpy.py | 4 +--- 10 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fc7c18f2..a6f96553 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "files.associations": { "*.spec": "python", }, + "mypy-type-checker.importStrategy": "fromEnvironment", "cSpell.words": [ "aarch", "ADDA", diff --git a/tcod/console.py b/tcod/console.py index c6767b59..c38a4de7 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -120,6 +120,7 @@ def __init__( order: Literal["C", "F"] = "C", buffer: NDArray[Any] | None = None, ) -> None: + """Initialize the console.""" self._key_color: tuple[int, int, int] | None = None self._order = tcod._internal.verify_order(order) if buffer is not None: @@ -930,7 +931,7 @@ def __repr__(self) -> str: def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" - return "<%s>" % "\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"]) + return "<{}>".format("\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"])) def print( # noqa: PLR0913 self, diff --git a/tcod/context.py b/tcod/context.py index f22c78de..3b3e8e0a 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -344,7 +344,8 @@ def new_console( scale = max(1, scale + event.y) """ if magnification < 0: - raise ValueError("Magnification must be greater than zero. (Got %f)" % magnification) + msg = f"Magnification must be greater than zero. (Got {magnification:f})" + raise ValueError(msg) size = ffi.new("int[2]") _check(lib.TCOD_context_recommended_console_size(self._p, magnification, size, size + 1)) width, height = max(min_columns, size[0]), max(min_rows, size[1]) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 004053d8..feaa0d52 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -317,7 +317,7 @@ def nb_dices(self, value: int) -> None: self.nb_rolls = value def __str__(self) -> str: - add = "+(%s)" % self.addsub if self.addsub != 0 else "" + add = f"+({self.addsub})" if self.addsub != 0 else "" return "%id%ix%s%s" % ( self.nb_dices, self.nb_faces, @@ -330,7 +330,7 @@ def __repr__(self) -> str: # reverse lookup table for KEY_X attributes, used by Key.__repr__ -_LOOKUP_VK = {value: "KEY_%s" % key[6:] for key, value in lib.__dict__.items() if key.startswith("TCODK")} +_LOOKUP_VK = {value: f"KEY_{key[6:]}" for key, value in lib.__dict__.items() if key.startswith("TCODK")} class Key(_CDataWrapper): @@ -418,9 +418,9 @@ def __repr__(self) -> str: params = [] params.append(f"pressed={self.pressed!r}, vk=tcod.{_LOOKUP_VK[self.vk]}") if self.c: - params.append("c=ord(%r)" % chr(self.c)) + params.append(f"c=ord({chr(self.c)!r})") if self.text: - params.append("text=%r" % self.text) + params.append(f"text={self.text!r}") for attr in [ "shift", "lalt", @@ -432,7 +432,7 @@ def __repr__(self) -> str: ]: if getattr(self, attr): params.append(f"{attr}={getattr(self, attr)!r}") - return "tcod.Key(%s)" % ", ".join(params) + return "tcod.Key({})".format(", ".join(params)) @property def key_p(self) -> Any: @@ -510,7 +510,7 @@ def __repr__(self) -> str: ]: if getattr(self, attr): params.append(f"{attr}={getattr(self, attr)!r}") - return "tcod.Mouse(%s)" % ", ".join(params) + return "tcod.Mouse({})".format(", ".join(params)) @property def mouse_p(self) -> Any: @@ -2361,7 +2361,8 @@ def _heightmap_cdata(array: NDArray[np.float32]) -> ffi.CData: msg = "array must be a contiguous segment." raise ValueError(msg) if array.dtype != np.float32: - raise ValueError("array dtype must be float32, not %r" % array.dtype) + msg = f"array dtype must be float32, not {array.dtype!r}" + raise ValueError(msg) height, width = array.shape pointer = ffi.from_buffer("float *", array) return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) diff --git a/tcod/map.py b/tcod/map.py index 68f4a04b..488c4878 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -76,6 +76,7 @@ def __init__( height: int, order: Literal["C", "F"] = "C", ) -> None: + """Initialize the map.""" warnings.warn( "This class may perform poorly and is no longer needed.", DeprecationWarning, @@ -234,7 +235,8 @@ def compute_fov( """ transparency = np.asarray(transparency) if len(transparency.shape) != 2: # noqa: PLR2004 - raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape) + msg = f"transparency must be an array of 2 dimensions (shape is {transparency.shape!r})" + raise TypeError(msg) if isinstance(pov, int): msg = "The tcod.map.compute_fov function has changed. The `x` and `y` parameters should now be given as a single tuple." raise TypeError(msg) diff --git a/tcod/noise.py b/tcod/noise.py index 8d9dc3ff..108fa2c0 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -133,6 +133,7 @@ def __init__( # noqa: PLR0913 octaves: float = 4, seed: int | tcod.random.Random | None = None, ) -> None: + """Initialize and seed the noise object.""" if not 0 < dimensions <= 4: # noqa: PLR2004 msg = f"dimensions must be in range 0 < n <= 4, got {dimensions}" raise ValueError(msg) diff --git a/tcod/path.py b/tcod/path.py index 8d9fc5db..8950505f 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -591,7 +591,7 @@ def _world_array(shape: tuple[int, ...], dtype: DTypeLike = np.int32) -> NDArray ) -def _as_hashable(obj: np.ndarray[Any, Any] | None) -> Any | None: +def _as_hashable(obj: np.ndarray[Any, Any] | None) -> object | None: """Return NumPy arrays as a more hashable form.""" if obj is None: return obj @@ -661,6 +661,7 @@ class CustomGraph: """ def __init__(self, shape: tuple[int, ...], *, order: str = "C") -> None: + """Initialize the custom graph.""" self._shape = self._shape_c = tuple(shape) self._ndim = len(self._shape) self._order = order @@ -894,8 +895,7 @@ def add_edges( edge_array = np.transpose(edge_nz) edge_array -= edge_center for edge, edge_cost in zip(edge_array, edge_costs): - edge = tuple(edge) - self.add_edge(edge, edge_cost, cost=cost, condition=condition) + self.add_edge(tuple(edge), edge_cost, cost=cost, condition=condition) def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0) -> None: """Set a pathfinder heuristic so that pathfinding can done with A*. @@ -1028,6 +1028,7 @@ class SimpleGraph: """ def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int = 1) -> None: + """Initialize the graph.""" cost = np.asarray(cost) if cost.ndim != 2: # noqa: PLR2004 msg = f"The cost array must e 2 dimensional, array of shape {cost.shape!r} given." @@ -1087,6 +1088,7 @@ class Pathfinder: """ def __init__(self, graph: CustomGraph | SimpleGraph) -> None: + """Initialize the pathfinder from a graph.""" self._graph = graph self._order = graph._order self._frontier_p = ffi.gc(lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete) @@ -1273,7 +1275,7 @@ def resolve(self, goal: tuple[int, ...] | None = None) -> None: if self._order == "F": # Goal is now ij indexed for the rest of this function. goal = goal[::-1] - if self._distance[goal] != np.iinfo(self._distance.dtype).max: + if self._distance[goal] != np.iinfo(self._distance.dtype).max: # noqa: SIM102 if not lib.frontier_has_index(self._frontier_p, goal): return self._update_heuristic(goal) diff --git a/tcod/render.py b/tcod/render.py index 80558dfb..09d3a137 100644 --- a/tcod/render.py +++ b/tcod/render.py @@ -42,6 +42,7 @@ class SDLTilesetAtlas: """Prepares a tileset for rendering using SDL.""" def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Tileset) -> None: + """Initialize the tileset atlas.""" self._renderer = renderer self.tileset: Final[tcod.tileset.Tileset] = tileset """The tileset used to create this SDLTilesetAtlas.""" @@ -64,6 +65,7 @@ class SDLConsoleRender: """Holds an internal cache console and texture which are used to optimized console rendering.""" def __init__(self, atlas: SDLTilesetAtlas) -> None: + """Initialize the console renderer.""" self.atlas: Final[SDLTilesetAtlas] = atlas """The SDLTilesetAtlas used to create this SDLConsoleRender. diff --git a/tcod/tileset.py b/tcod/tileset.py index 846aed32..022c57ac 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -22,7 +22,7 @@ from numpy.typing import ArrayLike, NDArray import tcod.console -from tcod._internal import _check, _console, _path_encode, _raise_tcod_error, deprecate +from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error, deprecate from tcod.cffi import ffi, lib @@ -33,9 +33,12 @@ class Tileset: """ def __init__(self, tile_width: int, tile_height: int) -> None: - self._tileset_p = ffi.gc( - lib.TCOD_tileset_new(tile_width, tile_height), - lib.TCOD_tileset_delete, + """Initialize the tileset.""" + self._tileset_p = _check_p( + ffi.gc( + lib.TCOD_tileset_new(tile_width, tile_height), + lib.TCOD_tileset_delete, + ) ) @classmethod diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 676ab2bc..76762c8f 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -671,9 +671,7 @@ def map_() -> Iterator[tcod.map.Map]: @pytest.fixture() def path_callback(map_: tcod.map.Map) -> Callable[[int, int, int, int, None], bool]: def callback(ox: int, oy: int, dx: int, dy: int, user_data: None) -> bool: - if map_.walkable[dy, dx]: - return True - return False + return bool(map_.walkable[dy, dx]) return callback From 233e4a72803aebfba6ad8b0e3b350e7d27f7d2ea Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 16 Jul 2024 17:03:44 -0700 Subject: [PATCH 0922/1101] Update deprecations to a more standard syntax --- tcod/bsp.py | 9 +++++---- tcod/console.py | 24 ++++++++++++------------ tcod/context.py | 8 ++++---- tcod/image.py | 6 +++--- tcod/libtcodpy.py | 14 +++----------- tcod/random.py | 7 ++++--- tcod/tileset.py | 9 +++++---- 7 files changed, 36 insertions(+), 41 deletions(-) diff --git a/tcod/bsp.py b/tcod/bsp.py index 030bb04b..98783685 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -29,8 +29,9 @@ from typing import Any, Iterator +from typing_extensions import deprecated + import tcod.random -from tcod._internal import deprecate from tcod.cffi import ffi, lib @@ -72,7 +73,7 @@ def __init__(self, x: int, y: int, width: int, height: int) -> None: self.children: tuple[()] | tuple[BSP, BSP] = () @property - @deprecate("This attribute has been renamed to `width`.", FutureWarning) + @deprecated("This attribute has been renamed to `width`.", category=FutureWarning) def w(self) -> int: # noqa: D102 return self.width @@ -81,7 +82,7 @@ def w(self, value: int) -> None: self.width = value @property - @deprecate("This attribute has been renamed to `height`.", FutureWarning) + @deprecated("This attribute has been renamed to `height`.", category=FutureWarning) def h(self) -> int: # noqa: D102 return self.height @@ -177,7 +178,7 @@ def split_recursive( # noqa: PLR0913 ) self._unpack_bsp_tree(cdata) - @deprecate("Use pre_order method instead of walk.") + @deprecated("Use pre_order method instead of walk.") def walk(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in pre order. diff --git a/tcod/console.py b/tcod/console.py index c38a4de7..8dc7309b 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -14,11 +14,11 @@ import numpy as np from numpy.typing import ArrayLike, NDArray -from typing_extensions import Literal +from typing_extensions import Literal, deprecated import tcod._internal import tcod.constants -from tcod._internal import _check, _path_encode, deprecate +from tcod._internal import _check, _path_encode from tcod.cffi import ffi, lib @@ -248,7 +248,7 @@ def ch(self) -> NDArray[np.intc]: return self._tiles["ch"].T if self._order == "F" else self._tiles["ch"] @property - @deprecate("This attribute has been renamed to `rgba`.", category=FutureWarning) + @deprecated("This attribute has been renamed to `rgba`.", category=FutureWarning) def tiles(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -264,7 +264,7 @@ def tiles(self) -> NDArray[Any]: return self.rgba @property - @deprecate("This attribute has been renamed to `rgba`.", category=FutureWarning) + @deprecated("This attribute has been renamed to `rgba`.", category=FutureWarning) def buffer(self) -> NDArray[Any]: """An array of this consoles raw tile data. @@ -276,7 +276,7 @@ def buffer(self) -> NDArray[Any]: return self.rgba @property - @deprecate("This attribute has been renamed to `rgb`.", category=FutureWarning) + @deprecated("This attribute has been renamed to `rgb`.", category=FutureWarning) def tiles_rgb(self) -> NDArray[Any]: """An array of this consoles data without the alpha channel. @@ -288,7 +288,7 @@ def tiles_rgb(self) -> NDArray[Any]: return self.rgb @property - @deprecate("This attribute has been renamed to `rgb`.", category=FutureWarning) + @deprecated("This attribute has been renamed to `rgb`.", category=FutureWarning) def tiles2(self) -> NDArray[Any]: """This name is deprecated in favour of :any:`rgb`. @@ -352,7 +352,7 @@ def default_bg(self) -> tuple[int, int, int]: return color.r, color.g, color.b @default_bg.setter - @deprecate("Console defaults have been deprecated.", category=FutureWarning) + @deprecated("Console defaults have been deprecated.", category=FutureWarning) def default_bg(self, color: tuple[int, int, int]) -> None: self._console_data.back = color @@ -363,7 +363,7 @@ def default_fg(self) -> tuple[int, int, int]: return color.r, color.g, color.b @default_fg.setter - @deprecate("Console defaults have been deprecated.", category=FutureWarning) + @deprecated("Console defaults have been deprecated.", category=FutureWarning) def default_fg(self, color: tuple[int, int, int]) -> None: self._console_data.fore = color @@ -373,7 +373,7 @@ def default_bg_blend(self) -> int: return self._console_data.bkgnd_flag # type: ignore @default_bg_blend.setter - @deprecate("Console defaults have been deprecated.", category=FutureWarning) + @deprecated("Console defaults have been deprecated.", category=FutureWarning) def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @@ -383,7 +383,7 @@ def default_alignment(self) -> int: return self._console_data.alignment # type: ignore @default_alignment.setter - @deprecate("Console defaults have been deprecated.", category=FutureWarning) + @deprecated("Console defaults have been deprecated.", category=FutureWarning) def default_alignment(self, value: int) -> None: self._console_data.alignment = value @@ -830,7 +830,7 @@ def blit( # noqa: PLR0913 bg_alpha, ) - @deprecate("Pass the key color to Console.blit instead of calling this function.") + @deprecated("Pass the key color to Console.blit instead of calling this function.") def set_key_color(self, color: tuple[int, int, int] | None) -> None: """Set a consoles blit transparent color. @@ -1234,7 +1234,7 @@ def get_height_rect(width: int, string: str) -> int: return int(lib.TCOD_console_get_height_rect_wn(width, len(string_), string_)) -@deprecate("This function does not support contexts.", category=FutureWarning) +@deprecated("This function does not support contexts.", category=FutureWarning) def recommended_size() -> tuple[int, int]: """Return the recommended size of a console for the current active window. diff --git a/tcod/context.py b/tcod/context.py index 3b3e8e0a..1b646e61 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -32,7 +32,7 @@ from pathlib import Path from typing import Any, Iterable, NoReturn, TypeVar -from typing_extensions import Literal +from typing_extensions import Literal, deprecated import tcod.console import tcod.event @@ -40,7 +40,7 @@ import tcod.sdl.render import tcod.sdl.video import tcod.tileset -from tcod._internal import _check, _check_warn, pending_deprecate +from tcod._internal import _check, _check_warn from tcod.cffi import ffi, lib __all__ = ( @@ -574,7 +574,7 @@ def new( # noqa: PLR0913 return Context._claim(context_pp[0]) -@pending_deprecate("Call tcod.context.new with width and height as keyword parameters.") +@deprecated("Call tcod.context.new with width and height as keyword parameters.") def new_window( # noqa: PLR0913 width: int, height: int, @@ -601,7 +601,7 @@ def new_window( # noqa: PLR0913 ) -@pending_deprecate("Call tcod.context.new with columns and rows as keyword parameters.") +@deprecated("Call tcod.context.new with columns and rows as keyword parameters.") def new_terminal( # noqa: PLR0913 columns: int, rows: int, diff --git a/tcod/image.py b/tcod/image.py index 7b2ee13d..dca67314 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -17,9 +17,10 @@ import numpy as np from numpy.typing import ArrayLike, NDArray +from typing_extensions import deprecated import tcod.console -from tcod._internal import _console, _path_encode, deprecate +from tcod._internal import _console, _path_encode from tcod.cffi import ffi, lib @@ -357,10 +358,9 @@ def _get_format_name(format: int) -> str: return str(format) -@deprecate( +@deprecated( "This function may be removed in the future." " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", - category=PendingDeprecationWarning, ) def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: """Load a PNG file as an RGBA array. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index feaa0d52..f18be0bf 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -12,7 +12,7 @@ import numpy as np from numpy.typing import NDArray -from typing_extensions import Literal +from typing_extensions import Literal, deprecated import tcod.bsp import tcod.console @@ -77,6 +77,7 @@ def BKGND_ADDALPHA(a: int) -> int: return BKGND_ADDA | (int(a * 255) << 8) +@deprecated("Console array attributes perform better than this class.") class ConsoleBuffer: """Simple console that allows direct (fast) access to cells. Simplifies use of the "fill" functions. @@ -111,11 +112,6 @@ def __init__( Values to fill the buffer are optional, defaults to black with no characters. """ - warnings.warn( - "Console array attributes perform better than this class.", - DeprecationWarning, - stacklevel=2, - ) self.width = width self.height = height self.clear(back_r, back_g, back_b, fore_r, fore_g, fore_b, char) @@ -271,6 +267,7 @@ def blit( dest.ch.ravel()[:] = self.char +@deprecated("Using this class is not recommended.") class Dice(_CDataWrapper): """A libtcod dice object. @@ -286,11 +283,6 @@ class Dice(_CDataWrapper): """ def __init__(self, *args: Any, **kwargs: Any) -> None: - warnings.warn( - "Using this class is not recommended.", - DeprecationWarning, - stacklevel=2, - ) super().__init__(*args, **kwargs) if self.cdata == ffi.NULL: self._init(*args, **kwargs) diff --git a/tcod/random.py b/tcod/random.py index 73121578..fe1944db 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -14,8 +14,9 @@ import warnings from typing import Any, Hashable +from typing_extensions import deprecated + import tcod.constants -from tcod._internal import deprecate from tcod.cffi import ffi, lib MERSENNE_TWISTER = tcod.constants.RNG_MT @@ -127,7 +128,7 @@ def gauss(self, mu: float, sigma: float) -> float: """ return float(lib.TCOD_random_get_gaussian_double(self.random_c, mu, sigma)) - @deprecate("This is a typo, rename this to 'gauss'", category=FutureWarning) + @deprecated("This is a typo, rename this to 'gauss'", category=FutureWarning) def guass(self, mu: float, sigma: float) -> float: # noqa: D102 return self.gauss(mu, sigma) @@ -146,7 +147,7 @@ def inverse_gauss(self, mu: float, sigma: float) -> float: """ return float(lib.TCOD_random_get_gaussian_double_inv(self.random_c, mu, sigma)) - @deprecate("This is a typo, rename this to 'inverse_gauss'", category=FutureWarning) + @deprecated("This is a typo, rename this to 'inverse_gauss'", category=FutureWarning) def inverse_guass(self, mu: float, sigma: float) -> float: # noqa: D102 return self.inverse_gauss(mu, sigma) diff --git a/tcod/tileset.py b/tcod/tileset.py index 022c57ac..dad40f18 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -20,9 +20,10 @@ import numpy as np from numpy.typing import ArrayLike, NDArray +from typing_extensions import deprecated import tcod.console -from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error, deprecate +from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error from tcod.cffi import ffi, lib @@ -235,7 +236,7 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: ) -@deprecate("Using the default tileset is deprecated.") +@deprecated("Using the default tileset is deprecated.") def get_default() -> Tileset: """Return a reference to the default Tileset. @@ -248,7 +249,7 @@ def get_default() -> Tileset: return Tileset._claim(lib.TCOD_get_default_tileset()) -@deprecate("Using the default tileset is deprecated.") +@deprecated("Using the default tileset is deprecated.") def set_default(tileset: Tileset) -> None: """Set the default tileset. @@ -278,7 +279,7 @@ def load_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: return Tileset._claim(cdata) -@deprecate("Accessing the default tileset is deprecated.") +@deprecated("Accessing the default tileset is deprecated.") def set_truetype_font(path: str | PathLike[str], tile_width: int, tile_height: int) -> None: """Set the default tileset from a `.ttf` or `.otf` file. From 82d3582b904991cacc7a920a765aaafb9d8f9958 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 7 Aug 2024 00:37:09 -0700 Subject: [PATCH 0923/1101] Mark KMOD_ names as deprecated --- CHANGELOG.md | 4 ++++ examples/samples_tcod.py | 11 +++++++---- tcod/event.py | 26 ++++++++------------------ tcod/event_constants.py | 17 ----------------- tests/test_deprecated.py | 7 +++++++ 5 files changed, 26 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5a2551..5b779612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Deprecated + +- Keyboard bitmask modifiers `tcod.event.KMOD_*` have been replaced by `tcod.event.Modifier`. + ## [16.2.3] - 2024-07-16 ### Fixed diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index d2a8cb86..c52f4be4 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -27,6 +27,7 @@ import tcod.sdl.mouse import tcod.sdl.render from tcod import libtcodpy +from tcod.sdl.video import WindowFlags # ruff: noqa: S311 @@ -82,11 +83,13 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: cur_sample = (cur_sample - 1) % len(SAMPLES) SAMPLES[cur_sample].on_enter() draw_samples_menu() - elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.KMOD_LALT: - libtcodpy.console_set_fullscreen(not libtcodpy.console_is_fullscreen()) - elif event.sym == tcod.event.KeySym.PRINTSCREEN or event.sym == tcod.event.KeySym.p: + elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.ALT: + sdl_window = context.sdl_window + if sdl_window: + sdl_window.fullscreen = False if sdl_window.fullscreen else WindowFlags.FULLSCREEN_DESKTOP + elif event.sym in (tcod.event.KeySym.PRINTSCREEN, tcod.event.KeySym.p): print("screenshot") - if event.mod & tcod.event.KMOD_LALT: + if event.mod & tcod.event.Modifier.ALT: libtcodpy.console_save_apf(root_console, "samples.apf") print("apf") else: diff --git a/tcod/event.py b/tcod/event.py index 5d7e9aef..d1a2dc99 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -95,7 +95,6 @@ import tcod.sdl.sys from tcod.cffi import ffi, lib from tcod.event_constants import * # noqa: F403 -from tcod.event_constants import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT from tcod.sdl.joystick import _HAT_DIRECTIONS T = TypeVar("T") @@ -2807,6 +2806,14 @@ def __getattr__(name: str) -> int: FutureWarning, stacklevel=2, ) + elif name.startswith("KMOD_"): + modifier = name[5:] + warnings.warn( + "Key modifiers have been replaced with the Modifier IntFlag.\n" + f"`tcod.event.{modifier}` should be replaced with `tcod.event.Modifier.{modifier}`", + FutureWarning, + stacklevel=2, + ) return value @@ -2859,23 +2866,6 @@ def __getattr__(name: str) -> int: "Scancode", "KeySym", # --- From event_constants.py --- - "KMOD_NONE", - "KMOD_LSHIFT", - "KMOD_RSHIFT", - "KMOD_SHIFT", - "KMOD_LCTRL", - "KMOD_RCTRL", - "KMOD_CTRL", - "KMOD_LALT", - "KMOD_RALT", - "KMOD_ALT", - "KMOD_LGUI", - "KMOD_RGUI", - "KMOD_GUI", - "KMOD_NUM", - "KMOD_CAPS", - "KMOD_MODE", - "KMOD_RESERVED", "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", "MOUSEWHEEL", diff --git a/tcod/event_constants.py b/tcod/event_constants.py index d10eeb74..0f55d545 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -540,23 +540,6 @@ } __all__ = [ - "KMOD_NONE", - "KMOD_LSHIFT", - "KMOD_RSHIFT", - "KMOD_SHIFT", - "KMOD_LCTRL", - "KMOD_RCTRL", - "KMOD_CTRL", - "KMOD_LALT", - "KMOD_RALT", - "KMOD_ALT", - "KMOD_LGUI", - "KMOD_RGUI", - "KMOD_GUI", - "KMOD_NUM", - "KMOD_CAPS", - "KMOD_MODE", - "KMOD_SCROLL", "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", "MOUSEWHEEL", diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 869c2b36..82001d47 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -51,6 +51,13 @@ def test_deprecate_mouse_constants() -> None: _ = tcod.event.BUTTON_LMASK +def test_deprecate_kmod_constants() -> None: + with pytest.warns(FutureWarning, match=r"Modifier.LSHIFT"): + _ = tcod.event.KMOD_LSHIFT + with pytest.warns(FutureWarning, match=r"Modifier.GUI"): + _ = tcod.event.KMOD_GUI + + def test_line_where() -> None: with pytest.warns(): where = tcod.libtcodpy.line_where(1, 0, 3, 4) From f187e9cb008fedd1bfe680ff040477b40f7403c5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 7 Aug 2024 01:19:52 -0700 Subject: [PATCH 0924/1101] Update pre-commit --- .pre-commit-config.yaml | 2 +- tcod/console.py | 4 ++-- tcod/context.py | 2 +- tcod/map.py | 2 +- tcod/sdl/render.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a312e948..e137cbe9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.2 + rev: v0.5.6 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] diff --git a/tcod/console.py b/tcod/console.py index 8dc7309b..a4f0a950 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -519,7 +519,7 @@ def __deprecate_defaults( # noqa: C901, PLR0912 stacklevel=3, ) - def print_( # noqa: PLR0913 + def print_( self, x: int, y: int, @@ -596,7 +596,7 @@ def print_rect( # noqa: PLR0913 ) ) - def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: # noqa: PLR0913 + def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) -> int: """Return the height of this text word-wrapped into this rectangle. Args: diff --git a/tcod/context.py b/tcod/context.py index 1b646e61..2132200f 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -181,7 +181,7 @@ def __exit__(self, *_: object) -> None: """Automatically close on the context on exit.""" self.close() - def present( # noqa: PLR0913 + def present( self, console: tcod.console.Console, *, diff --git a/tcod/map.py b/tcod/map.py index 488c4878..e8d31306 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -115,7 +115,7 @@ def fov(self) -> NDArray[np.bool_]: buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2] return buffer.T if self._order == "F" else buffer - def compute_fov( # noqa: PLR0913 + def compute_fov( self, x: int, y: int, diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index c57d2242..1def4c72 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -655,7 +655,7 @@ def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: raise TypeError(msg) @_required_version((2, 0, 18)) - def geometry( # noqa: PLR0913 + def geometry( self, texture: Texture | None, xy: NDArray[np.float32], From b692d789ff7ff99a6679b53f3722242e6343c837 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 13 Aug 2024 23:08:53 -0700 Subject: [PATCH 0925/1101] Make EventDispatch on event methods positional only --- CHANGELOG.md | 4 +++ tcod/event.py | 77 ++++++++++++++++++++++++++------------------------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b779612..efa8f927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- `EventDispatch`'s on event methods are now defined as positional parameters, so renaming the `event` parameter is now valid in subclasses. + ### Deprecated - Keyboard bitmask modifiers `tcod.event.KMOD_*` have been replaced by `tcod.event.Modifier`. diff --git a/tcod/event.py b/tcod/event.py index d1a2dc99..29594d7b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -90,6 +90,7 @@ from numpy.typing import NDArray from typing_extensions import Literal +import tcod.event import tcod.event_constants import tcod.sdl.joystick import tcod.sdl.sys @@ -1377,160 +1378,160 @@ def event_wait(self, timeout: float | None) -> None: wait(timeout) self.event_get() - def ev_quit(self, event: tcod.event.Quit) -> T | None: + def ev_quit(self, event: tcod.event.Quit, /) -> T | None: """Called when the termination of the program is requested.""" - def ev_keydown(self, event: tcod.event.KeyDown) -> T | None: + def ev_keydown(self, event: tcod.event.KeyDown, /) -> T | None: """Called when a keyboard key is pressed or repeated.""" - def ev_keyup(self, event: tcod.event.KeyUp) -> T | None: + def ev_keyup(self, event: tcod.event.KeyUp, /) -> T | None: """Called when a keyboard key is released.""" - def ev_mousemotion(self, event: tcod.event.MouseMotion) -> T | None: + def ev_mousemotion(self, event: tcod.event.MouseMotion, /) -> T | None: """Called when the mouse is moved.""" - def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> T | None: + def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown, /) -> T | None: """Called when a mouse button is pressed.""" - def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> T | None: + def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp, /) -> T | None: """Called when a mouse button is released.""" - def ev_mousewheel(self, event: tcod.event.MouseWheel) -> T | None: + def ev_mousewheel(self, event: tcod.event.MouseWheel, /) -> T | None: """Called when the mouse wheel is scrolled.""" - def ev_textinput(self, event: tcod.event.TextInput) -> T | None: + def ev_textinput(self, event: tcod.event.TextInput, /) -> T | None: """Called to handle Unicode input.""" - def ev_windowshown(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowshown(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is shown.""" - def ev_windowhidden(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowhidden(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is hidden.""" - def ev_windowexposed(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowexposed(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when a window is exposed, and needs to be refreshed. This usually means a call to :any:`libtcodpy.console_flush` is necessary. """ - def ev_windowmoved(self, event: tcod.event.WindowMoved) -> T | None: + def ev_windowmoved(self, event: tcod.event.WindowMoved, /) -> T | None: """Called when the window is moved.""" - def ev_windowresized(self, event: tcod.event.WindowResized) -> T | None: + def ev_windowresized(self, event: tcod.event.WindowResized, /) -> T | None: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: tcod.event.WindowResized) -> T | None: + def ev_windowsizechanged(self, event: tcod.event.WindowResized, /) -> T | None: """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowminimized(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is minimized.""" - def ev_windowmaximized(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowmaximized(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is maximized.""" - def ev_windowrestored(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowrestored(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is restored.""" - def ev_windowenter(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowenter(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window gains mouse focus.""" - def ev_windowleave(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowleave(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window loses mouse focus.""" - def ev_windowfocusgained(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowfocusgained(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window gains keyboard focus.""" - def ev_windowfocuslost(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowfocuslost(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window loses keyboard focus.""" - def ev_windowclose(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowclose(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowtakefocus(self, event: tcod.event.WindowEvent, /) -> T | None: pass - def ev_windowhittest(self, event: tcod.event.WindowEvent) -> T | None: + def ev_windowhittest(self, event: tcod.event.WindowEvent, /) -> T | None: pass - def ev_joyaxismotion(self, event: tcod.event.JoystickAxis) -> T | None: + def ev_joyaxismotion(self, event: tcod.event.JoystickAxis, /) -> T | None: """Called when a joystick analog is moved. .. versionadded:: 13.8 """ - def ev_joyballmotion(self, event: tcod.event.JoystickBall) -> T | None: + def ev_joyballmotion(self, event: tcod.event.JoystickBall, /) -> T | None: """Called when a joystick ball is moved. .. versionadded:: 13.8 """ - def ev_joyhatmotion(self, event: tcod.event.JoystickHat) -> T | None: + def ev_joyhatmotion(self, event: tcod.event.JoystickHat, /) -> T | None: """Called when a joystick hat is moved. .. versionadded:: 13.8 """ - def ev_joybuttondown(self, event: tcod.event.JoystickButton) -> T | None: + def ev_joybuttondown(self, event: tcod.event.JoystickButton, /) -> T | None: """Called when a joystick button is pressed. .. versionadded:: 13.8 """ - def ev_joybuttonup(self, event: tcod.event.JoystickButton) -> T | None: + def ev_joybuttonup(self, event: tcod.event.JoystickButton, /) -> T | None: """Called when a joystick button is released. .. versionadded:: 13.8 """ - def ev_joydeviceadded(self, event: tcod.event.JoystickDevice) -> T | None: + def ev_joydeviceadded(self, event: tcod.event.JoystickDevice, /) -> T | None: """Called when a joystick is added. .. versionadded:: 13.8 """ - def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice) -> T | None: + def ev_joydeviceremoved(self, event: tcod.event.JoystickDevice, /) -> T | None: """Called when a joystick is removed. .. versionadded:: 13.8 """ - def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis) -> T | None: + def ev_controlleraxismotion(self, event: tcod.event.ControllerAxis, /) -> T | None: """Called when a controller analog is moved. .. versionadded:: 13.8 """ - def ev_controllerbuttondown(self, event: tcod.event.ControllerButton) -> T | None: + def ev_controllerbuttondown(self, event: tcod.event.ControllerButton, /) -> T | None: """Called when a controller button is pressed. .. versionadded:: 13.8 """ - def ev_controllerbuttonup(self, event: tcod.event.ControllerButton) -> T | None: + def ev_controllerbuttonup(self, event: tcod.event.ControllerButton, /) -> T | None: """Called when a controller button is released. .. versionadded:: 13.8 """ - def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice) -> T | None: + def ev_controllerdeviceadded(self, event: tcod.event.ControllerDevice, /) -> T | None: """Called when a standard controller is added. .. versionadded:: 13.8 """ - def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice) -> T | None: + def ev_controllerdeviceremoved(self, event: tcod.event.ControllerDevice, /) -> T | None: """Called when a standard controller is removed. .. versionadded:: 13.8 """ - def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice) -> T | None: + def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice, /) -> T | None: """Called when a standard controller is remapped. .. versionadded:: 13.8 """ - def ev_(self, event: Any) -> T | None: + def ev_(self, event: Any, /) -> T | None: pass From a8f66fcaba57f15901897496a4ccdcb10c4363fb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 14 Aug 2024 14:34:43 -0700 Subject: [PATCH 0926/1101] Suppress internal mouse.tile_motion deprecation warning --- CHANGELOG.md | 4 ++++ tcod/context.py | 2 +- tcod/event.py | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa8f927..2b87ebb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Keyboard bitmask modifiers `tcod.event.KMOD_*` have been replaced by `tcod.event.Modifier`. +### Fixed + +- Suppressed internal `mouse.tile_motion` deprecation warning. + ## [16.2.3] - 2024-07-16 ### Fixed diff --git a/tcod/context.py b/tcod/context.py index 2132200f..1a2ea21c 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -265,7 +265,7 @@ def convert_event(self, event: _Event) -> _Event: event.position[0] - event.motion[0], event.position[1] - event.motion[1], ) - event_copy.motion = event.tile_motion = tcod.event.Point( + event_copy.motion = event._tile_motion = tcod.event.Point( event._tile[0] - prev_tile[0], event._tile[1] - prev_tile[1] ) return event_copy diff --git a/tcod/event.py b/tcod/event.py index 29594d7b..48cf9c37 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -486,7 +486,7 @@ def __init__( ) -> None: super().__init__(position, tile, state) self.motion = Point(*motion) - self.__tile_motion = Point(*tile_motion) if tile_motion is not None else None + self._tile_motion = Point(*tile_motion) if tile_motion is not None else None @property def pixel_motion(self) -> Point: @@ -514,7 +514,7 @@ def tile_motion(self) -> Point: DeprecationWarning, stacklevel=2, ) - return _verify_tile_coordinates(self.__tile_motion) + return _verify_tile_coordinates(self._tile_motion) @tile_motion.setter def tile_motion(self, xy: tuple[int, int]) -> None: @@ -524,7 +524,7 @@ def tile_motion(self, xy: tuple[int, int]) -> None: DeprecationWarning, stacklevel=2, ) - self.__tile_motion = Point(*xy) + self._tile_motion = Point(*xy) @classmethod def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: From b033bf265275449ab6ff159362ab27521c5c71ea Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 15 Aug 2024 20:21:01 -0700 Subject: [PATCH 0927/1101] pre-commit update --- .pre-commit-config.yaml | 2 +- examples/samples_tcod.py | 3 +-- tests/conftest.py | 10 +++++----- tests/test_libtcodpy.py | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e137cbe9..f19dd6aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.6 + rev: v0.6.0 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index c52f4be4..942c1d69 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -726,8 +726,7 @@ def on_draw(self) -> None: for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): d = libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) - if d > self.dijkstra_dist: - self.dijkstra_dist = d + self.dijkstra_dist = max(d, self.dijkstra_dist) # compute path from px,py to dx,dy libtcodpy.dijkstra_path_set(self.dijkstra, self.dx, self.dy) self.recalculate = False diff --git a/tests/conftest.py b/tests/conftest.py index 2998b4ae..0efc716d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") -@pytest.fixture() +@pytest.fixture def uses_window(request: pytest.FixtureRequest) -> Iterator[None]: """Marks tests which require a rendering context.""" if request.config.getoption("--no-window"): @@ -40,7 +40,7 @@ def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Con yield con -@pytest.fixture() +@pytest.fixture def console(session_console: tcod.console.Console) -> tcod.console.Console: console = session_console tcod.console_flush() @@ -54,18 +54,18 @@ def console(session_console: tcod.console.Console) -> tcod.console.Console: return console -@pytest.fixture() +@pytest.fixture def offscreen(console: tcod.console.Console) -> tcod.console.Console: """Return an off-screen console with the same size as the root console.""" return tcod.console.Console(console.width, console.height) -@pytest.fixture() +@pytest.fixture def fg() -> tcod.Color: return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) -@pytest.fixture() +@pytest.fixture def bg() -> tcod.Color: return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 76762c8f..7b0bfcc1 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -660,7 +660,7 @@ def test_heightmap() -> None: POINTS_AC = POINT_A + POINT_C # invalid path -@pytest.fixture() +@pytest.fixture def map_() -> Iterator[tcod.map.Map]: map_ = tcod.map.Map(MAP_WIDTH, MAP_HEIGHT) map_.walkable[...] = map_.transparent[...] = MAP[...] == " " @@ -668,7 +668,7 @@ def map_() -> Iterator[tcod.map.Map]: libtcodpy.map_delete(map_) -@pytest.fixture() +@pytest.fixture def path_callback(map_: tcod.map.Map) -> Callable[[int, int, int, int, None], bool]: def callback(ox: int, oy: int, dx: int, dy: int, user_data: None) -> bool: return bool(map_.walkable[dy, dx]) From b24d53974ad0f9e4d43658fcb70076cd2b33766e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 18 Oct 2024 19:19:35 -0700 Subject: [PATCH 0928/1101] Update pre-commit --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f19dd6aa..19933511 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: autoupdate_schedule: quarterly repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.0 + rev: v0.7.0 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From c36dd03979b83ec1625c6419cb1b504b614cd429 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 18 Oct 2024 19:16:54 -0700 Subject: [PATCH 0929/1101] Clean up type errors, use standard deprecation functions Fix minor typos Improve deprecation docs Set lower bound for typing_extensions --- pyproject.toml | 2 +- tcod/_internal.py | 30 ++++------ tcod/cffi.py | 2 +- tcod/color.py | 14 ++--- tcod/console.py | 61 +++++++++++++++---- tcod/libtcodpy.py | 128 ++++++++++++++++++++-------------------- tcod/map.py | 2 +- tcod/path.py | 2 +- tests/test_console.py | 4 +- tests/test_libtcodpy.py | 6 +- 10 files changed, 143 insertions(+), 108 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 97ee6190..6c68edb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ license = { text = "Simplified BSD License" } dependencies = [ "cffi>=1.15", 'numpy>=1.21.4; implementation_name != "pypy"', - "typing_extensions", + "typing_extensions>=4.12.2", ] keywords = [ "roguelike", diff --git a/tcod/_internal.py b/tcod/_internal.py index cebe249e..85410cb2 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -2,17 +2,16 @@ from __future__ import annotations -import functools import locale import sys import warnings from pathlib import Path from types import TracebackType -from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar, cast +from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar import numpy as np from numpy.typing import ArrayLike, NDArray -from typing_extensions import Literal +from typing_extensions import Literal, LiteralString, deprecated from tcod.cffi import ffi, lib @@ -24,31 +23,28 @@ T = TypeVar("T") -def deprecate(message: str, category: type[Warning] = DeprecationWarning, stacklevel: int = 0) -> Callable[[F], F]: - """Return a decorator which adds a warning to functions.""" +def _deprecate_passthrough( + message: str, /, *, category: type[Warning] = DeprecationWarning, stacklevel: int = 0 +) -> Callable[[F], F]: + """Return a decorator which skips wrapping a warning onto functions. This is used for non-debug runs.""" def decorator(func: F) -> F: - if not __debug__: - return func + return func - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401 - warnings.warn(message, category, stacklevel=stacklevel + 2) - return func(*args, **kwargs) + return decorator - return cast(F, wrapper) - return decorator +deprecate = deprecated if __debug__ or TYPE_CHECKING else _deprecate_passthrough def pending_deprecate( - message: str = "This function may be deprecated in the future." + message: LiteralString = "This function may be deprecated in the future." " Consider raising an issue on GitHub if you need this feature.", category: type[Warning] = PendingDeprecationWarning, stacklevel: int = 0, ) -> Callable[[F], F]: """Like deprecate, but the default parameters are filled out for a generic pending deprecation warning.""" - return deprecate(message, category, stacklevel) + return deprecate(message, category=category, stacklevel=stacklevel) def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]: @@ -119,7 +115,7 @@ def _unicode(string: AnyStr, stacklevel: int = 2) -> str: stacklevel=stacklevel + 1, ) return string.decode("latin-1") - return string + return str(string) def _fmt(string: str, stacklevel: int = 2) -> bytes: @@ -271,5 +267,5 @@ def __init__(self, array: ArrayLike) -> None: def _as_image(image: ArrayLike | tcod.image.Image) -> TempImage | tcod.image.Image: """Convert this input into an Image-like object.""" if hasattr(image, "image_c"): - return image # type: ignore + return image return TempImage(image) diff --git a/tcod/cffi.py b/tcod/cffi.py index 19a597b6..2325519d 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -59,7 +59,7 @@ def get_sdl_version() -> str: verify_dependencies() -from tcod._libtcod import ffi, lib # noqa +from tcod._libtcod import ffi, lib # noqa: E402 __sdl_version__ = get_sdl_version() diff --git a/tcod/color.py b/tcod/color.py index ef75bfb8..49b98ad7 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -31,7 +31,7 @@ def r(self) -> int: return int(self[0]) @r.setter - @deprecate("Setting color attributes has been deprecated.", FutureWarning) + @deprecate("Setting color attributes has been deprecated.", category=FutureWarning) def r(self, value: int) -> None: self[0] = value & 0xFF @@ -45,7 +45,7 @@ def g(self) -> int: return int(self[1]) @g.setter - @deprecate("Setting color attributes has been deprecated.", FutureWarning) + @deprecate("Setting color attributes has been deprecated.", category=FutureWarning) def g(self, value: int) -> None: self[1] = value & 0xFF @@ -59,7 +59,7 @@ def b(self) -> int: return int(self[2]) @b.setter - @deprecate("Setting color attributes has been deprecated.", FutureWarning) + @deprecate("Setting color attributes has been deprecated.", category=FutureWarning) def b(self, value: int) -> None: self[2] = value & 0xFF @@ -82,7 +82,7 @@ def __getitem__(self, index: Any) -> Any: # noqa: ANN401 return super().__getitem__("rgb".index(index)) return super().__getitem__(index) - @deprecate("This class will not be mutable in the future.", FutureWarning) + @deprecate("This class will not be mutable in the future.", category=FutureWarning) def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401, D105 if isinstance(index, str): super().__setitem__("rgb".index(index), value) @@ -99,7 +99,7 @@ def __eq__(self, other: object) -> bool: except TypeError: return False - @deprecate("Use NumPy instead for color math operations.", FutureWarning) + @deprecate("Use NumPy instead for color math operations.", category=FutureWarning) def __add__(self, other: object) -> Color: # type: ignore[override] """Add two colors together. @@ -108,7 +108,7 @@ def __add__(self, other: object) -> Color: # type: ignore[override] """ return Color._new_from_cdata(lib.TCOD_color_add(self, other)) - @deprecate("Use NumPy instead for color math operations.", FutureWarning) + @deprecate("Use NumPy instead for color math operations.", category=FutureWarning) def __sub__(self, other: object) -> Color: """Subtract one color from another. @@ -117,7 +117,7 @@ def __sub__(self, other: object) -> Color: """ return Color._new_from_cdata(lib.TCOD_color_subtract(self, other)) - @deprecate("Use NumPy instead for color math operations.", FutureWarning) + @deprecate("Use NumPy instead for color math operations.", category=FutureWarning) def __mul__(self, other: object) -> Color: """Multiply with a scaler or another color. diff --git a/tcod/console.py b/tcod/console.py index a4f0a950..04845a94 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -122,7 +122,7 @@ def __init__( ) -> None: """Initialize the console.""" self._key_color: tuple[int, int, int] | None = None - self._order = tcod._internal.verify_order(order) + self._order: Literal["C", "F"] = tcod._internal.verify_order(order) if buffer is not None: if self._order == "F": buffer = buffer.transpose() @@ -345,45 +345,82 @@ def rgb(self) -> NDArray[Any]: """ return self.rgba.view(self._DTYPE_RGB) + _DEPRECATE_CONSOLE_DEFAULTS_MSG = """Console defaults have been deprecated. +Consider one of the following: + + # Set parameters once then pass them as kwargs + DEFAULT_COLOR = {"bg": (0, 0, 127), "fg": (127, 127, 255)} + console.print(x, y, string, **DEFAULT_COLOR) + + # Clear the console to a color and then skip setting colors on printing/drawing + console.clear(fg=(127, 127, 255), bg=(0, 0, 127)) + console.print(x, y, string, fg=None) +""" + @property + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_bg(self) -> tuple[int, int, int]: - """Tuple[int, int, int]: The default background color.""" + """Tuple[int, int, int]: The default background color. + + .. deprecated:: 8.5 + These should not be used. Prefer passing defaults as kwargs. + + .. code-block:: + + DEFAULT_COLOR = {"bg": (0, 0, 127), "fg": (127, 127, 255)} + console.print(x, y, string, **DEFAULT_COLOR) + """ color = self._console_data.back return color.r, color.g, color.b @default_bg.setter - @deprecated("Console defaults have been deprecated.", category=FutureWarning) + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_bg(self, color: tuple[int, int, int]) -> None: self._console_data.back = color @property + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_fg(self) -> tuple[int, int, int]: - """Tuple[int, int, int]: The default foreground color.""" + """Tuple[int, int, int]: The default foreground color. + + .. deprecated:: 8.5 + These should not be used. Prefer passing defaults as kwargs. + """ color = self._console_data.fore return color.r, color.g, color.b @default_fg.setter - @deprecated("Console defaults have been deprecated.", category=FutureWarning) + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_fg(self, color: tuple[int, int, int]) -> None: self._console_data.fore = color @property + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_bg_blend(self) -> int: - """int: The default blending mode.""" + """int: The default blending mode. + + .. deprecated:: 8.5 + These should not be used. Prefer passing defaults as kwargs. + """ return self._console_data.bkgnd_flag # type: ignore @default_bg_blend.setter - @deprecated("Console defaults have been deprecated.", category=FutureWarning) + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @property + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_alignment(self) -> int: - """int: The default text alignment.""" + """int: The default text alignment. + + .. deprecated:: 8.5 + These should not be used. Prefer passing defaults as kwargs. + """ return self._console_data.alignment # type: ignore @default_alignment.setter - @deprecated("Console defaults have been deprecated.", category=FutureWarning) + @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_alignment(self, value: int) -> None: self._console_data.alignment = value @@ -391,7 +428,7 @@ def __clear_warning(self, name: str, value: tuple[int, int, int]) -> None: """Raise a warning for bad default values during calls to clear.""" warnings.warn( f"Clearing with the console default values is deprecated.\nAdd {name}={value!r} to this call.", - DeprecationWarning, + FutureWarning, stacklevel=3, ) @@ -737,8 +774,8 @@ def print_frame( # noqa: PLR0913 explicit. """ self.__deprecate_defaults("draw_frame", bg_blend) - string = _fmt(string) if string else ffi.NULL - _check(lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, clear, bg_blend, string)) + string_: Any = _fmt(string) if string else ffi.NULL + _check(lib.TCOD_console_printf_frame(self.console_c, x, y, width, height, clear, bg_blend, string_)) def blit( # noqa: PLR0913 self, diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index f18be0bf..aa9cc51b 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -347,6 +347,8 @@ class Key(_CDataWrapper): Use events from the :any:`tcod.event` module instead. """ + cdata: Any + _BOOL_ATTRIBUTES = ( "lalt", "lctrl", @@ -509,7 +511,7 @@ def mouse_p(self) -> Any: return self.cdata -@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.", FutureWarning) +@deprecate("Call tcod.bsp.BSP(x, y, width, height) instead.", category=FutureWarning) def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: """Create a new BSP instance with the given rectangle. @@ -528,7 +530,7 @@ def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: return Bsp(x, y, w, h) -@deprecate("Call node.split_once instead.", FutureWarning) +@deprecate("Call node.split_once instead.", category=FutureWarning) def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: """Deprecated function. @@ -538,7 +540,7 @@ def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: node.split_once(horizontal, position) -@deprecate("Call node.split_recursive instead.", FutureWarning) +@deprecate("Call node.split_recursive instead.", category=FutureWarning) def bsp_split_recursive( node: tcod.bsp.BSP, randomizer: tcod.random.Random | None, @@ -556,7 +558,7 @@ def bsp_split_recursive( node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer) -@deprecate("Assign values via attribute instead.", FutureWarning) +@deprecate("Assign values via attribute instead.", category=FutureWarning) def bsp_resize(node: tcod.bsp.BSP, x: int, y: int, w: int, h: int) -> None: """Deprecated function. @@ -589,7 +591,7 @@ def bsp_right(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: return None if not node.children else node.children[1] -@deprecate("Get the parent with 'node.parent' instead.", FutureWarning) +@deprecate("Get the parent with 'node.parent' instead.", category=FutureWarning) def bsp_father(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: """Deprecated function. @@ -599,7 +601,7 @@ def bsp_father(node: tcod.bsp.BSP) -> tcod.bsp.BSP | None: return node.parent -@deprecate("Check for children with 'bool(node.children)' instead.", FutureWarning) +@deprecate("Check for children with 'bool(node.children)' instead.", category=FutureWarning) def bsp_is_leaf(node: tcod.bsp.BSP) -> bool: """Deprecated function. @@ -609,7 +611,7 @@ def bsp_is_leaf(node: tcod.bsp.BSP) -> bool: return not node.children -@deprecate("Use 'node.contains' instead.", FutureWarning) +@deprecate("Use 'node.contains' instead.", category=FutureWarning) def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool: """Deprecated function. @@ -619,7 +621,7 @@ def bsp_contains(node: tcod.bsp.BSP, cx: int, cy: int) -> bool: return node.contains(cx, cy) -@deprecate("Use 'node.find_node' instead.", FutureWarning) +@deprecate("Use 'node.find_node' instead.", category=FutureWarning) def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> tcod.bsp.BSP | None: """Deprecated function. @@ -723,7 +725,7 @@ def bsp_remove_sons(node: tcod.bsp.BSP) -> None: node.children = () -@deprecate("libtcod objects are deleted automatically.", FutureWarning) +@deprecate("libtcod objects are deleted automatically.", category=FutureWarning) def bsp_delete(node: tcod.bsp.BSP) -> None: """Exists for backward compatibility. Does nothing. @@ -1609,8 +1611,8 @@ def console_print_frame( .. deprecated:: 8.5 Use :any:`Console.print_frame` instead. """ - fmt = _fmt(fmt) if fmt else ffi.NULL - _check(lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt)) + fmt_: Any = _fmt(fmt) if fmt else ffi.NULL + _check(lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt_)) @pending_deprecate() @@ -1680,7 +1682,7 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore -@deprecate("This function is not supported if contexts are being used.", FutureWarning) +@deprecate("This function is not supported if contexts are being used.", category=FutureWarning) def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: """Deprecated function. @@ -1690,7 +1692,7 @@ def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: lib.TCOD_console_set_fade(fade, fadingColor) -@deprecate("This function is not supported if contexts are being used.", FutureWarning) +@deprecate("This function is not supported if contexts are being used.", category=FutureWarning) def console_get_fade() -> int: """Deprecated function. @@ -1700,7 +1702,7 @@ def console_get_fade() -> int: return int(lib.TCOD_console_get_fade()) -@deprecate("This function is not supported if contexts are being used.", FutureWarning) +@deprecate("This function is not supported if contexts are being used.", category=FutureWarning) def console_get_fading_color() -> Color: """Deprecated function. @@ -1753,7 +1755,7 @@ def console_check_for_keypress(flags: int = KEY_RELEASED) -> Key: return key -@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.", FutureWarning) +@deprecate("Use tcod.event.get_keyboard_state to see if a key is held.", category=FutureWarning) def console_is_key_pressed(key: int) -> bool: """Return True if a key is held. @@ -2972,52 +2974,52 @@ def heightmap_delete(hm: Any) -> None: """ -@deprecate("Use `tcod.image.Image(width, height)` instead.", FutureWarning) +@deprecate("Use `tcod.image.Image(width, height)` instead.", category=FutureWarning) def image_new(width: int, height: int) -> tcod.image.Image: return tcod.image.Image(width, height) -@deprecate("Use the `image.clear()` method instead.", FutureWarning) +@deprecate("Use the `image.clear()` method instead.", category=FutureWarning) def image_clear(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.clear(col) -@deprecate("Use the `image.invert()` method instead.", FutureWarning) +@deprecate("Use the `image.invert()` method instead.", category=FutureWarning) def image_invert(image: tcod.image.Image) -> None: image.invert() -@deprecate("Use the `image.hflip()` method instead.", FutureWarning) +@deprecate("Use the `image.hflip()` method instead.", category=FutureWarning) def image_hflip(image: tcod.image.Image) -> None: image.hflip() -@deprecate("Use the `image.rotate90(n)` method instead.", FutureWarning) +@deprecate("Use the `image.rotate90(n)` method instead.", category=FutureWarning) def image_rotate90(image: tcod.image.Image, num: int = 1) -> None: image.rotate90(num) -@deprecate("Use the `image.vflip()` method instead.", FutureWarning) +@deprecate("Use the `image.vflip()` method instead.", category=FutureWarning) def image_vflip(image: tcod.image.Image) -> None: image.vflip() -@deprecate("Use the `image.scale(new_width, new_height)` method instead.", FutureWarning) +@deprecate("Use the `image.scale(new_width, new_height)` method instead.", category=FutureWarning) def image_scale(image: tcod.image.Image, neww: int, newh: int) -> None: image.scale(neww, newh) -@deprecate("Use the `image.image_set_key_color(rgb)` method instead.", FutureWarning) +@deprecate("Use the `image.image_set_key_color(rgb)` method instead.", category=FutureWarning) def image_set_key_color(image: tcod.image.Image, col: tuple[int, int, int]) -> None: image.set_key_color(col) -@deprecate("Use `np.asarray(image)[y, x, 3]` instead.", FutureWarning) +@deprecate("Use `np.asarray(image)[y, x, 3]` instead.", category=FutureWarning) def image_get_alpha(image: tcod.image.Image, x: int, y: int) -> int: return image.get_alpha(x, y) -@deprecate("Use the Numpy array interface to check alpha or color keys.", FutureWarning) +@deprecate("Use the Numpy array interface to check alpha or color keys.", category=FutureWarning) def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: return bool(lib.TCOD_image_is_pixel_transparent(image.image_c, x, y)) @@ -3025,7 +3027,7 @@ def image_is_pixel_transparent(image: tcod.image.Image, x: int, y: int) -> bool: @deprecate( "Call the classmethod `tcod.image.Image.from_file` instead to load images." "\nIt's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", - FutureWarning, + category=FutureWarning, ) def image_load(filename: str | PathLike[str]) -> tcod.image.Image: """Load an image file into an Image instance and return it. @@ -3042,7 +3044,7 @@ def image_load(filename: str | PathLike[str]) -> tcod.image.Image: return tcod.image.Image.from_file(filename) -@deprecate("Use `Tileset.render` instead of this function.", FutureWarning) +@deprecate("Use `Tileset.render` instead of this function.", category=FutureWarning) def image_from_console(console: tcod.console.Console) -> tcod.image.Image: """Return an Image with a Consoles pixel data. @@ -3062,7 +3064,7 @@ def image_from_console(console: tcod.console.Console) -> tcod.image.Image: ) -@deprecate("Use `Tileset.render` instead of this function.", FutureWarning) +@deprecate("Use `Tileset.render` instead of this function.", category=FutureWarning) def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console) -> None: """Update an image made with :any:`image_from_console`. @@ -3072,27 +3074,27 @@ def image_refresh_console(image: tcod.image.Image, console: tcod.console.Console image.refresh_console(console) -@deprecate("Access an images size with `image.width` or `image.height`.", FutureWarning) +@deprecate("Access an images size with `image.width` or `image.height`.", category=FutureWarning) def image_get_size(image: tcod.image.Image) -> tuple[int, int]: return image.width, image.height -@deprecate("Use `np.asarray(image)[y, x, :3]` instead.", FutureWarning) +@deprecate("Use `np.asarray(image)[y, x, :3]` instead.", category=FutureWarning) def image_get_pixel(image: tcod.image.Image, x: int, y: int) -> tuple[int, int, int]: return image.get_pixel(x, y) -@deprecate("Use the `image.get_mipmap_pixel(...)` method instead.", FutureWarning) +@deprecate("Use the `image.get_mipmap_pixel(...)` method instead.", category=FutureWarning) def image_get_mipmap_pixel(image: tcod.image.Image, x0: float, y0: float, x1: float, y1: float) -> tuple[int, int, int]: return image.get_mipmap_pixel(x0, y0, x1, y1) -@deprecate("Use `np.asarray(image)[y, x, :3] = rgb` instead.", FutureWarning) +@deprecate("Use `np.asarray(image)[y, x, :3] = rgb` instead.", category=FutureWarning) def image_put_pixel(image: tcod.image.Image, x: int, y: int, col: tuple[int, int, int]) -> None: image.put_pixel(x, y, col) -@deprecate("Use the `image.blit(...)` method instead.", FutureWarning) +@deprecate("Use the `image.blit(...)` method instead.", category=FutureWarning) def image_blit( image: tcod.image.Image, console: tcod.console.Console, @@ -3106,7 +3108,7 @@ def image_blit( image.blit(console, x, y, bkgnd_flag, scalex, scaley, angle) -@deprecate("Use the `image.blit_rect(...)` method instead.", FutureWarning) +@deprecate("Use the `image.blit_rect(...)` method instead.", category=FutureWarning) def image_blit_rect( image: tcod.image.Image, console: tcod.console.Console, @@ -3119,7 +3121,7 @@ def image_blit_rect( image.blit_rect(console, x, y, w, h, bkgnd_flag) -@deprecate("Use `Console.draw_semigraphics(image, ...)` instead.", FutureWarning) +@deprecate("Use `Console.draw_semigraphics(image, ...)` instead.", category=FutureWarning) def image_blit_2x( image: tcod.image.Image, console: tcod.console.Console, @@ -3133,12 +3135,12 @@ def image_blit_2x( image.blit_2x(console, dx, dy, sx, sy, w, h) -@deprecate("Use the `image.save_as` method instead.", FutureWarning) +@deprecate("Use the `image.save_as` method instead.", category=FutureWarning) def image_save(image: tcod.image.Image, filename: str | PathLike[str]) -> None: image.save_as(filename) -@deprecate("libtcod objects are deleted automatically.", FutureWarning) +@deprecate("libtcod objects are deleted automatically.", category=FutureWarning) def image_delete(image: tcod.image.Image) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -3146,7 +3148,7 @@ def image_delete(image: tcod.image.Image) -> None: """ -@deprecate("Use tcod.los.bresenham instead.", FutureWarning) +@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning) def line_init(xo: int, yo: int, xd: int, yd: int) -> None: """Initialize a line whose points will be returned by `line_step`. @@ -3166,7 +3168,7 @@ def line_init(xo: int, yo: int, xd: int, yd: int) -> None: lib.TCOD_line_init(xo, yo, xd, yd) -@deprecate("Use tcod.los.bresenham instead.", FutureWarning) +@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning) def line_step() -> tuple[int, int] | tuple[None, None]: """After calling line_init returns (x, y) points of the line. @@ -3188,7 +3190,7 @@ def line_step() -> tuple[int, int] | tuple[None, None]: return None, None -@deprecate("Use tcod.los.bresenham instead.", FutureWarning) +@deprecate("Use tcod.los.bresenham instead.", category=FutureWarning) def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], bool]) -> bool: """Iterate over a line using a callback function. @@ -3220,7 +3222,7 @@ def line(xo: int, yo: int, xd: int, yd: int, py_callback: Callable[[int, int], b return False -@deprecate("This function has been replaced by tcod.los.bresenham.", FutureWarning) +@deprecate("This function has been replaced by tcod.los.bresenham.", category=FutureWarning) def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: """Returns an Iterable over a Bresenham line. @@ -3247,7 +3249,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: yield (x[0], y[0]) -@deprecate("This function has been replaced by tcod.los.bresenham.", FutureWarning) +@deprecate("This function has been replaced by tcod.los.bresenham.", category=FutureWarning) def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: """Return a NumPy index array following a Bresenham line. @@ -3265,7 +3267,7 @@ def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tu return i, j -@deprecate("Call tcod.map.Map(width, height) instead.", FutureWarning) +@deprecate("Call tcod.map.Map(width, height) instead.", category=FutureWarning) def map_new(w: int, h: int) -> tcod.map.Map: """Return a :any:`tcod.map.Map` with a width and height. @@ -3276,7 +3278,7 @@ def map_new(w: int, h: int) -> tcod.map.Map: return tcod.map.Map(w, h) -@deprecate("Use Python's standard copy module instead.", FutureWarning) +@deprecate("Use Python's standard copy module instead.", category=FutureWarning) def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: """Copy map data from `source` to `dest`. @@ -3289,7 +3291,7 @@ def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore -@deprecate("Set properties using the m.transparent and m.walkable arrays.", FutureWarning) +@deprecate("Set properties using the m.transparent and m.walkable arrays.", category=FutureWarning) def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: """Set the properties of a single cell. @@ -3302,7 +3304,7 @@ def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: b lib.TCOD_map_set_properties(m.map_c, x, y, isTrans, isWalk) -@deprecate("Clear maps using NumPy broadcast rules instead.", FutureWarning) +@deprecate("Clear maps using NumPy broadcast rules instead.", category=FutureWarning) def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: """Change all map cells to a specific value. @@ -3314,7 +3316,7 @@ def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False m.walkable[:] = walkable -@deprecate("Call the map.compute_fov method instead.", FutureWarning) +@deprecate("Call the map.compute_fov method instead.", category=FutureWarning) def map_compute_fov( m: tcod.map.Map, x: int, @@ -3331,7 +3333,7 @@ def map_compute_fov( m.compute_fov(x, y, radius, light_walls, algo) -@deprecate("Use map.fov to check for this property.", FutureWarning) +@deprecate("Use map.fov to check for this property.", category=FutureWarning) def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: """Return True if the cell at x,y is lit by the last field-of-view algorithm. @@ -3343,7 +3345,7 @@ def map_is_in_fov(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_in_fov(m.map_c, x, y)) -@deprecate("Use map.transparent to check for this property.", FutureWarning) +@deprecate("Use map.transparent to check for this property.", category=FutureWarning) def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: """Return True is a map cell is transparent. @@ -3355,7 +3357,7 @@ def map_is_transparent(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_transparent(m.map_c, x, y)) -@deprecate("Use map.walkable to check for this property.", FutureWarning) +@deprecate("Use map.walkable to check for this property.", category=FutureWarning) def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: """Return True is a map cell is walkable. @@ -3367,7 +3369,7 @@ def map_is_walkable(m: tcod.map.Map, x: int, y: int) -> bool: return bool(lib.TCOD_map_is_walkable(m.map_c, x, y)) -@deprecate("libtcod objects are deleted automatically.", FutureWarning) +@deprecate("libtcod objects are deleted automatically.", category=FutureWarning) def map_delete(m: tcod.map.Map) -> None: """Does nothing. libtcod objects are managed by Python's garbage collector. @@ -3375,7 +3377,7 @@ def map_delete(m: tcod.map.Map) -> None: """ -@deprecate("Check the map.width attribute instead.", FutureWarning) +@deprecate("Check the map.width attribute instead.", category=FutureWarning) def map_get_width(map: tcod.map.Map) -> int: """Return the width of a map. @@ -3385,7 +3387,7 @@ def map_get_width(map: tcod.map.Map) -> int: return map.width -@deprecate("Check the map.height attribute instead.", FutureWarning) +@deprecate("Check the map.height attribute instead.", category=FutureWarning) def map_get_height(map: tcod.map.Map) -> int: """Return the height of a map. @@ -3395,7 +3397,7 @@ def map_get_height(map: tcod.map.Map) -> int: return map.height -@deprecate("Use `tcod.sdl.mouse.show(visible)` instead.", FutureWarning) +@deprecate("Use `tcod.sdl.mouse.show(visible)` instead.", category=FutureWarning) def mouse_show_cursor(visible: bool) -> None: """Change the visibility of the mouse cursor. @@ -3405,7 +3407,7 @@ def mouse_show_cursor(visible: bool) -> None: lib.TCOD_mouse_show_cursor(visible) -@deprecate("Use `is_visible = tcod.sdl.mouse.show()` instead.", FutureWarning) +@deprecate("Use `is_visible = tcod.sdl.mouse.show()` instead.", category=FutureWarning) def mouse_is_cursor_visible() -> bool: """Return True if the mouse cursor is visible. @@ -3415,12 +3417,12 @@ def mouse_is_cursor_visible() -> bool: return bool(lib.TCOD_mouse_is_cursor_visible()) -@deprecate("Use `tcod.sdl.mouse.warp_in_window` instead.", FutureWarning) +@deprecate("Use `tcod.sdl.mouse.warp_in_window` instead.", category=FutureWarning) def mouse_move(x: int, y: int) -> None: lib.TCOD_mouse_move(x, y) -@deprecate("Use tcod.event.get_mouse_state() instead.", FutureWarning) +@deprecate("Use tcod.event.get_mouse_state() instead.", category=FutureWarning) def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) @@ -3457,7 +3459,7 @@ def namegen_destroy() -> None: lib.TCOD_namegen_destroy() -@deprecate("Use `tcod.noise.Noise(dimensions, hurst=, lacunarity=)` instead.", FutureWarning) +@deprecate("Use `tcod.noise.Noise(dimensions, hurst=, lacunarity=)` instead.", category=FutureWarning) def noise_new( dim: int, h: float = NOISE_DEFAULT_HURST, @@ -3478,7 +3480,7 @@ def noise_new( return tcod.noise.Noise(dim, hurst=h, lacunarity=l, seed=random) -@deprecate("Use `noise.algorithm = x` instead.", FutureWarning) +@deprecate("Use `noise.algorithm = x` instead.", category=FutureWarning) def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: """Set a Noise objects default noise algorithm. @@ -3489,7 +3491,7 @@ def noise_set_type(n: tcod.noise.Noise, typ: int) -> None: n.algorithm = typ -@deprecate("Use `value = noise[x]` instead.", FutureWarning) +@deprecate("Use `value = noise[x]` instead.", category=FutureWarning) def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) -> float: """Return the noise value sampled from the ``f`` coordinate. @@ -3509,7 +3511,7 @@ def noise_get(n: tcod.noise.Noise, f: Sequence[float], typ: int = NOISE_DEFAULT) return float(lib.TCOD_noise_get_ex(n.noise_c, ffi.new("float[4]", f), typ)) -@deprecate("Configure a Noise instance for FBM and then sample it like normal.", FutureWarning) +@deprecate("Configure a Noise instance for FBM and then sample it like normal.", category=FutureWarning) def noise_get_fbm( n: tcod.noise.Noise, f: Sequence[float], @@ -3530,7 +3532,7 @@ def noise_get_fbm( return float(lib.TCOD_noise_get_fbm_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) -@deprecate("Configure a Noise instance for FBM and then sample it like normal.", FutureWarning) +@deprecate("Configure a Noise instance for FBM and then sample it like normal.", category=FutureWarning) def noise_get_turbulence( n: tcod.noise.Noise, f: Sequence[float], @@ -3551,7 +3553,7 @@ def noise_get_turbulence( return float(lib.TCOD_noise_get_turbulence_ex(n.noise_c, ffi.new("float[4]", f), oc, typ)) -@deprecate("libtcod objects are deleted automatically.", FutureWarning) +@deprecate("libtcod objects are deleted automatically.", category=FutureWarning) def noise_delete(n: tcod.noise.Noise) -> None: # type (Any) -> None """Does nothing. libtcod objects are managed by Python's garbage collector. diff --git a/tcod/map.py b/tcod/map.py index e8d31306..17aaf0b4 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -262,4 +262,4 @@ def compute_fov( ) map_buffer["transparent"] = transparency lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm) - return map_buffer["fov"] # type: ignore + return map_buffer["fov"] diff --git a/tcod/path.py b/tcod/path.py index 8950505f..ffbc1dab 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -131,7 +131,7 @@ class NodeCostArray(np.ndarray): # type: ignore def __new__(cls, array: ArrayLike) -> NodeCostArray: """Validate a numpy array and setup a C callback.""" - return np.asarray(array).view(cls) + return np.asarray(array).view(cls) # type: ignore[no-any-return] def __repr__(self) -> str: return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})" diff --git a/tests/test_console.py b/tests/test_console.py index e329fe6f..b0be600f 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -65,7 +65,7 @@ def test_console_defaults() -> None: @pytest.mark.filterwarnings("ignore:Parameter names have been moved around,") -@pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instea") +@pytest.mark.filterwarnings("ignore:Pass the key color to Console.blit instead") @pytest.mark.filterwarnings("ignore:.*default values have been deprecated") def test_console_methods() -> None: console = tcod.console.Console(width=12, height=10) @@ -150,7 +150,7 @@ def test_rexpaint(tmp_path: Path) -> None: assert consoles[0].rgb.shape == loaded[0].rgb.shape assert consoles[1].rgb.shape == loaded[1].rgb.shape with pytest.raises(FileNotFoundError): - tcod.console.load_xp(tmp_path / "non_existant") + tcod.console.load_xp(tmp_path / "non_existent") def test_draw_frame() -> None: diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 7b0bfcc1..b82392a1 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -260,9 +260,9 @@ def test_console_fill_numpy(console: tcod.console.Console) -> None: for y in range(height): fill[y, :] = y % 256 - libtcodpy.console_fill_background(console, fill, fill, fill) # type: ignore - libtcodpy.console_fill_foreground(console, fill, fill, fill) # type: ignore - libtcodpy.console_fill_char(console, fill) # type: ignore + libtcodpy.console_fill_background(console, fill, fill, fill) + libtcodpy.console_fill_foreground(console, fill, fill, fill) + libtcodpy.console_fill_char(console, fill) # verify fill bg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) From 3d19e29d8b5a6925ba4b287c272635699b99dd42 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 13 Nov 2024 12:56:58 -0800 Subject: [PATCH 0930/1101] Remove getter deprecation warnings These are used internally, but only the setters need to be deprecated --- tcod/console.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 04845a94..0760ec7d 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -357,8 +357,7 @@ def rgb(self) -> NDArray[Any]: console.print(x, y, string, fg=None) """ - @property - @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) + @property # Getters used internally, so only deprecate the setters. def default_bg(self) -> tuple[int, int, int]: """Tuple[int, int, int]: The default background color. @@ -379,7 +378,6 @@ def default_bg(self, color: tuple[int, int, int]) -> None: self._console_data.back = color @property - @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_fg(self) -> tuple[int, int, int]: """Tuple[int, int, int]: The default foreground color. @@ -395,7 +393,6 @@ def default_fg(self, color: tuple[int, int, int]) -> None: self._console_data.fore = color @property - @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_bg_blend(self) -> int: """int: The default blending mode. @@ -410,7 +407,6 @@ def default_bg_blend(self, value: int) -> None: self._console_data.bkgnd_flag = value @property - @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) def default_alignment(self) -> int: """int: The default text alignment. From 10661eabe1809a38cb5f107c66840644486a483c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 24 Nov 2024 19:34:28 -0800 Subject: [PATCH 0931/1101] Pre-commit update Apply to files, update scripts for new formatting and ignore generated and manually aligned code --- .pre-commit-config.yaml | 2 +- build_libtcod.py | 7 ++++--- pyproject.toml | 26 ++++++++++++-------------- tcod/__init__.py | 10 +++++----- tcod/cffi.py | 2 +- tcod/constants.py | 2 +- tcod/context.py | 26 +++++++++++++------------- tcod/event.py | 2 +- tcod/event_constants.py | 2 +- tcod/libtcodpy.py | 2 +- tcod/sdl/video.py | 4 ++-- tcod/tcod.py | 2 +- 12 files changed, 43 insertions(+), 44 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19933511..d3d62742 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.0 + rev: v0.8.0 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] diff --git a/build_libtcod.py b/build_libtcod.py index 2ffe6aad..1e824088 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -233,6 +233,7 @@ def walk_sources(directory: str) -> Iterator[str]: This module is auto-generated by `build_libtcod.py`. """ + from tcod.color import Color ''' @@ -366,7 +367,7 @@ def write_library_constants() -> None: f.write(f"{name[5:]} = {color!r}\n") all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) - f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") + f.write(f"\n__all__ = [ # noqa: RUF022\n {all_names_merged},\n]\n") update_module_all(Path("tcod/libtcodpy.py"), all_names_merged) with Path("tcod/event_constants.py").open("w", encoding="utf-8") as f: @@ -379,12 +380,12 @@ def write_library_constants() -> None: f.write(f"""{parse_sdl_attrs("SDLK", None)[0]}\n""") f.write("\n# --- SDL keyboard modifiers ---\n") - f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("KMOD", all_names))) + f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("KMOD", None))) f.write("\n# --- SDL wheel ---\n") f.write("{}\n_REVERSE_WHEEL_TABLE = {}\n".format(*parse_sdl_attrs("SDL_MOUSEWHEEL", all_names))) all_names_merged = ",\n ".join(f'"{name}"' for name in all_names) - f.write(f"\n__all__ = [\n {all_names_merged},\n]\n") + f.write(f"\n__all__ = [ # noqa: RUF022\n {all_names_merged},\n]\n") event_py = Path("tcod/event.py").read_text(encoding="utf-8") diff --git a/pyproject.toml b/pyproject.toml index 6c68edb6..78e88f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,20 +178,18 @@ select = [ "D", # pydocstyle ] ignore = [ - "E501", # line-too-long - "S101", # assert - "S301", # suspicious-pickle-usage - "S311", # suspicious-non-cryptographic-random-usage - "ANN101", # missing-type-self - "ANN102", # missing-type-cls - "D203", # one-blank-line-before-class - "D204", # one-blank-line-after-class - "D213", # multi-line-summary-second-line - "D407", # dashed-underline-after-section - "D408", # section-underline-after-name - "D409", # section-underline-matches-section-length - "D206", # indent-with-spaces - "W191", # tab-indentation + "E501", # line-too-long + "S101", # assert + "S301", # suspicious-pickle-usage + "S311", # suspicious-non-cryptographic-random-usage + "D203", # one-blank-line-before-class + "D204", # one-blank-line-after-class + "D213", # multi-line-summary-second-line + "D407", # dashed-underline-after-section + "D408", # section-underline-after-name + "D409", # section-underline-matches-section-length + "D206", # indent-with-spaces + "W191", # tab-indentation ] [tool.ruff.lint.pydocstyle] # https://docs.astral.sh/ruff/settings/#lintpydocstyle diff --git a/tcod/__init__.py b/tcod/__init__.py index 63335ebc..b155be01 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -19,22 +19,22 @@ from tcod.version import __version__ __all__ = [ - "__version__", + "Console", "__sdl_version__", - "lib", - "ffi", + "__version__", "bsp", "color", "console", "context", "event", - "tileset", + "ffi", "image", + "lib", "los", "map", "noise", "path", "random", "tileset", - "Console", + "tileset", ] diff --git a/tcod/cffi.py b/tcod/cffi.py index 2325519d..eb94d5e1 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -76,4 +76,4 @@ def _libtcod_log_watcher(message: Any, userdata: None) -> None: # noqa: ANN401 lib.TCOD_set_log_callback(lib._libtcod_log_watcher, ffi.NULL) lib.TCOD_set_log_level(0) -__all__ = ["ffi", "lib", "__sdl_version__"] +__all__ = ["__sdl_version__", "ffi", "lib"] diff --git a/tcod/constants.py b/tcod/constants.py index 98f1ef1f..ebaa4f4b 100644 --- a/tcod/constants.py +++ b/tcod/constants.py @@ -503,7 +503,7 @@ white = Color(255, 255, 255) yellow = Color(255, 255, 0) -__all__ = [ +__all__ = [ # noqa: RUF022 "FOV_BASIC", "FOV_DIAMOND", "FOV_PERMISSIVE_0", diff --git a/tcod/context.py b/tcod/context.py index 1a2ea21c..78d18de2 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -44,24 +44,24 @@ from tcod.cffi import ffi, lib __all__ = ( - "Context", - "new", - "new_window", - "new_terminal", - "SDL_WINDOW_FULLSCREEN", - "SDL_WINDOW_FULLSCREEN_DESKTOP", - "SDL_WINDOW_HIDDEN", - "SDL_WINDOW_BORDERLESS", - "SDL_WINDOW_RESIZABLE", - "SDL_WINDOW_MINIMIZED", - "SDL_WINDOW_MAXIMIZED", - "SDL_WINDOW_INPUT_GRABBED", - "SDL_WINDOW_ALLOW_HIGHDPI", "RENDERER_OPENGL", "RENDERER_OPENGL2", "RENDERER_SDL", "RENDERER_SDL2", "RENDERER_XTERM", + "SDL_WINDOW_ALLOW_HIGHDPI", + "SDL_WINDOW_BORDERLESS", + "SDL_WINDOW_FULLSCREEN", + "SDL_WINDOW_FULLSCREEN_DESKTOP", + "SDL_WINDOW_HIDDEN", + "SDL_WINDOW_INPUT_GRABBED", + "SDL_WINDOW_MAXIMIZED", + "SDL_WINDOW_MINIMIZED", + "SDL_WINDOW_RESIZABLE", + "Context", + "new", + "new_terminal", + "new_window", ) _Event = TypeVar("_Event", bound=tcod.event.Event) diff --git a/tcod/event.py b/tcod/event.py index 48cf9c37..2f77c679 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -2818,7 +2818,7 @@ def __getattr__(name: str) -> int: return value -__all__ = [ # noqa: F405 +__all__ = [ # noqa: F405 RUF022 "Modifier", "Point", "BUTTON_LEFT", diff --git a/tcod/event_constants.py b/tcod/event_constants.py index 0f55d545..6f012293 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -539,7 +539,7 @@ 1027: "MOUSEWHEEL", } -__all__ = [ +__all__ = [ # noqa: RUF022 "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", "MOUSEWHEEL", diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index aa9cc51b..578d2610 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -4281,7 +4281,7 @@ def __getattr__(name: str) -> Color: raise AttributeError(msg) from None -__all__ = [ # noqa: F405 +__all__ = [ # noqa: F405 RUF022 "Color", "Bsp", "NB_FOV_ALGORITHMS", diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 53a8e2db..f6dc922f 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -20,11 +20,11 @@ from tcod.sdl._internal import _check, _check_p, _required_version, _version_at_least __all__ = ( - "WindowFlags", "FlashOperation", "Window", - "new_window", + "WindowFlags", "get_grabbed_window", + "new_window", "screen_saver_allowed", ) diff --git a/tcod/tcod.py b/tcod/tcod.py index 8181da79..cf12dceb 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -79,7 +79,6 @@ def __getattr__(name: str, stacklevel: int = 1) -> Any: # noqa: ANN401 "console", "context", "event", - "tileset", "image", "los", "map", @@ -87,4 +86,5 @@ def __getattr__(name: str, stacklevel: int = 1) -> Any: # noqa: ANN401 "path", "random", "tileset", + "tileset", ] From e66b22124ff086633c98e68b29091a1171e24483 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 24 Nov 2024 20:11:18 -0800 Subject: [PATCH 0932/1101] Update PyInstaller example and docs for loading tilesets --- examples/distribution/PyInstaller/main.py | 5 ++--- .../distribution/PyInstaller/requirements.txt | 6 +++--- tcod/tileset.py | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index b2767fed..5e034afd 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -5,7 +5,6 @@ # https://creativecommons.org/publicdomain/zero/1.0/ """PyInstaller main script example.""" -import sys from pathlib import Path import tcod.console @@ -15,8 +14,8 @@ WIDTH, HEIGHT = 80, 60 -# The base directory, this is sys._MEIPASS when in one-file mode. -BASE_DIR = Path(getattr(sys, "_MEIPASS", ".")) +BASE_DIR = Path(__file__).parent +"""The directory of this script.""" FONT_PATH = BASE_DIR / "data/terminal8x8_gs_ro.png" diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt index 390bb9dd..477fbbdc 100644 --- a/examples/distribution/PyInstaller/requirements.txt +++ b/examples/distribution/PyInstaller/requirements.txt @@ -1,3 +1,3 @@ -tcod==12.2.0 -pyinstaller==5.13.1 -pypiwin32; sys_platform=="win32" +tcod==16.2.3 +pyinstaller==6.9.0 +pypiwin32; sys_platform=="win32" diff --git a/tcod/tileset.py b/tcod/tileset.py index dad40f18..f57304da 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -1,5 +1,7 @@ """Tileset and font related functions. +If you want to load a tileset from a common tileset image then you only need :any:`tcod.tileset.load_tilesheet`. + Tilesets can be loaded as a whole from tile-sheets or True-Type fonts, or they can be put together from multiple tile images by loading them separately using :any:`Tileset.set_tile`. @@ -342,6 +344,24 @@ def load_tilesheet(path: str | PathLike[str], columns: int, rows: int, charmap: If `None` is used then no tiles will be mapped, you will need to use :any:`Tileset.remap` to assign codepoints to this Tileset. + Image alpha and key colors are handled automatically. + For example any tileset from the `Dwarf Fortress tileset repository `_ + will load correctly with the following example: + + Example:: + + from pathlib import Path + + import tcod.tileset + + THIS_DIR = Path(__file__, "..") # Directory of this script file + FONT = THIS_DIR / "assets/tileset.png" # Replace with any tileset from the DF tileset repository + + # Will raise FileNotFoundError if the font is missing! + tileset = tcod.tileset.load_tilesheet(FONT, 16, 16, tcod.tileset.CHARMAP_CP437) + + The tileset return value is usually passed to the `tileset` parameter of :any:`tcod.context.new`. + .. versionadded:: 11.12 """ path = Path(path).resolve(strict=True) From 1ba890c2aeae7e28d7a7de5b5a3764301aca9030 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 24 Nov 2024 20:15:10 -0800 Subject: [PATCH 0933/1101] Normalize newlines in requirements.txt Noticed unusual newlines in a previous commit, this was the only other file with incorrect newlines. --- requirements.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index 03ad70a0..d0c9c27e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -attrs>=23.1.0 -cffi>=1.15 -numpy>=1.21.4 -pycparser>=2.14 -requests>=2.28.1 -setuptools==70.0.0 -types-cffi -types-requests -types-Pillow -types-setuptools -types-tabulate -typing_extensions -pcpp==1.30 +attrs>=23.1.0 +cffi>=1.15 +numpy>=1.21.4 +pycparser>=2.14 +requests>=2.28.1 +setuptools==70.0.0 +types-cffi +types-requests +types-Pillow +types-setuptools +types-tabulate +typing_extensions +pcpp==1.30 From 2b3d6fdc3c6d343e6859d4d90455a6770c9b3146 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 27 Mar 2025 23:53:38 -0700 Subject: [PATCH 0934/1101] Rewrite SDL renderer primitive drawing methods and add test coverage Fixes #159 --- CHANGELOG.md | 1 + tcod/sdl/render.py | 62 ++++++++++++++++++++++------------------------ tests/test_sdl.py | 17 +++++++++++++ 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b87ebb8..1e94088c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - Suppressed internal `mouse.tile_motion` deprecation warning. +- Fixed SDL renderer primitive drawing methods. #159 ## [16.2.3] - 2024-07-16 diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 1def4c72..5dcf65f4 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -581,62 +581,63 @@ def draw_point(self, xy: tuple[float, float]) -> None: .. versionadded:: 13.5 """ - _check(lib.SDL_RenderDrawPointF(self.p, (xy,))) + x, y = xy + _check(lib.SDL_RenderDrawPointF(self.p, x, y)) def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> None: """Draw a single line. .. versionadded:: 13.5 """ - _check(lib.SDL_RenderDrawLineF(self.p, *start, *end)) - - def fill_rects(self, rects: NDArray[np.intc | np.float32]) -> None: + x1, y1 = start + x2, y2 = end + _check(lib.SDL_RenderDrawLineF(self.p, x1, y1, x2, y2)) + + @staticmethod + def _convert_array(array: NDArray[np.number]) -> NDArray[np.intc] | NDArray[np.float32]: + """Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32.""" + if array.dtype in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16): + return np.ascontiguousarray(array, np.intc) + return np.ascontiguousarray(array, np.float32) + + def fill_rects(self, rects: NDArray[np.number]) -> None: """Fill multiple rectangles from an array. .. versionadded:: 13.5 """ assert len(rects.shape) == 2 # noqa: PLR2004 assert rects.shape[1] == 4 # noqa: PLR2004 - rects = np.ascontiguousarray(rects) + rects = self._convert_array(rects) if rects.dtype == np.intc: _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) - elif rects.dtype == np.float32: - _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - else: - msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}." - raise TypeError(msg) + return + _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - def draw_rects(self, rects: NDArray[np.intc | np.float32]) -> None: + def draw_rects(self, rects: NDArray[np.number]) -> None: """Draw multiple outlined rectangles from an array. .. versionadded:: 13.5 """ assert len(rects.shape) == 2 # noqa: PLR2004 assert rects.shape[1] == 4 # noqa: PLR2004 - rects = np.ascontiguousarray(rects) + rects = self._convert_array(rects) if rects.dtype == np.intc: _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) - elif rects.dtype == np.float32: - _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - else: - msg = f"Array must be an np.intc or np.float32 type, got {rects.dtype}." - raise TypeError(msg) + return + _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - def draw_points(self, points: NDArray[np.intc | np.float32]) -> None: + def draw_points(self, points: NDArray[np.number]) -> None: """Draw an array of points. .. versionadded:: 13.5 """ assert len(points.shape) == 2 # noqa: PLR2004 assert points.shape[1] == 2 # noqa: PLR2004 - points = np.ascontiguousarray(points) + points = self._convert_array(points) if points.dtype == np.intc: - _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) - elif points.dtype == np.float32: - _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) - else: - msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}." - raise TypeError(msg) + _check(lib.SDL_RenderDrawPoints(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) + return + _check(lib.SDL_RenderDrawPointsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: """Draw a connected series of lines from an array. @@ -645,14 +646,11 @@ def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: """ assert len(points.shape) == 2 # noqa: PLR2004 assert points.shape[1] == 2 # noqa: PLR2004 - points = np.ascontiguousarray(points) + points = self._convert_array(points) if points.dtype == np.intc: - _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) - elif points.dtype == np.float32: - _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1)) - else: - msg = f"Array must be an np.intc or np.float32 type, got {points.dtype}." - raise TypeError(msg) + _check(lib.SDL_RenderDrawLines(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) + return + _check(lib.SDL_RenderDrawLinesF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1)) @_required_version((2, 0, 18)) def geometry( diff --git a/tests/test_sdl.py b/tests/test_sdl.py index f22b7a72..d41c4152 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -76,6 +76,23 @@ def test_sdl_render(uses_window: None) -> None: assert (render.read_pixels(format="RGB") == (0, 0, 0)).all() assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4) + render.clear() + + render.draw_point((0, 0)) + render.draw_points(np.ones((3, 2), dtype=np.float32)) + render.draw_points(np.ones((3, 2), dtype=np.intc)) + render.draw_points(np.ones((3, 2), dtype=np.float16)) + render.draw_points(np.ones((3, 2), dtype=np.int8)) + render.draw_line((0, 0), (1, 1)) + render.draw_lines(np.ones((3, 2), dtype=np.float32)) + render.draw_lines(np.ones((3, 2), dtype=np.intc)) + render.draw_rect((0, 0, 1, 1)) + render.draw_rects(np.ones((3, 4), dtype=np.float32)) + render.draw_rects(np.ones((3, 4), dtype=np.intc)) + render.fill_rect((0, 0, 1, 1)) + render.fill_rects(np.ones((3, 4), dtype=np.float32)) + render.fill_rects(np.ones((3, 4), dtype=np.intc)) + def test_sdl_render_bad_types() -> None: with pytest.raises(TypeError): From aa7a0e7e913c5e5a6c61a5e0381e6c26ceb33a90 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 00:04:06 -0700 Subject: [PATCH 0935/1101] Update pre-commit, fix Ruff and Mypy errors --- .pre-commit-config.yaml | 2 +- examples/samples_libtcodpy.py | 7 ++----- examples/samples_tcod.py | 4 ++-- examples/thread_jobs.py | 9 +++------ tcod/console.py | 8 ++++---- tcod/event.py | 10 ++++------ tcod/libtcodpy.py | 8 ++++---- tcod/noise.py | 2 +- tcod/path.py | 2 +- tests/test_console.py | 2 +- 10 files changed, 23 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3d62742..8960f446 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.11.2 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index ab68eb91..9e0cb41d 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -220,7 +220,7 @@ def render_colors(first, key, mouse): SAMPLE_SCREEN_HEIGHT - 1, libtcod.BKGND_MULTIPLY, libtcod.CENTER, - "The Doryen library uses 24 bits " "colors, for both background and " "foreground.", + "The Doryen library uses 24 bits colors, for both background and foreground.", ) if key.c == ord("f"): @@ -270,10 +270,7 @@ def render_offscreen(first, key, mouse): SAMPLE_SCREEN_HEIGHT // 2, libtcod.BKGND_NONE, libtcod.CENTER, - b"You can render to an offscreen " - b"console and blit in on another " - b"one, simulating alpha " - b"transparency.", + b"You can render to an offscreen console and blit in on another one, simulating alpha transparency.", ) if first: libtcod.sys_set_fps(30) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 942c1d69..5f8ea1be 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -163,7 +163,7 @@ def print_banner(self) -> None: y=5, width=sample_console.width - 2, height=sample_console.height - 1, - string="The Doryen library uses 24 bits colors, for both " "background and foreground.", + string="The Doryen library uses 24 bits colors, for both background and foreground.", fg=WHITE, bg=GREY, bg_blend=libtcodpy.BKGND_MULTIPLY, @@ -198,7 +198,7 @@ def __init__(self) -> None: 2, sample_console.width // 2 - 2, sample_console.height // 2, - "You can render to an offscreen console and blit in on another " "one, simulating alpha transparency.", + "You can render to an offscreen console and blit in on another one, simulating alpha transparency.", fg=WHITE, bg=None, alignment=libtcodpy.CENTER, diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index 0c616bba..fa8ce9dc 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -77,7 +77,7 @@ def run_test( for i in range(1, THREADS + 1): executor = concurrent.futures.ThreadPoolExecutor(i) multi_time = min(timeit.repeat(lambda: multi_func(executor, maps), number=1, repeat=REPEAT)) - print(f"{i} threads: {multi_time * 1000:.2f}ms, " f"{single_time / (multi_time * i) * 100:.2f}% efficiency") + print(f"{i} threads: {multi_time * 1000:.2f}ms, {single_time / (multi_time * i) * 100:.2f}% efficiency") def main() -> None: @@ -89,13 +89,10 @@ def main() -> None: print(f"Python {sys.version}\n{platform.platform()}\n{platform.processor()}") - print(f"\nComputing field-of-view for " f"{len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps.") + print(f"\nComputing field-of-view for {len(maps)} empty {MAP_WIDTH}x{MAP_HEIGHT} maps.") run_test(maps, test_fov_single, test_fov_threads) - print( - f"\nComputing AStar from corner to corner {len(maps)} times " - f"on separate empty {MAP_WIDTH}x{MAP_HEIGHT} maps." - ) + print(f"\nComputing AStar from corner to corner {len(maps)} times on separate empty {MAP_WIDTH}x{MAP_HEIGHT} maps.") run_test(maps, test_astar_single, test_astar_threads) diff --git a/tcod/console.py b/tcod/console.py index 0760ec7d..80144ab4 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -29,7 +29,7 @@ def _fmt(string: str) -> bytes: _root_console = None -rgba_graphic = np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")]) +rgba_graphic: np.dtype[Any] = np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")]) """A NumPy :any:`dtype` compatible with :any:`Console.rgba`. This dtype is: ``np.dtype([("ch", np.intc), ("fg", "4B"), ("bg", "4B")])`` @@ -37,7 +37,7 @@ def _fmt(string: str) -> bytes: .. versionadded:: 12.3 """ -rgb_graphic = np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")]) +rgb_graphic: np.dtype[Any] = np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")]) """A NumPy :any:`dtype` compatible with :any:`Console.rgb`. This dtype is: ``np.dtype([("ch", np.intc), ("fg", "3B"), ("bg", "3B")])`` @@ -104,7 +104,7 @@ class Console: DTYPE = rgba_graphic # A structured array type with the added "fg_rgb" and "bg_rgb" fields. - _DTYPE_RGB = np.dtype( + _DTYPE_RGB: np.dtype[Any] = np.dtype( { "names": ["ch", "fg", "bg"], "formats": [np.int32, "3u1", "3u1"], @@ -955,7 +955,7 @@ def __setstate__(self, state: dict[str, Any]) -> None: def __repr__(self) -> str: """Return a string representation of this console.""" - return "tcod.console.Console(width=%i, height=%i, " "order=%r,buffer=\n%r)" % ( + return "tcod.console.Console(width=%i, height=%i, order=%r,buffer=\n%r)" % ( self.width, self.height, self._order, diff --git a/tcod/event.py b/tcod/event.py index 2f77c679..d225ae37 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -831,7 +831,7 @@ def joystick(self) -> tcod.sdl.joystick.Joystick: return tcod.sdl.joystick.Joystick._from_instance_id(self.which) def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})" + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which})" def __str__(self) -> str: prefix = super().__str__().strip("<>") @@ -934,9 +934,7 @@ def from_sdl_event(cls, sdl_event: Any) -> JoystickHat: return cls("JOYHATMOTION", sdl_event.jhat.which, *_HAT_DIRECTIONS[sdl_event.jhat.hat]) def __repr__(self) -> str: - return ( - f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which}, x={self.x}, y={self.y})" - ) + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which}, x={self.x}, y={self.y})" def __str__(self) -> str: prefix = super().__str__().strip("<>") @@ -977,7 +975,7 @@ def from_sdl_event(cls, sdl_event: Any) -> JoystickButton: return cls(type, sdl_event.jbutton.which, sdl_event.jbutton.button) def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which}, button={self.button})" + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which}, button={self.button})" def __str__(self) -> str: prefix = super().__str__().strip("<>") @@ -1032,7 +1030,7 @@ def controller(self) -> tcod.sdl.joystick.GameController: return tcod.sdl.joystick.GameController._from_instance_id(self.which) def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}" f"(type={self.type!r}, which={self.which})" + return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which})" def __str__(self) -> str: prefix = super().__str__().strip("<>") diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 578d2610..568f04d3 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -697,7 +697,7 @@ def bsp_traverse_level_order( _bsp_traverse(node.level_order(), callback, userData) -@deprecate("Iterate over nodes using " "'for n in node.inverted_level_order():' instead.") +@deprecate("Iterate over nodes using 'for n in node.inverted_level_order():' instead.") def bsp_traverse_inverted_level_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], @@ -1863,7 +1863,7 @@ def console_delete(con: tcod.console.Console) -> None: ) else: warnings.warn( - "You no longer need to make this call, " "Console's are deleted when they go out of scope.", + "You no longer need to make this call, Console's are deleted when they go out of scope.", DeprecationWarning, stacklevel=2, ) @@ -2398,7 +2398,7 @@ def heightmap_set_value(hm: NDArray[np.float32], x: int, y: int, value: float) - """ if hm.flags["C_CONTIGUOUS"]: warnings.warn( - "Assign to this heightmap with hm[i,j] = value\n" "consider using order='F'", + "Assign to this heightmap with hm[i,j] = value\nconsider using order='F'", DeprecationWarning, stacklevel=2, ) @@ -2847,7 +2847,7 @@ def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: """ if hm.flags["C_CONTIGUOUS"]: warnings.warn( - "Get a value from this heightmap with hm[i,j]\n" "consider using order='F'", + "Get a value from this heightmap with hm[i,j]\nconsider using order='F'", DeprecationWarning, stacklevel=2, ) diff --git a/tcod/noise.py b/tcod/noise.py index 108fa2c0..b91c90b6 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -91,7 +91,7 @@ def __repr__(self) -> str: def __getattr__(name: str) -> Implementation: if name in Implementation.__members__: warnings.warn( - f"'tcod.noise.{name}' is deprecated," f" use 'tcod.noise.Implementation.{name}' instead.", + f"'tcod.noise.{name}' is deprecated, use 'tcod.noise.Implementation.{name}' instead.", FutureWarning, stacklevel=2, ) diff --git a/tcod/path.py b/tcod/path.py index ffbc1dab..e55223d7 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -171,7 +171,7 @@ def __init__(self, cost: Any, diagonal: float = 1.41) -> None: if not hasattr(self.cost, "get_tcod_path_ffi"): assert not callable(self.cost), ( - "Any callback alone is missing shape information. " "Wrap your callback in tcod.path.EdgeCostCallback" + "Any callback alone is missing shape information. Wrap your callback in tcod.path.EdgeCostCallback" ) self.cost = NodeCostArray(self.cost) diff --git a/tests/test_console.py b/tests/test_console.py index b0be600f..0681368f 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -112,7 +112,7 @@ def test_console_str() -> None: console.ch[:] = ord(".") with pytest.warns(): console.print_(0, 0, "Test") - assert str(console) == ("") + assert str(console) == ("") def test_console_fortran_buffer() -> None: From bbf8b62045a3dcee4ad04bec184d49e7d9f3535c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 00:20:59 -0700 Subject: [PATCH 0936/1101] Catch sneaky whitespace when culling va_list functions --- .github/workflows/python-package.yml | 2 +- build_sdl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0417777c..5c61ff6e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -71,7 +71,7 @@ jobs: strategy: matrix: os: ["windows-latest", "macos-latest"] - sdl-version: ["2.0.14", "2.0.16"] + sdl-version: ["2.0.14", "2.0.16", "2.30.0"] fail-fast: true steps: - uses: actions/checkout@v4 diff --git a/build_sdl.py b/build_sdl.py index 629c98f4..99c08d25 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -33,7 +33,7 @@ # Used to remove excessive newlines in debug outputs. RE_NEWLINES = re.compile(r"\n\n+") # Functions using va_list need to be culled. -RE_VAFUNC = re.compile(r"^.*?\([^()]*va_list[^()]*\);$", re.MULTILINE) +RE_VAFUNC = re.compile(r"^.*?\([^()]*va_list[^()]*\)\s*;\s*$", re.MULTILINE) # Static inline functions need to be culled. RE_INLINE = re.compile(r"^static inline.*?^}$", re.MULTILINE | re.DOTALL) # Most SDL_PIXELFORMAT names need their values scrubbed. From 87dc70067a2daba5e1539b9a686fb2de898db290 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 00:26:48 -0700 Subject: [PATCH 0937/1101] Add more ignored defines --- build_sdl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_sdl.py b/build_sdl.py index 99c08d25..41f91b22 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -60,6 +60,9 @@ "SDL_INLINE", "SDL_FORCE_INLINE", "SDL_FALLTHROUGH", + "SDL_HAS_FALLTHROUGH", + "SDL_NO_THREAD_SAFETY_ANALYSIS", + "SDL_SCOPED_CAPABILITY", # Might show up in parsing and not in source. "SDL_ANDROID_EXTERNAL_STORAGE_READ", "SDL_ANDROID_EXTERNAL_STORAGE_WRITE", From fabb04676cd222eb2d96993d6ff685333c831d22 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 00:48:36 -0700 Subject: [PATCH 0938/1101] Fix SDL renderer test Clear first to ensure the renderer is blank for reading --- tests/test_sdl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sdl.py b/tests/test_sdl.py index d41c4152..5dc2bf40 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -58,7 +58,9 @@ def test_sdl_screen_saver(uses_window: None) -> None: def test_sdl_render(uses_window: None) -> None: window = tcod.sdl.video.new_window(1, 1) render = tcod.sdl.render.new_renderer(window, software=True, vsync=False, target_textures=True) + render.clear() render.present() + render.clear() rgb = render.upload_texture(np.zeros((8, 8, 3), np.uint8)) assert (rgb.width, rgb.height) == (8, 8) assert rgb.access == tcod.sdl.render.TextureAccess.STATIC @@ -76,8 +78,6 @@ def test_sdl_render(uses_window: None) -> None: assert (render.read_pixels(format="RGB") == (0, 0, 0)).all() assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4) - render.clear() - render.draw_point((0, 0)) render.draw_points(np.ones((3, 2), dtype=np.float32)) render.draw_points(np.ones((3, 2), dtype=np.intc)) From 048d1805a851046fb88bcffcdc66c3bb1a2813ff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 01:11:13 -0700 Subject: [PATCH 0939/1101] Prepare 17.0.0 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e94088c..fe453402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [17.0.0] - 2025-03-28 + ### Changed - `EventDispatch`'s on event methods are now defined as positional parameters, so renaming the `event` parameter is now valid in subclasses. From ea7c3b0fa43c22b1d83fa3dd4b0951372c0cafaa Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 16:49:56 -0700 Subject: [PATCH 0940/1101] Refactor type support for SDL primitive drawing Now raises TypeError for incorrect shapes --- CHANGELOG.md | 8 +++++++ tcod/sdl/render.py | 59 ++++++++++++++++++++++++++++++---------------- tests/test_sdl.py | 26 ++++++++++++-------- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe453402..7ad01870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- SDL renderer primitive drawing methods now support sequences of tuples. + +### Fixed + +- `tcod.sdl.Renderer.draw_lines` type hint was too narrow. + ## [17.0.0] - 2025-03-28 ### Changed diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 5dcf65f4..ed93fb6b 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -6,7 +6,7 @@ from __future__ import annotations import enum -from typing import Any, Final +from typing import Any, Final, Sequence import numpy as np from numpy.typing import NDArray @@ -594,59 +594,78 @@ def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> Non _check(lib.SDL_RenderDrawLineF(self.p, x1, y1, x2, y2)) @staticmethod - def _convert_array(array: NDArray[np.number]) -> NDArray[np.intc] | NDArray[np.float32]: - """Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32.""" - if array.dtype in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16): - return np.ascontiguousarray(array, np.intc) - return np.ascontiguousarray(array, np.float32) + def _convert_array( + array: NDArray[np.number] | Sequence[Sequence[float]], item_length: int + ) -> NDArray[np.intc] | NDArray[np.float32]: + """Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32. - def fill_rects(self, rects: NDArray[np.number]) -> None: + Array shape is enforced to be (n, item_length) + """ + if getattr(array, "dtype", None) in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16): + out = np.ascontiguousarray(array, np.intc) + else: + out = np.ascontiguousarray(array, np.float32) + if len(out.shape) != 2: # noqa: PLR2004 + msg = f"Array must have 2 axes, but shape is {out.shape!r}" + raise TypeError(msg) + if out.shape[1] != item_length: + msg = f"Array shape[1] must be {item_length}, but shape is {out.shape!r}" + raise TypeError(msg) + return out + + def fill_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None: """Fill multiple rectangles from an array. + Args: + rects: A sequence or array of (x, y, width, height) rectangles. + .. versionadded:: 13.5 """ - assert len(rects.shape) == 2 # noqa: PLR2004 - assert rects.shape[1] == 4 # noqa: PLR2004 - rects = self._convert_array(rects) + rects = self._convert_array(rects, item_length=4) if rects.dtype == np.intc: _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) return _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - def draw_rects(self, rects: NDArray[np.number]) -> None: + def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None: """Draw multiple outlined rectangles from an array. + Args: + rects: A sequence or array of (x, y, width, height) rectangles. + .. versionadded:: 13.5 """ + rects = self._convert_array(rects, item_length=4) assert len(rects.shape) == 2 # noqa: PLR2004 assert rects.shape[1] == 4 # noqa: PLR2004 - rects = self._convert_array(rects) if rects.dtype == np.intc: _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) return _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) - def draw_points(self, points: NDArray[np.number]) -> None: + def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw an array of points. + Args: + points: A sequence or array of (x, y) points. + .. versionadded:: 13.5 """ - assert len(points.shape) == 2 # noqa: PLR2004 - assert points.shape[1] == 2 # noqa: PLR2004 - points = self._convert_array(points) + points = self._convert_array(points, item_length=2) if points.dtype == np.intc: _check(lib.SDL_RenderDrawPoints(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) return _check(lib.SDL_RenderDrawPointsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) - def draw_lines(self, points: NDArray[np.intc | np.float32]) -> None: + def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw a connected series of lines from an array. + Args: + points: A sequence or array of (x, y) points. + .. versionadded:: 13.5 """ - assert len(points.shape) == 2 # noqa: PLR2004 - assert points.shape[1] == 2 # noqa: PLR2004 - points = self._convert_array(points) + points = self._convert_array(points, item_length=2) if points.dtype == np.intc: _check(lib.SDL_RenderDrawLines(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) return diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 5dc2bf40..f67049a6 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -79,19 +79,25 @@ def test_sdl_render(uses_window: None) -> None: assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4) render.draw_point((0, 0)) - render.draw_points(np.ones((3, 2), dtype=np.float32)) - render.draw_points(np.ones((3, 2), dtype=np.intc)) - render.draw_points(np.ones((3, 2), dtype=np.float16)) - render.draw_points(np.ones((3, 2), dtype=np.int8)) render.draw_line((0, 0), (1, 1)) - render.draw_lines(np.ones((3, 2), dtype=np.float32)) - render.draw_lines(np.ones((3, 2), dtype=np.intc)) render.draw_rect((0, 0, 1, 1)) - render.draw_rects(np.ones((3, 4), dtype=np.float32)) - render.draw_rects(np.ones((3, 4), dtype=np.intc)) render.fill_rect((0, 0, 1, 1)) - render.fill_rects(np.ones((3, 4), dtype=np.float32)) - render.fill_rects(np.ones((3, 4), dtype=np.intc)) + + render.draw_points([(0, 0)]) + render.draw_lines([(0, 0), (1, 1)]) + render.draw_rects([(0, 0, 1, 1)]) + render.fill_rects([(0, 0, 1, 1)]) + + for dtype in (np.intc, np.int8, np.uint8, np.float32, np.float16): + render.draw_points(np.ones((3, 2), dtype=dtype)) + render.draw_lines(np.ones((3, 2), dtype=dtype)) + render.draw_rects(np.ones((3, 4), dtype=dtype)) + render.fill_rects(np.ones((3, 4), dtype=dtype)) + + with pytest.raises(TypeError, match=r"shape\[1\] must be 2"): + render.draw_points(np.ones((3, 1), dtype=dtype)) + with pytest.raises(TypeError, match=r"must have 2 axes"): + render.draw_points(np.ones((3,), dtype=dtype)) def test_sdl_render_bad_types() -> None: From 9c7bfc0848030173075c28226fcec34349208cff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 16:59:58 -0700 Subject: [PATCH 0941/1101] Test and fix Renderer.geometry method --- CHANGELOG.md | 1 + tcod/sdl/render.py | 24 ++++++++++++++---------- tests/test_sdl.py | 8 ++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad01870..2d169a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - `tcod.sdl.Renderer.draw_lines` type hint was too narrow. +- Fixed crash in `tcod.sdl.Renderer.geometry`. ## [17.0.0] - 2025-03-28 diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index ed93fb6b..ae2b542d 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -675,29 +675,33 @@ def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) def geometry( self, texture: Texture | None, - xy: NDArray[np.float32], - color: NDArray[np.uint8], - uv: NDArray[np.float32], + xy: NDArray[np.float32] | Sequence[tuple[float, float]], + color: NDArray[np.uint8] | Sequence[tuple[int, int, int, int]], + uv: NDArray[np.float32] | Sequence[tuple[float, float]], indices: NDArray[np.uint8 | np.uint16 | np.uint32] | None = None, ) -> None: """Render triangles from texture and vertex data. + Args: + texture: The SDL texture to render from. + xy: A sequence of (x, y) points to buffer. + color: A sequence of (r, g, b, a) colors to buffer. + uv: A sequence of (x, y) coordinates to buffer. + indices: A sequence of indexes referring to the buffered data, every 3 indexes is a triangle to render. + .. versionadded:: 13.5 """ - assert xy.dtype == np.float32 + xy = np.ascontiguousarray(xy, np.float32) assert len(xy.shape) == 2 # noqa: PLR2004 assert xy.shape[1] == 2 # noqa: PLR2004 - assert xy[0].flags.c_contiguous - assert color.dtype == np.uint8 + color = np.ascontiguousarray(color, np.uint8) assert len(color.shape) == 2 # noqa: PLR2004 assert color.shape[1] == 4 # noqa: PLR2004 - assert color[0].flags.c_contiguous - assert uv.dtype == np.float32 + uv = np.ascontiguousarray(uv, np.float32) assert len(uv.shape) == 2 # noqa: PLR2004 assert uv.shape[1] == 2 # noqa: PLR2004 - assert uv[0].flags.c_contiguous if indices is not None: assert indices.dtype.type in (np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32) indices = np.ascontiguousarray(indices) @@ -709,7 +713,7 @@ def geometry( texture.p if texture else ffi.NULL, ffi.cast("float*", xy.ctypes.data), xy.strides[0], - ffi.cast("uint8_t*", color.ctypes.data), + ffi.cast("SDL_Color*", color.ctypes.data), color.strides[0], ffi.cast("float*", uv.ctypes.data), uv.strides[0], diff --git a/tests/test_sdl.py b/tests/test_sdl.py index f67049a6..e29554df 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -99,6 +99,14 @@ def test_sdl_render(uses_window: None) -> None: with pytest.raises(TypeError, match=r"must have 2 axes"): render.draw_points(np.ones((3,), dtype=dtype)) + render.geometry( + None, + np.zeros((1, 2), np.float32), + np.zeros((1, 4), np.uint8), + np.zeros((1, 2), np.float32), + np.zeros((3,), np.uint8), + ) + def test_sdl_render_bad_types() -> None: with pytest.raises(TypeError): From 250d0ad0d593e2df2ff87b2c3e7ef9a50e329088 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 28 Mar 2025 21:45:56 -0700 Subject: [PATCH 0942/1101] Prepare 17.1.0 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d169a30..c36415ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [17.1.0] - 2025-03-29 + ### Added - SDL renderer primitive drawing methods now support sequences of tuples. From 07ea8ac7d03ebf08704b5a89f602a618e4b69830 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Apr 2025 15:49:06 -0700 Subject: [PATCH 0943/1101] Decorate already deprecated functions Clean up deprecation warnings in tests --- pyproject.toml | 3 +-- tcod/console.py | 16 ++++++++++++---- tcod/libtcodpy.py | 13 +++++++------ tests/conftest.py | 25 ++++++++++++++----------- tests/test_console.py | 12 ++++++++---- tests/test_random.py | 8 ++++++-- tests/test_tcod.py | 19 ++++++++++--------- 7 files changed, 58 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 78e88f2d..2590d920 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,8 +99,7 @@ addopts = [ log_file_level = "DEBUG" faulthandler_timeout = 5 filterwarnings = [ - "ignore::DeprecationWarning:tcod.libtcodpy", - "ignore::PendingDeprecationWarning:tcod.libtcodpy", + "ignore:This function may be deprecated in the future:PendingDeprecationWarning", "ignore:This class may perform poorly and is no longer needed.::tcod.map", "ignore:'import tcod as libtcodpy' is preferred.", ] diff --git a/tcod/console.py b/tcod/console.py index 80144ab4..a1461965 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -645,6 +645,7 @@ def get_height_rect(self, x: int, y: int, width: int, height: int, string: str) string_ = string.encode("utf-8") return int(lib.TCOD_console_get_height_rect_n(self.console_c, x, y, width, height, len(string_), string_)) + @deprecated("""Replace with 'console.draw_rect(x, y, width, height, ch=..., fg=..., bg=..., bg_blend=...)'""") def rect( # noqa: PLR0913 self, x: int, @@ -677,6 +678,9 @@ def rect( # noqa: PLR0913 self.__deprecate_defaults("draw_rect", bg_blend, clear=bool(clear)) lib.TCOD_console_rect(self.console_c, x, y, width, height, clear, bg_blend) + @deprecated( + """Replace with 'console.draw_rect(x, y, width=width, height=1, ch=ord("─"), fg=..., bg=..., bg_blend=...)'""" + ) def hline( self, x: int, @@ -686,7 +690,7 @@ def hline( ) -> None: """Draw a horizontal line on the console. - This always uses ord('─'), the horizontal line character. + This always uses ord("─"), the horizontal line character. Args: x (int): The x coordinate from the left. @@ -704,6 +708,9 @@ def hline( self.__deprecate_defaults("draw_rect", bg_blend) lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend) + @deprecated( + """Replace with 'console.draw_rect(x, y, width=1, height=height, ch=ord("│"), fg=..., bg=..., bg_blend=...)'""" + ) def vline( self, x: int, @@ -713,7 +720,7 @@ def vline( ) -> None: """Draw a vertical line on the console. - This always uses ord('│'), the vertical line character. + This always uses ord("│"), the vertical line character. Args: x (int): The x coordinate from the left. @@ -1130,15 +1137,16 @@ def draw_frame( # noqa: PLR0913 Example:: + >>> from tcod import libtcodpy >>> console = tcod.console.Console(12, 6) >>> console.draw_frame(x=0, y=0, width=3, height=3) >>> console.draw_frame(x=3, y=0, width=3, height=3, decoration="╔═╗║ ║╚═╝") >>> console.draw_frame(x=6, y=0, width=3, height=3, decoration="123456789") >>> console.draw_frame(x=9, y=0, width=3, height=3, decoration="/-\\| |\\-/") >>> console.draw_frame(x=0, y=3, width=12, height=3) - >>> console.print_box(x=0, y=3, width=12, height=1, string=" Title ", alignment=tcod.CENTER) + >>> console.print_box(x=0, y=3, width=12, height=1, string=" Title ", alignment=libtcodpy.CENTER) 1 - >>> console.print_box(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=tcod.CENTER) + >>> console.print_box(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=libtcodpy.CENTER) 1 >>> print(console) <┌─┐╔═╗123/-\ diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 568f04d3..27c0032d 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -410,7 +410,7 @@ def __setattr__(self, attr: str, value: Any) -> None: def __repr__(self) -> str: """Return a representation of this Key object.""" params = [] - params.append(f"pressed={self.pressed!r}, vk=tcod.{_LOOKUP_VK[self.vk]}") + params.append(f"pressed={self.pressed!r}, vk=libtcodpy.{_LOOKUP_VK[self.vk]}") if self.c: params.append(f"c=ord({chr(self.c)!r})") if self.text: @@ -426,7 +426,7 @@ def __repr__(self) -> str: ]: if getattr(self, attr): params.append(f"{attr}={getattr(self, attr)!r}") - return "tcod.Key({})".format(", ".join(params)) + return "libtcodpy.Key({})".format(", ".join(params)) @property def key_p(self) -> Any: @@ -504,7 +504,7 @@ def __repr__(self) -> str: ]: if getattr(self, attr): params.append(f"{attr}={getattr(self, attr)!r}") - return "tcod.Mouse({})".format(", ".join(params)) + return "libtcodpy.Mouse({})".format(", ".join(params)) @property def mouse_p(self) -> Any: @@ -828,7 +828,8 @@ def color_gen_map(colors: Iterable[tuple[int, int, int]], indexes: Iterable[int] List[Color]: A list of Color instances. Example: - >>> tcod.color_gen_map([(0, 0, 0), (255, 128, 0)], [0, 5]) + >>> from tcod import libtcodpy + >>> libtcodpy.color_gen_map([(0, 0, 0), (255, 128, 0)], [0, 5]) [Color(0, 0, 0), Color(51, 25, 0), Color(102, 51, 0), \ Color(153, 76, 0), Color(204, 102, 0), Color(255, 128, 0)] """ @@ -2667,13 +2668,13 @@ def heightmap_kernel_transform( Example: >>> import numpy as np + >>> from tcod import libtcodpy >>> heightmap = np.zeros((3, 3), dtype=np.float32) >>> heightmap[:,1] = 1 >>> dx = [-1, 1, 0] >>> dy = [0, 0, 0] >>> weight = [0.33, 0.33, 0.33] - >>> tcod.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, - ... 0.0, 1.0) + >>> libtcodpy.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, 0.0, 1.0) """ c_dx = ffi.new("int[]", dx) c_dy = ffi.new("int[]", dy) diff --git a/tests/conftest.py b/tests/conftest.py index 0efc716d..0a04cf39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ """Test directory configuration.""" +from __future__ import annotations + import random import warnings from typing import Callable, Iterator, Union @@ -7,6 +9,7 @@ import pytest import tcod +from tcod import libtcodpy # ruff: noqa: D103 @@ -33,23 +36,23 @@ def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Con HEIGHT = 10 TITLE = "libtcod-cffi tests" FULLSCREEN = False - RENDERER = getattr(tcod, "RENDERER_" + request.param) + RENDERER = getattr(libtcodpy, "RENDERER_" + request.param) - tcod.console_set_custom_font(FONT_FILE) - with tcod.console_init_root(WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: + libtcodpy.console_set_custom_font(FONT_FILE) + with libtcodpy.console_init_root(WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: yield con @pytest.fixture def console(session_console: tcod.console.Console) -> tcod.console.Console: console = session_console - tcod.console_flush() + libtcodpy.console_flush() with warnings.catch_warnings(): warnings.simplefilter("ignore") console.default_fg = (255, 255, 255) console.default_bg = (0, 0, 0) - console.default_bg_blend = tcod.BKGND_SET - console.default_alignment = tcod.LEFT + console.default_bg_blend = libtcodpy.BKGND_SET + console.default_alignment = libtcodpy.LEFT console.clear() return console @@ -61,13 +64,13 @@ def offscreen(console: tcod.console.Console) -> tcod.console.Console: @pytest.fixture -def fg() -> tcod.Color: - return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) +def fg() -> libtcodpy.Color: + return libtcodpy.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) @pytest.fixture -def bg() -> tcod.Color: - return tcod.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) +def bg() -> libtcodpy.Color: + return libtcodpy.Color(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) def ch_ascii_int() -> int: @@ -96,4 +99,4 @@ def ch_latin1_str() -> str: ) def ch(request: pytest.FixtureRequest) -> Callable[[], Union[int, str]]: """Test with multiple types of ascii/latin1 characters.""" - return globals()["ch_%s" % request.param]() # type: ignore + return globals()[f"ch_{request.param}"]() # type: ignore diff --git a/tests/test_console.py b/tests/test_console.py index 0681368f..3aad0c69 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -73,13 +73,17 @@ def test_console_methods() -> None: console.print_(0, 0, "Test") console.print_rect(0, 0, 2, 8, "a b c d e f") console.get_height_rect(0, 0, 2, 8, "a b c d e f") - console.rect(0, 0, 2, 2, True) - console.hline(0, 1, 10) - console.vline(1, 0, 10) + with pytest.deprecated_call(): + console.rect(0, 0, 2, 2, True) + with pytest.deprecated_call(): + console.hline(0, 1, 10) + with pytest.deprecated_call(): + console.vline(1, 0, 10) console.print_frame(0, 0, 8, 8, "Frame") console.blit(0, 0, 0, 0, console, 0, 0) # type: ignore console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) # type: ignore - console.set_key_color((254, 0, 254)) + with pytest.deprecated_call(): + console.set_key_color((254, 0, 254)) def test_console_pickle() -> None: diff --git a/tests/test_random.py b/tests/test_random.py index 5bed9d95..d20045cc 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -4,6 +4,8 @@ import pickle from pathlib import Path +import pytest + import tcod.random # ruff: noqa: D103 @@ -15,8 +17,10 @@ def test_tcod_random() -> None: rand = tcod.random.Random(tcod.random.COMPLEMENTARY_MULTIPLY_WITH_CARRY) assert 0 <= rand.randint(0, 100) <= 100 # noqa: PLR2004 assert 0 <= rand.uniform(0, 100) <= 100 # noqa: PLR2004 - rand.guass(0, 1) - rand.inverse_guass(0, 1) + with pytest.warns(FutureWarning, match=r"typo"): + rand.guass(0, 1) + with pytest.warns(FutureWarning, match=r"typo"): + rand.inverse_guass(0, 1) def test_tcod_random_copy() -> None: diff --git a/tests/test_tcod.py b/tests/test_tcod.py index d15b145d..990aafe0 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -10,6 +10,7 @@ import tcod import tcod.console +from tcod import libtcodpy # ruff: noqa: D103 @@ -35,7 +36,7 @@ def test_tcod_bsp() -> None: assert not bsp.children with pytest.raises(RuntimeError): - tcod.bsp_traverse_pre_order(bsp, raise_Exception) + libtcodpy.bsp_traverse_pre_order(bsp, raise_Exception) bsp.split_recursive(3, 4, 4, 1, 1) for node in bsp.walk(): @@ -66,11 +67,11 @@ def test_tcod_map_set_bits() -> None: assert not map_.fov[:].any() map_.transparent[1, 0] = True - assert tcod.map_is_transparent(map_, 0, 1) + assert libtcodpy.map_is_transparent(map_, 0, 1) map_.walkable[1, 0] = True - assert tcod.map_is_walkable(map_, 0, 1) + assert libtcodpy.map_is_walkable(map_, 0, 1) map_.fov[1, 0] = True - assert tcod.map_is_in_fov(map_, 0, 1) + assert libtcodpy.map_is_in_fov(map_, 0, 1) @pytest.mark.filterwarnings("ignore:This class may perform poorly") @@ -115,7 +116,7 @@ def test_color_class() -> None: assert tcod.white - tcod.white == tcod.black assert tcod.black + (2, 2, 2) - (1, 1, 1) == (1, 1, 1) # noqa: RUF005 - color = tcod.Color() + color = libtcodpy.Color() color.r = 1 color.g = 2 color.b = 3 @@ -156,7 +157,7 @@ def test_path_callback() -> None: def test_key_repr() -> None: - Key = tcod.Key + Key = libtcodpy.Key key = Key(vk=1, c=2, shift=True) assert key.vk == 1 assert key.c == 2 # noqa: PLR2004 @@ -168,7 +169,7 @@ def test_key_repr() -> None: def test_mouse_repr() -> None: - Mouse = tcod.Mouse + Mouse = libtcodpy.Mouse mouse = Mouse(x=1, lbutton=True) mouse_copy = eval(repr(mouse)) # noqa: S307 assert mouse.x == mouse_copy.x @@ -188,10 +189,10 @@ def test_recommended_size(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") def test_context(uses_window: None) -> None: - with tcod.context.new_window(32, 32, renderer=tcod.RENDERER_SDL2): + with tcod.context.new_window(32, 32, renderer=libtcodpy.RENDERER_SDL2): pass WIDTH, HEIGHT = 16, 4 - with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=tcod.RENDERER_SDL2) as context: + with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=libtcodpy.RENDERER_SDL2) as context: console = tcod.console.Console(*context.recommended_console_size()) context.present(console) assert context.sdl_window_p is not None From 2e8efdd90fe75bab85b486b15984c238601a8149 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 3 Apr 2025 23:31:57 -0700 Subject: [PATCH 0944/1101] Merge Console.print_box into Console.print method and modernize Changes are mostly backwards compatible expect for a slight change in text placement. Also update some old docs which were not using correct directives. --- CHANGELOG.md | 11 +++ docs/tcod/getting-started.rst | 26 +++--- tcod/console.py | 162 ++++++++++++++++++++++++++++------ tests/test_console.py | 6 +- 4 files changed, 163 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36415ce..2cb837e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- `Console.print` now accepts `height` and `width` keywords and has renamed `string` to `text`. +- Text printed with `Console.print` using right-alignment has been shifted to the left by 1-tile. + +### Deprecated + +- Using `Console.print` without keywords for only the `x`, `y`, and `text` parameters has been deprecated. + The `string` parameter has been renamed to `text`. +- `Console.print_box` has been replaced by `Console.print`. + ## [17.1.0] - 2025-03-29 ### Added diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 7d5ec175..6a275637 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -19,7 +19,7 @@ console will be stretched to fit the window. You can add arguments to :any:`Context.present` to fix the aspect ratio or only scale the console by integer increments. -Example:: +.. code-block:: python #!/usr/bin/env python # Make sure 'dejavu10x10_gs_tc.png' is in the same directory as this script. @@ -38,14 +38,14 @@ Example:: "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD, ) # Create the main console. - console = tcod.console.Console(WIDTH, HEIGHT, order="F") + console = tcod.console.Console(WIDTH, HEIGHT) # Create a window based on this console and tileset. with tcod.context.new( # New window for a console of size columns×rows. columns=console.width, rows=console.height, tileset=tileset, ) as context: while True: # Main loop, runs until SystemExit is raised. console.clear() - console.print(x=0, y=0, string="Hello World!") + console.print(x=0, y=0, text="Hello World!") context.present(console) # Show the console. # This event loop will wait until at least one event is processed before exiting. @@ -53,8 +53,9 @@ Example:: for event in tcod.event.wait(): context.convert_event(event) # Sets tile coordinates for mouse events. print(event) # Print event names and attributes. - if isinstance(event, tcod.event.Quit): - raise SystemExit() + match event: + case tcod.event.Quit(): + raise SystemExit() # The window will be closed after the above with-block exits. @@ -87,7 +88,7 @@ You can call :any:`Context.new_console` every frame or only when the window is resized. This example creates a new console every frame instead of clearing the console every frame and replacing it only on resizing the window. -Example:: +.. code-block:: python #!/usr/bin/env python import tcod.context @@ -103,17 +104,18 @@ Example:: width=WIDTH, height=HEIGHT, sdl_window_flags=FLAGS ) as context: while True: - console = context.new_console(order="F") # Console size based on window resolution and tile size. + console = context.new_console() # Console size based on window resolution and tile size. console.print(0, 0, "Hello World") context.present(console, integer_scaling=True) for event in tcod.event.wait(): - context.convert_event(event) # Sets tile coordinates for mouse events. + event = context.convert_event(event) # Sets tile coordinates for mouse events. print(event) # Print event names and attributes. - if isinstance(event, tcod.event.Quit): - raise SystemExit() - elif isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": - pass # The next call to context.new_console may return a different size. + match event: + case tcod.event.Quit(): + raise SystemExit() + case tcod.event.WindowResized(type="WindowSizeChanged"): + pass # The next call to context.new_console may return a different size. if __name__ == "__main__": diff --git a/tcod/console.py b/tcod/console.py index a1461965..1b7f30e0 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -10,7 +10,7 @@ import warnings from os import PathLike from pathlib import Path -from typing import Any, Iterable +from typing import Any, Iterable, overload import numpy as np from numpy.typing import ArrayLike, NDArray @@ -552,6 +552,7 @@ def __deprecate_defaults( # noqa: C901, PLR0912 stacklevel=3, ) + @deprecated("Switch to using keywords and then replace with 'console.print(...)'") def print_( self, x: int, @@ -579,6 +580,7 @@ def print_( alignment = self.default_alignment if alignment is None else alignment lib.TCOD_console_printf_ex(self.console_c, x, y, bg_blend, alignment, _fmt(string)) + @deprecated("Switch to using keywords and then replace with 'console.print(...)'") def print_rect( # noqa: PLR0913 self, x: int, @@ -973,55 +975,159 @@ def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" return "<{}>".format("\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"])) + @overload + @deprecated( + "Switch all parameters to keywords such as 'console.print(x=..., y=..., text=..., width=..., height=..., fg=..., bg=..., bg_blend=..., alignment=...)'" + "\n'string' keyword should be renamed to `text`" + ) + def print( + self, + x: int, + y: int, + text: str, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + alignment: int = tcod.constants.LEFT, + *, + string: str = "", + ) -> int: ... + + @overload + def print( + self, + x: int, + y: int, + text: str, + *, + width: int | None = None, + height: int | None = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + alignment: int = tcod.constants.LEFT, + ) -> int: ... + def print( # noqa: PLR0913 self, x: int, y: int, - string: str, + text: str = "", fg: tuple[int, int, int] | None = None, bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, - ) -> None: - r"""Print a string on a console with manual line breaks. + *, + width: int | None = None, + height: int | None = None, + string: str = "", + ) -> int: + r"""Print a string of Unicode text on this console. - `x` and `y` are the starting tile, with ``0,0`` as the upper-left - corner of the console. + Prefer using keywords for this method call to avoid ambiguous parameters. - `string` is a Unicode string which may include color control - characters. Strings which are too long will be truncated until the - next newline character ``"\n"``. + Args: + x: Starting X coordinate, with the left-most tile as zero. + y: Starting Y coordinate, with the top-most tile as zero. + text: A Unicode string which may include color control characters. + width: Width in tiles to constrain the printing region. + If a `width` is given then `text` will have automatic word wrapping applied to it. + A `width` of `None` means `text` will only have manual line breaks. + height: Height in tiles to constrain the printing region. + fg: RGB tuple to use as the foreground color, or `None` to leave the foreground unchanged. + Tuple values should be 0-255. + bg: RGB tuple to use as the background color, or `None` to leave the foreground unchanged. + Tuple values should be 0-255. + bg_blend: Background blend type used by libtcod. + Typically starts with `libtcodpy.BKGND_*`. + alignment: One of `libtcodpy.LEFT`, `libtcodpy.CENTER`, or `libtcodpy.RIGHT` + string: Older deprecated name of the `text` parameter. - `fg` and `bg` are the foreground text color and background tile color - respectfully. This is a 3-item tuple with (r, g, b) color values from - 0 to 255. These parameters can also be set to `None` to leave the - colors unchanged. + Returns: + The height of `text` in lines via word wrapping and line breaks. - `bg_blend` is the blend type used by libtcod. + Example:: - `alignment` can be `tcod.LEFT`, `tcod.CENTER`, or `tcod.RIGHT`. + >>> from tcod import libtcodpy + >>> console = tcod.console.Console(20, 1) + >>> console.clear(ch=ord('·')) + >>> console.print(x=0, y=0, text="left") + 1 + >>> console.print(x=console.width, y=0, text="right", alignment=libtcodpy.RIGHT) + 1 + >>> console.print(x=10, y=0, text="center", alignment=libtcodpy.CENTER) + 1 + >>> print(console) + + + >>> console = tcod.console.Console(20, 4) + >>> console.clear(ch=ord('·')) + >>> console.print(x=1, y=1, text="words within bounds", width=8) + 3 + >>> print(console) + <···················· + ·words·············· + ·within············· + ·bounds·············> + >>> WHITE = (255, 255, 255) + >>> BLACK = (0, 0, 0) + >>> console.print(x=0, y=0, text="Black text on white background", fg=BLACK, bg=WHITE) + 1 .. versionadded:: 8.5 .. versionchanged:: 9.0 + `fg` and `bg` now default to `None` instead of white-on-black. .. versionchanged:: 13.0 + `x` and `y` are now always used as an absolute position for negative values. + + .. versionchanged:: Unreleased + + Added `text` parameter to replace `string`. + + Added `width` and `height` keyword parameters to bind text to a rectangle and replace other print functions. + Right-aligned text with `width=None` now treats the `x` coordinate as a past-the-end index, this will shift + the text of older calls to the left by 1 tile. + + Now returns the number of lines printed via word wrapping. """ - string_ = string.encode("utf-8") - lib.TCOD_console_printn( - self.console_c, - x, - y, - len(string_), - string_, - (fg,) if fg is not None else ffi.NULL, - (bg,) if bg is not None else ffi.NULL, - bg_blend, - alignment, + if width is not None and width <= 0: + return 0 + if width is None and alignment == tcod.constants.LEFT: # Fix alignment + width = 0x100000 + if width is None and alignment == tcod.constants.CENTER: # Fix center alignment + x -= 0x100000 + width = 0x200000 + if width is None and alignment == tcod.constants.RIGHT: # Fix right alignment + x -= 0x100000 + width = 0x100000 + rgb_fg = ffi.new("TCOD_ColorRGB*", fg) if fg is not None else ffi.NULL + rgb_bg = ffi.new("TCOD_ColorRGB*", bg) if bg is not None else ffi.NULL + utf8 = (string or text).encode("utf-8") + return _check( + int( + lib.TCOD_printn_rgb( + self.console_c, + { + "x": x, + "y": y, + "width": width or 0, + "height": height or 0, + "fg": rgb_fg, + "bg": rgb_bg, + "flag": bg_blend, + "alignment": alignment, + }, + len(utf8), + utf8, + ) + ) ) + @deprecated("Switch to using keywords and then replace with 'console.print(...)'") def print_box( # noqa: PLR0913 self, x: int, @@ -1144,9 +1250,9 @@ def draw_frame( # noqa: PLR0913 >>> console.draw_frame(x=6, y=0, width=3, height=3, decoration="123456789") >>> console.draw_frame(x=9, y=0, width=3, height=3, decoration="/-\\| |\\-/") >>> console.draw_frame(x=0, y=3, width=12, height=3) - >>> console.print_box(x=0, y=3, width=12, height=1, string=" Title ", alignment=libtcodpy.CENTER) + >>> console.print(x=0, y=3, width=12, height=1, string=" Title ", alignment=libtcodpy.CENTER) 1 - >>> console.print_box(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=libtcodpy.CENTER) + >>> console.print(x=0, y=5, width=12, height=1, string="┤Lower├", alignment=libtcodpy.CENTER) 1 >>> print(console) <┌─┐╔═╗123/-\ diff --git a/tests/test_console.py b/tests/test_console.py index 3aad0c69..04bae6cb 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -70,8 +70,10 @@ def test_console_defaults() -> None: def test_console_methods() -> None: console = tcod.console.Console(width=12, height=10) console.put_char(0, 0, ord("@")) - console.print_(0, 0, "Test") - console.print_rect(0, 0, 2, 8, "a b c d e f") + with pytest.deprecated_call(): + console.print_(0, 0, "Test") + with pytest.deprecated_call(): + console.print_rect(0, 0, 2, 8, "a b c d e f") console.get_height_rect(0, 0, 2, 8, "a b c d e f") with pytest.deprecated_call(): console.rect(0, 0, 2, 2, True) From 6e57885116cfee194d0f780fa01f658c903c19f4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Apr 2025 00:36:00 -0700 Subject: [PATCH 0945/1101] Resolve several Ruff and Mypy warnings Mostly applying and checking unsafe fixes --- build_libtcod.py | 8 +-- build_sdl.py | 12 ++--- docs/conf.py | 4 +- docs/tcod/getting-started.rst | 4 +- examples/distribution/PyInstaller/main.py | 2 +- examples/distribution/cx_Freeze/main.py | 2 +- examples/eventget.py | 2 +- examples/framerate.py | 2 +- examples/samples_tcod.py | 32 ++++++------ examples/sdl-hello-world.py | 4 +- examples/thread_jobs.py | 12 ++--- examples/ttf.py | 8 +-- pyproject.toml | 63 +++++++++-------------- scripts/tag_release.py | 2 +- setup.py | 6 ++- tcod/_internal.py | 8 +-- tcod/bsp.py | 18 ++++--- tcod/console.py | 13 +++-- tcod/context.py | 4 +- tcod/event.py | 12 +++-- tcod/image.py | 12 +++-- tcod/libtcodpy.py | 9 ++-- tcod/los.py | 6 ++- tcod/map.py | 6 ++- tcod/noise.py | 9 ++-- tcod/path.py | 10 ++-- tcod/sdl/_internal.py | 6 ++- tcod/sdl/audio.py | 9 ++-- tcod/sdl/mouse.py | 8 +-- tcod/sdl/render.py | 6 ++- tcod/sdl/sys.py | 4 +- tcod/sdl/video.py | 10 ++-- tcod/tileset.py | 13 +++-- tests/conftest.py | 6 +-- tests/test_libtcodpy.py | 19 ++++--- tests/test_parser.py | 2 +- tests/test_tcod.py | 2 +- 37 files changed, 197 insertions(+), 158 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index 1e824088..a5bca5ac 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -14,6 +14,8 @@ from cffi import FFI +# ruff: noqa: T201 + sys.path.append(str(Path(__file__).parent)) # Allow importing local modules. import build_sdl @@ -50,14 +52,14 @@ class ParsedHeader: def __init__(self, path: Path) -> None: """Initialize and organize a header file.""" - self.path = path = path.resolve(True) + self.path = path = path.resolve(strict=True) directory = path.parent depends = set() header = self.path.read_text(encoding="utf-8") header = RE_COMMENT.sub("", header) header = RE_CPLUSPLUS.sub("", header) for dependency in RE_INCLUDE.findall(header): - depends.add((directory / str(dependency)).resolve(True)) + depends.add((directory / str(dependency)).resolve(strict=True)) header = RE_PREPROCESSOR.sub("", header) header = RE_TAGS.sub("", header) header = RE_VAFUNC.sub("", header) @@ -91,7 +93,7 @@ def walk_includes(directory: str) -> Iterator[ParsedHeader]: if file in HEADER_PARSE_EXCLUDES: continue if file.endswith(".h"): - yield ParsedHeader(Path(path, file).resolve(True)) + yield ParsedHeader(Path(path, file).resolve(strict=True)) def resolve_dependencies( diff --git a/build_sdl.py b/build_sdl.py index 41f91b22..e395c4d4 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -14,11 +14,11 @@ from pathlib import Path from typing import Any -import pcpp # type: ignore +import pcpp # type: ignore[import-untyped] import requests # This script calls a lot of programs. -# ruff: noqa: S603, S607 +# ruff: noqa: S603, S607, T201 BIT_SIZE, LINKAGE = platform.architecture() @@ -141,7 +141,7 @@ def unpack_sdl2(version: str) -> Path: return sdl2_path -class SDLParser(pcpp.Preprocessor): # type: ignore +class SDLParser(pcpp.Preprocessor): # type: ignore[misc] """A modified preprocessor to output code in a format for CFFI.""" def __init__(self) -> None: @@ -159,7 +159,7 @@ def get_output(self) -> str: buffer.write(f"#define {name} ...\n") return buffer.getvalue() - def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: + def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: # noqa: ARG002, FBT001 """Remove bad includes such as stddef.h and stdarg.h.""" raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove) @@ -190,7 +190,7 @@ def on_directive_handle( self, directive: Any, # noqa: ANN401 tokens: list[Any], - if_passthru: bool, + if_passthru: bool, # noqa: FBT001 preceding_tokens: list[Any], ) -> Any: # noqa: ANN401 """Catch and store definitions.""" @@ -204,7 +204,7 @@ def on_directive_handle( check_sdl_version() -if sys.platform in ["win32", "darwin"]: +if sys.platform == "win32" or sys.platform == "darwin": SDL2_PARSE_PATH = unpack_sdl2(SDL2_PARSE_VERSION) SDL2_BUNDLE_PATH = unpack_sdl2(SDL2_BUNDLE_VERSION) diff --git a/docs/conf.py b/docs/conf.py index d4edf824..81c2d205 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,6 +27,8 @@ THIS_DIR = Path(__file__).parent +# ruff: noqa: ERA001 + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -79,7 +81,7 @@ ) release = git_describe.stdout.strip() assert release -print("release version: %r" % release) +print(f"release version: {release!r}") # The short X.Y version. match_version = re.match(r"([0-9]+\.[0-9]+).*?", release) diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index 6a275637..add5d9f0 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -55,7 +55,7 @@ integer increments. print(event) # Print event names and attributes. match event: case tcod.event.Quit(): - raise SystemExit() + raise SystemExit # The window will be closed after the above with-block exits. @@ -113,7 +113,7 @@ clearing the console every frame and replacing it only on resizing the window. print(event) # Print event names and attributes. match event: case tcod.event.Quit(): - raise SystemExit() + raise SystemExit case tcod.event.WindowResized(type="WindowSizeChanged"): pass # The next call to context.new_console may return a different size. diff --git a/examples/distribution/PyInstaller/main.py b/examples/distribution/PyInstaller/main.py index 5e034afd..ce500635 100755 --- a/examples/distribution/PyInstaller/main.py +++ b/examples/distribution/PyInstaller/main.py @@ -30,7 +30,7 @@ def main() -> None: context.present(console) for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if __name__ == "__main__": diff --git a/examples/distribution/cx_Freeze/main.py b/examples/distribution/cx_Freeze/main.py index 79846377..569dddad 100755 --- a/examples/distribution/cx_Freeze/main.py +++ b/examples/distribution/cx_Freeze/main.py @@ -20,7 +20,7 @@ def main() -> None: context.present(console) for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if __name__ == "__main__": diff --git a/examples/eventget.py b/examples/eventget.py index ada61668..9049203f 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -41,7 +41,7 @@ def main() -> None: context.convert_event(event) # Set tile coordinates for event. print(repr(event)) if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": console = context.new_console() if isinstance(event, tcod.event.ControllerDevice): diff --git a/examples/framerate.py b/examples/framerate.py index 25a1dd3f..d7f4a08d 100755 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -138,7 +138,7 @@ def main() -> None: for event in tcod.event.get(): context.convert_event(event) # Set tile coordinates for event. if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if isinstance(event, tcod.event.MouseWheel): desired_fps = max(1, desired_fps + event.y) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 5f8ea1be..adcf9656 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -14,10 +14,9 @@ import time import warnings from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import NDArray import tcod.cffi import tcod.context @@ -29,6 +28,9 @@ from tcod import libtcodpy from tcod.sdl.video import WindowFlags +if TYPE_CHECKING: + from numpy.typing import NDArray + # ruff: noqa: S311 if not sys.warnoptions: @@ -96,13 +98,13 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: libtcodpy.sys_save_screenshot() print("png") elif event.sym == tcod.event.KeySym.ESCAPE: - raise SystemExit() + raise SystemExit elif event.sym in RENDERER_KEYS: # Swap the active context for one with a different renderer. init_context(RENDERER_KEYS[event.sym]) def ev_quit(self, event: tcod.event.Quit) -> None: - raise SystemExit() + raise SystemExit class TrueColorSample(Sample): @@ -307,7 +309,7 @@ def on_draw(self) -> None: sample_console.print( 2, 2, - "%s (ENTER to change)" % self.FLAG_NAMES[self.bk_flag & 0xFF], + f"{self.FLAG_NAMES[self.bk_flag & 0xFF]} (ENTER to change)", fg=WHITE, bg=None, ) @@ -431,26 +433,26 @@ def on_draw(self) -> None: sample_console.print(2, 2 + cur_func, text, fg=WHITE, bg=LIGHT_BLUE) else: sample_console.print(2, 2 + cur_func, text, fg=GREY, bg=None) - sample_console.print(2, 11, "Y/H : zoom (%2.1f)" % self.zoom, fg=WHITE, bg=None) + sample_console.print(2, 11, f"Y/H : zoom ({self.zoom:2.1f})", fg=WHITE, bg=None) if self.implementation != tcod.noise.Implementation.SIMPLE: sample_console.print( 2, 12, - "E/D : hurst (%2.1f)" % self.hurst, + f"E/D : hurst ({self.hurst:2.1f})", fg=WHITE, bg=None, ) sample_console.print( 2, 13, - "R/F : lacunarity (%2.1f)" % self.lacunarity, + f"R/F : lacunarity ({self.lacunarity:2.1f})", fg=WHITE, bg=None, ) sample_console.print( 2, 14, - "T/G : octaves (%2.1f)" % self.octaves, + f"T/G : octaves ({self.octaves:2.1f})", fg=WHITE, bg=None, ) @@ -991,7 +993,7 @@ def on_draw(self) -> None: walls = "OFF" if bsp_room_walls: walls = "ON" - sample_console.print(1, 6, "2 : room walls %s" % walls, fg=WHITE, bg=None) + sample_console.print(1, 6, f"2 : room walls {walls}", fg=WHITE, bg=None) # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): @@ -1161,7 +1163,7 @@ def on_draw(self) -> None: sample_console.print( 1, 1, - "%s\n\n+ : next generator\n- : prev generator" % self.sets[self.current_set], + f"{self.sets[self.current_set]}\n\n+ : next generator\n- : prev generator", fg=WHITE, bg=None, ) @@ -1214,8 +1216,8 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: # the coordinates of all tiles in the screen, as numpy arrays. # example: (4x3 pixels screen) -# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] -# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] +# xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # noqa: ERA001 +# yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] # noqa: ERA001 if numpy_available: (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) # translate coordinates of all pixels to center @@ -1504,7 +1506,7 @@ def handle_events() -> None: SAMPLES[cur_sample].dispatch(event) if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit def draw_samples_menu() -> None: @@ -1518,7 +1520,7 @@ def draw_samples_menu() -> None: root_console.print( 2, 46 - (len(SAMPLES) - i), - " %s" % sample.name.ljust(19), + f" {sample.name.ljust(19)}", fg, bg, alignment=libtcodpy.LEFT, diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index 6afd02ab..67b0fa01 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -17,7 +17,7 @@ def render_text(renderer: tcod.sdl.render.Renderer, text: str) -> tcod.sdl.rende """Render text, upload it to VRAM, then return it as an SDL Texture.""" # Use Pillow to render the font. _left, _top, right, bottom = font.getbbox(text) - width, height = right, bottom + width, height = int(right), int(bottom) image = Image.new("RGBA", (width, height)) draw = ImageDraw.Draw(image) draw.text((0, 0), text, font=font) @@ -43,7 +43,7 @@ def main() -> None: renderer.present() for event in tcod.event.get(): if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if __name__ == "__main__": diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index fa8ce9dc..a7212c4f 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -32,32 +32,32 @@ REPEAT = 10 # Number to times to run a test. Only the fastest result is shown. -def test_fov(map_: tcod.map.Map) -> tcod.map.Map: # noqa: D103 +def test_fov(map_: tcod.map.Map) -> tcod.map.Map: map_.compute_fov(MAP_WIDTH // 2, MAP_HEIGHT // 2) return map_ -def test_fov_single(maps: List[tcod.map.Map]) -> None: # noqa: D103 +def test_fov_single(maps: List[tcod.map.Map]) -> None: for map_ in maps: test_fov(map_) -def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: # noqa: D103 +def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: for _result in executor.map(test_fov, maps): pass -def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: # noqa: D103 +def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: astar = tcod.path.AStar(map_) return astar.get_path(0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1) -def test_astar_single(maps: List[tcod.map.Map]) -> None: # noqa: D103 +def test_astar_single(maps: List[tcod.map.Map]) -> None: for map_ in maps: test_astar(map_) -def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: # noqa: D103 +def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: for _result in executor.map(test_astar, maps): pass diff --git a/examples/ttf.py b/examples/ttf.py index 3e13a837..d6504bd8 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -9,17 +9,19 @@ # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to this example script. # https://creativecommons.org/publicdomain/zero/1.0/ -from typing import Tuple +from typing import TYPE_CHECKING, Tuple import freetype # type: ignore # pip install freetype-py import numpy as np -from numpy.typing import NDArray import tcod.console import tcod.context import tcod.event import tcod.tileset +if TYPE_CHECKING: + from numpy.typing import NDArray + FONT = "VeraMono.ttf" @@ -81,7 +83,7 @@ def main() -> None: context.present(console, integer_scaling=True) for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): - raise SystemExit() + raise SystemExit if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": # Resize the Tileset to match the new screen size. context.change_tileset( diff --git a/pyproject.toml b/pyproject.toml index 2590d920..f6a26c12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,46 +149,31 @@ extend-exclude = ["libtcod"] # Ignore submodule line-length = 120 [tool.ruff.lint] # https://docs.astral.sh/ruff/rules/ -select = [ - "C90", # mccabe - "E", # pycodestyle - "W", # pycodestyle - "F", # Pyflakes - "I", # isort - "UP", # pyupgrade - "YTT", # flake8-2020 - "ANN", # flake8-annotations - "S", # flake8-bandit - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "DTZ", # flake8-datetimez - "EM", # flake8-errmsg - "EXE", # flake8-executable - "RET", # flake8-return - "ICN", # flake8-import-conventions - "PIE", # flake8-pie - "PT", # flake8-pytest-style - "SIM", # flake8-simplify - "PTH", # flake8-use-pathlib - "PL", # Pylint - "TRY", # tryceratops - "RUF", # NumPy-specific rules - "G", # flake8-logging-format - "D", # pydocstyle -] +select = ["ALL"] ignore = [ - "E501", # line-too-long - "S101", # assert - "S301", # suspicious-pickle-usage - "S311", # suspicious-non-cryptographic-random-usage - "D203", # one-blank-line-before-class - "D204", # one-blank-line-after-class - "D213", # multi-line-summary-second-line - "D407", # dashed-underline-after-section - "D408", # section-underline-after-name - "D409", # section-underline-matches-section-length - "D206", # indent-with-spaces - "W191", # tab-indentation + "COM", # flake8-commas + "D203", # one-blank-line-before-class + "D204", # one-blank-line-after-class + "D213", # multi-line-summary-second-line + "D407", # dashed-underline-after-section + "D408", # section-underline-after-name + "D409", # section-underline-matches-section-length + "D206", # indent-with-spaces + "E501", # line-too-long + "PYI064", # redundant-final-literal + "S101", # assert + "S301", # suspicious-pickle-usage + "S311", # suspicious-non-cryptographic-random-usage + "SLF001", # private-member-access + "W191", # tab-indentation +] +[tool.ruff.lint.per-file-ignores] +"**/{tests}/*" = [ + "D103", # undocumented-public-function +] +"**/{tests,docs,examples,scripts}/*" = [ + "D103", # undocumented-public-function + "T201", # print ] [tool.ruff.lint.pydocstyle] # https://docs.astral.sh/ruff/settings/#lintpydocstyle diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 2155aaae..066eaeda 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -42,7 +42,7 @@ def parse_changelog(args: argparse.Namespace) -> tuple[str, str]: print("--- Tagged section:") print(tagged) - return "".join((header, tagged, tail)), changes + return f"{header}{tagged}{tail}", changes def replace_unreleased_tags(tag: str, dry_run: bool) -> None: diff --git a/setup.py b/setup.py index 29f9cb3d..a98b64b3 100755 --- a/setup.py +++ b/setup.py @@ -10,6 +10,8 @@ from setuptools import setup +# ruff: noqa: T201 + SDL_VERSION_NEEDED = (2, 0, 5) SETUP_DIR = Path(__file__).parent # setup.py current directory @@ -17,7 +19,7 @@ def get_package_data() -> list[str]: """Get data files which will be included in the main tcod/ directory.""" - BIT_SIZE, _ = platform.architecture() + bit_size, _ = platform.architecture() files = [ "py.typed", "lib/LIBTCOD-CREDITS.txt", @@ -25,7 +27,7 @@ def get_package_data() -> list[str]: "lib/README-SDL.txt", ] if "win32" in sys.platform: - if BIT_SIZE == "32bit": + if bit_size == "32bit": files += ["x86/SDL2.dll"] else: files += ["x64/SDL2.dll"] diff --git a/tcod/_internal.py b/tcod/_internal.py index 85410cb2..01136c5e 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -5,17 +5,19 @@ import locale import sys import warnings -from pathlib import Path -from types import TracebackType from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar import numpy as np -from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal, LiteralString, deprecated from tcod.cffi import ffi, lib if TYPE_CHECKING: + from pathlib import Path + from types import TracebackType + + from numpy.typing import ArrayLike, NDArray + import tcod.image FuncType = Callable[..., Any] diff --git a/tcod/bsp.py b/tcod/bsp.py index 98783685..0f8caeaa 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -27,13 +27,15 @@ from __future__ import annotations -from typing import Any, Iterator +from typing import TYPE_CHECKING, Any, Iterator from typing_extensions import deprecated -import tcod.random from tcod.cffi import ffi, lib +if TYPE_CHECKING: + import tcod.random + class BSP: """A binary space partitioning tree which can be used for simple dungeon generation. @@ -236,13 +238,13 @@ def inverted_level_order(self) -> Iterator[BSP]: .. versionadded:: 8.3 """ levels: list[list[BSP]] = [] - next = [self] - while next: - levels.append(next) - level = next - next = [] + next_: list[BSP] = [self] + while next_: + levels.append(next_) + level = next_ + next_ = [] for node in level: - next.extend(node.children) + next_.extend(node.children) while levels: yield from levels.pop() diff --git a/tcod/console.py b/tcod/console.py index 1b7f30e0..0f108d95 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -8,19 +8,22 @@ from __future__ import annotations import warnings -from os import PathLike from pathlib import Path -from typing import Any, Iterable, overload +from typing import TYPE_CHECKING, Any, Iterable, overload import numpy as np -from numpy.typing import ArrayLike, NDArray -from typing_extensions import Literal, deprecated +from typing_extensions import Literal, Self, deprecated import tcod._internal import tcod.constants from tcod._internal import _check, _path_encode from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from os import PathLike + + from numpy.typing import ArrayLike, NDArray + def _fmt(string: str) -> bytes: """Return a string that escapes 'C printf' side effects.""" @@ -884,7 +887,7 @@ def set_key_color(self, color: tuple[int, int, int] | None) -> None: """ self._key_color = color - def __enter__(self) -> Console: + def __enter__(self) -> Self: """Return this console in a managed context. When the root console is used as a context, the graphical window will diff --git a/tcod/context.py b/tcod/context.py index 78d18de2..826307dc 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -32,7 +32,7 @@ from pathlib import Path from typing import Any, Iterable, NoReturn, TypeVar -from typing_extensions import Literal, deprecated +from typing_extensions import Literal, Self, deprecated import tcod.console import tcod.event @@ -164,7 +164,7 @@ def _p(self) -> Any: # noqa: ANN401 msg = "This context has been closed can no longer be used." raise RuntimeError(msg) from None - def __enter__(self) -> Context: + def __enter__(self) -> Self: """Enter this context which will close on exiting.""" return self diff --git a/tcod/event.py b/tcod/event.py index d225ae37..80c4acce 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -84,10 +84,9 @@ import enum import warnings -from typing import Any, Callable, Final, Generic, Iterator, Mapping, NamedTuple, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Final, Generic, Iterator, Mapping, NamedTuple, TypeVar import numpy as np -from numpy.typing import NDArray from typing_extensions import Literal import tcod.event @@ -98,6 +97,9 @@ from tcod.event_constants import * # noqa: F403 from tcod.sdl.joystick import _HAT_DIRECTIONS +if TYPE_CHECKING: + from numpy.typing import NDArray + T = TypeVar("T") @@ -299,7 +301,7 @@ def __init__(self, type: str | None = None) -> None: @classmethod def from_sdl_event(cls, sdl_event: Any) -> Event: """Return a class instance from a python-cffi 'SDL_Event*' pointer.""" - raise NotImplementedError() + raise NotImplementedError def __str__(self) -> str: return f"" @@ -2189,7 +2191,7 @@ def _missing_(cls, value: object) -> Scancode | None: result._value_ = value return result - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, KeySym): msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." raise TypeError(msg) @@ -2743,7 +2745,7 @@ def _missing_(cls, value: object) -> KeySym | None: result._value_ = value return result - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, Scancode): msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." raise TypeError(msg) diff --git a/tcod/image.py b/tcod/image.py index dca67314..219916cf 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -11,18 +11,22 @@ from __future__ import annotations -from os import PathLike from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import ArrayLike, NDArray from typing_extensions import deprecated -import tcod.console from tcod._internal import _console, _path_encode from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from os import PathLike + + from numpy.typing import ArrayLike, NDArray + + import tcod.console + class Image: """A libtcod image. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 27c0032d..52cba83e 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -6,12 +6,10 @@ import sys import threading import warnings -from os import PathLike from pathlib import Path -from typing import Any, Callable, Hashable, Iterable, Iterator, Sequence +from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable, Iterator, Sequence import numpy as np -from numpy.typing import NDArray from typing_extensions import Literal, deprecated import tcod.bsp @@ -54,6 +52,11 @@ NOISE_DEFAULT, ) +if TYPE_CHECKING: + from os import PathLike + + from numpy.typing import NDArray + # Functions are too deprecated to make changes. # ruff: noqa: ANN401 PLR0913 D102 D103 D105 D107 diff --git a/tcod/los.py b/tcod/los.py index 6d30d0b8..f09b073c 100644 --- a/tcod/los.py +++ b/tcod/los.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import NDArray from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from numpy.typing import NDArray + def bresenham(start: tuple[int, int], end: tuple[int, int]) -> NDArray[np.intc]: """Return a thin Bresenham line as a NumPy array of shape (length, 2). diff --git a/tcod/map.py b/tcod/map.py index 17aaf0b4..32e42d1e 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,16 +3,18 @@ from __future__ import annotations import warnings -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod._internal import tcod.constants from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from numpy.typing import ArrayLike, NDArray + class Map: """A map containing libtcod attributes. diff --git a/tcod/noise.py b/tcod/noise.py index b91c90b6..82920a1c 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -36,16 +36,18 @@ import enum import warnings -from typing import Any, Sequence +from typing import TYPE_CHECKING, Any, Sequence import numpy as np -from numpy.typing import ArrayLike, NDArray from typing_extensions import Literal import tcod.constants import tcod.random from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from numpy.typing import ArrayLike, NDArray + class Algorithm(enum.IntEnum): """Libtcod noise algorithms. @@ -274,7 +276,8 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: ffi.from_buffer("float*", out), ) else: - raise TypeError("Unexpected %r" % self.implementation) + msg = f"Unexpected {self.implementation!r}" + raise TypeError(msg) return out diff --git a/tcod/path.py b/tcod/path.py index e55223d7..8ce9aaa2 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,15 +21,17 @@ import functools import itertools import warnings -from typing import Any, Callable, Final +from typing import TYPE_CHECKING, Any, Callable, Final import numpy as np -from numpy.typing import ArrayLike, DTypeLike, NDArray -from typing_extensions import Literal +from typing_extensions import Literal, Self from tcod._internal import _check from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from numpy.typing import ArrayLike, DTypeLike, NDArray + @ffi.def_extern() # type: ignore def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 @@ -129,7 +131,7 @@ class NodeCostArray(np.ndarray): # type: ignore np.uint32: ("uint32_t*", _get_path_cost_func("PathCostArrayUInt32")), } - def __new__(cls, array: ArrayLike) -> NodeCostArray: + def __new__(cls, array: ArrayLike) -> Self: """Validate a numpy array and setup a C callback.""" return np.asarray(array).view(cls) # type: ignore[no-any-return] diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 8ea5e13f..8904e318 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -5,11 +5,13 @@ import logging import sys as _sys from dataclasses import dataclass -from types import TracebackType -from typing import Any, Callable, NoReturn, TypeVar +from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from types import TracebackType + T = TypeVar("T") logger = logging.getLogger("tcod.sdl") diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 33356132..a5da2f5b 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -48,17 +48,20 @@ import sys import threading import time -from types import TracebackType -from typing import Any, Callable, Final, Hashable, Iterator +from typing import TYPE_CHECKING, Any, Callable, Final, Hashable, Iterator import numpy as np -from numpy.typing import ArrayLike, DTypeLike, NDArray from typing_extensions import Literal, Self import tcod.sdl.sys from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _get_error, _ProtectedContext +if TYPE_CHECKING: + from types import TracebackType + + from numpy.typing import ArrayLike, DTypeLike, NDArray + def _get_format(format: DTypeLike) -> int: """Return a SDL_AudioFormat bit-field from a NumPy dtype.""" diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index fefd71d7..de481427 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -10,16 +10,18 @@ from __future__ import annotations import enum -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import ArrayLike, NDArray import tcod.event import tcod.sdl.video from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p +if TYPE_CHECKING: + from numpy.typing import ArrayLike, NDArray + class Cursor: """A cursor icon for use with :any:`set_cursor`.""" @@ -33,7 +35,7 @@ def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 raise TypeError(msg) self.p = sdl_cursor_p - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: return bool(self.p == getattr(other, "p", None)) @classmethod diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index ae2b542d..22d3911d 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -6,16 +6,18 @@ from __future__ import annotations import enum -from typing import Any, Final, Sequence +from typing import TYPE_CHECKING, Any, Final, Sequence import numpy as np -from numpy.typing import NDArray from typing_extensions import Literal import tcod.sdl.video from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p, _required_version +if TYPE_CHECKING: + from numpy.typing import NDArray + class TextureAccess(enum.IntEnum): """Determines how a texture is expected to be used.""" diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index dff4d4c0..7e7b2d88 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -3,6 +3,8 @@ import enum import warnings +from typing_extensions import Self + from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _get_error @@ -40,7 +42,7 @@ def close(self) -> None: def __del__(self) -> None: self.close() - def __enter__(self) -> _ScopeInit: + def __enter__(self) -> Self: return self def __exit__(self, *_: object) -> None: diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index f6dc922f..b140aa17 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -11,14 +11,16 @@ import enum import sys -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from numpy.typing import ArrayLike, NDArray from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p, _required_version, _version_at_least +if TYPE_CHECKING: + from numpy.typing import ArrayLike, NDArray + __all__ = ( "FlashOperation", "Window", @@ -134,7 +136,9 @@ def __init__(self, sdl_window_p: Any) -> None: # noqa: ANN401 raise TypeError(msg) self.p = sdl_window_p - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: + if not isinstance(other, Window): + return NotImplemented return bool(self.p == other.p) def set_icon(self, pixels: ArrayLike) -> None: diff --git a/tcod/tileset.py b/tcod/tileset.py index f57304da..5f5a7511 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -16,18 +16,22 @@ from __future__ import annotations import itertools -from os import PathLike from pathlib import Path -from typing import Any, Iterable +from typing import TYPE_CHECKING, Any, Iterable import numpy as np -from numpy.typing import ArrayLike, NDArray from typing_extensions import deprecated -import tcod.console from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from os import PathLike + + from numpy.typing import ArrayLike, NDArray + + import tcod.console + class Tileset: """A collection of graphical tiles. @@ -390,6 +394,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: Example:: + >>> import tcod.tileset >>> tileset = tcod.tileset.Tileset(8, 8) >>> tcod.tileset.procedural_block_elements(tileset=tileset) >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left. diff --git a/tests/conftest.py b/tests/conftest.py index 0a04cf39..f7a42fd5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import random import warnings -from typing import Callable, Iterator, Union +from typing import Callable, Iterator import pytest @@ -97,6 +97,6 @@ def ch_latin1_str() -> str: "latin1_str", ] ) -def ch(request: pytest.FixtureRequest) -> Callable[[], Union[int, str]]: +def ch(request: pytest.FixtureRequest) -> Callable[[], int | str]: """Test with multiple types of ascii/latin1 characters.""" - return globals()[f"ch_{request.param}"]() # type: ignore + return globals()[f"ch_{request.param}"]() # type: ignore[no-any-return] diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index b82392a1..f459f27f 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -433,7 +433,6 @@ def test_line_iter() -> None: @pytest.mark.filterwarnings("ignore") def test_bsp() -> None: - """Commented out statements work in libtcod-cffi.""" bsp = libtcodpy.bsp_new_with_size(0, 0, 64, 64) repr(bsp) # test __repr__ on leaf libtcodpy.bsp_resize(bsp, 0, 0, 32, 32) @@ -449,15 +448,15 @@ def test_bsp() -> None: bsp.level = bsp.level # cover functions on leaf - # self.assertFalse(libtcodpy.bsp_left(bsp)) - # self.assertFalse(libtcodpy.bsp_right(bsp)) - # self.assertFalse(libtcodpy.bsp_father(bsp)) + assert not libtcodpy.bsp_left(bsp) + assert not libtcodpy.bsp_right(bsp) + assert not libtcodpy.bsp_father(bsp) assert libtcodpy.bsp_is_leaf(bsp) assert libtcodpy.bsp_contains(bsp, 1, 1) - # self.assertFalse(libtcodpy.bsp_contains(bsp, -1, -1)) - # self.assertEqual(libtcodpy.bsp_find_node(bsp, 1, 1), bsp) - # self.assertFalse(libtcodpy.bsp_find_node(bsp, -1, -1)) + assert not libtcodpy.bsp_contains(bsp, -1, -1) + assert libtcodpy.bsp_find_node(bsp, 1, 1) == bsp + assert not libtcodpy.bsp_find_node(bsp, -1, -1) libtcodpy.bsp_split_once(bsp, False, 4) repr(bsp) # test __repr__ with parent @@ -467,10 +466,10 @@ def test_bsp() -> None: # cover functions on parent assert libtcodpy.bsp_left(bsp) assert libtcodpy.bsp_right(bsp) - # self.assertFalse(libtcodpy.bsp_father(bsp)) + assert not libtcodpy.bsp_father(bsp) assert not libtcodpy.bsp_is_leaf(bsp) - # self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)), bsp) - # self.assertEqual(libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)), bsp) + assert libtcodpy.bsp_father(libtcodpy.bsp_left(bsp)) == bsp # type: ignore[arg-type] + assert libtcodpy.bsp_father(libtcodpy.bsp_right(bsp)) == bsp # type: ignore[arg-type] libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0) diff --git a/tests/test_parser.py b/tests/test_parser.py index 07f736a1..5494b786 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -62,7 +62,7 @@ def new_property(self, name: str, typ: int, value: Any) -> bool: # noqa: ANN401 type_names = ["NONE", "BOOL", "CHAR", "INT", "FLOAT", "STRING", "COLOR", "DICE"] type_name = type_names[typ & 0xFF] if typ & libtcod.TYPE_LIST: - type_name = "LIST<%s>" % type_name + type_name = f"LIST<{type_name}>" print("new property named ", name, " type ", type_name, " value ", value) return True diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 990aafe0..8b910c3a 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -15,7 +15,7 @@ # ruff: noqa: D103 -def raise_Exception(*args: object) -> NoReturn: +def raise_Exception(*_args: object) -> NoReturn: raise RuntimeError("testing exception") # noqa: TRY003, EM101 From 56d0e605630e3d4bc84bca5dade92a1fe6aa7f2a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Apr 2025 00:42:32 -0700 Subject: [PATCH 0946/1101] Adjust right-aligned printing in samples --- examples/samples_tcod.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index adcf9656..8abba781 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1169,7 +1169,7 @@ def on_draw(self) -> None: ) for i in range(len(self.names)): sample_console.print( - SAMPLE_SCREEN_WIDTH - 2, + SAMPLE_SCREEN_WIDTH - 1, 2 + i, self.names[i], fg=WHITE, @@ -1533,14 +1533,14 @@ def draw_stats() -> None: except ZeroDivisionError: fps = 0 root_console.print( - 79, + root_console.width, 46, "last frame :%5.1f ms (%4d fps)" % (frame_length[-1] * 1000.0, fps), fg=GREY, alignment=libtcodpy.RIGHT, ) root_console.print( - 79, + root_console.width, 47, "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), fg=GREY, From 3a21c6d33e7f001f3d396df2a7806f478b952e67 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Apr 2025 01:14:57 -0700 Subject: [PATCH 0947/1101] Cleanup tcod samples --- examples/samples_tcod.py | 89 +++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 8abba781..e069c2ea 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -190,7 +190,7 @@ def __init__(self) -> None: sample_console.width // 2, sample_console.height // 2, "Offscreen console", - False, + clear=False, fg=WHITE, bg=BLACK, ) @@ -239,7 +239,7 @@ def on_draw(self) -> None: class LineDrawingSample(Sample): - FLAG_NAMES = [ + FLAG_NAMES = ( "BKGND_NONE", "BKGND_SET", "BKGND_MULTIPLY", @@ -253,7 +253,7 @@ class LineDrawingSample(Sample): "BKGND_BURN", "BKGND_OVERLAY", "BKGND_ALPHA", - ] + ) def __init__(self) -> None: self.name = "Line drawing" @@ -316,7 +316,7 @@ def on_draw(self) -> None: class NoiseSample(Sample): - NOISE_OPTIONS = [ # (name, algorithm, implementation) + NOISE_OPTIONS = ( # (name, algorithm, implementation) ( "perlin noise", tcod.noise.Algorithm.PERLIN, @@ -362,7 +362,7 @@ class NoiseSample(Sample): tcod.noise.Algorithm.WAVELET, tcod.noise.Implementation.TURBULENCE, ), - ] + ) def __init__(self) -> None: self.name = "Noise" @@ -428,7 +428,7 @@ def on_draw(self) -> None: ) for cur_func in range(len(self.NOISE_OPTIONS)): - text = "%i : %s" % (cur_func + 1, self.NOISE_OPTIONS[cur_func][0]) + text = f"{cur_func + 1} : {self.NOISE_OPTIONS[cur_func][0]}" if cur_func == self.func: sample_console.print(2, 2 + cur_func, text, fg=WHITE, bg=LIGHT_BLUE) else: @@ -495,7 +495,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: DARK_GROUND = (50, 50, 150) LIGHT_GROUND = (200, 180, 50) -SAMPLE_MAP_ = [ +SAMPLE_MAP_ = ( "##############################################", "####################### #################", "##################### # ###############", @@ -516,11 +516,11 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: "######## # #### ##### #####", "######## ##### ####################", "##############################################", -] +) SAMPLE_MAP: NDArray[Any] = np.array([list(line) for line in SAMPLE_MAP_]).transpose() -FOV_ALGO_NAMES = [ +FOV_ALGO_NAMES = ( "BASIC ", "DIAMOND ", "SHADOW ", @@ -535,7 +535,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: "PERMISSIVE8", "RESTRICTIVE", "SYMMETRIC_SHADOWCAST", -] +) TORCH_RADIUS = 10 SQUARED_TORCH_RADIUS = TORCH_RADIUS * TORCH_RADIUS @@ -635,13 +635,13 @@ def on_draw(self) -> None: sample_console.bg[...] = np.where(fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg) def ev_keydown(self, event: tcod.event.KeyDown) -> None: - MOVE_KEYS = { + MOVE_KEYS = { # noqa: N806 tcod.event.KeySym.i: (0, -1), tcod.event.KeySym.j: (-1, 0), tcod.event.KeySym.k: (0, 1), tcod.event.KeySym.l: (1, 0), } - FOV_SELECT_KEYS = { + FOV_SELECT_KEYS = { # noqa: N806 tcod.event.KeySym.MINUS: -1, tcod.event.KeySym.EQUALS: 1, tcod.event.KeySym.KP_MINUS: -1, @@ -753,7 +753,7 @@ def on_draw(self) -> None: sample_console, x, y, - libtcodpy.color_lerp( # type: ignore + libtcodpy.color_lerp( # type: ignore[arg-type] LIGHT_GROUND, DARK_GROUND, 0.9 * libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, @@ -771,11 +771,11 @@ def on_draw(self) -> None: if self.using_astar: if not libtcodpy.path_is_empty(self.path): libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) - self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore + self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore[assignment] libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) elif not libtcodpy.dijkstra_is_empty(self.dijkstra): libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) - self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore + self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore[assignment] libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) self.recalculate = True @@ -983,9 +983,9 @@ def on_draw(self) -> None: 1, "ENTER : rebuild bsp\n" "SPACE : rebuild dungeon\n" - "+-: bsp depth %d\n" - "*/: room size %d\n" - "1 : random room size %s" % (bsp_depth, bsp_min_room_size, rooms), + f"+-: bsp depth {bsp_depth}\n" + f"*/: room size {bsp_min_room_size}\n" + f"1 : random room size {rooms}", fg=WHITE, bg=None, ) @@ -1102,23 +1102,12 @@ def on_draw(self) -> None: sample_console.print( 1, 1, - "Pixel position : %4dx%4d\n" - "Tile position : %4dx%4d\n" - "Tile movement : %4dx%4d\n" - "Left button : %s\n" - "Right button : %s\n" - "Middle button : %s\n" - % ( - self.motion.position.x, - self.motion.position.y, - self.motion.tile.x, - self.motion.tile.y, - self.motion.tile_motion.x, - self.motion.tile_motion.y, - ("OFF", "ON")[self.mouse_left], - ("OFF", "ON")[self.mouse_right], - ("OFF", "ON")[self.mouse_middle], - ), + f"Pixel position : {self.motion.position.x:4d}x{self.motion.position.y:4d}\n" + f"Tile position : {self.motion.tile.x:4d}x{self.motion.tile.y:4d}\n" + f"Tile movement : {self.motion.tile_motion.x:4d}x{self.motion.tile_motion.y:4d}\n" + f"Left button : {'ON' if self.mouse_left else 'OFF'}\n" + f"Right button : {'ON' if self.mouse_right else 'OFF'}\n" + f"Middle button : {'ON' if self.mouse_middle else 'OFF'}\n", fg=LIGHT_YELLOW, bg=None, ) @@ -1318,9 +1307,9 @@ def on_draw(self) -> None: brightness = texture[uu.astype(int), vv.astype(int)] / 4.0 + 0.5 # use the brightness map to compose the final color of the tunnel - R = brightness * self.tex_r - G = brightness * self.tex_g - B = brightness * self.tex_b + rr = brightness * self.tex_r + gg = brightness * self.tex_g + bb = brightness * self.tex_b # create new light source if random.random() <= time_delta * LIGHTS_CHANCE and len(self.lights) < MAX_LIGHTS: @@ -1350,17 +1339,17 @@ def on_draw(self) -> None: brightness = light_brightness / ((xc - xl) ** 2 + (yc - yl) ** 2) # make all pixels shine around this light - R += brightness * light.r - G += brightness * light.g - B += brightness * light.b + rr += brightness * light.r + gg += brightness * light.g + bb += brightness * light.b # truncate values - R = R.clip(0, 255) - G = G.clip(0, 255) - B = B.clip(0, 255) + rr = rr.clip(0, 255) + gg = gg.clip(0, 255) + bb = bb.clip(0, 255) # fill the screen with these background colors - sample_console.bg.transpose(2, 1, 0)[...] = (R, G, B) + sample_console.bg.transpose(2, 1, 0)[...] = (rr, gg, bb) ############################################# @@ -1406,10 +1395,8 @@ def init_context(renderer: int) -> None: global context, console_render, sample_minimap if "context" in globals(): context.close() - libtcod_version = "%i.%i.%i" % ( - tcod.cffi.lib.TCOD_MAJOR_VERSION, - tcod.cffi.lib.TCOD_MINOR_VERSION, - tcod.cffi.lib.TCOD_PATCHLEVEL, + libtcod_version = ( + f"{tcod.cffi.lib.TCOD_MAJOR_VERSION}.{tcod.cffi.lib.TCOD_MINOR_VERSION}.{tcod.cffi.lib.TCOD_PATCHLEVEL}" ) context = tcod.context.new( columns=root_console.width, @@ -1535,14 +1522,14 @@ def draw_stats() -> None: root_console.print( root_console.width, 46, - "last frame :%5.1f ms (%4d fps)" % (frame_length[-1] * 1000.0, fps), + f"last frame :{frame_length[-1] * 1000.0:5.1f} ms ({int(fps):4d} fps)", fg=GREY, alignment=libtcodpy.RIGHT, ) root_console.print( root_console.width, 47, - "elapsed : %8d ms %5.2fs" % (time.perf_counter() * 1000, time.perf_counter()), + f"elapsed : {int(time.perf_counter() * 1000):8d} ms {time.perf_counter():5.2f}s", fg=GREY, alignment=libtcodpy.RIGHT, ) From c8103fdc5261148fe2599fe67c21b57b3ccc5063 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 4 Apr 2025 16:53:58 -0700 Subject: [PATCH 0948/1101] Resolve remaining warnings in console module --- tcod/console.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tcod/console.py b/tcod/console.py index 0f108d95..013ea84e 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -193,7 +193,7 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: else: self._console_data = ffi.cast("struct TCOD_Console*", self.console_c) - self._tiles: NDArray[Any] = np.frombuffer( # type: ignore + self._tiles = np.frombuffer( ffi.buffer(self._console_data.tiles[0 : self.width * self.height]), dtype=self.DTYPE, ).reshape((self.height, self.width)) @@ -203,12 +203,12 @@ def _init_setup_console_data(self, order: Literal["C", "F"] = "C") -> None: @property def width(self) -> int: """The width of this Console.""" - return lib.TCOD_console_get_width(self.console_c) # type: ignore + return int(lib.TCOD_console_get_width(self.console_c)) @property def height(self) -> int: """The height of this Console.""" - return lib.TCOD_console_get_height(self.console_c) # type: ignore + return int(lib.TCOD_console_get_height(self.console_c)) @property def bg(self) -> NDArray[np.uint8]: @@ -402,7 +402,7 @@ def default_bg_blend(self) -> int: .. deprecated:: 8.5 These should not be used. Prefer passing defaults as kwargs. """ - return self._console_data.bkgnd_flag # type: ignore + return int(self._console_data.bkgnd_flag) @default_bg_blend.setter @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) @@ -416,7 +416,7 @@ def default_alignment(self) -> int: .. deprecated:: 8.5 These should not be used. Prefer passing defaults as kwargs. """ - return self._console_data.alignment # type: ignore + return int(self._console_data.alignment) @default_alignment.setter @deprecated(_DEPRECATE_CONSOLE_DEFAULTS_MSG, category=FutureWarning) @@ -434,8 +434,8 @@ def __clear_warning(self, name: str, value: tuple[int, int, int]) -> None: def clear( self, ch: int = 0x20, - fg: tuple[int, int, int] = ..., # type: ignore - bg: tuple[int, int, int] = ..., # type: ignore + fg: tuple[int, int, int] = ..., # type: ignore[assignment] + bg: tuple[int, int, int] = ..., # type: ignore[assignment] ) -> None: """Reset all values in this console to a single value. @@ -454,11 +454,11 @@ def clear( Added the `ch`, `fg`, and `bg` parameters. Non-white-on-black default values are deprecated. """ - if fg is ...: # type: ignore + if fg is ...: # type: ignore[comparison-overlap] fg = self.default_fg if fg != (255, 255, 255): self.__clear_warning("fg", fg) - if bg is ...: # type: ignore + if bg is ...: # type: ignore[comparison-overlap] bg = self.default_bg if bg != (0, 0, 0): self.__clear_warning("bg", bg) @@ -657,7 +657,7 @@ def rect( # noqa: PLR0913 y: int, width: int, height: int, - clear: bool, + clear: bool, # noqa: FBT001 bg_blend: int = tcod.constants.BKGND_DEFAULT, ) -> None: """Draw a the background color on a rect optionally clearing the text. @@ -750,7 +750,7 @@ def print_frame( # noqa: PLR0913 width: int, height: int, string: str = "", - clear: bool = True, + clear: bool = True, # noqa: FBT001, FBT002 bg_blend: int = tcod.constants.BKGND_DEFAULT, ) -> None: """Draw a framed rectangle with optional text. @@ -831,11 +831,11 @@ def blit( # noqa: PLR0913 # The old syntax is easy to detect and correct. if hasattr(src_y, "console_c"): (src_x, src_y, width, height, dest, dest_x, dest_y) = ( - dest, # type: ignore + dest, # type: ignore[assignment] dest_x, dest_y, src_x, - src_y, # type: ignore + src_y, # type: ignore[assignment] width, height, ) @@ -933,6 +933,7 @@ def __bool__(self) -> bool: return bool(self.console_c != ffi.NULL) def __getstate__(self) -> dict[str, Any]: + """Support serialization via :mod:`pickle`.""" state = self.__dict__.copy() del state["console_c"] state["_console_data"] = { @@ -948,6 +949,7 @@ def __getstate__(self) -> dict[str, Any]: return state def __setstate__(self, state: dict[str, Any]) -> None: + """Support serialization via :mod:`pickle`.""" self._key_color = None if "_tiles" not in state: tiles: NDArray[Any] = np.ndarray((self.height, self.width), dtype=self.DTYPE) @@ -967,12 +969,7 @@ def __setstate__(self, state: dict[str, Any]) -> None: def __repr__(self) -> str: """Return a string representation of this console.""" - return "tcod.console.Console(width=%i, height=%i, order=%r,buffer=\n%r)" % ( - self.width, - self.height, - self._order, - self.rgba, - ) + return f"tcod.console.Console(width={self.width}, height={self.height}, order={self._order!r},buffer=\n{self.rgba!r})" def __str__(self) -> str: """Return a simplified representation of this consoles contents.""" @@ -1197,7 +1194,7 @@ def draw_frame( # noqa: PLR0913 width: int, height: int, title: str = "", - clear: bool = True, + clear: bool = True, # noqa: FBT001, FBT002 fg: tuple[int, int, int] | None = None, bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, From 079dd62cd726d80c62bc82142fe305cb5c7d0e23 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Apr 2025 12:44:22 -0700 Subject: [PATCH 0949/1101] Deprecate fg, bg, bg_blend keywords in Console methods --- CHANGELOG.md | 5 +- tcod/console.py | 112 ++++++++++++++++++++++++++++++++++++------ tests/test_console.py | 7 +-- 3 files changed, 104 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb837e1..e6390394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,12 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Deprecated -- Using `Console.print` without keywords for only the `x`, `y`, and `text` parameters has been deprecated. +- In general the `fg`, `bg`, and `bg_blend` keywords are too hard to keep track of as positional arguments so they must be replaced with keyword arguments instead. +- `Console.print`: deprecated `string`, `fg`, `bg`, and `bg_blend` being given as positional arguments. The `string` parameter has been renamed to `text`. - `Console.print_box` has been replaced by `Console.print`. +- `Console.draw_frame`: deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments. +- `Console.draw_rect`: deprecated `fg`, `bg`, and `bg_blend` being given as positional arguments. ## [17.1.0] - 2025-03-29 diff --git a/tcod/console.py b/tcod/console.py index 013ea84e..a0b08633 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -743,6 +743,7 @@ def vline( self.__deprecate_defaults("draw_rect", bg_blend) lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) + @deprecated("Replace with 'console.draw_frame(...)', use a separate print call for frame titles") def print_frame( # noqa: PLR0913 self, x: int, @@ -976,36 +977,36 @@ def __str__(self) -> str: return "<{}>".format("\n ".join("".join(chr(c) for c in line) for line in self._tiles["ch"])) @overload - @deprecated( - "Switch all parameters to keywords such as 'console.print(x=..., y=..., text=..., width=..., height=..., fg=..., bg=..., bg_blend=..., alignment=...)'" - "\n'string' keyword should be renamed to `text`" - ) def print( self, x: int, y: int, text: str, + *, + width: int | None = None, + height: int | None = None, fg: tuple[int, int, int] | None = None, bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, - *, - string: str = "", ) -> int: ... @overload + @deprecated( + "Replace text, fg, bg, bg_blend, and alignment with keyword arguments." + "\n'string' keyword should be renamed to `text`" + ) def print( self, x: int, y: int, text: str, - *, - width: int | None = None, - height: int | None = None, fg: tuple[int, int, int] | None = None, bg: tuple[int, int, int] | None = None, bg_blend: int = tcod.constants.BKGND_SET, alignment: int = tcod.constants.LEFT, + *, + string: str = "", ) -> int: ... def print( # noqa: PLR0913 @@ -1036,11 +1037,15 @@ def print( # noqa: PLR0913 height: Height in tiles to constrain the printing region. fg: RGB tuple to use as the foreground color, or `None` to leave the foreground unchanged. Tuple values should be 0-255. + Must be given as a keyword argument. bg: RGB tuple to use as the background color, or `None` to leave the foreground unchanged. Tuple values should be 0-255. + Must be given as a keyword argument. bg_blend: Background blend type used by libtcod. Typically starts with `libtcodpy.BKGND_*`. + Must be given as a keyword argument. alignment: One of `libtcodpy.LEFT`, `libtcodpy.CENTER`, or `libtcodpy.RIGHT` + Must be given as a keyword argument. string: Older deprecated name of the `text` parameter. Returns: @@ -1086,13 +1091,16 @@ def print( # noqa: PLR0913 .. versionchanged:: Unreleased + Deprecated giving `string`, `fg`, `bg`, and `bg_blend` as positional arguments. + Added `text` parameter to replace `string`. Added `width` and `height` keyword parameters to bind text to a rectangle and replace other print functions. Right-aligned text with `width=None` now treats the `x` coordinate as a past-the-end index, this will shift the text of older calls to the left by 1 tile. - Now returns the number of lines printed via word wrapping. + Now returns the number of lines printed via word wrapping, + same as previous print functions bound to rectangles. """ if width is not None and width <= 0: return 0 @@ -1187,6 +1195,38 @@ def print_box( # noqa: PLR0913 ) ) + @overload + def draw_frame( + self, + x: int, + y: int, + width: int, + height: int, + *, + clear: bool = True, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘", + ) -> None: ... + + @overload + @deprecated("Parameters clear, fg, bg, bg_blend should be keyword arguments. Remove title parameter") + def draw_frame( + self, + x: int, + y: int, + width: int, + height: int, + title: str = "", + clear: bool = True, # noqa: FBT001, FBT002 + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + *, + decoration: str | tuple[int, int, int, int, int, int, int, int, int] = "┌─┐│ │└─┘", + ) -> None: ... + def draw_frame( # noqa: PLR0913 self, x: int, @@ -1215,13 +1255,16 @@ def draw_frame( # noqa: PLR0913 border with :any:`Console.print_box` using your own style. If `clear` is True than the region inside of the frame will be cleared. + Must be given as a keyword argument. `fg` and `bg` are the foreground and background colors for the frame border. This is a 3-item tuple with (r, g, b) color values from 0 to 255. These parameters can also be set to `None` to leave the colors unchanged. + Must be given as a keyword argument. `bg_blend` is the blend type used by libtcod. + Must be given as a keyword argument. `decoration` is a sequence of glyphs to use for rendering the borders. This a str or tuple of int's with 9 items with the items arranged in @@ -1241,6 +1284,10 @@ def draw_frame( # noqa: PLR0913 .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. + .. versionchanged:: Unreleased + Deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments. + These should be keyword arguments only. + Example:: >>> from tcod import libtcodpy @@ -1305,6 +1352,34 @@ def draw_frame( # noqa: PLR0913 ) ) + @overload + def draw_rect( + self, + x: int, + y: int, + width: int, + height: int, + *, + ch: int, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + ) -> None: ... + + @overload + @deprecated("Parameters cg, fg, bg, bg_blend should be keyword arguments") + def draw_rect( + self, + x: int, + y: int, + width: int, + height: int, + ch: int, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + ) -> None: ... + def draw_rect( # noqa: PLR0913 self, x: int, @@ -1323,15 +1398,16 @@ def draw_rect( # noqa: PLR0913 `width` and `height` determine the size of the rectangle. - `ch` is a Unicode integer. You can use 0 to leave the current - characters unchanged. + `ch` is a Unicode integer. You can use 0 to leave the current characters unchanged. + Must be given as a keyword argument. - `fg` and `bg` are the foreground text color and background tile color - respectfully. This is a 3-item tuple with (r, g, b) color values from - 0 to 255. These parameters can also be set to `None` to leave the - colors unchanged. + `fg` and `bg` are the foreground text color and background tile color respectfully. + This is a 3-item tuple with (r, g, b) color values from 0 to 255. + These parameters can also be set to `None` to leave the colors unchanged. + Must be given as a keyword argument. `bg_blend` is the blend type used by libtcod. + Must be given as a keyword argument. .. versionadded:: 8.5 @@ -1340,6 +1416,10 @@ def draw_rect( # noqa: PLR0913 .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. + + .. versionchanged:: Unreleased + Deprecated `ch`, `fg`, `bg`, and `bg_blend` being given as positional arguments. + These should be keyword arguments only. """ lib.TCOD_console_draw_rect_rgb( self.console_c, diff --git a/tests/test_console.py b/tests/test_console.py index 04bae6cb..4b8f8435 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -81,9 +81,10 @@ def test_console_methods() -> None: console.hline(0, 1, 10) with pytest.deprecated_call(): console.vline(1, 0, 10) - console.print_frame(0, 0, 8, 8, "Frame") - console.blit(0, 0, 0, 0, console, 0, 0) # type: ignore - console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) # type: ignore + with pytest.deprecated_call(): + console.print_frame(0, 0, 8, 8, "Frame") + console.blit(0, 0, 0, 0, console, 0, 0) # type: ignore[arg-type] + console.blit(0, 0, 0, 0, console, 0, 0, key_color=(0, 0, 0)) # type: ignore[arg-type] with pytest.deprecated_call(): console.set_key_color((254, 0, 254)) From 3a04143a6b94b159f5ecb666185ed4355736b773 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 5 Apr 2025 20:24:28 -0700 Subject: [PATCH 0950/1101] Refactor FOV and pathfinding samples --- examples/samples_tcod.py | 241 ++++++++++++++------------------------- 1 file changed, 83 insertions(+), 158 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index e069c2ea..672ce4f6 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -63,6 +63,12 @@ cur_sample = 0 # Current selected sample. frame_times = [time.perf_counter()] frame_length = [0.0] +START_TIME = time.perf_counter() + + +def _get_elapsed_time() -> float: + """Return time passed since the start of the program.""" + return time.perf_counter() - START_TIME class Sample(tcod.event.EventDispatch[None]): @@ -207,13 +213,13 @@ def __init__(self) -> None: ) def on_enter(self) -> None: - self.counter = time.perf_counter() + self.counter = _get_elapsed_time() # get a "screenshot" of the current sample screen sample_console.blit(dest=self.screenshot) def on_draw(self) -> None: - if time.perf_counter() - self.counter >= 1: - self.counter = time.perf_counter() + if _get_elapsed_time() - self.counter >= 1: + self.counter = _get_elapsed_time() self.x += self.x_dir self.y += self.y_dir if self.x == sample_console.width / 2 + 5: @@ -396,8 +402,8 @@ def get_noise(self) -> tcod.noise.Noise: ) def on_draw(self) -> None: - self.dx = time.perf_counter() * 0.25 - self.dy = time.perf_counter() * 0.25 + self.dx = _get_elapsed_time() * 0.25 + self.dy = _get_elapsed_time() * 0.25 for y in range(2 * sample_console.height): for x in range(2 * sample_console.width): f = [ @@ -518,7 +524,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: "##############################################", ) -SAMPLE_MAP: NDArray[Any] = np.array([list(line) for line in SAMPLE_MAP_]).transpose() +SAMPLE_MAP: NDArray[Any] = np.array([[ord(c) for c in line] for line in SAMPLE_MAP_]).transpose() FOV_ALGO_NAMES = ( "BASIC ", @@ -555,17 +561,17 @@ def __init__(self) -> None: map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) self.walkable: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") - self.walkable[:] = SAMPLE_MAP[:] == " " + self.walkable[:] = SAMPLE_MAP[:] == ord(" ") self.transparent: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") - self.transparent[:] = self.walkable[:] | (SAMPLE_MAP == "=") + self.transparent[:] = self.walkable[:] | (SAMPLE_MAP[:] == ord("=")) # Lit background colors for the map. self.light_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, LIGHT_GROUND, dtype="3B") - self.light_map_bg[SAMPLE_MAP[:] == "#"] = LIGHT_WALL + self.light_map_bg[SAMPLE_MAP[:] == ord("#")] = LIGHT_WALL # Dark background colors for the map. self.dark_map_bg: NDArray[np.uint8] = np.full(SAMPLE_MAP.shape, DARK_GROUND, dtype="3B") - self.dark_map_bg[SAMPLE_MAP[:] == "#"] = DARK_WALL + self.dark_map_bg[SAMPLE_MAP[:] == ord("#")] = DARK_WALL def draw_ui(self) -> None: sample_console.print( @@ -586,8 +592,8 @@ def on_draw(self) -> None: self.draw_ui() sample_console.print(self.player_x, self.player_y, "@") # Draw windows. - sample_console.rgb["ch"][SAMPLE_MAP == "="] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL - sample_console.rgb["fg"][SAMPLE_MAP == "="] = BLACK + sample_console.rgb["ch"][SAMPLE_MAP[:] == ord("=")] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL + sample_console.rgb["fg"][SAMPLE_MAP[:] == ord("=")] = BLACK # Get a 2D boolean array of visible cells. fov = tcod.map.compute_fov( @@ -600,7 +606,7 @@ def on_draw(self) -> None: if self.torch: # Derive the touch from noise based on the current time. - torch_t = time.perf_counter() * 5 + torch_t = _get_elapsed_time() * 5 # Randomize the light position between -1.5 and 1.5 torch_x = self.player_x + self.noise.get_point(torch_t) * 1.5 torch_y = self.player_y + self.noise.get_point(torch_t + 11) * 1.5 @@ -632,7 +638,11 @@ def on_draw(self) -> None: # Linear interpolation between colors. sample_console.rgb["bg"] = dark_bg + (light_bg - dark_bg) * light[..., np.newaxis] else: - sample_console.bg[...] = np.where(fov[:, :, np.newaxis], self.light_map_bg, self.dark_map_bg) + sample_console.bg[...] = np.select( + condlist=[fov[:, :, np.newaxis]], + choicelist=[self.light_map_bg], + default=self.dark_map_bg, + ) def ev_keydown(self, event: tcod.event.KeyDown) -> None: MOVE_KEYS = { # noqa: N806 @@ -665,174 +675,89 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class PathfindingSample(Sample): def __init__(self) -> None: + """Initialize this sample.""" self.name = "Path finding" - self.px = 20 - self.py = 10 - self.dx = 24 - self.dy = 1 - self.dijkstra_dist = 0.0 + self.player_x = 20 + self.player_y = 10 + self.dest_x = 24 + self.dest_y = 1 self.using_astar = True - self.recalculate = False self.busy = 0.0 - self.old_char = " " + self.cost = SAMPLE_MAP.T[:] == ord(" ") + self.graph = tcod.path.SimpleGraph(cost=self.cost, cardinal=70, diagonal=99) + self.pathfinder = tcod.path.Pathfinder(graph=self.graph) - self.map = tcod.map.Map(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) - for y in range(SAMPLE_SCREEN_HEIGHT): - for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[x, y] == " ": - # ground - self.map.walkable[y, x] = True - self.map.transparent[y, x] = True - elif SAMPLE_MAP[x, y] == "=": - # window - self.map.walkable[y, x] = False - self.map.transparent[y, x] = True - self.path = tcod.path.AStar(self.map) - self.dijkstra = tcod.path.Dijkstra(self.map) + self.background_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + + # draw the dungeon + self.background_console.rgb["fg"] = BLACK + self.background_console.rgb["bg"] = DARK_GROUND + self.background_console.rgb["bg"][SAMPLE_MAP.T[:] == ord("#")] = DARK_WALL + self.background_console.rgb["ch"][SAMPLE_MAP.T[:] == ord("=")] = ord("═") def on_enter(self) -> None: - # we draw the foreground only the first time. - # during the player movement, only the @ is redrawn. - # the rest impacts only the background color - # draw the help text & player @ - sample_console.clear() - sample_console.ch[self.dx, self.dy] = ord("+") - sample_console.fg[self.dx, self.dy] = WHITE - sample_console.ch[self.px, self.py] = ord("@") - sample_console.fg[self.px, self.py] = WHITE - sample_console.print( - 1, - 1, - "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", - fg=WHITE, - bg=None, - ) - sample_console.print(1, 4, "Using : A*", fg=WHITE, bg=None) - # draw windows - for y in range(SAMPLE_SCREEN_HEIGHT): - for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[x, y] == "=": - libtcodpy.console_put_char(sample_console, x, y, libtcodpy.CHAR_DHLINE, libtcodpy.BKGND_NONE) - self.recalculate = True + """Do nothing.""" def on_draw(self) -> None: - if self.recalculate: - if self.using_astar: - libtcodpy.path_compute(self.path, self.px, self.py, self.dx, self.dy) - else: - self.dijkstra_dist = 0.0 - # compute dijkstra grid (distance from px,py) - libtcodpy.dijkstra_compute(self.dijkstra, self.px, self.py) - # get the maximum distance (needed for rendering) - for y in range(SAMPLE_SCREEN_HEIGHT): - for x in range(SAMPLE_SCREEN_WIDTH): - d = libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) - self.dijkstra_dist = max(d, self.dijkstra_dist) - # compute path from px,py to dx,dy - libtcodpy.dijkstra_path_set(self.dijkstra, self.dx, self.dy) - self.recalculate = False - self.busy = 0.2 + """Recompute and render pathfinding.""" + self.pathfinder = tcod.path.Pathfinder(graph=self.graph) + # self.pathfinder.clear() # Known issues, needs fixing # noqa: ERA001 + self.pathfinder.add_root((self.player_y, self.player_x)) + # draw the dungeon - for y in range(SAMPLE_SCREEN_HEIGHT): - for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[x, y] == "#": - libtcodpy.console_set_char_background(sample_console, x, y, DARK_WALL, libtcodpy.BKGND_SET) - else: - libtcodpy.console_set_char_background(sample_console, x, y, DARK_GROUND, libtcodpy.BKGND_SET) + self.background_console.blit(dest=sample_console) + + sample_console.print(self.dest_x, self.dest_y, "+", fg=WHITE) + sample_console.print(self.player_x, self.player_y, "@", fg=WHITE) + sample_console.print(1, 1, "IJKL / mouse :\nmove destination\nTAB : A*/dijkstra", fg=WHITE, bg=None) + sample_console.print(1, 4, "Using : A*", fg=WHITE, bg=None) + + if not self.using_astar: + self.pathfinder.resolve(goal=None) + reachable = self.pathfinder.distance != np.iinfo(self.pathfinder.distance.dtype).max + + # draw distance from player + dijkstra_max_dist = float(self.pathfinder.distance[reachable].max()) + np.array(self.pathfinder.distance, copy=True, dtype=np.float32) + interpolate = self.pathfinder.distance[reachable] * 0.9 / dijkstra_max_dist + color_delta = (np.array(DARK_GROUND) - np.array(LIGHT_GROUND)).astype(np.float32) + sample_console.rgb.T["bg"][reachable] = np.array(LIGHT_GROUND) + interpolate[:, np.newaxis] * color_delta + # draw the path - if self.using_astar: - for i in range(libtcodpy.path_size(self.path)): - x, y = libtcodpy.path_get(self.path, i) - libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, libtcodpy.BKGND_SET) - else: - for y in range(SAMPLE_SCREEN_HEIGHT): - for x in range(SAMPLE_SCREEN_WIDTH): - if SAMPLE_MAP[x, y] != "#": - libtcodpy.console_set_char_background( - sample_console, - x, - y, - libtcodpy.color_lerp( # type: ignore[arg-type] - LIGHT_GROUND, - DARK_GROUND, - 0.9 * libtcodpy.dijkstra_get_distance(self.dijkstra, x, y) / self.dijkstra_dist, - ), - libtcodpy.BKGND_SET, - ) - for i in range(libtcodpy.dijkstra_size(self.dijkstra)): - x, y = libtcodpy.dijkstra_get(self.dijkstra, i) - libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_GROUND, libtcodpy.BKGND_SET) + path = self.pathfinder.path_to((self.dest_y, self.dest_x))[1:, ::-1] + sample_console.rgb["bg"][tuple(path.T)] = LIGHT_GROUND # move the creature self.busy -= frame_length[-1] if self.busy <= 0.0: self.busy = 0.2 - if self.using_astar: - if not libtcodpy.path_is_empty(self.path): - libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) - self.px, self.py = libtcodpy.path_walk(self.path, True) # type: ignore[assignment] - libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) - elif not libtcodpy.dijkstra_is_empty(self.dijkstra): - libtcodpy.console_put_char(sample_console, self.px, self.py, " ", libtcodpy.BKGND_NONE) - self.px, self.py = libtcodpy.dijkstra_path_walk(self.dijkstra) # type: ignore[assignment] - libtcodpy.console_put_char(sample_console, self.px, self.py, "@", libtcodpy.BKGND_NONE) - self.recalculate = True + if len(path): + self.player_x = int(path.item(0, 0)) + self.player_y = int(path.item(0, 1)) def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == tcod.event.KeySym.i and self.dy > 0: - # destination move north - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) - self.dy -= 1 - self.old_char = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) - if SAMPLE_MAP[self.dx, self.dy] == " ": - self.recalculate = True - elif event.sym == tcod.event.KeySym.k and self.dy < SAMPLE_SCREEN_HEIGHT - 1: - # destination move south - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) - self.dy += 1 - self.old_char = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) - if SAMPLE_MAP[self.dx, self.dy] == " ": - self.recalculate = True - elif event.sym == tcod.event.KeySym.j and self.dx > 0: - # destination move west - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) - self.dx -= 1 - self.old_char = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) - if SAMPLE_MAP[self.dx, self.dy] == " ": - self.recalculate = True - elif event.sym == tcod.event.KeySym.l and self.dx < SAMPLE_SCREEN_WIDTH - 1: - # destination move east - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) - self.dx += 1 - self.old_char = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) - if SAMPLE_MAP[self.dx, self.dy] == " ": - self.recalculate = True + """Handle movement and UI.""" + if event.sym == tcod.event.KeySym.i and self.dest_y > 0: # destination move north + self.dest_y -= 1 + elif event.sym == tcod.event.KeySym.k and self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south + self.dest_y += 1 + elif event.sym == tcod.event.KeySym.j and self.dest_x > 0: # destination move west + self.dest_x -= 1 + elif event.sym == tcod.event.KeySym.l and self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east + self.dest_x += 1 elif event.sym == tcod.event.KeySym.TAB: self.using_astar = not self.using_astar - if self.using_astar: - libtcodpy.console_print(sample_console, 1, 4, "Using : A* ") - else: - libtcodpy.console_print(sample_console, 1, 4, "Using : Dijkstra") - self.recalculate = True else: super().ev_keydown(event) def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: + """Move destination via mouseover.""" mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y - if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT and (self.dx != mx or self.dy != my): - libtcodpy.console_put_char(sample_console, self.dx, self.dy, self.old_char, libtcodpy.BKGND_NONE) - self.dx = mx - self.dy = my - self.old_char = sample_console.ch[self.dx, self.dy] - libtcodpy.console_put_char(sample_console, self.dx, self.dy, "+", libtcodpy.BKGND_NONE) - if SAMPLE_MAP[self.dx, self.dy] == " ": - self.recalculate = True + if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT: + self.dest_x = mx + self.dest_y = my ############################################# @@ -1044,7 +969,7 @@ def on_draw(self) -> None: y = sample_console.height / 2 scalex = 0.2 + 1.8 * (1.0 + math.cos(time.time() / 2)) / 2.0 scaley = scalex - angle = time.perf_counter() + angle = _get_elapsed_time() if int(time.time()) % 2: # split the color channels of circle.png # the red channel @@ -1529,7 +1454,7 @@ def draw_stats() -> None: root_console.print( root_console.width, 47, - f"elapsed : {int(time.perf_counter() * 1000):8d} ms {time.perf_counter():5.2f}s", + f"elapsed : {int(_get_elapsed_time() * 1000):8d} ms {_get_elapsed_time():5.2f}s", fg=GREY, alignment=libtcodpy.RIGHT, ) From c7b6bc55d7395f17fe78fdfc645dcb0695adfafc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Apr 2025 16:29:11 -0700 Subject: [PATCH 0951/1101] Clean up warnings Specify specific warnings for Mypy ignores Narrow type hints Move image internals to image.py to avoid cyclic imports --- tcod/__init__.py | 2 +- tcod/_internal.py | 55 ++++++-------------------------------- tcod/bsp.py | 17 +++--------- tcod/cffi.py | 4 +-- tcod/console.py | 3 ++- tcod/context.py | 2 +- tcod/event.py | 2 +- tcod/image.py | 52 +++++++++++++++++++++++++++--------- tcod/libtcodpy.py | 24 +++++++---------- tcod/noise.py | 5 ++-- tcod/path.py | 61 ++++++++++++++++++++++++------------------- tcod/sdl/_internal.py | 4 +-- tcod/sdl/audio.py | 2 +- tcod/tcod.py | 2 +- tcod/tileset.py | 7 ++--- 15 files changed, 112 insertions(+), 130 deletions(-) diff --git a/tcod/__init__.py b/tcod/__init__.py index b155be01..2db18f54 100644 --- a/tcod/__init__.py +++ b/tcod/__init__.py @@ -13,7 +13,7 @@ __path__ = extend_path(__path__, __name__) -from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset +from tcod import bsp, color, console, context, event, image, los, map, noise, path, random, tileset # noqa: A004 from tcod.cffi import __sdl_version__, ffi, lib from tcod.tcod import __getattr__ # noqa: F401 from tcod.version import __version__ diff --git a/tcod/_internal.py b/tcod/_internal.py index 01136c5e..049dd37d 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -7,7 +7,6 @@ import warnings from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar -import numpy as np from typing_extensions import Literal, LiteralString, deprecated from tcod.cffi import ffi, lib @@ -16,9 +15,6 @@ from pathlib import Path from types import TracebackType - from numpy.typing import ArrayLike, NDArray - - import tcod.image FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) @@ -26,7 +22,11 @@ def _deprecate_passthrough( - message: str, /, *, category: type[Warning] = DeprecationWarning, stacklevel: int = 0 + message: str, # noqa: ARG001 + /, + *, + category: type[Warning] = DeprecationWarning, # noqa: ARG001 + stacklevel: int = 0, # noqa: ARG001 ) -> Callable[[F], F]: """Return a decorator which skips wrapping a warning onto functions. This is used for non-debug runs.""" @@ -51,7 +51,7 @@ def pending_deprecate( def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]: """Verify and return a Numpy order string.""" - order = order.upper() # type: ignore + order = order.upper() # type: ignore[assignment] if order not in ("C", "F"): msg = f"order must be 'C' or 'F', not {order!r}" raise TypeError(msg) @@ -91,7 +91,7 @@ def _check_warn(error: int, stacklevel: int = 2) -> int: def _unpack_char_p(char_p: Any) -> str: # noqa: ANN401 if char_p == ffi.NULL: return "" - return ffi.string(char_p).decode() # type: ignore + return str(ffi.string(char_p), encoding="utf-8") def _int(int_or_str: SupportsInt | str | bytes) -> int: @@ -171,7 +171,7 @@ def __enter__(self) -> Callable[[Any], None]: return self.propagate def __exit__( - self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + self, _type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None ) -> None: """If we're holding on to an exception, raise it now. @@ -232,42 +232,3 @@ def _console(console: Any) -> Any: # noqa: ANN401 stacklevel=3, ) return ffi.NULL - - -class TempImage: - """An Image-like container for NumPy arrays.""" - - def __init__(self, array: ArrayLike) -> None: - """Initialize an image from the given array. May copy or reference the array.""" - self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8) - height, width, depth = self._array.shape - if depth != 3: # noqa: PLR2004 - msg = f"Array must have RGB channels. Shape is: {self._array.shape!r}" - raise TypeError(msg) - self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) - self._mipmaps = ffi.new( - "struct TCOD_mipmap_*", - { - "width": width, - "height": height, - "fwidth": width, - "fheight": height, - "buf": self._buffer, - "dirty": True, - }, - ) - self.image_c = ffi.new( - "TCOD_Image*", - { - "nb_mipmaps": 1, - "mipmaps": self._mipmaps, - "has_key_color": False, - }, - ) - - -def _as_image(image: ArrayLike | tcod.image.Image) -> TempImage | tcod.image.Image: - """Convert this input into an Image-like object.""" - if hasattr(image, "image_c"): - return image - return TempImage(image) diff --git a/tcod/bsp.py b/tcod/bsp.py index 0f8caeaa..a2959b16 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -104,20 +104,9 @@ def __repr__(self) -> str: """Provide a useful readout when printed.""" status = "leaf" if self.children: - status = "split at position=%i,horizontal=%r" % ( - self.position, - self.horizontal, - ) - - return "<%s(x=%i,y=%i,width=%i,height=%i) level=%i %s>" % ( - self.__class__.__name__, - self.x, - self.y, - self.width, - self.height, - self.level, - status, - ) + status = f"split at position={self.position},horizontal={self.horizontal!r}" + + return f"<{self.__class__.__name__}(x={self.x},y={self.y},width={self.width},height={self.height}) level={self.level} {status}>" def _unpack_bsp_tree(self, cdata: Any) -> None: # noqa: ANN401 self.x = cdata.x diff --git a/tcod/cffi.py b/tcod/cffi.py index eb94d5e1..d1c93025 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -64,8 +64,8 @@ def get_sdl_version() -> str: __sdl_version__ = get_sdl_version() -@ffi.def_extern() # type: ignore -def _libtcod_log_watcher(message: Any, userdata: None) -> None: # noqa: ANN401 +@ffi.def_extern() # type: ignore[misc] +def _libtcod_log_watcher(message: Any, _userdata: None) -> None: # noqa: ANN401 text = str(ffi.string(message.message), encoding="utf-8") source = str(ffi.string(message.source), encoding="utf-8") level = int(message.level) diff --git a/tcod/console.py b/tcod/console.py index a0b08633..b1c7c24e 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -16,6 +16,7 @@ import tcod._internal import tcod.constants +import tcod.image from tcod._internal import _check, _path_encode from tcod.cffi import ffi, lib @@ -1444,7 +1445,7 @@ def draw_semigraphics(self, pixels: ArrayLike | tcod.image.Image, x: int = 0, y: .. versionadded:: 11.4 """ - image = tcod._internal._as_image(pixels) + image = tcod.image._as_image(pixels) lib.TCOD_image_blit_2x(image.image_c, self.console_c, x, y, 0, 0, -1, -1) diff --git a/tcod/context.py b/tcod/context.py index 826307dc..386b7324 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -447,7 +447,7 @@ def __reduce__(self) -> NoReturn: raise pickle.PicklingError(msg) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_cli_output(catch_reference: Any, output: Any) -> None: # noqa: ANN401 """Callback for the libtcod context CLI. diff --git a/tcod/event.py b/tcod/event.py index 80c4acce..85bbead8 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1548,7 +1548,7 @@ def get_mouse_state() -> MouseState: return MouseState((xy[0], xy[1]), (int(tile[0]), int(tile[1])), buttons) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: callback: Callable[[Event], None] = ffi.from_handle(userdata) callback(_parse_event(sdl_event)) diff --git a/tcod/image.py b/tcod/image.py index 219916cf..6b4a1083 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -145,7 +145,7 @@ def get_alpha(self, x: int, y: int) -> int: int: The alpha value of the pixel. With 0 being fully transparent and 255 being fully opaque. """ - return lib.TCOD_image_get_alpha(self.image_c, x, y) # type: ignore + return int(lib.TCOD_image_get_alpha(self.image_c, x, y)) def refresh_console(self, console: tcod.console.Console) -> None: """Update an Image created with :any:`libtcodpy.image_from_console`. @@ -351,17 +351,6 @@ def __array_interface__(self) -> dict[str, Any]: } -def _get_format_name(format: int) -> str: - """Return the SDL_PIXELFORMAT_X name for this format, if possible.""" - for attr in dir(lib): - if not attr.startswith("SDL_PIXELFORMAT"): - continue - if getattr(lib, attr) != format: - continue - return attr - return str(format) - - @deprecated( "This function may be removed in the future." " It's recommended to load images with a more complete image library such as python-Pillow or python-imageio.", @@ -388,3 +377,42 @@ def load(filename: str | PathLike[str]) -> NDArray[np.uint8]: axis=2, ) return array + + +class _TempImage: + """An Image-like container for NumPy arrays.""" + + def __init__(self, array: ArrayLike) -> None: + """Initialize an image from the given array. May copy or reference the array.""" + self._array: NDArray[np.uint8] = np.ascontiguousarray(array, dtype=np.uint8) + height, width, depth = self._array.shape + if depth != 3: # noqa: PLR2004 + msg = f"Array must have RGB channels. Shape is: {self._array.shape!r}" + raise TypeError(msg) + self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) + self._mipmaps = ffi.new( + "struct TCOD_mipmap_*", + { + "width": width, + "height": height, + "fwidth": width, + "fheight": height, + "buf": self._buffer, + "dirty": True, + }, + ) + self.image_c = ffi.new( + "TCOD_Image*", + { + "nb_mipmaps": 1, + "mipmaps": self._mipmaps, + "has_key_color": False, + }, + ) + + +def _as_image(image: ArrayLike | Image | _TempImage) -> _TempImage | Image: + """Convert this input into an Image-like object.""" + if isinstance(image, (Image, _TempImage)): + return image + return _TempImage(image) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 52cba83e..0018745d 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -238,8 +238,8 @@ def set( def blit( self, dest: tcod.console.Console, - fill_fore: bool = True, - fill_back: bool = True, + fill_fore: bool = True, # noqa: FBT001, FBT002 + fill_back: bool = True, # noqa: FBT001, FBT002 ) -> None: """Use libtcod's "fill" functions to write the buffer to a console. @@ -313,12 +313,7 @@ def nb_dices(self, value: int) -> None: def __str__(self) -> str: add = f"+({self.addsub})" if self.addsub != 0 else "" - return "%id%ix%s%s" % ( - self.nb_dices, - self.nb_faces, - self.multiplier, - add, - ) + return f"{self.nb_dices}d{self.nb_faces}x{self.multiplier}{add}" def __repr__(self) -> str: return f"{self.__class__.__name__}(nb_dices={self.nb_dices!r},nb_faces={self.nb_faces!r},multiplier={self.multiplier!r},addsub={self.addsub!r})" @@ -3584,7 +3579,8 @@ def _unpack_union(type_: int, union: Any) -> Any: # noqa: PLR0911 return Dice(union.dice) if type_ & lib.TCOD_TYPE_LIST: return _convert_TCODList(union.list, type_ & 0xFF) - raise RuntimeError("Unknown libtcod type: %i" % type_) + msg = f"Unknown libtcod type: {type_}" + raise RuntimeError(msg) def _convert_TCODList(c_list: Any, type_: int) -> Any: @@ -3607,27 +3603,27 @@ def parser_new_struct(parser: Any, name: str) -> Any: _parser_listener: Any = None -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_parser_new_struct(struct: Any, name: str) -> Any: return _parser_listener.new_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_parser_new_flag(name: str) -> Any: return _parser_listener.new_flag(_unpack_char_p(name)) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: return _parser_listener.new_property(_unpack_char_p(propname), type, _unpack_union(type, value)) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_parser_end_struct(struct: Any, name: Any) -> Any: return _parser_listener.end_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_parser_error(msg: Any) -> None: _parser_listener.error(_unpack_char_p(msg)) diff --git a/tcod/noise.py b/tcod/noise.py index 82920a1c..da14b3f2 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -236,9 +236,8 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: if not isinstance(indexes, tuple): indexes = (indexes,) if len(indexes) > self.dimensions: - raise IndexError( - "This noise generator has %i dimensions, but was indexed with %i." % (self.dimensions, len(indexes)) - ) + msg = f"This noise generator has {self.dimensions} dimensions, but was indexed with {len(indexes)}." + raise IndexError(msg) indexes = list(np.broadcast_arrays(*indexes)) c_input = [ffi.NULL, ffi.NULL, ffi.NULL, ffi.NULL] for i, index in enumerate(indexes): diff --git a/tcod/path.py b/tcod/path.py index 8ce9aaa2..a58cb218 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -26,6 +26,7 @@ import numpy as np from typing_extensions import Literal, Self +import tcod.map from tcod._internal import _check from tcod.cffi import ffi, lib @@ -33,29 +34,29 @@ from numpy.typing import ArrayLike, DTypeLike, NDArray -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 - """Libtcodpy style callback, needs to preserve the old userData issue.""" - func, userData = ffi.from_handle(handle) - return func(x1, y1, x2, y2, userData) # type: ignore + """Libtcodpy style callback, needs to preserve the old userdata issue.""" + func, userdata = ffi.from_handle(handle) + return func(x1, y1, x2, y2, userdata) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """Does less and should run faster, just calls the handle function.""" - return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore + return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function dest comes first to match up with a dest only call.""" - return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore + return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore -def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 +@ffi.def_extern() # type: ignore[misc] +def _pycall_path_dest_only(_x1: int, _y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function which samples the dest coordinate only.""" - return ffi.from_handle(handle)(x2, y2) # type: ignore + return ffi.from_handle(handle)(x2, y2) # type: ignore[no-any-return] def _get_path_cost_func( @@ -63,8 +64,8 @@ def _get_path_cost_func( ) -> Callable[[int, int, int, int, Any], float]: """Return a properly cast PathCostArray callback.""" if not ffi: - return lambda x1, y1, x2, y2, _: 0 - return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) # type: ignore + return lambda _x1, _y1, _x2, _y2, _: 0 + return ffi.cast("TCOD_path_func_t", ffi.addressof(lib, name)) # type: ignore[no-any-return] class _EdgeCostFunc: @@ -113,7 +114,7 @@ def __init__( super().__init__(callback, shape) -class NodeCostArray(np.ndarray): # type: ignore +class NodeCostArray(np.ndarray): # type: ignore[type-arg] """Calculate cost from a numpy array of nodes. `array` is a NumPy array holding the path-cost of each node. @@ -157,13 +158,13 @@ def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: class _PathFinder: """A class sharing methods used by AStar and Dijkstra.""" - def __init__(self, cost: Any, diagonal: float = 1.41) -> None: + def __init__(self, cost: tcod.map.Map | ArrayLike | _EdgeCostFunc, diagonal: float = 1.41) -> None: self.cost = cost self.diagonal = diagonal self._path_c: Any = None self._callback = self._userdata = None - if hasattr(self.cost, "map_c"): + if isinstance(self.cost, tcod.map.Map): self.shape = self.cost.width, self.cost.height self._path_c = ffi.gc( self._path_new_using_map(self.cost.map_c, diagonal), @@ -171,7 +172,7 @@ def __init__(self, cost: Any, diagonal: float = 1.41) -> None: ) return - if not hasattr(self.cost, "get_tcod_path_ffi"): + if not isinstance(self.cost, _EdgeCostFunc): assert not callable(self.cost), ( "Any callback alone is missing shape information. Wrap your callback in tcod.path.EdgeCostCallback" ) @@ -206,7 +207,7 @@ def __getstate__(self) -> dict[str, Any]: def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) - self.__init__(self.cost, self.diagonal) # type: ignore + _PathFinder.__init__(self, self.cost, self.diagonal) _path_new_using_map = lib.TCOD_path_new_using_map _path_new_using_function = lib.TCOD_path_new_using_function @@ -239,7 +240,7 @@ def get_path(self, start_x: int, start_y: int, goal_x: int, goal_y: int) -> list path = [] x = ffi.new("int[2]") y = x + 1 - while lib.TCOD_path_walk(self._path_c, x, y, False): + while lib.TCOD_path_walk(self._path_c, x, y, False): # noqa: FBT003 path.append((x[0], y[0])) return path @@ -323,7 +324,7 @@ def _export_dict(array: NDArray[Any]) -> dict[str, Any]: } -def _export(array: NDArray[Any]) -> Any: # noqa: ANN401 +def _export(array: NDArray[np.number]) -> Any: # noqa: ANN401 """Convert a NumPy array into a cffi object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -332,7 +333,8 @@ def _compile_cost_edges(edge_map: ArrayLike) -> tuple[NDArray[np.intc], int]: """Return an edge_cost array using an integer map.""" edge_map = np.array(edge_map, copy=True) if edge_map.ndim != 2: # noqa: PLR2004 - raise ValueError("edge_map must be 2 dimensional. (Got %i)" % edge_map.ndim) + msg = f"edge_map must be 2 dimensional. (Got {edge_map.ndim})" + raise ValueError(msg) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 edge_map[edge_center] = 0 edge_map[edge_map < 0] = 0 @@ -353,7 +355,7 @@ def dijkstra2d( # noqa: PLR0913 diagonal: int | None = None, *, edge_map: ArrayLike | None = None, - out: np.ndarray | None = ..., # type: ignore + out: NDArray[np.number] | None = ..., # type: ignore[assignment, unused-ignore] ) -> NDArray[Any]: """Return the computed distance of all nodes on a 2D Dijkstra grid. @@ -750,7 +752,8 @@ def add_edge( edge_dir = tuple(edge_dir) cost = np.asarray(cost) if len(edge_dir) != self._ndim: - raise TypeError("edge_dir must have exactly %i items, got %r" % (self._ndim, edge_dir)) + msg = f"edge_dir must have exactly {self._ndim} items, got {edge_dir!r}" + raise TypeError(msg) if edge_cost <= 0: msg = f"edge_cost must be greater than zero, got {edge_cost!r}" raise ValueError(msg) @@ -884,7 +887,8 @@ def add_edges( if edge_map.ndim < self._ndim: edge_map = np.asarray(edge_map[(np.newaxis,) * (self._ndim - edge_map.ndim)]) if edge_map.ndim != self._ndim: - raise TypeError("edge_map must must match graph dimensions (%i). (Got %i)" % (self.ndim, edge_map.ndim)) + msg = f"edge_map must must match graph dimensions ({self.ndim}). (Got {edge_map.ndim})" + raise TypeError(msg) if self._order == "F": # edge_map needs to be converted into C. # The other parameters are converted by the add_edge method. @@ -1186,7 +1190,8 @@ def add_root(self, index: tuple[int, ...], value: int = 0) -> None: if self._order == "F": # Convert to ij indexing order. index = index[::-1] if len(index) != self._distance.ndim: - raise TypeError("Index must be %i items, got %r" % (self._distance.ndim, index)) + msg = f"Index must be {self._distance.ndim} items, got {index!r}" + raise TypeError(msg) self._distance[index] = value self._update_heuristic(None) lib.TCOD_frontier_push(self._frontier_p, index, value, value) @@ -1273,7 +1278,8 @@ def resolve(self, goal: tuple[int, ...] | None = None) -> None: if goal is not None: goal = tuple(goal) # Check for bad input. if len(goal) != self._distance.ndim: - raise TypeError("Goal must be %i items, got %r" % (self._distance.ndim, goal)) + msg = f"Goal must be {self._distance.ndim} items, got {goal!r}" + raise TypeError(msg) if self._order == "F": # Goal is now ij indexed for the rest of this function. goal = goal[::-1] @@ -1320,7 +1326,8 @@ def path_from(self, index: tuple[int, ...]) -> NDArray[Any]: """ index = tuple(index) # Check for bad input. if len(index) != self._graph._ndim: - raise TypeError("Index must be %i items, got %r" % (self._distance.ndim, index)) + msg = f"Index must be {self._distance.ndim} items, got {index!r}" + raise TypeError(msg) self.resolve(index) if self._order == "F": # Convert to ij indexing order. index = index[::-1] diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 8904e318..e7f5aa8c 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -64,7 +64,7 @@ def __exit__( return True -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 """Pass logs sent by SDL to Python's logging system.""" message = str(ffi.string(message_p), encoding="utf-8") @@ -118,7 +118,7 @@ def replacement(*_args: object, **_kwargs: object) -> NoReturn: msg = f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}" raise RuntimeError(msg) - return lambda x: replacement # type: ignore[return-value] + return lambda _: replacement # type: ignore[return-value] lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index a5da2f5b..8627bdeb 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -564,7 +564,7 @@ class _AudioCallbackUserdata: device: AudioDevice -@ffi.def_extern() # type: ignore +@ffi.def_extern() # type: ignore[misc] def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: # noqa: ANN401 """Handle audio device callbacks.""" data: _AudioCallbackUserdata = ffi.from_handle(userdata) diff --git a/tcod/tcod.py b/tcod/tcod.py index cf12dceb..39240194 100644 --- a/tcod/tcod.py +++ b/tcod/tcod.py @@ -22,7 +22,7 @@ image, libtcodpy, los, - map, + map, # noqa: A004 noise, path, random, diff --git a/tcod/tileset.py b/tcod/tileset.py index 5f5a7511..56213fe7 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -229,10 +229,11 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: """ tile_i = x + y * self._tileset_p.virtual_columns if not (0 <= tile_i < self._tileset_p.tiles_count): - raise IndexError( - "Tile %i is non-existent and can't be assigned." - " (Tileset has %i tiles.)" % (tile_i, self._tileset_p.tiles_count) + msg = ( + f"Tile {tile_i} is non-existent and can't be assigned." + f" (Tileset has {self._tileset_p.tiles_count} tiles.)" ) + raise IndexError(msg) _check( lib.TCOD_tileset_assign_tile( self._tileset_p, From b4545249dfa1948b52b2c751d61eb7d179041f5b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 6 Apr 2025 18:33:58 -0700 Subject: [PATCH 0952/1101] Deprecate EventDispatch class This class has faded into irrelevance due to modern Python language features. I need to discourage its usage in new programs. --- CHANGELOG.md | 3 +++ tcod/event.py | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6390394..3678f4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `Console.print_box` has been replaced by `Console.print`. - `Console.draw_frame`: deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments. - `Console.draw_rect`: deprecated `fg`, `bg`, and `bg_blend` being given as positional arguments. +- The `EventDispatch` class is now deprecated. + This class was made before Python supported protocols and structural pattern matching, + now the class serves little purpose and its usage can create a minor technical burden. ## [17.1.0] - 2025-03-29 diff --git a/tcod/event.py b/tcod/event.py index 85bbead8..69bc8575 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -87,7 +87,7 @@ from typing import TYPE_CHECKING, Any, Callable, Final, Generic, Iterator, Mapping, NamedTuple, TypeVar import numpy as np -from typing_extensions import Literal +from typing_extensions import Literal, deprecated import tcod.event import tcod.event_constants @@ -1238,6 +1238,10 @@ def wait(timeout: float | None = None) -> Iterator[Any]: return get() +@deprecated( + "Event dispatch should be handled via a single custom method in a Protocol instead of this class.", + category=DeprecationWarning, +) class EventDispatch(Generic[T]): '''Dispatches events to methods depending on the events type attribute. @@ -1248,6 +1252,10 @@ class EventDispatch(Generic[T]): This is now a generic class. The type hints at the return value of :any:`dispatch` and the `ev_*` methods. + .. deprecated:: Unreleased + Event dispatch should be handled via a single custom method in a Protocol instead of this class. + Note that events can and should be handled using Python's `match` statement. + Example:: import tcod From c176a6f4d0f0e20d36b0b029c8224d30ef8b8c5d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 7 Apr 2025 13:34:59 -0700 Subject: [PATCH 0953/1101] Update pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8960f446..8f044c36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.11.4 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From de5d8456a05c49a2d78450611661329aa47d90af Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 7 Apr 2025 22:51:32 -0700 Subject: [PATCH 0954/1101] Prepare 18.0.0 release. --- CHANGELOG.md | 2 ++ tcod/console.py | 6 +++--- tcod/event.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3678f4b8..3dba327d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [18.0.0] - 2025-04-08 + ### Changed - `Console.print` now accepts `height` and `width` keywords and has renamed `string` to `text`. diff --git a/tcod/console.py b/tcod/console.py index b1c7c24e..bec669a3 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1090,7 +1090,7 @@ def print( # noqa: PLR0913 `x` and `y` are now always used as an absolute position for negative values. - .. versionchanged:: Unreleased + .. versionchanged:: 18.0 Deprecated giving `string`, `fg`, `bg`, and `bg_blend` as positional arguments. @@ -1285,7 +1285,7 @@ def draw_frame( # noqa: PLR0913 .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. - .. versionchanged:: Unreleased + .. versionchanged:: 18.0 Deprecated `clear`, `fg`, `bg`, and `bg_blend` being given as positional arguments. These should be keyword arguments only. @@ -1418,7 +1418,7 @@ def draw_rect( # noqa: PLR0913 .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. - .. versionchanged:: Unreleased + .. versionchanged:: 18.0 Deprecated `ch`, `fg`, `bg`, and `bg_blend` being given as positional arguments. These should be keyword arguments only. """ diff --git a/tcod/event.py b/tcod/event.py index 69bc8575..e8220285 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1252,7 +1252,7 @@ class EventDispatch(Generic[T]): This is now a generic class. The type hints at the return value of :any:`dispatch` and the `ev_*` methods. - .. deprecated:: Unreleased + .. deprecated:: 18.0 Event dispatch should be handled via a single custom method in a Protocol instead of this class. Note that events can and should be handled using Python's `match` statement. From f4c1d77b7357463551ca5abf92cbddcab2fa0e80 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 12 Apr 2025 01:51:46 -0700 Subject: [PATCH 0955/1101] Fix minor typo in deprecation string --- tcod/console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/console.py b/tcod/console.py index bec669a3..7e46ed7d 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1368,7 +1368,7 @@ def draw_rect( ) -> None: ... @overload - @deprecated("Parameters cg, fg, bg, bg_blend should be keyword arguments") + @deprecated("Parameters ch, fg, bg, bg_blend should be keyword arguments") def draw_rect( self, x: int, From cae1dc8c0a04042bf60668c3ce723749806107bb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 20 Apr 2025 21:58:50 -0700 Subject: [PATCH 0956/1101] Add more realistic Console.rgb example --- tcod/console.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tcod/console.py b/tcod/console.py index 7e46ed7d..e84ba372 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -335,6 +335,33 @@ def rgb(self) -> NDArray[Any]: The :any:`rgb_graphic` dtype can be used to make arrays compatible with this attribute that are independent of a :any:`Console`. + Example: + >>> tile_graphics = np.array( # Tile graphics lookup table + ... [ # (Unicode, foreground, background) + ... (ord("."), (255, 255, 255), (0, 0, 0)), # Tile 0 + ... (ord("#"), (255, 255, 255), (0, 0, 0)), # Tile 1 + ... (ord("^"), (255, 255, 255), (0, 0, 0)), # Tile 2 + ... (ord("~"), (255, 255, 255), (0, 0, 0)), # Tile 3 + ... ], + ... dtype=tcod.console.rgb_graphic, + ... ) + >>> console = tcod.console.Console(6, 5) + >>> console.rgb[:] = tile_graphics[ # Convert 2D array of indexes to tile graphics + ... [ + ... [1, 1, 1, 1, 1, 1], + ... [1, 0, 2, 0, 0, 1], + ... [1, 0, 0, 3, 3, 1], + ... [1, 0, 0, 3, 3, 1], + ... [1, 1, 1, 1, 1, 1], + ... ], + ... ] + >>> print(console) + <###### + #.^..# + #..~~# + #..~~# + ######> + Example: >>> con = tcod.console.Console(10, 2) >>> BLUE, YELLOW, BLACK = (0, 0, 255), (255, 255, 0), (0, 0, 0) From 76f8731be4e29164daa04e58ecaa20415432dade Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 23 Apr 2025 04:05:47 -0700 Subject: [PATCH 0957/1101] Fix edge case in tcod.noise.grid Number check needed to be more broad --- CHANGELOG.md | 4 ++++ tcod/noise.py | 4 ++-- tests/test_noise.py | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dba327d..91ea30a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- `tcod.noise.grid` would raise `TypeError` when given a plain integer for scale. + ## [18.0.0] - 2025-04-08 ### Changed diff --git a/tcod/noise.py b/tcod/noise.py index da14b3f2..808ac818 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -411,7 +411,7 @@ def grid( scale: tuple[float, ...] | float, origin: tuple[int, ...] | None = None, indexing: Literal["ij", "xy"] = "xy", -) -> tuple[NDArray[Any], ...]: +) -> tuple[NDArray[np.number], ...]: """Generate a mesh-grid of sample points to use with noise sampling. Args: @@ -449,7 +449,7 @@ def grid( .. versionadded:: 12.2 """ - if isinstance(scale, float): + if isinstance(scale, (int, float)): scale = (scale,) * len(shape) if origin is None: origin = (0,) * len(shape) diff --git a/tests/test_noise.py b/tests/test_noise.py index 72149062..80023f5f 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -7,6 +7,7 @@ import pytest import tcod.noise +import tcod.random # ruff: noqa: D103 @@ -100,3 +101,7 @@ def test_noise_copy() -> None: assert repr(noise3) == repr(pickle.loads(pickle.dumps(noise3))) noise4 = tcod.noise.Noise(2, seed=42) assert repr(noise4) == repr(pickle.loads(pickle.dumps(noise4))) + + +def test_noise_grid() -> None: + tcod.noise.grid((2, 2), scale=2) # Check int scale From ccdd225eeadeec3ef0c7e2f99bbfd775ef949f1e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 May 2025 05:30:47 -0700 Subject: [PATCH 0958/1101] Add tcod.path.path2d function The library really needed a simple A to B pathfinding function --- CHANGELOG.md | 4 ++ tcod/path.py | 157 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ea30a9..6e589d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `tcod.path.path2d` computes a path for the most basic cases. + ### Fixed - `tcod.noise.grid` would raise `TypeError` when given a plain integer for scale. diff --git a/tcod/path.py b/tcod/path.py index a58cb218..603a0d3e 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,7 +21,7 @@ import functools import itertools import warnings -from typing import TYPE_CHECKING, Any, Callable, Final +from typing import TYPE_CHECKING, Any, Callable, Final, Sequence import numpy as np from typing_extensions import Literal, Self @@ -324,7 +324,7 @@ def _export_dict(array: NDArray[Any]) -> dict[str, Any]: } -def _export(array: NDArray[np.number]) -> Any: # noqa: ANN401 +def _export(array: NDArray[np.integer]) -> Any: # noqa: ANN401 """Convert a NumPy array into a cffi object.""" return ffi.new("struct NArray*", _export_dict(array)) @@ -355,8 +355,8 @@ def dijkstra2d( # noqa: PLR0913 diagonal: int | None = None, *, edge_map: ArrayLike | None = None, - out: NDArray[np.number] | None = ..., # type: ignore[assignment, unused-ignore] -) -> NDArray[Any]: + out: NDArray[np.integer] | None = ..., # type: ignore[assignment, unused-ignore] +) -> NDArray[np.integer]: """Return the computed distance of all nodes on a 2D Dijkstra grid. `distance` is an input array of node distances. Is this often an @@ -528,7 +528,7 @@ def hillclimb2d( diagonal: bool | None = None, *, edge_map: ArrayLike | None = None, -) -> NDArray[Any]: +) -> NDArray[np.intc]: """Return a path on a grid from `start` to the lowest point. `distance` should be a fully computed distance array. This kind of array @@ -1289,7 +1289,7 @@ def resolve(self, goal: tuple[int, ...] | None = None) -> None: self._update_heuristic(goal) self._graph._resolve(self) - def path_from(self, index: tuple[int, ...]) -> NDArray[Any]: + def path_from(self, index: tuple[int, ...]) -> NDArray[np.intc]: """Return the shortest path from `index` to the nearest root. The returned array is of shape `(length, ndim)` where `length` is the @@ -1343,7 +1343,7 @@ def path_from(self, index: tuple[int, ...]) -> NDArray[Any]: ) return path[:, ::-1] if self._order == "F" else path - def path_to(self, index: tuple[int, ...]) -> NDArray[Any]: + def path_to(self, index: tuple[int, ...]) -> NDArray[np.intc]: """Return the shortest path from the nearest root to `index`. See :any:`path_from`. @@ -1370,3 +1370,146 @@ def path_to(self, index: tuple[int, ...]) -> NDArray[Any]: [] """ return self.path_from(index)[::-1] + + +def path2d( # noqa: C901, PLR0912, PLR0913 + cost: ArrayLike, + *, + start_points: Sequence[tuple[int, int]], + end_points: Sequence[tuple[int, int]], + cardinal: int, + diagonal: int | None = None, + check_bounds: bool = True, +) -> NDArray[np.intc]: + """Return a path between `start_points` and `end_points`. + + If `start_points` or `end_points` has only one item then this is equivalent to A*. + Otherwise it is equivalent to Dijkstra. + + If multiple `start_points` or `end_points` are given then the single shortest path between them is returned. + + Points placed on nodes with a cost of 0 are treated as always reachable from adjacent nodes. + + Args: + cost: A 2D array of integers with the cost of each node. + start_points: A sequence of one or more starting points indexing `cost`. + end_points: A sequence of one or more ending points indexing `cost`. + cardinal: The relative cost to move a cardinal direction. + diagonal: The relative cost to move a diagonal direction. + `None` or `0` will disable diagonal movement. + check_bounds: If `False` then out-of-bounds points are silently ignored. + If `True` (default) then out-of-bounds points raise :any:`IndexError`. + + Returns: + A `(length, 2)` array of indexes of the path including the start and end points. + If there is no path then an array with zero items will be returned. + + Example:: + + # Note: coordinates in this example are (i, j), or (y, x) + >>> cost = np.array([ + ... [1, 0, 1, 1, 1, 0, 1], + ... [1, 0, 1, 1, 1, 0, 1], + ... [1, 0, 1, 0, 1, 0, 1], + ... [1, 1, 1, 1, 1, 0, 1], + ... ]) + + # Endpoints are reachable even when endpoints are on blocked nodes + >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(2, 3)], cardinal=70, diagonal=99) + array([[0, 0], + [1, 0], + [2, 0], + [3, 1], + [2, 2], + [2, 3]], dtype=int...) + + # Unreachable endpoints return a zero length array + >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(3, 6)], cardinal=70, diagonal=99) + array([], shape=(0, 2), dtype=int...) + >>> tcod.path.path2d(cost, start_points=[(0, 0), (3, 0)], end_points=[(0, 6), (3, 6)], cardinal=70, diagonal=99) + array([], shape=(0, 2), dtype=int...) + >>> tcod.path.path2d(cost, start_points=[], end_points=[], cardinal=70, diagonal=99) + array([], shape=(0, 2), dtype=int...) + + # Overlapping endpoints return a single step + >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(0, 0)], cardinal=70, diagonal=99) + array([[0, 0]], dtype=int32) + + # Multiple endpoints return the shortest path + >>> tcod.path.path2d( + ... cost, start_points=[(0, 0)], end_points=[(1, 3), (3, 3), (2, 2), (2, 4)], cardinal=70, diagonal=99) + array([[0, 0], + [1, 0], + [2, 0], + [3, 1], + [2, 2]], dtype=int...) + >>> tcod.path.path2d( + ... cost, start_points=[(0, 0), (0, 2)], end_points=[(1, 3), (3, 3), (2, 2), (2, 4)], cardinal=70, diagonal=99) + array([[0, 2], + [1, 3]], dtype=int...) + >>> tcod.path.path2d(cost, start_points=[(0, 0), (0, 2)], end_points=[(3, 2)], cardinal=1) + array([[0, 2], + [1, 2], + [2, 2], + [3, 2]], dtype=int...) + + # Checking for out-of-bounds points may be toggled + >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(-1, -1), (3, 1)], cardinal=1) + Traceback (most recent call last): + ... + IndexError: End point (-1, -1) is out-of-bounds of cost shape (4, 7) + >>> tcod.path.path2d(cost, start_points=[(0, 0)], end_points=[(-1, -1), (3, 1)], cardinal=1, check_bounds=False) + array([[0, 0], + [1, 0], + [2, 0], + [3, 0], + [3, 1]], dtype=int...) + + .. versionadded:: Unreleased + """ + cost = np.copy(cost) # Copy array to later modify nodes to be always reachable + + # Check bounds of endpoints + if check_bounds: + for points, name in [(start_points, "start"), (end_points, "end")]: + for i, j in points: + if not (0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]): + msg = f"{name.capitalize()} point {(i, j)!r} is out-of-bounds of cost shape {cost.shape!r}" + raise IndexError(msg) + else: + start_points = [(i, j) for i, j in start_points if 0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]] + end_points = [(i, j) for i, j in end_points if 0 <= i < cost.shape[0] and 0 <= j < cost.shape[1]] + + if not start_points or not end_points: + return np.zeros((0, 2), dtype=np.intc) # Missing endpoints + + # Check if endpoints can be manipulated to use A* for a one-to-many computation + reversed_path = False + if len(end_points) == 1 and len(start_points) > 1: + # Swap endpoints to ensure single start point as the A* goal + reversed_path = True + start_points, end_points = end_points, start_points + + for ij in start_points: + cost[ij] = 1 # Enforce reachability of endpoint + + graph = SimpleGraph(cost=cost, cardinal=cardinal, diagonal=diagonal or 0) + pf = Pathfinder(graph) + for ij in end_points: + pf.add_root(ij) + + if len(start_points) == 1: # Compute A* from possibly multiple roots to one goal + out = pf.path_from(start_points[0]) + if pf.distance[start_points[0]] == np.iinfo(pf.distance.dtype).max: + return np.zeros((0, 2), dtype=np.intc) # Unreachable endpoint + if reversed_path: + out = out[::-1] + return out + + # Crude Dijkstra implementation until issues with Pathfinder are fixed + pf.resolve(None) + best_distance, best_ij = min((pf.distance[ij], ij) for ij in start_points) + if best_distance == np.iinfo(pf.distance.dtype).max: + return np.zeros((0, 2), dtype=np.intc) # All endpoints unreachable + + return hillclimb2d(pf.distance, best_ij, cardinal=bool(cardinal), diagonal=bool(diagonal)) From f553f45e8153572822569eac1e55e1d86e6a1cec Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 4 May 2025 17:59:00 -0700 Subject: [PATCH 0959/1101] Prepare 18.1.0 release. --- CHANGELOG.md | 4 +++- tcod/path.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e589d9b..5ab3b06b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [18.1.0] - 2025-05-05 + ### Added -- `tcod.path.path2d` computes a path for the most basic cases. +- `tcod.path.path2d` to compute paths for the most basic cases. ### Fixed diff --git a/tcod/path.py b/tcod/path.py index 603a0d3e..9ab634f3 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1465,7 +1465,7 @@ def path2d( # noqa: C901, PLR0912, PLR0913 [3, 0], [3, 1]], dtype=int...) - .. versionadded:: Unreleased + .. versionadded:: 18.1 """ cost = np.copy(cost) # Copy array to later modify nodes to be always reachable From 4b5e044214ba3897a3056fa4edb7ada7504bfa36 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 May 2025 01:24:30 -0700 Subject: [PATCH 0960/1101] Fix incorrect imageio calls in examples --- .vscode/settings.json | 1 + tcod/tileset.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a6f96553..78327ad2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -189,6 +189,7 @@ "iinfo", "IJKL", "imageio", + "imread", "INCOL", "INROW", "interactable", diff --git a/tcod/tileset.py b/tcod/tileset.py index 56213fe7..f2a5ac33 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -126,20 +126,20 @@ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: # Normal usage when a tile already has its own alpha channel. # The loaded tile must be the correct shape for the tileset you assign it to. # The tile is assigned to a private use area and will not conflict with any exiting codepoint. - tileset.set_tile(0x100000, imageio.load("rgba_tile.png")) + tileset.set_tile(0x100000, imageio.imread("rgba_tile.png")) # Load a greyscale tile. - tileset.set_tile(0x100001, imageio.load("greyscale_tile.png"), pilmode="L") + tileset.set_tile(0x100001, imageio.imread("greyscale_tile.png"), mode="L") # If you are stuck with an RGB array then you can use the red channel as the input: `rgb[:, :, 0]` # Loads an RGB sprite without a background. - tileset.set_tile(0x100002, imageio.load("rgb_no_background.png", pilmode="RGBA")) + tileset.set_tile(0x100002, imageio.imread("rgb_no_background.png", mode="RGBA")) # If you're stuck with an RGB array then you can pad the channel axis with an alpha of 255: # rgba = np.pad(rgb, pad_width=((0, 0), (0, 0), (0, 1)), constant_values=255) # Loads an RGB sprite with a key color background. KEY_COLOR = np.asarray((255, 0, 255), dtype=np.uint8) - sprite_rgb = imageio.load("rgb_tile.png") + sprite_rgb = imageio.imread("rgb_tile.png") # Compare the RGB colors to KEY_COLOR, compress full matches to a 2D mask. sprite_mask = (sprite_rgb != KEY_COLOR).all(axis=2) # Generate the alpha array, with 255 as the foreground and 0 as the background. From 484b6a250aaeb17f7b1dc4268a8e4ee4c560a107 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 May 2025 01:25:54 -0700 Subject: [PATCH 0961/1101] Pre-commit update --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f044c36..00cc1d41 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.11.8 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] From 01ddb08ada310b0bbc3b65db5ac29ced5e6c3dc3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 24 May 2025 15:10:04 -0700 Subject: [PATCH 0962/1101] Fix samples script Properly clear the renderer per frame. Use explicit namespace imports for tcod Ignore spelling of Numpy parameters Refactor redundant quit events --- .vscode/settings.json | 2 ++ examples/samples_tcod.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 78327ad2..e3434adc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -79,6 +79,7 @@ "CFLAGS", "CHARMAP", "Chebyshev", + "choicelist", "cibuildwheel", "CIBW", "CLEARAGAIN", @@ -92,6 +93,7 @@ "Coef", "COLCTRL", "COMPILEDVERSION", + "condlist", "consolas", "contextdata", "CONTROLLERAXISMOTION", diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 672ce4f6..593ef002 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -18,13 +18,20 @@ import numpy as np +import tcod.bsp import tcod.cffi +import tcod.console import tcod.context import tcod.event +import tcod.image +import tcod.los +import tcod.map import tcod.noise +import tcod.path import tcod.render import tcod.sdl.mouse import tcod.sdl.render +import tcod.tileset from tcod import libtcodpy from tcod.sdl.video import WindowFlags @@ -103,15 +110,10 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: else: libtcodpy.sys_save_screenshot() print("png") - elif event.sym == tcod.event.KeySym.ESCAPE: - raise SystemExit elif event.sym in RENDERER_KEYS: # Swap the active context for one with a different renderer. init_context(RENDERER_KEYS[event.sym]) - def ev_quit(self, event: tcod.event.Quit) -> None: - raise SystemExit - class TrueColorSample(Sample): def __init__(self) -> None: @@ -1364,6 +1366,9 @@ def main() -> None: sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() if context.sdl_renderer: + # Clear the screen to ensure no garbage data outside of the logical area is displayed + context.sdl_renderer.draw_color = (0, 0, 0, 255) + context.sdl_renderer.clear() # SDL renderer support, upload the sample console background to a minimap texture. sample_minimap.update(sample_console.rgb.T["bg"]) # Render the root_console normally, this is the drawing step of context.present without presenting. @@ -1419,6 +1424,8 @@ def handle_events() -> None: SAMPLES[cur_sample].dispatch(event) if isinstance(event, tcod.event.Quit): raise SystemExit + if isinstance(event, tcod.event.KeyDown) and event.sym == tcod.event.KeySym.ESCAPE: + raise SystemExit def draw_samples_menu() -> None: From 4ffca36f11431fc8cbd275c4edf8442026225a70 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 26 May 2025 12:40:08 -0700 Subject: [PATCH 0963/1101] Drop Python 3.8 and 3.9 support Apply Ruff safe fixes --- .github/workflows/python-package.yml | 14 ++++---- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 4 +++ README.rst | 2 +- build_libtcod.py | 5 +-- examples/eventget.py | 8 ++--- examples/framerate.py | 5 ++- examples/samples_tcod.py | 2 +- examples/thread_jobs.py | 18 +++++----- examples/ttf.py | 4 +-- pyproject.toml | 19 +++-------- scripts/generate_charmap_table.py | 2 +- tcod/_internal.py | 5 +-- tcod/bsp.py | 3 +- tcod/color.py | 4 +-- tcod/console.py | 5 +-- tcod/context.py | 5 +-- tcod/event.py | 5 +-- tcod/libtcodpy.py | 5 +-- tcod/map.py | 7 ++-- tcod/noise.py | 8 +++-- tcod/path.py | 22 ++++++++----- tcod/random.py | 3 +- tcod/sdl/_internal.py | 3 +- tcod/sdl/audio.py | 5 +-- tcod/sdl/joystick.py | 4 +-- tcod/sdl/render.py | 4 +-- tcod/tileset.py | 3 +- tests/conftest.py | 2 +- tests/test_libtcodpy.py | 49 ++++++++++++++-------------- 30 files changed, 117 insertions(+), 110 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5c61ff6e..b7337eb0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -95,14 +95,14 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - python-version: ["3.8", "pypy-3.8"] + python-version: ["3.10", "pypy-3.10"] architecture: ["x64"] include: - os: "windows-latest" - python-version: "3.8" + python-version: "3.10" architecture: "x86" - os: "windows-latest" - python-version: "pypy-3.8" + python-version: "pypy-3.10" architecture: "x86" fail-fast: false @@ -215,9 +215,9 @@ jobs: strategy: matrix: arch: ["x86_64", "aarch64"] - build: ["cp38-manylinux*", "pp38-manylinux*"] + build: ["cp310-manylinux*", "pp310-manylinux*"] env: - BUILD_DESC: + BUILD_DESC: "" steps: - uses: actions/checkout@v4 with: @@ -272,9 +272,9 @@ jobs: strategy: fail-fast: true matrix: - python: ["cp38-*_universal2", "pp38-*"] + python: ["cp310-*_universal2", "pp310-*"] env: - PYTHON_DESC: + PYTHON_DESC: "" steps: - uses: actions/checkout@v4 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00cc1d41..f7d3a915 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.8 + rev: v0.11.11 hooks: - id: ruff args: [--fix-only, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ab3b06b..0ce0d62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Removed + +- Support dropped for Python 3.8 and 3.9. + ## [18.1.0] - 2025-05-05 ### Added diff --git a/README.rst b/README.rst index 82aad962..515c90a7 100755 --- a/README.rst +++ b/README.rst @@ -46,7 +46,7 @@ For the most part it's just:: ============== Requirements ============== -* Python 3.8+ +* Python 3.10+ * Windows, Linux, or MacOS X 10.9+. * On Linux, requires libsdl2 (2.0.10+). diff --git a/build_libtcod.py b/build_libtcod.py index a5bca5ac..974904cc 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -9,8 +9,9 @@ import platform import re import sys +from collections.abc import Iterable, Iterator from pathlib import Path -from typing import Any, ClassVar, Iterable, Iterator +from typing import Any, ClassVar from cffi import FFI @@ -20,7 +21,7 @@ import build_sdl -Py_LIMITED_API = 0x03060000 +Py_LIMITED_API = 0x03100000 HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") diff --git a/examples/eventget.py b/examples/eventget.py index 9049203f..dad8649d 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -5,8 +5,6 @@ # https://creativecommons.org/publicdomain/zero/1.0/ """An demonstration of event handling using the tcod.event module.""" -from typing import List, Set - import tcod.context import tcod.event import tcod.sdl.joystick @@ -17,11 +15,11 @@ def main() -> None: """Example program for tcod.event.""" - event_log: List[str] = [] + event_log: list[str] = [] motion_desc = "" tcod.sdl.joystick.init() - controllers: Set[tcod.sdl.joystick.GameController] = set() - joysticks: Set[tcod.sdl.joystick.Joystick] = set() + controllers: set[tcod.sdl.joystick.GameController] = set() + joysticks: set[tcod.sdl.joystick.Joystick] = set() with tcod.context.new(width=WIDTH, height=HEIGHT) as context: console = context.new_console() diff --git a/examples/framerate.py b/examples/framerate.py index d7f4a08d..cc1195de 100755 --- a/examples/framerate.py +++ b/examples/framerate.py @@ -8,7 +8,6 @@ import statistics import time from collections import deque -from typing import Deque, Optional import tcod @@ -25,11 +24,11 @@ class Clock: def __init__(self) -> None: """Initialize this object with empty data.""" self.last_time = time.perf_counter() # Last time this was synced. - self.time_samples: Deque[float] = deque() # Delta time samples. + self.time_samples: deque[float] = deque() # Delta time samples. self.max_samples = 64 # Number of fps samples to log. Can be changed. self.drift_time = 0.0 # Tracks how much the last frame was overshot. - def sync(self, fps: Optional[float] = None) -> float: + def sync(self, fps: float | None = None) -> float: """Sync to a given framerate and return the delta time. `fps` is the desired framerate in frames-per-second. If None is given diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 593ef002..6c0e6a1c 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1209,7 +1209,7 @@ def on_draw(self) -> None: # new pixels are based on absolute elapsed time int_abs_t = int(self.abs_t) - texture = np.roll(texture, -int_t, 1) + texture = np.roll(texture, -int_t, 1) # type: ignore[assignment] # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(RES_U): diff --git a/examples/thread_jobs.py b/examples/thread_jobs.py index a7212c4f..9f6cf6c1 100755 --- a/examples/thread_jobs.py +++ b/examples/thread_jobs.py @@ -19,7 +19,7 @@ import platform import sys import timeit -from typing import Callable, List, Tuple +from collections.abc import Callable import tcod.map @@ -37,35 +37,35 @@ def test_fov(map_: tcod.map.Map) -> tcod.map.Map: return map_ -def test_fov_single(maps: List[tcod.map.Map]) -> None: +def test_fov_single(maps: list[tcod.map.Map]) -> None: for map_ in maps: test_fov(map_) -def test_fov_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: +def test_fov_threads(executor: concurrent.futures.Executor, maps: list[tcod.map.Map]) -> None: for _result in executor.map(test_fov, maps): pass -def test_astar(map_: tcod.map.Map) -> List[Tuple[int, int]]: +def test_astar(map_: tcod.map.Map) -> list[tuple[int, int]]: astar = tcod.path.AStar(map_) return astar.get_path(0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1) -def test_astar_single(maps: List[tcod.map.Map]) -> None: +def test_astar_single(maps: list[tcod.map.Map]) -> None: for map_ in maps: test_astar(map_) -def test_astar_threads(executor: concurrent.futures.Executor, maps: List[tcod.map.Map]) -> None: +def test_astar_threads(executor: concurrent.futures.Executor, maps: list[tcod.map.Map]) -> None: for _result in executor.map(test_astar, maps): pass def run_test( - maps: List[tcod.map.Map], - single_func: Callable[[List[tcod.map.Map]], None], - multi_func: Callable[[concurrent.futures.Executor, List[tcod.map.Map]], None], + maps: list[tcod.map.Map], + single_func: Callable[[list[tcod.map.Map]], None], + multi_func: Callable[[concurrent.futures.Executor, list[tcod.map.Map]], None], ) -> None: """Run a function designed for a single thread and compare it to a threaded version. diff --git a/examples/ttf.py b/examples/ttf.py index d6504bd8..fb1e35f6 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -9,7 +9,7 @@ # To the extent possible under law, the libtcod maintainers have waived all # copyright and related or neighboring rights to this example script. # https://creativecommons.org/publicdomain/zero/1.0/ -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING import freetype # type: ignore # pip install freetype-py import numpy as np @@ -25,7 +25,7 @@ FONT = "VeraMono.ttf" -def load_ttf(path: str, size: Tuple[int, int]) -> tcod.tileset.Tileset: +def load_ttf(path: str, size: tuple[int, int]) -> tcod.tileset.Tileset: """Load a TTF file and return a tcod Tileset. `path` is the file path to the font, this can be any font supported by the diff --git a/pyproject.toml b/pyproject.toml index f6a26c12..37ca4d41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dynamic = ["version"] description = "The official Python port of libtcod." authors = [{ name = "Kyle Benesch", email = "4b796c65+tcod@gmail.com" }] readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.10" license = { text = "Simplified BSD License" } dependencies = [ "cffi>=1.15", @@ -45,11 +45,10 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", @@ -70,21 +69,11 @@ Tracker = "https://github.com/libtcod/python-tcod/issues" Forum = "https://github.com/libtcod/python-tcod/discussions" [tool.distutils.bdist_wheel] -py-limited-api = "cp38" +py-limited-api = "cp310" [tool.setuptools_scm] write_to = "tcod/version.py" -[tool.black] -line-length = 120 -target-version = ["py38"] - -[tool.isort] -profile = "black" -py_version = "38" -skip_gitignore = true -line_length = 120 - [tool.pytest.ini_options] minversion = "6.0" required_plugins = ["pytest-cov", "pytest-benchmark"] @@ -106,7 +95,7 @@ filterwarnings = [ [tool.mypy] files = ["."] -python_version = "3.9" +python_version = "3.10" warn_unused_configs = true show_error_codes = true disallow_subclassing_any = true diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 50b6a779..2a7814b7 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -8,7 +8,7 @@ import argparse import unicodedata -from typing import Iterable, Iterator +from collections.abc import Iterable, Iterator from tabulate import tabulate diff --git a/tcod/_internal.py b/tcod/_internal.py index 049dd37d..3391a602 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -5,9 +5,10 @@ import locale import sys import warnings -from typing import TYPE_CHECKING, Any, AnyStr, Callable, NoReturn, SupportsInt, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, AnyStr, Literal, NoReturn, SupportsInt, TypeVar -from typing_extensions import Literal, LiteralString, deprecated +from typing_extensions import LiteralString, deprecated from tcod.cffi import ffi, lib diff --git a/tcod/bsp.py b/tcod/bsp.py index a2959b16..f4d7204d 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -27,7 +27,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator +from collections.abc import Iterator +from typing import TYPE_CHECKING, Any from typing_extensions import deprecated diff --git a/tcod/color.py b/tcod/color.py index 49b98ad7..61f18b49 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -3,13 +3,13 @@ from __future__ import annotations import warnings -from typing import Any, List +from typing import Any from tcod._internal import deprecate from tcod.cffi import lib -class Color(List[int]): +class Color(list[int]): """Old-style libtcodpy color class. Args: diff --git a/tcod/console.py b/tcod/console.py index e84ba372..ca9d4253 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -8,11 +8,12 @@ from __future__ import annotations import warnings +from collections.abc import Iterable from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterable, overload +from typing import TYPE_CHECKING, Any, Literal, overload import numpy as np -from typing_extensions import Literal, Self, deprecated +from typing_extensions import Self, deprecated import tcod._internal import tcod.constants diff --git a/tcod/context.py b/tcod/context.py index 386b7324..f9d562ee 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -29,10 +29,11 @@ import pickle import sys import warnings +from collections.abc import Iterable from pathlib import Path -from typing import Any, Iterable, NoReturn, TypeVar +from typing import Any, Literal, NoReturn, TypeVar -from typing_extensions import Literal, Self, deprecated +from typing_extensions import Self, deprecated import tcod.console import tcod.event diff --git a/tcod/event.py b/tcod/event.py index e8220285..25abfb7f 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -84,10 +84,11 @@ import enum import warnings -from typing import TYPE_CHECKING, Any, Callable, Final, Generic, Iterator, Mapping, NamedTuple, TypeVar +from collections.abc import Callable, Iterator, Mapping +from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar import numpy as np -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated import tcod.event import tcod.event_constants diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 0018745d..517f3377 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -6,11 +6,12 @@ import sys import threading import warnings +from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Hashable, Iterable, Iterator, Sequence +from typing import TYPE_CHECKING, Any, Literal import numpy as np -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated import tcod.bsp import tcod.console diff --git a/tcod/map.py b/tcod/map.py index 32e42d1e..45b4561a 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,10 +3,9 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal import numpy as np -from typing_extensions import Literal import tcod._internal import tcod.constants @@ -262,6 +261,6 @@ def compute_fov( ffi.from_buffer("struct TCOD_MapCell*", map_buffer), ), ) - map_buffer["transparent"] = transparency + map_buffer["transparent"] = transparency # type: ignore[call-overload] lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm) - return map_buffer["fov"] + return map_buffer["fov"] # type: ignore[no-any-return,call-overload] diff --git a/tcod/noise.py b/tcod/noise.py index 808ac818..23d84691 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -36,10 +36,10 @@ import enum import warnings -from typing import TYPE_CHECKING, Any, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Literal import numpy as np -from typing_extensions import Literal import tcod.constants import tcod.random @@ -459,5 +459,7 @@ def grid( if len(shape) != len(origin): msg = "shape must have the same length as origin" raise TypeError(msg) - indexes = (np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin)) + indexes = ( + np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin, strict=True) + ) return tuple(np.meshgrid(*indexes, copy=False, sparse=True, indexing=indexing)) diff --git a/tcod/path.py b/tcod/path.py index 9ab634f3..1668487a 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,10 +21,11 @@ import functools import itertools import warnings -from typing import TYPE_CHECKING, Any, Callable, Final, Sequence +from collections.abc import Callable, Sequence +from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np -from typing_extensions import Literal, Self +from typing_extensions import Self import tcod.map from tcod._internal import _check @@ -134,7 +135,7 @@ class NodeCostArray(np.ndarray): # type: ignore[type-arg] def __new__(cls, array: ArrayLike) -> Self: """Validate a numpy array and setup a C callback.""" - return np.asarray(array).view(cls) # type: ignore[no-any-return] + return np.asarray(array).view(cls) def __repr__(self) -> str: return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})" @@ -182,11 +183,11 @@ def __init__(self, cost: tcod.map.Map | ArrayLike | _EdgeCostFunc, diagonal: flo self._callback, self._userdata, self.shape, - ) = self.cost.get_tcod_path_ffi() + ) = self.cost.get_tcod_path_ffi() # type: ignore[union-attr] self._path_c = ffi.gc( self._path_new_using_function( - self.cost.shape[0], - self.cost.shape[1], + self.cost.shape[0], # type: ignore[union-attr] + self.cost.shape[1], # type: ignore[union-attr] self._callback, self._userdata, diagonal, @@ -900,8 +901,13 @@ def add_edges( edge_costs = edge_map[edge_nz] edge_array = np.transpose(edge_nz) edge_array -= edge_center - for edge, edge_cost in zip(edge_array, edge_costs): - self.add_edge(tuple(edge), edge_cost, cost=cost, condition=condition) + for edge, edge_cost in zip(edge_array, edge_costs, strict=True): + self.add_edge( + tuple(edge.tolist()), # type: ignore[arg-type] + edge_cost, + cost=cost, + condition=condition, + ) def set_heuristic(self, *, cardinal: int = 0, diagonal: int = 0, z: int = 0, w: int = 0) -> None: """Set a pathfinder heuristic so that pathfinding can done with A*. diff --git a/tcod/random.py b/tcod/random.py index fe1944db..6d332b86 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -12,7 +12,8 @@ import os import random import warnings -from typing import Any, Hashable +from collections.abc import Hashable +from typing import Any from typing_extensions import deprecated diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index e7f5aa8c..23b41632 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -4,8 +4,9 @@ import logging import sys as _sys +from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar +from typing import TYPE_CHECKING, Any, NoReturn, TypeVar from tcod.cffi import ffi, lib diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 8627bdeb..711d3fc4 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -48,10 +48,11 @@ import sys import threading import time -from typing import TYPE_CHECKING, Any, Callable, Final, Hashable, Iterator +from collections.abc import Callable, Hashable, Iterator +from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np -from typing_extensions import Literal, Self +from typing_extensions import Self import tcod.sdl.sys from tcod.cffi import ffi, lib diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index d5c66713..9015605c 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -6,11 +6,9 @@ from __future__ import annotations import enum -from typing import Any, ClassVar, Final +from typing import Any, ClassVar, Final, Literal from weakref import WeakValueDictionary -from typing_extensions import Literal - import tcod.sdl.sys from tcod.cffi import ffi, lib from tcod.sdl._internal import _check, _check_p diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 22d3911d..83606a03 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -6,10 +6,10 @@ from __future__ import annotations import enum -from typing import TYPE_CHECKING, Any, Final, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np -from typing_extensions import Literal import tcod.sdl.video from tcod.cffi import ffi, lib diff --git a/tcod/tileset.py b/tcod/tileset.py index f2a5ac33..b953cab3 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -16,8 +16,9 @@ from __future__ import annotations import itertools +from collections.abc import Iterable from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Any import numpy as np from typing_extensions import deprecated diff --git a/tests/conftest.py b/tests/conftest.py index f7a42fd5..182cb6d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import random import warnings -from typing import Callable, Iterator +from collections.abc import Callable, Iterator import pytest diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index f459f27f..7ace644a 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -1,7 +1,8 @@ """Tests for the libtcodpy API.""" +from collections.abc import Callable, Iterator from pathlib import Path -from typing import Any, Callable, Iterator, List, Optional, Tuple, Union +from typing import Any import numpy as np import pytest @@ -37,9 +38,9 @@ def assert_char( # noqa: PLR0913 console: tcod.console.Console, x: int, y: int, - ch: Optional[Union[str, int]] = None, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None, + ch: str | int | None = None, + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, ) -> None: if ch is not None: if isinstance(ch, str): @@ -52,7 +53,7 @@ def assert_char( # noqa: PLR0913 @pytest.mark.filterwarnings("ignore") -def test_console_defaults(console: tcod.console.Console, fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> None: +def test_console_defaults(console: tcod.console.Console, fg: tuple[int, int, int], bg: tuple[int, int, int]) -> None: libtcodpy.console_set_default_foreground(console, fg) libtcodpy.console_set_default_background(console, bg) libtcodpy.console_clear(console) @@ -60,13 +61,13 @@ def test_console_defaults(console: tcod.console.Console, fg: Tuple[int, int, int @pytest.mark.filterwarnings("ignore") -def test_console_set_char_background(console: tcod.console.Console, bg: Tuple[int, int, int]) -> None: +def test_console_set_char_background(console: tcod.console.Console, bg: tuple[int, int, int]) -> None: libtcodpy.console_set_char_background(console, 0, 0, bg, libtcodpy.BKGND_SET) assert_char(console, 0, 0, bg=bg) @pytest.mark.filterwarnings("ignore") -def test_console_set_char_foreground(console: tcod.console.Console, fg: Tuple[int, int, int]) -> None: +def test_console_set_char_foreground(console: tcod.console.Console, fg: tuple[int, int, int]) -> None: libtcodpy.console_set_char_foreground(console, 0, 0, fg) assert_char(console, 0, 0, fg=fg) @@ -85,14 +86,14 @@ def test_console_put_char(console: tcod.console.Console, ch: int) -> None: @pytest.mark.filterwarnings("ignore") def console_put_char_ex( - console: tcod.console.Console, ch: int, fg: Tuple[int, int, int], bg: Tuple[int, int, int] + console: tcod.console.Console, ch: int, fg: tuple[int, int, int], bg: tuple[int, int, int] ) -> None: libtcodpy.console_put_char_ex(console, 0, 0, ch, fg, bg) assert_char(console, 0, 0, ch=ch, fg=fg, bg=bg) @pytest.mark.filterwarnings("ignore") -def test_console_printing(console: tcod.console.Console, fg: Tuple[int, int, int], bg: Tuple[int, int, int]) -> None: +def test_console_printing(console: tcod.console.Console, fg: tuple[int, int, int], bg: tuple[int, int, int]) -> None: libtcodpy.console_set_background_flag(console, libtcodpy.BKGND_SET) assert libtcodpy.console_get_background_flag(console) == libtcodpy.BKGND_SET @@ -182,8 +183,8 @@ def test_console_rexpaint_save_load( console: tcod.console.Console, tmp_path: Path, ch: int, - fg: Tuple[int, int, int], - bg: Tuple[int, int, int], + fg: tuple[int, int, int], + bg: tuple[int, int, int], ) -> None: libtcodpy.console_print(console, 0, 0, "test") libtcodpy.console_put_char_ex(console, 1, 1, ch, fg, bg) @@ -206,7 +207,7 @@ def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path assert libtcodpy.console_list_save_xp([con1, con2], xp_file, 1) loaded_consoles = libtcodpy.console_list_load_xp(xp_file) assert loaded_consoles - for a, b in zip([con1, con2], loaded_consoles): + for a, b in zip([con1, con2], loaded_consoles, strict=True): assertConsolesEqual(a, b) libtcodpy.console_delete(a) libtcodpy.console_delete(b) @@ -260,9 +261,9 @@ def test_console_fill_numpy(console: tcod.console.Console) -> None: for y in range(height): fill[y, :] = y % 256 - libtcodpy.console_fill_background(console, fill, fill, fill) - libtcodpy.console_fill_foreground(console, fill, fill, fill) - libtcodpy.console_fill_char(console, fill) + libtcodpy.console_fill_background(console, fill, fill, fill) # type: ignore[arg-type] + libtcodpy.console_fill_foreground(console, fill, fill, fill) # type: ignore[arg-type] + libtcodpy.console_fill_char(console, fill) # type: ignore[arg-type] # verify fill bg: NDArray[np.intc] = np.zeros((height, width), dtype=np.intc) @@ -273,10 +274,10 @@ def test_console_fill_numpy(console: tcod.console.Console) -> None: bg[y, x] = libtcodpy.console_get_char_background(console, x, y)[0] fg[y, x] = libtcodpy.console_get_char_foreground(console, x, y)[0] ch[y, x] = libtcodpy.console_get_char(console, x, y) - fill = fill.tolist() - assert fill == bg.tolist() - assert fill == fg.tolist() - assert fill == ch.tolist() + fill_ = fill.tolist() + assert fill_ == bg.tolist() + assert fill_ == fg.tolist() + assert fill_ == ch.tolist() @pytest.mark.filterwarnings("ignore") @@ -405,7 +406,7 @@ def test_line_step() -> None: def test_line() -> None: """Tests normal use, lazy evaluation, and error propagation.""" # test normal results - test_result: List[Tuple[int, int]] = [] + test_result: list[tuple[int, int]] = [] def line_test(x: int, y: int) -> bool: test_result.append((x, y)) @@ -697,8 +698,8 @@ def test_astar(map_: tcod.map.Map) -> None: assert libtcodpy.path_size(astar) > 0 assert not libtcodpy.path_is_empty(astar) - x: Optional[int] - y: Optional[int] + x: int | None + y: int | None for i in range(libtcodpy.path_size(astar)): x, y = libtcodpy.path_get(astar, i) @@ -735,8 +736,8 @@ def test_dijkstra(map_: tcod.map.Map) -> None: libtcodpy.dijkstra_reverse(path) - x: Optional[int] - y: Optional[int] + x: int | None + y: int | None for i in range(libtcodpy.dijkstra_size(path)): x, y = libtcodpy.dijkstra_get(path, i) From 44ff19256671e4fa9dbaefe0b6d066164e7ae4a8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 26 May 2025 13:20:33 -0700 Subject: [PATCH 0964/1101] Update setuptools requirement --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d0c9c27e..06667de0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ cffi>=1.15 numpy>=1.21.4 pycparser>=2.14 requests>=2.28.1 -setuptools==70.0.0 +setuptools>=80.8.0 types-cffi types-requests types-Pillow From 2b99334b3e160bb193d32f51bbcc567e01ec6430 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 27 May 2025 18:11:14 -0700 Subject: [PATCH 0965/1101] Update license metadata and copyright year New metadata fixes deprecation warnings from setuptools Packaging module required by setuptools for license metadata, older versions of cibuildwheel pinned an old version of it. --- .github/workflows/python-package.yml | 4 ++-- LICENSE.txt | 2 +- docs/conf.py | 2 +- pyproject.toml | 11 ++++++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b7337eb0..e20f2a30 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -235,7 +235,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install cibuildwheel==2.19.2 + pip install cibuildwheel==2.23.3 - name: Build wheels run: | python -m cibuildwheel --output-dir wheelhouse @@ -290,7 +290,7 @@ jobs: # Downloads SDL2 for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.19.2 + uses: pypa/cibuildwheel@v2.23.3 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 diff --git a/LICENSE.txt b/LICENSE.txt index d91bf759..ed980457 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2009-2023, Kyle Benesch and the python-tcod contributors. +Copyright (c) 2009-2025, Kyle Benesch and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index 81c2d205..0fda406a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # General information about the project. project = "python-tcod" -copyright = "2009-2023, Kyle Benesch" +copyright = "2009-2025, Kyle Benesch" author = "Kyle Benesch" # The version info for the project you're documenting, acts as replacement for diff --git a/pyproject.toml b/pyproject.toml index 37ca4d41..eac81af3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,8 +2,9 @@ requires = [ # setuptools >=64.0.0 might break editable installs # https://github.com/pypa/setuptools/issues/3548 - "setuptools >=61.0.0", + "setuptools >=77.0.3", "setuptools_scm[toml]>=6.2", + "packaging>=24.2", "wheel>=0.37.1", "cffi>=1.15", "pycparser>=2.14", @@ -19,7 +20,12 @@ description = "The official Python port of libtcod." authors = [{ name = "Kyle Benesch", email = "4b796c65+tcod@gmail.com" }] readme = "README.rst" requires-python = ">=3.10" -license = { text = "Simplified BSD License" } +license = "BSD-2-Clause" +license-files = [ + "LICENSE.txt", + "libtcod/LICENSE.txt", + "libtcod/LIBTCOD-CREDITS.txt", +] dependencies = [ "cffi>=1.15", 'numpy>=1.21.4; implementation_name != "pypy"', @@ -39,7 +45,6 @@ classifiers = [ "Environment :: MacOS X", "Environment :: X11 Applications", "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", From 414a2dd0df5c8e461087ac0e6d4b183042bd92c6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 31 May 2025 14:11:09 -0700 Subject: [PATCH 0966/1101] Use arm64 runner for arm64 wheels Should improve performance of the vitalized build step --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e20f2a30..c025e115 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -211,7 +211,7 @@ jobs: linux-wheels: needs: [ruff, mypy] - runs-on: "ubuntu-latest" + runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}} strategy: matrix: arch: ["x86_64", "aarch64"] From 8c25d37bd7b278466703227dfe71ef9938b0b28c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 31 May 2025 14:17:07 -0700 Subject: [PATCH 0967/1101] Removed unused Mypy ignores Changed with Mypy update --- tcod/path.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tcod/path.py b/tcod/path.py index 1668487a..92980b85 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -183,11 +183,11 @@ def __init__(self, cost: tcod.map.Map | ArrayLike | _EdgeCostFunc, diagonal: flo self._callback, self._userdata, self.shape, - ) = self.cost.get_tcod_path_ffi() # type: ignore[union-attr] + ) = self.cost.get_tcod_path_ffi() self._path_c = ffi.gc( self._path_new_using_function( - self.cost.shape[0], # type: ignore[union-attr] - self.cost.shape[1], # type: ignore[union-attr] + self.cost.shape[0], + self.cost.shape[1], self._callback, self._userdata, diagonal, From 597871e0670416207540496ac9f614462c62c08d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 8 Apr 2025 19:36:16 -0700 Subject: [PATCH 0968/1101] Upgrade to use SDL3 version of libtcod Reconfigure CFFI porting scripts for SDL3, add _libtcod.pyi Many SDL functions were renamed and I'd have to port them blind without this new `_libtcod.py` file. Update type ignores for newest Numpy Enable more strict Mypy settings SDL_CommonEvent and others needed to be marked as packed/flexible Remove outdated Sphinx directives Check if audio devices work before testing them Update Linux wheel build workflows for SDL3 --- .github/workflows/python-package.yml | 67 +- .gitignore | 4 +- .readthedocs.yaml | 4 +- .vscode/settings.json | 11 + CHANGELOG.md | 36 + README.rst | 2 +- build_libtcod.py | 253 +- build_sdl.py | 287 +- docs/sdl/joystick.rst | 6 - docs/tcod/event.rst | 2 +- examples/audio_tone.py | 60 +- examples/samples_tcod.py | 59 +- examples/sdl-hello-world.py | 2 +- libtcod | 2 +- pyproject.toml | 3 + setup.py | 28 +- tcod/_libtcod.pyi | 9729 ++++++++++++++++++++++++++ tcod/cffi.py | 28 +- tcod/color.py | 2 +- tcod/console.py | 4 +- tcod/context.py | 26 +- tcod/event.py | 337 +- tcod/event_constants.py | 202 +- tcod/libtcodpy.py | 2 +- tcod/noise.py | 9 +- tcod/path.c | 4 +- tcod/path.h | 18 +- tcod/path.py | 30 +- tcod/sdl/_internal.py | 120 +- tcod/sdl/audio.py | 851 ++- tcod/sdl/constants.py | 489 ++ tcod/sdl/joystick.py | 189 +- tcod/sdl/mouse.py | 27 +- tcod/sdl/render.py | 216 +- tcod/sdl/sys.py | 8 +- tcod/sdl/video.py | 126 +- tcod/tcod.c | 2 +- tcod/tileset.py | 9 +- tests/test_sdl.py | 12 +- tests/test_sdl_audio.py | 41 +- tests/test_tcod.py | 1 - 41 files changed, 12233 insertions(+), 1075 deletions(-) create mode 100644 tcod/_libtcod.pyi create mode 100644 tcod/sdl/constants.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c025e115..714520ec 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -44,10 +44,11 @@ jobs: sdist: runs-on: ubuntu-latest steps: - - name: APT update - run: sudo apt-get update - - name: Install APT dependencies - run: sudo apt-get install libsdl2-dev + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + with: + install-linux-dependencies: true + build-type: "Debug" + version: "3.2.14" - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} @@ -71,7 +72,7 @@ jobs: strategy: matrix: os: ["windows-latest", "macos-latest"] - sdl-version: ["2.0.14", "2.0.16", "2.30.0"] + sdl-version: ["3.2.10"] fail-fast: true steps: - uses: actions/checkout@v4 @@ -90,7 +91,7 @@ jobs: SDL_VERSION: ${{ matrix.sdl-version }} build: - needs: [ruff, mypy] + needs: [ruff, mypy, sdist] runs-on: ${{ matrix.os }} strategy: matrix: @@ -122,7 +123,13 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install libsdl2-dev xvfb + sudo apt-get install xvfb + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + if: runner.os == 'Linux' + with: + install-linux-dependencies: true + build-type: "Release" + version: "3.2.14" - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -157,13 +164,15 @@ jobs: compression-level: 0 test-docs: - needs: [ruff, mypy] + needs: [ruff, mypy, sdist] runs-on: ubuntu-latest steps: - - name: Install APT dependencies - run: | - sudo apt-get update - sudo apt-get install libsdl2-dev + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + if: runner.os == 'Linux' + with: + install-linux-dependencies: true + build-type: "Debug" + version: "3.2.14" - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} @@ -182,11 +191,11 @@ jobs: run: python -m sphinx -T -E -W --keep-going . _build/html tox: - needs: [ruff] + needs: [ruff, sdist] runs-on: ${{ matrix.os }} strategy: matrix: - os: ["ubuntu-latest", "windows-latest"] + os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues steps: - uses: actions/checkout@v4 with: @@ -200,17 +209,18 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip tox - - name: Install APT dependencies + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install libsdl2-dev + with: + install-linux-dependencies: true + build-type: "Debug" + version: "3.2.14" - name: Run tox run: | tox -vv linux-wheels: - needs: [ruff, mypy] + needs: [ruff, mypy, sdist] runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}} strategy: matrix: @@ -248,7 +258,18 @@ jobs: CIBW_BEFORE_ALL_LINUX: > yum install -y epel-release && yum-config-manager --enable epel && - yum install -y SDL2-devel + yum install -y gcc git-core make cmake \ + alsa-lib-devel pulseaudio-libs-devel \ + libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \ + libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \ + systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ + mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ + libdrm-devel mesa-libgbm-devel libusb-devel + git clone --depth 1 --branch release-3.2.10 https://github.com/libsdl-org/SDL.git sdl_repo && + cmake -S sdl_repo -B sdl_build && + cmake --build sdl_build --config Release && + cmake --install sdl_build --config Release --prefix /usr/local && + cp --verbose /usr/local/lib64/pkgconfig/sdl3.pc /lib64/pkgconfig/sdl3.pc CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod.context" # Skip test on emulated architectures @@ -267,7 +288,7 @@ jobs: compression-level: 0 build-macos: - needs: [ruff, mypy] + needs: [ruff, mypy, sdist] runs-on: "macos-14" strategy: fail-fast: true @@ -287,7 +308,7 @@ jobs: - name: Install Python dependencies run: pip install -r requirements.txt - name: Prepare package - # Downloads SDL2 for the later step. + # Downloads SDL for the later step. run: python build_sdl.py - name: Build wheels uses: pypa/cibuildwheel@v2.23.3 @@ -298,7 +319,7 @@ jobs: CIBW_BEFORE_TEST: pip install numpy CIBW_TEST_COMMAND: python -c "import tcod.context" CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" - MACOSX_DEPLOYMENT_TARGET: "10.11" + MACOSX_DEPLOYMENT_TARGET: "10.13" - name: Remove asterisk from label run: | PYTHON_DESC=${{ matrix.python }} diff --git a/.gitignore b/.gitignore index 6ec1f7fb..28aabdcd 100644 --- a/.gitignore +++ b/.gitignore @@ -69,10 +69,10 @@ Release/ tcod/_*.c tcod/_*.pyd .benchmarks -dependencies/SDL2* +dependencies/SDL* tcod/x86 tcod/x64 -tcod/SDL2.framework +tcod/SDL?.framework deb_dist/ *.tar.gz tcod/version.py diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 81bb11ae..8d733f5e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,11 +5,11 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: python: "3.11" apt_packages: - - libsdl2-dev + - libsdl3-dev submodules: include: all diff --git a/.vscode/settings.json b/.vscode/settings.json index e3434adc..446a6363 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -96,6 +96,7 @@ "condlist", "consolas", "contextdata", + "contextlib", "CONTROLLERAXISMOTION", "CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP", @@ -112,6 +113,7 @@ "dataclasses", "datas", "DBLAMPERSAND", + "DBLAPOSTROPHE", "DBLVERTICALBAR", "dcost", "DCROSS", @@ -139,6 +141,7 @@ "dunder", "DVLINE", "elif", + "ENDCALL", "endianness", "epel", "epub", @@ -250,6 +253,7 @@ "lerp", "letterpaper", "LGUI", + "LHYPER", "libsdl", "libtcod", "libtcodpy", @@ -305,6 +309,7 @@ "neww", "noarchive", "NODISCARD", + "NOMESSAGE", "Nonrepresentable", "NONUSBACKSLASH", "NONUSHASH", @@ -315,10 +320,12 @@ "numpy", "ogrid", "ogrids", + "oldnames", "onefile", "OPENGL", "opensearch", "OPER", + "OVERSCAN", "packbits", "PAGEDOWN", "pagerefs", @@ -365,6 +372,7 @@ "repr", "rgba", "RGUI", + "RHYPER", "RIGHTBRACE", "RIGHTBRACKET", "RIGHTDOWN", @@ -406,6 +414,8 @@ "SIZEWE", "SMILIE", "snprintf", + "SOFTLEFT", + "SOFTRIGHT", "soundfile", "sourcelink", "sphinxstrong", @@ -444,6 +454,7 @@ "typestr", "undoc", "Unifont", + "UNISTD", "unraisable", "unraisablehook", "unraiseable", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce0d62c..0a3495f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,45 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- Switched to SDL3. + This will cause several breaking changes such as the names of keyboard constants and other SDL enums. +- `tcod.sdl.video.Window.grab` has been split into `.mouse_grab` and `.keyboard_grab` attributes. +- `tcod.event.KeySym` single letter symbols are now all uppercase. +- Relative mouse mode is set via `tcod.sdl.video.Window.relative_mouse_mode` instead of `tcod.sdl.mouse.set_relative_mode`. +- `tcod.sdl.render.new_renderer`: Removed `software` and `target_textures` parameters, `vsync` takes `int`, `driver` takes `str` instead of `int`. +- SDL renderer logical +- `tcod.sdl.render.Renderer`: `integer_scaling` and `logical_size` are now set with `set_logical_presentation` method. +- `tcod.sdl.render.Renderer.geometry` now takes float values for `color` instead of 8-bit integers. +- `tcod.event.Point` and other mouse/tile coordinate types now use `float` instead of `int`. + SDL3 has decided that mouse events have subpixel precision. + If you see any usual `float` types in your code then this is why. +- `tcod.sdl.audio` has been affected by major changes to SDL3. + - `tcod.sdl.audio.open` has new behavior due to SDL3 and should be avoided. + - Callbacks which were assigned to `AudioDevice`'s must now be applied to `AudioStream`'s instead. + - `AudioDevice`'s are now opened using references to existing devices. + - Sound queueing methods were moved from `AudioDevice` to a new `AudioStream` class. + - `BasicMixer` may require manually specifying `frequency` and `channels` to replicate old behavior. + - `get_devices` and `get_capture_devices` now return `dict[str, AudioDevice]`. + +### Deprecated + +- `tcod.sdl.audio.open` was replaced with a newer API, get a default device with `tcod.sdl.audio.get_default_playback().open()`. +- `tcod.sdl.audio.BasicMixer` should be replaced with `AudioStream`'s. +- Should no longer use `tcod.sdl.audio.AudioDevice` in a context, use `contextlib.closing` for the old behavior. + ### Removed - Support dropped for Python 3.8 and 3.9. +- Removed `Joystick.get_current_power` due to SDL3 changes. +- `WindowFlags.FULLSCREEN_DESKTOP` is now just `WindowFlags.FULLSCREEN` +- `tcod.sdl.render.Renderer.integer_scaling` removed. +- Removed `callback`, `spec`, `queued_samples`, `queue_audio`, and `dequeue_audio` attributes from `tcod.sdl.audio.AudioDevice`. + +### Fixed + +- `Joystick.get_ball` was broken. ## [18.1.0] - 2025-05-05 diff --git a/README.rst b/README.rst index 515c90a7..32e9164c 100755 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ For the most part it's just:: ============== * Python 3.10+ * Windows, Linux, or MacOS X 10.9+. -* On Linux, requires libsdl2 (2.0.10+). +* On Linux, requires libsdl3 =========== Changelog diff --git a/build_libtcod.py b/build_libtcod.py index 974904cc..f9a5e3dd 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -3,16 +3,22 @@ from __future__ import annotations +import ast import contextlib import glob import os import platform import re +import subprocess import sys from collections.abc import Iterable, Iterator from pathlib import Path from typing import Any, ClassVar +import attrs +import pycparser # type: ignore[import-untyped] +import pycparser.c_ast # type: ignore[import-untyped] +import pycparser.c_generator # type: ignore[import-untyped] from cffi import FFI # ruff: noqa: T201 @@ -204,15 +210,16 @@ def walk_sources(directory: str) -> Iterator[str]: extra_link_args.extend(GCC_CFLAGS[tdl_build]) ffi = FFI() -ffi.cdef(build_sdl.get_cdef()) +sdl_cdef, sdl_strings = build_sdl.get_cdef() +ffi.cdef(sdl_cdef) for include in includes: try: ffi.cdef(include.header) - except Exception: + except Exception: # noqa: PERF203 # Print the source, for debugging. print(f"Error with: {include.path}") for i, line in enumerate(include.header.split("\n"), 1): - print("%03i %s" % (i, line)) + print(f"{i:03i} {line}") raise ffi.cdef( """ @@ -221,7 +228,10 @@ def walk_sources(directory: str) -> Iterator[str]: ) ffi.set_source( module_name, - "#include \n#include ", + """\ +#include +#define SDL_oldnames_h_ +#include """, include_dirs=include_dirs, library_dirs=library_dirs, sources=sources, @@ -380,10 +390,10 @@ def write_library_constants() -> None: f.write(f"""{parse_sdl_attrs("SDL_SCANCODE", None)[0]}\n""") f.write("\n# --- SDL keyboard symbols ---\n") - f.write(f"""{parse_sdl_attrs("SDLK", None)[0]}\n""") + f.write(f"""{parse_sdl_attrs("SDLK_", None)[0]}\n""") f.write("\n# --- SDL keyboard modifiers ---\n") - f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("KMOD", None))) + f.write("{}\n_REVERSE_MOD_TABLE = {}\n".format(*parse_sdl_attrs("SDL_KMOD", None))) f.write("\n# --- SDL wheel ---\n") f.write("{}\n_REVERSE_WHEEL_TABLE = {}\n".format(*parse_sdl_attrs("SDL_MOUSEWHEEL", all_names))) @@ -407,6 +417,237 @@ def write_library_constants() -> None: Path("tcod/event.py").write_text(event_py, encoding="utf-8") + with Path("tcod/sdl/constants.py").open("w", encoding="utf-8") as f: + f.write('"""SDL private constants."""\n\n') + for name, value in sdl_strings.items(): + f.write(f"{name} = {ast.literal_eval(value)!r}\n") + + subprocess.run(["ruff", "format", "--silent", Path("tcod/sdl/constants.py")], check=True) # noqa: S603, S607 + + +def _fix_reserved_name(name: str) -> str: + """Add underscores to reserved Python keywords.""" + assert isinstance(name, str) + if name in ("def", "in"): + return name + "_" + return name + + +@attrs.define(frozen=True) +class ConvertedParam: + name: str = attrs.field(converter=_fix_reserved_name) + hint: str + original: str + + +def _type_from_names(names: list[str]) -> str: + if not names: + return "" + if names[-1] == "void": + return "None" + if names in (["unsigned", "char"], ["bool"]): + return "bool" + if names[-1] in ("size_t", "int", "ptrdiff_t"): + return "int" + if names[-1] in ("float", "double"): + return "float" + return "Any" + + +def _param_as_hint(node: pycparser.c_ast.Node, default_name: str) -> ConvertedParam: + original = pycparser.c_generator.CGenerator().visit(node) + name: str + names: list[str] + match node: + case pycparser.c_ast.Typename(type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names))): + # Unnamed type + return ConvertedParam(default_name, _type_from_names(names), original) + case pycparser.c_ast.Decl( + name=name, type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names)) + ): + # Named type + return ConvertedParam(name, _type_from_names(names), original) + case pycparser.c_ast.Decl( + name=name, + type=pycparser.c_ast.ArrayDecl( + type=pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names)) + ), + ): + # Named array + return ConvertedParam(name, "Any", original) + case pycparser.c_ast.Decl(name=name, type=pycparser.c_ast.PtrDecl()): + # Named pointer + return ConvertedParam(name, "Any", original) + case pycparser.c_ast.Typename(name=name, type=pycparser.c_ast.PtrDecl()): + # Forwarded struct + return ConvertedParam(name or default_name, "Any", original) + case pycparser.c_ast.TypeDecl(type=pycparser.c_ast.IdentifierType(names=names)): + # Return type + return ConvertedParam(default_name, _type_from_names(names), original) + case pycparser.c_ast.PtrDecl(): + # Return pointer + return ConvertedParam(default_name, "Any", original) + case pycparser.c_ast.EllipsisParam(): + # C variable args + return ConvertedParam("*__args", "Any", original) + case _: + raise AssertionError + + +class DefinitionCollector(pycparser.c_ast.NodeVisitor): # type: ignore[misc] + """Gathers functions and names from C headers.""" + + def __init__(self) -> None: + """Initialize the object with empty values.""" + self.functions: list[str] = [] + """Indented Python function definitions.""" + self.variables: set[str] = set() + """Python variable definitions.""" + + def parse_defines(self, string: str, /) -> None: + """Parse C define directives into hinted names.""" + for match in re.finditer(r"#define\s+(\S+)\s+(\S+)\s*", string): + name, value = match.groups() + if value == "...": + self.variables.add(f"{name}: Final[int]") + else: + self.variables.add(f"{name}: Final[Literal[{value}]] = {value}") + + def visit_Decl(self, node: pycparser.c_ast.Decl) -> None: # noqa: N802 + """Parse C FFI functions into type hinted Python functions.""" + match node: + case pycparser.c_ast.Decl( + type=pycparser.c_ast.FuncDecl(), + ): + assert isinstance(node.type.args, pycparser.c_ast.ParamList), type(node.type.args) + arg_hints = [_param_as_hint(param, f"arg{i}") for i, param in enumerate(node.type.args.params)] + return_hint = _param_as_hint(node.type.type, "") + if len(arg_hints) == 1 and arg_hints[0].hint == "None": # Remove void parameter + arg_hints = [] + + python_params = [f"{p.name}: {p.hint}" for p in arg_hints] + if python_params: + if arg_hints[-1].name.startswith("*"): + python_params.insert(-1, "/") + else: + python_params.append("/") + c_def = pycparser.c_generator.CGenerator().visit(node) + python_def = f"""def {node.name}({", ".join(python_params)}) -> {return_hint.hint}:""" + self.functions.append(f''' {python_def}\n """{c_def}"""''') + + def visit_Enumerator(self, node: pycparser.c_ast.Enumerator) -> None: # noqa: N802 + """Parse C enums into hinted names.""" + name: str | None + value: str | int + match node: + case pycparser.c_ast.Enumerator(name=name, value=None): + self.variables.add(f"{name}: Final[int]") + case pycparser.c_ast.Enumerator(name=name, value=pycparser.c_ast.ID()): + self.variables.add(f"{name}: Final[int]") + case pycparser.c_ast.Enumerator(name=name, value=pycparser.c_ast.Constant(value=value)): + value = int(str(value).removesuffix("u"), base=0) + self.variables.add(f"{name}: Final[Literal[{value}]] = {value}") + case pycparser.c_ast.Enumerator( + name=name, value=pycparser.c_ast.UnaryOp(op="-", expr=pycparser.c_ast.Constant(value=value)) + ): + value = -int(str(value).removesuffix("u"), base=0) + self.variables.add(f"{name}: Final[Literal[{value}]] = {value}") + case pycparser.c_ast.Enumerator(name=name): + self.variables.add(f"{name}: Final[int]") + case _: + raise AssertionError + + +def write_hints() -> None: + """Write a custom _libtcod.pyi file from C definitions.""" + function_collector = DefinitionCollector() + c = pycparser.CParser() + + # Parse SDL headers + cdef = sdl_cdef + cdef = cdef.replace("int...", "int") + cdef = ( + """ +typedef int bool; +typedef int int8_t; +typedef int uint8_t; +typedef int int16_t; +typedef int uint16_t; +typedef int int32_t; +typedef int uint32_t; +typedef int int64_t; +typedef int uint64_t; +typedef int wchar_t; +typedef int intptr_t; +""" + + cdef + ) + for match in re.finditer(r"SDL_PIXELFORMAT_\w+", cdef): + function_collector.variables.add(f"{match.group()}: int") + cdef = re.sub(r"(typedef enum SDL_PixelFormat).*(SDL_PixelFormat;)", r"\1 \2", cdef, flags=re.DOTALL) + cdef = cdef.replace("padding[...]", "padding[]") + cdef = cdef.replace("...;} SDL_TouchFingerEvent;", "} SDL_TouchFingerEvent;") + function_collector.parse_defines(cdef) + cdef = re.sub(r"\n#define .*", "", cdef) + cdef = re.sub(r"""extern "Python" \{(.*?)\}""", r"\1", cdef, flags=re.DOTALL) + cdef = re.sub(r"//.*", "", cdef) + cdef = cdef.replace("...;", ";") + ast = c.parse(cdef) + function_collector.visit(ast) + + # Parse libtcod headers + cdef = "\n".join(include.header for include in includes) + function_collector.parse_defines(cdef) + cdef = re.sub(r"\n?#define .*", "", cdef) + cdef = re.sub(r"//.*", "", cdef) + cdef = ( + """ +typedef int int8_t; +typedef int uint8_t; +typedef int int16_t; +typedef int uint16_t; +typedef int int32_t; +typedef int uint32_t; +typedef int int64_t; +typedef int uint64_t; +typedef int wchar_t; +typedef int intptr_t; +typedef int ptrdiff_t; +typedef int size_t; +typedef unsigned char bool; +typedef void* SDL_PropertiesID; +""" + + cdef + ) + cdef = re.sub(r"""extern "Python" \{(.*?)\}""", r"\1", cdef, flags=re.DOTALL) + function_collector.visit(c.parse(cdef)) + function_collector.variables.add("TCOD_ctx: Any") + function_collector.variables.add("TCOD_COMPILEDVERSION: Final[int]") + + # Write PYI file + out_functions = """\n\n @staticmethod\n""".join(sorted(function_collector.functions)) + out_variables = "\n ".join(sorted(function_collector.variables)) + + pyi = f"""\ +# Autogenerated with build_libtcod.py +from typing import Any, Final, Literal + +# pyi files for CFFI ports are not standard +# ruff: noqa: A002, ANN401, D402, D403, D415, N801, N802, N803, N815, PLW0211, PYI021 + +class _lib: + @staticmethod +{out_functions} + + {out_variables} + +lib: _lib +ffi: Any +""" + Path("tcod/_libtcod.pyi").write_text(pyi) + subprocess.run(["ruff", "format", "--silent", Path("tcod/_libtcod.pyi")], check=True) # noqa: S603, S607 + if __name__ == "__main__": + write_hints() write_library_constants() diff --git a/build_sdl.py b/build_sdl.py index e395c4d4..08a6f667 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -4,6 +4,7 @@ from __future__ import annotations import io +import logging import os import platform import re @@ -12,6 +13,7 @@ import sys import zipfile from pathlib import Path +from tempfile import TemporaryDirectory from typing import Any import pcpp # type: ignore[import-untyped] @@ -20,14 +22,20 @@ # This script calls a lot of programs. # ruff: noqa: S603, S607, T201 +# Ignore f-strings in logging, these will eventually be replaced with t-strings. +# ruff: noqa: G004 + +logger = logging.getLogger(__name__) + + BIT_SIZE, LINKAGE = platform.architecture() # Reject versions of SDL older than this, update the requirements in the readme if you change this. -SDL_MIN_VERSION = (2, 0, 10) -# The SDL2 version to parse and export symbols from. -SDL2_PARSE_VERSION = os.environ.get("SDL_VERSION", "2.0.20") -# The SDL2 version to include in binary distributions. -SDL2_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "2.28.1") +SDL_MIN_VERSION = (3, 2, 0) +# The SDL version to parse and export symbols from. +SDL_PARSE_VERSION = os.environ.get("SDL_VERSION", "3.2.10") +# The SDL version to include in binary distributions. +SDL_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "3.2.10") # Used to remove excessive newlines in debug outputs. @@ -41,16 +49,49 @@ # Most SDLK names need their values scrubbed. RE_SDLK = re.compile(r"(?PSDLK_\w+) =.*?(?=,\n|}\n)") # Remove compile time assertions from the cdef. -RE_ASSERT = re.compile(r"^.*SDL_compile_time_assert.*$", re.MULTILINE) +RE_ASSERT = re.compile(r"^.*SDL_COMPILE_TIME_ASSERT.*$", re.MULTILINE) # Padding values need to be scrubbed. RE_PADDING = re.compile(r"padding\[[^;]*\];") # These structs have an unusual size when packed by SDL on 32-bit platforms. FLEXIBLE_STRUCTS = ( - "SDL_AudioCVT", + "SDL_CommonEvent", + "SDL_DisplayEvent", + "SDL_WindowEvent", + "SDL_KeyboardDeviceEvent", + "SDL_KeyboardEvent", + "SDL_TextEditingEvent", + "SDL_TextEditingCandidatesEvent", + "SDL_TextInputEvent", + "SDL_MouseDeviceEvent", + "SDL_MouseMotionEvent", + "SDL_MouseButtonEvent", + "SDL_MouseWheelEvent", + "SDL_JoyAxisEvent", + "SDL_JoyBallEvent", + "SDL_JoyHatEvent", + "SDL_JoyButtonEvent", + "SDL_JoyDeviceEvent", + "SDL_JoyBatteryEvent", + "SDL_GamepadAxisEvent", + "SDL_GamepadButtonEvent", + "SDL_GamepadDeviceEvent", + "SDL_GamepadTouchpadEvent", + "SDL_GamepadSensorEvent", + "SDL_AudioDeviceEvent", + "SDL_CameraDeviceEvent", + "SDL_RenderEvent", "SDL_TouchFingerEvent", - "SDL_MultiGestureEvent", - "SDL_DollarGestureEvent", + "SDL_PenProximityEvent", + "SDL_PenMotionEvent", + "SDL_PenTouchEvent", + "SDL_PenButtonEvent", + "SDL_PenAxisEvent", + "SDL_DropEvent", + "SDL_ClipboardEvent", + "SDL_SensorEvent", + "SDL_QuitEvent", + "SDL_UserEvent", ) # Other defined names which sometimes cause issues when parsed. @@ -63,6 +104,9 @@ "SDL_HAS_FALLTHROUGH", "SDL_NO_THREAD_SAFETY_ANALYSIS", "SDL_SCOPED_CAPABILITY", + "SDL_NODISCARD", + "SDL_NOLONGLONG", + "SDL_WINAPI_FAMILY_PHONE", # Might show up in parsing and not in source. "SDL_ANDROID_EXTERNAL_STORAGE_READ", "SDL_ANDROID_EXTERNAL_STORAGE_WRITE", @@ -82,63 +126,65 @@ def check_sdl_version() -> None: - """Check the local SDL version on Linux distributions.""" + """Check the local SDL3 version on Linux distributions.""" if not sys.platform.startswith("linux"): return needed_version = f"{SDL_MIN_VERSION[0]}.{SDL_MIN_VERSION[1]}.{SDL_MIN_VERSION[2]}" try: - sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() + sdl_version_str = subprocess.check_output( + ["pkg-config", "sdl3", "--modversion"], universal_newlines=True + ).strip() except FileNotFoundError as exc: msg = ( - "libsdl2-dev or equivalent must be installed on your system and must be at least version" - f" {needed_version}.\nsdl2-config must be on PATH." + "libsdl3-dev or equivalent must be installed on your system and must be at least version" + f" {needed_version}.\nsdl3-config must be on PATH." ) raise RuntimeError(msg) from exc - print(f"Found SDL {sdl_version_str}.") + logger.info(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_MIN_VERSION: msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})" raise RuntimeError(msg) -def get_sdl2_file(version: str) -> Path: - """Return a path to an SDL2 archive for the current platform. The archive is downloaded if missing.""" +def get_sdl_file(version: str) -> Path: + """Return a path to an SDL3 archive for the current platform. The archive is downloaded if missing.""" if sys.platform == "win32": - sdl2_file = f"SDL2-devel-{version}-VC.zip" + sdl_archive = f"SDL3-devel-{version}-VC.zip" else: assert sys.platform == "darwin" - sdl2_file = f"SDL2-{version}.dmg" - sdl2_local_file = Path("dependencies", sdl2_file) - sdl2_remote_file = f"https://www.libsdl.org/release/{sdl2_file}" - if not sdl2_local_file.exists(): - print(f"Downloading {sdl2_remote_file}") + sdl_archive = f"SDL3-{version}.dmg" + sdl_local_file = Path("dependencies", sdl_archive) + sdl_remote_url = f"https://www.libsdl.org/release/{sdl_archive}" + if not sdl_local_file.exists(): + logger.info(f"Downloading {sdl_remote_url}") Path("dependencies/").mkdir(parents=True, exist_ok=True) - with requests.get(sdl2_remote_file) as response: # noqa: S113 + with requests.get(sdl_remote_url) as response: # noqa: S113 response.raise_for_status() - sdl2_local_file.write_bytes(response.content) - return sdl2_local_file + sdl_local_file.write_bytes(response.content) + return sdl_local_file -def unpack_sdl2(version: str) -> Path: +def unpack_sdl(version: str) -> Path: """Return the path to an extracted SDL distribution. Creates it if missing.""" - sdl2_path = Path(f"dependencies/SDL2-{version}") + sdl_path = Path(f"dependencies/SDL3-{version}") if sys.platform == "darwin": - sdl2_dir = sdl2_path - sdl2_path /= "SDL2.framework" - if sdl2_path.exists(): - return sdl2_path - sdl2_arc = get_sdl2_file(version) - print(f"Extracting {sdl2_arc}") - if sdl2_arc.suffix == ".zip": - with zipfile.ZipFile(sdl2_arc) as zf: + sdl_dir = sdl_path + sdl_path /= "SDL3.framework" + if sdl_path.exists(): + return sdl_path + sdl_archive = get_sdl_file(version) + logger.info(f"Extracting {sdl_archive}") + if sdl_archive.suffix == ".zip": + with zipfile.ZipFile(sdl_archive) as zf: zf.extractall("dependencies/") elif sys.platform == "darwin": - assert sdl2_arc.suffix == ".dmg" - subprocess.check_call(["hdiutil", "mount", sdl2_arc]) - subprocess.check_call(["mkdir", "-p", sdl2_dir]) - subprocess.check_call(["cp", "-r", "/Volumes/SDL2/SDL2.framework", sdl2_dir]) - subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL2"]) - return sdl2_path + assert sdl_archive.suffix == ".dmg" + subprocess.check_call(["hdiutil", "mount", sdl_archive]) + subprocess.check_call(["mkdir", "-p", sdl_dir]) + subprocess.check_call(["cp", "-r", "/Volumes/SDL3/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework", sdl_dir]) + subprocess.check_call(["hdiutil", "unmount", "/Volumes/SDL3"]) + return sdl_path class SDLParser(pcpp.Preprocessor): # type: ignore[misc] @@ -161,6 +207,7 @@ def get_output(self) -> str: def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: # noqa: ARG002, FBT001 """Remove bad includes such as stddef.h and stdarg.h.""" + assert "SDL3/SDL" not in includepath, (includepath, curdir) raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove) def _should_track_define(self, tokens: list[Any]) -> bool: @@ -180,11 +227,9 @@ def _should_track_define(self, tokens: list[Any]) -> bool: return False # Value is a floating point number. if tokens[0].value.startswith("SDL_PR") and (tokens[0].value.endswith("32") or tokens[0].value.endswith("64")): return False # Data type for printing, which is not needed. - return bool( - tokens[0].value.startswith("KMOD_") - or tokens[0].value.startswith("SDL_") - or tokens[0].value.startswith("AUDIO_") - ) + if tokens[0].value.startswith("SDL_PLATFORM_"): + return False # Ignore platform definitions + return bool(str(tokens[0].value).startswith(("SDL_", "SDLK_"))) def on_directive_handle( self, @@ -197,6 +242,8 @@ def on_directive_handle( if directive.value == "define" and self._should_track_define(tokens): if tokens[2].type == "CPP_STRING": self.known_string_defines[tokens[0].value] = tokens[2].value + elif tokens[2].value in self.known_string_defines: + self.known_string_defines[tokens[0].value] = "..." else: self.known_defines.add(tokens[0].value) return super().on_directive_handle(directive, tokens, if_passthru, preceding_tokens) @@ -205,25 +252,28 @@ def on_directive_handle( check_sdl_version() if sys.platform == "win32" or sys.platform == "darwin": - SDL2_PARSE_PATH = unpack_sdl2(SDL2_PARSE_VERSION) - SDL2_BUNDLE_PATH = unpack_sdl2(SDL2_BUNDLE_VERSION) + SDL_PARSE_PATH = unpack_sdl(SDL_PARSE_VERSION) + SDL_BUNDLE_PATH = unpack_sdl(SDL_BUNDLE_VERSION) -SDL2_INCLUDE: Path +SDL_INCLUDE: Path if sys.platform == "win32": - SDL2_INCLUDE = SDL2_PARSE_PATH / "include" + SDL_INCLUDE = SDL_PARSE_PATH / "include" elif sys.platform == "darwin": - SDL2_INCLUDE = SDL2_PARSE_PATH / "Versions/A/Headers" + SDL_INCLUDE = SDL_PARSE_PATH / "Versions/A/Headers" else: # Unix matches = re.findall( r"-I(\S+)", - subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True), + subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True), ) assert matches for match in matches: - if Path(match, "SDL_stdinc.h").is_file(): - SDL2_INCLUDE = match - assert SDL2_INCLUDE + if Path(match, "SDL3/SDL.h").is_file(): + SDL_INCLUDE = Path(match) + break + else: + raise AssertionError(matches) + assert SDL_INCLUDE EXTRA_CDEF = """ @@ -231,7 +281,7 @@ def on_directive_handle( extern "Python" { // SDL_AudioCallback callback. -void _sdl_audio_callback(void* userdata, Uint8* stream, int len); +void _sdl_audio_stream_callback(void* userdata, SDL_AudioStream *stream, int additional_amount, int total_amount); // SDL to Python log function. void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message); // Generic event watcher callback. @@ -240,43 +290,57 @@ def on_directive_handle( """ -def get_cdef() -> str: +def get_cdef() -> tuple[str, dict[str, str]]: """Return the parsed code of SDL for CFFI.""" - parser = SDLParser() - parser.add_path(SDL2_INCLUDE) - parser.parse( + with TemporaryDirectory() as temp_dir: + # Add a false SDL_oldnames.h to prevent old symbols from being collected + fake_header_dir = Path(temp_dir, "SDL3") + fake_header_dir.mkdir() + (fake_header_dir / "SDL_oldnames.h").write_text("") + + parser = SDLParser() + parser.add_path(temp_dir) + parser.add_path(SDL_INCLUDE) + if Path(SDL_INCLUDE, "../Headers/SDL.h").exists(): # Using MacOS dmg archive + fake_sdl_dir = Path(SDL_INCLUDE, "SDL3") + if not fake_sdl_dir.exists(): + fake_sdl_dir.mkdir(exist_ok=False) + for file in SDL_INCLUDE.glob("SDL*.h"): + shutil.copyfile(file, fake_sdl_dir / file.name) + else: # Regular path + assert Path(SDL_INCLUDE, "SDL3/SDL.h").exists(), SDL_INCLUDE + parser.parse( + """ + // Remove extern keyword. + #define extern + // Ignore some SDL assert statements. + #define DOXYGEN_SHOULD_IGNORE_THIS + #define SDL_COMPILE_TIME_ASSERT(x, y) + + #define _SIZE_T_DEFINED_ + typedef int... size_t; + #define bool _Bool + + #define SDL_oldnames_h_ + + #include """ - // Remove extern keyword. - #define extern - // Ignore some SDL assert statements. - #define DOXYGEN_SHOULD_IGNORE_THIS - - #define _SIZE_T_DEFINED_ - typedef int... size_t; - - // Skip these headers. - #define SDL_atomic_h_ - #define SDL_thread_h_ - - #include - """ - ) - sdl2_cdef = parser.get_output() - sdl2_cdef = RE_VAFUNC.sub("", sdl2_cdef) - sdl2_cdef = RE_INLINE.sub("", sdl2_cdef) - sdl2_cdef = RE_PIXELFORMAT.sub(r"\g = ...", sdl2_cdef) - sdl2_cdef = RE_SDLK.sub(r"\g = ...", sdl2_cdef) - sdl2_cdef = RE_NEWLINES.sub("\n", sdl2_cdef) - sdl2_cdef = RE_ASSERT.sub("", sdl2_cdef) - sdl2_cdef = RE_PADDING.sub("padding[...];", sdl2_cdef) - sdl2_cdef = ( - sdl2_cdef.replace("int SDL_main(int argc, char *argv[]);", "") - .replace("typedef unsigned int uintptr_t;", "typedef int... uintptr_t;") - .replace("typedef unsigned int size_t;", "typedef int... size_t;") + ) + sdl_cdef = parser.get_output() + + sdl_cdef = sdl_cdef.replace("_Bool", "bool") + sdl_cdef = RE_VAFUNC.sub("", sdl_cdef) + sdl_cdef = RE_INLINE.sub("", sdl_cdef) + sdl_cdef = RE_PIXELFORMAT.sub(r"\g = ...", sdl_cdef) + sdl_cdef = RE_SDLK.sub(r"\g = ...", sdl_cdef) + sdl_cdef = RE_NEWLINES.sub("\n", sdl_cdef) + sdl_cdef = RE_PADDING.sub("padding[...];", sdl_cdef) + sdl_cdef = sdl_cdef.replace("typedef unsigned int uintptr_t;", "typedef int... uintptr_t;").replace( + "typedef unsigned int size_t;", "typedef int... size_t;" ) for name in FLEXIBLE_STRUCTS: - sdl2_cdef = sdl2_cdef.replace(f"}} {name};", f"...;}} {name};") - return sdl2_cdef + EXTRA_CDEF + sdl_cdef = sdl_cdef.replace(f"}} {name};", f"...;}} {name};") + return sdl_cdef + EXTRA_CDEF, parser.known_string_defines include_dirs: list[str] = [] @@ -288,33 +352,36 @@ def get_cdef() -> str: if sys.platform == "darwin": - extra_link_args += ["-framework", "SDL2"] + extra_link_args += ["-framework", "SDL3"] else: - libraries += ["SDL2"] + libraries += ["SDL3"] -# Bundle the Windows SDL2 DLL. +# Bundle the Windows SDL DLL. if sys.platform == "win32": - include_dirs.append(str(SDL2_INCLUDE)) + include_dirs.append(str(SDL_INCLUDE)) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL2_LIB_DIR = Path(SDL2_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) - library_dirs.append(str(SDL2_LIB_DIR)) - SDL2_LIB_DEST = Path("tcod", ARCH_MAPPING[BIT_SIZE]) - SDL2_LIB_DEST.mkdir(exist_ok=True) - SDL2_LIB_DEST_FILE = SDL2_LIB_DEST / "SDL2.dll" - SDL2_LIB_FILE = SDL2_LIB_DIR / "SDL2.dll" - if not SDL2_LIB_DEST_FILE.exists() or SDL2_LIB_FILE.read_bytes() != SDL2_LIB_DEST_FILE.read_bytes(): - shutil.copy(SDL2_LIB_FILE, SDL2_LIB_DEST_FILE) - -# Link to the SDL2 framework on MacOS. + SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) + library_dirs.append(str(SDL_LIB_DIR)) + SDL_LIB_DEST = Path("tcod", ARCH_MAPPING[BIT_SIZE]) + SDL_LIB_DEST.mkdir(exist_ok=True) + SDL_LIB_DEST_FILE = SDL_LIB_DEST / "SDL3.dll" + SDL_LIB_FILE = SDL_LIB_DIR / "SDL3.dll" + if not SDL_LIB_DEST_FILE.exists() or SDL_LIB_FILE.read_bytes() != SDL_LIB_DEST_FILE.read_bytes(): + shutil.copy(SDL_LIB_FILE, SDL_LIB_DEST_FILE) + +# Link to the SDL framework on MacOS. # Delocate will bundle the binaries in a later step. if sys.platform == "darwin": - HEADER_DIR = Path(SDL2_PARSE_PATH, "Headers") - include_dirs.append(HEADER_DIR) - extra_link_args += [f"-F{SDL2_BUNDLE_PATH}/.."] - extra_link_args += ["-rpath", f"{SDL2_BUNDLE_PATH}/.."] + include_dirs.append(SDL_INCLUDE) + extra_link_args += [f"-F{SDL_BUNDLE_PATH}/.."] + extra_link_args += ["-rpath", f"{SDL_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] -# Use sdl2-config to link to SDL2 on Linux. +# Use sdl-config to link to SDL on Linux. if sys.platform not in ["win32", "darwin"]: - extra_compile_args += subprocess.check_output(["sdl2-config", "--cflags"], universal_newlines=True).strip().split() - extra_link_args += subprocess.check_output(["sdl2-config", "--libs"], universal_newlines=True).strip().split() + extra_compile_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split() + ) + extra_link_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() + ) diff --git a/docs/sdl/joystick.rst b/docs/sdl/joystick.rst index 60c7c26e..397cd1e2 100644 --- a/docs/sdl/joystick.rst +++ b/docs/sdl/joystick.rst @@ -3,9 +3,3 @@ SDL Joystick Support ``tcod.sdl.joystick`` .. automodule:: tcod.sdl.joystick :members: - :exclude-members: - Power - -.. autoclass:: tcod.sdl.joystick.Power - :members: - :member-order: bysource diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 10aed249..7f2f059e 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -1,4 +1,4 @@ -SDL2 Event Handling ``tcod.event`` +SDL Event Handling ``tcod.event`` ================================== .. automodule:: tcod.event diff --git a/examples/audio_tone.py b/examples/audio_tone.py index 22f21133..b65bb732 100755 --- a/examples/audio_tone.py +++ b/examples/audio_tone.py @@ -1,17 +1,15 @@ #!/usr/bin/env python -"""Shows how to use tcod.sdl.audio to play a custom-made audio stream. +"""Shows how to use tcod.sdl.audio to play audio. -Opens an audio device using SDL and plays a square wave for 1 second. +Opens an audio device using SDL then plays tones using various methods. """ import math import time -from typing import Any import attrs import numpy as np -from numpy.typing import NDArray -from scipy import signal # type: ignore +from scipy import signal # type: ignore[import-untyped] import tcod.sdl.audio @@ -22,28 +20,56 @@ class PullWave: """Square wave stream generator for an SDL audio device in pull mode.""" + frequency: float time: float = 0.0 - def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None: + def __call__(self, stream: tcod.sdl.audio.AudioStream, request: tcod.sdl.audio.AudioStreamCallbackData) -> None: """Stream a square wave to SDL on demand. This function must run faster than the stream duration. Numpy is used to keep performance within these limits. """ - sample_rate = device.frequency - n_samples = device.buffer_samples - duration = n_samples / sample_rate - print(f"{duration=} {self.time=}") + duration = request.additional_samples / self.frequency - t = np.linspace(self.time, self.time + duration, n_samples, endpoint=False) + t = np.linspace(self.time, self.time + duration, request.additional_samples, endpoint=False) self.time += duration wave = signal.square(t * (math.tau * 440)).astype(np.float32) - wave *= VOLUME - - stream[:] = device.convert(wave) + stream.queue_audio(wave) if __name__ == "__main__": - with tcod.sdl.audio.open(callback=PullWave()) as device: - print(device) - time.sleep(1) + device = tcod.sdl.audio.get_default_playback().open(channels=1, frequency=44100) + print(f"{device.name=}") + device.gain = VOLUME + print(device) + + print("Sawtooth wave queued with AudioStream.queue_audio") + stream = device.new_stream(format=np.float32, channels=1, frequency=44100) + t = np.linspace(0, 1.0, 44100, endpoint=False) + wave = signal.sawtooth(t * (math.tau * 440)).astype(np.float32) + stream.queue_audio(wave) + stream.flush() + while stream.queued_samples: + time.sleep(0.01) + + print("---") + time.sleep(0.5) + + print("Square wave attached to AudioStream.getter_callback") + stream = device.new_stream(format=np.float32, channels=1, frequency=44100) + stream.getter_callback = PullWave(device.frequency) + + time.sleep(1) + stream.getter_callback = None + + print("---") + time.sleep(0.5) + + print("Sawtooth wave played with BasicMixer.play") + mixer = tcod.sdl.audio.BasicMixer(device, frequency=44100, channels=2) + channel = mixer.play(wave) + while channel.busy: + time.sleep(0.01) + + print("---") + device.close() diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 6c0e6a1c..7c08906a 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -33,7 +33,6 @@ import tcod.sdl.render import tcod.tileset from tcod import libtcodpy -from tcod.sdl.video import WindowFlags if TYPE_CHECKING: from numpy.typing import NDArray @@ -101,8 +100,8 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.ALT: sdl_window = context.sdl_window if sdl_window: - sdl_window.fullscreen = False if sdl_window.fullscreen else WindowFlags.FULLSCREEN_DESKTOP - elif event.sym in (tcod.event.KeySym.PRINTSCREEN, tcod.event.KeySym.p): + sdl_window.fullscreen = not sdl_window.fullscreen + elif event.sym in (tcod.event.KeySym.PRINTSCREEN, tcod.event.KeySym.P): print("screenshot") if event.mod & tcod.event.Modifier.ALT: libtcodpy.console_save_apf(root_console, "samples.apf") @@ -469,27 +468,27 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: if tcod.event.KeySym.N9 >= event.sym >= tcod.event.KeySym.N1: self.func = event.sym - tcod.event.KeySym.N1 self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.e: + elif event.sym == tcod.event.KeySym.E: self.hurst += 0.1 self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.d: + elif event.sym == tcod.event.KeySym.D: self.hurst -= 0.1 self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.r: + elif event.sym == tcod.event.KeySym.R: self.lacunarity += 0.5 self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.f: + elif event.sym == tcod.event.KeySym.F: self.lacunarity -= 0.5 self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.t: + elif event.sym == tcod.event.KeySym.T: self.octaves += 0.5 self.noise.octaves = self.octaves - elif event.sym == tcod.event.KeySym.g: + elif event.sym == tcod.event.KeySym.G: self.octaves -= 0.5 self.noise.octaves = self.octaves - elif event.sym == tcod.event.KeySym.y: + elif event.sym == tcod.event.KeySym.Y: self.zoom += 0.2 - elif event.sym == tcod.event.KeySym.h: + elif event.sym == tcod.event.KeySym.H: self.zoom -= 0.2 else: super().ev_keydown(event) @@ -648,10 +647,10 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: MOVE_KEYS = { # noqa: N806 - tcod.event.KeySym.i: (0, -1), - tcod.event.KeySym.j: (-1, 0), - tcod.event.KeySym.k: (0, 1), - tcod.event.KeySym.l: (1, 0), + tcod.event.KeySym.I: (0, -1), + tcod.event.KeySym.J: (-1, 0), + tcod.event.KeySym.K: (0, 1), + tcod.event.KeySym.L: (1, 0), } FOV_SELECT_KEYS = { # noqa: N806 tcod.event.KeySym.MINUS: -1, @@ -664,9 +663,9 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: if self.walkable[self.player_x + x, self.player_y + y]: self.player_x += x self.player_y += y - elif event.sym == tcod.event.KeySym.t: + elif event.sym == tcod.event.KeySym.T: self.torch = not self.torch - elif event.sym == tcod.event.KeySym.w: + elif event.sym == tcod.event.KeySym.W: self.light_walls = not self.light_walls elif event.sym in FOV_SELECT_KEYS: self.algo_num += FOV_SELECT_KEYS[event.sym] @@ -740,13 +739,13 @@ def on_draw(self) -> None: def ev_keydown(self, event: tcod.event.KeyDown) -> None: """Handle movement and UI.""" - if event.sym == tcod.event.KeySym.i and self.dest_y > 0: # destination move north + if event.sym == tcod.event.KeySym.I and self.dest_y > 0: # destination move north self.dest_y -= 1 - elif event.sym == tcod.event.KeySym.k and self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south + elif event.sym == tcod.event.KeySym.K and self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south self.dest_y += 1 - elif event.sym == tcod.event.KeySym.j and self.dest_x > 0: # destination move west + elif event.sym == tcod.event.KeySym.J and self.dest_x > 0: # destination move west self.dest_x -= 1 - elif event.sym == tcod.event.KeySym.l and self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east + elif event.sym == tcod.event.KeySym.L and self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east self.dest_x += 1 elif event.sym == tcod.event.KeySym.TAB: self.using_astar = not self.using_astar @@ -758,8 +757,8 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: mx = event.tile.x - SAMPLE_SCREEN_X my = event.tile.y - SAMPLE_SCREEN_Y if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT: - self.dest_x = mx - self.dest_y = my + self.dest_x = int(mx) + self.dest_y = int(my) ############################################# @@ -1029,9 +1028,9 @@ def on_draw(self) -> None: sample_console.print( 1, 1, - f"Pixel position : {self.motion.position.x:4d}x{self.motion.position.y:4d}\n" - f"Tile position : {self.motion.tile.x:4d}x{self.motion.tile.y:4d}\n" - f"Tile movement : {self.motion.tile_motion.x:4d}x{self.motion.tile_motion.y:4d}\n" + f"Pixel position : {self.motion.position.x:4.0f}x{self.motion.position.y:4.0f}\n" + f"Tile position : {self.motion.tile.x:4.0f}x{self.motion.tile.y:4.0f}\n" + f"Tile movement : {self.motion.tile_motion.x:4.0f}x{self.motion.tile_motion.y:4.0f}\n" f"Left button : {'ON' if self.mouse_left else 'OFF'}\n" f"Right button : {'ON' if self.mouse_right else 'OFF'}\n" f"Middle button : {'ON' if self.mouse_middle else 'OFF'}\n", @@ -1209,7 +1208,7 @@ def on_draw(self) -> None: # new pixels are based on absolute elapsed time int_abs_t = int(self.abs_t) - texture = np.roll(texture, -int_t, 1) # type: ignore[assignment] + texture = np.roll(texture, -int_t, 1) # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(RES_U): @@ -1334,9 +1333,9 @@ def init_context(renderer: int) -> None: ) if context.sdl_renderer: # If this context supports SDL rendering. # Start by setting the logical size so that window resizing doesn't break anything. - context.sdl_renderer.logical_size = ( - tileset.tile_width * root_console.width, - tileset.tile_height * root_console.height, + context.sdl_renderer.set_logical_presentation( + resolution=(tileset.tile_width * root_console.width, tileset.tile_height * root_console.height), + mode=tcod.sdl.render.LogicalPresentation.STRETCH, ) assert context.sdl_atlas # Generate the console renderer and minimap. diff --git a/examples/sdl-hello-world.py b/examples/sdl-hello-world.py index 67b0fa01..02017f12 100644 --- a/examples/sdl-hello-world.py +++ b/examples/sdl-hello-world.py @@ -31,7 +31,7 @@ def main() -> None: """Show hello world until the window is closed.""" # Open an SDL window and renderer. window = tcod.sdl.video.new_window(720, 480, flags=tcod.sdl.video.WindowFlags.RESIZABLE) - renderer = tcod.sdl.render.new_renderer(window, target_textures=True) + renderer = tcod.sdl.render.new_renderer(window) # Render the text once, then reuse the texture. hello_world = render_text(renderer, "Hello World") hello_world.color_mod = (64, 255, 64) # Set the color when copied. diff --git a/libtcod b/libtcod index 747b2e9d..ffa44720 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit 747b2e9db06fca0f1281ba0ca6de173fbcb32eec +Subproject commit ffa447202e9b354691386e91f1288fd69dc1eaba diff --git a/pyproject.toml b/pyproject.toml index eac81af3..27a3dc16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ requires = [ "pycparser>=2.14", "pcpp==1.30", "requests>=2.28.1", + "attrs", ] build-backend = "setuptools.build_meta" @@ -116,6 +117,8 @@ warn_unused_ignores = true warn_return_any = true implicit_reexport = false strict_equality = true +strict_bytes = true +extra_checks = true exclude = [ "build/", "venv/", diff --git a/setup.py b/setup.py index a98b64b3..6a835e5b 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ # ruff: noqa: T201 -SDL_VERSION_NEEDED = (2, 0, 5) +SDL_VERSION_NEEDED = (3, 2, 0) SETUP_DIR = Path(__file__).parent # setup.py current directory @@ -28,11 +28,11 @@ def get_package_data() -> list[str]: ] if "win32" in sys.platform: if bit_size == "32bit": - files += ["x86/SDL2.dll"] + files += ["x86/SDL3.dll"] else: - files += ["x64/SDL2.dll"] + files += ["x64/SDL3.dll"] if sys.platform == "darwin": - files += ["SDL2.framework/Versions/A/SDL2"] + files += ["SDL3.framework/Versions/A/SDL3"] return files @@ -42,13 +42,19 @@ def check_sdl_version() -> None: return needed_version = "{}.{}.{}".format(*SDL_VERSION_NEEDED) try: - sdl_version_str = subprocess.check_output(["sdl2-config", "--version"], universal_newlines=True).strip() # noqa: S603, S607 - except FileNotFoundError as exc: - msg = ( - f"libsdl2-dev or equivalent must be installed on your system and must be at least version {needed_version}." - "\nsdl2-config must be on PATH." - ) - raise RuntimeError(msg) from exc + sdl_version_str = subprocess.check_output( + ["pkg-config", "sdl3", "--modversion"], # noqa: S607 + universal_newlines=True, + ).strip() + except FileNotFoundError: + try: + sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S603, S607 + except FileNotFoundError as exc: + msg = ( + f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." + "\nsdl3-config must be on PATH." + ) + raise RuntimeError(msg) from exc print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_VERSION_NEEDED: diff --git a/tcod/_libtcod.pyi b/tcod/_libtcod.pyi new file mode 100644 index 00000000..4b8ed0fe --- /dev/null +++ b/tcod/_libtcod.pyi @@ -0,0 +1,9729 @@ +# Autogenerated with build_libtcod.py +from typing import Any, Final, Literal + +# pyi files for CFFI ports are not standard +# ruff: noqa: A002, ANN401, D402, D403, D415, N801, N802, N803, N815, PLW0211, PYI021 + +class _lib: + @staticmethod + def NoiseGetSample(noise: Any, xyzw: Any, /) -> float: + """float NoiseGetSample(TDLNoise *noise, float *xyzw)""" + + @staticmethod + def NoiseSampleMeshGrid(noise: Any, len: Any, in_: Any, out: Any, /) -> None: + """void NoiseSampleMeshGrid(TDLNoise *noise, const long len, const float *in, float *out)""" + + @staticmethod + def NoiseSampleOpenMeshGrid(noise: Any, ndim: int, shape: Any, ogrid_in: Any, out: Any, /) -> None: + """void NoiseSampleOpenMeshGrid(TDLNoise *noise, const int ndim, const long *shape, const float **ogrid_in, float *out)""" + + @staticmethod + def PathCostArrayFloat32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayFloat32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayInt16(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayInt16(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayInt32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayInt8(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayInt8(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayUInt16(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayUInt16(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayUInt32(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayUInt32(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def PathCostArrayUInt8(x1: int, y1: int, x2: int, y2: int, map: Any, /) -> float: + """float PathCostArrayUInt8(int x1, int y1, int x2, int y2, const struct PathCostArray *map)""" + + @staticmethod + def SDL_AcquireCameraFrame(camera: Any, timestampNS: Any, /) -> Any: + """SDL_Surface *SDL_AcquireCameraFrame(SDL_Camera *camera, Uint64 *timestampNS)""" + + @staticmethod + def SDL_AcquireGPUCommandBuffer(device: Any, /) -> Any: + """SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer(SDL_GPUDevice *device)""" + + @staticmethod + def SDL_AcquireGPUSwapchainTexture( + command_buffer: Any, + window: Any, + swapchain_texture: Any, + swapchain_texture_width: Any, + swapchain_texture_height: Any, + /, + ) -> bool: + """bool SDL_AcquireGPUSwapchainTexture(SDL_GPUCommandBuffer *command_buffer, SDL_Window *window, SDL_GPUTexture **swapchain_texture, Uint32 *swapchain_texture_width, Uint32 *swapchain_texture_height)""" + + @staticmethod + def SDL_AddAtomicInt(a: Any, v: int, /) -> int: + """int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)""" + + @staticmethod + def SDL_AddEventWatch(filter: Any, userdata: Any, /) -> bool: + """bool SDL_AddEventWatch(SDL_EventFilter filter, void *userdata)""" + + @staticmethod + def SDL_AddGamepadMapping(mapping: Any, /) -> int: + """int SDL_AddGamepadMapping(const char *mapping)""" + + @staticmethod + def SDL_AddGamepadMappingsFromFile(file: Any, /) -> int: + """int SDL_AddGamepadMappingsFromFile(const char *file)""" + + @staticmethod + def SDL_AddGamepadMappingsFromIO(src: Any, closeio: bool, /) -> int: + """int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio)""" + + @staticmethod + def SDL_AddHintCallback(name: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)""" + + @staticmethod + def SDL_AddSurfaceAlternateImage(surface: Any, image: Any, /) -> bool: + """bool SDL_AddSurfaceAlternateImage(SDL_Surface *surface, SDL_Surface *image)""" + + @staticmethod + def SDL_AddTimer(interval: Any, callback: Any, userdata: Any, /) -> Any: + """SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)""" + + @staticmethod + def SDL_AddTimerNS(interval: Any, callback: Any, userdata: Any, /) -> Any: + """SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)""" + + @staticmethod + def SDL_AddVulkanRenderSemaphores( + renderer: Any, wait_stage_mask: Any, wait_semaphore: Any, signal_semaphore: Any, / + ) -> bool: + """bool SDL_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore)""" + + @staticmethod + def SDL_AsyncIOFromFile(file: Any, mode: Any, /) -> Any: + """SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode)""" + + @staticmethod + def SDL_AttachVirtualJoystick(desc: Any, /) -> Any: + """SDL_JoystickID SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc)""" + + @staticmethod + def SDL_AudioDevicePaused(devid: Any, /) -> bool: + """bool SDL_AudioDevicePaused(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_AudioStreamDevicePaused(stream: Any, /) -> bool: + """bool SDL_AudioStreamDevicePaused(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_BeginGPUComputePass( + command_buffer: Any, + storage_texture_bindings: Any, + num_storage_texture_bindings: Any, + storage_buffer_bindings: Any, + num_storage_buffer_bindings: Any, + /, + ) -> Any: + """SDL_GPUComputePass *SDL_BeginGPUComputePass(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUStorageTextureReadWriteBinding *storage_texture_bindings, Uint32 num_storage_texture_bindings, const SDL_GPUStorageBufferReadWriteBinding *storage_buffer_bindings, Uint32 num_storage_buffer_bindings)""" + + @staticmethod + def SDL_BeginGPUCopyPass(command_buffer: Any, /) -> Any: + """SDL_GPUCopyPass *SDL_BeginGPUCopyPass(SDL_GPUCommandBuffer *command_buffer)""" + + @staticmethod + def SDL_BeginGPURenderPass( + command_buffer: Any, color_target_infos: Any, num_color_targets: Any, depth_stencil_target_info: Any, / + ) -> Any: + """SDL_GPURenderPass *SDL_BeginGPURenderPass(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUColorTargetInfo *color_target_infos, Uint32 num_color_targets, const SDL_GPUDepthStencilTargetInfo *depth_stencil_target_info)""" + + @staticmethod + def SDL_BindAudioStream(devid: Any, stream: Any, /) -> bool: + """bool SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream)""" + + @staticmethod + def SDL_BindAudioStreams(devid: Any, streams: Any, num_streams: int, /) -> bool: + """bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *streams, int num_streams)""" + + @staticmethod + def SDL_BindGPUComputePipeline(compute_pass: Any, compute_pipeline: Any, /) -> None: + """void SDL_BindGPUComputePipeline(SDL_GPUComputePass *compute_pass, SDL_GPUComputePipeline *compute_pipeline)""" + + @staticmethod + def SDL_BindGPUComputeSamplers( + compute_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUComputeSamplers(SDL_GPUComputePass *compute_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUComputeStorageBuffers( + compute_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUComputeStorageBuffers(SDL_GPUComputePass *compute_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUComputeStorageTextures( + compute_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUComputeStorageTextures(SDL_GPUComputePass *compute_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUFragmentSamplers( + render_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUFragmentSamplers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUFragmentStorageBuffers( + render_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUFragmentStorageBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUFragmentStorageTextures( + render_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUFragmentStorageTextures(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUGraphicsPipeline(render_pass: Any, graphics_pipeline: Any, /) -> None: + """void SDL_BindGPUGraphicsPipeline(SDL_GPURenderPass *render_pass, SDL_GPUGraphicsPipeline *graphics_pipeline)""" + + @staticmethod + def SDL_BindGPUIndexBuffer(render_pass: Any, binding: Any, index_element_size: Any, /) -> None: + """void SDL_BindGPUIndexBuffer(SDL_GPURenderPass *render_pass, const SDL_GPUBufferBinding *binding, SDL_GPUIndexElementSize index_element_size)""" + + @staticmethod + def SDL_BindGPUVertexBuffers(render_pass: Any, first_slot: Any, bindings: Any, num_bindings: Any, /) -> None: + """void SDL_BindGPUVertexBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUBufferBinding *bindings, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUVertexSamplers( + render_pass: Any, first_slot: Any, texture_sampler_bindings: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUVertexSamplers(SDL_GPURenderPass *render_pass, Uint32 first_slot, const SDL_GPUTextureSamplerBinding *texture_sampler_bindings, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUVertexStorageBuffers( + render_pass: Any, first_slot: Any, storage_buffers: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUVertexStorageBuffers(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUBuffer * const *storage_buffers, Uint32 num_bindings)""" + + @staticmethod + def SDL_BindGPUVertexStorageTextures( + render_pass: Any, first_slot: Any, storage_textures: Any, num_bindings: Any, / + ) -> None: + """void SDL_BindGPUVertexStorageTextures(SDL_GPURenderPass *render_pass, Uint32 first_slot, SDL_GPUTexture * const *storage_textures, Uint32 num_bindings)""" + + @staticmethod + def SDL_BlitGPUTexture(command_buffer: Any, info: Any, /) -> None: + """void SDL_BlitGPUTexture(SDL_GPUCommandBuffer *command_buffer, const SDL_GPUBlitInfo *info)""" + + @staticmethod + def SDL_BlitSurface(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool: + """bool SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)""" + + @staticmethod + def SDL_BlitSurface9Grid( + src: Any, + srcrect: Any, + left_width: int, + right_width: int, + top_height: int, + bottom_height: int, + scale: float, + scaleMode: Any, + dst: Any, + dstrect: Any, + /, + ) -> bool: + """bool SDL_BlitSurface9Grid(SDL_Surface *src, const SDL_Rect *srcrect, int left_width, int right_width, int top_height, int bottom_height, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)""" + + @staticmethod + def SDL_BlitSurfaceScaled(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool: + """bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)""" + + @staticmethod + def SDL_BlitSurfaceTiled(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool: + """bool SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)""" + + @staticmethod + def SDL_BlitSurfaceTiledWithScale( + src: Any, srcrect: Any, scale: float, scaleMode: Any, dst: Any, dstrect: Any, / + ) -> bool: + """bool SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)""" + + @staticmethod + def SDL_BlitSurfaceUnchecked(src: Any, srcrect: Any, dst: Any, dstrect: Any, /) -> bool: + """bool SDL_BlitSurfaceUnchecked(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)""" + + @staticmethod + def SDL_BlitSurfaceUncheckedScaled(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool: + """bool SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)""" + + @staticmethod + def SDL_BroadcastCondition(cond: Any, /) -> None: + """void SDL_BroadcastCondition(SDL_Condition *cond)""" + + @staticmethod + def SDL_CalculateGPUTextureFormatSize(format: Any, width: Any, height: Any, depth_or_layer_count: Any, /) -> Any: + """Uint32 SDL_CalculateGPUTextureFormatSize(SDL_GPUTextureFormat format, Uint32 width, Uint32 height, Uint32 depth_or_layer_count)""" + + @staticmethod + def SDL_CancelGPUCommandBuffer(command_buffer: Any, /) -> bool: + """bool SDL_CancelGPUCommandBuffer(SDL_GPUCommandBuffer *command_buffer)""" + + @staticmethod + def SDL_CaptureMouse(enabled: bool, /) -> bool: + """bool SDL_CaptureMouse(bool enabled)""" + + @staticmethod + def SDL_ClaimWindowForGPUDevice(device: Any, window: Any, /) -> bool: + """bool SDL_ClaimWindowForGPUDevice(SDL_GPUDevice *device, SDL_Window *window)""" + + @staticmethod + def SDL_CleanupTLS() -> None: + """void SDL_CleanupTLS(void)""" + + @staticmethod + def SDL_ClearAudioStream(stream: Any, /) -> bool: + """bool SDL_ClearAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_ClearClipboardData() -> bool: + """bool SDL_ClearClipboardData(void)""" + + @staticmethod + def SDL_ClearComposition(window: Any, /) -> bool: + """bool SDL_ClearComposition(SDL_Window *window)""" + + @staticmethod + def SDL_ClearError() -> bool: + """bool SDL_ClearError(void)""" + + @staticmethod + def SDL_ClearProperty(props: Any, name: Any, /) -> bool: + """bool SDL_ClearProperty(SDL_PropertiesID props, const char *name)""" + + @staticmethod + def SDL_ClearSurface(surface: Any, r: float, g: float, b: float, a: float, /) -> bool: + """bool SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a)""" + + @staticmethod + def SDL_ClickTrayEntry(entry: Any, /) -> None: + """void SDL_ClickTrayEntry(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_CloseAsyncIO(asyncio: Any, flush: bool, queue: Any, userdata: Any, /) -> bool: + """bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata)""" + + @staticmethod + def SDL_CloseAudioDevice(devid: Any, /) -> None: + """void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_CloseCamera(camera: Any, /) -> None: + """void SDL_CloseCamera(SDL_Camera *camera)""" + + @staticmethod + def SDL_CloseGamepad(gamepad: Any, /) -> None: + """void SDL_CloseGamepad(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_CloseHaptic(haptic: Any, /) -> None: + """void SDL_CloseHaptic(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_CloseIO(context: Any, /) -> bool: + """bool SDL_CloseIO(SDL_IOStream *context)""" + + @staticmethod + def SDL_CloseJoystick(joystick: Any, /) -> None: + """void SDL_CloseJoystick(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_CloseSensor(sensor: Any, /) -> None: + """void SDL_CloseSensor(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_CloseStorage(storage: Any, /) -> bool: + """bool SDL_CloseStorage(SDL_Storage *storage)""" + + @staticmethod + def SDL_CompareAndSwapAtomicInt(a: Any, oldval: int, newval: int, /) -> bool: + """bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)""" + + @staticmethod + def SDL_CompareAndSwapAtomicPointer(a: Any, oldval: Any, newval: Any, /) -> bool: + """bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)""" + + @staticmethod + def SDL_CompareAndSwapAtomicU32(a: Any, oldval: Any, newval: Any, /) -> bool: + """bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)""" + + @staticmethod + def SDL_ComposeCustomBlendMode( + srcColorFactor: Any, + dstColorFactor: Any, + colorOperation: Any, + srcAlphaFactor: Any, + dstAlphaFactor: Any, + alphaOperation: Any, + /, + ) -> Any: + """SDL_BlendMode SDL_ComposeCustomBlendMode(SDL_BlendFactor srcColorFactor, SDL_BlendFactor dstColorFactor, SDL_BlendOperation colorOperation, SDL_BlendFactor srcAlphaFactor, SDL_BlendFactor dstAlphaFactor, SDL_BlendOperation alphaOperation)""" + + @staticmethod + def SDL_ConvertAudioSamples( + src_spec: Any, src_data: Any, src_len: int, dst_spec: Any, dst_data: Any, dst_len: Any, / + ) -> bool: + """bool SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len)""" + + @staticmethod + def SDL_ConvertEventToRenderCoordinates(renderer: Any, event: Any, /) -> bool: + """bool SDL_ConvertEventToRenderCoordinates(SDL_Renderer *renderer, SDL_Event *event)""" + + @staticmethod + def SDL_ConvertPixels( + width: int, height: int, src_format: Any, src: Any, src_pitch: int, dst_format: Any, dst: Any, dst_pitch: int, / + ) -> bool: + """bool SDL_ConvertPixels(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch)""" + + @staticmethod + def SDL_ConvertPixelsAndColorspace( + width: int, + height: int, + src_format: Any, + src_colorspace: Any, + src_properties: Any, + src: Any, + src_pitch: int, + dst_format: Any, + dst_colorspace: Any, + dst_properties: Any, + dst: Any, + dst_pitch: int, + /, + ) -> bool: + """bool SDL_ConvertPixelsAndColorspace(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch)""" + + @staticmethod + def SDL_ConvertSurface(surface: Any, format: Any, /) -> Any: + """SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, SDL_PixelFormat format)""" + + @staticmethod + def SDL_ConvertSurfaceAndColorspace(surface: Any, format: Any, palette: Any, colorspace: Any, props: Any, /) -> Any: + """SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelFormat format, SDL_Palette *palette, SDL_Colorspace colorspace, SDL_PropertiesID props)""" + + @staticmethod + def SDL_CopyFile(oldpath: Any, newpath: Any, /) -> bool: + """bool SDL_CopyFile(const char *oldpath, const char *newpath)""" + + @staticmethod + def SDL_CopyGPUBufferToBuffer(copy_pass: Any, source: Any, destination: Any, size: Any, cycle: bool, /) -> None: + """void SDL_CopyGPUBufferToBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUBufferLocation *source, const SDL_GPUBufferLocation *destination, Uint32 size, bool cycle)""" + + @staticmethod + def SDL_CopyGPUTextureToTexture( + copy_pass: Any, source: Any, destination: Any, w: Any, h: Any, d: Any, cycle: bool, / + ) -> None: + """void SDL_CopyGPUTextureToTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureLocation *source, const SDL_GPUTextureLocation *destination, Uint32 w, Uint32 h, Uint32 d, bool cycle)""" + + @staticmethod + def SDL_CopyProperties(src: Any, dst: Any, /) -> bool: + """bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)""" + + @staticmethod + def SDL_CopyStorageFile(storage: Any, oldpath: Any, newpath: Any, /) -> bool: + """bool SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath)""" + + @staticmethod + def SDL_CreateAsyncIOQueue() -> Any: + """SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void)""" + + @staticmethod + def SDL_CreateAudioStream(src_spec: Any, dst_spec: Any, /) -> Any: + """SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)""" + + @staticmethod + def SDL_CreateColorCursor(surface: Any, hot_x: int, hot_y: int, /) -> Any: + """SDL_Cursor *SDL_CreateColorCursor(SDL_Surface *surface, int hot_x, int hot_y)""" + + @staticmethod + def SDL_CreateCondition() -> Any: + """SDL_Condition *SDL_CreateCondition(void)""" + + @staticmethod + def SDL_CreateCursor(data: Any, mask: Any, w: int, h: int, hot_x: int, hot_y: int, /) -> Any: + """SDL_Cursor *SDL_CreateCursor(const Uint8 *data, const Uint8 *mask, int w, int h, int hot_x, int hot_y)""" + + @staticmethod + def SDL_CreateDirectory(path: Any, /) -> bool: + """bool SDL_CreateDirectory(const char *path)""" + + @staticmethod + def SDL_CreateEnvironment(populated: bool, /) -> Any: + """SDL_Environment *SDL_CreateEnvironment(bool populated)""" + + @staticmethod + def SDL_CreateGPUBuffer(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUBuffer *SDL_CreateGPUBuffer(SDL_GPUDevice *device, const SDL_GPUBufferCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUComputePipeline(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUComputePipeline *SDL_CreateGPUComputePipeline(SDL_GPUDevice *device, const SDL_GPUComputePipelineCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUDevice(format_flags: Any, debug_mode: bool, name: Any, /) -> Any: + """SDL_GPUDevice *SDL_CreateGPUDevice(SDL_GPUShaderFormat format_flags, bool debug_mode, const char *name)""" + + @staticmethod + def SDL_CreateGPUDeviceWithProperties(props: Any, /) -> Any: + """SDL_GPUDevice *SDL_CreateGPUDeviceWithProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_CreateGPUGraphicsPipeline(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUGraphicsPipeline *SDL_CreateGPUGraphicsPipeline(SDL_GPUDevice *device, const SDL_GPUGraphicsPipelineCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUSampler(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUSampler *SDL_CreateGPUSampler(SDL_GPUDevice *device, const SDL_GPUSamplerCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUShader(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUShader *SDL_CreateGPUShader(SDL_GPUDevice *device, const SDL_GPUShaderCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUTexture(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUTexture *SDL_CreateGPUTexture(SDL_GPUDevice *device, const SDL_GPUTextureCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateGPUTransferBuffer(device: Any, createinfo: Any, /) -> Any: + """SDL_GPUTransferBuffer *SDL_CreateGPUTransferBuffer(SDL_GPUDevice *device, const SDL_GPUTransferBufferCreateInfo *createinfo)""" + + @staticmethod + def SDL_CreateHapticEffect(haptic: Any, effect: Any, /) -> int: + """int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)""" + + @staticmethod + def SDL_CreateMutex() -> Any: + """SDL_Mutex *SDL_CreateMutex(void)""" + + @staticmethod + def SDL_CreatePalette(ncolors: int, /) -> Any: + """SDL_Palette *SDL_CreatePalette(int ncolors)""" + + @staticmethod + def SDL_CreatePopupWindow(parent: Any, offset_x: int, offset_y: int, w: int, h: int, flags: Any, /) -> Any: + """SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, SDL_WindowFlags flags)""" + + @staticmethod + def SDL_CreateProcess(args: Any, pipe_stdio: bool, /) -> Any: + """SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio)""" + + @staticmethod + def SDL_CreateProcessWithProperties(props: Any, /) -> Any: + """SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_CreateProperties() -> Any: + """SDL_PropertiesID SDL_CreateProperties(void)""" + + @staticmethod + def SDL_CreateRWLock() -> Any: + """SDL_RWLock *SDL_CreateRWLock(void)""" + + @staticmethod + def SDL_CreateRenderer(window: Any, name: Any, /) -> Any: + """SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name)""" + + @staticmethod + def SDL_CreateRendererWithProperties(props: Any, /) -> Any: + """SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_CreateSemaphore(initial_value: Any, /) -> Any: + """SDL_Semaphore *SDL_CreateSemaphore(Uint32 initial_value)""" + + @staticmethod + def SDL_CreateSoftwareRenderer(surface: Any, /) -> Any: + """SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface)""" + + @staticmethod + def SDL_CreateStorageDirectory(storage: Any, path: Any, /) -> bool: + """bool SDL_CreateStorageDirectory(SDL_Storage *storage, const char *path)""" + + @staticmethod + def SDL_CreateSurface(width: int, height: int, format: Any, /) -> Any: + """SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format)""" + + @staticmethod + def SDL_CreateSurfaceFrom(width: int, height: int, format: Any, pixels: Any, pitch: int, /) -> Any: + """SDL_Surface *SDL_CreateSurfaceFrom(int width, int height, SDL_PixelFormat format, void *pixels, int pitch)""" + + @staticmethod + def SDL_CreateSurfacePalette(surface: Any, /) -> Any: + """SDL_Palette *SDL_CreateSurfacePalette(SDL_Surface *surface)""" + + @staticmethod + def SDL_CreateSystemCursor(id: Any, /) -> Any: + """SDL_Cursor *SDL_CreateSystemCursor(SDL_SystemCursor id)""" + + @staticmethod + def SDL_CreateTexture(renderer: Any, format: Any, access: Any, w: int, h: int, /) -> Any: + """SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer, SDL_PixelFormat format, SDL_TextureAccess access, int w, int h)""" + + @staticmethod + def SDL_CreateTextureFromSurface(renderer: Any, surface: Any, /) -> Any: + """SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface)""" + + @staticmethod + def SDL_CreateTextureWithProperties(renderer: Any, props: Any, /) -> Any: + """SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props)""" + + @staticmethod + def SDL_CreateThreadRuntime(fn: Any, name: Any, data: Any, pfnBeginThread: Any, pfnEndThread: Any, /) -> Any: + """SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread)""" + + @staticmethod + def SDL_CreateThreadWithPropertiesRuntime(props: Any, pfnBeginThread: Any, pfnEndThread: Any, /) -> Any: + """SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread)""" + + @staticmethod + def SDL_CreateTray(icon: Any, tooltip: Any, /) -> Any: + """SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)""" + + @staticmethod + def SDL_CreateTrayMenu(tray: Any, /) -> Any: + """SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)""" + + @staticmethod + def SDL_CreateTraySubmenu(entry: Any, /) -> Any: + """SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_CreateWindow(title: Any, w: int, h: int, flags: Any, /) -> Any: + """SDL_Window *SDL_CreateWindow(const char *title, int w, int h, SDL_WindowFlags flags)""" + + @staticmethod + def SDL_CreateWindowAndRenderer( + title: Any, width: int, height: int, window_flags: Any, window: Any, renderer: Any, / + ) -> bool: + """bool SDL_CreateWindowAndRenderer(const char *title, int width, int height, SDL_WindowFlags window_flags, SDL_Window **window, SDL_Renderer **renderer)""" + + @staticmethod + def SDL_CreateWindowWithProperties(props: Any, /) -> Any: + """SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_CursorVisible() -> bool: + """bool SDL_CursorVisible(void)""" + + @staticmethod + def SDL_DateTimeToTime(dt: Any, ticks: Any, /) -> bool: + """bool SDL_DateTimeToTime(const SDL_DateTime *dt, SDL_Time *ticks)""" + + @staticmethod + def SDL_Delay(ms: Any, /) -> None: + """void SDL_Delay(Uint32 ms)""" + + @staticmethod + def SDL_DelayNS(ns: Any, /) -> None: + """void SDL_DelayNS(Uint64 ns)""" + + @staticmethod + def SDL_DelayPrecise(ns: Any, /) -> None: + """void SDL_DelayPrecise(Uint64 ns)""" + + @staticmethod + def SDL_DestroyAsyncIOQueue(queue: Any, /) -> None: + """void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue)""" + + @staticmethod + def SDL_DestroyAudioStream(stream: Any, /) -> None: + """void SDL_DestroyAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_DestroyCondition(cond: Any, /) -> None: + """void SDL_DestroyCondition(SDL_Condition *cond)""" + + @staticmethod + def SDL_DestroyCursor(cursor: Any, /) -> None: + """void SDL_DestroyCursor(SDL_Cursor *cursor)""" + + @staticmethod + def SDL_DestroyEnvironment(env: Any, /) -> None: + """void SDL_DestroyEnvironment(SDL_Environment *env)""" + + @staticmethod + def SDL_DestroyGPUDevice(device: Any, /) -> None: + """void SDL_DestroyGPUDevice(SDL_GPUDevice *device)""" + + @staticmethod + def SDL_DestroyHapticEffect(haptic: Any, effect: int, /) -> None: + """void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)""" + + @staticmethod + def SDL_DestroyMutex(mutex: Any, /) -> None: + """void SDL_DestroyMutex(SDL_Mutex *mutex)""" + + @staticmethod + def SDL_DestroyPalette(palette: Any, /) -> None: + """void SDL_DestroyPalette(SDL_Palette *palette)""" + + @staticmethod + def SDL_DestroyProcess(process: Any, /) -> None: + """void SDL_DestroyProcess(SDL_Process *process)""" + + @staticmethod + def SDL_DestroyProperties(props: Any, /) -> None: + """void SDL_DestroyProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_DestroyRWLock(rwlock: Any, /) -> None: + """void SDL_DestroyRWLock(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_DestroyRenderer(renderer: Any, /) -> None: + """void SDL_DestroyRenderer(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_DestroySemaphore(sem: Any, /) -> None: + """void SDL_DestroySemaphore(SDL_Semaphore *sem)""" + + @staticmethod + def SDL_DestroySurface(surface: Any, /) -> None: + """void SDL_DestroySurface(SDL_Surface *surface)""" + + @staticmethod + def SDL_DestroyTexture(texture: Any, /) -> None: + """void SDL_DestroyTexture(SDL_Texture *texture)""" + + @staticmethod + def SDL_DestroyTray(tray: Any, /) -> None: + """void SDL_DestroyTray(SDL_Tray *tray)""" + + @staticmethod + def SDL_DestroyWindow(window: Any, /) -> None: + """void SDL_DestroyWindow(SDL_Window *window)""" + + @staticmethod + def SDL_DestroyWindowSurface(window: Any, /) -> bool: + """bool SDL_DestroyWindowSurface(SDL_Window *window)""" + + @staticmethod + def SDL_DetachThread(thread: Any, /) -> None: + """void SDL_DetachThread(SDL_Thread *thread)""" + + @staticmethod + def SDL_DetachVirtualJoystick(instance_id: Any, /) -> bool: + """bool SDL_DetachVirtualJoystick(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_DisableScreenSaver() -> bool: + """bool SDL_DisableScreenSaver(void)""" + + @staticmethod + def SDL_DispatchGPUCompute(compute_pass: Any, groupcount_x: Any, groupcount_y: Any, groupcount_z: Any, /) -> None: + """void SDL_DispatchGPUCompute(SDL_GPUComputePass *compute_pass, Uint32 groupcount_x, Uint32 groupcount_y, Uint32 groupcount_z)""" + + @staticmethod + def SDL_DispatchGPUComputeIndirect(compute_pass: Any, buffer: Any, offset: Any, /) -> None: + """void SDL_DispatchGPUComputeIndirect(SDL_GPUComputePass *compute_pass, SDL_GPUBuffer *buffer, Uint32 offset)""" + + @staticmethod + def SDL_DownloadFromGPUBuffer(copy_pass: Any, source: Any, destination: Any, /) -> None: + """void SDL_DownloadFromGPUBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUBufferRegion *source, const SDL_GPUTransferBufferLocation *destination)""" + + @staticmethod + def SDL_DownloadFromGPUTexture(copy_pass: Any, source: Any, destination: Any, /) -> None: + """void SDL_DownloadFromGPUTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureRegion *source, const SDL_GPUTextureTransferInfo *destination)""" + + @staticmethod + def SDL_DrawGPUIndexedPrimitives( + render_pass: Any, + num_indices: Any, + num_instances: Any, + first_index: Any, + vertex_offset: Any, + first_instance: Any, + /, + ) -> None: + """void SDL_DrawGPUIndexedPrimitives(SDL_GPURenderPass *render_pass, Uint32 num_indices, Uint32 num_instances, Uint32 first_index, Sint32 vertex_offset, Uint32 first_instance)""" + + @staticmethod + def SDL_DrawGPUIndexedPrimitivesIndirect(render_pass: Any, buffer: Any, offset: Any, draw_count: Any, /) -> None: + """void SDL_DrawGPUIndexedPrimitivesIndirect(SDL_GPURenderPass *render_pass, SDL_GPUBuffer *buffer, Uint32 offset, Uint32 draw_count)""" + + @staticmethod + def SDL_DrawGPUPrimitives( + render_pass: Any, num_vertices: Any, num_instances: Any, first_vertex: Any, first_instance: Any, / + ) -> None: + """void SDL_DrawGPUPrimitives(SDL_GPURenderPass *render_pass, Uint32 num_vertices, Uint32 num_instances, Uint32 first_vertex, Uint32 first_instance)""" + + @staticmethod + def SDL_DrawGPUPrimitivesIndirect(render_pass: Any, buffer: Any, offset: Any, draw_count: Any, /) -> None: + """void SDL_DrawGPUPrimitivesIndirect(SDL_GPURenderPass *render_pass, SDL_GPUBuffer *buffer, Uint32 offset, Uint32 draw_count)""" + + @staticmethod + def SDL_DuplicateSurface(surface: Any, /) -> Any: + """SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)""" + + @staticmethod + def SDL_EGL_GetCurrentConfig() -> Any: + """SDL_EGLConfig SDL_EGL_GetCurrentConfig(void)""" + + @staticmethod + def SDL_EGL_GetCurrentDisplay() -> Any: + """SDL_EGLDisplay SDL_EGL_GetCurrentDisplay(void)""" + + @staticmethod + def SDL_EGL_GetProcAddress(proc: Any, /) -> Any: + """SDL_FunctionPointer SDL_EGL_GetProcAddress(const char *proc)""" + + @staticmethod + def SDL_EGL_GetWindowSurface(window: Any, /) -> Any: + """SDL_EGLSurface SDL_EGL_GetWindowSurface(SDL_Window *window)""" + + @staticmethod + def SDL_EGL_SetAttributeCallbacks( + platformAttribCallback: Any, surfaceAttribCallback: Any, contextAttribCallback: Any, userdata: Any, / + ) -> None: + """void SDL_EGL_SetAttributeCallbacks(SDL_EGLAttribArrayCallback platformAttribCallback, SDL_EGLIntArrayCallback surfaceAttribCallback, SDL_EGLIntArrayCallback contextAttribCallback, void *userdata)""" + + @staticmethod + def SDL_EnableScreenSaver() -> bool: + """bool SDL_EnableScreenSaver(void)""" + + @staticmethod + def SDL_EndGPUComputePass(compute_pass: Any, /) -> None: + """void SDL_EndGPUComputePass(SDL_GPUComputePass *compute_pass)""" + + @staticmethod + def SDL_EndGPUCopyPass(copy_pass: Any, /) -> None: + """void SDL_EndGPUCopyPass(SDL_GPUCopyPass *copy_pass)""" + + @staticmethod + def SDL_EndGPURenderPass(render_pass: Any, /) -> None: + """void SDL_EndGPURenderPass(SDL_GPURenderPass *render_pass)""" + + @staticmethod + def SDL_EnumerateDirectory(path: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)""" + + @staticmethod + def SDL_EnumerateProperties(props: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)""" + + @staticmethod + def SDL_EnumerateStorageDirectory(storage: Any, path: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_EnumerateStorageDirectory(SDL_Storage *storage, const char *path, SDL_EnumerateDirectoryCallback callback, void *userdata)""" + + @staticmethod + def SDL_EventEnabled(type: Any, /) -> bool: + """bool SDL_EventEnabled(Uint32 type)""" + + @staticmethod + def SDL_FillSurfaceRect(dst: Any, rect: Any, color: Any, /) -> bool: + """bool SDL_FillSurfaceRect(SDL_Surface *dst, const SDL_Rect *rect, Uint32 color)""" + + @staticmethod + def SDL_FillSurfaceRects(dst: Any, rects: Any, count: int, color: Any, /) -> bool: + """bool SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, Uint32 color)""" + + @staticmethod + def SDL_FilterEvents(filter: Any, userdata: Any, /) -> None: + """void SDL_FilterEvents(SDL_EventFilter filter, void *userdata)""" + + @staticmethod + def SDL_FlashWindow(window: Any, operation: Any, /) -> bool: + """bool SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation)""" + + @staticmethod + def SDL_FlipSurface(surface: Any, flip: Any, /) -> bool: + """bool SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip)""" + + @staticmethod + def SDL_FlushAudioStream(stream: Any, /) -> bool: + """bool SDL_FlushAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_FlushEvent(type: Any, /) -> None: + """void SDL_FlushEvent(Uint32 type)""" + + @staticmethod + def SDL_FlushEvents(minType: Any, maxType: Any, /) -> None: + """void SDL_FlushEvents(Uint32 minType, Uint32 maxType)""" + + @staticmethod + def SDL_FlushIO(context: Any, /) -> bool: + """bool SDL_FlushIO(SDL_IOStream *context)""" + + @staticmethod + def SDL_FlushRenderer(renderer: Any, /) -> bool: + """bool SDL_FlushRenderer(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GL_CreateContext(window: Any, /) -> Any: + """SDL_GLContext SDL_GL_CreateContext(SDL_Window *window)""" + + @staticmethod + def SDL_GL_DestroyContext(context: Any, /) -> bool: + """bool SDL_GL_DestroyContext(SDL_GLContext context)""" + + @staticmethod + def SDL_GL_ExtensionSupported(extension: Any, /) -> bool: + """bool SDL_GL_ExtensionSupported(const char *extension)""" + + @staticmethod + def SDL_GL_GetAttribute(attr: Any, value: Any, /) -> bool: + """bool SDL_GL_GetAttribute(SDL_GLAttr attr, int *value)""" + + @staticmethod + def SDL_GL_GetCurrentContext() -> Any: + """SDL_GLContext SDL_GL_GetCurrentContext(void)""" + + @staticmethod + def SDL_GL_GetCurrentWindow() -> Any: + """SDL_Window *SDL_GL_GetCurrentWindow(void)""" + + @staticmethod + def SDL_GL_GetProcAddress(proc: Any, /) -> Any: + """SDL_FunctionPointer SDL_GL_GetProcAddress(const char *proc)""" + + @staticmethod + def SDL_GL_GetSwapInterval(interval: Any, /) -> bool: + """bool SDL_GL_GetSwapInterval(int *interval)""" + + @staticmethod + def SDL_GL_LoadLibrary(path: Any, /) -> bool: + """bool SDL_GL_LoadLibrary(const char *path)""" + + @staticmethod + def SDL_GL_MakeCurrent(window: Any, context: Any, /) -> bool: + """bool SDL_GL_MakeCurrent(SDL_Window *window, SDL_GLContext context)""" + + @staticmethod + def SDL_GL_ResetAttributes() -> None: + """void SDL_GL_ResetAttributes(void)""" + + @staticmethod + def SDL_GL_SetAttribute(attr: Any, value: int, /) -> bool: + """bool SDL_GL_SetAttribute(SDL_GLAttr attr, int value)""" + + @staticmethod + def SDL_GL_SetSwapInterval(interval: int, /) -> bool: + """bool SDL_GL_SetSwapInterval(int interval)""" + + @staticmethod + def SDL_GL_SwapWindow(window: Any, /) -> bool: + """bool SDL_GL_SwapWindow(SDL_Window *window)""" + + @staticmethod + def SDL_GL_UnloadLibrary() -> None: + """void SDL_GL_UnloadLibrary(void)""" + + @staticmethod + def SDL_GPUSupportsProperties(props: Any, /) -> bool: + """bool SDL_GPUSupportsProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_GPUSupportsShaderFormats(format_flags: Any, name: Any, /) -> bool: + """bool SDL_GPUSupportsShaderFormats(SDL_GPUShaderFormat format_flags, const char *name)""" + + @staticmethod + def SDL_GPUTextureFormatTexelBlockSize(format: Any, /) -> Any: + """Uint32 SDL_GPUTextureFormatTexelBlockSize(SDL_GPUTextureFormat format)""" + + @staticmethod + def SDL_GPUTextureSupportsFormat(device: Any, format: Any, type: Any, usage: Any, /) -> bool: + """bool SDL_GPUTextureSupportsFormat(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUTextureType type, SDL_GPUTextureUsageFlags usage)""" + + @staticmethod + def SDL_GPUTextureSupportsSampleCount(device: Any, format: Any, sample_count: Any, /) -> bool: + """bool SDL_GPUTextureSupportsSampleCount(SDL_GPUDevice *device, SDL_GPUTextureFormat format, SDL_GPUSampleCount sample_count)""" + + @staticmethod + def SDL_GUIDToString(guid: Any, pszGUID: Any, cbGUID: int, /) -> None: + """void SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)""" + + @staticmethod + def SDL_GamepadConnected(gamepad: Any, /) -> bool: + """bool SDL_GamepadConnected(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GamepadEventsEnabled() -> bool: + """bool SDL_GamepadEventsEnabled(void)""" + + @staticmethod + def SDL_GamepadHasAxis(gamepad: Any, axis: Any, /) -> bool: + """bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)""" + + @staticmethod + def SDL_GamepadHasButton(gamepad: Any, button: Any, /) -> bool: + """bool SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)""" + + @staticmethod + def SDL_GamepadHasSensor(gamepad: Any, type: Any, /) -> bool: + """bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type)""" + + @staticmethod + def SDL_GamepadSensorEnabled(gamepad: Any, type: Any, /) -> bool: + """bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type)""" + + @staticmethod + def SDL_GenerateMipmapsForGPUTexture(command_buffer: Any, texture: Any, /) -> None: + """void SDL_GenerateMipmapsForGPUTexture(SDL_GPUCommandBuffer *command_buffer, SDL_GPUTexture *texture)""" + + @staticmethod + def SDL_GetAppMetadataProperty(name: Any, /) -> Any: + """const char *SDL_GetAppMetadataProperty(const char *name)""" + + @staticmethod + def SDL_GetAssertionHandler(puserdata: Any, /) -> Any: + """SDL_AssertionHandler SDL_GetAssertionHandler(void **puserdata)""" + + @staticmethod + def SDL_GetAssertionReport() -> Any: + """const SDL_AssertData *SDL_GetAssertionReport(void)""" + + @staticmethod + def SDL_GetAsyncIOResult(queue: Any, outcome: Any, /) -> bool: + """bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)""" + + @staticmethod + def SDL_GetAsyncIOSize(asyncio: Any, /) -> Any: + """Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio)""" + + @staticmethod + def SDL_GetAtomicInt(a: Any, /) -> int: + """int SDL_GetAtomicInt(SDL_AtomicInt *a)""" + + @staticmethod + def SDL_GetAtomicPointer(a: Any, /) -> Any: + """void *SDL_GetAtomicPointer(void **a)""" + + @staticmethod + def SDL_GetAtomicU32(a: Any, /) -> Any: + """Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)""" + + @staticmethod + def SDL_GetAudioDeviceChannelMap(devid: Any, count: Any, /) -> Any: + """int *SDL_GetAudioDeviceChannelMap(SDL_AudioDeviceID devid, int *count)""" + + @staticmethod + def SDL_GetAudioDeviceFormat(devid: Any, spec: Any, sample_frames: Any, /) -> bool: + """bool SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames)""" + + @staticmethod + def SDL_GetAudioDeviceGain(devid: Any, /) -> float: + """float SDL_GetAudioDeviceGain(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_GetAudioDeviceName(devid: Any, /) -> Any: + """const char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_GetAudioDriver(index: int, /) -> Any: + """const char *SDL_GetAudioDriver(int index)""" + + @staticmethod + def SDL_GetAudioFormatName(format: Any, /) -> Any: + """const char *SDL_GetAudioFormatName(SDL_AudioFormat format)""" + + @staticmethod + def SDL_GetAudioPlaybackDevices(count: Any, /) -> Any: + """SDL_AudioDeviceID *SDL_GetAudioPlaybackDevices(int *count)""" + + @staticmethod + def SDL_GetAudioRecordingDevices(count: Any, /) -> Any: + """SDL_AudioDeviceID *SDL_GetAudioRecordingDevices(int *count)""" + + @staticmethod + def SDL_GetAudioStreamAvailable(stream: Any, /) -> int: + """int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetAudioStreamData(stream: Any, buf: Any, len: int, /) -> int: + """int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *buf, int len)""" + + @staticmethod + def SDL_GetAudioStreamDevice(stream: Any, /) -> Any: + """SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetAudioStreamFormat(stream: Any, src_spec: Any, dst_spec: Any, /) -> bool: + """bool SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec)""" + + @staticmethod + def SDL_GetAudioStreamFrequencyRatio(stream: Any, /) -> float: + """float SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetAudioStreamGain(stream: Any, /) -> float: + """float SDL_GetAudioStreamGain(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetAudioStreamInputChannelMap(stream: Any, count: Any, /) -> Any: + """int *SDL_GetAudioStreamInputChannelMap(SDL_AudioStream *stream, int *count)""" + + @staticmethod + def SDL_GetAudioStreamOutputChannelMap(stream: Any, count: Any, /) -> Any: + """int *SDL_GetAudioStreamOutputChannelMap(SDL_AudioStream *stream, int *count)""" + + @staticmethod + def SDL_GetAudioStreamProperties(stream: Any, /) -> Any: + """SDL_PropertiesID SDL_GetAudioStreamProperties(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetAudioStreamQueued(stream: Any, /) -> int: + """int SDL_GetAudioStreamQueued(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_GetBasePath() -> Any: + """const char *SDL_GetBasePath(void)""" + + @staticmethod + def SDL_GetBooleanProperty(props: Any, name: Any, default_value: bool, /) -> bool: + """bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, bool default_value)""" + + @staticmethod + def SDL_GetCPUCacheLineSize() -> int: + """int SDL_GetCPUCacheLineSize(void)""" + + @staticmethod + def SDL_GetCameraDriver(index: int, /) -> Any: + """const char *SDL_GetCameraDriver(int index)""" + + @staticmethod + def SDL_GetCameraFormat(camera: Any, spec: Any, /) -> bool: + """bool SDL_GetCameraFormat(SDL_Camera *camera, SDL_CameraSpec *spec)""" + + @staticmethod + def SDL_GetCameraID(camera: Any, /) -> Any: + """SDL_CameraID SDL_GetCameraID(SDL_Camera *camera)""" + + @staticmethod + def SDL_GetCameraName(instance_id: Any, /) -> Any: + """const char *SDL_GetCameraName(SDL_CameraID instance_id)""" + + @staticmethod + def SDL_GetCameraPermissionState(camera: Any, /) -> int: + """int SDL_GetCameraPermissionState(SDL_Camera *camera)""" + + @staticmethod + def SDL_GetCameraPosition(instance_id: Any, /) -> Any: + """SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id)""" + + @staticmethod + def SDL_GetCameraProperties(camera: Any, /) -> Any: + """SDL_PropertiesID SDL_GetCameraProperties(SDL_Camera *camera)""" + + @staticmethod + def SDL_GetCameraSupportedFormats(instance_id: Any, count: Any, /) -> Any: + """SDL_CameraSpec **SDL_GetCameraSupportedFormats(SDL_CameraID instance_id, int *count)""" + + @staticmethod + def SDL_GetCameras(count: Any, /) -> Any: + """SDL_CameraID *SDL_GetCameras(int *count)""" + + @staticmethod + def SDL_GetClipboardData(mime_type: Any, size: Any, /) -> Any: + """void *SDL_GetClipboardData(const char *mime_type, size_t *size)""" + + @staticmethod + def SDL_GetClipboardMimeTypes(num_mime_types: Any, /) -> Any: + """char **SDL_GetClipboardMimeTypes(size_t *num_mime_types)""" + + @staticmethod + def SDL_GetClipboardText() -> Any: + """char *SDL_GetClipboardText(void)""" + + @staticmethod + def SDL_GetClosestFullscreenDisplayMode( + displayID: Any, w: int, h: int, refresh_rate: float, include_high_density_modes: bool, closest: Any, / + ) -> bool: + """bool SDL_GetClosestFullscreenDisplayMode(SDL_DisplayID displayID, int w, int h, float refresh_rate, bool include_high_density_modes, SDL_DisplayMode *closest)""" + + @staticmethod + def SDL_GetCurrentAudioDriver() -> Any: + """const char *SDL_GetCurrentAudioDriver(void)""" + + @staticmethod + def SDL_GetCurrentCameraDriver() -> Any: + """const char *SDL_GetCurrentCameraDriver(void)""" + + @staticmethod + def SDL_GetCurrentDirectory() -> Any: + """char *SDL_GetCurrentDirectory(void)""" + + @staticmethod + def SDL_GetCurrentDisplayMode(displayID: Any, /) -> Any: + """const SDL_DisplayMode *SDL_GetCurrentDisplayMode(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetCurrentDisplayOrientation(displayID: Any, /) -> Any: + """SDL_DisplayOrientation SDL_GetCurrentDisplayOrientation(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetCurrentRenderOutputSize(renderer: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetCurrentRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)""" + + @staticmethod + def SDL_GetCurrentThreadID() -> Any: + """SDL_ThreadID SDL_GetCurrentThreadID(void)""" + + @staticmethod + def SDL_GetCurrentTime(ticks: Any, /) -> bool: + """bool SDL_GetCurrentTime(SDL_Time *ticks)""" + + @staticmethod + def SDL_GetCurrentVideoDriver() -> Any: + """const char *SDL_GetCurrentVideoDriver(void)""" + + @staticmethod + def SDL_GetCursor() -> Any: + """SDL_Cursor *SDL_GetCursor(void)""" + + @staticmethod + def SDL_GetDateTimeLocalePreferences(dateFormat: Any, timeFormat: Any, /) -> bool: + """bool SDL_GetDateTimeLocalePreferences(SDL_DateFormat *dateFormat, SDL_TimeFormat *timeFormat)""" + + @staticmethod + def SDL_GetDayOfWeek(year: int, month: int, day: int, /) -> int: + """int SDL_GetDayOfWeek(int year, int month, int day)""" + + @staticmethod + def SDL_GetDayOfYear(year: int, month: int, day: int, /) -> int: + """int SDL_GetDayOfYear(int year, int month, int day)""" + + @staticmethod + def SDL_GetDaysInMonth(year: int, month: int, /) -> int: + """int SDL_GetDaysInMonth(int year, int month)""" + + @staticmethod + def SDL_GetDefaultAssertionHandler() -> Any: + """SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)""" + + @staticmethod + def SDL_GetDefaultCursor() -> Any: + """SDL_Cursor *SDL_GetDefaultCursor(void)""" + + @staticmethod + def SDL_GetDefaultLogOutputFunction() -> Any: + """SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)""" + + @staticmethod + def SDL_GetDesktopDisplayMode(displayID: Any, /) -> Any: + """const SDL_DisplayMode *SDL_GetDesktopDisplayMode(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetDisplayBounds(displayID: Any, rect: Any, /) -> bool: + """bool SDL_GetDisplayBounds(SDL_DisplayID displayID, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetDisplayContentScale(displayID: Any, /) -> float: + """float SDL_GetDisplayContentScale(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetDisplayForPoint(point: Any, /) -> Any: + """SDL_DisplayID SDL_GetDisplayForPoint(const SDL_Point *point)""" + + @staticmethod + def SDL_GetDisplayForRect(rect: Any, /) -> Any: + """SDL_DisplayID SDL_GetDisplayForRect(const SDL_Rect *rect)""" + + @staticmethod + def SDL_GetDisplayForWindow(window: Any, /) -> Any: + """SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window)""" + + @staticmethod + def SDL_GetDisplayName(displayID: Any, /) -> Any: + """const char *SDL_GetDisplayName(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetDisplayProperties(displayID: Any, /) -> Any: + """SDL_PropertiesID SDL_GetDisplayProperties(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetDisplayUsableBounds(displayID: Any, rect: Any, /) -> bool: + """bool SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetDisplays(count: Any, /) -> Any: + """SDL_DisplayID *SDL_GetDisplays(int *count)""" + + @staticmethod + def SDL_GetEnvironment() -> Any: + """SDL_Environment *SDL_GetEnvironment(void)""" + + @staticmethod + def SDL_GetEnvironmentVariable(env: Any, name: Any, /) -> Any: + """const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name)""" + + @staticmethod + def SDL_GetEnvironmentVariables(env: Any, /) -> Any: + """char **SDL_GetEnvironmentVariables(SDL_Environment *env)""" + + @staticmethod + def SDL_GetError() -> Any: + """const char *SDL_GetError(void)""" + + @staticmethod + def SDL_GetEventFilter(filter: Any, userdata: Any, /) -> bool: + """bool SDL_GetEventFilter(SDL_EventFilter *filter, void **userdata)""" + + @staticmethod + def SDL_GetFloatProperty(props: Any, name: Any, default_value: float, /) -> float: + """float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)""" + + @staticmethod + def SDL_GetFullscreenDisplayModes(displayID: Any, count: Any, /) -> Any: + """SDL_DisplayMode **SDL_GetFullscreenDisplayModes(SDL_DisplayID displayID, int *count)""" + + @staticmethod + def SDL_GetGPUDeviceDriver(device: Any, /) -> Any: + """const char *SDL_GetGPUDeviceDriver(SDL_GPUDevice *device)""" + + @staticmethod + def SDL_GetGPUDriver(index: int, /) -> Any: + """const char *SDL_GetGPUDriver(int index)""" + + @staticmethod + def SDL_GetGPUShaderFormats(device: Any, /) -> Any: + """SDL_GPUShaderFormat SDL_GetGPUShaderFormats(SDL_GPUDevice *device)""" + + @staticmethod + def SDL_GetGPUSwapchainTextureFormat(device: Any, window: Any, /) -> Any: + """SDL_GPUTextureFormat SDL_GetGPUSwapchainTextureFormat(SDL_GPUDevice *device, SDL_Window *window)""" + + @staticmethod + def SDL_GetGamepadAppleSFSymbolsNameForAxis(gamepad: Any, axis: Any, /) -> Any: + """const char *SDL_GetGamepadAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)""" + + @staticmethod + def SDL_GetGamepadAppleSFSymbolsNameForButton(gamepad: Any, button: Any, /) -> Any: + """const char *SDL_GetGamepadAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)""" + + @staticmethod + def SDL_GetGamepadAxis(gamepad: Any, axis: Any, /) -> Any: + """Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)""" + + @staticmethod + def SDL_GetGamepadAxisFromString(str: Any, /) -> Any: + """SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)""" + + @staticmethod + def SDL_GetGamepadBindings(gamepad: Any, count: Any, /) -> Any: + """SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count)""" + + @staticmethod + def SDL_GetGamepadButton(gamepad: Any, button: Any, /) -> bool: + """bool SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)""" + + @staticmethod + def SDL_GetGamepadButtonFromString(str: Any, /) -> Any: + """SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)""" + + @staticmethod + def SDL_GetGamepadButtonLabel(gamepad: Any, button: Any, /) -> Any: + """SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button)""" + + @staticmethod + def SDL_GetGamepadButtonLabelForType(type: Any, button: Any, /) -> Any: + """SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button)""" + + @staticmethod + def SDL_GetGamepadConnectionState(gamepad: Any, /) -> Any: + """SDL_JoystickConnectionState SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadFirmwareVersion(gamepad: Any, /) -> Any: + """Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadFromID(instance_id: Any, /) -> Any: + """SDL_Gamepad *SDL_GetGamepadFromID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadFromPlayerIndex(player_index: int, /) -> Any: + """SDL_Gamepad *SDL_GetGamepadFromPlayerIndex(int player_index)""" + + @staticmethod + def SDL_GetGamepadGUIDForID(instance_id: Any, /) -> Any: + """SDL_GUID SDL_GetGamepadGUIDForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadID(gamepad: Any, /) -> Any: + """SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadJoystick(gamepad: Any, /) -> Any: + """SDL_Joystick *SDL_GetGamepadJoystick(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadMapping(gamepad: Any, /) -> Any: + """char *SDL_GetGamepadMapping(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadMappingForGUID(guid: Any, /) -> Any: + """char *SDL_GetGamepadMappingForGUID(SDL_GUID guid)""" + + @staticmethod + def SDL_GetGamepadMappingForID(instance_id: Any, /) -> Any: + """char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadMappings(count: Any, /) -> Any: + """char **SDL_GetGamepadMappings(int *count)""" + + @staticmethod + def SDL_GetGamepadName(gamepad: Any, /) -> Any: + """const char *SDL_GetGamepadName(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadPath(gamepad: Any, /) -> Any: + """const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadPathForID(instance_id: Any, /) -> Any: + """const char *SDL_GetGamepadPathForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadPlayerIndex(gamepad: Any, /) -> int: + """int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadPlayerIndexForID(instance_id: Any, /) -> int: + """int SDL_GetGamepadPlayerIndexForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadPowerInfo(gamepad: Any, percent: Any, /) -> Any: + """SDL_PowerState SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent)""" + + @staticmethod + def SDL_GetGamepadProduct(gamepad: Any, /) -> Any: + """Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadProductForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetGamepadProductForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadProductVersion(gamepad: Any, /) -> Any: + """Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadProductVersionForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetGamepadProductVersionForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadProperties(gamepad: Any, /) -> Any: + """SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadSensorData(gamepad: Any, type: Any, data: Any, num_values: int, /) -> bool: + """bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values)""" + + @staticmethod + def SDL_GetGamepadSensorDataRate(gamepad: Any, type: Any, /) -> float: + """float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type)""" + + @staticmethod + def SDL_GetGamepadSerial(gamepad: Any, /) -> Any: + """const char *SDL_GetGamepadSerial(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadSteamHandle(gamepad: Any, /) -> Any: + """Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadStringForAxis(axis: Any, /) -> Any: + """const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis)""" + + @staticmethod + def SDL_GetGamepadStringForButton(button: Any, /) -> Any: + """const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button)""" + + @staticmethod + def SDL_GetGamepadStringForType(type: Any, /) -> Any: + """const char *SDL_GetGamepadStringForType(SDL_GamepadType type)""" + + @staticmethod + def SDL_GetGamepadTouchpadFinger( + gamepad: Any, touchpad: int, finger: int, down: Any, x: Any, y: Any, pressure: Any, / + ) -> bool: + """bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger, bool *down, float *x, float *y, float *pressure)""" + + @staticmethod + def SDL_GetGamepadType(gamepad: Any, /) -> Any: + """SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadTypeForID(instance_id: Any, /) -> Any: + """SDL_GamepadType SDL_GetGamepadTypeForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepadTypeFromString(str: Any, /) -> Any: + """SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str)""" + + @staticmethod + def SDL_GetGamepadVendor(gamepad: Any, /) -> Any: + """Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetGamepadVendorForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetGamepadVendorForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetGamepads(count: Any, /) -> Any: + """SDL_JoystickID *SDL_GetGamepads(int *count)""" + + @staticmethod + def SDL_GetGlobalMouseState(x: Any, y: Any, /) -> Any: + """SDL_MouseButtonFlags SDL_GetGlobalMouseState(float *x, float *y)""" + + @staticmethod + def SDL_GetGlobalProperties() -> Any: + """SDL_PropertiesID SDL_GetGlobalProperties(void)""" + + @staticmethod + def SDL_GetGrabbedWindow() -> Any: + """SDL_Window *SDL_GetGrabbedWindow(void)""" + + @staticmethod + def SDL_GetHapticEffectStatus(haptic: Any, effect: int, /) -> bool: + """bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)""" + + @staticmethod + def SDL_GetHapticFeatures(haptic: Any, /) -> Any: + """Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetHapticFromID(instance_id: Any, /) -> Any: + """SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)""" + + @staticmethod + def SDL_GetHapticID(haptic: Any, /) -> Any: + """SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetHapticName(haptic: Any, /) -> Any: + """const char *SDL_GetHapticName(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetHapticNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)""" + + @staticmethod + def SDL_GetHaptics(count: Any, /) -> Any: + """SDL_HapticID *SDL_GetHaptics(int *count)""" + + @staticmethod + def SDL_GetHint(name: Any, /) -> Any: + """const char *SDL_GetHint(const char *name)""" + + @staticmethod + def SDL_GetHintBoolean(name: Any, default_value: bool, /) -> bool: + """bool SDL_GetHintBoolean(const char *name, bool default_value)""" + + @staticmethod + def SDL_GetIOProperties(context: Any, /) -> Any: + """SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context)""" + + @staticmethod + def SDL_GetIOSize(context: Any, /) -> Any: + """Sint64 SDL_GetIOSize(SDL_IOStream *context)""" + + @staticmethod + def SDL_GetIOStatus(context: Any, /) -> Any: + """SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context)""" + + @staticmethod + def SDL_GetJoystickAxis(joystick: Any, axis: int, /) -> Any: + """Sint16 SDL_GetJoystickAxis(SDL_Joystick *joystick, int axis)""" + + @staticmethod + def SDL_GetJoystickAxisInitialState(joystick: Any, axis: int, state: Any, /) -> bool: + """bool SDL_GetJoystickAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state)""" + + @staticmethod + def SDL_GetJoystickBall(joystick: Any, ball: int, dx: Any, dy: Any, /) -> bool: + """bool SDL_GetJoystickBall(SDL_Joystick *joystick, int ball, int *dx, int *dy)""" + + @staticmethod + def SDL_GetJoystickButton(joystick: Any, button: int, /) -> bool: + """bool SDL_GetJoystickButton(SDL_Joystick *joystick, int button)""" + + @staticmethod + def SDL_GetJoystickConnectionState(joystick: Any, /) -> Any: + """SDL_JoystickConnectionState SDL_GetJoystickConnectionState(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickFirmwareVersion(joystick: Any, /) -> Any: + """Uint16 SDL_GetJoystickFirmwareVersion(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickFromID(instance_id: Any, /) -> Any: + """SDL_Joystick *SDL_GetJoystickFromID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickFromPlayerIndex(player_index: int, /) -> Any: + """SDL_Joystick *SDL_GetJoystickFromPlayerIndex(int player_index)""" + + @staticmethod + def SDL_GetJoystickGUID(joystick: Any, /) -> Any: + """SDL_GUID SDL_GetJoystickGUID(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickGUIDForID(instance_id: Any, /) -> Any: + """SDL_GUID SDL_GetJoystickGUIDForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickGUIDInfo(guid: Any, vendor: Any, product: Any, version: Any, crc16: Any, /) -> None: + """void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version, Uint16 *crc16)""" + + @staticmethod + def SDL_GetJoystickHat(joystick: Any, hat: int, /) -> Any: + """Uint8 SDL_GetJoystickHat(SDL_Joystick *joystick, int hat)""" + + @staticmethod + def SDL_GetJoystickID(joystick: Any, /) -> Any: + """SDL_JoystickID SDL_GetJoystickID(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickName(joystick: Any, /) -> Any: + """const char *SDL_GetJoystickName(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetJoystickNameForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickPath(joystick: Any, /) -> Any: + """const char *SDL_GetJoystickPath(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickPathForID(instance_id: Any, /) -> Any: + """const char *SDL_GetJoystickPathForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickPlayerIndex(joystick: Any, /) -> int: + """int SDL_GetJoystickPlayerIndex(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickPlayerIndexForID(instance_id: Any, /) -> int: + """int SDL_GetJoystickPlayerIndexForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickPowerInfo(joystick: Any, percent: Any, /) -> Any: + """SDL_PowerState SDL_GetJoystickPowerInfo(SDL_Joystick *joystick, int *percent)""" + + @staticmethod + def SDL_GetJoystickProduct(joystick: Any, /) -> Any: + """Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickProductForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetJoystickProductForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickProductVersion(joystick: Any, /) -> Any: + """Uint16 SDL_GetJoystickProductVersion(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickProductVersionForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetJoystickProductVersionForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickProperties(joystick: Any, /) -> Any: + """SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickSerial(joystick: Any, /) -> Any: + """const char *SDL_GetJoystickSerial(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickType(joystick: Any, /) -> Any: + """SDL_JoystickType SDL_GetJoystickType(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickTypeForID(instance_id: Any, /) -> Any: + """SDL_JoystickType SDL_GetJoystickTypeForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoystickVendor(joystick: Any, /) -> Any: + """Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetJoystickVendorForID(instance_id: Any, /) -> Any: + """Uint16 SDL_GetJoystickVendorForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetJoysticks(count: Any, /) -> Any: + """SDL_JoystickID *SDL_GetJoysticks(int *count)""" + + @staticmethod + def SDL_GetKeyFromName(name: Any, /) -> Any: + """SDL_Keycode SDL_GetKeyFromName(const char *name)""" + + @staticmethod + def SDL_GetKeyFromScancode(scancode: Any, modstate: Any, key_event: bool, /) -> Any: + """SDL_Keycode SDL_GetKeyFromScancode(SDL_Scancode scancode, SDL_Keymod modstate, bool key_event)""" + + @staticmethod + def SDL_GetKeyName(key: Any, /) -> Any: + """const char *SDL_GetKeyName(SDL_Keycode key)""" + + @staticmethod + def SDL_GetKeyboardFocus() -> Any: + """SDL_Window *SDL_GetKeyboardFocus(void)""" + + @staticmethod + def SDL_GetKeyboardNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetKeyboardNameForID(SDL_KeyboardID instance_id)""" + + @staticmethod + def SDL_GetKeyboardState(numkeys: Any, /) -> Any: + """const bool *SDL_GetKeyboardState(int *numkeys)""" + + @staticmethod + def SDL_GetKeyboards(count: Any, /) -> Any: + """SDL_KeyboardID *SDL_GetKeyboards(int *count)""" + + @staticmethod + def SDL_GetLogOutputFunction(callback: Any, userdata: Any, /) -> None: + """void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)""" + + @staticmethod + def SDL_GetLogPriority(category: int, /) -> Any: + """SDL_LogPriority SDL_GetLogPriority(int category)""" + + @staticmethod + def SDL_GetMasksForPixelFormat(format: Any, bpp: Any, Rmask: Any, Gmask: Any, Bmask: Any, Amask: Any, /) -> bool: + """bool SDL_GetMasksForPixelFormat(SDL_PixelFormat format, int *bpp, Uint32 *Rmask, Uint32 *Gmask, Uint32 *Bmask, Uint32 *Amask)""" + + @staticmethod + def SDL_GetMaxHapticEffects(haptic: Any, /) -> int: + """int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetMaxHapticEffectsPlaying(haptic: Any, /) -> int: + """int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetMemoryFunctions(malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, /) -> None: + """void SDL_GetMemoryFunctions(SDL_malloc_func *malloc_func, SDL_calloc_func *calloc_func, SDL_realloc_func *realloc_func, SDL_free_func *free_func)""" + + @staticmethod + def SDL_GetMice(count: Any, /) -> Any: + """SDL_MouseID *SDL_GetMice(int *count)""" + + @staticmethod + def SDL_GetModState() -> Any: + """SDL_Keymod SDL_GetModState(void)""" + + @staticmethod + def SDL_GetMouseFocus() -> Any: + """SDL_Window *SDL_GetMouseFocus(void)""" + + @staticmethod + def SDL_GetMouseNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetMouseNameForID(SDL_MouseID instance_id)""" + + @staticmethod + def SDL_GetMouseState(x: Any, y: Any, /) -> Any: + """SDL_MouseButtonFlags SDL_GetMouseState(float *x, float *y)""" + + @staticmethod + def SDL_GetNaturalDisplayOrientation(displayID: Any, /) -> Any: + """SDL_DisplayOrientation SDL_GetNaturalDisplayOrientation(SDL_DisplayID displayID)""" + + @staticmethod + def SDL_GetNumAllocations() -> int: + """int SDL_GetNumAllocations(void)""" + + @staticmethod + def SDL_GetNumAudioDrivers() -> int: + """int SDL_GetNumAudioDrivers(void)""" + + @staticmethod + def SDL_GetNumCameraDrivers() -> int: + """int SDL_GetNumCameraDrivers(void)""" + + @staticmethod + def SDL_GetNumGPUDrivers() -> int: + """int SDL_GetNumGPUDrivers(void)""" + + @staticmethod + def SDL_GetNumGamepadTouchpadFingers(gamepad: Any, touchpad: int, /) -> int: + """int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad)""" + + @staticmethod + def SDL_GetNumGamepadTouchpads(gamepad: Any, /) -> int: + """int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetNumHapticAxes(haptic: Any, /) -> int: + """int SDL_GetNumHapticAxes(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_GetNumJoystickAxes(joystick: Any, /) -> int: + """int SDL_GetNumJoystickAxes(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetNumJoystickBalls(joystick: Any, /) -> int: + """int SDL_GetNumJoystickBalls(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetNumJoystickButtons(joystick: Any, /) -> int: + """int SDL_GetNumJoystickButtons(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetNumJoystickHats(joystick: Any, /) -> int: + """int SDL_GetNumJoystickHats(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_GetNumLogicalCPUCores() -> int: + """int SDL_GetNumLogicalCPUCores(void)""" + + @staticmethod + def SDL_GetNumRenderDrivers() -> int: + """int SDL_GetNumRenderDrivers(void)""" + + @staticmethod + def SDL_GetNumVideoDrivers() -> int: + """int SDL_GetNumVideoDrivers(void)""" + + @staticmethod + def SDL_GetNumberProperty(props: Any, name: Any, default_value: Any, /) -> Any: + """Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)""" + + @staticmethod + def SDL_GetOriginalMemoryFunctions( + malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, / + ) -> None: + """void SDL_GetOriginalMemoryFunctions(SDL_malloc_func *malloc_func, SDL_calloc_func *calloc_func, SDL_realloc_func *realloc_func, SDL_free_func *free_func)""" + + @staticmethod + def SDL_GetPathInfo(path: Any, info: Any, /) -> bool: + """bool SDL_GetPathInfo(const char *path, SDL_PathInfo *info)""" + + @staticmethod + def SDL_GetPerformanceCounter() -> Any: + """Uint64 SDL_GetPerformanceCounter(void)""" + + @staticmethod + def SDL_GetPerformanceFrequency() -> Any: + """Uint64 SDL_GetPerformanceFrequency(void)""" + + @staticmethod + def SDL_GetPixelFormatDetails(format: Any, /) -> Any: + """const SDL_PixelFormatDetails *SDL_GetPixelFormatDetails(SDL_PixelFormat format)""" + + @staticmethod + def SDL_GetPixelFormatForMasks(bpp: int, Rmask: Any, Gmask: Any, Bmask: Any, Amask: Any, /) -> Any: + """SDL_PixelFormat SDL_GetPixelFormatForMasks(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)""" + + @staticmethod + def SDL_GetPixelFormatName(format: Any, /) -> Any: + """const char *SDL_GetPixelFormatName(SDL_PixelFormat format)""" + + @staticmethod + def SDL_GetPlatform() -> Any: + """const char *SDL_GetPlatform(void)""" + + @staticmethod + def SDL_GetPointerProperty(props: Any, name: Any, default_value: Any, /) -> Any: + """void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *default_value)""" + + @staticmethod + def SDL_GetPowerInfo(seconds: Any, percent: Any, /) -> Any: + """SDL_PowerState SDL_GetPowerInfo(int *seconds, int *percent)""" + + @staticmethod + def SDL_GetPrefPath(org: Any, app: Any, /) -> Any: + """char *SDL_GetPrefPath(const char *org, const char *app)""" + + @staticmethod + def SDL_GetPreferredLocales(count: Any, /) -> Any: + """SDL_Locale **SDL_GetPreferredLocales(int *count)""" + + @staticmethod + def SDL_GetPrimaryDisplay() -> Any: + """SDL_DisplayID SDL_GetPrimaryDisplay(void)""" + + @staticmethod + def SDL_GetPrimarySelectionText() -> Any: + """char *SDL_GetPrimarySelectionText(void)""" + + @staticmethod + def SDL_GetProcessInput(process: Any, /) -> Any: + """SDL_IOStream *SDL_GetProcessInput(SDL_Process *process)""" + + @staticmethod + def SDL_GetProcessOutput(process: Any, /) -> Any: + """SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process)""" + + @staticmethod + def SDL_GetProcessProperties(process: Any, /) -> Any: + """SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process)""" + + @staticmethod + def SDL_GetPropertyType(props: Any, name: Any, /) -> Any: + """SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)""" + + @staticmethod + def SDL_GetRGB(pixel: Any, format: Any, palette: Any, r: Any, g: Any, b: Any, /) -> None: + """void SDL_GetRGB(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b)""" + + @staticmethod + def SDL_GetRGBA(pixel: Any, format: Any, palette: Any, r: Any, g: Any, b: Any, a: Any, /) -> None: + """void SDL_GetRGBA(Uint32 pixel, const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)""" + + @staticmethod + def SDL_GetRealGamepadType(gamepad: Any, /) -> Any: + """SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad)""" + + @staticmethod + def SDL_GetRealGamepadTypeForID(instance_id: Any, /) -> Any: + """SDL_GamepadType SDL_GetRealGamepadTypeForID(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_GetRectAndLineIntersection(rect: Any, X1: Any, Y1: Any, X2: Any, Y2: Any, /) -> bool: + """bool SDL_GetRectAndLineIntersection(const SDL_Rect *rect, int *X1, int *Y1, int *X2, int *Y2)""" + + @staticmethod + def SDL_GetRectAndLineIntersectionFloat(rect: Any, X1: Any, Y1: Any, X2: Any, Y2: Any, /) -> bool: + """bool SDL_GetRectAndLineIntersectionFloat(const SDL_FRect *rect, float *X1, float *Y1, float *X2, float *Y2)""" + + @staticmethod + def SDL_GetRectEnclosingPoints(points: Any, count: int, clip: Any, result: Any, /) -> bool: + """bool SDL_GetRectEnclosingPoints(const SDL_Point *points, int count, const SDL_Rect *clip, SDL_Rect *result)""" + + @staticmethod + def SDL_GetRectEnclosingPointsFloat(points: Any, count: int, clip: Any, result: Any, /) -> bool: + """bool SDL_GetRectEnclosingPointsFloat(const SDL_FPoint *points, int count, const SDL_FRect *clip, SDL_FRect *result)""" + + @staticmethod + def SDL_GetRectIntersection(A: Any, B: Any, result: Any, /) -> bool: + """bool SDL_GetRectIntersection(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result)""" + + @staticmethod + def SDL_GetRectIntersectionFloat(A: Any, B: Any, result: Any, /) -> bool: + """bool SDL_GetRectIntersectionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result)""" + + @staticmethod + def SDL_GetRectUnion(A: Any, B: Any, result: Any, /) -> bool: + """bool SDL_GetRectUnion(const SDL_Rect *A, const SDL_Rect *B, SDL_Rect *result)""" + + @staticmethod + def SDL_GetRectUnionFloat(A: Any, B: Any, result: Any, /) -> bool: + """bool SDL_GetRectUnionFloat(const SDL_FRect *A, const SDL_FRect *B, SDL_FRect *result)""" + + @staticmethod + def SDL_GetRelativeMouseState(x: Any, y: Any, /) -> Any: + """SDL_MouseButtonFlags SDL_GetRelativeMouseState(float *x, float *y)""" + + @staticmethod + def SDL_GetRenderClipRect(renderer: Any, rect: Any, /) -> bool: + """bool SDL_GetRenderClipRect(SDL_Renderer *renderer, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetRenderColorScale(renderer: Any, scale: Any, /) -> bool: + """bool SDL_GetRenderColorScale(SDL_Renderer *renderer, float *scale)""" + + @staticmethod + def SDL_GetRenderDrawBlendMode(renderer: Any, blendMode: Any, /) -> bool: + """bool SDL_GetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode *blendMode)""" + + @staticmethod + def SDL_GetRenderDrawColor(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_GetRenderDrawColor(SDL_Renderer *renderer, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)""" + + @staticmethod + def SDL_GetRenderDrawColorFloat(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_GetRenderDrawColorFloat(SDL_Renderer *renderer, float *r, float *g, float *b, float *a)""" + + @staticmethod + def SDL_GetRenderDriver(index: int, /) -> Any: + """const char *SDL_GetRenderDriver(int index)""" + + @staticmethod + def SDL_GetRenderLogicalPresentation(renderer: Any, w: Any, h: Any, mode: Any, /) -> bool: + """bool SDL_GetRenderLogicalPresentation(SDL_Renderer *renderer, int *w, int *h, SDL_RendererLogicalPresentation *mode)""" + + @staticmethod + def SDL_GetRenderLogicalPresentationRect(renderer: Any, rect: Any, /) -> bool: + """bool SDL_GetRenderLogicalPresentationRect(SDL_Renderer *renderer, SDL_FRect *rect)""" + + @staticmethod + def SDL_GetRenderMetalCommandEncoder(renderer: Any, /) -> Any: + """void *SDL_GetRenderMetalCommandEncoder(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRenderMetalLayer(renderer: Any, /) -> Any: + """void *SDL_GetRenderMetalLayer(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRenderOutputSize(renderer: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)""" + + @staticmethod + def SDL_GetRenderSafeArea(renderer: Any, rect: Any, /) -> bool: + """bool SDL_GetRenderSafeArea(SDL_Renderer *renderer, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetRenderScale(renderer: Any, scaleX: Any, scaleY: Any, /) -> bool: + """bool SDL_GetRenderScale(SDL_Renderer *renderer, float *scaleX, float *scaleY)""" + + @staticmethod + def SDL_GetRenderTarget(renderer: Any, /) -> Any: + """SDL_Texture *SDL_GetRenderTarget(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRenderVSync(renderer: Any, vsync: Any, /) -> bool: + """bool SDL_GetRenderVSync(SDL_Renderer *renderer, int *vsync)""" + + @staticmethod + def SDL_GetRenderViewport(renderer: Any, rect: Any, /) -> bool: + """bool SDL_GetRenderViewport(SDL_Renderer *renderer, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetRenderWindow(renderer: Any, /) -> Any: + """SDL_Window *SDL_GetRenderWindow(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRenderer(window: Any, /) -> Any: + """SDL_Renderer *SDL_GetRenderer(SDL_Window *window)""" + + @staticmethod + def SDL_GetRendererFromTexture(texture: Any, /) -> Any: + """SDL_Renderer *SDL_GetRendererFromTexture(SDL_Texture *texture)""" + + @staticmethod + def SDL_GetRendererName(renderer: Any, /) -> Any: + """const char *SDL_GetRendererName(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRendererProperties(renderer: Any, /) -> Any: + """SDL_PropertiesID SDL_GetRendererProperties(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_GetRevision() -> Any: + """const char *SDL_GetRevision(void)""" + + @staticmethod + def SDL_GetSIMDAlignment() -> int: + """size_t SDL_GetSIMDAlignment(void)""" + + @staticmethod + def SDL_GetSandbox() -> Any: + """SDL_Sandbox SDL_GetSandbox(void)""" + + @staticmethod + def SDL_GetScancodeFromKey(key: Any, modstate: Any, /) -> Any: + """SDL_Scancode SDL_GetScancodeFromKey(SDL_Keycode key, SDL_Keymod *modstate)""" + + @staticmethod + def SDL_GetScancodeFromName(name: Any, /) -> Any: + """SDL_Scancode SDL_GetScancodeFromName(const char *name)""" + + @staticmethod + def SDL_GetScancodeName(scancode: Any, /) -> Any: + """const char *SDL_GetScancodeName(SDL_Scancode scancode)""" + + @staticmethod + def SDL_GetSemaphoreValue(sem: Any, /) -> Any: + """Uint32 SDL_GetSemaphoreValue(SDL_Semaphore *sem)""" + + @staticmethod + def SDL_GetSensorData(sensor: Any, data: Any, num_values: int, /) -> bool: + """bool SDL_GetSensorData(SDL_Sensor *sensor, float *data, int num_values)""" + + @staticmethod + def SDL_GetSensorFromID(instance_id: Any, /) -> Any: + """SDL_Sensor *SDL_GetSensorFromID(SDL_SensorID instance_id)""" + + @staticmethod + def SDL_GetSensorID(sensor: Any, /) -> Any: + """SDL_SensorID SDL_GetSensorID(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_GetSensorName(sensor: Any, /) -> Any: + """const char *SDL_GetSensorName(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_GetSensorNameForID(instance_id: Any, /) -> Any: + """const char *SDL_GetSensorNameForID(SDL_SensorID instance_id)""" + + @staticmethod + def SDL_GetSensorNonPortableType(sensor: Any, /) -> int: + """int SDL_GetSensorNonPortableType(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_GetSensorNonPortableTypeForID(instance_id: Any, /) -> int: + """int SDL_GetSensorNonPortableTypeForID(SDL_SensorID instance_id)""" + + @staticmethod + def SDL_GetSensorProperties(sensor: Any, /) -> Any: + """SDL_PropertiesID SDL_GetSensorProperties(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_GetSensorType(sensor: Any, /) -> Any: + """SDL_SensorType SDL_GetSensorType(SDL_Sensor *sensor)""" + + @staticmethod + def SDL_GetSensorTypeForID(instance_id: Any, /) -> Any: + """SDL_SensorType SDL_GetSensorTypeForID(SDL_SensorID instance_id)""" + + @staticmethod + def SDL_GetSensors(count: Any, /) -> Any: + """SDL_SensorID *SDL_GetSensors(int *count)""" + + @staticmethod + def SDL_GetSilenceValueForFormat(format: Any, /) -> int: + """int SDL_GetSilenceValueForFormat(SDL_AudioFormat format)""" + + @staticmethod + def SDL_GetStorageFileSize(storage: Any, path: Any, length: Any, /) -> bool: + """bool SDL_GetStorageFileSize(SDL_Storage *storage, const char *path, Uint64 *length)""" + + @staticmethod + def SDL_GetStoragePathInfo(storage: Any, path: Any, info: Any, /) -> bool: + """bool SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo *info)""" + + @staticmethod + def SDL_GetStorageSpaceRemaining(storage: Any, /) -> Any: + """Uint64 SDL_GetStorageSpaceRemaining(SDL_Storage *storage)""" + + @staticmethod + def SDL_GetStringProperty(props: Any, name: Any, default_value: Any, /) -> Any: + """const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)""" + + @staticmethod + def SDL_GetSurfaceAlphaMod(surface: Any, alpha: Any, /) -> bool: + """bool SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Uint8 *alpha)""" + + @staticmethod + def SDL_GetSurfaceBlendMode(surface: Any, blendMode: Any, /) -> bool: + """bool SDL_GetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode *blendMode)""" + + @staticmethod + def SDL_GetSurfaceClipRect(surface: Any, rect: Any, /) -> bool: + """bool SDL_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetSurfaceColorKey(surface: Any, key: Any, /) -> bool: + """bool SDL_GetSurfaceColorKey(SDL_Surface *surface, Uint32 *key)""" + + @staticmethod + def SDL_GetSurfaceColorMod(surface: Any, r: Any, g: Any, b: Any, /) -> bool: + """bool SDL_GetSurfaceColorMod(SDL_Surface *surface, Uint8 *r, Uint8 *g, Uint8 *b)""" + + @staticmethod + def SDL_GetSurfaceColorspace(surface: Any, /) -> Any: + """SDL_Colorspace SDL_GetSurfaceColorspace(SDL_Surface *surface)""" + + @staticmethod + def SDL_GetSurfaceImages(surface: Any, count: Any, /) -> Any: + """SDL_Surface **SDL_GetSurfaceImages(SDL_Surface *surface, int *count)""" + + @staticmethod + def SDL_GetSurfacePalette(surface: Any, /) -> Any: + """SDL_Palette *SDL_GetSurfacePalette(SDL_Surface *surface)""" + + @staticmethod + def SDL_GetSurfaceProperties(surface: Any, /) -> Any: + """SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface)""" + + @staticmethod + def SDL_GetSystemRAM() -> int: + """int SDL_GetSystemRAM(void)""" + + @staticmethod + def SDL_GetSystemTheme() -> Any: + """SDL_SystemTheme SDL_GetSystemTheme(void)""" + + @staticmethod + def SDL_GetTLS(id: Any, /) -> Any: + """void *SDL_GetTLS(SDL_TLSID *id)""" + + @staticmethod + def SDL_GetTextInputArea(window: Any, rect: Any, cursor: Any, /) -> bool: + """bool SDL_GetTextInputArea(SDL_Window *window, SDL_Rect *rect, int *cursor)""" + + @staticmethod + def SDL_GetTextureAlphaMod(texture: Any, alpha: Any, /) -> bool: + """bool SDL_GetTextureAlphaMod(SDL_Texture *texture, Uint8 *alpha)""" + + @staticmethod + def SDL_GetTextureAlphaModFloat(texture: Any, alpha: Any, /) -> bool: + """bool SDL_GetTextureAlphaModFloat(SDL_Texture *texture, float *alpha)""" + + @staticmethod + def SDL_GetTextureBlendMode(texture: Any, blendMode: Any, /) -> bool: + """bool SDL_GetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode *blendMode)""" + + @staticmethod + def SDL_GetTextureColorMod(texture: Any, r: Any, g: Any, b: Any, /) -> bool: + """bool SDL_GetTextureColorMod(SDL_Texture *texture, Uint8 *r, Uint8 *g, Uint8 *b)""" + + @staticmethod + def SDL_GetTextureColorModFloat(texture: Any, r: Any, g: Any, b: Any, /) -> bool: + """bool SDL_GetTextureColorModFloat(SDL_Texture *texture, float *r, float *g, float *b)""" + + @staticmethod + def SDL_GetTextureProperties(texture: Any, /) -> Any: + """SDL_PropertiesID SDL_GetTextureProperties(SDL_Texture *texture)""" + + @staticmethod + def SDL_GetTextureScaleMode(texture: Any, scaleMode: Any, /) -> bool: + """bool SDL_GetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode *scaleMode)""" + + @staticmethod + def SDL_GetTextureSize(texture: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetTextureSize(SDL_Texture *texture, float *w, float *h)""" + + @staticmethod + def SDL_GetThreadID(thread: Any, /) -> Any: + """SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread)""" + + @staticmethod + def SDL_GetThreadName(thread: Any, /) -> Any: + """const char *SDL_GetThreadName(SDL_Thread *thread)""" + + @staticmethod + def SDL_GetThreadState(thread: Any, /) -> Any: + """SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread)""" + + @staticmethod + def SDL_GetTicks() -> Any: + """Uint64 SDL_GetTicks(void)""" + + @staticmethod + def SDL_GetTicksNS() -> Any: + """Uint64 SDL_GetTicksNS(void)""" + + @staticmethod + def SDL_GetTouchDeviceName(touchID: Any, /) -> Any: + """const char *SDL_GetTouchDeviceName(SDL_TouchID touchID)""" + + @staticmethod + def SDL_GetTouchDeviceType(touchID: Any, /) -> Any: + """SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID touchID)""" + + @staticmethod + def SDL_GetTouchDevices(count: Any, /) -> Any: + """SDL_TouchID *SDL_GetTouchDevices(int *count)""" + + @staticmethod + def SDL_GetTouchFingers(touchID: Any, count: Any, /) -> Any: + """SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count)""" + + @staticmethod + def SDL_GetTrayEntries(menu: Any, count: Any, /) -> Any: + """const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *count)""" + + @staticmethod + def SDL_GetTrayEntryChecked(entry: Any, /) -> bool: + """bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_GetTrayEntryEnabled(entry: Any, /) -> bool: + """bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_GetTrayEntryLabel(entry: Any, /) -> Any: + """const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_GetTrayEntryParent(entry: Any, /) -> Any: + """SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_GetTrayMenu(tray: Any, /) -> Any: + """SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)""" + + @staticmethod + def SDL_GetTrayMenuParentEntry(menu: Any, /) -> Any: + """SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)""" + + @staticmethod + def SDL_GetTrayMenuParentTray(menu: Any, /) -> Any: + """SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)""" + + @staticmethod + def SDL_GetTraySubmenu(entry: Any, /) -> Any: + """SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_GetUserFolder(folder: Any, /) -> Any: + """const char *SDL_GetUserFolder(SDL_Folder folder)""" + + @staticmethod + def SDL_GetVersion() -> int: + """int SDL_GetVersion(void)""" + + @staticmethod + def SDL_GetVideoDriver(index: int, /) -> Any: + """const char *SDL_GetVideoDriver(int index)""" + + @staticmethod + def SDL_GetWindowAspectRatio(window: Any, min_aspect: Any, max_aspect: Any, /) -> bool: + """bool SDL_GetWindowAspectRatio(SDL_Window *window, float *min_aspect, float *max_aspect)""" + + @staticmethod + def SDL_GetWindowBordersSize(window: Any, top: Any, left: Any, bottom: Any, right: Any, /) -> bool: + """bool SDL_GetWindowBordersSize(SDL_Window *window, int *top, int *left, int *bottom, int *right)""" + + @staticmethod + def SDL_GetWindowDisplayScale(window: Any, /) -> float: + """float SDL_GetWindowDisplayScale(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowFlags(window: Any, /) -> Any: + """SDL_WindowFlags SDL_GetWindowFlags(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowFromEvent(event: Any, /) -> Any: + """SDL_Window *SDL_GetWindowFromEvent(const SDL_Event *event)""" + + @staticmethod + def SDL_GetWindowFromID(id: Any, /) -> Any: + """SDL_Window *SDL_GetWindowFromID(SDL_WindowID id)""" + + @staticmethod + def SDL_GetWindowFullscreenMode(window: Any, /) -> Any: + """const SDL_DisplayMode *SDL_GetWindowFullscreenMode(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowICCProfile(window: Any, size: Any, /) -> Any: + """void *SDL_GetWindowICCProfile(SDL_Window *window, size_t *size)""" + + @staticmethod + def SDL_GetWindowID(window: Any, /) -> Any: + """SDL_WindowID SDL_GetWindowID(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowKeyboardGrab(window: Any, /) -> bool: + """bool SDL_GetWindowKeyboardGrab(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowMaximumSize(window: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetWindowMaximumSize(SDL_Window *window, int *w, int *h)""" + + @staticmethod + def SDL_GetWindowMinimumSize(window: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetWindowMinimumSize(SDL_Window *window, int *w, int *h)""" + + @staticmethod + def SDL_GetWindowMouseGrab(window: Any, /) -> bool: + """bool SDL_GetWindowMouseGrab(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowMouseRect(window: Any, /) -> Any: + """const SDL_Rect *SDL_GetWindowMouseRect(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowOpacity(window: Any, /) -> float: + """float SDL_GetWindowOpacity(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowParent(window: Any, /) -> Any: + """SDL_Window *SDL_GetWindowParent(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowPixelDensity(window: Any, /) -> float: + """float SDL_GetWindowPixelDensity(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowPixelFormat(window: Any, /) -> Any: + """SDL_PixelFormat SDL_GetWindowPixelFormat(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowPosition(window: Any, x: Any, y: Any, /) -> bool: + """bool SDL_GetWindowPosition(SDL_Window *window, int *x, int *y)""" + + @staticmethod + def SDL_GetWindowProperties(window: Any, /) -> Any: + """SDL_PropertiesID SDL_GetWindowProperties(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowRelativeMouseMode(window: Any, /) -> bool: + """bool SDL_GetWindowRelativeMouseMode(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowSafeArea(window: Any, rect: Any, /) -> bool: + """bool SDL_GetWindowSafeArea(SDL_Window *window, SDL_Rect *rect)""" + + @staticmethod + def SDL_GetWindowSize(window: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetWindowSize(SDL_Window *window, int *w, int *h)""" + + @staticmethod + def SDL_GetWindowSizeInPixels(window: Any, w: Any, h: Any, /) -> bool: + """bool SDL_GetWindowSizeInPixels(SDL_Window *window, int *w, int *h)""" + + @staticmethod + def SDL_GetWindowSurface(window: Any, /) -> Any: + """SDL_Surface *SDL_GetWindowSurface(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindowSurfaceVSync(window: Any, vsync: Any, /) -> bool: + """bool SDL_GetWindowSurfaceVSync(SDL_Window *window, int *vsync)""" + + @staticmethod + def SDL_GetWindowTitle(window: Any, /) -> Any: + """const char *SDL_GetWindowTitle(SDL_Window *window)""" + + @staticmethod + def SDL_GetWindows(count: Any, /) -> Any: + """SDL_Window **SDL_GetWindows(int *count)""" + + @staticmethod + def SDL_GlobDirectory(path: Any, pattern: Any, flags: Any, count: Any, /) -> Any: + """char **SDL_GlobDirectory(const char *path, const char *pattern, SDL_GlobFlags flags, int *count)""" + + @staticmethod + def SDL_GlobStorageDirectory(storage: Any, path: Any, pattern: Any, flags: Any, count: Any, /) -> Any: + """char **SDL_GlobStorageDirectory(SDL_Storage *storage, const char *path, const char *pattern, SDL_GlobFlags flags, int *count)""" + + @staticmethod + def SDL_HapticEffectSupported(haptic: Any, effect: Any, /) -> bool: + """bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)""" + + @staticmethod + def SDL_HapticRumbleSupported(haptic: Any, /) -> bool: + """bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_HasARMSIMD() -> bool: + """bool SDL_HasARMSIMD(void)""" + + @staticmethod + def SDL_HasAVX() -> bool: + """bool SDL_HasAVX(void)""" + + @staticmethod + def SDL_HasAVX2() -> bool: + """bool SDL_HasAVX2(void)""" + + @staticmethod + def SDL_HasAVX512F() -> bool: + """bool SDL_HasAVX512F(void)""" + + @staticmethod + def SDL_HasAltiVec() -> bool: + """bool SDL_HasAltiVec(void)""" + + @staticmethod + def SDL_HasClipboardData(mime_type: Any, /) -> bool: + """bool SDL_HasClipboardData(const char *mime_type)""" + + @staticmethod + def SDL_HasClipboardText() -> bool: + """bool SDL_HasClipboardText(void)""" + + @staticmethod + def SDL_HasEvent(type: Any, /) -> bool: + """bool SDL_HasEvent(Uint32 type)""" + + @staticmethod + def SDL_HasEvents(minType: Any, maxType: Any, /) -> bool: + """bool SDL_HasEvents(Uint32 minType, Uint32 maxType)""" + + @staticmethod + def SDL_HasGamepad() -> bool: + """bool SDL_HasGamepad(void)""" + + @staticmethod + def SDL_HasJoystick() -> bool: + """bool SDL_HasJoystick(void)""" + + @staticmethod + def SDL_HasKeyboard() -> bool: + """bool SDL_HasKeyboard(void)""" + + @staticmethod + def SDL_HasLASX() -> bool: + """bool SDL_HasLASX(void)""" + + @staticmethod + def SDL_HasLSX() -> bool: + """bool SDL_HasLSX(void)""" + + @staticmethod + def SDL_HasMMX() -> bool: + """bool SDL_HasMMX(void)""" + + @staticmethod + def SDL_HasMouse() -> bool: + """bool SDL_HasMouse(void)""" + + @staticmethod + def SDL_HasNEON() -> bool: + """bool SDL_HasNEON(void)""" + + @staticmethod + def SDL_HasPrimarySelectionText() -> bool: + """bool SDL_HasPrimarySelectionText(void)""" + + @staticmethod + def SDL_HasProperty(props: Any, name: Any, /) -> bool: + """bool SDL_HasProperty(SDL_PropertiesID props, const char *name)""" + + @staticmethod + def SDL_HasRectIntersection(A: Any, B: Any, /) -> bool: + """bool SDL_HasRectIntersection(const SDL_Rect *A, const SDL_Rect *B)""" + + @staticmethod + def SDL_HasRectIntersectionFloat(A: Any, B: Any, /) -> bool: + """bool SDL_HasRectIntersectionFloat(const SDL_FRect *A, const SDL_FRect *B)""" + + @staticmethod + def SDL_HasSSE() -> bool: + """bool SDL_HasSSE(void)""" + + @staticmethod + def SDL_HasSSE2() -> bool: + """bool SDL_HasSSE2(void)""" + + @staticmethod + def SDL_HasSSE3() -> bool: + """bool SDL_HasSSE3(void)""" + + @staticmethod + def SDL_HasSSE41() -> bool: + """bool SDL_HasSSE41(void)""" + + @staticmethod + def SDL_HasSSE42() -> bool: + """bool SDL_HasSSE42(void)""" + + @staticmethod + def SDL_HasScreenKeyboardSupport() -> bool: + """bool SDL_HasScreenKeyboardSupport(void)""" + + @staticmethod + def SDL_HideCursor() -> bool: + """bool SDL_HideCursor(void)""" + + @staticmethod + def SDL_HideWindow(window: Any, /) -> bool: + """bool SDL_HideWindow(SDL_Window *window)""" + + @staticmethod + def SDL_IOFromConstMem(mem: Any, size: int, /) -> Any: + """SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size)""" + + @staticmethod + def SDL_IOFromDynamicMem() -> Any: + """SDL_IOStream *SDL_IOFromDynamicMem(void)""" + + @staticmethod + def SDL_IOFromFile(file: Any, mode: Any, /) -> Any: + """SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)""" + + @staticmethod + def SDL_IOFromMem(mem: Any, size: int, /) -> Any: + """SDL_IOStream *SDL_IOFromMem(void *mem, size_t size)""" + + @staticmethod + def SDL_IOprintf(context: Any, fmt: Any, /, *__args: Any) -> int: + """size_t SDL_IOprintf(SDL_IOStream *context, const char *fmt, ...)""" + + @staticmethod + def SDL_Init(flags: Any, /) -> bool: + """bool SDL_Init(SDL_InitFlags flags)""" + + @staticmethod + def SDL_InitHapticRumble(haptic: Any, /) -> bool: + """bool SDL_InitHapticRumble(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_InitSubSystem(flags: Any, /) -> bool: + """bool SDL_InitSubSystem(SDL_InitFlags flags)""" + + @staticmethod + def SDL_InsertGPUDebugLabel(command_buffer: Any, text: Any, /) -> None: + """void SDL_InsertGPUDebugLabel(SDL_GPUCommandBuffer *command_buffer, const char *text)""" + + @staticmethod + def SDL_InsertTrayEntryAt(menu: Any, pos: int, label: Any, flags: Any, /) -> Any: + """SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)""" + + @staticmethod + def SDL_IsAudioDevicePhysical(devid: Any, /) -> bool: + """bool SDL_IsAudioDevicePhysical(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_IsAudioDevicePlayback(devid: Any, /) -> bool: + """bool SDL_IsAudioDevicePlayback(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_IsGamepad(instance_id: Any, /) -> bool: + """bool SDL_IsGamepad(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_IsJoystickHaptic(joystick: Any, /) -> bool: + """bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_IsJoystickVirtual(instance_id: Any, /) -> bool: + """bool SDL_IsJoystickVirtual(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_IsMainThread() -> bool: + """bool SDL_IsMainThread(void)""" + + @staticmethod + def SDL_IsMouseHaptic() -> bool: + """bool SDL_IsMouseHaptic(void)""" + + @staticmethod + def SDL_IsTV() -> bool: + """bool SDL_IsTV(void)""" + + @staticmethod + def SDL_IsTablet() -> bool: + """bool SDL_IsTablet(void)""" + + @staticmethod + def SDL_JoystickConnected(joystick: Any, /) -> bool: + """bool SDL_JoystickConnected(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_JoystickEventsEnabled() -> bool: + """bool SDL_JoystickEventsEnabled(void)""" + + @staticmethod + def SDL_KillProcess(process: Any, force: bool, /) -> bool: + """bool SDL_KillProcess(SDL_Process *process, bool force)""" + + @staticmethod + def SDL_LoadBMP(file: Any, /) -> Any: + """SDL_Surface *SDL_LoadBMP(const char *file)""" + + @staticmethod + def SDL_LoadBMP_IO(src: Any, closeio: bool, /) -> Any: + """SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio)""" + + @staticmethod + def SDL_LoadFile(file: Any, datasize: Any, /) -> Any: + """void *SDL_LoadFile(const char *file, size_t *datasize)""" + + @staticmethod + def SDL_LoadFileAsync(file: Any, queue: Any, userdata: Any, /) -> bool: + """bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)""" + + @staticmethod + def SDL_LoadFile_IO(src: Any, datasize: Any, closeio: bool, /) -> Any: + """void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio)""" + + @staticmethod + def SDL_LoadFunction(handle: Any, name: Any, /) -> Any: + """SDL_FunctionPointer SDL_LoadFunction(SDL_SharedObject *handle, const char *name)""" + + @staticmethod + def SDL_LoadObject(sofile: Any, /) -> Any: + """SDL_SharedObject *SDL_LoadObject(const char *sofile)""" + + @staticmethod + def SDL_LoadWAV(path: Any, spec: Any, audio_buf: Any, audio_len: Any, /) -> bool: + """bool SDL_LoadWAV(const char *path, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)""" + + @staticmethod + def SDL_LoadWAV_IO(src: Any, closeio: bool, spec: Any, audio_buf: Any, audio_len: Any, /) -> bool: + """bool SDL_LoadWAV_IO(SDL_IOStream *src, bool closeio, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)""" + + @staticmethod + def SDL_LockAudioStream(stream: Any, /) -> bool: + """bool SDL_LockAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_LockJoysticks() -> None: + """void SDL_LockJoysticks(void)""" + + @staticmethod + def SDL_LockMutex(mutex: Any, /) -> None: + """void SDL_LockMutex(SDL_Mutex *mutex)""" + + @staticmethod + def SDL_LockProperties(props: Any, /) -> bool: + """bool SDL_LockProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_LockRWLockForReading(rwlock: Any, /) -> None: + """void SDL_LockRWLockForReading(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_LockRWLockForWriting(rwlock: Any, /) -> None: + """void SDL_LockRWLockForWriting(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_LockSpinlock(lock: Any, /) -> None: + """void SDL_LockSpinlock(SDL_SpinLock *lock)""" + + @staticmethod + def SDL_LockSurface(surface: Any, /) -> bool: + """bool SDL_LockSurface(SDL_Surface *surface)""" + + @staticmethod + def SDL_LockTexture(texture: Any, rect: Any, pixels: Any, pitch: Any, /) -> bool: + """bool SDL_LockTexture(SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch)""" + + @staticmethod + def SDL_LockTextureToSurface(texture: Any, rect: Any, surface: Any, /) -> bool: + """bool SDL_LockTextureToSurface(SDL_Texture *texture, const SDL_Rect *rect, SDL_Surface **surface)""" + + @staticmethod + def SDL_Log(fmt: Any, /, *__args: Any) -> None: + """void SDL_Log(const char *fmt, ...)""" + + @staticmethod + def SDL_LogCritical(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogCritical(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogDebug(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogDebug(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogError(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogError(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogInfo(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogInfo(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogMessage(category: int, priority: Any, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogMessage(int category, SDL_LogPriority priority, const char *fmt, ...)""" + + @staticmethod + def SDL_LogTrace(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogTrace(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogVerbose(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogVerbose(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_LogWarn(category: int, fmt: Any, /, *__args: Any) -> None: + """void SDL_LogWarn(int category, const char *fmt, ...)""" + + @staticmethod + def SDL_MapGPUTransferBuffer(device: Any, transfer_buffer: Any, cycle: bool, /) -> Any: + """void *SDL_MapGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer, bool cycle)""" + + @staticmethod + def SDL_MapRGB(format: Any, palette: Any, r: Any, g: Any, b: Any, /) -> Any: + """Uint32 SDL_MapRGB(const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 r, Uint8 g, Uint8 b)""" + + @staticmethod + def SDL_MapRGBA(format: Any, palette: Any, r: Any, g: Any, b: Any, a: Any, /) -> Any: + """Uint32 SDL_MapRGBA(const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 r, Uint8 g, Uint8 b, Uint8 a)""" + + @staticmethod + def SDL_MapSurfaceRGB(surface: Any, r: Any, g: Any, b: Any, /) -> Any: + """Uint32 SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)""" + + @staticmethod + def SDL_MapSurfaceRGBA(surface: Any, r: Any, g: Any, b: Any, a: Any, /) -> Any: + """Uint32 SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a)""" + + @staticmethod + def SDL_MaximizeWindow(window: Any, /) -> bool: + """bool SDL_MaximizeWindow(SDL_Window *window)""" + + @staticmethod + def SDL_MemoryBarrierAcquireFunction() -> None: + """void SDL_MemoryBarrierAcquireFunction(void)""" + + @staticmethod + def SDL_MemoryBarrierReleaseFunction() -> None: + """void SDL_MemoryBarrierReleaseFunction(void)""" + + @staticmethod + def SDL_Metal_CreateView(window: Any, /) -> Any: + """SDL_MetalView SDL_Metal_CreateView(SDL_Window *window)""" + + @staticmethod + def SDL_Metal_DestroyView(view: Any, /) -> None: + """void SDL_Metal_DestroyView(SDL_MetalView view)""" + + @staticmethod + def SDL_Metal_GetLayer(view: Any, /) -> Any: + """void *SDL_Metal_GetLayer(SDL_MetalView view)""" + + @staticmethod + def SDL_MinimizeWindow(window: Any, /) -> bool: + """bool SDL_MinimizeWindow(SDL_Window *window)""" + + @staticmethod + def SDL_MixAudio(dst: Any, src: Any, format: Any, len: Any, volume: float, /) -> bool: + """bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float volume)""" + + @staticmethod + def SDL_OnApplicationDidEnterBackground() -> None: + """void SDL_OnApplicationDidEnterBackground(void)""" + + @staticmethod + def SDL_OnApplicationDidEnterForeground() -> None: + """void SDL_OnApplicationDidEnterForeground(void)""" + + @staticmethod + def SDL_OnApplicationDidReceiveMemoryWarning() -> None: + """void SDL_OnApplicationDidReceiveMemoryWarning(void)""" + + @staticmethod + def SDL_OnApplicationWillEnterBackground() -> None: + """void SDL_OnApplicationWillEnterBackground(void)""" + + @staticmethod + def SDL_OnApplicationWillEnterForeground() -> None: + """void SDL_OnApplicationWillEnterForeground(void)""" + + @staticmethod + def SDL_OnApplicationWillTerminate() -> None: + """void SDL_OnApplicationWillTerminate(void)""" + + @staticmethod + def SDL_OpenAudioDevice(devid: Any, spec: Any, /) -> Any: + """SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)""" + + @staticmethod + def SDL_OpenAudioDeviceStream(devid: Any, spec: Any, callback: Any, userdata: Any, /) -> Any: + """SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata)""" + + @staticmethod + def SDL_OpenCamera(instance_id: Any, spec: Any, /) -> Any: + """SDL_Camera *SDL_OpenCamera(SDL_CameraID instance_id, const SDL_CameraSpec *spec)""" + + @staticmethod + def SDL_OpenFileStorage(path: Any, /) -> Any: + """SDL_Storage *SDL_OpenFileStorage(const char *path)""" + + @staticmethod + def SDL_OpenGamepad(instance_id: Any, /) -> Any: + """SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_OpenHaptic(instance_id: Any, /) -> Any: + """SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)""" + + @staticmethod + def SDL_OpenHapticFromJoystick(joystick: Any, /) -> Any: + """SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)""" + + @staticmethod + def SDL_OpenHapticFromMouse() -> Any: + """SDL_Haptic *SDL_OpenHapticFromMouse(void)""" + + @staticmethod + def SDL_OpenIO(iface: Any, userdata: Any, /) -> Any: + """SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata)""" + + @staticmethod + def SDL_OpenJoystick(instance_id: Any, /) -> Any: + """SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)""" + + @staticmethod + def SDL_OpenSensor(instance_id: Any, /) -> Any: + """SDL_Sensor *SDL_OpenSensor(SDL_SensorID instance_id)""" + + @staticmethod + def SDL_OpenStorage(iface: Any, userdata: Any, /) -> Any: + """SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata)""" + + @staticmethod + def SDL_OpenTitleStorage(override: Any, props: Any, /) -> Any: + """SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props)""" + + @staticmethod + def SDL_OpenURL(url: Any, /) -> bool: + """bool SDL_OpenURL(const char *url)""" + + @staticmethod + def SDL_OpenUserStorage(org: Any, app: Any, props: Any, /) -> Any: + """SDL_Storage *SDL_OpenUserStorage(const char *org, const char *app, SDL_PropertiesID props)""" + + @staticmethod + def SDL_OutOfMemory() -> bool: + """bool SDL_OutOfMemory(void)""" + + @staticmethod + def SDL_PauseAudioDevice(devid: Any, /) -> bool: + """bool SDL_PauseAudioDevice(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_PauseAudioStreamDevice(stream: Any, /) -> bool: + """bool SDL_PauseAudioStreamDevice(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_PauseHaptic(haptic: Any, /) -> bool: + """bool SDL_PauseHaptic(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_PeepEvents(events: Any, numevents: int, action: Any, minType: Any, maxType: Any, /) -> int: + """int SDL_PeepEvents(SDL_Event *events, int numevents, SDL_EventAction action, Uint32 minType, Uint32 maxType)""" + + @staticmethod + def SDL_PlayHapticRumble(haptic: Any, strength: float, length: Any, /) -> bool: + """bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)""" + + @staticmethod + def SDL_PollEvent(event: Any, /) -> bool: + """bool SDL_PollEvent(SDL_Event *event)""" + + @staticmethod + def SDL_PopGPUDebugGroup(command_buffer: Any, /) -> None: + """void SDL_PopGPUDebugGroup(SDL_GPUCommandBuffer *command_buffer)""" + + @staticmethod + def SDL_PremultiplyAlpha( + width: int, + height: int, + src_format: Any, + src: Any, + src_pitch: int, + dst_format: Any, + dst: Any, + dst_pitch: int, + linear: bool, + /, + ) -> bool: + """bool SDL_PremultiplyAlpha(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, bool linear)""" + + @staticmethod + def SDL_PremultiplySurfaceAlpha(surface: Any, linear: bool, /) -> bool: + """bool SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, bool linear)""" + + @staticmethod + def SDL_PumpEvents() -> None: + """void SDL_PumpEvents(void)""" + + @staticmethod + def SDL_PushEvent(event: Any, /) -> bool: + """bool SDL_PushEvent(SDL_Event *event)""" + + @staticmethod + def SDL_PushGPUComputeUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None: + """void SDL_PushGPUComputeUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)""" + + @staticmethod + def SDL_PushGPUDebugGroup(command_buffer: Any, name: Any, /) -> None: + """void SDL_PushGPUDebugGroup(SDL_GPUCommandBuffer *command_buffer, const char *name)""" + + @staticmethod + def SDL_PushGPUFragmentUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None: + """void SDL_PushGPUFragmentUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)""" + + @staticmethod + def SDL_PushGPUVertexUniformData(command_buffer: Any, slot_index: Any, data: Any, length: Any, /) -> None: + """void SDL_PushGPUVertexUniformData(SDL_GPUCommandBuffer *command_buffer, Uint32 slot_index, const void *data, Uint32 length)""" + + @staticmethod + def SDL_PutAudioStreamData(stream: Any, buf: Any, len: int, /) -> bool: + """bool SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len)""" + + @staticmethod + def SDL_QueryGPUFence(device: Any, fence: Any, /) -> bool: + """bool SDL_QueryGPUFence(SDL_GPUDevice *device, SDL_GPUFence *fence)""" + + @staticmethod + def SDL_Quit() -> None: + """void SDL_Quit(void)""" + + @staticmethod + def SDL_QuitSubSystem(flags: Any, /) -> None: + """void SDL_QuitSubSystem(SDL_InitFlags flags)""" + + @staticmethod + def SDL_RaiseWindow(window: Any, /) -> bool: + """bool SDL_RaiseWindow(SDL_Window *window)""" + + @staticmethod + def SDL_ReadAsyncIO(asyncio: Any, ptr: Any, offset: Any, size: Any, queue: Any, userdata: Any, /) -> bool: + """bool SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)""" + + @staticmethod + def SDL_ReadIO(context: Any, ptr: Any, size: int, /) -> int: + """size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size)""" + + @staticmethod + def SDL_ReadProcess(process: Any, datasize: Any, exitcode: Any, /) -> Any: + """void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode)""" + + @staticmethod + def SDL_ReadS16BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value)""" + + @staticmethod + def SDL_ReadS16LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value)""" + + @staticmethod + def SDL_ReadS32BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value)""" + + @staticmethod + def SDL_ReadS32LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value)""" + + @staticmethod + def SDL_ReadS64BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value)""" + + @staticmethod + def SDL_ReadS64LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value)""" + + @staticmethod + def SDL_ReadS8(src: Any, value: Any, /) -> bool: + """bool SDL_ReadS8(SDL_IOStream *src, Sint8 *value)""" + + @staticmethod + def SDL_ReadStorageFile(storage: Any, path: Any, destination: Any, length: Any, /) -> bool: + """bool SDL_ReadStorageFile(SDL_Storage *storage, const char *path, void *destination, Uint64 length)""" + + @staticmethod + def SDL_ReadSurfacePixel(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)""" + + @staticmethod + def SDL_ReadSurfacePixelFloat(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a)""" + + @staticmethod + def SDL_ReadU16BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value)""" + + @staticmethod + def SDL_ReadU16LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value)""" + + @staticmethod + def SDL_ReadU32BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value)""" + + @staticmethod + def SDL_ReadU32LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value)""" + + @staticmethod + def SDL_ReadU64BE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value)""" + + @staticmethod + def SDL_ReadU64LE(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value)""" + + @staticmethod + def SDL_ReadU8(src: Any, value: Any, /) -> bool: + """bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value)""" + + @staticmethod + def SDL_RegisterEvents(numevents: int, /) -> Any: + """Uint32 SDL_RegisterEvents(int numevents)""" + + @staticmethod + def SDL_ReleaseCameraFrame(camera: Any, frame: Any, /) -> None: + """void SDL_ReleaseCameraFrame(SDL_Camera *camera, SDL_Surface *frame)""" + + @staticmethod + def SDL_ReleaseGPUBuffer(device: Any, buffer: Any, /) -> None: + """void SDL_ReleaseGPUBuffer(SDL_GPUDevice *device, SDL_GPUBuffer *buffer)""" + + @staticmethod + def SDL_ReleaseGPUComputePipeline(device: Any, compute_pipeline: Any, /) -> None: + """void SDL_ReleaseGPUComputePipeline(SDL_GPUDevice *device, SDL_GPUComputePipeline *compute_pipeline)""" + + @staticmethod + def SDL_ReleaseGPUFence(device: Any, fence: Any, /) -> None: + """void SDL_ReleaseGPUFence(SDL_GPUDevice *device, SDL_GPUFence *fence)""" + + @staticmethod + def SDL_ReleaseGPUGraphicsPipeline(device: Any, graphics_pipeline: Any, /) -> None: + """void SDL_ReleaseGPUGraphicsPipeline(SDL_GPUDevice *device, SDL_GPUGraphicsPipeline *graphics_pipeline)""" + + @staticmethod + def SDL_ReleaseGPUSampler(device: Any, sampler: Any, /) -> None: + """void SDL_ReleaseGPUSampler(SDL_GPUDevice *device, SDL_GPUSampler *sampler)""" + + @staticmethod + def SDL_ReleaseGPUShader(device: Any, shader: Any, /) -> None: + """void SDL_ReleaseGPUShader(SDL_GPUDevice *device, SDL_GPUShader *shader)""" + + @staticmethod + def SDL_ReleaseGPUTexture(device: Any, texture: Any, /) -> None: + """void SDL_ReleaseGPUTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture)""" + + @staticmethod + def SDL_ReleaseGPUTransferBuffer(device: Any, transfer_buffer: Any, /) -> None: + """void SDL_ReleaseGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer)""" + + @staticmethod + def SDL_ReleaseWindowFromGPUDevice(device: Any, window: Any, /) -> None: + """void SDL_ReleaseWindowFromGPUDevice(SDL_GPUDevice *device, SDL_Window *window)""" + + @staticmethod + def SDL_ReloadGamepadMappings() -> bool: + """bool SDL_ReloadGamepadMappings(void)""" + + @staticmethod + def SDL_RemoveEventWatch(filter: Any, userdata: Any, /) -> None: + """void SDL_RemoveEventWatch(SDL_EventFilter filter, void *userdata)""" + + @staticmethod + def SDL_RemoveHintCallback(name: Any, callback: Any, userdata: Any, /) -> None: + """void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)""" + + @staticmethod + def SDL_RemovePath(path: Any, /) -> bool: + """bool SDL_RemovePath(const char *path)""" + + @staticmethod + def SDL_RemoveStoragePath(storage: Any, path: Any, /) -> bool: + """bool SDL_RemoveStoragePath(SDL_Storage *storage, const char *path)""" + + @staticmethod + def SDL_RemoveSurfaceAlternateImages(surface: Any, /) -> None: + """void SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface)""" + + @staticmethod + def SDL_RemoveTimer(id: Any, /) -> bool: + """bool SDL_RemoveTimer(SDL_TimerID id)""" + + @staticmethod + def SDL_RemoveTrayEntry(entry: Any, /) -> None: + """void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)""" + + @staticmethod + def SDL_RenamePath(oldpath: Any, newpath: Any, /) -> bool: + """bool SDL_RenamePath(const char *oldpath, const char *newpath)""" + + @staticmethod + def SDL_RenameStoragePath(storage: Any, oldpath: Any, newpath: Any, /) -> bool: + """bool SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char *newpath)""" + + @staticmethod + def SDL_RenderClear(renderer: Any, /) -> bool: + """bool SDL_RenderClear(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_RenderClipEnabled(renderer: Any, /) -> bool: + """bool SDL_RenderClipEnabled(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_RenderCoordinatesFromWindow(renderer: Any, window_x: float, window_y: float, x: Any, y: Any, /) -> bool: + """bool SDL_RenderCoordinatesFromWindow(SDL_Renderer *renderer, float window_x, float window_y, float *x, float *y)""" + + @staticmethod + def SDL_RenderCoordinatesToWindow(renderer: Any, x: float, y: float, window_x: Any, window_y: Any, /) -> bool: + """bool SDL_RenderCoordinatesToWindow(SDL_Renderer *renderer, float x, float y, float *window_x, float *window_y)""" + + @staticmethod + def SDL_RenderDebugText(renderer: Any, x: float, y: float, str: Any, /) -> bool: + """bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *str)""" + + @staticmethod + def SDL_RenderDebugTextFormat(renderer: Any, x: float, y: float, fmt: Any, /, *__args: Any) -> bool: + """bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, const char *fmt, ...)""" + + @staticmethod + def SDL_RenderFillRect(renderer: Any, rect: Any, /) -> bool: + """bool SDL_RenderFillRect(SDL_Renderer *renderer, const SDL_FRect *rect)""" + + @staticmethod + def SDL_RenderFillRects(renderer: Any, rects: Any, count: int, /) -> bool: + """bool SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)""" + + @staticmethod + def SDL_RenderGeometry( + renderer: Any, texture: Any, vertices: Any, num_vertices: int, indices: Any, num_indices: int, / + ) -> bool: + """bool SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, const int *indices, int num_indices)""" + + @staticmethod + def SDL_RenderGeometryRaw( + renderer: Any, + texture: Any, + xy: Any, + xy_stride: int, + color: Any, + color_stride: int, + uv: Any, + uv_stride: int, + num_vertices: int, + indices: Any, + num_indices: int, + size_indices: int, + /, + ) -> bool: + """bool SDL_RenderGeometryRaw(SDL_Renderer *renderer, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices)""" + + @staticmethod + def SDL_RenderLine(renderer: Any, x1: float, y1: float, x2: float, y2: float, /) -> bool: + """bool SDL_RenderLine(SDL_Renderer *renderer, float x1, float y1, float x2, float y2)""" + + @staticmethod + def SDL_RenderLines(renderer: Any, points: Any, count: int, /) -> bool: + """bool SDL_RenderLines(SDL_Renderer *renderer, const SDL_FPoint *points, int count)""" + + @staticmethod + def SDL_RenderPoint(renderer: Any, x: float, y: float, /) -> bool: + """bool SDL_RenderPoint(SDL_Renderer *renderer, float x, float y)""" + + @staticmethod + def SDL_RenderPoints(renderer: Any, points: Any, count: int, /) -> bool: + """bool SDL_RenderPoints(SDL_Renderer *renderer, const SDL_FPoint *points, int count)""" + + @staticmethod + def SDL_RenderPresent(renderer: Any, /) -> bool: + """bool SDL_RenderPresent(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_RenderReadPixels(renderer: Any, rect: Any, /) -> Any: + """SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)""" + + @staticmethod + def SDL_RenderRect(renderer: Any, rect: Any, /) -> bool: + """bool SDL_RenderRect(SDL_Renderer *renderer, const SDL_FRect *rect)""" + + @staticmethod + def SDL_RenderRects(renderer: Any, rects: Any, count: int, /) -> bool: + """bool SDL_RenderRects(SDL_Renderer *renderer, const SDL_FRect *rects, int count)""" + + @staticmethod + def SDL_RenderTexture(renderer: Any, texture: Any, srcrect: Any, dstrect: Any, /) -> bool: + """bool SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect)""" + + @staticmethod + def SDL_RenderTexture9Grid( + renderer: Any, + texture: Any, + srcrect: Any, + left_width: float, + right_width: float, + top_height: float, + bottom_height: float, + scale: float, + dstrect: Any, + /, + ) -> bool: + """bool SDL_RenderTexture9Grid(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float left_width, float right_width, float top_height, float bottom_height, float scale, const SDL_FRect *dstrect)""" + + @staticmethod + def SDL_RenderTextureAffine( + renderer: Any, texture: Any, srcrect: Any, origin: Any, right: Any, down: Any, / + ) -> bool: + """bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FPoint *origin, const SDL_FPoint *right, const SDL_FPoint *down)""" + + @staticmethod + def SDL_RenderTextureRotated( + renderer: Any, texture: Any, srcrect: Any, dstrect: Any, angle: float, center: Any, flip: Any, / + ) -> bool: + """bool SDL_RenderTextureRotated(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect, double angle, const SDL_FPoint *center, SDL_FlipMode flip)""" + + @staticmethod + def SDL_RenderTextureTiled(renderer: Any, texture: Any, srcrect: Any, scale: float, dstrect: Any, /) -> bool: + """bool SDL_RenderTextureTiled(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, float scale, const SDL_FRect *dstrect)""" + + @staticmethod + def SDL_RenderViewportSet(renderer: Any, /) -> bool: + """bool SDL_RenderViewportSet(SDL_Renderer *renderer)""" + + @staticmethod + def SDL_ReportAssertion(data: Any, func: Any, file: Any, line: int, /) -> Any: + """SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)""" + + @staticmethod + def SDL_ResetAssertionReport() -> None: + """void SDL_ResetAssertionReport(void)""" + + @staticmethod + def SDL_ResetHint(name: Any, /) -> bool: + """bool SDL_ResetHint(const char *name)""" + + @staticmethod + def SDL_ResetHints() -> None: + """void SDL_ResetHints(void)""" + + @staticmethod + def SDL_ResetKeyboard() -> None: + """void SDL_ResetKeyboard(void)""" + + @staticmethod + def SDL_ResetLogPriorities() -> None: + """void SDL_ResetLogPriorities(void)""" + + @staticmethod + def SDL_RestoreWindow(window: Any, /) -> bool: + """bool SDL_RestoreWindow(SDL_Window *window)""" + + @staticmethod + def SDL_ResumeAudioDevice(devid: Any, /) -> bool: + """bool SDL_ResumeAudioDevice(SDL_AudioDeviceID devid)""" + + @staticmethod + def SDL_ResumeAudioStreamDevice(stream: Any, /) -> bool: + """bool SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_ResumeHaptic(haptic: Any, /) -> bool: + """bool SDL_ResumeHaptic(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_RumbleGamepad( + gamepad: Any, low_frequency_rumble: Any, high_frequency_rumble: Any, duration_ms: Any, / + ) -> bool: + """bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)""" + + @staticmethod + def SDL_RumbleGamepadTriggers(gamepad: Any, left_rumble: Any, right_rumble: Any, duration_ms: Any, /) -> bool: + """bool SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)""" + + @staticmethod + def SDL_RumbleJoystick( + joystick: Any, low_frequency_rumble: Any, high_frequency_rumble: Any, duration_ms: Any, / + ) -> bool: + """bool SDL_RumbleJoystick(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)""" + + @staticmethod + def SDL_RumbleJoystickTriggers(joystick: Any, left_rumble: Any, right_rumble: Any, duration_ms: Any, /) -> bool: + """bool SDL_RumbleJoystickTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)""" + + @staticmethod + def SDL_RunHapticEffect(haptic: Any, effect: int, iterations: Any, /) -> bool: + """bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)""" + + @staticmethod + def SDL_RunOnMainThread(callback: Any, userdata: Any, wait_complete: bool, /) -> bool: + """bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool wait_complete)""" + + @staticmethod + def SDL_SaveBMP(surface: Any, file: Any, /) -> bool: + """bool SDL_SaveBMP(SDL_Surface *surface, const char *file)""" + + @staticmethod + def SDL_SaveBMP_IO(surface: Any, dst: Any, closeio: bool, /) -> bool: + """bool SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)""" + + @staticmethod + def SDL_SaveFile(file: Any, data: Any, datasize: int, /) -> bool: + """bool SDL_SaveFile(const char *file, const void *data, size_t datasize)""" + + @staticmethod + def SDL_SaveFile_IO(src: Any, data: Any, datasize: int, closeio: bool, /) -> bool: + """bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio)""" + + @staticmethod + def SDL_ScaleSurface(surface: Any, width: int, height: int, scaleMode: Any, /) -> Any: + """SDL_Surface *SDL_ScaleSurface(SDL_Surface *surface, int width, int height, SDL_ScaleMode scaleMode)""" + + @staticmethod + def SDL_ScreenKeyboardShown(window: Any, /) -> bool: + """bool SDL_ScreenKeyboardShown(SDL_Window *window)""" + + @staticmethod + def SDL_ScreenSaverEnabled() -> bool: + """bool SDL_ScreenSaverEnabled(void)""" + + @staticmethod + def SDL_SeekIO(context: Any, offset: Any, whence: Any, /) -> Any: + """Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence)""" + + @staticmethod + def SDL_SendGamepadEffect(gamepad: Any, data: Any, size: int, /) -> bool: + """bool SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size)""" + + @staticmethod + def SDL_SendJoystickEffect(joystick: Any, data: Any, size: int, /) -> bool: + """bool SDL_SendJoystickEffect(SDL_Joystick *joystick, const void *data, int size)""" + + @staticmethod + def SDL_SendJoystickVirtualSensorData( + joystick: Any, type: Any, sensor_timestamp: Any, data: Any, num_values: int, / + ) -> bool: + """bool SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)""" + + @staticmethod + def SDL_SetAppMetadata(appname: Any, appversion: Any, appidentifier: Any, /) -> bool: + """bool SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)""" + + @staticmethod + def SDL_SetAppMetadataProperty(name: Any, value: Any, /) -> bool: + """bool SDL_SetAppMetadataProperty(const char *name, const char *value)""" + + @staticmethod + def SDL_SetAssertionHandler(handler: Any, userdata: Any, /) -> None: + """void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)""" + + @staticmethod + def SDL_SetAtomicInt(a: Any, v: int, /) -> int: + """int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)""" + + @staticmethod + def SDL_SetAtomicPointer(a: Any, v: Any, /) -> Any: + """void *SDL_SetAtomicPointer(void **a, void *v)""" + + @staticmethod + def SDL_SetAtomicU32(a: Any, v: Any, /) -> Any: + """Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)""" + + @staticmethod + def SDL_SetAudioDeviceGain(devid: Any, gain: float, /) -> bool: + """bool SDL_SetAudioDeviceGain(SDL_AudioDeviceID devid, float gain)""" + + @staticmethod + def SDL_SetAudioPostmixCallback(devid: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallback callback, void *userdata)""" + + @staticmethod + def SDL_SetAudioStreamFormat(stream: Any, src_spec: Any, dst_spec: Any, /) -> bool: + """bool SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)""" + + @staticmethod + def SDL_SetAudioStreamFrequencyRatio(stream: Any, ratio: float, /) -> bool: + """bool SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio)""" + + @staticmethod + def SDL_SetAudioStreamGain(stream: Any, gain: float, /) -> bool: + """bool SDL_SetAudioStreamGain(SDL_AudioStream *stream, float gain)""" + + @staticmethod + def SDL_SetAudioStreamGetCallback(stream: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)""" + + @staticmethod + def SDL_SetAudioStreamInputChannelMap(stream: Any, chmap: Any, count: int, /) -> bool: + """bool SDL_SetAudioStreamInputChannelMap(SDL_AudioStream *stream, const int *chmap, int count)""" + + @staticmethod + def SDL_SetAudioStreamOutputChannelMap(stream: Any, chmap: Any, count: int, /) -> bool: + """bool SDL_SetAudioStreamOutputChannelMap(SDL_AudioStream *stream, const int *chmap, int count)""" + + @staticmethod + def SDL_SetAudioStreamPutCallback(stream: Any, callback: Any, userdata: Any, /) -> bool: + """bool SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamCallback callback, void *userdata)""" + + @staticmethod + def SDL_SetBooleanProperty(props: Any, name: Any, value: bool, /) -> bool: + """bool SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, bool value)""" + + @staticmethod + def SDL_SetClipboardData( + callback: Any, cleanup: Any, userdata: Any, mime_types: Any, num_mime_types: int, / + ) -> bool: + """bool SDL_SetClipboardData(SDL_ClipboardDataCallback callback, SDL_ClipboardCleanupCallback cleanup, void *userdata, const char **mime_types, size_t num_mime_types)""" + + @staticmethod + def SDL_SetClipboardText(text: Any, /) -> bool: + """bool SDL_SetClipboardText(const char *text)""" + + @staticmethod + def SDL_SetCurrentThreadPriority(priority: Any, /) -> bool: + """bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority)""" + + @staticmethod + def SDL_SetCursor(cursor: Any, /) -> bool: + """bool SDL_SetCursor(SDL_Cursor *cursor)""" + + @staticmethod + def SDL_SetEnvironmentVariable(env: Any, name: Any, value: Any, overwrite: bool, /) -> bool: + """bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite)""" + + @staticmethod + def SDL_SetError(fmt: Any, /, *__args: Any) -> bool: + """bool SDL_SetError(const char *fmt, ...)""" + + @staticmethod + def SDL_SetEventEnabled(type: Any, enabled: bool, /) -> None: + """void SDL_SetEventEnabled(Uint32 type, bool enabled)""" + + @staticmethod + def SDL_SetEventFilter(filter: Any, userdata: Any, /) -> None: + """void SDL_SetEventFilter(SDL_EventFilter filter, void *userdata)""" + + @staticmethod + def SDL_SetFloatProperty(props: Any, name: Any, value: float, /) -> bool: + """bool SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)""" + + @staticmethod + def SDL_SetGPUAllowedFramesInFlight(device: Any, allowed_frames_in_flight: Any, /) -> bool: + """bool SDL_SetGPUAllowedFramesInFlight(SDL_GPUDevice *device, Uint32 allowed_frames_in_flight)""" + + @staticmethod + def SDL_SetGPUBlendConstants(render_pass: Any, blend_constants: Any, /) -> None: + """void SDL_SetGPUBlendConstants(SDL_GPURenderPass *render_pass, SDL_FColor blend_constants)""" + + @staticmethod + def SDL_SetGPUBufferName(device: Any, buffer: Any, text: Any, /) -> None: + """void SDL_SetGPUBufferName(SDL_GPUDevice *device, SDL_GPUBuffer *buffer, const char *text)""" + + @staticmethod + def SDL_SetGPUScissor(render_pass: Any, scissor: Any, /) -> None: + """void SDL_SetGPUScissor(SDL_GPURenderPass *render_pass, const SDL_Rect *scissor)""" + + @staticmethod + def SDL_SetGPUStencilReference(render_pass: Any, reference: Any, /) -> None: + """void SDL_SetGPUStencilReference(SDL_GPURenderPass *render_pass, Uint8 reference)""" + + @staticmethod + def SDL_SetGPUSwapchainParameters( + device: Any, window: Any, swapchain_composition: Any, present_mode: Any, / + ) -> bool: + """bool SDL_SetGPUSwapchainParameters(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUSwapchainComposition swapchain_composition, SDL_GPUPresentMode present_mode)""" + + @staticmethod + def SDL_SetGPUTextureName(device: Any, texture: Any, text: Any, /) -> None: + """void SDL_SetGPUTextureName(SDL_GPUDevice *device, SDL_GPUTexture *texture, const char *text)""" + + @staticmethod + def SDL_SetGPUViewport(render_pass: Any, viewport: Any, /) -> None: + """void SDL_SetGPUViewport(SDL_GPURenderPass *render_pass, const SDL_GPUViewport *viewport)""" + + @staticmethod + def SDL_SetGamepadEventsEnabled(enabled: bool, /) -> None: + """void SDL_SetGamepadEventsEnabled(bool enabled)""" + + @staticmethod + def SDL_SetGamepadLED(gamepad: Any, red: Any, green: Any, blue: Any, /) -> bool: + """bool SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue)""" + + @staticmethod + def SDL_SetGamepadMapping(instance_id: Any, mapping: Any, /) -> bool: + """bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping)""" + + @staticmethod + def SDL_SetGamepadPlayerIndex(gamepad: Any, player_index: int, /) -> bool: + """bool SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index)""" + + @staticmethod + def SDL_SetGamepadSensorEnabled(gamepad: Any, type: Any, enabled: bool, /) -> bool: + """bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled)""" + + @staticmethod + def SDL_SetHapticAutocenter(haptic: Any, autocenter: int, /) -> bool: + """bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)""" + + @staticmethod + def SDL_SetHapticGain(haptic: Any, gain: int, /) -> bool: + """bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)""" + + @staticmethod + def SDL_SetHint(name: Any, value: Any, /) -> bool: + """bool SDL_SetHint(const char *name, const char *value)""" + + @staticmethod + def SDL_SetHintWithPriority(name: Any, value: Any, priority: Any, /) -> bool: + """bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)""" + + @staticmethod + def SDL_SetInitialized(state: Any, initialized: bool, /) -> None: + """void SDL_SetInitialized(SDL_InitState *state, bool initialized)""" + + @staticmethod + def SDL_SetJoystickEventsEnabled(enabled: bool, /) -> None: + """void SDL_SetJoystickEventsEnabled(bool enabled)""" + + @staticmethod + def SDL_SetJoystickLED(joystick: Any, red: Any, green: Any, blue: Any, /) -> bool: + """bool SDL_SetJoystickLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)""" + + @staticmethod + def SDL_SetJoystickPlayerIndex(joystick: Any, player_index: int, /) -> bool: + """bool SDL_SetJoystickPlayerIndex(SDL_Joystick *joystick, int player_index)""" + + @staticmethod + def SDL_SetJoystickVirtualAxis(joystick: Any, axis: int, value: Any, /) -> bool: + """bool SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)""" + + @staticmethod + def SDL_SetJoystickVirtualBall(joystick: Any, ball: int, xrel: Any, yrel: Any, /) -> bool: + """bool SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)""" + + @staticmethod + def SDL_SetJoystickVirtualButton(joystick: Any, button: int, down: bool, /) -> bool: + """bool SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, bool down)""" + + @staticmethod + def SDL_SetJoystickVirtualHat(joystick: Any, hat: int, value: Any, /) -> bool: + """bool SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)""" + + @staticmethod + def SDL_SetJoystickVirtualTouchpad( + joystick: Any, touchpad: int, finger: int, down: bool, x: float, y: float, pressure: float, / + ) -> bool: + """bool SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure)""" + + @staticmethod + def SDL_SetLogOutputFunction(callback: Any, userdata: Any, /) -> None: + """void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)""" + + @staticmethod + def SDL_SetLogPriorities(priority: Any, /) -> None: + """void SDL_SetLogPriorities(SDL_LogPriority priority)""" + + @staticmethod + def SDL_SetLogPriority(category: int, priority: Any, /) -> None: + """void SDL_SetLogPriority(int category, SDL_LogPriority priority)""" + + @staticmethod + def SDL_SetLogPriorityPrefix(priority: Any, prefix: Any, /) -> bool: + """bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)""" + + @staticmethod + def SDL_SetMemoryFunctions(malloc_func: Any, calloc_func: Any, realloc_func: Any, free_func: Any, /) -> bool: + """bool SDL_SetMemoryFunctions(SDL_malloc_func malloc_func, SDL_calloc_func calloc_func, SDL_realloc_func realloc_func, SDL_free_func free_func)""" + + @staticmethod + def SDL_SetModState(modstate: Any, /) -> None: + """void SDL_SetModState(SDL_Keymod modstate)""" + + @staticmethod + def SDL_SetNumberProperty(props: Any, name: Any, value: Any, /) -> bool: + """bool SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)""" + + @staticmethod + def SDL_SetPaletteColors(palette: Any, colors: Any, firstcolor: int, ncolors: int, /) -> bool: + """bool SDL_SetPaletteColors(SDL_Palette *palette, const SDL_Color *colors, int firstcolor, int ncolors)""" + + @staticmethod + def SDL_SetPointerProperty(props: Any, name: Any, value: Any, /) -> bool: + """bool SDL_SetPointerProperty(SDL_PropertiesID props, const char *name, void *value)""" + + @staticmethod + def SDL_SetPointerPropertyWithCleanup(props: Any, name: Any, value: Any, cleanup: Any, userdata: Any, /) -> bool: + """bool SDL_SetPointerPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, SDL_CleanupPropertyCallback cleanup, void *userdata)""" + + @staticmethod + def SDL_SetPrimarySelectionText(text: Any, /) -> bool: + """bool SDL_SetPrimarySelectionText(const char *text)""" + + @staticmethod + def SDL_SetRenderClipRect(renderer: Any, rect: Any, /) -> bool: + """bool SDL_SetRenderClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)""" + + @staticmethod + def SDL_SetRenderColorScale(renderer: Any, scale: float, /) -> bool: + """bool SDL_SetRenderColorScale(SDL_Renderer *renderer, float scale)""" + + @staticmethod + def SDL_SetRenderDrawBlendMode(renderer: Any, blendMode: Any, /) -> bool: + """bool SDL_SetRenderDrawBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)""" + + @staticmethod + def SDL_SetRenderDrawColor(renderer: Any, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_SetRenderDrawColor(SDL_Renderer *renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a)""" + + @staticmethod + def SDL_SetRenderDrawColorFloat(renderer: Any, r: float, g: float, b: float, a: float, /) -> bool: + """bool SDL_SetRenderDrawColorFloat(SDL_Renderer *renderer, float r, float g, float b, float a)""" + + @staticmethod + def SDL_SetRenderLogicalPresentation(renderer: Any, w: int, h: int, mode: Any, /) -> bool: + """bool SDL_SetRenderLogicalPresentation(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode)""" + + @staticmethod + def SDL_SetRenderScale(renderer: Any, scaleX: float, scaleY: float, /) -> bool: + """bool SDL_SetRenderScale(SDL_Renderer *renderer, float scaleX, float scaleY)""" + + @staticmethod + def SDL_SetRenderTarget(renderer: Any, texture: Any, /) -> bool: + """bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)""" + + @staticmethod + def SDL_SetRenderVSync(renderer: Any, vsync: int, /) -> bool: + """bool SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)""" + + @staticmethod + def SDL_SetRenderViewport(renderer: Any, rect: Any, /) -> bool: + """bool SDL_SetRenderViewport(SDL_Renderer *renderer, const SDL_Rect *rect)""" + + @staticmethod + def SDL_SetScancodeName(scancode: Any, name: Any, /) -> bool: + """bool SDL_SetScancodeName(SDL_Scancode scancode, const char *name)""" + + @staticmethod + def SDL_SetStringProperty(props: Any, name: Any, value: Any, /) -> bool: + """bool SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)""" + + @staticmethod + def SDL_SetSurfaceAlphaMod(surface: Any, alpha: Any, /) -> bool: + """bool SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha)""" + + @staticmethod + def SDL_SetSurfaceBlendMode(surface: Any, blendMode: Any, /) -> bool: + """bool SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode)""" + + @staticmethod + def SDL_SetSurfaceClipRect(surface: Any, rect: Any, /) -> bool: + """bool SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect)""" + + @staticmethod + def SDL_SetSurfaceColorKey(surface: Any, enabled: bool, key: Any, /) -> bool: + """bool SDL_SetSurfaceColorKey(SDL_Surface *surface, bool enabled, Uint32 key)""" + + @staticmethod + def SDL_SetSurfaceColorMod(surface: Any, r: Any, g: Any, b: Any, /) -> bool: + """bool SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)""" + + @staticmethod + def SDL_SetSurfaceColorspace(surface: Any, colorspace: Any, /) -> bool: + """bool SDL_SetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace colorspace)""" + + @staticmethod + def SDL_SetSurfacePalette(surface: Any, palette: Any, /) -> bool: + """bool SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette)""" + + @staticmethod + def SDL_SetSurfaceRLE(surface: Any, enabled: bool, /) -> bool: + """bool SDL_SetSurfaceRLE(SDL_Surface *surface, bool enabled)""" + + @staticmethod + def SDL_SetTLS(id: Any, value: Any, destructor: Any, /) -> bool: + """bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor)""" + + @staticmethod + def SDL_SetTextInputArea(window: Any, rect: Any, cursor: int, /) -> bool: + """bool SDL_SetTextInputArea(SDL_Window *window, const SDL_Rect *rect, int cursor)""" + + @staticmethod + def SDL_SetTextureAlphaMod(texture: Any, alpha: Any, /) -> bool: + """bool SDL_SetTextureAlphaMod(SDL_Texture *texture, Uint8 alpha)""" + + @staticmethod + def SDL_SetTextureAlphaModFloat(texture: Any, alpha: float, /) -> bool: + """bool SDL_SetTextureAlphaModFloat(SDL_Texture *texture, float alpha)""" + + @staticmethod + def SDL_SetTextureBlendMode(texture: Any, blendMode: Any, /) -> bool: + """bool SDL_SetTextureBlendMode(SDL_Texture *texture, SDL_BlendMode blendMode)""" + + @staticmethod + def SDL_SetTextureColorMod(texture: Any, r: Any, g: Any, b: Any, /) -> bool: + """bool SDL_SetTextureColorMod(SDL_Texture *texture, Uint8 r, Uint8 g, Uint8 b)""" + + @staticmethod + def SDL_SetTextureColorModFloat(texture: Any, r: float, g: float, b: float, /) -> bool: + """bool SDL_SetTextureColorModFloat(SDL_Texture *texture, float r, float g, float b)""" + + @staticmethod + def SDL_SetTextureScaleMode(texture: Any, scaleMode: Any, /) -> bool: + """bool SDL_SetTextureScaleMode(SDL_Texture *texture, SDL_ScaleMode scaleMode)""" + + @staticmethod + def SDL_SetTrayEntryCallback(entry: Any, callback: Any, userdata: Any, /) -> None: + """void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)""" + + @staticmethod + def SDL_SetTrayEntryChecked(entry: Any, checked: bool, /) -> None: + """void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)""" + + @staticmethod + def SDL_SetTrayEntryEnabled(entry: Any, enabled: bool, /) -> None: + """void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)""" + + @staticmethod + def SDL_SetTrayEntryLabel(entry: Any, label: Any, /) -> None: + """void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)""" + + @staticmethod + def SDL_SetTrayIcon(tray: Any, icon: Any, /) -> None: + """void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)""" + + @staticmethod + def SDL_SetTrayTooltip(tray: Any, tooltip: Any, /) -> None: + """void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)""" + + @staticmethod + def SDL_SetWindowAlwaysOnTop(window: Any, on_top: bool, /) -> bool: + """bool SDL_SetWindowAlwaysOnTop(SDL_Window *window, bool on_top)""" + + @staticmethod + def SDL_SetWindowAspectRatio(window: Any, min_aspect: float, max_aspect: float, /) -> bool: + """bool SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect)""" + + @staticmethod + def SDL_SetWindowBordered(window: Any, bordered: bool, /) -> bool: + """bool SDL_SetWindowBordered(SDL_Window *window, bool bordered)""" + + @staticmethod + def SDL_SetWindowFocusable(window: Any, focusable: bool, /) -> bool: + """bool SDL_SetWindowFocusable(SDL_Window *window, bool focusable)""" + + @staticmethod + def SDL_SetWindowFullscreen(window: Any, fullscreen: bool, /) -> bool: + """bool SDL_SetWindowFullscreen(SDL_Window *window, bool fullscreen)""" + + @staticmethod + def SDL_SetWindowFullscreenMode(window: Any, mode: Any, /) -> bool: + """bool SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode)""" + + @staticmethod + def SDL_SetWindowHitTest(window: Any, callback: Any, callback_data: Any, /) -> bool: + """bool SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callback_data)""" + + @staticmethod + def SDL_SetWindowIcon(window: Any, icon: Any, /) -> bool: + """bool SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon)""" + + @staticmethod + def SDL_SetWindowKeyboardGrab(window: Any, grabbed: bool, /) -> bool: + """bool SDL_SetWindowKeyboardGrab(SDL_Window *window, bool grabbed)""" + + @staticmethod + def SDL_SetWindowMaximumSize(window: Any, max_w: int, max_h: int, /) -> bool: + """bool SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h)""" + + @staticmethod + def SDL_SetWindowMinimumSize(window: Any, min_w: int, min_h: int, /) -> bool: + """bool SDL_SetWindowMinimumSize(SDL_Window *window, int min_w, int min_h)""" + + @staticmethod + def SDL_SetWindowModal(window: Any, modal: bool, /) -> bool: + """bool SDL_SetWindowModal(SDL_Window *window, bool modal)""" + + @staticmethod + def SDL_SetWindowMouseGrab(window: Any, grabbed: bool, /) -> bool: + """bool SDL_SetWindowMouseGrab(SDL_Window *window, bool grabbed)""" + + @staticmethod + def SDL_SetWindowMouseRect(window: Any, rect: Any, /) -> bool: + """bool SDL_SetWindowMouseRect(SDL_Window *window, const SDL_Rect *rect)""" + + @staticmethod + def SDL_SetWindowOpacity(window: Any, opacity: float, /) -> bool: + """bool SDL_SetWindowOpacity(SDL_Window *window, float opacity)""" + + @staticmethod + def SDL_SetWindowParent(window: Any, parent: Any, /) -> bool: + """bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent)""" + + @staticmethod + def SDL_SetWindowPosition(window: Any, x: int, y: int, /) -> bool: + """bool SDL_SetWindowPosition(SDL_Window *window, int x, int y)""" + + @staticmethod + def SDL_SetWindowRelativeMouseMode(window: Any, enabled: bool, /) -> bool: + """bool SDL_SetWindowRelativeMouseMode(SDL_Window *window, bool enabled)""" + + @staticmethod + def SDL_SetWindowResizable(window: Any, resizable: bool, /) -> bool: + """bool SDL_SetWindowResizable(SDL_Window *window, bool resizable)""" + + @staticmethod + def SDL_SetWindowShape(window: Any, shape: Any, /) -> bool: + """bool SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape)""" + + @staticmethod + def SDL_SetWindowSize(window: Any, w: int, h: int, /) -> bool: + """bool SDL_SetWindowSize(SDL_Window *window, int w, int h)""" + + @staticmethod + def SDL_SetWindowSurfaceVSync(window: Any, vsync: int, /) -> bool: + """bool SDL_SetWindowSurfaceVSync(SDL_Window *window, int vsync)""" + + @staticmethod + def SDL_SetWindowTitle(window: Any, title: Any, /) -> bool: + """bool SDL_SetWindowTitle(SDL_Window *window, const char *title)""" + + @staticmethod + def SDL_SetX11EventHook(callback: Any, userdata: Any, /) -> None: + """void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)""" + + @staticmethod + def SDL_ShouldInit(state: Any, /) -> bool: + """bool SDL_ShouldInit(SDL_InitState *state)""" + + @staticmethod + def SDL_ShouldQuit(state: Any, /) -> bool: + """bool SDL_ShouldQuit(SDL_InitState *state)""" + + @staticmethod + def SDL_ShowCursor() -> bool: + """bool SDL_ShowCursor(void)""" + + @staticmethod + def SDL_ShowFileDialogWithProperties(type: Any, callback: Any, userdata: Any, props: Any, /) -> None: + """void SDL_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)""" + + @staticmethod + def SDL_ShowMessageBox(messageboxdata: Any, buttonid: Any, /) -> bool: + """bool SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)""" + + @staticmethod + def SDL_ShowOpenFileDialog( + callback: Any, + userdata: Any, + window: Any, + filters: Any, + nfilters: int, + default_location: Any, + allow_many: bool, + /, + ) -> None: + """void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)""" + + @staticmethod + def SDL_ShowOpenFolderDialog( + callback: Any, userdata: Any, window: Any, default_location: Any, allow_many: bool, / + ) -> None: + """void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)""" + + @staticmethod + def SDL_ShowSaveFileDialog( + callback: Any, userdata: Any, window: Any, filters: Any, nfilters: int, default_location: Any, / + ) -> None: + """void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location)""" + + @staticmethod + def SDL_ShowSimpleMessageBox(flags: Any, title: Any, message: Any, window: Any, /) -> bool: + """bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)""" + + @staticmethod + def SDL_ShowWindow(window: Any, /) -> bool: + """bool SDL_ShowWindow(SDL_Window *window)""" + + @staticmethod + def SDL_ShowWindowSystemMenu(window: Any, x: int, y: int, /) -> bool: + """bool SDL_ShowWindowSystemMenu(SDL_Window *window, int x, int y)""" + + @staticmethod + def SDL_SignalAsyncIOQueue(queue: Any, /) -> None: + """void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue)""" + + @staticmethod + def SDL_SignalCondition(cond: Any, /) -> None: + """void SDL_SignalCondition(SDL_Condition *cond)""" + + @staticmethod + def SDL_SignalSemaphore(sem: Any, /) -> None: + """void SDL_SignalSemaphore(SDL_Semaphore *sem)""" + + @staticmethod + def SDL_StartTextInput(window: Any, /) -> bool: + """bool SDL_StartTextInput(SDL_Window *window)""" + + @staticmethod + def SDL_StartTextInputWithProperties(window: Any, props: Any, /) -> bool: + """bool SDL_StartTextInputWithProperties(SDL_Window *window, SDL_PropertiesID props)""" + + @staticmethod + def SDL_StepBackUTF8(start: Any, pstr: Any, /) -> Any: + """Uint32 SDL_StepBackUTF8(const char *start, const char **pstr)""" + + @staticmethod + def SDL_StepUTF8(pstr: Any, pslen: Any, /) -> Any: + """Uint32 SDL_StepUTF8(const char **pstr, size_t *pslen)""" + + @staticmethod + def SDL_StopHapticEffect(haptic: Any, effect: int, /) -> bool: + """bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)""" + + @staticmethod + def SDL_StopHapticEffects(haptic: Any, /) -> bool: + """bool SDL_StopHapticEffects(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_StopHapticRumble(haptic: Any, /) -> bool: + """bool SDL_StopHapticRumble(SDL_Haptic *haptic)""" + + @staticmethod + def SDL_StopTextInput(window: Any, /) -> bool: + """bool SDL_StopTextInput(SDL_Window *window)""" + + @staticmethod + def SDL_StorageReady(storage: Any, /) -> bool: + """bool SDL_StorageReady(SDL_Storage *storage)""" + + @staticmethod + def SDL_StretchSurface(src: Any, srcrect: Any, dst: Any, dstrect: Any, scaleMode: Any, /) -> bool: + """bool SDL_StretchSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)""" + + @staticmethod + def SDL_StringToGUID(pchGUID: Any, /) -> Any: + """SDL_GUID SDL_StringToGUID(const char *pchGUID)""" + + @staticmethod + def SDL_SubmitGPUCommandBuffer(command_buffer: Any, /) -> bool: + """bool SDL_SubmitGPUCommandBuffer(SDL_GPUCommandBuffer *command_buffer)""" + + @staticmethod + def SDL_SubmitGPUCommandBufferAndAcquireFence(command_buffer: Any, /) -> Any: + """SDL_GPUFence *SDL_SubmitGPUCommandBufferAndAcquireFence(SDL_GPUCommandBuffer *command_buffer)""" + + @staticmethod + def SDL_SurfaceHasAlternateImages(surface: Any, /) -> bool: + """bool SDL_SurfaceHasAlternateImages(SDL_Surface *surface)""" + + @staticmethod + def SDL_SurfaceHasColorKey(surface: Any, /) -> bool: + """bool SDL_SurfaceHasColorKey(SDL_Surface *surface)""" + + @staticmethod + def SDL_SurfaceHasRLE(surface: Any, /) -> bool: + """bool SDL_SurfaceHasRLE(SDL_Surface *surface)""" + + @staticmethod + def SDL_SyncWindow(window: Any, /) -> bool: + """bool SDL_SyncWindow(SDL_Window *window)""" + + @staticmethod + def SDL_TellIO(context: Any, /) -> Any: + """Sint64 SDL_TellIO(SDL_IOStream *context)""" + + @staticmethod + def SDL_TextInputActive(window: Any, /) -> bool: + """bool SDL_TextInputActive(SDL_Window *window)""" + + @staticmethod + def SDL_TimeFromWindows(dwLowDateTime: Any, dwHighDateTime: Any, /) -> Any: + """SDL_Time SDL_TimeFromWindows(Uint32 dwLowDateTime, Uint32 dwHighDateTime)""" + + @staticmethod + def SDL_TimeToDateTime(ticks: Any, dt: Any, localTime: bool, /) -> bool: + """bool SDL_TimeToDateTime(SDL_Time ticks, SDL_DateTime *dt, bool localTime)""" + + @staticmethod + def SDL_TimeToWindows(ticks: Any, dwLowDateTime: Any, dwHighDateTime: Any, /) -> None: + """void SDL_TimeToWindows(SDL_Time ticks, Uint32 *dwLowDateTime, Uint32 *dwHighDateTime)""" + + @staticmethod + def SDL_TryLockMutex(mutex: Any, /) -> bool: + """bool SDL_TryLockMutex(SDL_Mutex *mutex)""" + + @staticmethod + def SDL_TryLockRWLockForReading(rwlock: Any, /) -> bool: + """bool SDL_TryLockRWLockForReading(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_TryLockRWLockForWriting(rwlock: Any, /) -> bool: + """bool SDL_TryLockRWLockForWriting(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_TryLockSpinlock(lock: Any, /) -> bool: + """bool SDL_TryLockSpinlock(SDL_SpinLock *lock)""" + + @staticmethod + def SDL_TryWaitSemaphore(sem: Any, /) -> bool: + """bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)""" + + @staticmethod + def SDL_UCS4ToUTF8(codepoint: Any, dst: Any, /) -> Any: + """char *SDL_UCS4ToUTF8(Uint32 codepoint, char *dst)""" + + @staticmethod + def SDL_UnbindAudioStream(stream: Any, /) -> None: + """void SDL_UnbindAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_UnbindAudioStreams(streams: Any, num_streams: int, /) -> None: + """void SDL_UnbindAudioStreams(SDL_AudioStream * const *streams, int num_streams)""" + + @staticmethod + def SDL_UnloadObject(handle: Any, /) -> None: + """void SDL_UnloadObject(SDL_SharedObject *handle)""" + + @staticmethod + def SDL_UnlockAudioStream(stream: Any, /) -> bool: + """bool SDL_UnlockAudioStream(SDL_AudioStream *stream)""" + + @staticmethod + def SDL_UnlockJoysticks() -> None: + """void SDL_UnlockJoysticks(void)""" + + @staticmethod + def SDL_UnlockMutex(mutex: Any, /) -> None: + """void SDL_UnlockMutex(SDL_Mutex *mutex)""" + + @staticmethod + def SDL_UnlockProperties(props: Any, /) -> None: + """void SDL_UnlockProperties(SDL_PropertiesID props)""" + + @staticmethod + def SDL_UnlockRWLock(rwlock: Any, /) -> None: + """void SDL_UnlockRWLock(SDL_RWLock *rwlock)""" + + @staticmethod + def SDL_UnlockSpinlock(lock: Any, /) -> None: + """void SDL_UnlockSpinlock(SDL_SpinLock *lock)""" + + @staticmethod + def SDL_UnlockSurface(surface: Any, /) -> None: + """void SDL_UnlockSurface(SDL_Surface *surface)""" + + @staticmethod + def SDL_UnlockTexture(texture: Any, /) -> None: + """void SDL_UnlockTexture(SDL_Texture *texture)""" + + @staticmethod + def SDL_UnmapGPUTransferBuffer(device: Any, transfer_buffer: Any, /) -> None: + """void SDL_UnmapGPUTransferBuffer(SDL_GPUDevice *device, SDL_GPUTransferBuffer *transfer_buffer)""" + + @staticmethod + def SDL_UnsetEnvironmentVariable(env: Any, name: Any, /) -> bool: + """bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)""" + + @staticmethod + def SDL_UpdateGamepads() -> None: + """void SDL_UpdateGamepads(void)""" + + @staticmethod + def SDL_UpdateHapticEffect(haptic: Any, effect: int, data: Any, /) -> bool: + """bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)""" + + @staticmethod + def SDL_UpdateJoysticks() -> None: + """void SDL_UpdateJoysticks(void)""" + + @staticmethod + def SDL_UpdateNVTexture(texture: Any, rect: Any, Yplane: Any, Ypitch: int, UVplane: Any, UVpitch: int, /) -> bool: + """bool SDL_UpdateNVTexture(SDL_Texture *texture, const SDL_Rect *rect, const Uint8 *Yplane, int Ypitch, const Uint8 *UVplane, int UVpitch)""" + + @staticmethod + def SDL_UpdateSensors() -> None: + """void SDL_UpdateSensors(void)""" + + @staticmethod + def SDL_UpdateTexture(texture: Any, rect: Any, pixels: Any, pitch: int, /) -> bool: + """bool SDL_UpdateTexture(SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)""" + + @staticmethod + def SDL_UpdateTrays() -> None: + """void SDL_UpdateTrays(void)""" + + @staticmethod + def SDL_UpdateWindowSurface(window: Any, /) -> bool: + """bool SDL_UpdateWindowSurface(SDL_Window *window)""" + + @staticmethod + def SDL_UpdateWindowSurfaceRects(window: Any, rects: Any, numrects: int, /) -> bool: + """bool SDL_UpdateWindowSurfaceRects(SDL_Window *window, const SDL_Rect *rects, int numrects)""" + + @staticmethod + def SDL_UpdateYUVTexture( + texture: Any, rect: Any, Yplane: Any, Ypitch: int, Uplane: Any, Upitch: int, Vplane: Any, Vpitch: int, / + ) -> bool: + """bool SDL_UpdateYUVTexture(SDL_Texture *texture, const SDL_Rect *rect, const Uint8 *Yplane, int Ypitch, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch)""" + + @staticmethod + def SDL_UploadToGPUBuffer(copy_pass: Any, source: Any, destination: Any, cycle: bool, /) -> None: + """void SDL_UploadToGPUBuffer(SDL_GPUCopyPass *copy_pass, const SDL_GPUTransferBufferLocation *source, const SDL_GPUBufferRegion *destination, bool cycle)""" + + @staticmethod + def SDL_UploadToGPUTexture(copy_pass: Any, source: Any, destination: Any, cycle: bool, /) -> None: + """void SDL_UploadToGPUTexture(SDL_GPUCopyPass *copy_pass, const SDL_GPUTextureTransferInfo *source, const SDL_GPUTextureRegion *destination, bool cycle)""" + + @staticmethod + def SDL_WaitAndAcquireGPUSwapchainTexture( + command_buffer: Any, + window: Any, + swapchain_texture: Any, + swapchain_texture_width: Any, + swapchain_texture_height: Any, + /, + ) -> bool: + """bool SDL_WaitAndAcquireGPUSwapchainTexture(SDL_GPUCommandBuffer *command_buffer, SDL_Window *window, SDL_GPUTexture **swapchain_texture, Uint32 *swapchain_texture_width, Uint32 *swapchain_texture_height)""" + + @staticmethod + def SDL_WaitAsyncIOResult(queue: Any, outcome: Any, timeoutMS: Any, /) -> bool: + """bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)""" + + @staticmethod + def SDL_WaitCondition(cond: Any, mutex: Any, /) -> None: + """void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)""" + + @staticmethod + def SDL_WaitConditionTimeout(cond: Any, mutex: Any, timeoutMS: Any, /) -> bool: + """bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS)""" + + @staticmethod + def SDL_WaitEvent(event: Any, /) -> bool: + """bool SDL_WaitEvent(SDL_Event *event)""" + + @staticmethod + def SDL_WaitEventTimeout(event: Any, timeoutMS: Any, /) -> bool: + """bool SDL_WaitEventTimeout(SDL_Event *event, Sint32 timeoutMS)""" + + @staticmethod + def SDL_WaitForGPUFences(device: Any, wait_all: bool, fences: Any, num_fences: Any, /) -> bool: + """bool SDL_WaitForGPUFences(SDL_GPUDevice *device, bool wait_all, SDL_GPUFence * const *fences, Uint32 num_fences)""" + + @staticmethod + def SDL_WaitForGPUIdle(device: Any, /) -> bool: + """bool SDL_WaitForGPUIdle(SDL_GPUDevice *device)""" + + @staticmethod + def SDL_WaitForGPUSwapchain(device: Any, window: Any, /) -> bool: + """bool SDL_WaitForGPUSwapchain(SDL_GPUDevice *device, SDL_Window *window)""" + + @staticmethod + def SDL_WaitProcess(process: Any, block: bool, exitcode: Any, /) -> bool: + """bool SDL_WaitProcess(SDL_Process *process, bool block, int *exitcode)""" + + @staticmethod + def SDL_WaitSemaphore(sem: Any, /) -> None: + """void SDL_WaitSemaphore(SDL_Semaphore *sem)""" + + @staticmethod + def SDL_WaitSemaphoreTimeout(sem: Any, timeoutMS: Any, /) -> bool: + """bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS)""" + + @staticmethod + def SDL_WaitThread(thread: Any, status: Any, /) -> None: + """void SDL_WaitThread(SDL_Thread *thread, int *status)""" + + @staticmethod + def SDL_WarpMouseGlobal(x: float, y: float, /) -> bool: + """bool SDL_WarpMouseGlobal(float x, float y)""" + + @staticmethod + def SDL_WarpMouseInWindow(window: Any, x: float, y: float, /) -> None: + """void SDL_WarpMouseInWindow(SDL_Window *window, float x, float y)""" + + @staticmethod + def SDL_WasInit(flags: Any, /) -> Any: + """SDL_InitFlags SDL_WasInit(SDL_InitFlags flags)""" + + @staticmethod + def SDL_WindowHasSurface(window: Any, /) -> bool: + """bool SDL_WindowHasSurface(SDL_Window *window)""" + + @staticmethod + def SDL_WindowSupportsGPUPresentMode(device: Any, window: Any, present_mode: Any, /) -> bool: + """bool SDL_WindowSupportsGPUPresentMode(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUPresentMode present_mode)""" + + @staticmethod + def SDL_WindowSupportsGPUSwapchainComposition(device: Any, window: Any, swapchain_composition: Any, /) -> bool: + """bool SDL_WindowSupportsGPUSwapchainComposition(SDL_GPUDevice *device, SDL_Window *window, SDL_GPUSwapchainComposition swapchain_composition)""" + + @staticmethod + def SDL_WriteAsyncIO(asyncio: Any, ptr: Any, offset: Any, size: Any, queue: Any, userdata: Any, /) -> bool: + """bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)""" + + @staticmethod + def SDL_WriteIO(context: Any, ptr: Any, size: int, /) -> int: + """size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size)""" + + @staticmethod + def SDL_WriteS16BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value)""" + + @staticmethod + def SDL_WriteS16LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value)""" + + @staticmethod + def SDL_WriteS32BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value)""" + + @staticmethod + def SDL_WriteS32LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value)""" + + @staticmethod + def SDL_WriteS64BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS64BE(SDL_IOStream *dst, Sint64 value)""" + + @staticmethod + def SDL_WriteS64LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value)""" + + @staticmethod + def SDL_WriteS8(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteS8(SDL_IOStream *dst, Sint8 value)""" + + @staticmethod + def SDL_WriteStorageFile(storage: Any, path: Any, source: Any, length: Any, /) -> bool: + """bool SDL_WriteStorageFile(SDL_Storage *storage, const char *path, const void *source, Uint64 length)""" + + @staticmethod + def SDL_WriteSurfacePixel(surface: Any, x: int, y: int, r: Any, g: Any, b: Any, a: Any, /) -> bool: + """bool SDL_WriteSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a)""" + + @staticmethod + def SDL_WriteSurfacePixelFloat(surface: Any, x: int, y: int, r: float, g: float, b: float, a: float, /) -> bool: + """bool SDL_WriteSurfacePixelFloat(SDL_Surface *surface, int x, int y, float r, float g, float b, float a)""" + + @staticmethod + def SDL_WriteU16BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value)""" + + @staticmethod + def SDL_WriteU16LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value)""" + + @staticmethod + def SDL_WriteU32BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value)""" + + @staticmethod + def SDL_WriteU32LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value)""" + + @staticmethod + def SDL_WriteU64BE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value)""" + + @staticmethod + def SDL_WriteU64LE(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value)""" + + @staticmethod + def SDL_WriteU8(dst: Any, value: Any, /) -> bool: + """bool SDL_WriteU8(SDL_IOStream *dst, Uint8 value)""" + + @staticmethod + def SDL_abs(x: int, /) -> int: + """int SDL_abs(int x)""" + + @staticmethod + def SDL_acos(x: float, /) -> float: + """double SDL_acos(double x)""" + + @staticmethod + def SDL_acosf(x: float, /) -> float: + """float SDL_acosf(float x)""" + + @staticmethod + def SDL_aligned_alloc(alignment: int, size: int, /) -> Any: + """void *SDL_aligned_alloc(size_t alignment, size_t size)""" + + @staticmethod + def SDL_aligned_free(mem: Any, /) -> None: + """void SDL_aligned_free(void *mem)""" + + @staticmethod + def SDL_asin(x: float, /) -> float: + """double SDL_asin(double x)""" + + @staticmethod + def SDL_asinf(x: float, /) -> float: + """float SDL_asinf(float x)""" + + @staticmethod + def SDL_asprintf(strp: Any, fmt: Any, /, *__args: Any) -> int: + """int SDL_asprintf(char **strp, const char *fmt, ...)""" + + @staticmethod + def SDL_atan(x: float, /) -> float: + """double SDL_atan(double x)""" + + @staticmethod + def SDL_atan2(y: float, x: float, /) -> float: + """double SDL_atan2(double y, double x)""" + + @staticmethod + def SDL_atan2f(y: float, x: float, /) -> float: + """float SDL_atan2f(float y, float x)""" + + @staticmethod + def SDL_atanf(x: float, /) -> float: + """float SDL_atanf(float x)""" + + @staticmethod + def SDL_atof(str: Any, /) -> float: + """double SDL_atof(const char *str)""" + + @staticmethod + def SDL_atoi(str: Any, /) -> int: + """int SDL_atoi(const char *str)""" + + @staticmethod + def SDL_bsearch(key: Any, base: Any, nmemb: int, size: int, compare: Any, /) -> Any: + """void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, SDL_CompareCallback compare)""" + + @staticmethod + def SDL_bsearch_r(key: Any, base: Any, nmemb: int, size: int, compare: Any, userdata: Any, /) -> Any: + """void *SDL_bsearch_r(const void *key, const void *base, size_t nmemb, size_t size, SDL_CompareCallback_r compare, void *userdata)""" + + @staticmethod + def SDL_calloc(nmemb: int, size: int, /) -> Any: + """void *SDL_calloc(size_t nmemb, size_t size)""" + + @staticmethod + def SDL_ceil(x: float, /) -> float: + """double SDL_ceil(double x)""" + + @staticmethod + def SDL_ceilf(x: float, /) -> float: + """float SDL_ceilf(float x)""" + + @staticmethod + def SDL_copysign(x: float, y: float, /) -> float: + """double SDL_copysign(double x, double y)""" + + @staticmethod + def SDL_copysignf(x: float, y: float, /) -> float: + """float SDL_copysignf(float x, float y)""" + + @staticmethod + def SDL_cos(x: float, /) -> float: + """double SDL_cos(double x)""" + + @staticmethod + def SDL_cosf(x: float, /) -> float: + """float SDL_cosf(float x)""" + + @staticmethod + def SDL_crc16(crc: Any, data: Any, len: int, /) -> Any: + """Uint16 SDL_crc16(Uint16 crc, const void *data, size_t len)""" + + @staticmethod + def SDL_crc32(crc: Any, data: Any, len: int, /) -> Any: + """Uint32 SDL_crc32(Uint32 crc, const void *data, size_t len)""" + + @staticmethod + def SDL_exp(x: float, /) -> float: + """double SDL_exp(double x)""" + + @staticmethod + def SDL_expf(x: float, /) -> float: + """float SDL_expf(float x)""" + + @staticmethod + def SDL_fabs(x: float, /) -> float: + """double SDL_fabs(double x)""" + + @staticmethod + def SDL_fabsf(x: float, /) -> float: + """float SDL_fabsf(float x)""" + + @staticmethod + def SDL_floor(x: float, /) -> float: + """double SDL_floor(double x)""" + + @staticmethod + def SDL_floorf(x: float, /) -> float: + """float SDL_floorf(float x)""" + + @staticmethod + def SDL_fmod(x: float, y: float, /) -> float: + """double SDL_fmod(double x, double y)""" + + @staticmethod + def SDL_fmodf(x: float, y: float, /) -> float: + """float SDL_fmodf(float x, float y)""" + + @staticmethod + def SDL_free(mem: Any, /) -> None: + """void SDL_free(void *mem)""" + + @staticmethod + def SDL_getenv(name: Any, /) -> Any: + """const char *SDL_getenv(const char *name)""" + + @staticmethod + def SDL_getenv_unsafe(name: Any, /) -> Any: + """const char *SDL_getenv_unsafe(const char *name)""" + + @staticmethod + def SDL_hid_ble_scan(active: bool, /) -> None: + """void SDL_hid_ble_scan(bool active)""" + + @staticmethod + def SDL_hid_close(dev: Any, /) -> int: + """int SDL_hid_close(SDL_hid_device *dev)""" + + @staticmethod + def SDL_hid_device_change_count() -> Any: + """Uint32 SDL_hid_device_change_count(void)""" + + @staticmethod + def SDL_hid_enumerate(vendor_id: Any, product_id: Any, /) -> Any: + """SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned short product_id)""" + + @staticmethod + def SDL_hid_exit() -> int: + """int SDL_hid_exit(void)""" + + @staticmethod + def SDL_hid_free_enumeration(devs: Any, /) -> None: + """void SDL_hid_free_enumeration(SDL_hid_device_info *devs)""" + + @staticmethod + def SDL_hid_get_device_info(dev: Any, /) -> Any: + """SDL_hid_device_info *SDL_hid_get_device_info(SDL_hid_device *dev)""" + + @staticmethod + def SDL_hid_get_feature_report(dev: Any, data: Any, length: int, /) -> int: + """int SDL_hid_get_feature_report(SDL_hid_device *dev, unsigned char *data, size_t length)""" + + @staticmethod + def SDL_hid_get_indexed_string(dev: Any, string_index: int, string: Any, maxlen: int, /) -> int: + """int SDL_hid_get_indexed_string(SDL_hid_device *dev, int string_index, wchar_t *string, size_t maxlen)""" + + @staticmethod + def SDL_hid_get_input_report(dev: Any, data: Any, length: int, /) -> int: + """int SDL_hid_get_input_report(SDL_hid_device *dev, unsigned char *data, size_t length)""" + + @staticmethod + def SDL_hid_get_manufacturer_string(dev: Any, string: Any, maxlen: int, /) -> int: + """int SDL_hid_get_manufacturer_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)""" + + @staticmethod + def SDL_hid_get_product_string(dev: Any, string: Any, maxlen: int, /) -> int: + """int SDL_hid_get_product_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)""" + + @staticmethod + def SDL_hid_get_report_descriptor(dev: Any, buf: Any, buf_size: int, /) -> int: + """int SDL_hid_get_report_descriptor(SDL_hid_device *dev, unsigned char *buf, size_t buf_size)""" + + @staticmethod + def SDL_hid_get_serial_number_string(dev: Any, string: Any, maxlen: int, /) -> int: + """int SDL_hid_get_serial_number_string(SDL_hid_device *dev, wchar_t *string, size_t maxlen)""" + + @staticmethod + def SDL_hid_init() -> int: + """int SDL_hid_init(void)""" + + @staticmethod + def SDL_hid_open(vendor_id: Any, product_id: Any, serial_number: Any, /) -> Any: + """SDL_hid_device *SDL_hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)""" + + @staticmethod + def SDL_hid_open_path(path: Any, /) -> Any: + """SDL_hid_device *SDL_hid_open_path(const char *path)""" + + @staticmethod + def SDL_hid_read(dev: Any, data: Any, length: int, /) -> int: + """int SDL_hid_read(SDL_hid_device *dev, unsigned char *data, size_t length)""" + + @staticmethod + def SDL_hid_read_timeout(dev: Any, data: Any, length: int, milliseconds: int, /) -> int: + """int SDL_hid_read_timeout(SDL_hid_device *dev, unsigned char *data, size_t length, int milliseconds)""" + + @staticmethod + def SDL_hid_send_feature_report(dev: Any, data: Any, length: int, /) -> int: + """int SDL_hid_send_feature_report(SDL_hid_device *dev, const unsigned char *data, size_t length)""" + + @staticmethod + def SDL_hid_set_nonblocking(dev: Any, nonblock: int, /) -> int: + """int SDL_hid_set_nonblocking(SDL_hid_device *dev, int nonblock)""" + + @staticmethod + def SDL_hid_write(dev: Any, data: Any, length: int, /) -> int: + """int SDL_hid_write(SDL_hid_device *dev, const unsigned char *data, size_t length)""" + + @staticmethod + def SDL_iconv(cd: Any, inbuf: Any, inbytesleft: Any, outbuf: Any, outbytesleft: Any, /) -> int: + """size_t SDL_iconv(SDL_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)""" + + @staticmethod + def SDL_iconv_close(cd: Any, /) -> int: + """int SDL_iconv_close(SDL_iconv_t cd)""" + + @staticmethod + def SDL_iconv_open(tocode: Any, fromcode: Any, /) -> Any: + """SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode)""" + + @staticmethod + def SDL_iconv_string(tocode: Any, fromcode: Any, inbuf: Any, inbytesleft: int, /) -> Any: + """char *SDL_iconv_string(const char *tocode, const char *fromcode, const char *inbuf, size_t inbytesleft)""" + + @staticmethod + def SDL_isalnum(x: int, /) -> int: + """int SDL_isalnum(int x)""" + + @staticmethod + def SDL_isalpha(x: int, /) -> int: + """int SDL_isalpha(int x)""" + + @staticmethod + def SDL_isblank(x: int, /) -> int: + """int SDL_isblank(int x)""" + + @staticmethod + def SDL_iscntrl(x: int, /) -> int: + """int SDL_iscntrl(int x)""" + + @staticmethod + def SDL_isdigit(x: int, /) -> int: + """int SDL_isdigit(int x)""" + + @staticmethod + def SDL_isgraph(x: int, /) -> int: + """int SDL_isgraph(int x)""" + + @staticmethod + def SDL_isinf(x: float, /) -> int: + """int SDL_isinf(double x)""" + + @staticmethod + def SDL_isinff(x: float, /) -> int: + """int SDL_isinff(float x)""" + + @staticmethod + def SDL_islower(x: int, /) -> int: + """int SDL_islower(int x)""" + + @staticmethod + def SDL_isnan(x: float, /) -> int: + """int SDL_isnan(double x)""" + + @staticmethod + def SDL_isnanf(x: float, /) -> int: + """int SDL_isnanf(float x)""" + + @staticmethod + def SDL_isprint(x: int, /) -> int: + """int SDL_isprint(int x)""" + + @staticmethod + def SDL_ispunct(x: int, /) -> int: + """int SDL_ispunct(int x)""" + + @staticmethod + def SDL_isspace(x: int, /) -> int: + """int SDL_isspace(int x)""" + + @staticmethod + def SDL_isupper(x: int, /) -> int: + """int SDL_isupper(int x)""" + + @staticmethod + def SDL_isxdigit(x: int, /) -> int: + """int SDL_isxdigit(int x)""" + + @staticmethod + def SDL_itoa(value: int, str: Any, radix: int, /) -> Any: + """char *SDL_itoa(int value, char *str, int radix)""" + + @staticmethod + def SDL_lltoa(value: Any, str: Any, radix: int, /) -> Any: + """char *SDL_lltoa(long long value, char *str, int radix)""" + + @staticmethod + def SDL_log(x: float, /) -> float: + """double SDL_log(double x)""" + + @staticmethod + def SDL_log10(x: float, /) -> float: + """double SDL_log10(double x)""" + + @staticmethod + def SDL_log10f(x: float, /) -> float: + """float SDL_log10f(float x)""" + + @staticmethod + def SDL_logf(x: float, /) -> float: + """float SDL_logf(float x)""" + + @staticmethod + def SDL_lround(x: float, /) -> Any: + """long SDL_lround(double x)""" + + @staticmethod + def SDL_lroundf(x: float, /) -> Any: + """long SDL_lroundf(float x)""" + + @staticmethod + def SDL_ltoa(value: Any, str: Any, radix: int, /) -> Any: + """char *SDL_ltoa(long value, char *str, int radix)""" + + @staticmethod + def SDL_malloc(size: int, /) -> Any: + """void *SDL_malloc(size_t size)""" + + @staticmethod + def SDL_memcmp(s1: Any, s2: Any, len: int, /) -> int: + """int SDL_memcmp(const void *s1, const void *s2, size_t len)""" + + @staticmethod + def SDL_memcpy(dst: Any, src: Any, len: int, /) -> Any: + """void *SDL_memcpy(void *dst, const void *src, size_t len)""" + + @staticmethod + def SDL_memmove(dst: Any, src: Any, len: int, /) -> Any: + """void *SDL_memmove(void *dst, const void *src, size_t len)""" + + @staticmethod + def SDL_memset(dst: Any, c: int, len: int, /) -> Any: + """void *SDL_memset(void *dst, int c, size_t len)""" + + @staticmethod + def SDL_memset4(dst: Any, val: Any, dwords: int, /) -> Any: + """void *SDL_memset4(void *dst, Uint32 val, size_t dwords)""" + + @staticmethod + def SDL_modf(x: float, y: Any, /) -> float: + """double SDL_modf(double x, double *y)""" + + @staticmethod + def SDL_modff(x: float, y: Any, /) -> float: + """float SDL_modff(float x, float *y)""" + + @staticmethod + def SDL_murmur3_32(data: Any, len: int, seed: Any, /) -> Any: + """Uint32 SDL_murmur3_32(const void *data, size_t len, Uint32 seed)""" + + @staticmethod + def SDL_pow(x: float, y: float, /) -> float: + """double SDL_pow(double x, double y)""" + + @staticmethod + def SDL_powf(x: float, y: float, /) -> float: + """float SDL_powf(float x, float y)""" + + @staticmethod + def SDL_qsort(base: Any, nmemb: int, size: int, compare: Any, /) -> None: + """void SDL_qsort(void *base, size_t nmemb, size_t size, SDL_CompareCallback compare)""" + + @staticmethod + def SDL_qsort_r(base: Any, nmemb: int, size: int, compare: Any, userdata: Any, /) -> None: + """void SDL_qsort_r(void *base, size_t nmemb, size_t size, SDL_CompareCallback_r compare, void *userdata)""" + + @staticmethod + def SDL_rand(n: Any, /) -> Any: + """Sint32 SDL_rand(Sint32 n)""" + + @staticmethod + def SDL_rand_bits() -> Any: + """Uint32 SDL_rand_bits(void)""" + + @staticmethod + def SDL_rand_bits_r(state: Any, /) -> Any: + """Uint32 SDL_rand_bits_r(Uint64 *state)""" + + @staticmethod + def SDL_rand_r(state: Any, n: Any, /) -> Any: + """Sint32 SDL_rand_r(Uint64 *state, Sint32 n)""" + + @staticmethod + def SDL_randf() -> float: + """float SDL_randf(void)""" + + @staticmethod + def SDL_randf_r(state: Any, /) -> float: + """float SDL_randf_r(Uint64 *state)""" + + @staticmethod + def SDL_realloc(mem: Any, size: int, /) -> Any: + """void *SDL_realloc(void *mem, size_t size)""" + + @staticmethod + def SDL_round(x: float, /) -> float: + """double SDL_round(double x)""" + + @staticmethod + def SDL_roundf(x: float, /) -> float: + """float SDL_roundf(float x)""" + + @staticmethod + def SDL_scalbn(x: float, n: int, /) -> float: + """double SDL_scalbn(double x, int n)""" + + @staticmethod + def SDL_scalbnf(x: float, n: int, /) -> float: + """float SDL_scalbnf(float x, int n)""" + + @staticmethod + def SDL_setenv_unsafe(name: Any, value: Any, overwrite: int, /) -> int: + """int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)""" + + @staticmethod + def SDL_sin(x: float, /) -> float: + """double SDL_sin(double x)""" + + @staticmethod + def SDL_sinf(x: float, /) -> float: + """float SDL_sinf(float x)""" + + @staticmethod + def SDL_snprintf(text: Any, maxlen: int, fmt: Any, /, *__args: Any) -> int: + """int SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...)""" + + @staticmethod + def SDL_sqrt(x: float, /) -> float: + """double SDL_sqrt(double x)""" + + @staticmethod + def SDL_sqrtf(x: float, /) -> float: + """float SDL_sqrtf(float x)""" + + @staticmethod + def SDL_srand(seed: Any, /) -> None: + """void SDL_srand(Uint64 seed)""" + + @staticmethod + def SDL_sscanf(text: Any, fmt: Any, /, *__args: Any) -> int: + """int SDL_sscanf(const char *text, const char *fmt, ...)""" + + @staticmethod + def SDL_strcasecmp(str1: Any, str2: Any, /) -> int: + """int SDL_strcasecmp(const char *str1, const char *str2)""" + + @staticmethod + def SDL_strcasestr(haystack: Any, needle: Any, /) -> Any: + """char *SDL_strcasestr(const char *haystack, const char *needle)""" + + @staticmethod + def SDL_strchr(str: Any, c: int, /) -> Any: + """char *SDL_strchr(const char *str, int c)""" + + @staticmethod + def SDL_strcmp(str1: Any, str2: Any, /) -> int: + """int SDL_strcmp(const char *str1, const char *str2)""" + + @staticmethod + def SDL_strdup(str: Any, /) -> Any: + """char *SDL_strdup(const char *str)""" + + @staticmethod + def SDL_strlcat(dst: Any, src: Any, maxlen: int, /) -> int: + """size_t SDL_strlcat(char *dst, const char *src, size_t maxlen)""" + + @staticmethod + def SDL_strlcpy(dst: Any, src: Any, maxlen: int, /) -> int: + """size_t SDL_strlcpy(char *dst, const char *src, size_t maxlen)""" + + @staticmethod + def SDL_strlen(str: Any, /) -> int: + """size_t SDL_strlen(const char *str)""" + + @staticmethod + def SDL_strlwr(str: Any, /) -> Any: + """char *SDL_strlwr(char *str)""" + + @staticmethod + def SDL_strncasecmp(str1: Any, str2: Any, maxlen: int, /) -> int: + """int SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen)""" + + @staticmethod + def SDL_strncmp(str1: Any, str2: Any, maxlen: int, /) -> int: + """int SDL_strncmp(const char *str1, const char *str2, size_t maxlen)""" + + @staticmethod + def SDL_strndup(str: Any, maxlen: int, /) -> Any: + """char *SDL_strndup(const char *str, size_t maxlen)""" + + @staticmethod + def SDL_strnlen(str: Any, maxlen: int, /) -> int: + """size_t SDL_strnlen(const char *str, size_t maxlen)""" + + @staticmethod + def SDL_strnstr(haystack: Any, needle: Any, maxlen: int, /) -> Any: + """char *SDL_strnstr(const char *haystack, const char *needle, size_t maxlen)""" + + @staticmethod + def SDL_strpbrk(str: Any, breakset: Any, /) -> Any: + """char *SDL_strpbrk(const char *str, const char *breakset)""" + + @staticmethod + def SDL_strrchr(str: Any, c: int, /) -> Any: + """char *SDL_strrchr(const char *str, int c)""" + + @staticmethod + def SDL_strrev(str: Any, /) -> Any: + """char *SDL_strrev(char *str)""" + + @staticmethod + def SDL_strstr(haystack: Any, needle: Any, /) -> Any: + """char *SDL_strstr(const char *haystack, const char *needle)""" + + @staticmethod + def SDL_strtod(str: Any, endp: Any, /) -> float: + """double SDL_strtod(const char *str, char **endp)""" + + @staticmethod + def SDL_strtok_r(str: Any, delim: Any, saveptr: Any, /) -> Any: + """char *SDL_strtok_r(char *str, const char *delim, char **saveptr)""" + + @staticmethod + def SDL_strtol(str: Any, endp: Any, base: int, /) -> Any: + """long SDL_strtol(const char *str, char **endp, int base)""" + + @staticmethod + def SDL_strtoll(str: Any, endp: Any, base: int, /) -> Any: + """long long SDL_strtoll(const char *str, char **endp, int base)""" + + @staticmethod + def SDL_strtoul(str: Any, endp: Any, base: int, /) -> Any: + """unsigned long SDL_strtoul(const char *str, char **endp, int base)""" + + @staticmethod + def SDL_strtoull(str: Any, endp: Any, base: int, /) -> Any: + """unsigned long long SDL_strtoull(const char *str, char **endp, int base)""" + + @staticmethod + def SDL_strupr(str: Any, /) -> Any: + """char *SDL_strupr(char *str)""" + + @staticmethod + def SDL_swprintf(text: Any, maxlen: int, fmt: Any, /, *__args: Any) -> int: + """int SDL_swprintf(wchar_t *text, size_t maxlen, const wchar_t *fmt, ...)""" + + @staticmethod + def SDL_tan(x: float, /) -> float: + """double SDL_tan(double x)""" + + @staticmethod + def SDL_tanf(x: float, /) -> float: + """float SDL_tanf(float x)""" + + @staticmethod + def SDL_tolower(x: int, /) -> int: + """int SDL_tolower(int x)""" + + @staticmethod + def SDL_toupper(x: int, /) -> int: + """int SDL_toupper(int x)""" + + @staticmethod + def SDL_trunc(x: float, /) -> float: + """double SDL_trunc(double x)""" + + @staticmethod + def SDL_truncf(x: float, /) -> float: + """float SDL_truncf(float x)""" + + @staticmethod + def SDL_uitoa(value: int, str: Any, radix: int, /) -> Any: + """char *SDL_uitoa(unsigned int value, char *str, int radix)""" + + @staticmethod + def SDL_ulltoa(value: Any, str: Any, radix: int, /) -> Any: + """char *SDL_ulltoa(unsigned long long value, char *str, int radix)""" + + @staticmethod + def SDL_ultoa(value: Any, str: Any, radix: int, /) -> Any: + """char *SDL_ultoa(unsigned long value, char *str, int radix)""" + + @staticmethod + def SDL_unsetenv_unsafe(name: Any, /) -> int: + """int SDL_unsetenv_unsafe(const char *name)""" + + @staticmethod + def SDL_utf8strlcpy(dst: Any, src: Any, dst_bytes: int, /) -> int: + """size_t SDL_utf8strlcpy(char *dst, const char *src, size_t dst_bytes)""" + + @staticmethod + def SDL_utf8strlen(str: Any, /) -> int: + """size_t SDL_utf8strlen(const char *str)""" + + @staticmethod + def SDL_utf8strnlen(str: Any, bytes: int, /) -> int: + """size_t SDL_utf8strnlen(const char *str, size_t bytes)""" + + @staticmethod + def SDL_wcscasecmp(str1: Any, str2: Any, /) -> int: + """int SDL_wcscasecmp(const wchar_t *str1, const wchar_t *str2)""" + + @staticmethod + def SDL_wcscmp(str1: Any, str2: Any, /) -> int: + """int SDL_wcscmp(const wchar_t *str1, const wchar_t *str2)""" + + @staticmethod + def SDL_wcsdup(wstr: Any, /) -> Any: + """wchar_t *SDL_wcsdup(const wchar_t *wstr)""" + + @staticmethod + def SDL_wcslcat(dst: Any, src: Any, maxlen: int, /) -> int: + """size_t SDL_wcslcat(wchar_t *dst, const wchar_t *src, size_t maxlen)""" + + @staticmethod + def SDL_wcslcpy(dst: Any, src: Any, maxlen: int, /) -> int: + """size_t SDL_wcslcpy(wchar_t *dst, const wchar_t *src, size_t maxlen)""" + + @staticmethod + def SDL_wcslen(wstr: Any, /) -> int: + """size_t SDL_wcslen(const wchar_t *wstr)""" + + @staticmethod + def SDL_wcsncasecmp(str1: Any, str2: Any, maxlen: int, /) -> int: + """int SDL_wcsncasecmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)""" + + @staticmethod + def SDL_wcsncmp(str1: Any, str2: Any, maxlen: int, /) -> int: + """int SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen)""" + + @staticmethod + def SDL_wcsnlen(wstr: Any, maxlen: int, /) -> int: + """size_t SDL_wcsnlen(const wchar_t *wstr, size_t maxlen)""" + + @staticmethod + def SDL_wcsnstr(haystack: Any, needle: Any, maxlen: int, /) -> Any: + """wchar_t *SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen)""" + + @staticmethod + def SDL_wcsstr(haystack: Any, needle: Any, /) -> Any: + """wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)""" + + @staticmethod + def SDL_wcstol(str: Any, endp: Any, base: int, /) -> Any: + """long SDL_wcstol(const wchar_t *str, wchar_t **endp, int base)""" + + @staticmethod + def TCOD_bsp_contains(node: Any, x: int, y: int, /) -> bool: + """bool TCOD_bsp_contains(TCOD_bsp_t *node, int x, int y)""" + + @staticmethod + def TCOD_bsp_delete(node: Any, /) -> None: + """void TCOD_bsp_delete(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_father(node: Any, /) -> Any: + """TCOD_bsp_t *TCOD_bsp_father(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_find_node(node: Any, x: int, y: int, /) -> Any: + """TCOD_bsp_t *TCOD_bsp_find_node(TCOD_bsp_t *node, int x, int y)""" + + @staticmethod + def TCOD_bsp_is_leaf(node: Any, /) -> bool: + """bool TCOD_bsp_is_leaf(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_left(node: Any, /) -> Any: + """TCOD_bsp_t *TCOD_bsp_left(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_new() -> Any: + """TCOD_bsp_t *TCOD_bsp_new(void)""" + + @staticmethod + def TCOD_bsp_new_with_size(x: int, y: int, w: int, h: int, /) -> Any: + """TCOD_bsp_t *TCOD_bsp_new_with_size(int x, int y, int w, int h)""" + + @staticmethod + def TCOD_bsp_remove_sons(node: Any, /) -> None: + """void TCOD_bsp_remove_sons(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_resize(node: Any, x: int, y: int, w: int, h: int, /) -> None: + """void TCOD_bsp_resize(TCOD_bsp_t *node, int x, int y, int w, int h)""" + + @staticmethod + def TCOD_bsp_right(node: Any, /) -> Any: + """TCOD_bsp_t *TCOD_bsp_right(TCOD_bsp_t *node)""" + + @staticmethod + def TCOD_bsp_split_once(node: Any, horizontal: bool, position: int, /) -> None: + """void TCOD_bsp_split_once(TCOD_bsp_t *node, bool horizontal, int position)""" + + @staticmethod + def TCOD_bsp_split_recursive( + node: Any, randomizer: Any, nb: int, minHSize: int, minVSize: int, maxHRatio: float, maxVRatio: float, / + ) -> None: + """void TCOD_bsp_split_recursive(TCOD_bsp_t *node, TCOD_Random *randomizer, int nb, int minHSize, int minVSize, float maxHRatio, float maxVRatio)""" + + @staticmethod + def TCOD_bsp_traverse_in_order(node: Any, listener: Any, userData: Any, /) -> bool: + """bool TCOD_bsp_traverse_in_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)""" + + @staticmethod + def TCOD_bsp_traverse_inverted_level_order(node: Any, listener: Any, userData: Any, /) -> bool: + """bool TCOD_bsp_traverse_inverted_level_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)""" + + @staticmethod + def TCOD_bsp_traverse_level_order(node: Any, listener: Any, userData: Any, /) -> bool: + """bool TCOD_bsp_traverse_level_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)""" + + @staticmethod + def TCOD_bsp_traverse_post_order(node: Any, listener: Any, userData: Any, /) -> bool: + """bool TCOD_bsp_traverse_post_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)""" + + @staticmethod + def TCOD_bsp_traverse_pre_order(node: Any, listener: Any, userData: Any, /) -> bool: + """bool TCOD_bsp_traverse_pre_order(TCOD_bsp_t *node, TCOD_bsp_callback_t listener, void *userData)""" + + @staticmethod + def TCOD_clear_error() -> None: + """void TCOD_clear_error(void)""" + + @staticmethod + def TCOD_close_library(arg0: Any, /) -> None: + """void TCOD_close_library(TCOD_library_t)""" + + @staticmethod + def TCOD_color_HSV(hue: float, saturation: float, value: float, /) -> Any: + """TCOD_color_t TCOD_color_HSV(float hue, float saturation, float value)""" + + @staticmethod + def TCOD_color_RGB(r: Any, g: Any, b: Any, /) -> Any: + """TCOD_color_t TCOD_color_RGB(uint8_t r, uint8_t g, uint8_t b)""" + + @staticmethod + def TCOD_color_add(c1: Any, c2: Any, /) -> Any: + """TCOD_color_t TCOD_color_add(TCOD_color_t c1, TCOD_color_t c2)""" + + @staticmethod + def TCOD_color_add_wrapper(c1: Any, c2: Any, /) -> Any: + """colornum_t TCOD_color_add_wrapper(colornum_t c1, colornum_t c2)""" + + @staticmethod + def TCOD_color_alpha_blend(dst: Any, src: Any, /) -> None: + """void TCOD_color_alpha_blend(TCOD_ColorRGBA *dst, const TCOD_ColorRGBA *src)""" + + @staticmethod + def TCOD_color_equals(c1: Any, c2: Any, /) -> bool: + """bool TCOD_color_equals(TCOD_color_t c1, TCOD_color_t c2)""" + + @staticmethod + def TCOD_color_equals_wrapper(c1: Any, c2: Any, /) -> bool: + """bool TCOD_color_equals_wrapper(colornum_t c1, colornum_t c2)""" + + @staticmethod + def TCOD_color_gen_map(map: Any, nb_key: int, key_color: Any, key_index: Any, /) -> None: + """void TCOD_color_gen_map(TCOD_color_t *map, int nb_key, const TCOD_color_t *key_color, const int *key_index)""" + + @staticmethod + def TCOD_color_get_HSV(color: Any, hue: Any, saturation: Any, value: Any, /) -> None: + """void TCOD_color_get_HSV(TCOD_color_t color, float *hue, float *saturation, float *value)""" + + @staticmethod + def TCOD_color_get_HSV_wrapper(c: Any, h: Any, s: Any, v: Any, /) -> None: + """void TCOD_color_get_HSV_wrapper(colornum_t c, float *h, float *s, float *v)""" + + @staticmethod + def TCOD_color_get_hue(color: Any, /) -> float: + """float TCOD_color_get_hue(TCOD_color_t color)""" + + @staticmethod + def TCOD_color_get_hue_wrapper(c: Any, /) -> float: + """float TCOD_color_get_hue_wrapper(colornum_t c)""" + + @staticmethod + def TCOD_color_get_saturation(color: Any, /) -> float: + """float TCOD_color_get_saturation(TCOD_color_t color)""" + + @staticmethod + def TCOD_color_get_saturation_wrapper(c: Any, /) -> float: + """float TCOD_color_get_saturation_wrapper(colornum_t c)""" + + @staticmethod + def TCOD_color_get_value(color: Any, /) -> float: + """float TCOD_color_get_value(TCOD_color_t color)""" + + @staticmethod + def TCOD_color_get_value_wrapper(c: Any, /) -> float: + """float TCOD_color_get_value_wrapper(colornum_t c)""" + + @staticmethod + def TCOD_color_lerp(c1: Any, c2: Any, coef: float, /) -> Any: + """TCOD_color_t TCOD_color_lerp(TCOD_color_t c1, TCOD_color_t c2, float coef)""" + + @staticmethod + def TCOD_color_lerp_wrapper(c1: Any, c2: Any, coef: float, /) -> Any: + """colornum_t TCOD_color_lerp_wrapper(colornum_t c1, colornum_t c2, float coef)""" + + @staticmethod + def TCOD_color_multiply(c1: Any, c2: Any, /) -> Any: + """TCOD_color_t TCOD_color_multiply(TCOD_color_t c1, TCOD_color_t c2)""" + + @staticmethod + def TCOD_color_multiply_scalar(c1: Any, value: float, /) -> Any: + """TCOD_color_t TCOD_color_multiply_scalar(TCOD_color_t c1, float value)""" + + @staticmethod + def TCOD_color_multiply_scalar_wrapper(c1: Any, value: float, /) -> Any: + """colornum_t TCOD_color_multiply_scalar_wrapper(colornum_t c1, float value)""" + + @staticmethod + def TCOD_color_multiply_wrapper(c1: Any, c2: Any, /) -> Any: + """colornum_t TCOD_color_multiply_wrapper(colornum_t c1, colornum_t c2)""" + + @staticmethod + def TCOD_color_scale_HSV(color: Any, saturation_coef: float, value_coef: float, /) -> None: + """void TCOD_color_scale_HSV(TCOD_color_t *color, float saturation_coef, float value_coef)""" + + @staticmethod + def TCOD_color_set_HSV(color: Any, hue: float, saturation: float, value: float, /) -> None: + """void TCOD_color_set_HSV(TCOD_color_t *color, float hue, float saturation, float value)""" + + @staticmethod + def TCOD_color_set_hue(color: Any, hue: float, /) -> None: + """void TCOD_color_set_hue(TCOD_color_t *color, float hue)""" + + @staticmethod + def TCOD_color_set_saturation(color: Any, saturation: float, /) -> None: + """void TCOD_color_set_saturation(TCOD_color_t *color, float saturation)""" + + @staticmethod + def TCOD_color_set_value(color: Any, value: float, /) -> None: + """void TCOD_color_set_value(TCOD_color_t *color, float value)""" + + @staticmethod + def TCOD_color_shift_hue(color: Any, shift: float, /) -> None: + """void TCOD_color_shift_hue(TCOD_color_t *color, float shift)""" + + @staticmethod + def TCOD_color_subtract(c1: Any, c2: Any, /) -> Any: + """TCOD_color_t TCOD_color_subtract(TCOD_color_t c1, TCOD_color_t c2)""" + + @staticmethod + def TCOD_color_subtract_wrapper(c1: Any, c2: Any, /) -> Any: + """colornum_t TCOD_color_subtract_wrapper(colornum_t c1, colornum_t c2)""" + + @staticmethod + def TCOD_condition_broadcast(sem: Any, /) -> None: + """void TCOD_condition_broadcast(TCOD_cond_t sem)""" + + @staticmethod + def TCOD_condition_delete(sem: Any, /) -> None: + """void TCOD_condition_delete(TCOD_cond_t sem)""" + + @staticmethod + def TCOD_condition_new() -> Any: + """TCOD_cond_t TCOD_condition_new(void)""" + + @staticmethod + def TCOD_condition_signal(sem: Any, /) -> None: + """void TCOD_condition_signal(TCOD_cond_t sem)""" + + @staticmethod + def TCOD_condition_wait(sem: Any, mut: Any, /) -> None: + """void TCOD_condition_wait(TCOD_cond_t sem, TCOD_mutex_t mut)""" + + @staticmethod + def TCOD_console_blit( + src: Any, + xSrc: int, + ySrc: int, + wSrc: int, + hSrc: int, + dst: Any, + xDst: int, + yDst: int, + foreground_alpha: float, + background_alpha: float, + /, + ) -> None: + """void TCOD_console_blit(const TCOD_Console *src, int xSrc, int ySrc, int wSrc, int hSrc, TCOD_Console *dst, int xDst, int yDst, float foreground_alpha, float background_alpha)""" + + @staticmethod + def TCOD_console_blit_key_color( + src: Any, + xSrc: int, + ySrc: int, + wSrc: int, + hSrc: int, + dst: Any, + xDst: int, + yDst: int, + foreground_alpha: float, + background_alpha: float, + key_color: Any, + /, + ) -> None: + """void TCOD_console_blit_key_color(const TCOD_Console *src, int xSrc, int ySrc, int wSrc, int hSrc, TCOD_Console *dst, int xDst, int yDst, float foreground_alpha, float background_alpha, const TCOD_color_t *key_color)""" + + @staticmethod + def TCOD_console_check_for_keypress(flags: int, /) -> Any: + """TCOD_key_t TCOD_console_check_for_keypress(int flags)""" + + @staticmethod + def TCOD_console_check_for_keypress_wrapper(holder: Any, flags: int, /) -> bool: + """bool TCOD_console_check_for_keypress_wrapper(TCOD_key_t *holder, int flags)""" + + @staticmethod + def TCOD_console_clear(con: Any, /) -> None: + """void TCOD_console_clear(TCOD_Console *con)""" + + @staticmethod + def TCOD_console_credits() -> None: + """void TCOD_console_credits(void)""" + + @staticmethod + def TCOD_console_credits_render(x: int, y: int, alpha: bool, /) -> bool: + """bool TCOD_console_credits_render(int x, int y, bool alpha)""" + + @staticmethod + def TCOD_console_credits_render_ex(console: Any, x: int, y: int, alpha: bool, delta_time: float, /) -> bool: + """bool TCOD_console_credits_render_ex(TCOD_Console *console, int x, int y, bool alpha, float delta_time)""" + + @staticmethod + def TCOD_console_credits_reset() -> None: + """void TCOD_console_credits_reset(void)""" + + @staticmethod + def TCOD_console_delete(console: Any, /) -> None: + """void TCOD_console_delete(TCOD_Console *console)""" + + @staticmethod + def TCOD_console_disable_keyboard_repeat() -> None: + """void TCOD_console_disable_keyboard_repeat(void)""" + + @staticmethod + def TCOD_console_double_hline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None: + """void TCOD_console_double_hline(TCOD_console_t con, int x, int y, int l, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_double_vline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None: + """void TCOD_console_double_vline(TCOD_console_t con, int x, int y, int l, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_draw_frame_rgb( + con: Any, x: int, y: int, width: int, height: int, decoration: Any, fg: Any, bg: Any, flag: Any, clear: bool, / + ) -> Any: + """TCOD_Error TCOD_console_draw_frame_rgb(struct TCOD_Console *con, int x, int y, int width, int height, const int *decoration, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, bool clear)""" + + @staticmethod + def TCOD_console_draw_rect_rgb( + console: Any, x: int, y: int, width: int, height: int, ch: int, fg: Any, bg: Any, flag: Any, / + ) -> Any: + """TCOD_Error TCOD_console_draw_rect_rgb(TCOD_Console *console, int x, int y, int width, int height, int ch, const TCOD_color_t *fg, const TCOD_color_t *bg, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_fill_background(con: Any, r: Any, g: Any, b: Any, /) -> None: + """void TCOD_console_fill_background(TCOD_console_t con, int *r, int *g, int *b)""" + + @staticmethod + def TCOD_console_fill_char(con: Any, arr: Any, /) -> None: + """void TCOD_console_fill_char(TCOD_console_t con, int *arr)""" + + @staticmethod + def TCOD_console_fill_foreground(con: Any, r: Any, g: Any, b: Any, /) -> None: + """void TCOD_console_fill_foreground(TCOD_console_t con, int *r, int *g, int *b)""" + + @staticmethod + def TCOD_console_flush() -> Any: + """TCOD_Error TCOD_console_flush(void)""" + + @staticmethod + def TCOD_console_flush_ex(console: Any, viewport: Any, /) -> Any: + """TCOD_Error TCOD_console_flush_ex(TCOD_Console *console, struct TCOD_ViewportOptions *viewport)""" + + @staticmethod + def TCOD_console_forward(s: Any, l: int, /) -> Any: + """unsigned char *TCOD_console_forward(unsigned char *s, int l)""" + + @staticmethod + def TCOD_console_from_file(filename: Any, /) -> Any: + """TCOD_console_t TCOD_console_from_file(const char *filename)""" + + @staticmethod + def TCOD_console_from_xp(filename: Any, /) -> Any: + """TCOD_console_t TCOD_console_from_xp(const char *filename)""" + + @staticmethod + def TCOD_console_get_alignment(con: Any, /) -> Any: + """TCOD_alignment_t TCOD_console_get_alignment(TCOD_Console *con)""" + + @staticmethod + def TCOD_console_get_background_flag(con: Any, /) -> Any: + """TCOD_bkgnd_flag_t TCOD_console_get_background_flag(TCOD_Console *con)""" + + @staticmethod + def TCOD_console_get_char(con: Any, x: int, y: int, /) -> int: + """int TCOD_console_get_char(const TCOD_Console *con, int x, int y)""" + + @staticmethod + def TCOD_console_get_char_background(con: Any, x: int, y: int, /) -> Any: + """TCOD_color_t TCOD_console_get_char_background(const TCOD_Console *con, int x, int y)""" + + @staticmethod + def TCOD_console_get_char_background_wrapper(con: Any, x: int, y: int, /) -> Any: + """colornum_t TCOD_console_get_char_background_wrapper(TCOD_console_t con, int x, int y)""" + + @staticmethod + def TCOD_console_get_char_foreground(con: Any, x: int, y: int, /) -> Any: + """TCOD_color_t TCOD_console_get_char_foreground(const TCOD_Console *con, int x, int y)""" + + @staticmethod + def TCOD_console_get_char_foreground_wrapper(con: Any, x: int, y: int, /) -> Any: + """colornum_t TCOD_console_get_char_foreground_wrapper(TCOD_console_t con, int x, int y)""" + + @staticmethod + def TCOD_console_get_default_background(con: Any, /) -> Any: + """TCOD_color_t TCOD_console_get_default_background(TCOD_Console *con)""" + + @staticmethod + def TCOD_console_get_default_background_wrapper(con: Any, /) -> Any: + """colornum_t TCOD_console_get_default_background_wrapper(TCOD_console_t con)""" + + @staticmethod + def TCOD_console_get_default_foreground(con: Any, /) -> Any: + """TCOD_color_t TCOD_console_get_default_foreground(TCOD_Console *con)""" + + @staticmethod + def TCOD_console_get_default_foreground_wrapper(con: Any, /) -> Any: + """colornum_t TCOD_console_get_default_foreground_wrapper(TCOD_console_t con)""" + + @staticmethod + def TCOD_console_get_fade() -> Any: + """uint8_t TCOD_console_get_fade(void)""" + + @staticmethod + def TCOD_console_get_fading_color() -> Any: + """TCOD_color_t TCOD_console_get_fading_color(void)""" + + @staticmethod + def TCOD_console_get_fading_color_wrapper() -> Any: + """colornum_t TCOD_console_get_fading_color_wrapper(void)""" + + @staticmethod + def TCOD_console_get_height(con: Any, /) -> int: + """int TCOD_console_get_height(const TCOD_Console *con)""" + + @staticmethod + def TCOD_console_get_height_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_get_height_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_get_height_rect_fmt(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_get_height_rect_fmt(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_get_height_rect_n( + console: Any, x: int, y: int, width: int, height: int, n: int, str: Any, / + ) -> int: + """int TCOD_console_get_height_rect_n(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *str)""" + + @staticmethod + def TCOD_console_get_height_rect_utf(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_get_height_rect_utf(TCOD_Console *con, int x, int y, int w, int h, const wchar_t *fmt, ...)""" + + @staticmethod + def TCOD_console_get_height_rect_wn(width: int, n: int, str: Any, /) -> int: + """int TCOD_console_get_height_rect_wn(int width, size_t n, const char *str)""" + + @staticmethod + def TCOD_console_get_width(con: Any, /) -> int: + """int TCOD_console_get_width(const TCOD_Console *con)""" + + @staticmethod + def TCOD_console_has_mouse_focus() -> bool: + """bool TCOD_console_has_mouse_focus(void)""" + + @staticmethod + def TCOD_console_hline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None: + """void TCOD_console_hline(TCOD_Console *con, int x, int y, int l, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_init_root(w: int, h: int, title: Any, fullscreen: bool, renderer: Any, /) -> Any: + """TCOD_Error TCOD_console_init_root(int w, int h, const char *title, bool fullscreen, TCOD_renderer_t renderer)""" + + @staticmethod + def TCOD_console_init_root_(w: int, h: int, title: Any, fullscreen: bool, renderer: Any, vsync: bool, /) -> Any: + """TCOD_Error TCOD_console_init_root_(int w, int h, const char *title, bool fullscreen, TCOD_renderer_t renderer, bool vsync)""" + + @staticmethod + def TCOD_console_is_active() -> bool: + """bool TCOD_console_is_active(void)""" + + @staticmethod + def TCOD_console_is_fullscreen() -> bool: + """bool TCOD_console_is_fullscreen(void)""" + + @staticmethod + def TCOD_console_is_index_valid_(console: Any, x: int, y: int, /) -> bool: + """inline static bool TCOD_console_is_index_valid_(const TCOD_Console *console, int x, int y)""" + + @staticmethod + def TCOD_console_is_key_pressed(key: Any, /) -> bool: + """bool TCOD_console_is_key_pressed(TCOD_keycode_t key)""" + + @staticmethod + def TCOD_console_is_window_closed() -> bool: + """bool TCOD_console_is_window_closed(void)""" + + @staticmethod + def TCOD_console_list_from_xp(filename: Any, /) -> Any: + """TCOD_list_t TCOD_console_list_from_xp(const char *filename)""" + + @staticmethod + def TCOD_console_list_save_xp(console_list: Any, filename: Any, compress_level: int, /) -> bool: + """bool TCOD_console_list_save_xp(TCOD_list_t console_list, const char *filename, int compress_level)""" + + @staticmethod + def TCOD_console_load_apf(con: Any, filename: Any, /) -> bool: + """bool TCOD_console_load_apf(TCOD_console_t con, const char *filename)""" + + @staticmethod + def TCOD_console_load_asc(con: Any, filename: Any, /) -> bool: + """bool TCOD_console_load_asc(TCOD_console_t con, const char *filename)""" + + @staticmethod + def TCOD_console_load_xp(con: Any, filename: Any, /) -> bool: + """bool TCOD_console_load_xp(TCOD_Console *con, const char *filename)""" + + @staticmethod + def TCOD_console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int, /) -> None: + """void TCOD_console_map_ascii_code_to_font(int asciiCode, int fontCharX, int fontCharY)""" + + @staticmethod + def TCOD_console_map_ascii_codes_to_font(asciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int, /) -> None: + """void TCOD_console_map_ascii_codes_to_font(int asciiCode, int nbCodes, int fontCharX, int fontCharY)""" + + @staticmethod + def TCOD_console_map_string_to_font(s: Any, fontCharX: int, fontCharY: int, /) -> None: + """void TCOD_console_map_string_to_font(const char *s, int fontCharX, int fontCharY)""" + + @staticmethod + def TCOD_console_map_string_to_font_utf(s: Any, fontCharX: int, fontCharY: int, /) -> None: + """void TCOD_console_map_string_to_font_utf(const wchar_t *s, int fontCharX, int fontCharY)""" + + @staticmethod + def TCOD_console_new(w: int, h: int, /) -> Any: + """TCOD_Console *TCOD_console_new(int w, int h)""" + + @staticmethod + def TCOD_console_print(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> None: + """void TCOD_console_print(TCOD_Console *con, int x, int y, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_double_frame( + con: Any, x: int, y: int, w: int, h: int, empty: bool, flag: Any, fmt: Any, /, *__args: Any + ) -> None: + """void TCOD_console_print_double_frame(TCOD_console_t con, int x, int y, int w, int h, bool empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_ex(con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any) -> None: + """void TCOD_console_print_ex(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_ex_utf( + con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any + ) -> None: + """void TCOD_console_print_ex_utf(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const wchar_t *fmt, ...)""" + + @staticmethod + def TCOD_console_print_frame( + con: Any, x: int, y: int, w: int, h: int, empty: bool, flag: Any, fmt: Any, /, *__args: Any + ) -> None: + """void TCOD_console_print_frame(TCOD_console_t con, int x, int y, int w, int h, bool empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_internal( + con: Any, x: int, y: int, w: int, h: int, flag: Any, align: Any, msg: Any, can_split: bool, count_only: bool, / + ) -> int: + """int TCOD_console_print_internal(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, char *msg, bool can_split, bool count_only)""" + + @staticmethod + def TCOD_console_print_internal_utf( + con: Any, + x: int, + y: int, + rw: int, + rh: int, + flag: Any, + align: Any, + msg: Any, + can_split: bool, + count_only: bool, + /, + ) -> int: + """int TCOD_console_print_internal_utf(TCOD_console_t con, int x, int y, int rw, int rh, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, wchar_t *msg, bool can_split, bool count_only)""" + + @staticmethod + def TCOD_console_print_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_print_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_rect_ex( + con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any + ) -> int: + """int TCOD_console_print_rect_ex(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_print_rect_ex_utf( + con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any + ) -> int: + """int TCOD_console_print_rect_ex_utf(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const wchar_t *fmt, ...)""" + + @staticmethod + def TCOD_console_print_rect_utf(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_print_rect_utf(TCOD_Console *con, int x, int y, int w, int h, const wchar_t *fmt, ...)""" + + @staticmethod + def TCOD_console_print_return_string( + con: Any, + x: int, + y: int, + rw: int, + rh: int, + flag: Any, + align: Any, + msg: Any, + can_split: bool, + count_only: bool, + /, + ) -> Any: + """char *TCOD_console_print_return_string(TCOD_console_t con, int x, int y, int rw, int rh, TCOD_bkgnd_flag_t flag, TCOD_alignment_t align, char *msg, bool can_split, bool count_only)""" + + @staticmethod + def TCOD_console_print_utf(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> None: + """void TCOD_console_print_utf(TCOD_Console *con, int x, int y, const wchar_t *fmt, ...)""" + + @staticmethod + def TCOD_console_printf(con: Any, x: int, y: int, fmt: Any, /, *__args: Any) -> Any: + """TCOD_Error TCOD_console_printf(TCOD_Console *con, int x, int y, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_printf_ex(con: Any, x: int, y: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any) -> Any: + """TCOD_Error TCOD_console_printf_ex(TCOD_Console *con, int x, int y, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_printf_frame( + con: Any, x: int, y: int, w: int, h: int, empty: int, flag: Any, fmt: Any, /, *__args: Any + ) -> Any: + """TCOD_Error TCOD_console_printf_frame(TCOD_Console *con, int x, int y, int w, int h, int empty, TCOD_bkgnd_flag_t flag, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_printf_rect(con: Any, x: int, y: int, w: int, h: int, fmt: Any, /, *__args: Any) -> int: + """int TCOD_console_printf_rect(TCOD_Console *con, int x, int y, int w, int h, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_printf_rect_ex( + con: Any, x: int, y: int, w: int, h: int, flag: Any, alignment: Any, fmt: Any, /, *__args: Any + ) -> int: + """int TCOD_console_printf_rect_ex(TCOD_Console *con, int x, int y, int w, int h, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment, const char *fmt, ...)""" + + @staticmethod + def TCOD_console_printn( + console: Any, x: int, y: int, n: int, str: Any, fg: Any, bg: Any, flag: Any, alignment: Any, / + ) -> Any: + """TCOD_Error TCOD_console_printn(TCOD_Console *console, int x, int y, size_t n, const char *str, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment)""" + + @staticmethod + def TCOD_console_printn_frame( + console: Any, + x: int, + y: int, + width: int, + height: int, + n: int, + title: Any, + fg: Any, + bg: Any, + flag: Any, + clear: bool, + /, + ) -> Any: + """TCOD_Error TCOD_console_printn_frame(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *title, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, bool clear)""" + + @staticmethod + def TCOD_console_printn_rect( + console: Any, + x: int, + y: int, + width: int, + height: int, + n: int, + str: Any, + fg: Any, + bg: Any, + flag: Any, + alignment: Any, + /, + ) -> int: + """int TCOD_console_printn_rect(TCOD_Console *console, int x, int y, int width, int height, size_t n, const char *str, const TCOD_ColorRGB *fg, const TCOD_ColorRGB *bg, TCOD_bkgnd_flag_t flag, TCOD_alignment_t alignment)""" + + @staticmethod + def TCOD_console_put_char(con: Any, x: int, y: int, c: int, flag: Any, /) -> None: + """void TCOD_console_put_char(TCOD_Console *con, int x, int y, int c, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_put_char_ex(con: Any, x: int, y: int, c: int, fore: Any, back: Any, /) -> None: + """void TCOD_console_put_char_ex(TCOD_Console *con, int x, int y, int c, TCOD_color_t fore, TCOD_color_t back)""" + + @staticmethod + def TCOD_console_put_char_ex_wrapper(con: Any, x: int, y: int, c: int, fore: Any, back: Any, /) -> None: + """void TCOD_console_put_char_ex_wrapper(TCOD_console_t con, int x, int y, int c, colornum_t fore, colornum_t back)""" + + @staticmethod + def TCOD_console_put_rgb(console: Any, x: int, y: int, ch: int, fg: Any, bg: Any, flag: Any, /) -> None: + """void TCOD_console_put_rgb(TCOD_Console *console, int x, int y, int ch, const TCOD_color_t *fg, const TCOD_color_t *bg, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_rect(con: Any, x: int, y: int, rw: int, rh: int, clear: bool, flag: Any, /) -> None: + """void TCOD_console_rect(TCOD_Console *con, int x, int y, int rw, int rh, bool clear, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_resize_(console: Any, width: int, height: int, /) -> None: + """void TCOD_console_resize_(TCOD_Console *console, int width, int height)""" + + @staticmethod + def TCOD_console_save_apf(con: Any, filename: Any, /) -> bool: + """bool TCOD_console_save_apf(TCOD_console_t con, const char *filename)""" + + @staticmethod + def TCOD_console_save_asc(con: Any, filename: Any, /) -> bool: + """bool TCOD_console_save_asc(TCOD_console_t con, const char *filename)""" + + @staticmethod + def TCOD_console_save_xp(con: Any, filename: Any, compress_level: int, /) -> bool: + """bool TCOD_console_save_xp(const TCOD_Console *con, const char *filename, int compress_level)""" + + @staticmethod + def TCOD_console_set_alignment(con: Any, alignment: Any, /) -> None: + """void TCOD_console_set_alignment(TCOD_Console *con, TCOD_alignment_t alignment)""" + + @staticmethod + def TCOD_console_set_background_flag(con: Any, flag: Any, /) -> None: + """void TCOD_console_set_background_flag(TCOD_Console *con, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_set_char(con: Any, x: int, y: int, c: int, /) -> None: + """void TCOD_console_set_char(TCOD_Console *con, int x, int y, int c)""" + + @staticmethod + def TCOD_console_set_char_background(con: Any, x: int, y: int, col: Any, flag: Any, /) -> None: + """void TCOD_console_set_char_background(TCOD_Console *con, int x, int y, TCOD_color_t col, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_set_char_background_wrapper(con: Any, x: int, y: int, col: Any, flag: Any, /) -> None: + """void TCOD_console_set_char_background_wrapper(TCOD_console_t con, int x, int y, colornum_t col, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_set_char_foreground(con: Any, x: int, y: int, col: Any, /) -> None: + """void TCOD_console_set_char_foreground(TCOD_Console *con, int x, int y, TCOD_color_t col)""" + + @staticmethod + def TCOD_console_set_char_foreground_wrapper(con: Any, x: int, y: int, col: Any, /) -> None: + """void TCOD_console_set_char_foreground_wrapper(TCOD_console_t con, int x, int y, colornum_t col)""" + + @staticmethod + def TCOD_console_set_color_control(con: Any, fore: Any, back: Any, /) -> None: + """void TCOD_console_set_color_control(TCOD_colctrl_t con, TCOD_color_t fore, TCOD_color_t back)""" + + @staticmethod + def TCOD_console_set_color_control_wrapper(con: Any, fore: Any, back: Any, /) -> None: + """void TCOD_console_set_color_control_wrapper(TCOD_colctrl_t con, colornum_t fore, colornum_t back)""" + + @staticmethod + def TCOD_console_set_custom_font(fontFile: Any, flags: int, nb_char_horiz: int, nb_char_vertic: int, /) -> Any: + """TCOD_Error TCOD_console_set_custom_font(const char *fontFile, int flags, int nb_char_horiz, int nb_char_vertic)""" + + @staticmethod + def TCOD_console_set_default_background(con: Any, col: Any, /) -> None: + """void TCOD_console_set_default_background(TCOD_Console *con, TCOD_color_t col)""" + + @staticmethod + def TCOD_console_set_default_background_wrapper(con: Any, col: Any, /) -> None: + """void TCOD_console_set_default_background_wrapper(TCOD_console_t con, colornum_t col)""" + + @staticmethod + def TCOD_console_set_default_foreground(con: Any, col: Any, /) -> None: + """void TCOD_console_set_default_foreground(TCOD_Console *con, TCOD_color_t col)""" + + @staticmethod + def TCOD_console_set_default_foreground_wrapper(con: Any, col: Any, /) -> None: + """void TCOD_console_set_default_foreground_wrapper(TCOD_console_t con, colornum_t col)""" + + @staticmethod + def TCOD_console_set_dirty(x: int, y: int, w: int, h: int, /) -> None: + """void TCOD_console_set_dirty(int x, int y, int w, int h)""" + + @staticmethod + def TCOD_console_set_fade(val: Any, fade_color: Any, /) -> None: + """void TCOD_console_set_fade(uint8_t val, TCOD_color_t fade_color)""" + + @staticmethod + def TCOD_console_set_fade_wrapper(val: Any, fade: Any, /) -> None: + """void TCOD_console_set_fade_wrapper(uint8_t val, colornum_t fade)""" + + @staticmethod + def TCOD_console_set_fullscreen(fullscreen: bool, /) -> None: + """void TCOD_console_set_fullscreen(bool fullscreen)""" + + @staticmethod + def TCOD_console_set_key_color(con: Any, col: Any, /) -> None: + """void TCOD_console_set_key_color(TCOD_Console *con, TCOD_color_t col)""" + + @staticmethod + def TCOD_console_set_key_color_wrapper(con: Any, c: Any, /) -> None: + """void TCOD_console_set_key_color_wrapper(TCOD_console_t con, colornum_t c)""" + + @staticmethod + def TCOD_console_set_keyboard_repeat(initial_delay: int, interval: int, /) -> None: + """void TCOD_console_set_keyboard_repeat(int initial_delay, int interval)""" + + @staticmethod + def TCOD_console_set_window_title(title: Any, /) -> None: + """void TCOD_console_set_window_title(const char *title)""" + + @staticmethod + def TCOD_console_stringLength(s: Any, /) -> int: + """int TCOD_console_stringLength(const unsigned char *s)""" + + @staticmethod + def TCOD_console_validate_(console: Any, /) -> Any: + """inline static TCOD_Console *TCOD_console_validate_(const TCOD_Console *console)""" + + @staticmethod + def TCOD_console_vline(con: Any, x: int, y: int, l: int, flag: Any, /) -> None: + """void TCOD_console_vline(TCOD_Console *con, int x, int y, int l, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TCOD_console_wait_for_keypress(flush: bool, /) -> Any: + """TCOD_key_t TCOD_console_wait_for_keypress(bool flush)""" + + @staticmethod + def TCOD_console_wait_for_keypress_wrapper(holder: Any, flush: bool, /) -> None: + """void TCOD_console_wait_for_keypress_wrapper(TCOD_key_t *holder, bool flush)""" + + @staticmethod + def TCOD_context_change_tileset(self: Any, tileset: Any, /) -> Any: + """TCOD_Error TCOD_context_change_tileset(struct TCOD_Context *self, TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_context_convert_event_coordinates(context: Any, event: Any, /) -> Any: + """TCOD_Error TCOD_context_convert_event_coordinates(struct TCOD_Context *context, union SDL_Event *event)""" + + @staticmethod + def TCOD_context_delete(renderer: Any, /) -> None: + """void TCOD_context_delete(struct TCOD_Context *renderer)""" + + @staticmethod + def TCOD_context_get_renderer_type(context: Any, /) -> int: + """int TCOD_context_get_renderer_type(struct TCOD_Context *context)""" + + @staticmethod + def TCOD_context_get_sdl_renderer(context: Any, /) -> Any: + """struct SDL_Renderer *TCOD_context_get_sdl_renderer(struct TCOD_Context *context)""" + + @staticmethod + def TCOD_context_get_sdl_window(context: Any, /) -> Any: + """struct SDL_Window *TCOD_context_get_sdl_window(struct TCOD_Context *context)""" + + @staticmethod + def TCOD_context_new(params: Any, out: Any, /) -> Any: + """TCOD_Error TCOD_context_new(const TCOD_ContextParams *params, TCOD_Context **out)""" + + @staticmethod + def TCOD_context_new_() -> Any: + """struct TCOD_Context *TCOD_context_new_(void)""" + + @staticmethod + def TCOD_context_present(context: Any, console: Any, viewport: Any, /) -> Any: + """TCOD_Error TCOD_context_present(struct TCOD_Context *context, const struct TCOD_Console *console, const struct TCOD_ViewportOptions *viewport)""" + + @staticmethod + def TCOD_context_recommended_console_size(context: Any, magnification: float, columns: Any, rows: Any, /) -> Any: + """TCOD_Error TCOD_context_recommended_console_size(struct TCOD_Context *context, float magnification, int *columns, int *rows)""" + + @staticmethod + def TCOD_context_save_screenshot(context: Any, filename: Any, /) -> Any: + """TCOD_Error TCOD_context_save_screenshot(struct TCOD_Context *context, const char *filename)""" + + @staticmethod + def TCOD_context_screen_capture(context: Any, out_pixels: Any, width: Any, height: Any, /) -> Any: + """TCOD_Error TCOD_context_screen_capture(struct TCOD_Context *context, TCOD_ColorRGBA *out_pixels, int *width, int *height)""" + + @staticmethod + def TCOD_context_screen_capture_alloc(context: Any, width: Any, height: Any, /) -> Any: + """TCOD_ColorRGBA *TCOD_context_screen_capture_alloc(struct TCOD_Context *context, int *width, int *height)""" + + @staticmethod + def TCOD_context_screen_pixel_to_tile_d(context: Any, x: Any, y: Any, /) -> Any: + """TCOD_Error TCOD_context_screen_pixel_to_tile_d(struct TCOD_Context *context, double *x, double *y)""" + + @staticmethod + def TCOD_context_screen_pixel_to_tile_i(context: Any, x: Any, y: Any, /) -> Any: + """TCOD_Error TCOD_context_screen_pixel_to_tile_i(struct TCOD_Context *context, int *x, int *y)""" + + @staticmethod + def TCOD_context_set_mouse_transform(context: Any, transform: Any, /) -> Any: + """TCOD_Error TCOD_context_set_mouse_transform(struct TCOD_Context *context, const TCOD_MouseTransform *transform)""" + + @staticmethod + def TCOD_dijkstra_compute(dijkstra: Any, root_x: int, root_y: int, /) -> None: + """void TCOD_dijkstra_compute(TCOD_Dijkstra *dijkstra, int root_x, int root_y)""" + + @staticmethod + def TCOD_dijkstra_delete(dijkstra: Any, /) -> None: + """void TCOD_dijkstra_delete(TCOD_Dijkstra *dijkstra)""" + + @staticmethod + def TCOD_dijkstra_get(path: Any, index: int, x: Any, y: Any, /) -> None: + """void TCOD_dijkstra_get(TCOD_Dijkstra *path, int index, int *x, int *y)""" + + @staticmethod + def TCOD_dijkstra_get_distance(dijkstra: Any, x: int, y: int, /) -> float: + """float TCOD_dijkstra_get_distance(TCOD_Dijkstra *dijkstra, int x, int y)""" + + @staticmethod + def TCOD_dijkstra_is_empty(path: Any, /) -> bool: + """bool TCOD_dijkstra_is_empty(TCOD_Dijkstra *path)""" + + @staticmethod + def TCOD_dijkstra_new(map: Any, diagonalCost: float, /) -> Any: + """TCOD_Dijkstra *TCOD_dijkstra_new(TCOD_Map *map, float diagonalCost)""" + + @staticmethod + def TCOD_dijkstra_new_using_function( + map_width: int, map_height: int, func: Any, user_data: Any, diagonalCost: float, / + ) -> Any: + """TCOD_Dijkstra *TCOD_dijkstra_new_using_function(int map_width, int map_height, TCOD_path_func_t func, void *user_data, float diagonalCost)""" + + @staticmethod + def TCOD_dijkstra_path_set(dijkstra: Any, x: int, y: int, /) -> bool: + """bool TCOD_dijkstra_path_set(TCOD_Dijkstra *dijkstra, int x, int y)""" + + @staticmethod + def TCOD_dijkstra_path_walk(dijkstra: Any, x: Any, y: Any, /) -> bool: + """bool TCOD_dijkstra_path_walk(TCOD_Dijkstra *dijkstra, int *x, int *y)""" + + @staticmethod + def TCOD_dijkstra_reverse(path: Any, /) -> None: + """void TCOD_dijkstra_reverse(TCOD_Dijkstra *path)""" + + @staticmethod + def TCOD_dijkstra_size(path: Any, /) -> int: + """int TCOD_dijkstra_size(TCOD_Dijkstra *path)""" + + @staticmethod + def TCOD_frontier_clear(frontier: Any, /) -> Any: + """TCOD_Error TCOD_frontier_clear(struct TCOD_Frontier *frontier)""" + + @staticmethod + def TCOD_frontier_delete(frontier: Any, /) -> None: + """void TCOD_frontier_delete(struct TCOD_Frontier *frontier)""" + + @staticmethod + def TCOD_frontier_new(ndim: int, /) -> Any: + """struct TCOD_Frontier *TCOD_frontier_new(int ndim)""" + + @staticmethod + def TCOD_frontier_pop(frontier: Any, /) -> Any: + """TCOD_Error TCOD_frontier_pop(struct TCOD_Frontier *frontier)""" + + @staticmethod + def TCOD_frontier_push(frontier: Any, index: Any, dist: int, heuristic: int, /) -> Any: + """TCOD_Error TCOD_frontier_push(struct TCOD_Frontier *frontier, const int *index, int dist, int heuristic)""" + + @staticmethod + def TCOD_frontier_size(frontier: Any, /) -> int: + """int TCOD_frontier_size(const struct TCOD_Frontier *frontier)""" + + @staticmethod + def TCOD_get_default_tileset() -> Any: + """TCOD_Tileset *TCOD_get_default_tileset(void)""" + + @staticmethod + def TCOD_get_error() -> Any: + """const char *TCOD_get_error(void)""" + + @staticmethod + def TCOD_get_function_address(library: Any, function_name: Any, /) -> Any: + """void *TCOD_get_function_address(TCOD_library_t library, const char *function_name)""" + + @staticmethod + def TCOD_heap_clear(heap: Any, /) -> None: + """void TCOD_heap_clear(struct TCOD_Heap *heap)""" + + @staticmethod + def TCOD_heap_init(heap: Any, data_size: int, /) -> int: + """int TCOD_heap_init(struct TCOD_Heap *heap, size_t data_size)""" + + @staticmethod + def TCOD_heap_uninit(heap: Any, /) -> None: + """void TCOD_heap_uninit(struct TCOD_Heap *heap)""" + + @staticmethod + def TCOD_heightmap_add(hm: Any, value: float, /) -> None: + """void TCOD_heightmap_add(TCOD_heightmap_t *hm, float value)""" + + @staticmethod + def TCOD_heightmap_add_fbm( + hm: Any, + noise: Any, + mul_x: float, + mul_y: float, + add_x: float, + add_y: float, + octaves: float, + delta: float, + scale: float, + /, + ) -> None: + """void TCOD_heightmap_add_fbm(TCOD_heightmap_t *hm, TCOD_noise_t noise, float mul_x, float mul_y, float add_x, float add_y, float octaves, float delta, float scale)""" + + @staticmethod + def TCOD_heightmap_add_hill(hm: Any, hx: float, hy: float, h_radius: float, h_height: float, /) -> None: + """void TCOD_heightmap_add_hill(TCOD_heightmap_t *hm, float hx, float hy, float h_radius, float h_height)""" + + @staticmethod + def TCOD_heightmap_add_hm(hm1: Any, hm2: Any, out: Any, /) -> None: + """void TCOD_heightmap_add_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out)""" + + @staticmethod + def TCOD_heightmap_add_voronoi(hm: Any, nbPoints: int, nbCoef: int, coef: Any, rnd: Any, /) -> None: + """void TCOD_heightmap_add_voronoi(TCOD_heightmap_t *hm, int nbPoints, int nbCoef, const float *coef, TCOD_Random *rnd)""" + + @staticmethod + def TCOD_heightmap_clamp(hm: Any, min: float, max: float, /) -> None: + """void TCOD_heightmap_clamp(TCOD_heightmap_t *hm, float min, float max)""" + + @staticmethod + def TCOD_heightmap_clear(hm: Any, /) -> None: + """void TCOD_heightmap_clear(TCOD_heightmap_t *hm)""" + + @staticmethod + def TCOD_heightmap_copy(hm_source: Any, hm_dest: Any, /) -> None: + """void TCOD_heightmap_copy(const TCOD_heightmap_t *hm_source, TCOD_heightmap_t *hm_dest)""" + + @staticmethod + def TCOD_heightmap_count_cells(hm: Any, min: float, max: float, /) -> int: + """int TCOD_heightmap_count_cells(const TCOD_heightmap_t *hm, float min, float max)""" + + @staticmethod + def TCOD_heightmap_delete(hm: Any, /) -> None: + """void TCOD_heightmap_delete(TCOD_heightmap_t *hm)""" + + @staticmethod + def TCOD_heightmap_dig_bezier( + hm: Any, px: Any, py: Any, startRadius: float, startDepth: float, endRadius: float, endDepth: float, / + ) -> None: + """void TCOD_heightmap_dig_bezier(TCOD_heightmap_t *hm, int px[4], int py[4], float startRadius, float startDepth, float endRadius, float endDepth)""" + + @staticmethod + def TCOD_heightmap_dig_hill(hm: Any, hx: float, hy: float, h_radius: float, h_height: float, /) -> None: + """void TCOD_heightmap_dig_hill(TCOD_heightmap_t *hm, float hx, float hy, float h_radius, float h_height)""" + + @staticmethod + def TCOD_heightmap_get_interpolated_value(hm: Any, x: float, y: float, /) -> float: + """float TCOD_heightmap_get_interpolated_value(const TCOD_heightmap_t *hm, float x, float y)""" + + @staticmethod + def TCOD_heightmap_get_minmax(hm: Any, min: Any, max: Any, /) -> None: + """void TCOD_heightmap_get_minmax(const TCOD_heightmap_t *hm, float *min, float *max)""" + + @staticmethod + def TCOD_heightmap_get_normal(hm: Any, x: float, y: float, n: Any, waterLevel: float, /) -> None: + """void TCOD_heightmap_get_normal(const TCOD_heightmap_t *hm, float x, float y, float n[3], float waterLevel)""" + + @staticmethod + def TCOD_heightmap_get_slope(hm: Any, x: int, y: int, /) -> float: + """float TCOD_heightmap_get_slope(const TCOD_heightmap_t *hm, int x, int y)""" + + @staticmethod + def TCOD_heightmap_get_value(hm: Any, x: int, y: int, /) -> float: + """float TCOD_heightmap_get_value(const TCOD_heightmap_t *hm, int x, int y)""" + + @staticmethod + def TCOD_heightmap_has_land_on_border(hm: Any, waterLevel: float, /) -> bool: + """bool TCOD_heightmap_has_land_on_border(const TCOD_heightmap_t *hm, float waterLevel)""" + + @staticmethod + def TCOD_heightmap_islandify(hm: Any, seaLevel: float, rnd: Any, /) -> None: + """void TCOD_heightmap_islandify(TCOD_heightmap_t *hm, float seaLevel, TCOD_Random *rnd)""" + + @staticmethod + def TCOD_heightmap_kernel_transform( + hm: Any, kernel_size: int, dx: Any, dy: Any, weight: Any, minLevel: float, maxLevel: float, / + ) -> None: + """void TCOD_heightmap_kernel_transform(TCOD_heightmap_t *hm, int kernel_size, const int *dx, const int *dy, const float *weight, float minLevel, float maxLevel)""" + + @staticmethod + def TCOD_heightmap_lerp_hm(hm1: Any, hm2: Any, out: Any, coef: float, /) -> None: + """void TCOD_heightmap_lerp_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out, float coef)""" + + @staticmethod + def TCOD_heightmap_mid_point_displacement(hm: Any, rnd: Any, roughness: float, /) -> None: + """void TCOD_heightmap_mid_point_displacement(TCOD_heightmap_t *hm, TCOD_Random *rnd, float roughness)""" + + @staticmethod + def TCOD_heightmap_multiply_hm(hm1: Any, hm2: Any, out: Any, /) -> None: + """void TCOD_heightmap_multiply_hm(const TCOD_heightmap_t *hm1, const TCOD_heightmap_t *hm2, TCOD_heightmap_t *out)""" + + @staticmethod + def TCOD_heightmap_new(w: int, h: int, /) -> Any: + """TCOD_heightmap_t *TCOD_heightmap_new(int w, int h)""" + + @staticmethod + def TCOD_heightmap_normalize(hm: Any, min: float, max: float, /) -> None: + """void TCOD_heightmap_normalize(TCOD_heightmap_t *hm, float min, float max)""" + + @staticmethod + def TCOD_heightmap_rain_erosion( + hm: Any, nbDrops: int, erosionCoef: float, sedimentationCoef: float, rnd: Any, / + ) -> None: + """void TCOD_heightmap_rain_erosion(TCOD_heightmap_t *hm, int nbDrops, float erosionCoef, float sedimentationCoef, TCOD_Random *rnd)""" + + @staticmethod + def TCOD_heightmap_scale(hm: Any, value: float, /) -> None: + """void TCOD_heightmap_scale(TCOD_heightmap_t *hm, float value)""" + + @staticmethod + def TCOD_heightmap_scale_fbm( + hm: Any, + noise: Any, + mul_x: float, + mul_y: float, + add_x: float, + add_y: float, + octaves: float, + delta: float, + scale: float, + /, + ) -> None: + """void TCOD_heightmap_scale_fbm(TCOD_heightmap_t *hm, TCOD_noise_t noise, float mul_x, float mul_y, float add_x, float add_y, float octaves, float delta, float scale)""" + + @staticmethod + def TCOD_heightmap_set_value(hm: Any, x: int, y: int, value: float, /) -> None: + """void TCOD_heightmap_set_value(TCOD_heightmap_t *hm, int x, int y, float value)""" + + @staticmethod + def TCOD_image_blit( + image: Any, console: Any, x: float, y: float, bkgnd_flag: Any, scale_x: float, scale_y: float, angle: float, / + ) -> None: + """void TCOD_image_blit(TCOD_Image *image, TCOD_console_t console, float x, float y, TCOD_bkgnd_flag_t bkgnd_flag, float scale_x, float scale_y, float angle)""" + + @staticmethod + def TCOD_image_blit_2x(image: Any, dest: Any, dx: int, dy: int, sx: int, sy: int, w: int, h: int, /) -> None: + """void TCOD_image_blit_2x(const TCOD_Image *image, TCOD_Console *dest, int dx, int dy, int sx, int sy, int w, int h)""" + + @staticmethod + def TCOD_image_blit_rect(image: Any, console: Any, x: int, y: int, w: int, h: int, bkgnd_flag: Any, /) -> None: + """void TCOD_image_blit_rect(TCOD_Image *image, TCOD_console_t console, int x, int y, int w, int h, TCOD_bkgnd_flag_t bkgnd_flag)""" + + @staticmethod + def TCOD_image_clear(image: Any, color: Any, /) -> None: + """void TCOD_image_clear(TCOD_Image *image, TCOD_color_t color)""" + + @staticmethod + def TCOD_image_clear_wrapper(image: Any, color: Any, /) -> None: + """void TCOD_image_clear_wrapper(TCOD_image_t image, colornum_t color)""" + + @staticmethod + def TCOD_image_delete(image: Any, /) -> None: + """void TCOD_image_delete(TCOD_Image *image)""" + + @staticmethod + def TCOD_image_from_console(console: Any, /) -> Any: + """TCOD_Image *TCOD_image_from_console(const TCOD_Console *console)""" + + @staticmethod + def TCOD_image_get_alpha(image: Any, x: int, y: int, /) -> int: + """int TCOD_image_get_alpha(const TCOD_Image *image, int x, int y)""" + + @staticmethod + def TCOD_image_get_mipmap_pixel(image: Any, x0: float, y0: float, x1: float, y1: float, /) -> Any: + """TCOD_color_t TCOD_image_get_mipmap_pixel(TCOD_Image *image, float x0, float y0, float x1, float y1)""" + + @staticmethod + def TCOD_image_get_mipmap_pixel_wrapper(image: Any, x0: float, y0: float, x1: float, y1: float, /) -> Any: + """colornum_t TCOD_image_get_mipmap_pixel_wrapper(TCOD_image_t image, float x0, float y0, float x1, float y1)""" + + @staticmethod + def TCOD_image_get_pixel(image: Any, x: int, y: int, /) -> Any: + """TCOD_color_t TCOD_image_get_pixel(const TCOD_Image *image, int x, int y)""" + + @staticmethod + def TCOD_image_get_pixel_wrapper(image: Any, x: int, y: int, /) -> Any: + """colornum_t TCOD_image_get_pixel_wrapper(TCOD_image_t image, int x, int y)""" + + @staticmethod + def TCOD_image_get_size(image: Any, w: Any, h: Any, /) -> None: + """void TCOD_image_get_size(const TCOD_Image *image, int *w, int *h)""" + + @staticmethod + def TCOD_image_hflip(image: Any, /) -> None: + """void TCOD_image_hflip(TCOD_Image *image)""" + + @staticmethod + def TCOD_image_invert(image: Any, /) -> None: + """void TCOD_image_invert(TCOD_Image *image)""" + + @staticmethod + def TCOD_image_is_pixel_transparent(image: Any, x: int, y: int, /) -> bool: + """bool TCOD_image_is_pixel_transparent(const TCOD_Image *image, int x, int y)""" + + @staticmethod + def TCOD_image_load(filename: Any, /) -> Any: + """TCOD_Image *TCOD_image_load(const char *filename)""" + + @staticmethod + def TCOD_image_new(width: int, height: int, /) -> Any: + """TCOD_Image *TCOD_image_new(int width, int height)""" + + @staticmethod + def TCOD_image_put_pixel(image: Any, x: int, y: int, col: Any, /) -> None: + """void TCOD_image_put_pixel(TCOD_Image *image, int x, int y, TCOD_color_t col)""" + + @staticmethod + def TCOD_image_put_pixel_wrapper(image: Any, x: int, y: int, col: Any, /) -> None: + """void TCOD_image_put_pixel_wrapper(TCOD_image_t image, int x, int y, colornum_t col)""" + + @staticmethod + def TCOD_image_refresh_console(image: Any, console: Any, /) -> None: + """void TCOD_image_refresh_console(TCOD_Image *image, const TCOD_Console *console)""" + + @staticmethod + def TCOD_image_rotate90(image: Any, numRotations: int, /) -> None: + """void TCOD_image_rotate90(TCOD_Image *image, int numRotations)""" + + @staticmethod + def TCOD_image_save(image: Any, filename: Any, /) -> Any: + """TCOD_Error TCOD_image_save(const TCOD_Image *image, const char *filename)""" + + @staticmethod + def TCOD_image_scale(image: Any, new_w: int, new_h: int, /) -> None: + """void TCOD_image_scale(TCOD_Image *image, int new_w, int new_h)""" + + @staticmethod + def TCOD_image_set_key_color(image: Any, key_color: Any, /) -> None: + """void TCOD_image_set_key_color(TCOD_Image *image, TCOD_color_t key_color)""" + + @staticmethod + def TCOD_image_set_key_color_wrapper(image: Any, key_color: Any, /) -> None: + """void TCOD_image_set_key_color_wrapper(TCOD_image_t image, colornum_t key_color)""" + + @staticmethod + def TCOD_image_vflip(image: Any, /) -> None: + """void TCOD_image_vflip(TCOD_Image *image)""" + + @staticmethod + def TCOD_lex_delete(lex: Any, /) -> None: + """void TCOD_lex_delete(TCOD_lex_t *lex)""" + + @staticmethod + def TCOD_lex_expect_token_type(lex: Any, token_type: int, /) -> bool: + """bool TCOD_lex_expect_token_type(TCOD_lex_t *lex, int token_type)""" + + @staticmethod + def TCOD_lex_expect_token_value(lex: Any, token_type: int, token_value: Any, /) -> bool: + """bool TCOD_lex_expect_token_value(TCOD_lex_t *lex, int token_type, const char *token_value)""" + + @staticmethod + def TCOD_lex_get_last_javadoc(lex: Any, /) -> Any: + """char *TCOD_lex_get_last_javadoc(TCOD_lex_t *lex)""" + + @staticmethod + def TCOD_lex_get_token_name(token_type: int, /) -> Any: + """const char *TCOD_lex_get_token_name(int token_type)""" + + @staticmethod + def TCOD_lex_hextoint(c: Any, /) -> int: + """int TCOD_lex_hextoint(char c)""" + + @staticmethod + def TCOD_lex_new( + symbols: Any, + keywords: Any, + simpleComment: Any, + commentStart: Any, + commentStop: Any, + javadocCommentStart: Any, + stringDelim: Any, + flags: int, + /, + ) -> Any: + """TCOD_lex_t *TCOD_lex_new(const char * const *symbols, const char * const *keywords, const char *simpleComment, const char *commentStart, const char *commentStop, const char *javadocCommentStart, const char *stringDelim, int flags)""" + + @staticmethod + def TCOD_lex_new_intern() -> Any: + """TCOD_lex_t *TCOD_lex_new_intern(void)""" + + @staticmethod + def TCOD_lex_parse(lex: Any, /) -> int: + """int TCOD_lex_parse(TCOD_lex_t *lex)""" + + @staticmethod + def TCOD_lex_parse_until_token_type(lex: Any, token_type: int, /) -> int: + """int TCOD_lex_parse_until_token_type(TCOD_lex_t *lex, int token_type)""" + + @staticmethod + def TCOD_lex_parse_until_token_value(lex: Any, token_value: Any, /) -> int: + """int TCOD_lex_parse_until_token_value(TCOD_lex_t *lex, const char *token_value)""" + + @staticmethod + def TCOD_lex_restore(lex: Any, savepoint: Any, /) -> None: + """void TCOD_lex_restore(TCOD_lex_t *lex, TCOD_lex_t *savepoint)""" + + @staticmethod + def TCOD_lex_savepoint(lex: Any, savepoint: Any, /) -> None: + """void TCOD_lex_savepoint(TCOD_lex_t *lex, TCOD_lex_t *savepoint)""" + + @staticmethod + def TCOD_lex_set_data_buffer(lex: Any, dat: Any, /) -> None: + """void TCOD_lex_set_data_buffer(TCOD_lex_t *lex, char *dat)""" + + @staticmethod + def TCOD_lex_set_data_file(lex: Any, filename: Any, /) -> bool: + """bool TCOD_lex_set_data_file(TCOD_lex_t *lex, const char *filename)""" + + @staticmethod + def TCOD_line(xFrom: int, yFrom: int, xTo: int, yTo: int, listener: Any, /) -> bool: + """bool TCOD_line(int xFrom, int yFrom, int xTo, int yTo, TCOD_line_listener_t listener)""" + + @staticmethod + def TCOD_line_init(xFrom: int, yFrom: int, xTo: int, yTo: int, /) -> None: + """void TCOD_line_init(int xFrom, int yFrom, int xTo, int yTo)""" + + @staticmethod + def TCOD_line_init_mt(xFrom: int, yFrom: int, xTo: int, yTo: int, data: Any, /) -> None: + """void TCOD_line_init_mt(int xFrom, int yFrom, int xTo, int yTo, TCOD_bresenham_data_t *data)""" + + @staticmethod + def TCOD_line_mt(xFrom: int, yFrom: int, xTo: int, yTo: int, listener: Any, data: Any, /) -> bool: + """bool TCOD_line_mt(int xFrom, int yFrom, int xTo, int yTo, TCOD_line_listener_t listener, TCOD_bresenham_data_t *data)""" + + @staticmethod + def TCOD_line_step(xCur: Any, yCur: Any, /) -> bool: + """bool TCOD_line_step(int *xCur, int *yCur)""" + + @staticmethod + def TCOD_line_step_mt(xCur: Any, yCur: Any, data: Any, /) -> bool: + """bool TCOD_line_step_mt(int *xCur, int *yCur, TCOD_bresenham_data_t *data)""" + + @staticmethod + def TCOD_list_add_all(l: Any, l2: Any, /) -> None: + """void TCOD_list_add_all(TCOD_list_t l, TCOD_list_t l2)""" + + @staticmethod + def TCOD_list_allocate(nb_elements: int, /) -> Any: + """TCOD_list_t TCOD_list_allocate(int nb_elements)""" + + @staticmethod + def TCOD_list_begin(l: Any, /) -> Any: + """void **TCOD_list_begin(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_clear(l: Any, /) -> None: + """void TCOD_list_clear(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_clear_and_delete(l: Any, /) -> None: + """void TCOD_list_clear_and_delete(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_contains(l: Any, elt: Any, /) -> bool: + """bool TCOD_list_contains(TCOD_list_t l, const void *elt)""" + + @staticmethod + def TCOD_list_delete(l: Any, /) -> None: + """void TCOD_list_delete(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_duplicate(l: Any, /) -> Any: + """TCOD_list_t TCOD_list_duplicate(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_end(l: Any, /) -> Any: + """void **TCOD_list_end(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_get(l: Any, idx: int, /) -> Any: + """void *TCOD_list_get(TCOD_list_t l, int idx)""" + + @staticmethod + def TCOD_list_insert_before(l: Any, elt: Any, before: int, /) -> Any: + """void **TCOD_list_insert_before(TCOD_list_t l, const void *elt, int before)""" + + @staticmethod + def TCOD_list_is_empty(l: Any, /) -> bool: + """bool TCOD_list_is_empty(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_new() -> Any: + """TCOD_list_t TCOD_list_new(void)""" + + @staticmethod + def TCOD_list_peek(l: Any, /) -> Any: + """void *TCOD_list_peek(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_pop(l: Any, /) -> Any: + """void *TCOD_list_pop(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_push(l: Any, elt: Any, /) -> None: + """void TCOD_list_push(TCOD_list_t l, const void *elt)""" + + @staticmethod + def TCOD_list_remove(l: Any, elt: Any, /) -> None: + """void TCOD_list_remove(TCOD_list_t l, const void *elt)""" + + @staticmethod + def TCOD_list_remove_fast(l: Any, elt: Any, /) -> None: + """void TCOD_list_remove_fast(TCOD_list_t l, const void *elt)""" + + @staticmethod + def TCOD_list_remove_iterator(l: Any, elt: Any, /) -> Any: + """void **TCOD_list_remove_iterator(TCOD_list_t l, void **elt)""" + + @staticmethod + def TCOD_list_remove_iterator_fast(l: Any, elt: Any, /) -> Any: + """void **TCOD_list_remove_iterator_fast(TCOD_list_t l, void **elt)""" + + @staticmethod + def TCOD_list_reverse(l: Any, /) -> None: + """void TCOD_list_reverse(TCOD_list_t l)""" + + @staticmethod + def TCOD_list_set(l: Any, elt: Any, idx: int, /) -> None: + """void TCOD_list_set(TCOD_list_t l, const void *elt, int idx)""" + + @staticmethod + def TCOD_list_set_size(l: Any, size: int, /) -> None: + """void TCOD_list_set_size(TCOD_list_t l, int size)""" + + @staticmethod + def TCOD_list_size(l: Any, /) -> int: + """int TCOD_list_size(TCOD_list_t l)""" + + @staticmethod + def TCOD_load_bdf(path: Any, /) -> Any: + """TCOD_Tileset *TCOD_load_bdf(const char *path)""" + + @staticmethod + def TCOD_load_bdf_memory(size: int, buffer: Any, /) -> Any: + """TCOD_Tileset *TCOD_load_bdf_memory(int size, const unsigned char *buffer)""" + + @staticmethod + def TCOD_load_library(path: Any, /) -> Any: + """TCOD_library_t TCOD_load_library(const char *path)""" + + @staticmethod + def TCOD_load_truetype_font_(path: Any, tile_width: int, tile_height: int, /) -> Any: + """TCOD_Tileset *TCOD_load_truetype_font_(const char *path, int tile_width, int tile_height)""" + + @staticmethod + def TCOD_load_xp(path: Any, n: int, out: Any, /) -> int: + """int TCOD_load_xp(const char *path, int n, TCOD_Console **out)""" + + @staticmethod + def TCOD_load_xp_from_memory(n_data: int, data: Any, n_out: int, out: Any, /) -> int: + """int TCOD_load_xp_from_memory(int n_data, const unsigned char *data, int n_out, TCOD_Console **out)""" + + @staticmethod + def TCOD_log_verbose_(msg: Any, level: int, source: Any, line: int, /) -> None: + """void TCOD_log_verbose_(const char *msg, int level, const char *source, int line)""" + + @staticmethod + def TCOD_log_verbose_fmt_(level: int, source: Any, line: int, fmt: Any, /, *__args: Any) -> None: + """void TCOD_log_verbose_fmt_(int level, const char *source, int line, const char *fmt, ...)""" + + @staticmethod + def TCOD_map_clear(map: Any, transparent: bool, walkable: bool, /) -> None: + """void TCOD_map_clear(TCOD_Map *map, bool transparent, bool walkable)""" + + @staticmethod + def TCOD_map_compute_fov(map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, algo: Any, /) -> Any: + """TCOD_Error TCOD_map_compute_fov(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls, TCOD_fov_algorithm_t algo)""" + + @staticmethod + def TCOD_map_compute_fov_circular_raycasting( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_circular_raycasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)""" + + @staticmethod + def TCOD_map_compute_fov_diamond_raycasting( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_diamond_raycasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)""" + + @staticmethod + def TCOD_map_compute_fov_permissive2( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, permissiveness: int, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_permissive2(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls, int permissiveness)""" + + @staticmethod + def TCOD_map_compute_fov_recursive_shadowcasting( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_recursive_shadowcasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)""" + + @staticmethod + def TCOD_map_compute_fov_restrictive_shadowcasting( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_restrictive_shadowcasting(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)""" + + @staticmethod + def TCOD_map_compute_fov_symmetric_shadowcast( + map: Any, pov_x: int, pov_y: int, max_radius: int, light_walls: bool, / + ) -> Any: + """TCOD_Error TCOD_map_compute_fov_symmetric_shadowcast(TCOD_Map *map, int pov_x, int pov_y, int max_radius, bool light_walls)""" + + @staticmethod + def TCOD_map_copy(source: Any, dest: Any, /) -> Any: + """TCOD_Error TCOD_map_copy(const TCOD_Map *source, TCOD_Map *dest)""" + + @staticmethod + def TCOD_map_delete(map: Any, /) -> None: + """void TCOD_map_delete(TCOD_Map *map)""" + + @staticmethod + def TCOD_map_get_height(map: Any, /) -> int: + """int TCOD_map_get_height(const TCOD_Map *map)""" + + @staticmethod + def TCOD_map_get_nb_cells(map: Any, /) -> int: + """int TCOD_map_get_nb_cells(const TCOD_Map *map)""" + + @staticmethod + def TCOD_map_get_width(map: Any, /) -> int: + """int TCOD_map_get_width(const TCOD_Map *map)""" + + @staticmethod + def TCOD_map_in_bounds(map: Any, x: int, y: int, /) -> bool: + """inline static bool TCOD_map_in_bounds(const struct TCOD_Map *map, int x, int y)""" + + @staticmethod + def TCOD_map_is_in_fov(map: Any, x: int, y: int, /) -> bool: + """bool TCOD_map_is_in_fov(const TCOD_Map *map, int x, int y)""" + + @staticmethod + def TCOD_map_is_transparent(map: Any, x: int, y: int, /) -> bool: + """bool TCOD_map_is_transparent(const TCOD_Map *map, int x, int y)""" + + @staticmethod + def TCOD_map_is_walkable(map: Any, x: int, y: int, /) -> bool: + """bool TCOD_map_is_walkable(TCOD_Map *map, int x, int y)""" + + @staticmethod + def TCOD_map_new(width: int, height: int, /) -> Any: + """TCOD_Map *TCOD_map_new(int width, int height)""" + + @staticmethod + def TCOD_map_postprocess(map: Any, pov_x: int, pov_y: int, radius: int, /) -> Any: + """TCOD_Error TCOD_map_postprocess(TCOD_Map *map, int pov_x, int pov_y, int radius)""" + + @staticmethod + def TCOD_map_set_in_fov(map: Any, x: int, y: int, fov: bool, /) -> None: + """void TCOD_map_set_in_fov(TCOD_Map *map, int x, int y, bool fov)""" + + @staticmethod + def TCOD_map_set_properties(map: Any, x: int, y: int, is_transparent: bool, is_walkable: bool, /) -> None: + """void TCOD_map_set_properties(TCOD_Map *map, int x, int y, bool is_transparent, bool is_walkable)""" + + @staticmethod + def TCOD_minheap_heapify(minheap: Any, /) -> None: + """void TCOD_minheap_heapify(struct TCOD_Heap *minheap)""" + + @staticmethod + def TCOD_minheap_pop(minheap: Any, out: Any, /) -> None: + """void TCOD_minheap_pop(struct TCOD_Heap *minheap, void *out)""" + + @staticmethod + def TCOD_minheap_push(minheap: Any, priority: int, data: Any, /) -> int: + """int TCOD_minheap_push(struct TCOD_Heap *minheap, int priority, const void *data)""" + + @staticmethod + def TCOD_mouse_get_status() -> Any: + """TCOD_mouse_t TCOD_mouse_get_status(void)""" + + @staticmethod + def TCOD_mouse_get_status_wrapper(holder: Any, /) -> None: + """void TCOD_mouse_get_status_wrapper(TCOD_mouse_t *holder)""" + + @staticmethod + def TCOD_mouse_includes_touch(enable: bool, /) -> None: + """void TCOD_mouse_includes_touch(bool enable)""" + + @staticmethod + def TCOD_mouse_is_cursor_visible() -> bool: + """bool TCOD_mouse_is_cursor_visible(void)""" + + @staticmethod + def TCOD_mouse_move(x: int, y: int, /) -> None: + """void TCOD_mouse_move(int x, int y)""" + + @staticmethod + def TCOD_mouse_show_cursor(visible: bool, /) -> None: + """void TCOD_mouse_show_cursor(bool visible)""" + + @staticmethod + def TCOD_mutex_delete(mut: Any, /) -> None: + """void TCOD_mutex_delete(TCOD_mutex_t mut)""" + + @staticmethod + def TCOD_mutex_in(mut: Any, /) -> None: + """void TCOD_mutex_in(TCOD_mutex_t mut)""" + + @staticmethod + def TCOD_mutex_new() -> Any: + """TCOD_mutex_t TCOD_mutex_new(void)""" + + @staticmethod + def TCOD_mutex_out(mut: Any, /) -> None: + """void TCOD_mutex_out(TCOD_mutex_t mut)""" + + @staticmethod + def TCOD_namegen_destroy() -> None: + """void TCOD_namegen_destroy(void)""" + + @staticmethod + def TCOD_namegen_generate(name: Any, allocate: bool, /) -> Any: + """char *TCOD_namegen_generate(const char *name, bool allocate)""" + + @staticmethod + def TCOD_namegen_generate_custom(name: Any, rule: Any, allocate: bool, /) -> Any: + """char *TCOD_namegen_generate_custom(const char *name, const char *rule, bool allocate)""" + + @staticmethod + def TCOD_namegen_get_nb_sets_wrapper() -> int: + """int TCOD_namegen_get_nb_sets_wrapper(void)""" + + @staticmethod + def TCOD_namegen_get_sets() -> Any: + """TCOD_list_t TCOD_namegen_get_sets(void)""" + + @staticmethod + def TCOD_namegen_get_sets_wrapper(sets: Any, /) -> None: + """void TCOD_namegen_get_sets_wrapper(char **sets)""" + + @staticmethod + def TCOD_namegen_parse(filename: Any, random: Any, /) -> None: + """void TCOD_namegen_parse(const char *filename, TCOD_Random *random)""" + + @staticmethod + def TCOD_noise_delete(noise: Any, /) -> None: + """void TCOD_noise_delete(TCOD_Noise *noise)""" + + @staticmethod + def TCOD_noise_get(noise: Any, f: Any, /) -> float: + """float TCOD_noise_get(TCOD_Noise *noise, const float *f)""" + + @staticmethod + def TCOD_noise_get_ex(noise: Any, f: Any, type: Any, /) -> float: + """float TCOD_noise_get_ex(TCOD_Noise *noise, const float *f, TCOD_noise_type_t type)""" + + @staticmethod + def TCOD_noise_get_fbm(noise: Any, f: Any, octaves: float, /) -> float: + """float TCOD_noise_get_fbm(TCOD_Noise *noise, const float *f, float octaves)""" + + @staticmethod + def TCOD_noise_get_fbm_ex(noise: Any, f: Any, octaves: float, type: Any, /) -> float: + """float TCOD_noise_get_fbm_ex(TCOD_Noise *noise, const float *f, float octaves, TCOD_noise_type_t type)""" + + @staticmethod + def TCOD_noise_get_fbm_vectorized( + noise: Any, type: Any, octaves: float, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, / + ) -> None: + """void TCOD_noise_get_fbm_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, float octaves, int n, float *x, float *y, float *z, float *w, float *out)""" + + @staticmethod + def TCOD_noise_get_turbulence(noise: Any, f: Any, octaves: float, /) -> float: + """float TCOD_noise_get_turbulence(TCOD_Noise *noise, const float *f, float octaves)""" + + @staticmethod + def TCOD_noise_get_turbulence_ex(noise: Any, f: Any, octaves: float, type: Any, /) -> float: + """float TCOD_noise_get_turbulence_ex(TCOD_Noise *noise, const float *f, float octaves, TCOD_noise_type_t type)""" + + @staticmethod + def TCOD_noise_get_turbulence_vectorized( + noise: Any, type: Any, octaves: float, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, / + ) -> None: + """void TCOD_noise_get_turbulence_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, float octaves, int n, float *x, float *y, float *z, float *w, float *out)""" + + @staticmethod + def TCOD_noise_get_vectorized(noise: Any, type: Any, n: int, x: Any, y: Any, z: Any, w: Any, out: Any, /) -> None: + """void TCOD_noise_get_vectorized(TCOD_Noise *noise, TCOD_noise_type_t type, int n, float *x, float *y, float *z, float *w, float *out)""" + + @staticmethod + def TCOD_noise_new(dimensions: int, hurst: float, lacunarity: float, random: Any, /) -> Any: + """TCOD_Noise *TCOD_noise_new(int dimensions, float hurst, float lacunarity, TCOD_Random *random)""" + + @staticmethod + def TCOD_noise_set_type(noise: Any, type: Any, /) -> None: + """void TCOD_noise_set_type(TCOD_Noise *noise, TCOD_noise_type_t type)""" + + @staticmethod + def TCOD_parse_bool_value() -> Any: + """TCOD_value_t TCOD_parse_bool_value(void)""" + + @staticmethod + def TCOD_parse_char_value() -> Any: + """TCOD_value_t TCOD_parse_char_value(void)""" + + @staticmethod + def TCOD_parse_color_value() -> Any: + """TCOD_value_t TCOD_parse_color_value(void)""" + + @staticmethod + def TCOD_parse_dice_value() -> Any: + """TCOD_value_t TCOD_parse_dice_value(void)""" + + @staticmethod + def TCOD_parse_float_value() -> Any: + """TCOD_value_t TCOD_parse_float_value(void)""" + + @staticmethod + def TCOD_parse_integer_value() -> Any: + """TCOD_value_t TCOD_parse_integer_value(void)""" + + @staticmethod + def TCOD_parse_property_value(parser: Any, def_: Any, propname: Any, list: bool, /) -> Any: + """TCOD_value_t TCOD_parse_property_value(TCOD_Parser *parser, TCOD_ParserStruct *def, char *propname, bool list)""" + + @staticmethod + def TCOD_parse_string_value() -> Any: + """TCOD_value_t TCOD_parse_string_value(void)""" + + @staticmethod + def TCOD_parse_value_list_value(def_: Any, list_num: int, /) -> Any: + """TCOD_value_t TCOD_parse_value_list_value(TCOD_ParserStruct *def, int list_num)""" + + @staticmethod + def TCOD_parser_delete(parser: Any, /) -> None: + """void TCOD_parser_delete(TCOD_Parser *parser)""" + + @staticmethod + def TCOD_parser_error(msg: Any, /, *__args: Any) -> None: + """void TCOD_parser_error(const char *msg, ...)""" + + @staticmethod + def TCOD_parser_get_bool_property(parser: Any, name: Any, /) -> bool: + """bool TCOD_parser_get_bool_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_char_property(parser: Any, name: Any, /) -> int: + """int TCOD_parser_get_char_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_color_property(parser: Any, name: Any, /) -> Any: + """TCOD_color_t TCOD_parser_get_color_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_color_property_wrapper(parser: Any, name: Any, /) -> Any: + """colornum_t TCOD_parser_get_color_property_wrapper(TCOD_parser_t parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_custom_property(parser: Any, name: Any, /) -> Any: + """void *TCOD_parser_get_custom_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_dice_property(parser: Any, name: Any, /) -> Any: + """TCOD_dice_t TCOD_parser_get_dice_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_dice_property_py(parser: Any, name: Any, dice: Any, /) -> None: + """void TCOD_parser_get_dice_property_py(TCOD_Parser *parser, const char *name, TCOD_dice_t *dice)""" + + @staticmethod + def TCOD_parser_get_float_property(parser: Any, name: Any, /) -> float: + """float TCOD_parser_get_float_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_int_property(parser: Any, name: Any, /) -> int: + """int TCOD_parser_get_int_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_get_list_property(parser: Any, name: Any, type: Any, /) -> Any: + """TCOD_list_t TCOD_parser_get_list_property(TCOD_Parser *parser, const char *name, TCOD_value_type_t type)""" + + @staticmethod + def TCOD_parser_get_string_property(parser: Any, name: Any, /) -> Any: + """const char *TCOD_parser_get_string_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_has_property(parser: Any, name: Any, /) -> bool: + """bool TCOD_parser_has_property(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_new() -> Any: + """TCOD_Parser *TCOD_parser_new(void)""" + + @staticmethod + def TCOD_parser_new_custom_type(parser: Any, custom_type_parser: Any, /) -> Any: + """TCOD_value_type_t TCOD_parser_new_custom_type(TCOD_Parser *parser, TCOD_parser_custom_t custom_type_parser)""" + + @staticmethod + def TCOD_parser_new_struct(parser: Any, name: Any, /) -> Any: + """TCOD_ParserStruct *TCOD_parser_new_struct(TCOD_Parser *parser, const char *name)""" + + @staticmethod + def TCOD_parser_run(parser: Any, filename: Any, listener: Any, /) -> None: + """void TCOD_parser_run(TCOD_Parser *parser, const char *filename, TCOD_parser_listener_t *listener)""" + + @staticmethod + def TCOD_path_compute(path: Any, ox: int, oy: int, dx: int, dy: int, /) -> bool: + """bool TCOD_path_compute(TCOD_path_t path, int ox, int oy, int dx, int dy)""" + + @staticmethod + def TCOD_path_delete(path: Any, /) -> None: + """void TCOD_path_delete(TCOD_path_t path)""" + + @staticmethod + def TCOD_path_get(path: Any, index: int, x: Any, y: Any, /) -> None: + """void TCOD_path_get(TCOD_path_t path, int index, int *x, int *y)""" + + @staticmethod + def TCOD_path_get_destination(path: Any, x: Any, y: Any, /) -> None: + """void TCOD_path_get_destination(TCOD_path_t path, int *x, int *y)""" + + @staticmethod + def TCOD_path_get_origin(path: Any, x: Any, y: Any, /) -> None: + """void TCOD_path_get_origin(TCOD_path_t path, int *x, int *y)""" + + @staticmethod + def TCOD_path_is_empty(path: Any, /) -> bool: + """bool TCOD_path_is_empty(TCOD_path_t path)""" + + @staticmethod + def TCOD_path_new_using_function( + map_width: int, map_height: int, func: Any, user_data: Any, diagonalCost: float, / + ) -> Any: + """TCOD_path_t TCOD_path_new_using_function(int map_width, int map_height, TCOD_path_func_t func, void *user_data, float diagonalCost)""" + + @staticmethod + def TCOD_path_new_using_map(map: Any, diagonalCost: float, /) -> Any: + """TCOD_path_t TCOD_path_new_using_map(TCOD_Map *map, float diagonalCost)""" + + @staticmethod + def TCOD_path_reverse(path: Any, /) -> None: + """void TCOD_path_reverse(TCOD_path_t path)""" + + @staticmethod + def TCOD_path_size(path: Any, /) -> int: + """int TCOD_path_size(TCOD_path_t path)""" + + @staticmethod + def TCOD_path_walk(path: Any, x: Any, y: Any, recalculate_when_needed: bool, /) -> bool: + """bool TCOD_path_walk(TCOD_path_t path, int *x, int *y, bool recalculate_when_needed)""" + + @staticmethod + def TCOD_pf_compute(path: Any, /) -> int: + """int TCOD_pf_compute(struct TCOD_Pathfinder *path)""" + + @staticmethod + def TCOD_pf_compute_step(path: Any, /) -> int: + """int TCOD_pf_compute_step(struct TCOD_Pathfinder *path)""" + + @staticmethod + def TCOD_pf_delete(path: Any, /) -> None: + """void TCOD_pf_delete(struct TCOD_Pathfinder *path)""" + + @staticmethod + def TCOD_pf_new(ndim: int, shape: Any, /) -> Any: + """struct TCOD_Pathfinder *TCOD_pf_new(int ndim, const size_t *shape)""" + + @staticmethod + def TCOD_pf_recompile(path: Any, /) -> int: + """int TCOD_pf_recompile(struct TCOD_Pathfinder *path)""" + + @staticmethod + def TCOD_pf_set_distance_pointer(path: Any, data: Any, int_type: int, strides: Any, /) -> None: + """void TCOD_pf_set_distance_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides)""" + + @staticmethod + def TCOD_pf_set_graph2d_pointer( + path: Any, data: Any, int_type: int, strides: Any, cardinal: int, diagonal: int, / + ) -> None: + """void TCOD_pf_set_graph2d_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides, int cardinal, int diagonal)""" + + @staticmethod + def TCOD_pf_set_traversal_pointer(path: Any, data: Any, int_type: int, strides: Any, /) -> None: + """void TCOD_pf_set_traversal_pointer(struct TCOD_Pathfinder *path, void *data, int int_type, const size_t *strides)""" + + @staticmethod + def TCOD_printf_rgb(console: Any, params: Any, fmt: Any, /, *__args: Any) -> int: + """int TCOD_printf_rgb(TCOD_Console *console, TCOD_PrintParamsRGB params, const char *fmt, ...)""" + + @staticmethod + def TCOD_printn_rgb(console: Any, params: Any, n: int, str: Any, /) -> int: + """int TCOD_printn_rgb(TCOD_Console *console, TCOD_PrintParamsRGB params, int n, const char *str)""" + + @staticmethod + def TCOD_quit() -> None: + """void TCOD_quit(void)""" + + @staticmethod + def TCOD_random_delete(mersenne: Any, /) -> None: + """void TCOD_random_delete(TCOD_Random *mersenne)""" + + @staticmethod + def TCOD_random_dice_new(s: Any, /) -> Any: + """TCOD_dice_t TCOD_random_dice_new(const char *s)""" + + @staticmethod + def TCOD_random_dice_roll(mersenne: Any, dice: Any, /) -> int: + """int TCOD_random_dice_roll(TCOD_Random *mersenne, TCOD_dice_t dice)""" + + @staticmethod + def TCOD_random_dice_roll_s(mersenne: Any, s: Any, /) -> int: + """int TCOD_random_dice_roll_s(TCOD_Random *mersenne, const char *s)""" + + @staticmethod + def TCOD_random_get_d(mersenne: Any, min: float, max: float, /) -> float: + """double TCOD_random_get_d(TCOD_random_t mersenne, double min, double max)""" + + @staticmethod + def TCOD_random_get_double(mersenne: Any, min: float, max: float, /) -> float: + """double TCOD_random_get_double(TCOD_Random *mersenne, double min, double max)""" + + @staticmethod + def TCOD_random_get_double_mean(mersenne: Any, min: float, max: float, mean: float, /) -> float: + """double TCOD_random_get_double_mean(TCOD_Random *mersenne, double min, double max, double mean)""" + + @staticmethod + def TCOD_random_get_float(mersenne: Any, min: float, max: float, /) -> float: + """float TCOD_random_get_float(TCOD_Random *mersenne, float min, float max)""" + + @staticmethod + def TCOD_random_get_float_mean(mersenne: Any, min: float, max: float, mean: float, /) -> float: + """float TCOD_random_get_float_mean(TCOD_Random *mersenne, float min, float max, float mean)""" + + @staticmethod + def TCOD_random_get_gaussian_double(mersenne: Any, mean: float, std_deviation: float, /) -> float: + """double TCOD_random_get_gaussian_double(TCOD_random_t mersenne, double mean, double std_deviation)""" + + @staticmethod + def TCOD_random_get_gaussian_double_inv(mersenne: Any, mean: float, std_deviation: float, /) -> float: + """double TCOD_random_get_gaussian_double_inv(TCOD_random_t mersenne, double mean, double std_deviation)""" + + @staticmethod + def TCOD_random_get_gaussian_double_range(mersenne: Any, min: float, max: float, /) -> float: + """double TCOD_random_get_gaussian_double_range(TCOD_random_t mersenne, double min, double max)""" + + @staticmethod + def TCOD_random_get_gaussian_double_range_custom(mersenne: Any, min: float, max: float, mean: float, /) -> float: + """double TCOD_random_get_gaussian_double_range_custom(TCOD_random_t mersenne, double min, double max, double mean)""" + + @staticmethod + def TCOD_random_get_gaussian_double_range_custom_inv( + mersenne: Any, min: float, max: float, mean: float, / + ) -> float: + """double TCOD_random_get_gaussian_double_range_custom_inv(TCOD_random_t mersenne, double min, double max, double mean)""" + + @staticmethod + def TCOD_random_get_gaussian_double_range_inv(mersenne: Any, min: float, max: float, /) -> float: + """double TCOD_random_get_gaussian_double_range_inv(TCOD_random_t mersenne, double min, double max)""" + + @staticmethod + def TCOD_random_get_i(mersenne: Any, min: int, max: int, /) -> int: + """int TCOD_random_get_i(TCOD_random_t mersenne, int min, int max)""" + + @staticmethod + def TCOD_random_get_instance() -> Any: + """TCOD_Random *TCOD_random_get_instance(void)""" + + @staticmethod + def TCOD_random_get_int(mersenne: Any, min: int, max: int, /) -> int: + """int TCOD_random_get_int(TCOD_Random *mersenne, int min, int max)""" + + @staticmethod + def TCOD_random_get_int_mean(mersenne: Any, min: int, max: int, mean: int, /) -> int: + """int TCOD_random_get_int_mean(TCOD_Random *mersenne, int min, int max, int mean)""" + + @staticmethod + def TCOD_random_new(algo: Any, /) -> Any: + """TCOD_Random *TCOD_random_new(TCOD_random_algo_t algo)""" + + @staticmethod + def TCOD_random_new_from_seed(algo: Any, seed: Any, /) -> Any: + """TCOD_Random *TCOD_random_new_from_seed(TCOD_random_algo_t algo, uint32_t seed)""" + + @staticmethod + def TCOD_random_restore(mersenne: Any, backup: Any, /) -> None: + """void TCOD_random_restore(TCOD_Random *mersenne, TCOD_Random *backup)""" + + @staticmethod + def TCOD_random_save(mersenne: Any, /) -> Any: + """TCOD_Random *TCOD_random_save(TCOD_Random *mersenne)""" + + @staticmethod + def TCOD_random_set_distribution(mersenne: Any, distribution: Any, /) -> None: + """void TCOD_random_set_distribution(TCOD_Random *mersenne, TCOD_distribution_t distribution)""" + + @staticmethod + def TCOD_renderer_init_sdl2( + x: int, y: int, width: int, height: int, title: Any, window_flags: int, vsync: int, tileset: Any, / + ) -> Any: + """struct TCOD_Context *TCOD_renderer_init_sdl2(int x, int y, int width, int height, const char *title, int window_flags, int vsync, struct TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_renderer_init_sdl3(window_props: Any, renderer_props: Any, tileset: Any, /) -> Any: + """TCOD_Context *TCOD_renderer_init_sdl3(SDL_PropertiesID window_props, SDL_PropertiesID renderer_props, struct TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_renderer_init_xterm( + window_x: int, window_y: int, pixel_width: int, pixel_height: int, columns: int, rows: int, window_title: Any, / + ) -> Any: + """TCOD_Context *TCOD_renderer_init_xterm(int window_x, int window_y, int pixel_width, int pixel_height, int columns, int rows, const char *window_title)""" + + @staticmethod + def TCOD_rng_splitmix64_next(state: Any, /) -> Any: + """inline static uint64_t TCOD_rng_splitmix64_next(uint64_t *state)""" + + @staticmethod + def TCOD_save_xp(n: int, consoles: Any, path: Any, compress_level: int, /) -> Any: + """TCOD_Error TCOD_save_xp(int n, const TCOD_Console * const *consoles, const char *path, int compress_level)""" + + @staticmethod + def TCOD_save_xp_to_memory(n_consoles: int, consoles: Any, n_out: int, out: Any, compression_level: int, /) -> int: + """int TCOD_save_xp_to_memory(int n_consoles, const TCOD_Console * const *consoles, int n_out, unsigned char *out, int compression_level)""" + + @staticmethod + def TCOD_sdl2_atlas_delete(atlas: Any, /) -> None: + """void TCOD_sdl2_atlas_delete(struct TCOD_TilesetAtlasSDL2 *atlas)""" + + @staticmethod + def TCOD_sdl2_atlas_new(renderer: Any, tileset: Any, /) -> Any: + """struct TCOD_TilesetAtlasSDL2 *TCOD_sdl2_atlas_new(struct SDL_Renderer *renderer, struct TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_sdl2_render_texture(atlas: Any, console: Any, cache: Any, target: Any, /) -> Any: + """TCOD_Error TCOD_sdl2_render_texture(const struct TCOD_TilesetAtlasSDL2 *atlas, const struct TCOD_Console *console, struct TCOD_Console *cache, struct SDL_Texture *target)""" + + @staticmethod + def TCOD_sdl2_render_texture_setup(atlas: Any, console: Any, cache: Any, target: Any, /) -> Any: + """TCOD_Error TCOD_sdl2_render_texture_setup(const struct TCOD_TilesetAtlasSDL2 *atlas, const struct TCOD_Console *console, struct TCOD_Console **cache, struct SDL_Texture **target)""" + + @staticmethod + def TCOD_semaphore_delete(sem: Any, /) -> None: + """void TCOD_semaphore_delete(TCOD_semaphore_t sem)""" + + @staticmethod + def TCOD_semaphore_lock(sem: Any, /) -> None: + """void TCOD_semaphore_lock(TCOD_semaphore_t sem)""" + + @staticmethod + def TCOD_semaphore_new(initVal: int, /) -> Any: + """TCOD_semaphore_t TCOD_semaphore_new(int initVal)""" + + @staticmethod + def TCOD_semaphore_unlock(sem: Any, /) -> None: + """void TCOD_semaphore_unlock(TCOD_semaphore_t sem)""" + + @staticmethod + def TCOD_set_default_tileset(tileset: Any, /) -> None: + """void TCOD_set_default_tileset(TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_set_error(msg: Any, /) -> Any: + """TCOD_Error TCOD_set_error(const char *msg)""" + + @staticmethod + def TCOD_set_errorf(fmt: Any, /, *__args: Any) -> Any: + """TCOD_Error TCOD_set_errorf(const char *fmt, ...)""" + + @staticmethod + def TCOD_set_log_callback(callback: Any, userdata: Any, /) -> None: + """void TCOD_set_log_callback(TCOD_LoggingCallback callback, void *userdata)""" + + @staticmethod + def TCOD_set_log_level(level: int, /) -> None: + """void TCOD_set_log_level(int level)""" + + @staticmethod + def TCOD_strcasecmp(s1: Any, s2: Any, /) -> int: + """int TCOD_strcasecmp(const char *s1, const char *s2)""" + + @staticmethod + def TCOD_strdup(s: Any, /) -> Any: + """char *TCOD_strdup(const char *s)""" + + @staticmethod + def TCOD_strncasecmp(s1: Any, s2: Any, n: int, /) -> int: + """int TCOD_strncasecmp(const char *s1, const char *s2, size_t n)""" + + @staticmethod + def TCOD_struct_add_flag(def_: Any, propname: Any, /) -> None: + """void TCOD_struct_add_flag(TCOD_ParserStruct *def, const char *propname)""" + + @staticmethod + def TCOD_struct_add_list_property(def_: Any, name: Any, type: Any, mandatory: bool, /) -> None: + """void TCOD_struct_add_list_property(TCOD_ParserStruct *def, const char *name, TCOD_value_type_t type, bool mandatory)""" + + @staticmethod + def TCOD_struct_add_property(def_: Any, name: Any, type: Any, mandatory: bool, /) -> None: + """void TCOD_struct_add_property(TCOD_ParserStruct *def, const char *name, TCOD_value_type_t type, bool mandatory)""" + + @staticmethod + def TCOD_struct_add_structure(def_: Any, sub_structure: Any, /) -> None: + """void TCOD_struct_add_structure(TCOD_ParserStruct *def, const TCOD_ParserStruct *sub_structure)""" + + @staticmethod + def TCOD_struct_add_value_list(def_: Any, name: Any, value_list: Any, mandatory: bool, /) -> None: + """void TCOD_struct_add_value_list(TCOD_ParserStruct *def, const char *name, const char * const *value_list, bool mandatory)""" + + @staticmethod + def TCOD_struct_add_value_list_sized(def_: Any, name: Any, value_list: Any, size: int, mandatory: bool, /) -> None: + """void TCOD_struct_add_value_list_sized(TCOD_ParserStruct *def, const char *name, const char * const *value_list, int size, bool mandatory)""" + + @staticmethod + def TCOD_struct_get_name(def_: Any, /) -> Any: + """const char *TCOD_struct_get_name(const TCOD_ParserStruct *def)""" + + @staticmethod + def TCOD_struct_get_type(def_: Any, propname: Any, /) -> Any: + """TCOD_value_type_t TCOD_struct_get_type(const TCOD_ParserStruct *def, const char *propname)""" + + @staticmethod + def TCOD_struct_is_mandatory(def_: Any, propname: Any, /) -> bool: + """bool TCOD_struct_is_mandatory(TCOD_ParserStruct *def, const char *propname)""" + + @staticmethod + def TCOD_sys_accumulate_console(console: Any, /) -> int: + """int TCOD_sys_accumulate_console(const TCOD_Console *console)""" + + @staticmethod + def TCOD_sys_accumulate_console_(console: Any, viewport: Any, /) -> int: + """int TCOD_sys_accumulate_console_(const TCOD_Console *console, const struct SDL_Rect *viewport)""" + + @staticmethod + def TCOD_sys_check_for_event(eventMask: int, key: Any, mouse: Any, /) -> Any: + """TCOD_event_t TCOD_sys_check_for_event(int eventMask, TCOD_key_t *key, TCOD_mouse_t *mouse)""" + + @staticmethod + def TCOD_sys_check_for_keypress(flags: int, /) -> Any: + """TCOD_key_t TCOD_sys_check_for_keypress(int flags)""" + + @staticmethod + def TCOD_sys_check_magic_number(filename: Any, size: int, data: Any, /) -> bool: + """bool TCOD_sys_check_magic_number(const char *filename, size_t size, uint8_t *data)""" + + @staticmethod + def TCOD_sys_clipboard_get() -> Any: + """char *TCOD_sys_clipboard_get(void)""" + + @staticmethod + def TCOD_sys_clipboard_set(value: Any, /) -> bool: + """bool TCOD_sys_clipboard_set(const char *value)""" + + @staticmethod + def TCOD_sys_create_directory(path: Any, /) -> bool: + """bool TCOD_sys_create_directory(const char *path)""" + + @staticmethod + def TCOD_sys_decode_font_() -> None: + """void TCOD_sys_decode_font_(void)""" + + @staticmethod + def TCOD_sys_delete_directory(path: Any, /) -> bool: + """bool TCOD_sys_delete_directory(const char *path)""" + + @staticmethod + def TCOD_sys_delete_file(path: Any, /) -> bool: + """bool TCOD_sys_delete_file(const char *path)""" + + @staticmethod + def TCOD_sys_elapsed_milli() -> Any: + """uint32_t TCOD_sys_elapsed_milli(void)""" + + @staticmethod + def TCOD_sys_elapsed_seconds() -> float: + """float TCOD_sys_elapsed_seconds(void)""" + + @staticmethod + def TCOD_sys_file_exists(filename: Any, /, *__args: Any) -> bool: + """bool TCOD_sys_file_exists(const char *filename, ...)""" + + @staticmethod + def TCOD_sys_force_fullscreen_resolution(width: int, height: int, /) -> None: + """void TCOD_sys_force_fullscreen_resolution(int width, int height)""" + + @staticmethod + def TCOD_sys_get_SDL_renderer() -> Any: + """struct SDL_Renderer *TCOD_sys_get_SDL_renderer(void)""" + + @staticmethod + def TCOD_sys_get_SDL_window() -> Any: + """struct SDL_Window *TCOD_sys_get_SDL_window(void)""" + + @staticmethod + def TCOD_sys_get_char_size(w: Any, h: Any, /) -> None: + """void TCOD_sys_get_char_size(int *w, int *h)""" + + @staticmethod + def TCOD_sys_get_current_resolution(w: Any, h: Any, /) -> Any: + """TCOD_Error TCOD_sys_get_current_resolution(int *w, int *h)""" + + @staticmethod + def TCOD_sys_get_current_resolution_x() -> int: + """int TCOD_sys_get_current_resolution_x(void)""" + + @staticmethod + def TCOD_sys_get_current_resolution_y() -> int: + """int TCOD_sys_get_current_resolution_y(void)""" + + @staticmethod + def TCOD_sys_get_directory_content(path: Any, pattern: Any, /) -> Any: + """TCOD_list_t TCOD_sys_get_directory_content(const char *path, const char *pattern)""" + + @staticmethod + def TCOD_sys_get_fps() -> int: + """int TCOD_sys_get_fps(void)""" + + @staticmethod + def TCOD_sys_get_fullscreen_offsets(offset_x: Any, offset_y: Any, /) -> None: + """void TCOD_sys_get_fullscreen_offsets(int *offset_x, int *offset_y)""" + + @staticmethod + def TCOD_sys_get_image_alpha(image: Any, x: int, y: int, /) -> int: + """int TCOD_sys_get_image_alpha(const struct SDL_Surface *image, int x, int y)""" + + @staticmethod + def TCOD_sys_get_image_pixel(image: Any, x: int, y: int, /) -> Any: + """TCOD_color_t TCOD_sys_get_image_pixel(const struct SDL_Surface *image, int x, int y)""" + + @staticmethod + def TCOD_sys_get_image_size(image: Any, w: Any, h: Any, /) -> None: + """void TCOD_sys_get_image_size(const struct SDL_Surface *image, int *w, int *h)""" + + @staticmethod + def TCOD_sys_get_internal_console() -> Any: + """TCOD_Console *TCOD_sys_get_internal_console(void)""" + + @staticmethod + def TCOD_sys_get_internal_context() -> Any: + """TCOD_Context *TCOD_sys_get_internal_context(void)""" + + @staticmethod + def TCOD_sys_get_last_frame_length() -> float: + """float TCOD_sys_get_last_frame_length(void)""" + + @staticmethod + def TCOD_sys_get_num_cores() -> int: + """int TCOD_sys_get_num_cores(void)""" + + @staticmethod + def TCOD_sys_get_renderer() -> Any: + """TCOD_renderer_t TCOD_sys_get_renderer(void)""" + + @staticmethod + def TCOD_sys_get_sdl_renderer() -> Any: + """struct SDL_Renderer *TCOD_sys_get_sdl_renderer(void)""" + + @staticmethod + def TCOD_sys_get_sdl_window() -> Any: + """struct SDL_Window *TCOD_sys_get_sdl_window(void)""" + + @staticmethod + def TCOD_sys_handle_key_event(ev: Any, key: Any, /) -> Any: + """TCOD_event_t TCOD_sys_handle_key_event(const union SDL_Event *ev, TCOD_key_t *key)""" + + @staticmethod + def TCOD_sys_handle_mouse_event(ev: Any, mouse: Any, /) -> Any: + """TCOD_event_t TCOD_sys_handle_mouse_event(const union SDL_Event *ev, TCOD_mouse_t *mouse)""" + + @staticmethod + def TCOD_sys_is_directory(path: Any, /) -> bool: + """bool TCOD_sys_is_directory(const char *path)""" + + @staticmethod + def TCOD_sys_is_key_pressed(key: Any, /) -> bool: + """bool TCOD_sys_is_key_pressed(TCOD_keycode_t key)""" + + @staticmethod + def TCOD_sys_load_image(filename: Any, /) -> Any: + """struct SDL_Surface *TCOD_sys_load_image(const char *filename)""" + + @staticmethod + def TCOD_sys_load_player_config() -> Any: + """TCOD_Error TCOD_sys_load_player_config(void)""" + + @staticmethod + def TCOD_sys_map_ascii_to_font(asciiCode: int, fontCharX: int, fontCharY: int, /) -> None: + """void TCOD_sys_map_ascii_to_font(int asciiCode, int fontCharX, int fontCharY)""" + + @staticmethod + def TCOD_sys_pixel_to_tile(x: Any, y: Any, /) -> None: + """void TCOD_sys_pixel_to_tile(double *x, double *y)""" + + @staticmethod + def TCOD_sys_read_file(filename: Any, buf: Any, size: Any, /) -> bool: + """bool TCOD_sys_read_file(const char *filename, unsigned char **buf, size_t *size)""" + + @staticmethod + def TCOD_sys_register_SDL_renderer(renderer: Any, /) -> None: + """void TCOD_sys_register_SDL_renderer(SDL_renderer_t renderer)""" + + @staticmethod + def TCOD_sys_restore_fps() -> None: + """void TCOD_sys_restore_fps(void)""" + + @staticmethod + def TCOD_sys_save_bitmap(bitmap: Any, filename: Any, /) -> Any: + """TCOD_Error TCOD_sys_save_bitmap(struct SDL_Surface *bitmap, const char *filename)""" + + @staticmethod + def TCOD_sys_save_fps() -> None: + """void TCOD_sys_save_fps(void)""" + + @staticmethod + def TCOD_sys_save_screenshot(filename: Any, /) -> None: + """void TCOD_sys_save_screenshot(const char *filename)""" + + @staticmethod + def TCOD_sys_set_fps(val: int, /) -> None: + """void TCOD_sys_set_fps(int val)""" + + @staticmethod + def TCOD_sys_set_renderer(renderer: Any, /) -> int: + """int TCOD_sys_set_renderer(TCOD_renderer_t renderer)""" + + @staticmethod + def TCOD_sys_shutdown() -> None: + """void TCOD_sys_shutdown(void)""" + + @staticmethod + def TCOD_sys_sleep_milli(val: Any, /) -> None: + """void TCOD_sys_sleep_milli(uint32_t val)""" + + @staticmethod + def TCOD_sys_startup() -> None: + """void TCOD_sys_startup(void)""" + + @staticmethod + def TCOD_sys_update_char(asciiCode: int, font_x: int, font_y: int, img: Any, x: int, y: int, /) -> None: + """void TCOD_sys_update_char(int asciiCode, int font_x, int font_y, const TCOD_Image *img, int x, int y)""" + + @staticmethod + def TCOD_sys_wait_for_event(eventMask: int, key: Any, mouse: Any, flush: bool, /) -> Any: + """TCOD_event_t TCOD_sys_wait_for_event(int eventMask, TCOD_key_t *key, TCOD_mouse_t *mouse, bool flush)""" + + @staticmethod + def TCOD_sys_wait_for_keypress(flush: bool, /) -> Any: + """TCOD_key_t TCOD_sys_wait_for_keypress(bool flush)""" + + @staticmethod + def TCOD_sys_write_file(filename: Any, buf: Any, size: Any, /) -> bool: + """bool TCOD_sys_write_file(const char *filename, unsigned char *buf, uint32_t size)""" + + @staticmethod + def TCOD_text_delete(txt: Any, /) -> None: + """void TCOD_text_delete(TCOD_text_t txt)""" + + @staticmethod + def TCOD_text_get(txt: Any, /) -> Any: + """const char *TCOD_text_get(TCOD_text_t txt)""" + + @staticmethod + def TCOD_text_init(x: int, y: int, w: int, h: int, max_chars: int, /) -> Any: + """TCOD_text_t TCOD_text_init(int x, int y, int w, int h, int max_chars)""" + + @staticmethod + def TCOD_text_init2(w: int, h: int, max_chars: int, /) -> Any: + """TCOD_text_t TCOD_text_init2(int w, int h, int max_chars)""" + + @staticmethod + def TCOD_text_render(txt: Any, con: Any, /) -> None: + """void TCOD_text_render(TCOD_text_t txt, TCOD_console_t con)""" + + @staticmethod + def TCOD_text_reset(txt: Any, /) -> None: + """void TCOD_text_reset(TCOD_text_t txt)""" + + @staticmethod + def TCOD_text_set_colors(txt: Any, fore: Any, back: Any, back_transparency: float, /) -> None: + """void TCOD_text_set_colors(TCOD_text_t txt, TCOD_color_t fore, TCOD_color_t back, float back_transparency)""" + + @staticmethod + def TCOD_text_set_pos(txt: Any, x: int, y: int, /) -> None: + """void TCOD_text_set_pos(TCOD_text_t txt, int x, int y)""" + + @staticmethod + def TCOD_text_set_properties( + txt: Any, cursor_char: int, blink_interval: int, prompt: Any, tab_size: int, / + ) -> None: + """void TCOD_text_set_properties(TCOD_text_t txt, int cursor_char, int blink_interval, const char *prompt, int tab_size)""" + + @staticmethod + def TCOD_text_update(txt: Any, key: Any, /) -> bool: + """bool TCOD_text_update(TCOD_text_t txt, TCOD_key_t key)""" + + @staticmethod + def TCOD_thread_delete(th: Any, /) -> None: + """void TCOD_thread_delete(TCOD_thread_t th)""" + + @staticmethod + def TCOD_thread_new(func: Any, data: Any, /) -> Any: + """TCOD_thread_t TCOD_thread_new(int (*func)(void *), void *data)""" + + @staticmethod + def TCOD_thread_wait(th: Any, /) -> None: + """void TCOD_thread_wait(TCOD_thread_t th)""" + + @staticmethod + def TCOD_tileset_assign_tile(tileset: Any, tile_id: int, codepoint: int, /) -> int: + """int TCOD_tileset_assign_tile(struct TCOD_Tileset *tileset, int tile_id, int codepoint)""" + + @staticmethod + def TCOD_tileset_delete(tileset: Any, /) -> None: + """void TCOD_tileset_delete(TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_tileset_get_tile(tileset: Any, codepoint: int, /) -> Any: + """const struct TCOD_ColorRGBA *TCOD_tileset_get_tile(const TCOD_Tileset *tileset, int codepoint)""" + + @staticmethod + def TCOD_tileset_get_tile_(tileset: Any, codepoint: int, buffer: Any, /) -> Any: + """TCOD_Error TCOD_tileset_get_tile_(const TCOD_Tileset *tileset, int codepoint, struct TCOD_ColorRGBA *buffer)""" + + @staticmethod + def TCOD_tileset_get_tile_height_(tileset: Any, /) -> int: + """int TCOD_tileset_get_tile_height_(const TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_tileset_get_tile_width_(tileset: Any, /) -> int: + """int TCOD_tileset_get_tile_width_(const TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_tileset_load(filename: Any, columns: int, rows: int, n: int, charmap: Any, /) -> Any: + """TCOD_Tileset *TCOD_tileset_load(const char *filename, int columns, int rows, int n, const int *charmap)""" + + @staticmethod + def TCOD_tileset_load_fallback_font_(tile_width: int, tile_height: int, /) -> Any: + """TCOD_Tileset *TCOD_tileset_load_fallback_font_(int tile_width, int tile_height)""" + + @staticmethod + def TCOD_tileset_load_mem(buffer_length: int, buffer: Any, columns: int, rows: int, n: int, charmap: Any, /) -> Any: + """TCOD_Tileset *TCOD_tileset_load_mem(size_t buffer_length, const unsigned char *buffer, int columns, int rows, int n, const int *charmap)""" + + @staticmethod + def TCOD_tileset_load_raw( + width: int, height: int, pixels: Any, columns: int, rows: int, n: int, charmap: Any, / + ) -> Any: + """TCOD_Tileset *TCOD_tileset_load_raw(int width, int height, const struct TCOD_ColorRGBA *pixels, int columns, int rows, int n, const int *charmap)""" + + @staticmethod + def TCOD_tileset_load_truetype_(path: Any, tile_width: int, tile_height: int, /) -> Any: + """TCOD_Error TCOD_tileset_load_truetype_(const char *path, int tile_width, int tile_height)""" + + @staticmethod + def TCOD_tileset_new(tile_width: int, tile_height: int, /) -> Any: + """TCOD_Tileset *TCOD_tileset_new(int tile_width, int tile_height)""" + + @staticmethod + def TCOD_tileset_notify_tile_changed(tileset: Any, tile_id: int, /) -> None: + """void TCOD_tileset_notify_tile_changed(TCOD_Tileset *tileset, int tile_id)""" + + @staticmethod + def TCOD_tileset_observer_delete(observer: Any, /) -> None: + """void TCOD_tileset_observer_delete(struct TCOD_TilesetObserver *observer)""" + + @staticmethod + def TCOD_tileset_observer_new(tileset: Any, /) -> Any: + """struct TCOD_TilesetObserver *TCOD_tileset_observer_new(struct TCOD_Tileset *tileset)""" + + @staticmethod + def TCOD_tileset_render_to_surface(tileset: Any, console: Any, cache: Any, surface_out: Any, /) -> Any: + """TCOD_Error TCOD_tileset_render_to_surface(const TCOD_Tileset *tileset, const TCOD_Console *console, TCOD_Console **cache, struct SDL_Surface **surface_out)""" + + @staticmethod + def TCOD_tileset_reserve(tileset: Any, desired: int, /) -> Any: + """TCOD_Error TCOD_tileset_reserve(TCOD_Tileset *tileset, int desired)""" + + @staticmethod + def TCOD_tileset_set_tile_(tileset: Any, codepoint: int, buffer: Any, /) -> Any: + """TCOD_Error TCOD_tileset_set_tile_(TCOD_Tileset *tileset, int codepoint, const struct TCOD_ColorRGBA *buffer)""" + + @staticmethod + def TCOD_tree_add_son(node: Any, son: Any, /) -> None: + """void TCOD_tree_add_son(TCOD_tree_t *node, TCOD_tree_t *son)""" + + @staticmethod + def TCOD_tree_new() -> Any: + """TCOD_tree_t *TCOD_tree_new(void)""" + + @staticmethod + def TCOD_viewport_delete(viewport: Any, /) -> None: + """void TCOD_viewport_delete(TCOD_ViewportOptions *viewport)""" + + @staticmethod + def TCOD_viewport_new() -> Any: + """TCOD_ViewportOptions *TCOD_viewport_new(void)""" + + @staticmethod + def TCOD_zip_delete(zip: Any, /) -> None: + """void TCOD_zip_delete(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_char(zip: Any, /) -> Any: + """char TCOD_zip_get_char(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_color(zip: Any, /) -> Any: + """TCOD_color_t TCOD_zip_get_color(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_console(zip: Any, /) -> Any: + """TCOD_console_t TCOD_zip_get_console(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_current_bytes(zip: Any, /) -> Any: + """uint32_t TCOD_zip_get_current_bytes(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_data(zip: Any, nbBytes: int, data: Any, /) -> int: + """int TCOD_zip_get_data(TCOD_zip_t zip, int nbBytes, void *data)""" + + @staticmethod + def TCOD_zip_get_float(zip: Any, /) -> float: + """float TCOD_zip_get_float(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_image(zip: Any, /) -> Any: + """TCOD_Image *TCOD_zip_get_image(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_int(zip: Any, /) -> int: + """int TCOD_zip_get_int(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_random(zip: Any, /) -> Any: + """TCOD_Random *TCOD_zip_get_random(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_remaining_bytes(zip: Any, /) -> Any: + """uint32_t TCOD_zip_get_remaining_bytes(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_get_string(zip: Any, /) -> Any: + """const char *TCOD_zip_get_string(TCOD_zip_t zip)""" + + @staticmethod + def TCOD_zip_load_from_file(zip: Any, filename: Any, /) -> int: + """int TCOD_zip_load_from_file(TCOD_zip_t zip, const char *filename)""" + + @staticmethod + def TCOD_zip_new() -> Any: + """TCOD_zip_t TCOD_zip_new(void)""" + + @staticmethod + def TCOD_zip_put_char(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_char(TCOD_zip_t zip, char val)""" + + @staticmethod + def TCOD_zip_put_color(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_color(TCOD_zip_t zip, const TCOD_color_t val)""" + + @staticmethod + def TCOD_zip_put_console(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_console(TCOD_zip_t zip, const TCOD_Console *val)""" + + @staticmethod + def TCOD_zip_put_data(zip: Any, nbBytes: int, data: Any, /) -> None: + """void TCOD_zip_put_data(TCOD_zip_t zip, int nbBytes, const void *data)""" + + @staticmethod + def TCOD_zip_put_float(zip: Any, val: float, /) -> None: + """void TCOD_zip_put_float(TCOD_zip_t zip, float val)""" + + @staticmethod + def TCOD_zip_put_image(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_image(TCOD_zip_t zip, const TCOD_Image *val)""" + + @staticmethod + def TCOD_zip_put_int(zip: Any, val: int, /) -> None: + """void TCOD_zip_put_int(TCOD_zip_t zip, int val)""" + + @staticmethod + def TCOD_zip_put_random(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_random(TCOD_zip_t zip, const TCOD_Random *val)""" + + @staticmethod + def TCOD_zip_put_string(zip: Any, val: Any, /) -> None: + """void TCOD_zip_put_string(TCOD_zip_t zip, const char *val)""" + + @staticmethod + def TCOD_zip_save_to_file(zip: Any, filename: Any, /) -> int: + """int TCOD_zip_save_to_file(TCOD_zip_t zip, const char *filename)""" + + @staticmethod + def TCOD_zip_skip_bytes(zip: Any, nbBytes: Any, /) -> None: + """void TCOD_zip_skip_bytes(TCOD_zip_t zip, uint32_t nbBytes)""" + + @staticmethod + def TDL_color_HSV(h: float, s: float, v: float, /) -> int: + """int TDL_color_HSV(float h, float s, float v)""" + + @staticmethod + def TDL_color_RGB(r: int, g: int, b: int, /) -> int: + """int TDL_color_RGB(int r, int g, int b)""" + + @staticmethod + def TDL_color_add(c1: int, c2: int, /) -> int: + """int TDL_color_add(int c1, int c2)""" + + @staticmethod + def TDL_color_equals(c1: int, c2: int, /) -> bool: + """bool TDL_color_equals(int c1, int c2)""" + + @staticmethod + def TDL_color_from_int(color: int, /) -> Any: + """TCOD_color_t TDL_color_from_int(int color)""" + + @staticmethod + def TDL_color_get_hue(color: int, /) -> float: + """float TDL_color_get_hue(int color)""" + + @staticmethod + def TDL_color_get_saturation(color: int, /) -> float: + """float TDL_color_get_saturation(int color)""" + + @staticmethod + def TDL_color_get_value(color: int, /) -> float: + """float TDL_color_get_value(int color)""" + + @staticmethod + def TDL_color_int_to_array(color: int, /) -> Any: + """int *TDL_color_int_to_array(int color)""" + + @staticmethod + def TDL_color_lerp(c1: int, c2: int, coef: float, /) -> int: + """int TDL_color_lerp(int c1, int c2, float coef)""" + + @staticmethod + def TDL_color_multiply(c1: int, c2: int, /) -> int: + """int TDL_color_multiply(int c1, int c2)""" + + @staticmethod + def TDL_color_multiply_scalar(c: int, value: float, /) -> int: + """int TDL_color_multiply_scalar(int c, float value)""" + + @staticmethod + def TDL_color_scale_HSV(color: int, scoef: float, vcoef: float, /) -> int: + """int TDL_color_scale_HSV(int color, float scoef, float vcoef)""" + + @staticmethod + def TDL_color_set_hue(color: int, h: float, /) -> int: + """int TDL_color_set_hue(int color, float h)""" + + @staticmethod + def TDL_color_set_saturation(color: int, h: float, /) -> int: + """int TDL_color_set_saturation(int color, float h)""" + + @staticmethod + def TDL_color_set_value(color: int, h: float, /) -> int: + """int TDL_color_set_value(int color, float h)""" + + @staticmethod + def TDL_color_shift_hue(color: int, hue_shift: float, /) -> int: + """int TDL_color_shift_hue(int color, float hue_shift)""" + + @staticmethod + def TDL_color_subtract(c1: int, c2: int, /) -> int: + """int TDL_color_subtract(int c1, int c2)""" + + @staticmethod + def TDL_color_to_int(color: Any, /) -> int: + """int TDL_color_to_int(TCOD_color_t *color)""" + + @staticmethod + def TDL_console_get_bg(console: Any, x: int, y: int, /) -> int: + """int TDL_console_get_bg(TCOD_console_t console, int x, int y)""" + + @staticmethod + def TDL_console_get_fg(console: Any, x: int, y: int, /) -> int: + """int TDL_console_get_fg(TCOD_console_t console, int x, int y)""" + + @staticmethod + def TDL_console_put_char_ex(console: Any, x: int, y: int, ch: int, fg: int, bg: int, flag: Any, /) -> int: + """int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TDL_console_set_bg(console: Any, x: int, y: int, color: int, flag: Any, /) -> None: + """void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag)""" + + @staticmethod + def TDL_console_set_fg(console: Any, x: int, y: int, color: int, /) -> None: + """void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color)""" + + @staticmethod + def TDL_list_get_bool(l: Any, idx: int, /) -> bool: + """bool TDL_list_get_bool(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_char(l: Any, idx: int, /) -> Any: + """char TDL_list_get_char(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_color(l: Any, idx: int, /) -> Any: + """TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_dice(l: Any, idx: int, /) -> Any: + """TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_float(l: Any, idx: int, /) -> float: + """float TDL_list_get_float(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_int(l: Any, idx: int, /) -> int: + """int TDL_list_get_int(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_string(l: Any, idx: int, /) -> Any: + """char *TDL_list_get_string(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_list_get_union(l: Any, idx: int, /) -> Any: + """TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx)""" + + @staticmethod + def TDL_map_data_from_buffer(map: Any, buffer: Any, /) -> None: + """void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer)""" + + @staticmethod + def TDL_map_fov_to_buffer(map: Any, buffer: Any, cumulative: bool, /) -> None: + """void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative)""" + + @staticmethod + def _libtcod_log_watcher(message: Any, userdata: Any, /) -> None: + """void _libtcod_log_watcher(const TCOD_LogMessage *message, void *userdata)""" + + @staticmethod + def _pycall_bsp_callback(node: Any, userData: Any, /) -> bool: + """bool _pycall_bsp_callback(TCOD_bsp_t *node, void *userData)""" + + @staticmethod + def _pycall_cli_output(userdata: Any, output: Any, /) -> None: + """void _pycall_cli_output(void *userdata, const char *output)""" + + @staticmethod + def _pycall_parser_end_struct(str: Any, name: Any, /) -> bool: + """bool _pycall_parser_end_struct(TCOD_parser_struct_t str, const char *name)""" + + @staticmethod + def _pycall_parser_error(msg: Any, /) -> None: + """void _pycall_parser_error(const char *msg)""" + + @staticmethod + def _pycall_parser_new_flag(name: Any, /) -> bool: + """bool _pycall_parser_new_flag(const char *name)""" + + @staticmethod + def _pycall_parser_new_property(propname: Any, type: Any, value: Any, /) -> bool: + """bool _pycall_parser_new_property(const char *propname, TCOD_value_type_t type, TCOD_value_t value)""" + + @staticmethod + def _pycall_parser_new_struct(str: Any, name: Any, /) -> bool: + """bool _pycall_parser_new_struct(TCOD_parser_struct_t str, const char *name)""" + + @staticmethod + def _pycall_path_dest_only(x1: int, y1: int, x2: int, y2: int, user_data: Any, /) -> float: + """float _pycall_path_dest_only(int x1, int y1, int x2, int y2, void *user_data)""" + + @staticmethod + def _pycall_path_old(x: int, y: int, xDest: int, yDest: int, user_data: Any, /) -> float: + """float _pycall_path_old(int x, int y, int xDest, int yDest, void *user_data)""" + + @staticmethod + def _pycall_path_simple(x: int, y: int, xDest: int, yDest: int, user_data: Any, /) -> float: + """float _pycall_path_simple(int x, int y, int xDest, int yDest, void *user_data)""" + + @staticmethod + def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, user_data: Any, /) -> float: + """float _pycall_path_swap_src_dest(int x1, int y1, int x2, int y2, void *user_data)""" + + @staticmethod + def _pycall_sdl_hook(arg0: Any, /) -> None: + """void _pycall_sdl_hook(struct SDL_Surface *)""" + + @staticmethod + def _sdl_audio_stream_callback(userdata: Any, stream: Any, additional_amount: int, total_amount: int, /) -> None: + """void _sdl_audio_stream_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)""" + + @staticmethod + def _sdl_event_watcher(userdata: Any, event: Any, /) -> int: + """int _sdl_event_watcher(void *userdata, SDL_Event *event)""" + + @staticmethod + def _sdl_log_output_function(userdata: Any, category: int, priority: Any, message: Any, /) -> None: + """void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message)""" + + @staticmethod + def alloca(arg0: int, /) -> Any: + """void *alloca(size_t)""" + + @staticmethod + def bresenham(x1: int, y1: int, x2: int, y2: int, n: int, out: Any, /) -> int: + """int bresenham(int x1, int y1, int x2, int y2, int n, int *out)""" + + @staticmethod + def compute_heuristic(heuristic: Any, ndim: int, index: Any, /) -> int: + """int compute_heuristic(const struct PathfinderHeuristic *heuristic, int ndim, const int *index)""" + + @staticmethod + def dijkstra2d(dist: Any, cost: Any, edges_2d_n: int, edges_2d: Any, /) -> int: + """int dijkstra2d(struct NArray *dist, const struct NArray *cost, int edges_2d_n, const int *edges_2d)""" + + @staticmethod + def dijkstra2d_basic(dist: Any, cost: Any, cardinal: int, diagonal: int, /) -> int: + """int dijkstra2d_basic(struct NArray *dist, const struct NArray *cost, int cardinal, int diagonal)""" + + @staticmethod + def frontier_has_index(frontier: Any, index: Any, /) -> int: + """int frontier_has_index(const struct TCOD_Frontier *frontier, const int *index)""" + + @staticmethod + def get_travel_path(ndim: Any, travel_map: Any, start: Any, out: Any, /) -> int: + """ptrdiff_t get_travel_path(int8_t ndim, const NArray *travel_map, const int *start, int *out)""" + + @staticmethod + def hillclimb2d(dist_array: Any, start_i: int, start_j: int, edges_2d_n: int, edges_2d: Any, out: Any, /) -> int: + """int hillclimb2d(const struct NArray *dist_array, int start_i, int start_j, int edges_2d_n, const int *edges_2d, int *out)""" + + @staticmethod + def hillclimb2d_basic(dist: Any, x: int, y: int, cardinal: bool, diagonal: bool, out: Any, /) -> int: + """int hillclimb2d_basic(const struct NArray *dist, int x, int y, bool cardinal, bool diagonal, int *out)""" + + @staticmethod + def path_compute(frontier: Any, dist_map: Any, travel_map: Any, n: int, rules: Any, heuristic: Any, /) -> int: + """int path_compute(struct TCOD_Frontier *frontier, struct NArray *dist_map, struct NArray *travel_map, int n, const struct PathfinderRule *rules, const struct PathfinderHeuristic *heuristic)""" + + @staticmethod + def path_compute_step(frontier: Any, dist_map: Any, travel_map: Any, n: int, rules: Any, heuristic: Any, /) -> int: + """int path_compute_step(struct TCOD_Frontier *frontier, struct NArray *dist_map, struct NArray *travel_map, int n, const struct PathfinderRule *rules, const struct PathfinderHeuristic *heuristic)""" + + @staticmethod + def rebuild_frontier_from_distance(frontier: Any, dist_map: Any, /) -> int: + """int rebuild_frontier_from_distance(struct TCOD_Frontier *frontier, const NArray *dist_map)""" + + @staticmethod + def sync_time_() -> None: + """void sync_time_(void)""" + + @staticmethod + def update_frontier_heuristic(frontier: Any, heuristic: Any, /) -> int: + """int update_frontier_heuristic(struct TCOD_Frontier *frontier, const struct PathfinderHeuristic *heuristic)""" + + FOV_BASIC: Final[int] + FOV_DIAMOND: Final[int] + FOV_PERMISSIVE_0: Final[int] + FOV_PERMISSIVE_1: Final[int] + FOV_PERMISSIVE_2: Final[int] + FOV_PERMISSIVE_3: Final[int] + FOV_PERMISSIVE_4: Final[int] + FOV_PERMISSIVE_5: Final[int] + FOV_PERMISSIVE_6: Final[int] + FOV_PERMISSIVE_7: Final[int] + FOV_PERMISSIVE_8: Final[int] + FOV_RESTRICTIVE: Final[int] + FOV_SHADOW: Final[int] + FOV_SYMMETRIC_SHADOWCAST: Final[int] + NB_FOV_ALGORITHMS: Final[int] + SDLK_0: Final[int] + SDLK_1: Final[int] + SDLK_2: Final[int] + SDLK_3: Final[int] + SDLK_4: Final[int] + SDLK_5: Final[int] + SDLK_6: Final[int] + SDLK_7: Final[int] + SDLK_8: Final[int] + SDLK_9: Final[int] + SDLK_A: Final[int] + SDLK_AC_BACK: Final[int] + SDLK_AC_BOOKMARKS: Final[int] + SDLK_AC_CLOSE: Final[int] + SDLK_AC_EXIT: Final[int] + SDLK_AC_FORWARD: Final[int] + SDLK_AC_HOME: Final[int] + SDLK_AC_NEW: Final[int] + SDLK_AC_OPEN: Final[int] + SDLK_AC_PRINT: Final[int] + SDLK_AC_PROPERTIES: Final[int] + SDLK_AC_REFRESH: Final[int] + SDLK_AC_SAVE: Final[int] + SDLK_AC_SEARCH: Final[int] + SDLK_AC_STOP: Final[int] + SDLK_AGAIN: Final[int] + SDLK_ALTERASE: Final[int] + SDLK_AMPERSAND: Final[int] + SDLK_APOSTROPHE: Final[int] + SDLK_APPLICATION: Final[int] + SDLK_ASTERISK: Final[int] + SDLK_AT: Final[int] + SDLK_B: Final[int] + SDLK_BACKSLASH: Final[int] + SDLK_BACKSPACE: Final[int] + SDLK_C: Final[int] + SDLK_CALL: Final[int] + SDLK_CANCEL: Final[int] + SDLK_CAPSLOCK: Final[int] + SDLK_CARET: Final[int] + SDLK_CHANNEL_DECREMENT: Final[int] + SDLK_CHANNEL_INCREMENT: Final[int] + SDLK_CLEAR: Final[int] + SDLK_CLEARAGAIN: Final[int] + SDLK_COLON: Final[int] + SDLK_COMMA: Final[int] + SDLK_COPY: Final[int] + SDLK_CRSEL: Final[int] + SDLK_CURRENCYSUBUNIT: Final[int] + SDLK_CURRENCYUNIT: Final[int] + SDLK_CUT: Final[int] + SDLK_D: Final[int] + SDLK_DBLAPOSTROPHE: Final[int] + SDLK_DECIMALSEPARATOR: Final[int] + SDLK_DELETE: Final[int] + SDLK_DOLLAR: Final[int] + SDLK_DOWN: Final[int] + SDLK_E: Final[int] + SDLK_END: Final[int] + SDLK_ENDCALL: Final[int] + SDLK_EQUALS: Final[int] + SDLK_ESCAPE: Final[int] + SDLK_EXCLAIM: Final[int] + SDLK_EXECUTE: Final[int] + SDLK_EXSEL: Final[int] + SDLK_EXTENDED_MASK: Final[int] + SDLK_F10: Final[int] + SDLK_F11: Final[int] + SDLK_F12: Final[int] + SDLK_F13: Final[int] + SDLK_F14: Final[int] + SDLK_F15: Final[int] + SDLK_F16: Final[int] + SDLK_F17: Final[int] + SDLK_F18: Final[int] + SDLK_F19: Final[int] + SDLK_F1: Final[int] + SDLK_F20: Final[int] + SDLK_F21: Final[int] + SDLK_F22: Final[int] + SDLK_F23: Final[int] + SDLK_F24: Final[int] + SDLK_F2: Final[int] + SDLK_F3: Final[int] + SDLK_F4: Final[int] + SDLK_F5: Final[int] + SDLK_F6: Final[int] + SDLK_F7: Final[int] + SDLK_F8: Final[int] + SDLK_F9: Final[int] + SDLK_F: Final[int] + SDLK_FIND: Final[int] + SDLK_G: Final[int] + SDLK_GRAVE: Final[int] + SDLK_GREATER: Final[int] + SDLK_H: Final[int] + SDLK_HASH: Final[int] + SDLK_HELP: Final[int] + SDLK_HOME: Final[int] + SDLK_I: Final[int] + SDLK_INSERT: Final[int] + SDLK_J: Final[int] + SDLK_K: Final[int] + SDLK_KP_000: Final[int] + SDLK_KP_00: Final[int] + SDLK_KP_0: Final[int] + SDLK_KP_1: Final[int] + SDLK_KP_2: Final[int] + SDLK_KP_3: Final[int] + SDLK_KP_4: Final[int] + SDLK_KP_5: Final[int] + SDLK_KP_6: Final[int] + SDLK_KP_7: Final[int] + SDLK_KP_8: Final[int] + SDLK_KP_9: Final[int] + SDLK_KP_A: Final[int] + SDLK_KP_AMPERSAND: Final[int] + SDLK_KP_AT: Final[int] + SDLK_KP_B: Final[int] + SDLK_KP_BACKSPACE: Final[int] + SDLK_KP_BINARY: Final[int] + SDLK_KP_C: Final[int] + SDLK_KP_CLEAR: Final[int] + SDLK_KP_CLEARENTRY: Final[int] + SDLK_KP_COLON: Final[int] + SDLK_KP_COMMA: Final[int] + SDLK_KP_D: Final[int] + SDLK_KP_DBLAMPERSAND: Final[int] + SDLK_KP_DBLVERTICALBAR: Final[int] + SDLK_KP_DECIMAL: Final[int] + SDLK_KP_DIVIDE: Final[int] + SDLK_KP_E: Final[int] + SDLK_KP_ENTER: Final[int] + SDLK_KP_EQUALS: Final[int] + SDLK_KP_EQUALSAS400: Final[int] + SDLK_KP_EXCLAM: Final[int] + SDLK_KP_F: Final[int] + SDLK_KP_GREATER: Final[int] + SDLK_KP_HASH: Final[int] + SDLK_KP_HEXADECIMAL: Final[int] + SDLK_KP_LEFTBRACE: Final[int] + SDLK_KP_LEFTPAREN: Final[int] + SDLK_KP_LESS: Final[int] + SDLK_KP_MEMADD: Final[int] + SDLK_KP_MEMCLEAR: Final[int] + SDLK_KP_MEMDIVIDE: Final[int] + SDLK_KP_MEMMULTIPLY: Final[int] + SDLK_KP_MEMRECALL: Final[int] + SDLK_KP_MEMSTORE: Final[int] + SDLK_KP_MEMSUBTRACT: Final[int] + SDLK_KP_MINUS: Final[int] + SDLK_KP_MULTIPLY: Final[int] + SDLK_KP_OCTAL: Final[int] + SDLK_KP_PERCENT: Final[int] + SDLK_KP_PERIOD: Final[int] + SDLK_KP_PLUS: Final[int] + SDLK_KP_PLUSMINUS: Final[int] + SDLK_KP_POWER: Final[int] + SDLK_KP_RIGHTBRACE: Final[int] + SDLK_KP_RIGHTPAREN: Final[int] + SDLK_KP_SPACE: Final[int] + SDLK_KP_TAB: Final[int] + SDLK_KP_VERTICALBAR: Final[int] + SDLK_KP_XOR: Final[int] + SDLK_L: Final[int] + SDLK_LALT: Final[int] + SDLK_LCTRL: Final[int] + SDLK_LEFT: Final[int] + SDLK_LEFTBRACE: Final[int] + SDLK_LEFTBRACKET: Final[int] + SDLK_LEFTPAREN: Final[int] + SDLK_LEFT_TAB: Final[int] + SDLK_LESS: Final[int] + SDLK_LEVEL5_SHIFT: Final[int] + SDLK_LGUI: Final[int] + SDLK_LHYPER: Final[int] + SDLK_LMETA: Final[int] + SDLK_LSHIFT: Final[int] + SDLK_M: Final[int] + SDLK_MEDIA_EJECT: Final[int] + SDLK_MEDIA_FAST_FORWARD: Final[int] + SDLK_MEDIA_NEXT_TRACK: Final[int] + SDLK_MEDIA_PAUSE: Final[int] + SDLK_MEDIA_PLAY: Final[int] + SDLK_MEDIA_PLAY_PAUSE: Final[int] + SDLK_MEDIA_PREVIOUS_TRACK: Final[int] + SDLK_MEDIA_RECORD: Final[int] + SDLK_MEDIA_REWIND: Final[int] + SDLK_MEDIA_SELECT: Final[int] + SDLK_MEDIA_STOP: Final[int] + SDLK_MENU: Final[int] + SDLK_MINUS: Final[int] + SDLK_MODE: Final[int] + SDLK_MULTI_KEY_COMPOSE: Final[int] + SDLK_MUTE: Final[int] + SDLK_N: Final[int] + SDLK_NUMLOCKCLEAR: Final[int] + SDLK_O: Final[int] + SDLK_OPER: Final[int] + SDLK_OUT: Final[int] + SDLK_P: Final[int] + SDLK_PAGEDOWN: Final[int] + SDLK_PAGEUP: Final[int] + SDLK_PASTE: Final[int] + SDLK_PAUSE: Final[int] + SDLK_PERCENT: Final[int] + SDLK_PERIOD: Final[int] + SDLK_PIPE: Final[int] + SDLK_PLUS: Final[int] + SDLK_PLUSMINUS: Final[int] + SDLK_POWER: Final[int] + SDLK_PRINTSCREEN: Final[int] + SDLK_PRIOR: Final[int] + SDLK_Q: Final[int] + SDLK_QUESTION: Final[int] + SDLK_R: Final[int] + SDLK_RALT: Final[int] + SDLK_RCTRL: Final[int] + SDLK_RETURN2: Final[int] + SDLK_RETURN: Final[int] + SDLK_RGUI: Final[int] + SDLK_RHYPER: Final[int] + SDLK_RIGHT: Final[int] + SDLK_RIGHTBRACE: Final[int] + SDLK_RIGHTBRACKET: Final[int] + SDLK_RIGHTPAREN: Final[int] + SDLK_RMETA: Final[int] + SDLK_RSHIFT: Final[int] + SDLK_S: Final[int] + SDLK_SCANCODE_MASK: Final[int] + SDLK_SCROLLLOCK: Final[int] + SDLK_SELECT: Final[int] + SDLK_SEMICOLON: Final[int] + SDLK_SEPARATOR: Final[int] + SDLK_SLASH: Final[int] + SDLK_SLEEP: Final[int] + SDLK_SOFTLEFT: Final[int] + SDLK_SOFTRIGHT: Final[int] + SDLK_SPACE: Final[int] + SDLK_STOP: Final[int] + SDLK_SYSREQ: Final[int] + SDLK_T: Final[int] + SDLK_TAB: Final[int] + SDLK_THOUSANDSSEPARATOR: Final[int] + SDLK_TILDE: Final[int] + SDLK_U: Final[int] + SDLK_UNDERSCORE: Final[int] + SDLK_UNDO: Final[int] + SDLK_UNKNOWN: Final[int] + SDLK_UP: Final[int] + SDLK_V: Final[int] + SDLK_VOLUMEDOWN: Final[int] + SDLK_VOLUMEUP: Final[int] + SDLK_W: Final[int] + SDLK_WAKE: Final[int] + SDLK_X: Final[int] + SDLK_Y: Final[int] + SDLK_Z: Final[int] + SDL_ADDEVENT: Final[int] + SDL_ALPHA_OPAQUE: Final[int] + SDL_ALPHA_TRANSPARENT: Final[int] + SDL_APP_CONTINUE: Final[int] + SDL_APP_FAILURE: Final[int] + SDL_APP_SUCCESS: Final[int] + SDL_ARRAYORDER_ABGR: Final[int] + SDL_ARRAYORDER_ARGB: Final[int] + SDL_ARRAYORDER_BGR: Final[int] + SDL_ARRAYORDER_BGRA: Final[int] + SDL_ARRAYORDER_NONE: Final[int] + SDL_ARRAYORDER_RGB: Final[int] + SDL_ARRAYORDER_RGBA: Final[int] + SDL_ASSERTION_ABORT: Final[int] + SDL_ASSERTION_ALWAYS_IGNORE: Final[int] + SDL_ASSERTION_BREAK: Final[int] + SDL_ASSERTION_IGNORE: Final[int] + SDL_ASSERTION_RETRY: Final[int] + SDL_ASSERT_LEVEL: Final[int] + SDL_ASYNCIO_CANCELED: Final[int] + SDL_ASYNCIO_COMPLETE: Final[int] + SDL_ASYNCIO_FAILURE: Final[int] + SDL_ASYNCIO_TASK_CLOSE: Final[int] + SDL_ASYNCIO_TASK_READ: Final[int] + SDL_ASYNCIO_TASK_WRITE: Final[int] + SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK: Final[int] + SDL_AUDIO_DEVICE_DEFAULT_RECORDING: Final[int] + SDL_AUDIO_F32: Final[int] + SDL_AUDIO_F32BE: Final[Literal[37152]] = 37152 + SDL_AUDIO_F32LE: Final[Literal[33056]] = 33056 + SDL_AUDIO_MASK_BIG_ENDIAN: Final[int] + SDL_AUDIO_MASK_BITSIZE: Final[int] + SDL_AUDIO_MASK_FLOAT: Final[int] + SDL_AUDIO_MASK_SIGNED: Final[int] + SDL_AUDIO_S16: Final[int] + SDL_AUDIO_S16BE: Final[Literal[36880]] = 36880 + SDL_AUDIO_S16LE: Final[Literal[32784]] = 32784 + SDL_AUDIO_S32: Final[int] + SDL_AUDIO_S32BE: Final[Literal[36896]] = 36896 + SDL_AUDIO_S32LE: Final[Literal[32800]] = 32800 + SDL_AUDIO_S8: Final[Literal[32776]] = 32776 + SDL_AUDIO_U8: Final[Literal[8]] = 8 + SDL_AUDIO_UNKNOWN: Final[Literal[0]] = 0 + SDL_BIG_ENDIAN: Final[int] + SDL_BITMAPORDER_1234: Final[int] + SDL_BITMAPORDER_4321: Final[int] + SDL_BITMAPORDER_NONE: Final[int] + SDL_BLENDFACTOR_DST_ALPHA: Final[Literal[9]] = 9 + SDL_BLENDFACTOR_DST_COLOR: Final[Literal[7]] = 7 + SDL_BLENDFACTOR_ONE: Final[Literal[2]] = 2 + SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: Final[Literal[10]] = 10 + SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: Final[Literal[8]] = 8 + SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: Final[Literal[6]] = 6 + SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: Final[Literal[4]] = 4 + SDL_BLENDFACTOR_SRC_ALPHA: Final[Literal[5]] = 5 + SDL_BLENDFACTOR_SRC_COLOR: Final[Literal[3]] = 3 + SDL_BLENDFACTOR_ZERO: Final[Literal[1]] = 1 + SDL_BLENDMODE_ADD: Final[int] + SDL_BLENDMODE_ADD_PREMULTIPLIED: Final[int] + SDL_BLENDMODE_BLEND: Final[int] + SDL_BLENDMODE_BLEND_PREMULTIPLIED: Final[int] + SDL_BLENDMODE_INVALID: Final[int] + SDL_BLENDMODE_MOD: Final[int] + SDL_BLENDMODE_MUL: Final[int] + SDL_BLENDMODE_NONE: Final[int] + SDL_BLENDOPERATION_ADD: Final[Literal[1]] = 1 + SDL_BLENDOPERATION_MAXIMUM: Final[Literal[5]] = 5 + SDL_BLENDOPERATION_MINIMUM: Final[Literal[4]] = 4 + SDL_BLENDOPERATION_REV_SUBTRACT: Final[Literal[3]] = 3 + SDL_BLENDOPERATION_SUBTRACT: Final[Literal[2]] = 2 + SDL_BUTTON_LEFT: Final[int] + SDL_BUTTON_LMASK: Final[int] + SDL_BUTTON_MIDDLE: Final[int] + SDL_BUTTON_MMASK: Final[int] + SDL_BUTTON_RIGHT: Final[int] + SDL_BUTTON_RMASK: Final[int] + SDL_BUTTON_X1: Final[int] + SDL_BUTTON_X1MASK: Final[int] + SDL_BUTTON_X2: Final[int] + SDL_BUTTON_X2MASK: Final[int] + SDL_BYTEORDER: Final[int] + SDL_CACHELINE_SIZE: Final[int] + SDL_CAMERA_POSITION_BACK_FACING: Final[int] + SDL_CAMERA_POSITION_FRONT_FACING: Final[int] + SDL_CAMERA_POSITION_UNKNOWN: Final[int] + SDL_CAPITALIZE_LETTERS: Final[int] + SDL_CAPITALIZE_NONE: Final[int] + SDL_CAPITALIZE_SENTENCES: Final[int] + SDL_CAPITALIZE_WORDS: Final[int] + SDL_CHROMA_LOCATION_CENTER: Final[Literal[2]] = 2 + SDL_CHROMA_LOCATION_LEFT: Final[Literal[1]] = 1 + SDL_CHROMA_LOCATION_NONE: Final[Literal[0]] = 0 + SDL_CHROMA_LOCATION_TOPLEFT: Final[Literal[3]] = 3 + SDL_COLORSPACE_BT2020_FULL: Final[Literal[571483657]] = 571483657 + SDL_COLORSPACE_BT2020_LIMITED: Final[Literal[554706441]] = 554706441 + SDL_COLORSPACE_BT601_FULL: Final[Literal[571480262]] = 571480262 + SDL_COLORSPACE_BT601_LIMITED: Final[Literal[554703046]] = 554703046 + SDL_COLORSPACE_BT709_FULL: Final[Literal[571474977]] = 571474977 + SDL_COLORSPACE_BT709_LIMITED: Final[Literal[554697761]] = 554697761 + SDL_COLORSPACE_HDR10: Final[Literal[301999616]] = 301999616 + SDL_COLORSPACE_JPEG: Final[Literal[570426566]] = 570426566 + SDL_COLORSPACE_RGB_DEFAULT: Final[int] + SDL_COLORSPACE_SRGB: Final[Literal[301991328]] = 301991328 + SDL_COLORSPACE_SRGB_LINEAR: Final[Literal[301991168]] = 301991168 + SDL_COLORSPACE_UNKNOWN: Final[Literal[0]] = 0 + SDL_COLORSPACE_YUV_DEFAULT: Final[int] + SDL_COLOR_PRIMARIES_BT2020: Final[Literal[9]] = 9 + SDL_COLOR_PRIMARIES_BT470BG: Final[Literal[5]] = 5 + SDL_COLOR_PRIMARIES_BT470M: Final[Literal[4]] = 4 + SDL_COLOR_PRIMARIES_BT601: Final[Literal[6]] = 6 + SDL_COLOR_PRIMARIES_BT709: Final[Literal[1]] = 1 + SDL_COLOR_PRIMARIES_CUSTOM: Final[Literal[31]] = 31 + SDL_COLOR_PRIMARIES_EBU3213: Final[Literal[22]] = 22 + SDL_COLOR_PRIMARIES_GENERIC_FILM: Final[Literal[8]] = 8 + SDL_COLOR_PRIMARIES_SMPTE240: Final[Literal[7]] = 7 + SDL_COLOR_PRIMARIES_SMPTE431: Final[Literal[11]] = 11 + SDL_COLOR_PRIMARIES_SMPTE432: Final[Literal[12]] = 12 + SDL_COLOR_PRIMARIES_UNKNOWN: Final[Literal[0]] = 0 + SDL_COLOR_PRIMARIES_UNSPECIFIED: Final[Literal[2]] = 2 + SDL_COLOR_PRIMARIES_XYZ: Final[Literal[10]] = 10 + SDL_COLOR_RANGE_FULL: Final[Literal[2]] = 2 + SDL_COLOR_RANGE_LIMITED: Final[Literal[1]] = 1 + SDL_COLOR_RANGE_UNKNOWN: Final[Literal[0]] = 0 + SDL_COLOR_TYPE_RGB: Final[Literal[1]] = 1 + SDL_COLOR_TYPE_UNKNOWN: Final[Literal[0]] = 0 + SDL_COLOR_TYPE_YCBCR: Final[Literal[2]] = 2 + SDL_DATE_FORMAT_DDMMYYYY: Final[Literal[1]] = 1 + SDL_DATE_FORMAT_MMDDYYYY: Final[Literal[2]] = 2 + SDL_DATE_FORMAT_YYYYMMDD: Final[Literal[0]] = 0 + SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE: Final[int] + SDL_ENUM_CONTINUE: Final[int] + SDL_ENUM_FAILURE: Final[int] + SDL_ENUM_SUCCESS: Final[int] + SDL_EVENT_AUDIO_DEVICE_ADDED: Final[Literal[4352]] = 4352 + SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: Final[int] + SDL_EVENT_AUDIO_DEVICE_REMOVED: Final[int] + SDL_EVENT_CAMERA_DEVICE_ADDED: Final[Literal[5120]] = 5120 + SDL_EVENT_CAMERA_DEVICE_APPROVED: Final[int] + SDL_EVENT_CAMERA_DEVICE_DENIED: Final[int] + SDL_EVENT_CAMERA_DEVICE_REMOVED: Final[int] + SDL_EVENT_CLIPBOARD_UPDATE: Final[Literal[2304]] = 2304 + SDL_EVENT_DID_ENTER_BACKGROUND: Final[int] + SDL_EVENT_DID_ENTER_FOREGROUND: Final[int] + SDL_EVENT_DISPLAY_ADDED: Final[int] + SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: Final[int] + SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: Final[int] + SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: Final[int] + SDL_EVENT_DISPLAY_FIRST: Final[int] + SDL_EVENT_DISPLAY_LAST: Final[int] + SDL_EVENT_DISPLAY_MOVED: Final[int] + SDL_EVENT_DISPLAY_ORIENTATION: Final[Literal[337]] = 337 + SDL_EVENT_DISPLAY_REMOVED: Final[int] + SDL_EVENT_DROP_BEGIN: Final[int] + SDL_EVENT_DROP_COMPLETE: Final[int] + SDL_EVENT_DROP_FILE: Final[Literal[4096]] = 4096 + SDL_EVENT_DROP_POSITION: Final[int] + SDL_EVENT_DROP_TEXT: Final[int] + SDL_EVENT_ENUM_PADDING: Final[Literal[2147483647]] = 2147483647 + SDL_EVENT_FINGER_CANCELED: Final[int] + SDL_EVENT_FINGER_DOWN: Final[Literal[1792]] = 1792 + SDL_EVENT_FINGER_MOTION: Final[int] + SDL_EVENT_FINGER_UP: Final[int] + SDL_EVENT_FIRST: Final[Literal[0]] = 0 + SDL_EVENT_GAMEPAD_ADDED: Final[int] + SDL_EVENT_GAMEPAD_AXIS_MOTION: Final[Literal[1616]] = 1616 + SDL_EVENT_GAMEPAD_BUTTON_DOWN: Final[int] + SDL_EVENT_GAMEPAD_BUTTON_UP: Final[int] + SDL_EVENT_GAMEPAD_REMAPPED: Final[int] + SDL_EVENT_GAMEPAD_REMOVED: Final[int] + SDL_EVENT_GAMEPAD_SENSOR_UPDATE: Final[int] + SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: Final[int] + SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: Final[int] + SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: Final[int] + SDL_EVENT_GAMEPAD_TOUCHPAD_UP: Final[int] + SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: Final[int] + SDL_EVENT_JOYSTICK_ADDED: Final[int] + SDL_EVENT_JOYSTICK_AXIS_MOTION: Final[Literal[1536]] = 1536 + SDL_EVENT_JOYSTICK_BALL_MOTION: Final[int] + SDL_EVENT_JOYSTICK_BATTERY_UPDATED: Final[int] + SDL_EVENT_JOYSTICK_BUTTON_DOWN: Final[int] + SDL_EVENT_JOYSTICK_BUTTON_UP: Final[int] + SDL_EVENT_JOYSTICK_HAT_MOTION: Final[int] + SDL_EVENT_JOYSTICK_REMOVED: Final[int] + SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: Final[int] + SDL_EVENT_KEYBOARD_ADDED: Final[int] + SDL_EVENT_KEYBOARD_REMOVED: Final[int] + SDL_EVENT_KEYMAP_CHANGED: Final[int] + SDL_EVENT_KEY_DOWN: Final[Literal[768]] = 768 + SDL_EVENT_KEY_UP: Final[int] + SDL_EVENT_LAST: Final[Literal[65535]] = 65535 + SDL_EVENT_LOCALE_CHANGED: Final[int] + SDL_EVENT_LOW_MEMORY: Final[int] + SDL_EVENT_MOUSE_ADDED: Final[int] + SDL_EVENT_MOUSE_BUTTON_DOWN: Final[int] + SDL_EVENT_MOUSE_BUTTON_UP: Final[int] + SDL_EVENT_MOUSE_MOTION: Final[Literal[1024]] = 1024 + SDL_EVENT_MOUSE_REMOVED: Final[int] + SDL_EVENT_MOUSE_WHEEL: Final[int] + SDL_EVENT_PEN_AXIS: Final[int] + SDL_EVENT_PEN_BUTTON_DOWN: Final[int] + SDL_EVENT_PEN_BUTTON_UP: Final[int] + SDL_EVENT_PEN_DOWN: Final[int] + SDL_EVENT_PEN_MOTION: Final[int] + SDL_EVENT_PEN_PROXIMITY_IN: Final[Literal[4864]] = 4864 + SDL_EVENT_PEN_PROXIMITY_OUT: Final[int] + SDL_EVENT_PEN_UP: Final[int] + SDL_EVENT_POLL_SENTINEL: Final[Literal[32512]] = 32512 + SDL_EVENT_PRIVATE0: Final[Literal[16384]] = 16384 + SDL_EVENT_PRIVATE1: Final[int] + SDL_EVENT_PRIVATE2: Final[int] + SDL_EVENT_PRIVATE3: Final[int] + SDL_EVENT_QUIT: Final[Literal[256]] = 256 + SDL_EVENT_RENDER_DEVICE_LOST: Final[int] + SDL_EVENT_RENDER_DEVICE_RESET: Final[int] + SDL_EVENT_RENDER_TARGETS_RESET: Final[Literal[8192]] = 8192 + SDL_EVENT_SENSOR_UPDATE: Final[Literal[4608]] = 4608 + SDL_EVENT_SYSTEM_THEME_CHANGED: Final[int] + SDL_EVENT_TERMINATING: Final[int] + SDL_EVENT_TEXT_EDITING: Final[int] + SDL_EVENT_TEXT_EDITING_CANDIDATES: Final[int] + SDL_EVENT_TEXT_INPUT: Final[int] + SDL_EVENT_USER: Final[Literal[32768]] = 32768 + SDL_EVENT_WILL_ENTER_BACKGROUND: Final[int] + SDL_EVENT_WILL_ENTER_FOREGROUND: Final[int] + SDL_EVENT_WINDOW_CLOSE_REQUESTED: Final[int] + SDL_EVENT_WINDOW_DESTROYED: Final[int] + SDL_EVENT_WINDOW_DISPLAY_CHANGED: Final[int] + SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: Final[int] + SDL_EVENT_WINDOW_ENTER_FULLSCREEN: Final[int] + SDL_EVENT_WINDOW_EXPOSED: Final[int] + SDL_EVENT_WINDOW_FIRST: Final[int] + SDL_EVENT_WINDOW_FOCUS_GAINED: Final[int] + SDL_EVENT_WINDOW_FOCUS_LOST: Final[int] + SDL_EVENT_WINDOW_HDR_STATE_CHANGED: Final[int] + SDL_EVENT_WINDOW_HIDDEN: Final[int] + SDL_EVENT_WINDOW_HIT_TEST: Final[int] + SDL_EVENT_WINDOW_ICCPROF_CHANGED: Final[int] + SDL_EVENT_WINDOW_LAST: Final[int] + SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: Final[int] + SDL_EVENT_WINDOW_MAXIMIZED: Final[int] + SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: Final[int] + SDL_EVENT_WINDOW_MINIMIZED: Final[int] + SDL_EVENT_WINDOW_MOUSE_ENTER: Final[int] + SDL_EVENT_WINDOW_MOUSE_LEAVE: Final[int] + SDL_EVENT_WINDOW_MOVED: Final[int] + SDL_EVENT_WINDOW_OCCLUDED: Final[int] + SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: Final[int] + SDL_EVENT_WINDOW_RESIZED: Final[int] + SDL_EVENT_WINDOW_RESTORED: Final[int] + SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: Final[int] + SDL_EVENT_WINDOW_SHOWN: Final[Literal[514]] = 514 + SDL_FILEDIALOG_OPENFILE: Final[int] + SDL_FILEDIALOG_OPENFOLDER: Final[int] + SDL_FILEDIALOG_SAVEFILE: Final[int] + SDL_FLASH_BRIEFLY: Final[int] + SDL_FLASH_CANCEL: Final[int] + SDL_FLASH_UNTIL_FOCUSED: Final[int] + SDL_FLIP_HORIZONTAL: Final[int] + SDL_FLIP_NONE: Final[int] + SDL_FLIP_VERTICAL: Final[int] + SDL_FLOATWORDORDER: Final[int] + SDL_FOLDER_COUNT: Final[int] + SDL_FOLDER_DESKTOP: Final[int] + SDL_FOLDER_DOCUMENTS: Final[int] + SDL_FOLDER_DOWNLOADS: Final[int] + SDL_FOLDER_HOME: Final[int] + SDL_FOLDER_MUSIC: Final[int] + SDL_FOLDER_PICTURES: Final[int] + SDL_FOLDER_PUBLICSHARE: Final[int] + SDL_FOLDER_SAVEDGAMES: Final[int] + SDL_FOLDER_SCREENSHOTS: Final[int] + SDL_FOLDER_TEMPLATES: Final[int] + SDL_FOLDER_VIDEOS: Final[int] + SDL_GAMEPAD_AXIS_COUNT: Final[int] + SDL_GAMEPAD_AXIS_INVALID: Final[Literal[-1]] = -1 + SDL_GAMEPAD_AXIS_LEFTX: Final[int] + SDL_GAMEPAD_AXIS_LEFTY: Final[int] + SDL_GAMEPAD_AXIS_LEFT_TRIGGER: Final[int] + SDL_GAMEPAD_AXIS_RIGHTX: Final[int] + SDL_GAMEPAD_AXIS_RIGHTY: Final[int] + SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: Final[int] + SDL_GAMEPAD_BINDTYPE_AXIS: Final[int] + SDL_GAMEPAD_BINDTYPE_BUTTON: Final[int] + SDL_GAMEPAD_BINDTYPE_HAT: Final[int] + SDL_GAMEPAD_BINDTYPE_NONE: Final[Literal[0]] = 0 + SDL_GAMEPAD_BUTTON_BACK: Final[int] + SDL_GAMEPAD_BUTTON_COUNT: Final[int] + SDL_GAMEPAD_BUTTON_DPAD_DOWN: Final[int] + SDL_GAMEPAD_BUTTON_DPAD_LEFT: Final[int] + SDL_GAMEPAD_BUTTON_DPAD_RIGHT: Final[int] + SDL_GAMEPAD_BUTTON_DPAD_UP: Final[int] + SDL_GAMEPAD_BUTTON_EAST: Final[int] + SDL_GAMEPAD_BUTTON_GUIDE: Final[int] + SDL_GAMEPAD_BUTTON_INVALID: Final[Literal[-1]] = -1 + SDL_GAMEPAD_BUTTON_LABEL_A: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_B: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_CROSS: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_SQUARE: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_X: Final[int] + SDL_GAMEPAD_BUTTON_LABEL_Y: Final[int] + SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: Final[int] + SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: Final[int] + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: Final[int] + SDL_GAMEPAD_BUTTON_LEFT_STICK: Final[int] + SDL_GAMEPAD_BUTTON_MISC1: Final[int] + SDL_GAMEPAD_BUTTON_MISC2: Final[int] + SDL_GAMEPAD_BUTTON_MISC3: Final[int] + SDL_GAMEPAD_BUTTON_MISC4: Final[int] + SDL_GAMEPAD_BUTTON_MISC5: Final[int] + SDL_GAMEPAD_BUTTON_MISC6: Final[int] + SDL_GAMEPAD_BUTTON_NORTH: Final[int] + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: Final[int] + SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: Final[int] + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: Final[int] + SDL_GAMEPAD_BUTTON_RIGHT_STICK: Final[int] + SDL_GAMEPAD_BUTTON_SOUTH: Final[int] + SDL_GAMEPAD_BUTTON_START: Final[int] + SDL_GAMEPAD_BUTTON_TOUCHPAD: Final[int] + SDL_GAMEPAD_BUTTON_WEST: Final[int] + SDL_GAMEPAD_TYPE_COUNT: Final[int] + SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: Final[int] + SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: Final[int] + SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: Final[int] + SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: Final[int] + SDL_GAMEPAD_TYPE_PS3: Final[int] + SDL_GAMEPAD_TYPE_PS4: Final[int] + SDL_GAMEPAD_TYPE_PS5: Final[int] + SDL_GAMEPAD_TYPE_STANDARD: Final[int] + SDL_GAMEPAD_TYPE_UNKNOWN: Final[Literal[0]] = 0 + SDL_GAMEPAD_TYPE_XBOX360: Final[int] + SDL_GAMEPAD_TYPE_XBOXONE: Final[int] + SDL_GETEVENT: Final[int] + SDL_GLOB_CASEINSENSITIVE: Final[int] + SDL_GL_ACCELERATED_VISUAL: Final[int] + SDL_GL_ACCUM_ALPHA_SIZE: Final[int] + SDL_GL_ACCUM_BLUE_SIZE: Final[int] + SDL_GL_ACCUM_GREEN_SIZE: Final[int] + SDL_GL_ACCUM_RED_SIZE: Final[int] + SDL_GL_ALPHA_SIZE: Final[int] + SDL_GL_BLUE_SIZE: Final[int] + SDL_GL_BUFFER_SIZE: Final[int] + SDL_GL_CONTEXT_DEBUG_FLAG: Final[int] + SDL_GL_CONTEXT_FLAGS: Final[int] + SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG: Final[int] + SDL_GL_CONTEXT_MAJOR_VERSION: Final[int] + SDL_GL_CONTEXT_MINOR_VERSION: Final[int] + SDL_GL_CONTEXT_NO_ERROR: Final[int] + SDL_GL_CONTEXT_PROFILE_COMPATIBILITY: Final[int] + SDL_GL_CONTEXT_PROFILE_CORE: Final[int] + SDL_GL_CONTEXT_PROFILE_ES: Final[int] + SDL_GL_CONTEXT_PROFILE_MASK: Final[int] + SDL_GL_CONTEXT_RELEASE_BEHAVIOR: Final[int] + SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH: Final[int] + SDL_GL_CONTEXT_RELEASE_BEHAVIOR_NONE: Final[int] + SDL_GL_CONTEXT_RESET_ISOLATION_FLAG: Final[int] + SDL_GL_CONTEXT_RESET_LOSE_CONTEXT: Final[int] + SDL_GL_CONTEXT_RESET_NOTIFICATION: Final[int] + SDL_GL_CONTEXT_RESET_NO_NOTIFICATION: Final[int] + SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG: Final[int] + SDL_GL_DEPTH_SIZE: Final[int] + SDL_GL_DOUBLEBUFFER: Final[int] + SDL_GL_EGL_PLATFORM: Final[int] + SDL_GL_FLOATBUFFERS: Final[int] + SDL_GL_FRAMEBUFFER_SRGB_CAPABLE: Final[int] + SDL_GL_GREEN_SIZE: Final[int] + SDL_GL_MULTISAMPLEBUFFERS: Final[int] + SDL_GL_MULTISAMPLESAMPLES: Final[int] + SDL_GL_RED_SIZE: Final[int] + SDL_GL_RETAINED_BACKING: Final[int] + SDL_GL_SHARE_WITH_CURRENT_CONTEXT: Final[int] + SDL_GL_STENCIL_SIZE: Final[int] + SDL_GL_STEREO: Final[int] + SDL_GPU_BLENDFACTOR_CONSTANT_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_DST_ALPHA: Final[int] + SDL_GPU_BLENDFACTOR_DST_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_INVALID: Final[int] + SDL_GPU_BLENDFACTOR_ONE: Final[int] + SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA: Final[int] + SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: Final[int] + SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_SRC_ALPHA: Final[int] + SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE: Final[int] + SDL_GPU_BLENDFACTOR_SRC_COLOR: Final[int] + SDL_GPU_BLENDFACTOR_ZERO: Final[int] + SDL_GPU_BLENDOP_ADD: Final[int] + SDL_GPU_BLENDOP_INVALID: Final[int] + SDL_GPU_BLENDOP_MAX: Final[int] + SDL_GPU_BLENDOP_MIN: Final[int] + SDL_GPU_BLENDOP_REVERSE_SUBTRACT: Final[int] + SDL_GPU_BLENDOP_SUBTRACT: Final[int] + SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ: Final[int] + SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE: Final[int] + SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ: Final[int] + SDL_GPU_BUFFERUSAGE_INDEX: Final[int] + SDL_GPU_BUFFERUSAGE_INDIRECT: Final[int] + SDL_GPU_BUFFERUSAGE_VERTEX: Final[int] + SDL_GPU_COLORCOMPONENT_A: Final[int] + SDL_GPU_COLORCOMPONENT_B: Final[int] + SDL_GPU_COLORCOMPONENT_G: Final[int] + SDL_GPU_COLORCOMPONENT_R: Final[int] + SDL_GPU_COMPAREOP_ALWAYS: Final[int] + SDL_GPU_COMPAREOP_EQUAL: Final[int] + SDL_GPU_COMPAREOP_GREATER: Final[int] + SDL_GPU_COMPAREOP_GREATER_OR_EQUAL: Final[int] + SDL_GPU_COMPAREOP_INVALID: Final[int] + SDL_GPU_COMPAREOP_LESS: Final[int] + SDL_GPU_COMPAREOP_LESS_OR_EQUAL: Final[int] + SDL_GPU_COMPAREOP_NEVER: Final[int] + SDL_GPU_COMPAREOP_NOT_EQUAL: Final[int] + SDL_GPU_CUBEMAPFACE_NEGATIVEX: Final[int] + SDL_GPU_CUBEMAPFACE_NEGATIVEY: Final[int] + SDL_GPU_CUBEMAPFACE_NEGATIVEZ: Final[int] + SDL_GPU_CUBEMAPFACE_POSITIVEX: Final[int] + SDL_GPU_CUBEMAPFACE_POSITIVEY: Final[int] + SDL_GPU_CUBEMAPFACE_POSITIVEZ: Final[int] + SDL_GPU_CULLMODE_BACK: Final[int] + SDL_GPU_CULLMODE_FRONT: Final[int] + SDL_GPU_CULLMODE_NONE: Final[int] + SDL_GPU_FILLMODE_FILL: Final[int] + SDL_GPU_FILLMODE_LINE: Final[int] + SDL_GPU_FILTER_LINEAR: Final[int] + SDL_GPU_FILTER_NEAREST: Final[int] + SDL_GPU_FRONTFACE_CLOCKWISE: Final[int] + SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE: Final[int] + SDL_GPU_INDEXELEMENTSIZE_16BIT: Final[int] + SDL_GPU_INDEXELEMENTSIZE_32BIT: Final[int] + SDL_GPU_LOADOP_CLEAR: Final[int] + SDL_GPU_LOADOP_DONT_CARE: Final[int] + SDL_GPU_LOADOP_LOAD: Final[int] + SDL_GPU_PRESENTMODE_IMMEDIATE: Final[int] + SDL_GPU_PRESENTMODE_MAILBOX: Final[int] + SDL_GPU_PRESENTMODE_VSYNC: Final[int] + SDL_GPU_PRIMITIVETYPE_LINELIST: Final[int] + SDL_GPU_PRIMITIVETYPE_LINESTRIP: Final[int] + SDL_GPU_PRIMITIVETYPE_POINTLIST: Final[int] + SDL_GPU_PRIMITIVETYPE_TRIANGLELIST: Final[int] + SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP: Final[int] + SDL_GPU_SAMPLECOUNT_1: Final[int] + SDL_GPU_SAMPLECOUNT_2: Final[int] + SDL_GPU_SAMPLECOUNT_4: Final[int] + SDL_GPU_SAMPLECOUNT_8: Final[int] + SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE: Final[int] + SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT: Final[int] + SDL_GPU_SAMPLERADDRESSMODE_REPEAT: Final[int] + SDL_GPU_SAMPLERMIPMAPMODE_LINEAR: Final[int] + SDL_GPU_SAMPLERMIPMAPMODE_NEAREST: Final[int] + SDL_GPU_SHADERFORMAT_DXBC: Final[int] + SDL_GPU_SHADERFORMAT_DXIL: Final[int] + SDL_GPU_SHADERFORMAT_INVALID: Final[int] + SDL_GPU_SHADERFORMAT_METALLIB: Final[int] + SDL_GPU_SHADERFORMAT_MSL: Final[int] + SDL_GPU_SHADERFORMAT_PRIVATE: Final[int] + SDL_GPU_SHADERFORMAT_SPIRV: Final[int] + SDL_GPU_SHADERSTAGE_FRAGMENT: Final[int] + SDL_GPU_SHADERSTAGE_VERTEX: Final[int] + SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP: Final[int] + SDL_GPU_STENCILOP_DECREMENT_AND_WRAP: Final[int] + SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP: Final[int] + SDL_GPU_STENCILOP_INCREMENT_AND_WRAP: Final[int] + SDL_GPU_STENCILOP_INVALID: Final[int] + SDL_GPU_STENCILOP_INVERT: Final[int] + SDL_GPU_STENCILOP_KEEP: Final[int] + SDL_GPU_STENCILOP_REPLACE: Final[int] + SDL_GPU_STENCILOP_ZERO: Final[int] + SDL_GPU_STOREOP_DONT_CARE: Final[int] + SDL_GPU_STOREOP_RESOLVE: Final[int] + SDL_GPU_STOREOP_RESOLVE_AND_STORE: Final[int] + SDL_GPU_STOREOP_STORE: Final[int] + SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084: Final[int] + SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR: Final[int] + SDL_GPU_SWAPCHAINCOMPOSITION_SDR: Final[int] + SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR: Final[int] + SDL_GPU_TEXTUREFORMAT_A8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_D16_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_D24_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_D32_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_INVALID: Final[int] + SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16G16_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R16_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R16_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R16_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32G32_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32_FLOAT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R32_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8G8_UNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R8_INT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8_SNORM: Final[int] + SDL_GPU_TEXTUREFORMAT_R8_UINT: Final[int] + SDL_GPU_TEXTUREFORMAT_R8_UNORM: Final[int] + SDL_GPU_TEXTURETYPE_2D: Final[int] + SDL_GPU_TEXTURETYPE_2D_ARRAY: Final[int] + SDL_GPU_TEXTURETYPE_3D: Final[int] + SDL_GPU_TEXTURETYPE_CUBE: Final[int] + SDL_GPU_TEXTURETYPE_CUBE_ARRAY: Final[int] + SDL_GPU_TEXTUREUSAGE_COLOR_TARGET: Final[int] + SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ: Final[int] + SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE: Final[int] + SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE: Final[int] + SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET: Final[int] + SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ: Final[int] + SDL_GPU_TEXTUREUSAGE_SAMPLER: Final[int] + SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD: Final[int] + SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_BYTE2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_BYTE4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_FLOAT: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_HALF2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_HALF4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_INT2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_INT3: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_INT4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_INT: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_INVALID: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_SHORT2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_SHORT4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UINT2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UINT3: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UINT4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_UINT: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_USHORT2: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_USHORT4: Final[int] + SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM: Final[int] + SDL_GPU_VERTEXINPUTRATE_INSTANCE: Final[int] + SDL_GPU_VERTEXINPUTRATE_VERTEX: Final[int] + SDL_HAPTIC_AUTOCENTER: Final[int] + SDL_HAPTIC_CARTESIAN: Final[int] + SDL_HAPTIC_CONSTANT: Final[int] + SDL_HAPTIC_CUSTOM: Final[int] + SDL_HAPTIC_DAMPER: Final[int] + SDL_HAPTIC_FRICTION: Final[int] + SDL_HAPTIC_GAIN: Final[int] + SDL_HAPTIC_INERTIA: Final[int] + SDL_HAPTIC_INFINITY: Final[int] + SDL_HAPTIC_LEFTRIGHT: Final[int] + SDL_HAPTIC_PAUSE: Final[int] + SDL_HAPTIC_POLAR: Final[int] + SDL_HAPTIC_RAMP: Final[int] + SDL_HAPTIC_RESERVED1: Final[int] + SDL_HAPTIC_RESERVED2: Final[int] + SDL_HAPTIC_RESERVED3: Final[int] + SDL_HAPTIC_SAWTOOTHDOWN: Final[int] + SDL_HAPTIC_SAWTOOTHUP: Final[int] + SDL_HAPTIC_SINE: Final[int] + SDL_HAPTIC_SPHERICAL: Final[int] + SDL_HAPTIC_SPRING: Final[int] + SDL_HAPTIC_SQUARE: Final[int] + SDL_HAPTIC_STATUS: Final[int] + SDL_HAPTIC_STEERING_AXIS: Final[int] + SDL_HAPTIC_TRIANGLE: Final[int] + SDL_HAT_CENTERED: Final[int] + SDL_HAT_DOWN: Final[int] + SDL_HAT_LEFT: Final[int] + SDL_HAT_LEFTDOWN: Final[int] + SDL_HAT_LEFTUP: Final[int] + SDL_HAT_RIGHT: Final[int] + SDL_HAT_RIGHTDOWN: Final[int] + SDL_HAT_RIGHTUP: Final[int] + SDL_HAT_UP: Final[int] + SDL_HID_API_BUS_BLUETOOTH: Final[Literal[2]] = 2 + SDL_HID_API_BUS_I2C: Final[Literal[3]] = 3 + SDL_HID_API_BUS_SPI: Final[Literal[4]] = 4 + SDL_HID_API_BUS_UNKNOWN: Final[Literal[0]] = 0 + SDL_HID_API_BUS_USB: Final[Literal[1]] = 1 + SDL_HINT_DEFAULT: Final[int] + SDL_HINT_NORMAL: Final[int] + SDL_HINT_OVERRIDE: Final[int] + SDL_HITTEST_DRAGGABLE: Final[int] + SDL_HITTEST_NORMAL: Final[int] + SDL_HITTEST_RESIZE_BOTTOM: Final[int] + SDL_HITTEST_RESIZE_BOTTOMLEFT: Final[int] + SDL_HITTEST_RESIZE_BOTTOMRIGHT: Final[int] + SDL_HITTEST_RESIZE_LEFT: Final[int] + SDL_HITTEST_RESIZE_RIGHT: Final[int] + SDL_HITTEST_RESIZE_TOP: Final[int] + SDL_HITTEST_RESIZE_TOPLEFT: Final[int] + SDL_HITTEST_RESIZE_TOPRIGHT: Final[int] + SDL_ICONV_E2BIG: Final[int] + SDL_ICONV_EILSEQ: Final[int] + SDL_ICONV_EINVAL: Final[int] + SDL_ICONV_ERROR: Final[int] + SDL_INIT_AUDIO: Final[int] + SDL_INIT_CAMERA: Final[int] + SDL_INIT_EVENTS: Final[int] + SDL_INIT_GAMEPAD: Final[int] + SDL_INIT_HAPTIC: Final[int] + SDL_INIT_JOYSTICK: Final[int] + SDL_INIT_SENSOR: Final[int] + SDL_INIT_STATUS_INITIALIZED: Final[int] + SDL_INIT_STATUS_INITIALIZING: Final[int] + SDL_INIT_STATUS_UNINITIALIZED: Final[int] + SDL_INIT_STATUS_UNINITIALIZING: Final[int] + SDL_INIT_VIDEO: Final[int] + SDL_INVALID_UNICODE_CODEPOINT: Final[int] + SDL_IO_SEEK_CUR: Final[int] + SDL_IO_SEEK_END: Final[int] + SDL_IO_SEEK_SET: Final[int] + SDL_IO_STATUS_EOF: Final[int] + SDL_IO_STATUS_ERROR: Final[int] + SDL_IO_STATUS_NOT_READY: Final[int] + SDL_IO_STATUS_READONLY: Final[int] + SDL_IO_STATUS_READY: Final[int] + SDL_IO_STATUS_WRITEONLY: Final[int] + SDL_JOYSTICK_AXIS_MAX: Final[int] + SDL_JOYSTICK_AXIS_MIN: Final[int] + SDL_JOYSTICK_CONNECTION_INVALID: Final[Literal[-1]] = -1 + SDL_JOYSTICK_CONNECTION_UNKNOWN: Final[int] + SDL_JOYSTICK_CONNECTION_WIRED: Final[int] + SDL_JOYSTICK_CONNECTION_WIRELESS: Final[int] + SDL_JOYSTICK_TYPE_ARCADE_PAD: Final[int] + SDL_JOYSTICK_TYPE_ARCADE_STICK: Final[int] + SDL_JOYSTICK_TYPE_COUNT: Final[int] + SDL_JOYSTICK_TYPE_DANCE_PAD: Final[int] + SDL_JOYSTICK_TYPE_DRUM_KIT: Final[int] + SDL_JOYSTICK_TYPE_FLIGHT_STICK: Final[int] + SDL_JOYSTICK_TYPE_GAMEPAD: Final[int] + SDL_JOYSTICK_TYPE_GUITAR: Final[int] + SDL_JOYSTICK_TYPE_THROTTLE: Final[int] + SDL_JOYSTICK_TYPE_UNKNOWN: Final[int] + SDL_JOYSTICK_TYPE_WHEEL: Final[int] + SDL_KMOD_ALT: Final[int] + SDL_KMOD_CAPS: Final[int] + SDL_KMOD_CTRL: Final[int] + SDL_KMOD_GUI: Final[int] + SDL_KMOD_LALT: Final[int] + SDL_KMOD_LCTRL: Final[int] + SDL_KMOD_LEVEL5: Final[int] + SDL_KMOD_LGUI: Final[int] + SDL_KMOD_LSHIFT: Final[int] + SDL_KMOD_MODE: Final[int] + SDL_KMOD_NONE: Final[int] + SDL_KMOD_NUM: Final[int] + SDL_KMOD_RALT: Final[int] + SDL_KMOD_RCTRL: Final[int] + SDL_KMOD_RGUI: Final[int] + SDL_KMOD_RSHIFT: Final[int] + SDL_KMOD_SCROLL: Final[int] + SDL_KMOD_SHIFT: Final[int] + SDL_LIL_ENDIAN: Final[int] + SDL_LOGICAL_PRESENTATION_DISABLED: Final[int] + SDL_LOGICAL_PRESENTATION_INTEGER_SCALE: Final[int] + SDL_LOGICAL_PRESENTATION_LETTERBOX: Final[int] + SDL_LOGICAL_PRESENTATION_OVERSCAN: Final[int] + SDL_LOGICAL_PRESENTATION_STRETCH: Final[int] + SDL_LOG_CATEGORY_APPLICATION: Final[int] + SDL_LOG_CATEGORY_ASSERT: Final[int] + SDL_LOG_CATEGORY_AUDIO: Final[int] + SDL_LOG_CATEGORY_CUSTOM: Final[int] + SDL_LOG_CATEGORY_ERROR: Final[int] + SDL_LOG_CATEGORY_GPU: Final[int] + SDL_LOG_CATEGORY_INPUT: Final[int] + SDL_LOG_CATEGORY_RENDER: Final[int] + SDL_LOG_CATEGORY_RESERVED10: Final[int] + SDL_LOG_CATEGORY_RESERVED2: Final[int] + SDL_LOG_CATEGORY_RESERVED3: Final[int] + SDL_LOG_CATEGORY_RESERVED4: Final[int] + SDL_LOG_CATEGORY_RESERVED5: Final[int] + SDL_LOG_CATEGORY_RESERVED6: Final[int] + SDL_LOG_CATEGORY_RESERVED7: Final[int] + SDL_LOG_CATEGORY_RESERVED8: Final[int] + SDL_LOG_CATEGORY_RESERVED9: Final[int] + SDL_LOG_CATEGORY_SYSTEM: Final[int] + SDL_LOG_CATEGORY_TEST: Final[int] + SDL_LOG_CATEGORY_VIDEO: Final[int] + SDL_LOG_PRIORITY_COUNT: Final[int] + SDL_LOG_PRIORITY_CRITICAL: Final[int] + SDL_LOG_PRIORITY_DEBUG: Final[int] + SDL_LOG_PRIORITY_ERROR: Final[int] + SDL_LOG_PRIORITY_INFO: Final[int] + SDL_LOG_PRIORITY_INVALID: Final[int] + SDL_LOG_PRIORITY_TRACE: Final[int] + SDL_LOG_PRIORITY_VERBOSE: Final[int] + SDL_LOG_PRIORITY_WARN: Final[int] + SDL_MAJOR_VERSION: Final[int] + SDL_MATRIX_COEFFICIENTS_BT2020_CL: Final[Literal[10]] = 10 + SDL_MATRIX_COEFFICIENTS_BT2020_NCL: Final[Literal[9]] = 9 + SDL_MATRIX_COEFFICIENTS_BT470BG: Final[Literal[5]] = 5 + SDL_MATRIX_COEFFICIENTS_BT601: Final[Literal[6]] = 6 + SDL_MATRIX_COEFFICIENTS_BT709: Final[Literal[1]] = 1 + SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL: Final[Literal[13]] = 13 + SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL: Final[Literal[12]] = 12 + SDL_MATRIX_COEFFICIENTS_CUSTOM: Final[Literal[31]] = 31 + SDL_MATRIX_COEFFICIENTS_FCC: Final[Literal[4]] = 4 + SDL_MATRIX_COEFFICIENTS_ICTCP: Final[Literal[14]] = 14 + SDL_MATRIX_COEFFICIENTS_IDENTITY: Final[Literal[0]] = 0 + SDL_MATRIX_COEFFICIENTS_SMPTE2085: Final[Literal[11]] = 11 + SDL_MATRIX_COEFFICIENTS_SMPTE240: Final[Literal[7]] = 7 + SDL_MATRIX_COEFFICIENTS_UNSPECIFIED: Final[Literal[2]] = 2 + SDL_MATRIX_COEFFICIENTS_YCGCO: Final[Literal[8]] = 8 + SDL_MAX_SINT16: Final[int] + SDL_MAX_SINT32: Final[int] + SDL_MAX_SINT64: Final[int] + SDL_MAX_SINT8: Final[int] + SDL_MAX_TIME: Final[int] + SDL_MAX_UINT16: Final[int] + SDL_MAX_UINT32: Final[int] + SDL_MAX_UINT64: Final[int] + SDL_MAX_UINT8: Final[int] + SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT: Final[int] + SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT: Final[int] + SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT: Final[int] + SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT: Final[int] + SDL_MESSAGEBOX_COLOR_BACKGROUND: Final[int] + SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND: Final[int] + SDL_MESSAGEBOX_COLOR_BUTTON_BORDER: Final[int] + SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED: Final[int] + SDL_MESSAGEBOX_COLOR_COUNT: Final[int] + SDL_MESSAGEBOX_COLOR_TEXT: Final[int] + SDL_MESSAGEBOX_ERROR: Final[int] + SDL_MESSAGEBOX_INFORMATION: Final[int] + SDL_MESSAGEBOX_WARNING: Final[int] + SDL_MICRO_VERSION: Final[int] + SDL_MINOR_VERSION: Final[int] + SDL_MIN_SINT16: Final[int] + SDL_MIN_SINT32: Final[int] + SDL_MIN_SINT64: Final[int] + SDL_MIN_SINT8: Final[int] + SDL_MIN_TIME: Final[int] + SDL_MIN_UINT16: Final[int] + SDL_MIN_UINT32: Final[int] + SDL_MIN_UINT64: Final[int] + SDL_MIN_UINT8: Final[int] + SDL_MOUSEWHEEL_FLIPPED: Final[int] + SDL_MOUSEWHEEL_NORMAL: Final[int] + SDL_MOUSE_TOUCHID: Final[int] + SDL_MS_PER_SECOND: Final[int] + SDL_NS_PER_MS: Final[int] + SDL_NS_PER_SECOND: Final[int] + SDL_NS_PER_US: Final[int] + SDL_NULL_WHILE_LOOP_CONDITION: Final[int] + SDL_ORIENTATION_LANDSCAPE: Final[int] + SDL_ORIENTATION_LANDSCAPE_FLIPPED: Final[int] + SDL_ORIENTATION_PORTRAIT: Final[int] + SDL_ORIENTATION_PORTRAIT_FLIPPED: Final[int] + SDL_ORIENTATION_UNKNOWN: Final[int] + SDL_PACKEDLAYOUT_1010102: Final[int] + SDL_PACKEDLAYOUT_1555: Final[int] + SDL_PACKEDLAYOUT_2101010: Final[int] + SDL_PACKEDLAYOUT_332: Final[int] + SDL_PACKEDLAYOUT_4444: Final[int] + SDL_PACKEDLAYOUT_5551: Final[int] + SDL_PACKEDLAYOUT_565: Final[int] + SDL_PACKEDLAYOUT_8888: Final[int] + SDL_PACKEDLAYOUT_NONE: Final[int] + SDL_PACKEDORDER_ABGR: Final[int] + SDL_PACKEDORDER_ARGB: Final[int] + SDL_PACKEDORDER_BGRA: Final[int] + SDL_PACKEDORDER_BGRX: Final[int] + SDL_PACKEDORDER_NONE: Final[int] + SDL_PACKEDORDER_RGBA: Final[int] + SDL_PACKEDORDER_RGBX: Final[int] + SDL_PACKEDORDER_XBGR: Final[int] + SDL_PACKEDORDER_XRGB: Final[int] + SDL_PATHTYPE_DIRECTORY: Final[int] + SDL_PATHTYPE_FILE: Final[int] + SDL_PATHTYPE_NONE: Final[int] + SDL_PATHTYPE_OTHER: Final[int] + SDL_PEEKEVENT: Final[int] + SDL_PEN_AXIS_COUNT: Final[int] + SDL_PEN_AXIS_DISTANCE: Final[int] + SDL_PEN_AXIS_PRESSURE: Final[int] + SDL_PEN_AXIS_ROTATION: Final[int] + SDL_PEN_AXIS_SLIDER: Final[int] + SDL_PEN_AXIS_TANGENTIAL_PRESSURE: Final[int] + SDL_PEN_AXIS_XTILT: Final[int] + SDL_PEN_AXIS_YTILT: Final[int] + SDL_PEN_INPUT_BUTTON_1: Final[int] + SDL_PEN_INPUT_BUTTON_2: Final[int] + SDL_PEN_INPUT_BUTTON_3: Final[int] + SDL_PEN_INPUT_BUTTON_4: Final[int] + SDL_PEN_INPUT_BUTTON_5: Final[int] + SDL_PEN_INPUT_DOWN: Final[int] + SDL_PEN_INPUT_ERASER_TIP: Final[int] + SDL_PEN_MOUSEID: Final[int] + SDL_PEN_TOUCHID: Final[int] + SDL_PIXELFORMAT_ABGR128_FLOAT: int + SDL_PIXELFORMAT_ABGR1555: int + SDL_PIXELFORMAT_ABGR2101010: int + SDL_PIXELFORMAT_ABGR32: int + SDL_PIXELFORMAT_ABGR4444: int + SDL_PIXELFORMAT_ABGR64: int + SDL_PIXELFORMAT_ABGR64_FLOAT: int + SDL_PIXELFORMAT_ABGR8888: int + SDL_PIXELFORMAT_ARGB128_FLOAT: int + SDL_PIXELFORMAT_ARGB1555: int + SDL_PIXELFORMAT_ARGB2101010: int + SDL_PIXELFORMAT_ARGB32: int + SDL_PIXELFORMAT_ARGB4444: int + SDL_PIXELFORMAT_ARGB64: int + SDL_PIXELFORMAT_ARGB64_FLOAT: int + SDL_PIXELFORMAT_ARGB8888: int + SDL_PIXELFORMAT_BGR24: int + SDL_PIXELFORMAT_BGR48: int + SDL_PIXELFORMAT_BGR48_FLOAT: int + SDL_PIXELFORMAT_BGR565: int + SDL_PIXELFORMAT_BGR96_FLOAT: int + SDL_PIXELFORMAT_BGRA128_FLOAT: int + SDL_PIXELFORMAT_BGRA32: int + SDL_PIXELFORMAT_BGRA4444: int + SDL_PIXELFORMAT_BGRA5551: int + SDL_PIXELFORMAT_BGRA64: int + SDL_PIXELFORMAT_BGRA64_FLOAT: int + SDL_PIXELFORMAT_BGRA8888: int + SDL_PIXELFORMAT_BGRX32: int + SDL_PIXELFORMAT_BGRX8888: int + SDL_PIXELFORMAT_EXTERNAL_OES: int + SDL_PIXELFORMAT_INDEX1LSB: int + SDL_PIXELFORMAT_INDEX1MSB: int + SDL_PIXELFORMAT_INDEX2LSB: int + SDL_PIXELFORMAT_INDEX2MSB: int + SDL_PIXELFORMAT_INDEX4LSB: int + SDL_PIXELFORMAT_INDEX4MSB: int + SDL_PIXELFORMAT_INDEX8: int + SDL_PIXELFORMAT_IYUV: int + SDL_PIXELFORMAT_MJPG: int + SDL_PIXELFORMAT_NV12: int + SDL_PIXELFORMAT_NV21: int + SDL_PIXELFORMAT_P010: int + SDL_PIXELFORMAT_RGB24: int + SDL_PIXELFORMAT_RGB332: int + SDL_PIXELFORMAT_RGB48: int + SDL_PIXELFORMAT_RGB48_FLOAT: int + SDL_PIXELFORMAT_RGB565: int + SDL_PIXELFORMAT_RGB96_FLOAT: int + SDL_PIXELFORMAT_RGBA128_FLOAT: int + SDL_PIXELFORMAT_RGBA32: int + SDL_PIXELFORMAT_RGBA4444: int + SDL_PIXELFORMAT_RGBA5551: int + SDL_PIXELFORMAT_RGBA64: int + SDL_PIXELFORMAT_RGBA64_FLOAT: int + SDL_PIXELFORMAT_RGBA8888: int + SDL_PIXELFORMAT_RGBX32: int + SDL_PIXELFORMAT_RGBX8888: int + SDL_PIXELFORMAT_UNKNOWN: int + SDL_PIXELFORMAT_UYVY: int + SDL_PIXELFORMAT_XBGR1555: int + SDL_PIXELFORMAT_XBGR2101010: int + SDL_PIXELFORMAT_XBGR32: int + SDL_PIXELFORMAT_XBGR4444: int + SDL_PIXELFORMAT_XBGR8888: int + SDL_PIXELFORMAT_XRGB1555: int + SDL_PIXELFORMAT_XRGB2101010: int + SDL_PIXELFORMAT_XRGB32: int + SDL_PIXELFORMAT_XRGB4444: int + SDL_PIXELFORMAT_XRGB8888: int + SDL_PIXELFORMAT_YUY2: int + SDL_PIXELFORMAT_YV12: int + SDL_PIXELFORMAT_YVYU: int + SDL_PIXELTYPE_ARRAYF16: Final[int] + SDL_PIXELTYPE_ARRAYF32: Final[int] + SDL_PIXELTYPE_ARRAYU16: Final[int] + SDL_PIXELTYPE_ARRAYU32: Final[int] + SDL_PIXELTYPE_ARRAYU8: Final[int] + SDL_PIXELTYPE_INDEX1: Final[int] + SDL_PIXELTYPE_INDEX2: Final[int] + SDL_PIXELTYPE_INDEX4: Final[int] + SDL_PIXELTYPE_INDEX8: Final[int] + SDL_PIXELTYPE_PACKED16: Final[int] + SDL_PIXELTYPE_PACKED32: Final[int] + SDL_PIXELTYPE_PACKED8: Final[int] + SDL_PIXELTYPE_UNKNOWN: Final[int] + SDL_POWERSTATE_CHARGED: Final[int] + SDL_POWERSTATE_CHARGING: Final[int] + SDL_POWERSTATE_ERROR: Final[Literal[-1]] = -1 + SDL_POWERSTATE_NO_BATTERY: Final[int] + SDL_POWERSTATE_ON_BATTERY: Final[int] + SDL_POWERSTATE_UNKNOWN: Final[int] + SDL_PROCESS_STDIO_APP: Final[int] + SDL_PROCESS_STDIO_INHERITED: Final[int] + SDL_PROCESS_STDIO_NULL: Final[int] + SDL_PROCESS_STDIO_REDIRECT: Final[int] + SDL_PROPERTY_TYPE_BOOLEAN: Final[int] + SDL_PROPERTY_TYPE_FLOAT: Final[int] + SDL_PROPERTY_TYPE_INVALID: Final[int] + SDL_PROPERTY_TYPE_NUMBER: Final[int] + SDL_PROPERTY_TYPE_POINTER: Final[int] + SDL_PROPERTY_TYPE_STRING: Final[int] + SDL_RENDERER_VSYNC_ADAPTIVE: Final[int] + SDL_RENDERER_VSYNC_DISABLED: Final[int] + SDL_SANDBOX_FLATPAK: Final[int] + SDL_SANDBOX_MACOS: Final[int] + SDL_SANDBOX_NONE: Final[Literal[0]] = 0 + SDL_SANDBOX_SNAP: Final[int] + SDL_SANDBOX_UNKNOWN_CONTAINER: Final[int] + SDL_SCALEMODE_INVALID: Final[Literal[-1]] = -1 + SDL_SCALEMODE_LINEAR: Final[int] + SDL_SCALEMODE_NEAREST: Final[int] + SDL_SCANCODE_0: Final[Literal[39]] = 39 + SDL_SCANCODE_1: Final[Literal[30]] = 30 + SDL_SCANCODE_2: Final[Literal[31]] = 31 + SDL_SCANCODE_3: Final[Literal[32]] = 32 + SDL_SCANCODE_4: Final[Literal[33]] = 33 + SDL_SCANCODE_5: Final[Literal[34]] = 34 + SDL_SCANCODE_6: Final[Literal[35]] = 35 + SDL_SCANCODE_7: Final[Literal[36]] = 36 + SDL_SCANCODE_8: Final[Literal[37]] = 37 + SDL_SCANCODE_9: Final[Literal[38]] = 38 + SDL_SCANCODE_A: Final[Literal[4]] = 4 + SDL_SCANCODE_AC_BACK: Final[Literal[282]] = 282 + SDL_SCANCODE_AC_BOOKMARKS: Final[Literal[286]] = 286 + SDL_SCANCODE_AC_CLOSE: Final[Literal[275]] = 275 + SDL_SCANCODE_AC_EXIT: Final[Literal[276]] = 276 + SDL_SCANCODE_AC_FORWARD: Final[Literal[283]] = 283 + SDL_SCANCODE_AC_HOME: Final[Literal[281]] = 281 + SDL_SCANCODE_AC_NEW: Final[Literal[273]] = 273 + SDL_SCANCODE_AC_OPEN: Final[Literal[274]] = 274 + SDL_SCANCODE_AC_PRINT: Final[Literal[278]] = 278 + SDL_SCANCODE_AC_PROPERTIES: Final[Literal[279]] = 279 + SDL_SCANCODE_AC_REFRESH: Final[Literal[285]] = 285 + SDL_SCANCODE_AC_SAVE: Final[Literal[277]] = 277 + SDL_SCANCODE_AC_SEARCH: Final[Literal[280]] = 280 + SDL_SCANCODE_AC_STOP: Final[Literal[284]] = 284 + SDL_SCANCODE_AGAIN: Final[Literal[121]] = 121 + SDL_SCANCODE_ALTERASE: Final[Literal[153]] = 153 + SDL_SCANCODE_APOSTROPHE: Final[Literal[52]] = 52 + SDL_SCANCODE_APPLICATION: Final[Literal[101]] = 101 + SDL_SCANCODE_B: Final[Literal[5]] = 5 + SDL_SCANCODE_BACKSLASH: Final[Literal[49]] = 49 + SDL_SCANCODE_BACKSPACE: Final[Literal[42]] = 42 + SDL_SCANCODE_C: Final[Literal[6]] = 6 + SDL_SCANCODE_CALL: Final[Literal[289]] = 289 + SDL_SCANCODE_CANCEL: Final[Literal[155]] = 155 + SDL_SCANCODE_CAPSLOCK: Final[Literal[57]] = 57 + SDL_SCANCODE_CHANNEL_DECREMENT: Final[Literal[261]] = 261 + SDL_SCANCODE_CHANNEL_INCREMENT: Final[Literal[260]] = 260 + SDL_SCANCODE_CLEAR: Final[Literal[156]] = 156 + SDL_SCANCODE_CLEARAGAIN: Final[Literal[162]] = 162 + SDL_SCANCODE_COMMA: Final[Literal[54]] = 54 + SDL_SCANCODE_COPY: Final[Literal[124]] = 124 + SDL_SCANCODE_COUNT: Final[Literal[512]] = 512 + SDL_SCANCODE_CRSEL: Final[Literal[163]] = 163 + SDL_SCANCODE_CURRENCYSUBUNIT: Final[Literal[181]] = 181 + SDL_SCANCODE_CURRENCYUNIT: Final[Literal[180]] = 180 + SDL_SCANCODE_CUT: Final[Literal[123]] = 123 + SDL_SCANCODE_D: Final[Literal[7]] = 7 + SDL_SCANCODE_DECIMALSEPARATOR: Final[Literal[179]] = 179 + SDL_SCANCODE_DELETE: Final[Literal[76]] = 76 + SDL_SCANCODE_DOWN: Final[Literal[81]] = 81 + SDL_SCANCODE_E: Final[Literal[8]] = 8 + SDL_SCANCODE_END: Final[Literal[77]] = 77 + SDL_SCANCODE_ENDCALL: Final[Literal[290]] = 290 + SDL_SCANCODE_EQUALS: Final[Literal[46]] = 46 + SDL_SCANCODE_ESCAPE: Final[Literal[41]] = 41 + SDL_SCANCODE_EXECUTE: Final[Literal[116]] = 116 + SDL_SCANCODE_EXSEL: Final[Literal[164]] = 164 + SDL_SCANCODE_F10: Final[Literal[67]] = 67 + SDL_SCANCODE_F11: Final[Literal[68]] = 68 + SDL_SCANCODE_F12: Final[Literal[69]] = 69 + SDL_SCANCODE_F13: Final[Literal[104]] = 104 + SDL_SCANCODE_F14: Final[Literal[105]] = 105 + SDL_SCANCODE_F15: Final[Literal[106]] = 106 + SDL_SCANCODE_F16: Final[Literal[107]] = 107 + SDL_SCANCODE_F17: Final[Literal[108]] = 108 + SDL_SCANCODE_F18: Final[Literal[109]] = 109 + SDL_SCANCODE_F19: Final[Literal[110]] = 110 + SDL_SCANCODE_F1: Final[Literal[58]] = 58 + SDL_SCANCODE_F20: Final[Literal[111]] = 111 + SDL_SCANCODE_F21: Final[Literal[112]] = 112 + SDL_SCANCODE_F22: Final[Literal[113]] = 113 + SDL_SCANCODE_F23: Final[Literal[114]] = 114 + SDL_SCANCODE_F24: Final[Literal[115]] = 115 + SDL_SCANCODE_F2: Final[Literal[59]] = 59 + SDL_SCANCODE_F3: Final[Literal[60]] = 60 + SDL_SCANCODE_F4: Final[Literal[61]] = 61 + SDL_SCANCODE_F5: Final[Literal[62]] = 62 + SDL_SCANCODE_F6: Final[Literal[63]] = 63 + SDL_SCANCODE_F7: Final[Literal[64]] = 64 + SDL_SCANCODE_F8: Final[Literal[65]] = 65 + SDL_SCANCODE_F9: Final[Literal[66]] = 66 + SDL_SCANCODE_F: Final[Literal[9]] = 9 + SDL_SCANCODE_FIND: Final[Literal[126]] = 126 + SDL_SCANCODE_G: Final[Literal[10]] = 10 + SDL_SCANCODE_GRAVE: Final[Literal[53]] = 53 + SDL_SCANCODE_H: Final[Literal[11]] = 11 + SDL_SCANCODE_HELP: Final[Literal[117]] = 117 + SDL_SCANCODE_HOME: Final[Literal[74]] = 74 + SDL_SCANCODE_I: Final[Literal[12]] = 12 + SDL_SCANCODE_INSERT: Final[Literal[73]] = 73 + SDL_SCANCODE_INTERNATIONAL1: Final[Literal[135]] = 135 + SDL_SCANCODE_INTERNATIONAL2: Final[Literal[136]] = 136 + SDL_SCANCODE_INTERNATIONAL3: Final[Literal[137]] = 137 + SDL_SCANCODE_INTERNATIONAL4: Final[Literal[138]] = 138 + SDL_SCANCODE_INTERNATIONAL5: Final[Literal[139]] = 139 + SDL_SCANCODE_INTERNATIONAL6: Final[Literal[140]] = 140 + SDL_SCANCODE_INTERNATIONAL7: Final[Literal[141]] = 141 + SDL_SCANCODE_INTERNATIONAL8: Final[Literal[142]] = 142 + SDL_SCANCODE_INTERNATIONAL9: Final[Literal[143]] = 143 + SDL_SCANCODE_J: Final[Literal[13]] = 13 + SDL_SCANCODE_K: Final[Literal[14]] = 14 + SDL_SCANCODE_KP_000: Final[Literal[177]] = 177 + SDL_SCANCODE_KP_00: Final[Literal[176]] = 176 + SDL_SCANCODE_KP_0: Final[Literal[98]] = 98 + SDL_SCANCODE_KP_1: Final[Literal[89]] = 89 + SDL_SCANCODE_KP_2: Final[Literal[90]] = 90 + SDL_SCANCODE_KP_3: Final[Literal[91]] = 91 + SDL_SCANCODE_KP_4: Final[Literal[92]] = 92 + SDL_SCANCODE_KP_5: Final[Literal[93]] = 93 + SDL_SCANCODE_KP_6: Final[Literal[94]] = 94 + SDL_SCANCODE_KP_7: Final[Literal[95]] = 95 + SDL_SCANCODE_KP_8: Final[Literal[96]] = 96 + SDL_SCANCODE_KP_9: Final[Literal[97]] = 97 + SDL_SCANCODE_KP_A: Final[Literal[188]] = 188 + SDL_SCANCODE_KP_AMPERSAND: Final[Literal[199]] = 199 + SDL_SCANCODE_KP_AT: Final[Literal[206]] = 206 + SDL_SCANCODE_KP_B: Final[Literal[189]] = 189 + SDL_SCANCODE_KP_BACKSPACE: Final[Literal[187]] = 187 + SDL_SCANCODE_KP_BINARY: Final[Literal[218]] = 218 + SDL_SCANCODE_KP_C: Final[Literal[190]] = 190 + SDL_SCANCODE_KP_CLEAR: Final[Literal[216]] = 216 + SDL_SCANCODE_KP_CLEARENTRY: Final[Literal[217]] = 217 + SDL_SCANCODE_KP_COLON: Final[Literal[203]] = 203 + SDL_SCANCODE_KP_COMMA: Final[Literal[133]] = 133 + SDL_SCANCODE_KP_D: Final[Literal[191]] = 191 + SDL_SCANCODE_KP_DBLAMPERSAND: Final[Literal[200]] = 200 + SDL_SCANCODE_KP_DBLVERTICALBAR: Final[Literal[202]] = 202 + SDL_SCANCODE_KP_DECIMAL: Final[Literal[220]] = 220 + SDL_SCANCODE_KP_DIVIDE: Final[Literal[84]] = 84 + SDL_SCANCODE_KP_E: Final[Literal[192]] = 192 + SDL_SCANCODE_KP_ENTER: Final[Literal[88]] = 88 + SDL_SCANCODE_KP_EQUALS: Final[Literal[103]] = 103 + SDL_SCANCODE_KP_EQUALSAS400: Final[Literal[134]] = 134 + SDL_SCANCODE_KP_EXCLAM: Final[Literal[207]] = 207 + SDL_SCANCODE_KP_F: Final[Literal[193]] = 193 + SDL_SCANCODE_KP_GREATER: Final[Literal[198]] = 198 + SDL_SCANCODE_KP_HASH: Final[Literal[204]] = 204 + SDL_SCANCODE_KP_HEXADECIMAL: Final[Literal[221]] = 221 + SDL_SCANCODE_KP_LEFTBRACE: Final[Literal[184]] = 184 + SDL_SCANCODE_KP_LEFTPAREN: Final[Literal[182]] = 182 + SDL_SCANCODE_KP_LESS: Final[Literal[197]] = 197 + SDL_SCANCODE_KP_MEMADD: Final[Literal[211]] = 211 + SDL_SCANCODE_KP_MEMCLEAR: Final[Literal[210]] = 210 + SDL_SCANCODE_KP_MEMDIVIDE: Final[Literal[214]] = 214 + SDL_SCANCODE_KP_MEMMULTIPLY: Final[Literal[213]] = 213 + SDL_SCANCODE_KP_MEMRECALL: Final[Literal[209]] = 209 + SDL_SCANCODE_KP_MEMSTORE: Final[Literal[208]] = 208 + SDL_SCANCODE_KP_MEMSUBTRACT: Final[Literal[212]] = 212 + SDL_SCANCODE_KP_MINUS: Final[Literal[86]] = 86 + SDL_SCANCODE_KP_MULTIPLY: Final[Literal[85]] = 85 + SDL_SCANCODE_KP_OCTAL: Final[Literal[219]] = 219 + SDL_SCANCODE_KP_PERCENT: Final[Literal[196]] = 196 + SDL_SCANCODE_KP_PERIOD: Final[Literal[99]] = 99 + SDL_SCANCODE_KP_PLUS: Final[Literal[87]] = 87 + SDL_SCANCODE_KP_PLUSMINUS: Final[Literal[215]] = 215 + SDL_SCANCODE_KP_POWER: Final[Literal[195]] = 195 + SDL_SCANCODE_KP_RIGHTBRACE: Final[Literal[185]] = 185 + SDL_SCANCODE_KP_RIGHTPAREN: Final[Literal[183]] = 183 + SDL_SCANCODE_KP_SPACE: Final[Literal[205]] = 205 + SDL_SCANCODE_KP_TAB: Final[Literal[186]] = 186 + SDL_SCANCODE_KP_VERTICALBAR: Final[Literal[201]] = 201 + SDL_SCANCODE_KP_XOR: Final[Literal[194]] = 194 + SDL_SCANCODE_L: Final[Literal[15]] = 15 + SDL_SCANCODE_LALT: Final[Literal[226]] = 226 + SDL_SCANCODE_LANG1: Final[Literal[144]] = 144 + SDL_SCANCODE_LANG2: Final[Literal[145]] = 145 + SDL_SCANCODE_LANG3: Final[Literal[146]] = 146 + SDL_SCANCODE_LANG4: Final[Literal[147]] = 147 + SDL_SCANCODE_LANG5: Final[Literal[148]] = 148 + SDL_SCANCODE_LANG6: Final[Literal[149]] = 149 + SDL_SCANCODE_LANG7: Final[Literal[150]] = 150 + SDL_SCANCODE_LANG8: Final[Literal[151]] = 151 + SDL_SCANCODE_LANG9: Final[Literal[152]] = 152 + SDL_SCANCODE_LCTRL: Final[Literal[224]] = 224 + SDL_SCANCODE_LEFT: Final[Literal[80]] = 80 + SDL_SCANCODE_LEFTBRACKET: Final[Literal[47]] = 47 + SDL_SCANCODE_LGUI: Final[Literal[227]] = 227 + SDL_SCANCODE_LSHIFT: Final[Literal[225]] = 225 + SDL_SCANCODE_M: Final[Literal[16]] = 16 + SDL_SCANCODE_MEDIA_EJECT: Final[Literal[270]] = 270 + SDL_SCANCODE_MEDIA_FAST_FORWARD: Final[Literal[265]] = 265 + SDL_SCANCODE_MEDIA_NEXT_TRACK: Final[Literal[267]] = 267 + SDL_SCANCODE_MEDIA_PAUSE: Final[Literal[263]] = 263 + SDL_SCANCODE_MEDIA_PLAY: Final[Literal[262]] = 262 + SDL_SCANCODE_MEDIA_PLAY_PAUSE: Final[Literal[271]] = 271 + SDL_SCANCODE_MEDIA_PREVIOUS_TRACK: Final[Literal[268]] = 268 + SDL_SCANCODE_MEDIA_RECORD: Final[Literal[264]] = 264 + SDL_SCANCODE_MEDIA_REWIND: Final[Literal[266]] = 266 + SDL_SCANCODE_MEDIA_SELECT: Final[Literal[272]] = 272 + SDL_SCANCODE_MEDIA_STOP: Final[Literal[269]] = 269 + SDL_SCANCODE_MENU: Final[Literal[118]] = 118 + SDL_SCANCODE_MINUS: Final[Literal[45]] = 45 + SDL_SCANCODE_MODE: Final[Literal[257]] = 257 + SDL_SCANCODE_MUTE: Final[Literal[127]] = 127 + SDL_SCANCODE_N: Final[Literal[17]] = 17 + SDL_SCANCODE_NONUSBACKSLASH: Final[Literal[100]] = 100 + SDL_SCANCODE_NONUSHASH: Final[Literal[50]] = 50 + SDL_SCANCODE_NUMLOCKCLEAR: Final[Literal[83]] = 83 + SDL_SCANCODE_O: Final[Literal[18]] = 18 + SDL_SCANCODE_OPER: Final[Literal[161]] = 161 + SDL_SCANCODE_OUT: Final[Literal[160]] = 160 + SDL_SCANCODE_P: Final[Literal[19]] = 19 + SDL_SCANCODE_PAGEDOWN: Final[Literal[78]] = 78 + SDL_SCANCODE_PAGEUP: Final[Literal[75]] = 75 + SDL_SCANCODE_PASTE: Final[Literal[125]] = 125 + SDL_SCANCODE_PAUSE: Final[Literal[72]] = 72 + SDL_SCANCODE_PERIOD: Final[Literal[55]] = 55 + SDL_SCANCODE_POWER: Final[Literal[102]] = 102 + SDL_SCANCODE_PRINTSCREEN: Final[Literal[70]] = 70 + SDL_SCANCODE_PRIOR: Final[Literal[157]] = 157 + SDL_SCANCODE_Q: Final[Literal[20]] = 20 + SDL_SCANCODE_R: Final[Literal[21]] = 21 + SDL_SCANCODE_RALT: Final[Literal[230]] = 230 + SDL_SCANCODE_RCTRL: Final[Literal[228]] = 228 + SDL_SCANCODE_RESERVED: Final[Literal[400]] = 400 + SDL_SCANCODE_RETURN2: Final[Literal[158]] = 158 + SDL_SCANCODE_RETURN: Final[Literal[40]] = 40 + SDL_SCANCODE_RGUI: Final[Literal[231]] = 231 + SDL_SCANCODE_RIGHT: Final[Literal[79]] = 79 + SDL_SCANCODE_RIGHTBRACKET: Final[Literal[48]] = 48 + SDL_SCANCODE_RSHIFT: Final[Literal[229]] = 229 + SDL_SCANCODE_S: Final[Literal[22]] = 22 + SDL_SCANCODE_SCROLLLOCK: Final[Literal[71]] = 71 + SDL_SCANCODE_SELECT: Final[Literal[119]] = 119 + SDL_SCANCODE_SEMICOLON: Final[Literal[51]] = 51 + SDL_SCANCODE_SEPARATOR: Final[Literal[159]] = 159 + SDL_SCANCODE_SLASH: Final[Literal[56]] = 56 + SDL_SCANCODE_SLEEP: Final[Literal[258]] = 258 + SDL_SCANCODE_SOFTLEFT: Final[Literal[287]] = 287 + SDL_SCANCODE_SOFTRIGHT: Final[Literal[288]] = 288 + SDL_SCANCODE_SPACE: Final[Literal[44]] = 44 + SDL_SCANCODE_STOP: Final[Literal[120]] = 120 + SDL_SCANCODE_SYSREQ: Final[Literal[154]] = 154 + SDL_SCANCODE_T: Final[Literal[23]] = 23 + SDL_SCANCODE_TAB: Final[Literal[43]] = 43 + SDL_SCANCODE_THOUSANDSSEPARATOR: Final[Literal[178]] = 178 + SDL_SCANCODE_U: Final[Literal[24]] = 24 + SDL_SCANCODE_UNDO: Final[Literal[122]] = 122 + SDL_SCANCODE_UNKNOWN: Final[Literal[0]] = 0 + SDL_SCANCODE_UP: Final[Literal[82]] = 82 + SDL_SCANCODE_V: Final[Literal[25]] = 25 + SDL_SCANCODE_VOLUMEDOWN: Final[Literal[129]] = 129 + SDL_SCANCODE_VOLUMEUP: Final[Literal[128]] = 128 + SDL_SCANCODE_W: Final[Literal[26]] = 26 + SDL_SCANCODE_WAKE: Final[Literal[259]] = 259 + SDL_SCANCODE_X: Final[Literal[27]] = 27 + SDL_SCANCODE_Y: Final[Literal[28]] = 28 + SDL_SCANCODE_Z: Final[Literal[29]] = 29 + SDL_SENSOR_ACCEL: Final[int] + SDL_SENSOR_ACCEL_L: Final[int] + SDL_SENSOR_ACCEL_R: Final[int] + SDL_SENSOR_GYRO: Final[int] + SDL_SENSOR_GYRO_L: Final[int] + SDL_SENSOR_GYRO_R: Final[int] + SDL_SENSOR_INVALID: Final[Literal[-1]] = -1 + SDL_SENSOR_UNKNOWN: Final[int] + SDL_SIZE_MAX: Final[int] + SDL_SURFACE_LOCKED: Final[int] + SDL_SURFACE_LOCK_NEEDED: Final[int] + SDL_SURFACE_PREALLOCATED: Final[int] + SDL_SURFACE_SIMD_ALIGNED: Final[int] + SDL_SYSTEM_CURSOR_COUNT: Final[int] + SDL_SYSTEM_CURSOR_CROSSHAIR: Final[int] + SDL_SYSTEM_CURSOR_DEFAULT: Final[int] + SDL_SYSTEM_CURSOR_EW_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_E_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_MOVE: Final[int] + SDL_SYSTEM_CURSOR_NESW_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_NE_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_NOT_ALLOWED: Final[int] + SDL_SYSTEM_CURSOR_NS_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_NWSE_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_NW_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_N_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_POINTER: Final[int] + SDL_SYSTEM_CURSOR_PROGRESS: Final[int] + SDL_SYSTEM_CURSOR_SE_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_SW_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_S_RESIZE: Final[int] + SDL_SYSTEM_CURSOR_TEXT: Final[int] + SDL_SYSTEM_CURSOR_WAIT: Final[int] + SDL_SYSTEM_CURSOR_W_RESIZE: Final[int] + SDL_SYSTEM_THEME_DARK: Final[int] + SDL_SYSTEM_THEME_LIGHT: Final[int] + SDL_SYSTEM_THEME_UNKNOWN: Final[int] + SDL_TEXTINPUT_TYPE_NUMBER: Final[int] + SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: Final[int] + SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: Final[int] + SDL_TEXTINPUT_TYPE_TEXT: Final[int] + SDL_TEXTINPUT_TYPE_TEXT_EMAIL: Final[int] + SDL_TEXTINPUT_TYPE_TEXT_NAME: Final[int] + SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN: Final[int] + SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE: Final[int] + SDL_TEXTINPUT_TYPE_TEXT_USERNAME: Final[int] + SDL_TEXTUREACCESS_STATIC: Final[int] + SDL_TEXTUREACCESS_STREAMING: Final[int] + SDL_TEXTUREACCESS_TARGET: Final[int] + SDL_THREAD_ALIVE: Final[int] + SDL_THREAD_COMPLETE: Final[int] + SDL_THREAD_DETACHED: Final[int] + SDL_THREAD_PRIORITY_HIGH: Final[int] + SDL_THREAD_PRIORITY_LOW: Final[int] + SDL_THREAD_PRIORITY_NORMAL: Final[int] + SDL_THREAD_PRIORITY_TIME_CRITICAL: Final[int] + SDL_THREAD_UNKNOWN: Final[int] + SDL_TIME_FORMAT_12HR: Final[Literal[1]] = 1 + SDL_TIME_FORMAT_24HR: Final[Literal[0]] = 0 + SDL_TOUCH_DEVICE_DIRECT: Final[int] + SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE: Final[int] + SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: Final[int] + SDL_TOUCH_DEVICE_INVALID: Final[Literal[-1]] = -1 + SDL_TOUCH_MOUSEID: Final[int] + SDL_TRANSFER_CHARACTERISTICS_BT1361: Final[Literal[12]] = 12 + SDL_TRANSFER_CHARACTERISTICS_BT2020_10BIT: Final[Literal[14]] = 14 + SDL_TRANSFER_CHARACTERISTICS_BT2020_12BIT: Final[Literal[15]] = 15 + SDL_TRANSFER_CHARACTERISTICS_BT601: Final[Literal[6]] = 6 + SDL_TRANSFER_CHARACTERISTICS_BT709: Final[Literal[1]] = 1 + SDL_TRANSFER_CHARACTERISTICS_CUSTOM: Final[Literal[31]] = 31 + SDL_TRANSFER_CHARACTERISTICS_GAMMA22: Final[Literal[4]] = 4 + SDL_TRANSFER_CHARACTERISTICS_GAMMA28: Final[Literal[5]] = 5 + SDL_TRANSFER_CHARACTERISTICS_HLG: Final[Literal[18]] = 18 + SDL_TRANSFER_CHARACTERISTICS_IEC61966: Final[Literal[11]] = 11 + SDL_TRANSFER_CHARACTERISTICS_LINEAR: Final[Literal[8]] = 8 + SDL_TRANSFER_CHARACTERISTICS_LOG100: Final[Literal[9]] = 9 + SDL_TRANSFER_CHARACTERISTICS_LOG100_SQRT10: Final[Literal[10]] = 10 + SDL_TRANSFER_CHARACTERISTICS_PQ: Final[Literal[16]] = 16 + SDL_TRANSFER_CHARACTERISTICS_SMPTE240: Final[Literal[7]] = 7 + SDL_TRANSFER_CHARACTERISTICS_SMPTE428: Final[Literal[17]] = 17 + SDL_TRANSFER_CHARACTERISTICS_SRGB: Final[Literal[13]] = 13 + SDL_TRANSFER_CHARACTERISTICS_UNKNOWN: Final[Literal[0]] = 0 + SDL_TRANSFER_CHARACTERISTICS_UNSPECIFIED: Final[Literal[2]] = 2 + SDL_TRAYENTRY_BUTTON: Final[int] + SDL_TRAYENTRY_CHECKBOX: Final[int] + SDL_TRAYENTRY_CHECKED: Final[int] + SDL_TRAYENTRY_DISABLED: Final[int] + SDL_TRAYENTRY_SUBMENU: Final[int] + SDL_US_PER_SECOND: Final[int] + SDL_VERSION: Final[int] + SDL_WINDOWPOS_CENTERED: Final[int] + SDL_WINDOWPOS_CENTERED_MASK: Final[int] + SDL_WINDOWPOS_UNDEFINED: Final[int] + SDL_WINDOWPOS_UNDEFINED_MASK: Final[int] + SDL_WINDOW_ALWAYS_ON_TOP: Final[int] + SDL_WINDOW_BORDERLESS: Final[int] + SDL_WINDOW_EXTERNAL: Final[int] + SDL_WINDOW_FULLSCREEN: Final[int] + SDL_WINDOW_HIDDEN: Final[int] + SDL_WINDOW_HIGH_PIXEL_DENSITY: Final[int] + SDL_WINDOW_INPUT_FOCUS: Final[int] + SDL_WINDOW_KEYBOARD_GRABBED: Final[int] + SDL_WINDOW_MAXIMIZED: Final[int] + SDL_WINDOW_METAL: Final[int] + SDL_WINDOW_MINIMIZED: Final[int] + SDL_WINDOW_MODAL: Final[int] + SDL_WINDOW_MOUSE_CAPTURE: Final[int] + SDL_WINDOW_MOUSE_FOCUS: Final[int] + SDL_WINDOW_MOUSE_GRABBED: Final[int] + SDL_WINDOW_MOUSE_RELATIVE_MODE: Final[int] + SDL_WINDOW_NOT_FOCUSABLE: Final[int] + SDL_WINDOW_OCCLUDED: Final[int] + SDL_WINDOW_OPENGL: Final[int] + SDL_WINDOW_POPUP_MENU: Final[int] + SDL_WINDOW_RESIZABLE: Final[int] + SDL_WINDOW_SURFACE_VSYNC_ADAPTIVE: Final[int] + SDL_WINDOW_SURFACE_VSYNC_DISABLED: Final[int] + SDL_WINDOW_TOOLTIP: Final[int] + SDL_WINDOW_TRANSPARENT: Final[int] + SDL_WINDOW_UTILITY: Final[int] + SDL_WINDOW_VULKAN: Final[int] + TCODK_0: Final[int] + TCODK_1: Final[int] + TCODK_2: Final[int] + TCODK_3: Final[int] + TCODK_4: Final[int] + TCODK_5: Final[int] + TCODK_6: Final[int] + TCODK_7: Final[int] + TCODK_8: Final[int] + TCODK_9: Final[int] + TCODK_ALT: Final[int] + TCODK_APPS: Final[int] + TCODK_BACKSPACE: Final[int] + TCODK_CAPSLOCK: Final[int] + TCODK_CHAR: Final[int] + TCODK_CONTROL: Final[int] + TCODK_DELETE: Final[int] + TCODK_DOWN: Final[int] + TCODK_END: Final[int] + TCODK_ENTER: Final[int] + TCODK_ESCAPE: Final[int] + TCODK_F10: Final[int] + TCODK_F11: Final[int] + TCODK_F12: Final[int] + TCODK_F1: Final[int] + TCODK_F2: Final[int] + TCODK_F3: Final[int] + TCODK_F4: Final[int] + TCODK_F5: Final[int] + TCODK_F6: Final[int] + TCODK_F7: Final[int] + TCODK_F8: Final[int] + TCODK_F9: Final[int] + TCODK_HOME: Final[int] + TCODK_INSERT: Final[int] + TCODK_KP0: Final[int] + TCODK_KP1: Final[int] + TCODK_KP2: Final[int] + TCODK_KP3: Final[int] + TCODK_KP4: Final[int] + TCODK_KP5: Final[int] + TCODK_KP6: Final[int] + TCODK_KP7: Final[int] + TCODK_KP8: Final[int] + TCODK_KP9: Final[int] + TCODK_KPADD: Final[int] + TCODK_KPDEC: Final[int] + TCODK_KPDIV: Final[int] + TCODK_KPENTER: Final[int] + TCODK_KPMUL: Final[int] + TCODK_KPSUB: Final[int] + TCODK_LEFT: Final[int] + TCODK_LWIN: Final[int] + TCODK_NONE: Final[int] + TCODK_NUMLOCK: Final[int] + TCODK_PAGEDOWN: Final[int] + TCODK_PAGEUP: Final[int] + TCODK_PAUSE: Final[int] + TCODK_PRINTSCREEN: Final[int] + TCODK_RIGHT: Final[int] + TCODK_RWIN: Final[int] + TCODK_SCROLLLOCK: Final[int] + TCODK_SHIFT: Final[int] + TCODK_SPACE: Final[int] + TCODK_TAB: Final[int] + TCODK_TEXT: Final[int] + TCODK_UP: Final[int] + TCOD_BKGND_ADD: Final[int] + TCOD_BKGND_ADDA: Final[int] + TCOD_BKGND_ALPH: Final[int] + TCOD_BKGND_BURN: Final[int] + TCOD_BKGND_COLOR_BURN: Final[int] + TCOD_BKGND_COLOR_DODGE: Final[int] + TCOD_BKGND_DARKEN: Final[int] + TCOD_BKGND_DEFAULT: Final[int] + TCOD_BKGND_LIGHTEN: Final[int] + TCOD_BKGND_MULTIPLY: Final[int] + TCOD_BKGND_NONE: Final[int] + TCOD_BKGND_OVERLAY: Final[int] + TCOD_BKGND_SCREEN: Final[int] + TCOD_BKGND_SET: Final[int] + TCOD_CENTER: Final[int] + TCOD_CHAR_ARROW2_E: Final[Literal[16]] = 16 + TCOD_CHAR_ARROW2_N: Final[Literal[30]] = 30 + TCOD_CHAR_ARROW2_S: Final[Literal[31]] = 31 + TCOD_CHAR_ARROW2_W: Final[Literal[17]] = 17 + TCOD_CHAR_ARROW_E: Final[Literal[26]] = 26 + TCOD_CHAR_ARROW_N: Final[Literal[24]] = 24 + TCOD_CHAR_ARROW_S: Final[Literal[25]] = 25 + TCOD_CHAR_ARROW_W: Final[Literal[27]] = 27 + TCOD_CHAR_BLOCK1: Final[Literal[176]] = 176 + TCOD_CHAR_BLOCK2: Final[Literal[177]] = 177 + TCOD_CHAR_BLOCK3: Final[Literal[178]] = 178 + TCOD_CHAR_BULLET: Final[Literal[7]] = 7 + TCOD_CHAR_BULLET_INV: Final[Literal[8]] = 8 + TCOD_CHAR_BULLET_SQUARE: Final[Literal[254]] = 254 + TCOD_CHAR_CENT: Final[Literal[189]] = 189 + TCOD_CHAR_CHECKBOX_SET: Final[Literal[225]] = 225 + TCOD_CHAR_CHECKBOX_UNSET: Final[Literal[224]] = 224 + TCOD_CHAR_CLUB: Final[Literal[5]] = 5 + TCOD_CHAR_COPYRIGHT: Final[Literal[184]] = 184 + TCOD_CHAR_CROSS: Final[Literal[197]] = 197 + TCOD_CHAR_CURRENCY: Final[Literal[207]] = 207 + TCOD_CHAR_DARROW_H: Final[Literal[29]] = 29 + TCOD_CHAR_DARROW_V: Final[Literal[18]] = 18 + TCOD_CHAR_DCROSS: Final[Literal[206]] = 206 + TCOD_CHAR_DHLINE: Final[Literal[205]] = 205 + TCOD_CHAR_DIAMOND: Final[Literal[4]] = 4 + TCOD_CHAR_DIVISION: Final[Literal[246]] = 246 + TCOD_CHAR_DNE: Final[Literal[187]] = 187 + TCOD_CHAR_DNW: Final[Literal[201]] = 201 + TCOD_CHAR_DSE: Final[Literal[188]] = 188 + TCOD_CHAR_DSW: Final[Literal[200]] = 200 + TCOD_CHAR_DTEEE: Final[Literal[204]] = 204 + TCOD_CHAR_DTEEN: Final[Literal[202]] = 202 + TCOD_CHAR_DTEES: Final[Literal[203]] = 203 + TCOD_CHAR_DTEEW: Final[Literal[185]] = 185 + TCOD_CHAR_DVLINE: Final[Literal[186]] = 186 + TCOD_CHAR_EXCLAM_DOUBLE: Final[Literal[19]] = 19 + TCOD_CHAR_FEMALE: Final[Literal[12]] = 12 + TCOD_CHAR_FUNCTION: Final[Literal[159]] = 159 + TCOD_CHAR_GRADE: Final[Literal[248]] = 248 + TCOD_CHAR_HALF: Final[Literal[171]] = 171 + TCOD_CHAR_HEART: Final[Literal[3]] = 3 + TCOD_CHAR_HLINE: Final[Literal[196]] = 196 + TCOD_CHAR_LIGHT: Final[Literal[15]] = 15 + TCOD_CHAR_MALE: Final[Literal[11]] = 11 + TCOD_CHAR_MULTIPLICATION: Final[Literal[158]] = 158 + TCOD_CHAR_NE: Final[Literal[191]] = 191 + TCOD_CHAR_NOTE: Final[Literal[13]] = 13 + TCOD_CHAR_NOTE_DOUBLE: Final[Literal[14]] = 14 + TCOD_CHAR_NW: Final[Literal[218]] = 218 + TCOD_CHAR_ONE_QUARTER: Final[Literal[172]] = 172 + TCOD_CHAR_PILCROW: Final[Literal[20]] = 20 + TCOD_CHAR_POUND: Final[Literal[156]] = 156 + TCOD_CHAR_POW1: Final[Literal[251]] = 251 + TCOD_CHAR_POW2: Final[Literal[253]] = 253 + TCOD_CHAR_POW3: Final[Literal[252]] = 252 + TCOD_CHAR_RADIO_SET: Final[Literal[10]] = 10 + TCOD_CHAR_RADIO_UNSET: Final[Literal[9]] = 9 + TCOD_CHAR_RESERVED: Final[Literal[169]] = 169 + TCOD_CHAR_SE: Final[Literal[217]] = 217 + TCOD_CHAR_SECTION: Final[Literal[21]] = 21 + TCOD_CHAR_SMILIE: Final[Literal[1]] = 1 + TCOD_CHAR_SMILIE_INV: Final[Literal[2]] = 2 + TCOD_CHAR_SPADE: Final[Literal[6]] = 6 + TCOD_CHAR_SUBP_DIAG: Final[Literal[230]] = 230 + TCOD_CHAR_SUBP_E: Final[Literal[231]] = 231 + TCOD_CHAR_SUBP_N: Final[Literal[228]] = 228 + TCOD_CHAR_SUBP_NE: Final[Literal[227]] = 227 + TCOD_CHAR_SUBP_NW: Final[Literal[226]] = 226 + TCOD_CHAR_SUBP_SE: Final[Literal[229]] = 229 + TCOD_CHAR_SUBP_SW: Final[Literal[232]] = 232 + TCOD_CHAR_SW: Final[Literal[192]] = 192 + TCOD_CHAR_TEEE: Final[Literal[195]] = 195 + TCOD_CHAR_TEEN: Final[Literal[193]] = 193 + TCOD_CHAR_TEES: Final[Literal[194]] = 194 + TCOD_CHAR_TEEW: Final[Literal[180]] = 180 + TCOD_CHAR_THREE_QUARTERS: Final[Literal[243]] = 243 + TCOD_CHAR_UMLAUT: Final[Literal[249]] = 249 + TCOD_CHAR_VLINE: Final[Literal[179]] = 179 + TCOD_CHAR_YEN: Final[Literal[190]] = 190 + TCOD_COLCTRL_1: Final[Literal[1]] = 1 + TCOD_COLCTRL_2: Final[int] + TCOD_COLCTRL_3: Final[int] + TCOD_COLCTRL_4: Final[int] + TCOD_COLCTRL_5: Final[int] + TCOD_COLCTRL_BACK_RGB: Final[int] + TCOD_COLCTRL_FORE_RGB: Final[int] + TCOD_COLCTRL_NUMBER: Final[Literal[5]] = 5 + TCOD_COLCTRL_STOP: Final[int] + TCOD_COMPILEDVERSION: Final[int] + TCOD_DISTRIBUTION_GAUSSIAN: Final[int] + TCOD_DISTRIBUTION_GAUSSIAN_INVERSE: Final[int] + TCOD_DISTRIBUTION_GAUSSIAN_RANGE: Final[int] + TCOD_DISTRIBUTION_GAUSSIAN_RANGE_INVERSE: Final[int] + TCOD_DISTRIBUTION_LINEAR: Final[int] + TCOD_EVENT_ANY: Final[int] + TCOD_EVENT_FINGER: Final[int] + TCOD_EVENT_FINGER_MOVE: Final[Literal[32]] = 32 + TCOD_EVENT_FINGER_PRESS: Final[Literal[64]] = 64 + TCOD_EVENT_FINGER_RELEASE: Final[Literal[128]] = 128 + TCOD_EVENT_KEY: Final[int] + TCOD_EVENT_KEY_PRESS: Final[Literal[1]] = 1 + TCOD_EVENT_KEY_RELEASE: Final[Literal[2]] = 2 + TCOD_EVENT_MOUSE: Final[int] + TCOD_EVENT_MOUSE_MOVE: Final[Literal[4]] = 4 + TCOD_EVENT_MOUSE_PRESS: Final[Literal[8]] = 8 + TCOD_EVENT_MOUSE_RELEASE: Final[Literal[16]] = 16 + TCOD_EVENT_NONE: Final[Literal[0]] = 0 + TCOD_E_ERROR: Final[Literal[-1]] = -1 + TCOD_E_INVALID_ARGUMENT: Final[Literal[-2]] = -2 + TCOD_E_OK: Final[Literal[0]] = 0 + TCOD_E_OUT_OF_MEMORY: Final[Literal[-3]] = -3 + TCOD_E_REQUIRES_ATTENTION: Final[Literal[-4]] = -4 + TCOD_E_WARN: Final[Literal[1]] = 1 + TCOD_FALLBACK_FONT_SIZE: Final[Literal[16]] = 16 + TCOD_FONT_LAYOUT_ASCII_INCOL: Final[Literal[1]] = 1 + TCOD_FONT_LAYOUT_ASCII_INROW: Final[Literal[2]] = 2 + TCOD_FONT_LAYOUT_CP437: Final[Literal[16]] = 16 + TCOD_FONT_LAYOUT_TCOD: Final[Literal[8]] = 8 + TCOD_FONT_TYPE_GRAYSCALE: Final[Literal[4]] = 4 + TCOD_FONT_TYPE_GREYSCALE: Final[Literal[4]] = 4 + TCOD_KEY_PRESSED: Final[Literal[1]] = 1 + TCOD_KEY_RELEASED: Final[Literal[2]] = 2 + TCOD_KEY_TEXT_SIZE: Final[Literal[32]] = 32 + TCOD_LEFT: Final[int] + TCOD_LEX_CHAR: Final[Literal[7]] = 7 + TCOD_LEX_COMMENT: Final[Literal[9]] = 9 + TCOD_LEX_EOF: Final[Literal[8]] = 8 + TCOD_LEX_FLAG_NESTING_COMMENT: Final[Literal[2]] = 2 + TCOD_LEX_FLAG_NOCASE: Final[Literal[1]] = 1 + TCOD_LEX_FLAG_TOKENIZE_COMMENTS: Final[Literal[4]] = 4 + TCOD_LEX_FLOAT: Final[Literal[6]] = 6 + TCOD_LEX_IDEN: Final[Literal[3]] = 3 + TCOD_LEX_INTEGER: Final[Literal[5]] = 5 + TCOD_LEX_KEYWORD: Final[Literal[2]] = 2 + TCOD_LEX_KEYWORD_SIZE: Final[Literal[20]] = 20 + TCOD_LEX_MAX_KEYWORDS: Final[Literal[100]] = 100 + TCOD_LEX_MAX_SYMBOLS: Final[Literal[100]] = 100 + TCOD_LEX_STRING: Final[Literal[4]] = 4 + TCOD_LEX_SYMBOL: Final[Literal[1]] = 1 + TCOD_LEX_SYMBOL_SIZE: Final[Literal[5]] = 5 + TCOD_LEX_UNKNOWN: Final[Literal[0]] = 0 + TCOD_LOG_CRITICAL: Final[Literal[50]] = 50 + TCOD_LOG_DEBUG: Final[Literal[10]] = 10 + TCOD_LOG_ERROR: Final[Literal[40]] = 40 + TCOD_LOG_INFO: Final[Literal[20]] = 20 + TCOD_LOG_WARNING: Final[Literal[30]] = 30 + TCOD_MAJOR_VERSION: Final[Literal[2]] = 2 + TCOD_MINOR_VERSION: Final[Literal[1]] = 1 + TCOD_NB_RENDERERS: Final[int] + TCOD_NOISE_DEFAULT: Final[Literal[0]] = 0 + TCOD_NOISE_MAX_DIMENSIONS: Final[Literal[4]] = 4 + TCOD_NOISE_MAX_OCTAVES: Final[Literal[128]] = 128 + TCOD_NOISE_PERLIN: Final[Literal[1]] = 1 + TCOD_NOISE_SIMPLEX: Final[Literal[2]] = 2 + TCOD_NOISE_WAVELET: Final[Literal[4]] = 4 + TCOD_PATCHLEVEL: Final[Literal[1]] = 1 + TCOD_PATHFINDER_MAX_DIMENSIONS: Final[Literal[4]] = 4 + TCOD_RENDERER_GLSL: Final[int] + TCOD_RENDERER_OPENGL2: Final[int] + TCOD_RENDERER_OPENGL: Final[int] + TCOD_RENDERER_SDL2: Final[int] + TCOD_RENDERER_SDL: Final[int] + TCOD_RENDERER_XTERM: Final[int] + TCOD_RIGHT: Final[int] + TCOD_RNG_CMWC: Final[int] + TCOD_RNG_MT: Final[int] + TCOD_TYPE_BOOL: Final[int] + TCOD_TYPE_CHAR: Final[int] + TCOD_TYPE_COLOR: Final[int] + TCOD_TYPE_CUSTOM00: Final[int] + TCOD_TYPE_CUSTOM01: Final[int] + TCOD_TYPE_CUSTOM02: Final[int] + TCOD_TYPE_CUSTOM03: Final[int] + TCOD_TYPE_CUSTOM04: Final[int] + TCOD_TYPE_CUSTOM05: Final[int] + TCOD_TYPE_CUSTOM06: Final[int] + TCOD_TYPE_CUSTOM07: Final[int] + TCOD_TYPE_CUSTOM08: Final[int] + TCOD_TYPE_CUSTOM09: Final[int] + TCOD_TYPE_CUSTOM10: Final[int] + TCOD_TYPE_CUSTOM11: Final[int] + TCOD_TYPE_CUSTOM12: Final[int] + TCOD_TYPE_CUSTOM13: Final[int] + TCOD_TYPE_CUSTOM14: Final[int] + TCOD_TYPE_CUSTOM15: Final[int] + TCOD_TYPE_DICE: Final[int] + TCOD_TYPE_FLOAT: Final[int] + TCOD_TYPE_INT: Final[int] + TCOD_TYPE_LIST: Final[Literal[1024]] = 1024 + TCOD_TYPE_NONE: Final[int] + TCOD_TYPE_STRING: Final[int] + TCOD_TYPE_VALUELIST00: Final[int] + TCOD_TYPE_VALUELIST01: Final[int] + TCOD_TYPE_VALUELIST02: Final[int] + TCOD_TYPE_VALUELIST03: Final[int] + TCOD_TYPE_VALUELIST04: Final[int] + TCOD_TYPE_VALUELIST05: Final[int] + TCOD_TYPE_VALUELIST06: Final[int] + TCOD_TYPE_VALUELIST07: Final[int] + TCOD_TYPE_VALUELIST08: Final[int] + TCOD_TYPE_VALUELIST09: Final[int] + TCOD_TYPE_VALUELIST10: Final[int] + TCOD_TYPE_VALUELIST11: Final[int] + TCOD_TYPE_VALUELIST12: Final[int] + TCOD_TYPE_VALUELIST13: Final[int] + TCOD_TYPE_VALUELIST14: Final[int] + TCOD_TYPE_VALUELIST15: Final[int] + TCOD_ctx: Any + kNoiseImplementationFBM: Final[int] + kNoiseImplementationSimple: Final[int] + kNoiseImplementationTurbulence: Final[int] + np_float16: Final[int] + np_float32: Final[int] + np_float64: Final[int] + np_int16: Final[int] + np_int32: Final[int] + np_int64: Final[int] + np_int8: Final[int] + np_uint16: Final[int] + np_uint32: Final[int] + np_uint64: Final[int] + np_uint8: Final[int] + np_undefined: Final[Literal[0]] = 0 + +lib: _lib +ffi: Any diff --git a/tcod/cffi.py b/tcod/cffi.py index d1c93025..b0590d0e 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -15,17 +15,12 @@ __sdl_version__ = "" +REQUIRED_SDL_VERSION = (3, 2, 0) + ffi_check = cffi.FFI() ffi_check.cdef( """ -typedef struct SDL_version -{ - uint8_t major; - uint8_t minor; - uint8_t patch; -} SDL_version; - -void SDL_GetVersion(SDL_version * ver); +int SDL_GetVersion(void); """ ) @@ -33,11 +28,13 @@ def verify_dependencies() -> None: """Try to make sure dependencies exist on this system.""" if sys.platform == "win32": - lib_test: Any = ffi_check.dlopen("SDL2.dll") # Make sure SDL2.dll is here. - version: Any = ffi_check.new("struct SDL_version*") - lib_test.SDL_GetVersion(version) # Need to check this version. - version_tuple = version.major, version.minor, version.patch - if version_tuple < (2, 0, 5): + lib_test: Any = ffi_check.dlopen("SDL3.dll") # Make sure SDL3.dll is here. + int_version = lib_test.SDL_GetVersion() # Need to check this version. + major = int_version // 1000000 + minor = (int_version // 1000) % 1000 + patch = int_version % 1000 + version_tuple = major, minor, patch + if version_tuple < REQUIRED_SDL_VERSION: msg = f"Tried to load an old version of SDL {version_tuple!r}" raise RuntimeError(msg) @@ -48,9 +45,8 @@ def get_architecture() -> str: def get_sdl_version() -> str: - sdl_version = ffi.new("SDL_version*") - lib.SDL_GetVersion(sdl_version) - return f"{sdl_version.major}.{sdl_version.minor}.{sdl_version.patch}" + int_version = lib.SDL_GetVersion() + return f"{int_version // 1000000}.{(int_version // 1000) % 1000}.{int_version % 1000}" if sys.platform == "win32": diff --git a/tcod/color.py b/tcod/color.py index 61f18b49..9fc6f260 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -126,7 +126,7 @@ def __mul__(self, other: object) -> Color: """ if isinstance(other, (Color, list, tuple)): return Color._new_from_cdata(lib.TCOD_color_multiply(self, other)) - return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) + return Color._new_from_cdata(lib.TCOD_color_multiply_scalar(self, other)) # type: ignore[arg-type] def __repr__(self) -> str: """Return a printable representation of the current color.""" diff --git a/tcod/console.py b/tcod/console.py index ca9d4253..c4b24281 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1516,9 +1516,9 @@ def recommended_size() -> tuple[int, int]: renderer = lib.TCOD_sys_get_sdl_renderer() with ffi.new("int[2]") as xy: if renderer: - lib.SDL_GetRendererOutputSize(renderer, xy, xy + 1) + lib.SDL_GetCurrentRenderOutputSize(renderer, xy, xy + 1) else: # Assume OpenGL if a renderer does not exist. - lib.SDL_GL_GetDrawableSize(window, xy, xy + 1) + lib.SDL_GetWindowSizeInPixels(window, xy, xy + 1) w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width) h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height) return w, h diff --git a/tcod/context.py b/tcod/context.py index f9d562ee..769f84d2 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -68,14 +68,9 @@ _Event = TypeVar("_Event", bound=tcod.event.Event) SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN -"""Exclusive fullscreen mode. - -It's generally not recommended to use this flag unless you know what you're -doing. -`SDL_WINDOW_FULLSCREEN_DESKTOP` should be used instead whenever possible. -""" -SDL_WINDOW_FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP -"""A borderless fullscreen window at the desktop resolution.""" +"""Fullscreen mode.""" +# SDL_WINDOW_FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP +# """A borderless fullscreen window at the desktop resolution.""" SDL_WINDOW_HIDDEN = lib.SDL_WINDOW_HIDDEN """Window is hidden.""" SDL_WINDOW_BORDERLESS = lib.SDL_WINDOW_BORDERLESS @@ -86,9 +81,9 @@ """Window is minimized.""" SDL_WINDOW_MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED """Window is maximized.""" -SDL_WINDOW_INPUT_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED +SDL_WINDOW_INPUT_GRABBED = lib.SDL_WINDOW_MOUSE_GRABBED """Window has grabbed the input.""" -SDL_WINDOW_ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI +SDL_WINDOW_ALLOW_HIGHDPI = lib.SDL_WINDOW_HIGH_PIXEL_DENSITY """High DPI mode, see the SDL documentation.""" RENDERER_OPENGL = lib.TCOD_RENDERER_OPENGL @@ -225,13 +220,14 @@ def present( ) _check(lib.TCOD_context_present(self._p, console.console_c, viewport_args)) - def pixel_to_tile(self, x: int, y: int) -> tuple[int, int]: + def pixel_to_tile(self, x: float, y: float) -> tuple[float, float]: """Convert window pixel coordinates to tile coordinates.""" - with ffi.new("int[2]", (x, y)) as xy: - _check(lib.TCOD_context_screen_pixel_to_tile_i(self._p, xy, xy + 1)) + with ffi.new("double[2]", (x, y)) as xy: + _check(lib.TCOD_context_screen_pixel_to_tile_d(self._p, xy, xy + 1)) return xy[0], xy[1] - def pixel_to_subtile(self, x: int, y: int) -> tuple[float, float]: + @deprecated("Use pixel_to_tile method instead.") + def pixel_to_subtile(self, x: float, y: float) -> tuple[float, float]: """Convert window pixel coordinates to sub-tile coordinates.""" with ffi.new("double[2]", (x, y)) as xy: _check(lib.TCOD_context_screen_pixel_to_tile_d(self._p, xy, xy + 1)) @@ -267,7 +263,7 @@ def convert_event(self, event: _Event) -> _Event: event.position[1] - event.motion[1], ) event_copy.motion = event._tile_motion = tcod.event.Point( - event._tile[0] - prev_tile[0], event._tile[1] - prev_tile[1] + int(event._tile[0]) - int(prev_tile[0]), int(event._tile[1]) - int(prev_tile[1]) ) return event_copy diff --git a/tcod/event.py b/tcod/event.py index 25abfb7f..b780ffdf 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -154,9 +154,9 @@ class Point(NamedTuple): :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp` """ - x: int + x: float """A pixel or tile coordinate starting with zero as the left-most position.""" - y: int + y: float """A pixel or tile coordinate starting with zero as the top-most position.""" @@ -357,8 +357,8 @@ def __init__(self, scancode: int, sym: int, mod: int, repeat: bool = False) -> N @classmethod def from_sdl_event(cls, sdl_event: Any) -> Any: - keysym = sdl_event.key.keysym - self = cls(keysym.scancode, keysym.sym, keysym.mod, bool(sdl_event.key.repeat)) + keysym = sdl_event.key + self = cls(keysym.scancode, keysym.key, keysym.mod, bool(sdl_event.key.repeat)) self.sdl_event = sdl_event return self @@ -408,8 +408,8 @@ class MouseState(Event): def __init__( self, - position: tuple[int, int] = (0, 0), - tile: tuple[int, int] | None = (0, 0), + position: tuple[float, float] = (0, 0), + tile: tuple[float, float] | None = (0, 0), state: int = 0, ) -> None: super().__init__() @@ -440,7 +440,7 @@ def tile(self) -> Point: return _verify_tile_coordinates(self._tile) @tile.setter - def tile(self, xy: tuple[int, int]) -> None: + def tile(self, xy: tuple[float, float]) -> None: self._tile = Point(*xy) def __repr__(self) -> str: @@ -481,10 +481,10 @@ class MouseMotion(MouseState): def __init__( self, - position: tuple[int, int] = (0, 0), - motion: tuple[int, int] = (0, 0), - tile: tuple[int, int] | None = (0, 0), - tile_motion: tuple[int, int] | None = (0, 0), + position: tuple[float, float] = (0, 0), + motion: tuple[float, float] = (0, 0), + tile: tuple[float, float] | None = (0, 0), + tile_motion: tuple[float, float] | None = (0, 0), state: int = 0, ) -> None: super().__init__(position, tile, state) @@ -520,7 +520,7 @@ def tile_motion(self) -> Point: return _verify_tile_coordinates(self._tile_motion) @tile_motion.setter - def tile_motion(self, xy: tuple[int, int]) -> None: + def tile_motion(self, xy: tuple[float, float]) -> None: warnings.warn( "The mouse.tile_motion attribute is deprecated." " Use mouse.motion of the event returned by context.convert_event instead.", @@ -581,8 +581,8 @@ class MouseButtonEvent(MouseState): def __init__( self, - pixel: tuple[int, int] = (0, 0), - tile: tuple[int, int] | None = (0, 0), + pixel: tuple[float, float] = (0, 0), + tile: tuple[float, float] | None = (0, 0), button: int = 0, ) -> None: super().__init__(pixel, tile, button) @@ -601,9 +601,9 @@ def from_sdl_event(cls, sdl_event: Any) -> Any: pixel = button.x, button.y subtile = _pixel_to_tile(*pixel) if subtile is None: - tile: tuple[int, int] | None = None + tile: tuple[float, float] | None = None else: - tile = int(subtile[0]), int(subtile[1]) + tile = float(subtile[0]), float(subtile[1]) self = cls(pixel, tile, button.button) self.sdl_event = sdl_event return self @@ -727,11 +727,11 @@ def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: return Undefined.from_sdl_event(sdl_event) event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event] self: WindowEvent - if sdl_event.window.event == lib.SDL_WINDOWEVENT_MOVED: + if sdl_event.window.event == lib.SDL_EVENT_WINDOW_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) elif sdl_event.window.event in ( - lib.SDL_WINDOWEVENT_RESIZED, - lib.SDL_WINDOWEVENT_SIZE_CHANGED, + lib.SDL_EVENT_WINDOW_RESIZED, + lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, ): self = WindowResized(event_type, sdl_event.window.data1, sdl_event.window.data2) else: @@ -743,22 +743,20 @@ def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}(type={self.type!r})" __WINDOW_TYPES: Final = { - lib.SDL_WINDOWEVENT_SHOWN: "WindowShown", - lib.SDL_WINDOWEVENT_HIDDEN: "WindowHidden", - lib.SDL_WINDOWEVENT_EXPOSED: "WindowExposed", - lib.SDL_WINDOWEVENT_MOVED: "WindowMoved", - lib.SDL_WINDOWEVENT_RESIZED: "WindowResized", - lib.SDL_WINDOWEVENT_SIZE_CHANGED: "WindowSizeChanged", - lib.SDL_WINDOWEVENT_MINIMIZED: "WindowMinimized", - lib.SDL_WINDOWEVENT_MAXIMIZED: "WindowMaximized", - lib.SDL_WINDOWEVENT_RESTORED: "WindowRestored", - lib.SDL_WINDOWEVENT_ENTER: "WindowEnter", - lib.SDL_WINDOWEVENT_LEAVE: "WindowLeave", - lib.SDL_WINDOWEVENT_FOCUS_GAINED: "WindowFocusGained", - lib.SDL_WINDOWEVENT_FOCUS_LOST: "WindowFocusLost", - lib.SDL_WINDOWEVENT_CLOSE: "WindowClose", - lib.SDL_WINDOWEVENT_TAKE_FOCUS: "WindowTakeFocus", - lib.SDL_WINDOWEVENT_HIT_TEST: "WindowHitTest", + lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown", + lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden", + lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed", + lib.SDL_EVENT_WINDOW_MOVED: "WindowMoved", + lib.SDL_EVENT_WINDOW_RESIZED: "WindowResized", + lib.SDL_EVENT_WINDOW_MINIMIZED: "WindowMinimized", + lib.SDL_EVENT_WINDOW_MAXIMIZED: "WindowMaximized", + lib.SDL_EVENT_WINDOW_RESTORED: "WindowRestored", + lib.SDL_EVENT_WINDOW_MOUSE_ENTER: "WindowEnter", + lib.SDL_EVENT_WINDOW_MOUSE_LEAVE: "WindowLeave", + lib.SDL_EVENT_WINDOW_FOCUS_GAINED: "WindowFocusGained", + lib.SDL_EVENT_WINDOW_FOCUS_LOST: "WindowFocusLost", + lib.SDL_EVENT_WINDOW_CLOSE_REQUESTED: "WindowClose", + lib.SDL_EVENT_WINDOW_HIT_TEST: "WindowHitTest", } @@ -974,7 +972,10 @@ def pressed(self) -> bool: @classmethod def from_sdl_event(cls, sdl_event: Any) -> JoystickButton: - type = {lib.SDL_JOYBUTTONDOWN: "JOYBUTTONDOWN", lib.SDL_JOYBUTTONUP: "JOYBUTTONUP"}[sdl_event.type] + type = { + lib.SDL_EVENT_JOYSTICK_BUTTON_DOWN: "JOYBUTTONDOWN", + lib.SDL_EVENT_JOYSTICK_BUTTON_UP: "JOYBUTTONUP", + }[sdl_event.type] return cls(type, sdl_event.jbutton.which, sdl_event.jbutton.button) def __repr__(self) -> str: @@ -1010,7 +1011,10 @@ class JoystickDevice(JoystickEvent): @classmethod def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice: - type = {lib.SDL_JOYDEVICEADDED: "JOYDEVICEADDED", lib.SDL_JOYDEVICEREMOVED: "JOYDEVICEREMOVED"}[sdl_event.type] + type = { + lib.SDL_EVENT_JOYSTICK_ADDED: "JOYDEVICEADDED", + lib.SDL_EVENT_JOYSTICK_REMOVED: "JOYDEVICEREMOVED", + }[sdl_event.type] return cls(type, sdl_event.jdevice.which) @@ -1095,14 +1099,14 @@ def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerBu @classmethod def from_sdl_event(cls, sdl_event: Any) -> ControllerButton: type = { - lib.SDL_CONTROLLERBUTTONDOWN: "CONTROLLERBUTTONDOWN", - lib.SDL_CONTROLLERBUTTONUP: "CONTROLLERBUTTONUP", + lib.SDL_EVENT_GAMEPAD_BUTTON_DOWN: "CONTROLLERBUTTONDOWN", + lib.SDL_EVENT_GAMEPAD_BUTTON_UP: "CONTROLLERBUTTONUP", }[sdl_event.type] return cls( type, sdl_event.cbutton.which, tcod.sdl.joystick.ControllerButton(sdl_event.cbutton.button), - sdl_event.cbutton.state == lib.SDL_PRESSED, + bool(sdl_event.cbutton.down), ) def __repr__(self) -> str: @@ -1127,9 +1131,9 @@ class ControllerDevice(ControllerEvent): @classmethod def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: type = { - lib.SDL_CONTROLLERDEVICEADDED: "CONTROLLERDEVICEADDED", - lib.SDL_CONTROLLERDEVICEREMOVED: "CONTROLLERDEVICEREMOVED", - lib.SDL_CONTROLLERDEVICEREMAPPED: "CONTROLLERDEVICEREMAPPED", + lib.SDL_EVENT_GAMEPAD_ADDED: "CONTROLLERDEVICEADDED", + lib.SDL_EVENT_GAMEPAD_REMOVED: "CONTROLLERDEVICEREMOVED", + lib.SDL_EVENT_GAMEPAD_REMAPPED: "CONTROLLERDEVICEREMAPPED", }[sdl_event.type] return cls(type, sdl_event.cdevice.which) @@ -1153,28 +1157,28 @@ def __str__(self) -> str: _SDL_TO_CLASS_TABLE: dict[int, type[Event]] = { - lib.SDL_QUIT: Quit, - lib.SDL_KEYDOWN: KeyDown, - lib.SDL_KEYUP: KeyUp, - lib.SDL_MOUSEMOTION: MouseMotion, - lib.SDL_MOUSEBUTTONDOWN: MouseButtonDown, - lib.SDL_MOUSEBUTTONUP: MouseButtonUp, - lib.SDL_MOUSEWHEEL: MouseWheel, - lib.SDL_TEXTINPUT: TextInput, - lib.SDL_WINDOWEVENT: WindowEvent, - lib.SDL_JOYAXISMOTION: JoystickAxis, - lib.SDL_JOYBALLMOTION: JoystickBall, - lib.SDL_JOYHATMOTION: JoystickHat, - lib.SDL_JOYBUTTONDOWN: JoystickButton, - lib.SDL_JOYBUTTONUP: JoystickButton, - lib.SDL_JOYDEVICEADDED: JoystickDevice, - lib.SDL_JOYDEVICEREMOVED: JoystickDevice, - lib.SDL_CONTROLLERAXISMOTION: ControllerAxis, - lib.SDL_CONTROLLERBUTTONDOWN: ControllerButton, - lib.SDL_CONTROLLERBUTTONUP: ControllerButton, - lib.SDL_CONTROLLERDEVICEADDED: ControllerDevice, - lib.SDL_CONTROLLERDEVICEREMOVED: ControllerDevice, - lib.SDL_CONTROLLERDEVICEREMAPPED: ControllerDevice, + lib.SDL_EVENT_QUIT: Quit, + lib.SDL_EVENT_KEY_DOWN: KeyDown, + lib.SDL_EVENT_KEY_UP: KeyUp, + lib.SDL_EVENT_MOUSE_MOTION: MouseMotion, + lib.SDL_EVENT_MOUSE_BUTTON_DOWN: MouseButtonDown, + lib.SDL_EVENT_MOUSE_BUTTON_UP: MouseButtonUp, + lib.SDL_EVENT_MOUSE_WHEEL: MouseWheel, + lib.SDL_EVENT_TEXT_INPUT: TextInput, + # lib.SDL_EVENT_WINDOW_EVENT: WindowEvent, + lib.SDL_EVENT_JOYSTICK_AXIS_MOTION: JoystickAxis, + lib.SDL_EVENT_JOYSTICK_BALL_MOTION: JoystickBall, + lib.SDL_EVENT_JOYSTICK_HAT_MOTION: JoystickHat, + lib.SDL_EVENT_JOYSTICK_BUTTON_DOWN: JoystickButton, + lib.SDL_EVENT_JOYSTICK_BUTTON_UP: JoystickButton, + lib.SDL_EVENT_JOYSTICK_ADDED: JoystickDevice, + lib.SDL_EVENT_JOYSTICK_REMOVED: JoystickDevice, + lib.SDL_EVENT_GAMEPAD_AXIS_MOTION: ControllerAxis, + lib.SDL_EVENT_GAMEPAD_BUTTON_DOWN: ControllerButton, + lib.SDL_EVENT_GAMEPAD_BUTTON_UP: ControllerButton, + lib.SDL_EVENT_GAMEPAD_ADDED: ControllerDevice, + lib.SDL_EVENT_GAMEPAD_REMOVED: ControllerDevice, + lib.SDL_EVENT_GAMEPAD_REMAPPED: ControllerDevice, } @@ -1618,7 +1622,7 @@ def remove_watch(callback: Callable[[Event], None]) -> None: warnings.warn(f"{callback} is not an active event watcher, nothing was removed.", RuntimeWarning, stacklevel=2) return handle = _event_watch_handles[callback] - lib.SDL_DelEventWatch(lib._sdl_event_watcher, handle) + lib.SDL_RemoveEventWatch(lib._sdl_event_watcher, handle) del _event_watch_handles[callback] @@ -2129,35 +2133,41 @@ class Scancode(enum.IntEnum): RALT = 230 RGUI = 231 MODE = 257 - AUDIONEXT = 258 - AUDIOPREV = 259 - AUDIOSTOP = 260 - AUDIOPLAY = 261 - AUDIOMUTE = 262 - MEDIASELECT = 263 - WWW = 264 - MAIL = 265 - CALCULATOR = 266 - COMPUTER = 267 - AC_SEARCH = 268 - AC_HOME = 269 - AC_BACK = 270 - AC_FORWARD = 271 - AC_STOP = 272 - AC_REFRESH = 273 - AC_BOOKMARKS = 274 - BRIGHTNESSDOWN = 275 - BRIGHTNESSUP = 276 - DISPLAYSWITCH = 277 - KBDILLUMTOGGLE = 278 - KBDILLUMDOWN = 279 - KBDILLUMUP = 280 - EJECT = 281 - SLEEP = 282 - APP1 = 283 - APP2 = 284 - AUDIOREWIND = 285 - AUDIOFASTFORWARD = 286 + SLEEP = 258 + WAKE = 259 + CHANNEL_INCREMENT = 260 + CHANNEL_DECREMENT = 261 + MEDIA_PLAY = 262 + MEDIA_PAUSE = 263 + MEDIA_RECORD = 264 + MEDIA_FAST_FORWARD = 265 + MEDIA_REWIND = 266 + MEDIA_NEXT_TRACK = 267 + MEDIA_PREVIOUS_TRACK = 268 + MEDIA_STOP = 269 + MEDIA_EJECT = 270 + MEDIA_PLAY_PAUSE = 271 + MEDIA_SELECT = 272 + AC_NEW = 273 + AC_OPEN = 274 + AC_CLOSE = 275 + AC_EXIT = 276 + AC_SAVE = 277 + AC_PRINT = 278 + AC_PROPERTIES = 279 + AC_SEARCH = 280 + AC_HOME = 281 + AC_BACK = 282 + AC_FORWARD = 283 + AC_STOP = 284 + AC_REFRESH = 285 + AC_BOOKMARKS = 286 + SOFTLEFT = 287 + SOFTRIGHT = 288 + CALL = 289 + ENDCALL = 290 + RESERVED = 400 + COUNT = 512 # --- end --- @property @@ -2179,7 +2189,7 @@ def keysym(self) -> KeySym: Based on the current keyboard layout. """ _init_sdl_video() - return KeySym(lib.SDL_GetKeyFromScancode(self.value)) + return KeySym(lib.SDL_GetKeyFromScancode(self.value, 0, False)) # noqa: FBT003 @property def scancode(self) -> Scancode: @@ -2472,12 +2482,12 @@ class KeySym(enum.IntEnum): ESCAPE = 27 SPACE = 32 EXCLAIM = 33 - QUOTEDBL = 34 + DBLAPOSTROPHE = 34 HASH = 35 DOLLAR = 36 PERCENT = 37 AMPERSAND = 38 - QUOTE = 39 + APOSTROPHE = 39 LEFTPAREN = 40 RIGHTPAREN = 41 ASTERISK = 42 @@ -2508,34 +2518,47 @@ class KeySym(enum.IntEnum): RIGHTBRACKET = 93 CARET = 94 UNDERSCORE = 95 - BACKQUOTE = 96 - a = 97 - b = 98 - c = 99 - d = 100 - e = 101 - f = 102 - g = 103 - h = 104 - i = 105 - j = 106 - k = 107 - l = 108 # noqa: E741 - m = 109 - n = 110 - o = 111 - p = 112 - q = 113 - r = 114 - s = 115 - t = 116 - u = 117 - v = 118 - w = 119 - x = 120 - y = 121 - z = 122 + GRAVE = 96 + A = 97 + B = 98 + C = 99 + D = 100 + E = 101 + F = 102 + G = 103 + H = 104 + I = 105 # noqa: E741 + J = 106 + K = 107 + L = 108 + M = 109 + N = 110 + O = 111 # noqa: E741 + P = 112 + Q = 113 + R = 114 + S = 115 + T = 116 + U = 117 + V = 118 + W = 119 + X = 120 + Y = 121 + Z = 122 + LEFTBRACE = 123 + PIPE = 124 + RIGHTBRACE = 125 + TILDE = 126 DELETE = 127 + PLUSMINUS = 177 + EXTENDED_MASK = 536870912 + LEFT_TAB = 536870913 + LEVEL5_SHIFT = 536870914 + MULTI_KEY_COMPOSE = 536870915 + LMETA = 536870916 + RMETA = 536870917 + LHYPER = 536870918 + RHYPER = 536870919 SCANCODE_MASK = 1073741824 CAPSLOCK = 1073741881 F1 = 1073741882 @@ -2677,35 +2700,39 @@ class KeySym(enum.IntEnum): RALT = 1073742054 RGUI = 1073742055 MODE = 1073742081 - AUDIONEXT = 1073742082 - AUDIOPREV = 1073742083 - AUDIOSTOP = 1073742084 - AUDIOPLAY = 1073742085 - AUDIOMUTE = 1073742086 - MEDIASELECT = 1073742087 - WWW = 1073742088 - MAIL = 1073742089 - CALCULATOR = 1073742090 - COMPUTER = 1073742091 - AC_SEARCH = 1073742092 - AC_HOME = 1073742093 - AC_BACK = 1073742094 - AC_FORWARD = 1073742095 - AC_STOP = 1073742096 - AC_REFRESH = 1073742097 - AC_BOOKMARKS = 1073742098 - BRIGHTNESSDOWN = 1073742099 - BRIGHTNESSUP = 1073742100 - DISPLAYSWITCH = 1073742101 - KBDILLUMTOGGLE = 1073742102 - KBDILLUMDOWN = 1073742103 - KBDILLUMUP = 1073742104 - EJECT = 1073742105 - SLEEP = 1073742106 - APP1 = 1073742107 - APP2 = 1073742108 - AUDIOREWIND = 1073742109 - AUDIOFASTFORWARD = 1073742110 + SLEEP = 1073742082 + WAKE = 1073742083 + CHANNEL_INCREMENT = 1073742084 + CHANNEL_DECREMENT = 1073742085 + MEDIA_PLAY = 1073742086 + MEDIA_PAUSE = 1073742087 + MEDIA_RECORD = 1073742088 + MEDIA_FAST_FORWARD = 1073742089 + MEDIA_REWIND = 1073742090 + MEDIA_NEXT_TRACK = 1073742091 + MEDIA_PREVIOUS_TRACK = 1073742092 + MEDIA_STOP = 1073742093 + MEDIA_EJECT = 1073742094 + MEDIA_PLAY_PAUSE = 1073742095 + MEDIA_SELECT = 1073742096 + AC_NEW = 1073742097 + AC_OPEN = 1073742098 + AC_CLOSE = 1073742099 + AC_EXIT = 1073742100 + AC_SAVE = 1073742101 + AC_PRINT = 1073742102 + AC_PROPERTIES = 1073742103 + AC_SEARCH = 1073742104 + AC_HOME = 1073742105 + AC_BACK = 1073742106 + AC_FORWARD = 1073742107 + AC_STOP = 1073742108 + AC_REFRESH = 1073742109 + AC_BOOKMARKS = 1073742110 + SOFTLEFT = 1073742111 + SOFTRIGHT = 1073742112 + CALL = 1073742113 + ENDCALL = 1073742114 # --- end --- @property @@ -2744,7 +2771,7 @@ def scancode(self) -> Scancode: Based on the current keyboard layout. """ _init_sdl_video() - return Scancode(lib.SDL_GetScancodeFromKey(self.value)) + return Scancode(lib.SDL_GetScancodeFromKey(self.value, ffi.NULL)) @classmethod def _missing_(cls, value: object) -> KeySym | None: diff --git a/tcod/event_constants.py b/tcod/event_constants.py index 6f012293..efeecc56 100644 --- a/tcod/event_constants.py +++ b/tcod/event_constants.py @@ -218,35 +218,41 @@ SCANCODE_RALT = 230 SCANCODE_RGUI = 231 SCANCODE_MODE = 257 -SCANCODE_AUDIONEXT = 258 -SCANCODE_AUDIOPREV = 259 -SCANCODE_AUDIOSTOP = 260 -SCANCODE_AUDIOPLAY = 261 -SCANCODE_AUDIOMUTE = 262 -SCANCODE_MEDIASELECT = 263 -SCANCODE_WWW = 264 -SCANCODE_MAIL = 265 -SCANCODE_CALCULATOR = 266 -SCANCODE_COMPUTER = 267 -SCANCODE_AC_SEARCH = 268 -SCANCODE_AC_HOME = 269 -SCANCODE_AC_BACK = 270 -SCANCODE_AC_FORWARD = 271 -SCANCODE_AC_STOP = 272 -SCANCODE_AC_REFRESH = 273 -SCANCODE_AC_BOOKMARKS = 274 -SCANCODE_BRIGHTNESSDOWN = 275 -SCANCODE_BRIGHTNESSUP = 276 -SCANCODE_DISPLAYSWITCH = 277 -SCANCODE_KBDILLUMTOGGLE = 278 -SCANCODE_KBDILLUMDOWN = 279 -SCANCODE_KBDILLUMUP = 280 -SCANCODE_EJECT = 281 -SCANCODE_SLEEP = 282 -SCANCODE_APP1 = 283 -SCANCODE_APP2 = 284 -SCANCODE_AUDIOREWIND = 285 -SCANCODE_AUDIOFASTFORWARD = 286 +SCANCODE_SLEEP = 258 +SCANCODE_WAKE = 259 +SCANCODE_CHANNEL_INCREMENT = 260 +SCANCODE_CHANNEL_DECREMENT = 261 +SCANCODE_MEDIA_PLAY = 262 +SCANCODE_MEDIA_PAUSE = 263 +SCANCODE_MEDIA_RECORD = 264 +SCANCODE_MEDIA_FAST_FORWARD = 265 +SCANCODE_MEDIA_REWIND = 266 +SCANCODE_MEDIA_NEXT_TRACK = 267 +SCANCODE_MEDIA_PREVIOUS_TRACK = 268 +SCANCODE_MEDIA_STOP = 269 +SCANCODE_MEDIA_EJECT = 270 +SCANCODE_MEDIA_PLAY_PAUSE = 271 +SCANCODE_MEDIA_SELECT = 272 +SCANCODE_AC_NEW = 273 +SCANCODE_AC_OPEN = 274 +SCANCODE_AC_CLOSE = 275 +SCANCODE_AC_EXIT = 276 +SCANCODE_AC_SAVE = 277 +SCANCODE_AC_PRINT = 278 +SCANCODE_AC_PROPERTIES = 279 +SCANCODE_AC_SEARCH = 280 +SCANCODE_AC_HOME = 281 +SCANCODE_AC_BACK = 282 +SCANCODE_AC_FORWARD = 283 +SCANCODE_AC_STOP = 284 +SCANCODE_AC_REFRESH = 285 +SCANCODE_AC_BOOKMARKS = 286 +SCANCODE_SOFTLEFT = 287 +SCANCODE_SOFTRIGHT = 288 +SCANCODE_CALL = 289 +SCANCODE_ENDCALL = 290 +SCANCODE_RESERVED = 400 +SCANCODE_COUNT = 512 # --- SDL keyboard symbols --- K_UNKNOWN = 0 @@ -256,12 +262,12 @@ K_ESCAPE = 27 K_SPACE = 32 K_EXCLAIM = 33 -K_QUOTEDBL = 34 +K_DBLAPOSTROPHE = 34 K_HASH = 35 K_DOLLAR = 36 K_PERCENT = 37 K_AMPERSAND = 38 -K_QUOTE = 39 +K_APOSTROPHE = 39 K_LEFTPAREN = 40 K_RIGHTPAREN = 41 K_ASTERISK = 42 @@ -292,34 +298,47 @@ K_RIGHTBRACKET = 93 K_CARET = 94 K_UNDERSCORE = 95 -K_BACKQUOTE = 96 -K_a = 97 -K_b = 98 -K_c = 99 -K_d = 100 -K_e = 101 -K_f = 102 -K_g = 103 -K_h = 104 -K_i = 105 -K_j = 106 -K_k = 107 -K_l = 108 -K_m = 109 -K_n = 110 -K_o = 111 -K_p = 112 -K_q = 113 -K_r = 114 -K_s = 115 -K_t = 116 -K_u = 117 -K_v = 118 -K_w = 119 -K_x = 120 -K_y = 121 -K_z = 122 +K_GRAVE = 96 +K_A = 97 +K_B = 98 +K_C = 99 +K_D = 100 +K_E = 101 +K_F = 102 +K_G = 103 +K_H = 104 +K_I = 105 +K_J = 106 +K_K = 107 +K_L = 108 +K_M = 109 +K_N = 110 +K_O = 111 +K_P = 112 +K_Q = 113 +K_R = 114 +K_S = 115 +K_T = 116 +K_U = 117 +K_V = 118 +K_W = 119 +K_X = 120 +K_Y = 121 +K_Z = 122 +K_LEFTBRACE = 123 +K_PIPE = 124 +K_RIGHTBRACE = 125 +K_TILDE = 126 K_DELETE = 127 +K_PLUSMINUS = 177 +K_EXTENDED_MASK = 536870912 +K_LEFT_TAB = 536870913 +K_LEVEL5_SHIFT = 536870914 +K_MULTI_KEY_COMPOSE = 536870915 +K_LMETA = 536870916 +K_RMETA = 536870917 +K_LHYPER = 536870918 +K_RHYPER = 536870919 K_SCANCODE_MASK = 1073741824 K_CAPSLOCK = 1073741881 K_F1 = 1073741882 @@ -461,41 +480,46 @@ K_RALT = 1073742054 K_RGUI = 1073742055 K_MODE = 1073742081 -K_AUDIONEXT = 1073742082 -K_AUDIOPREV = 1073742083 -K_AUDIOSTOP = 1073742084 -K_AUDIOPLAY = 1073742085 -K_AUDIOMUTE = 1073742086 -K_MEDIASELECT = 1073742087 -K_WWW = 1073742088 -K_MAIL = 1073742089 -K_CALCULATOR = 1073742090 -K_COMPUTER = 1073742091 -K_AC_SEARCH = 1073742092 -K_AC_HOME = 1073742093 -K_AC_BACK = 1073742094 -K_AC_FORWARD = 1073742095 -K_AC_STOP = 1073742096 -K_AC_REFRESH = 1073742097 -K_AC_BOOKMARKS = 1073742098 -K_BRIGHTNESSDOWN = 1073742099 -K_BRIGHTNESSUP = 1073742100 -K_DISPLAYSWITCH = 1073742101 -K_KBDILLUMTOGGLE = 1073742102 -K_KBDILLUMDOWN = 1073742103 -K_KBDILLUMUP = 1073742104 -K_EJECT = 1073742105 -K_SLEEP = 1073742106 -K_APP1 = 1073742107 -K_APP2 = 1073742108 -K_AUDIOREWIND = 1073742109 -K_AUDIOFASTFORWARD = 1073742110 +K_SLEEP = 1073742082 +K_WAKE = 1073742083 +K_CHANNEL_INCREMENT = 1073742084 +K_CHANNEL_DECREMENT = 1073742085 +K_MEDIA_PLAY = 1073742086 +K_MEDIA_PAUSE = 1073742087 +K_MEDIA_RECORD = 1073742088 +K_MEDIA_FAST_FORWARD = 1073742089 +K_MEDIA_REWIND = 1073742090 +K_MEDIA_NEXT_TRACK = 1073742091 +K_MEDIA_PREVIOUS_TRACK = 1073742092 +K_MEDIA_STOP = 1073742093 +K_MEDIA_EJECT = 1073742094 +K_MEDIA_PLAY_PAUSE = 1073742095 +K_MEDIA_SELECT = 1073742096 +K_AC_NEW = 1073742097 +K_AC_OPEN = 1073742098 +K_AC_CLOSE = 1073742099 +K_AC_EXIT = 1073742100 +K_AC_SAVE = 1073742101 +K_AC_PRINT = 1073742102 +K_AC_PROPERTIES = 1073742103 +K_AC_SEARCH = 1073742104 +K_AC_HOME = 1073742105 +K_AC_BACK = 1073742106 +K_AC_FORWARD = 1073742107 +K_AC_STOP = 1073742108 +K_AC_REFRESH = 1073742109 +K_AC_BOOKMARKS = 1073742110 +K_SOFTLEFT = 1073742111 +K_SOFTRIGHT = 1073742112 +K_CALL = 1073742113 +K_ENDCALL = 1073742114 # --- SDL keyboard modifiers --- KMOD_NONE = 0 KMOD_LSHIFT = 1 KMOD_RSHIFT = 2 KMOD_SHIFT = 3 +KMOD_LEVEL5 = 4 KMOD_LCTRL = 64 KMOD_RCTRL = 128 KMOD_CTRL = 192 @@ -514,6 +538,7 @@ 1: "KMOD_LSHIFT", 2: "KMOD_RSHIFT", 3: "KMOD_SHIFT", + 4: "KMOD_LEVEL5", 64: "KMOD_LCTRL", 128: "KMOD_RCTRL", 192: "KMOD_CTRL", @@ -532,15 +557,12 @@ # --- SDL wheel --- MOUSEWHEEL_NORMAL = 0 MOUSEWHEEL_FLIPPED = 1 -MOUSEWHEEL = 1027 _REVERSE_WHEEL_TABLE = { 0: "MOUSEWHEEL_NORMAL", 1: "MOUSEWHEEL_FLIPPED", - 1027: "MOUSEWHEEL", } __all__ = [ # noqa: RUF022 "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", - "MOUSEWHEEL", ] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 517f3377..2bff5397 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -1679,7 +1679,7 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: Array access performs significantly faster than using this function. See :any:`Console.ch`. """ - return lib.TCOD_console_get_char(_console(con), x, y) # type: ignore + return lib.TCOD_console_get_char(_console(con), x, y) @deprecate("This function is not supported if contexts are being used.", category=FutureWarning) diff --git a/tcod/noise.py b/tcod/noise.py index 23d84691..b4631455 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -247,13 +247,16 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: indexes[i] = np.ascontiguousarray(index, dtype=np.float32) c_input[i] = ffi.from_buffer("float*", indexes[i]) + c_input_tuple = tuple(c_input) + assert len(c_input_tuple) == 4 # noqa: PLR2004 + out: NDArray[np.float32] = np.empty(indexes[0].shape, dtype=np.float32) if self.implementation == Implementation.SIMPLE: lib.TCOD_noise_get_vectorized( self.noise_c, self.algorithm, out.size, - *c_input, + *c_input_tuple, ffi.from_buffer("float*", out), ) elif self.implementation == Implementation.FBM: @@ -262,7 +265,7 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: self.algorithm, self.octaves, out.size, - *c_input, + *c_input_tuple, ffi.from_buffer("float*", out), ) elif self.implementation == Implementation.TURBULENCE: @@ -271,7 +274,7 @@ def __getitem__(self, indexes: Any) -> NDArray[np.float32]: self.algorithm, self.octaves, out.size, - *c_input, + *c_input_tuple, ffi.from_buffer("float*", out), ) else: diff --git a/tcod/path.c b/tcod/path.c index 873e2dae..61a09260 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -315,8 +315,8 @@ int compute_heuristic(const struct PathfinderHeuristic* __restrict heuristic, in default: return 0; } - int diagonal = heuristic->diagonal != 0 ? MIN(x, y) : 0; - int straight = MAX(x, y) - diagonal; + int diagonal = heuristic->diagonal != 0 ? TCOD_MIN(x, y) : 0; + int straight = TCOD_MAX(x, y) - diagonal; return (straight * heuristic->cardinal + diagonal * heuristic->diagonal + w * heuristic->w + z * heuristic->z); } void path_compute_add_edge( diff --git a/tcod/path.h b/tcod/path.h index dc87cae3..75754d1b 100644 --- a/tcod/path.h +++ b/tcod/path.h @@ -13,7 +13,7 @@ extern "C" { /** * Common NumPy data types. */ -enum NP_Type { +typedef enum NP_Type { np_undefined = 0, np_int8, np_int16, @@ -26,23 +26,23 @@ enum NP_Type { np_float16, np_float32, np_float64, -}; +} NP_Type; /** * A simple 4D NumPy array ctype. */ -struct NArray { - enum NP_Type type; +typedef struct NArray { + NP_Type type; int8_t ndim; char* __restrict data; ptrdiff_t shape[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 ptrdiff_t strides[5]; // TCOD_PATHFINDER_MAX_DIMENSIONS + 1 -}; +} NArray; struct PathfinderRule { /** Rule condition, could be uninitialized zeros. */ - struct NArray condition; + NArray condition; /** Edge cost map, required. */ - struct NArray cost; + NArray cost; /** Number of edge rules in `edge_array`. */ int edge_count; /** Example of 2D edges: [i, j, cost, i_2, j_2, cost_2, ...] */ @@ -131,7 +131,7 @@ int path_compute( parameters. */ ptrdiff_t get_travel_path( - int8_t ndim, const struct NArray* __restrict travel_map, const int* __restrict start, int* __restrict out); + int8_t ndim, const NArray* __restrict travel_map, const int* __restrict start, int* __restrict out); /** Update the priority of nodes on the frontier and sort them. */ @@ -142,7 +142,7 @@ int update_frontier_heuristic( Assumes no heuristic is active. */ -int rebuild_frontier_from_distance(struct TCOD_Frontier* __restrict frontier, const struct NArray* __restrict dist_map); +int rebuild_frontier_from_distance(struct TCOD_Frontier* __restrict frontier, const NArray* __restrict dist_map); /** Return true if `index[frontier->ndim]` is a node in `frontier`. */ diff --git a/tcod/path.py b/tcod/path.py index 92980b85..f16c0210 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -115,7 +115,7 @@ def __init__( super().__init__(callback, shape) -class NodeCostArray(np.ndarray): # type: ignore[type-arg] +class NodeCostArray(np.ndarray): """Calculate cost from a numpy array of nodes. `array` is a NumPy array holding the path-cost of each node. @@ -574,7 +574,7 @@ def hillclimb2d( c_edges, n_edges = _compile_bool_edges(edge_map) func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges, c_edges) else: - func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, cardinal, diagonal) + func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, bool(cardinal), bool(diagonal)) length = _check(func(ffi.NULL)) path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, 2), dtype=np.intc) c_path = ffi.from_buffer("int*", path) @@ -596,7 +596,7 @@ def _world_array(shape: tuple[int, ...], dtype: DTypeLike = np.int32) -> NDArray ) -def _as_hashable(obj: np.ndarray[Any, Any] | None) -> object | None: +def _as_hashable(obj: NDArray[Any] | None) -> object | None: """Return NumPy arrays as a more hashable form.""" if obj is None: return obj @@ -772,7 +772,7 @@ def add_edge( cost = cost.T if condition is not None: condition = condition.T - key = (_as_hashable(cost), _as_hashable(condition)) + key = (_as_hashable(cost), _as_hashable(condition)) # type: ignore[arg-type] try: rule = self._graph[key] except KeyError: @@ -789,7 +789,7 @@ def add_edge( def add_edges( self, *, - edge_map: ArrayLike, + edge_map: ArrayLike | NDArray[np.integer], cost: NDArray[Any], condition: ArrayLike | None = None, ) -> None: @@ -894,16 +894,20 @@ def add_edges( # edge_map needs to be converted into C. # The other parameters are converted by the add_edge method. edge_map = edge_map.T - edge_center = tuple(i // 2 for i in edge_map.shape) - edge_map[edge_center] = 0 - edge_map[edge_map < 0] = 0 - edge_nz = edge_map.nonzero() - edge_costs = edge_map[edge_nz] + edge_center = tuple(i // 2 for i in edge_map.shape) # type: ignore[union-attr] + edge_map[edge_center] = 0 # type: ignore[index] + edge_map[edge_map < 0] = 0 # type: ignore[index, operator] + edge_nz = edge_map.nonzero() # type: ignore[union-attr] + edge_costs = edge_map[edge_nz] # type: ignore[index] edge_array = np.transpose(edge_nz) edge_array -= edge_center - for edge, edge_cost in zip(edge_array, edge_costs, strict=True): + for edge, edge_cost in zip( + edge_array, + edge_costs, # type: ignore[arg-type] + strict=True, + ): self.add_edge( - tuple(edge.tolist()), # type: ignore[arg-type] + tuple(edge.tolist()), edge_cost, cost=cost, condition=condition, @@ -1170,7 +1174,7 @@ def traversal(self) -> NDArray[Any]: """ if self._order == "F": axes = range(self._travel.ndim) - return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] + return self._travel.transpose((*axes[-2::-1], axes[-1]))[..., ::-1] # type: ignore[no-any-return] return self._travel def clear(self) -> None: diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 23b41632..a671331e 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -3,10 +3,12 @@ from __future__ import annotations import logging -import sys as _sys +import sys from collections.abc import Callable from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, NoReturn, TypeVar +from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, overload, runtime_checkable + +from typing_extensions import Self from tcod.cffi import ffi, lib @@ -61,10 +63,92 @@ def __exit__( ) -> bool: if exc_type is None: return False - _sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) + sys.unraisablehook(_UnraisableHookArgs(exc_type, value, traceback, None, self.obj)) return True +@runtime_checkable +class PropertyPointer(Protocol): + """Methods for classes which support pointers being set to properties.""" + + @classmethod + def _from_property_pointer(cls, raw_cffi_pointer: Any, /) -> Self: # noqa: ANN401 + """Convert a raw pointer to this class.""" + ... + + def _as_property_pointer(self) -> Any: # noqa: ANN401 + """Return a CFFI pointer for this object.""" + ... + + +class Properties: + """SDL properties interface.""" + + def __init__(self, p: Any | None = None) -> None: # noqa: ANN401 + """Create new properties or use an existing pointer.""" + if p is None: + self.p = ffi.gc( + ffi.cast("SDL_PropertiesID", _check_int(lib.SDL_CreateProperties(), failure=0)), + lib.SDL_DestroyProperties, + ) + else: + self.p = p + + @overload + def __getitem__(self, key: tuple[str, type[bool]], /) -> bool: ... + @overload + def __getitem__(self, key: tuple[str, type[int]], /) -> int: ... + @overload + def __getitem__(self, key: tuple[str, type[float]], /) -> float: ... + @overload + def __getitem__(self, key: tuple[str, type[str]], /) -> str: ... + + def __getitem__(self, key: tuple[str, type[Any]], /) -> Any: + """Get a typed value from this property.""" + key_, type_ = key + name = key_.encode("utf-8") + match lib.SDL_GetPropertyType(self.p, name): + case lib.SDL_PROPERTY_TYPE_STRING: + assert type_ is str + return str(ffi.string(lib.SDL_GetStringProperty(self.p, name, ffi.NULL)), encoding="utf-8") + case lib.SDL_PROPERTY_TYPE_NUMBER: + assert type_ is int + return int(lib.SDL_GetNumberProperty(self.p, name, 0)) + case lib.SDL_PROPERTY_TYPE_FLOAT: + assert type_ is float + return float(lib.SDL_GetFloatProperty(self.p, name, 0.0)) + case lib.SDL_PROPERTY_TYPE_BOOLEAN: + assert type_ is bool + return bool(lib.SDL_GetBooleanProperty(self.p, name, False)) # noqa: FBT003 + case lib.SDL_PROPERTY_TYPE_POINTER: + assert isinstance(type_, PropertyPointer) + return type_._from_property_pointer(lib.SDL_GetPointerProperty(self.p, name, ffi.NULL)) + case lib.SDL_PROPERTY_TYPE_INVALID: + raise KeyError("Invalid type.") # noqa: EM101, TRY003 + case _: + raise AssertionError + + def __setitem__(self, key: tuple[str, type[T]], value: T, /) -> None: + """Assign a property.""" + key_, type_ = key + name = key_.encode("utf-8") + if type_ is str: + assert isinstance(value, str) + lib.SDL_SetStringProperty(self.p, name, value.encode("utf-8")) + elif type_ is int: + assert isinstance(value, int) + lib.SDL_SetNumberProperty(self.p, name, value) + elif type_ is float: + assert isinstance(value, (int, float)) + lib.SDL_SetFloatProperty(self.p, name, value) + elif type_ is bool: + lib.SDL_SetFloatProperty(self.p, name, bool(value)) + else: + assert isinstance(type_, PropertyPointer) + assert isinstance(value, PropertyPointer) + lib.SDL_SetPointerProperty(self.p, name, value._as_property_pointer()) + + @ffi.def_extern() # type: ignore[misc] def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 """Pass logs sent by SDL to Python's logging system.""" @@ -77,9 +161,23 @@ def _get_error() -> str: return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") -def _check(result: int) -> int: +def _check(result: bool, /) -> bool: + """Check if an SDL function returned without errors, and raise an exception if it did.""" + if not result: + raise RuntimeError(_get_error()) + return result + + +def _check_int(result: int, /, failure: int) -> int: + """Check if an SDL function returned without errors, and raise an exception if it did.""" + if result == failure: + raise RuntimeError(_get_error()) + return result + + +def _check_float(result: float, /, failure: float) -> float: """Check if an SDL function returned without errors, and raise an exception if it did.""" - if result < 0: + if result == failure: raise RuntimeError(_get_error()) return result @@ -92,13 +190,7 @@ def _check_p(result: Any) -> Any: # noqa: ANN401 def _compiled_version() -> tuple[int, int, int]: - return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL) - - -def _linked_version() -> tuple[int, int, int]: - sdl_version = ffi.new("SDL_version*") - lib.SDL_GetVersion(sdl_version) - return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch) + return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_MICRO_VERSION) def _version_at_least(required: tuple[int, int, int]) -> None: @@ -122,6 +214,6 @@ def replacement(*_args: object, **_kwargs: object) -> NoReturn: return lambda _: replacement # type: ignore[return-value] -lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL) +lib.SDL_SetLogOutputFunction(lib._sdl_log_output_function, ffi.NULL) if __debug__: - lib.SDL_LogSetAllPriority(lib.SDL_LOG_PRIORITY_VERBOSE) + lib.SDL_SetLogPriorities(lib.SDL_LOG_PRIORITY_VERBOSE) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 711d3fc4..d15abb32 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -8,35 +8,28 @@ It leaves the loading to sound samples to other libraries like `SoundFile `_. -Example:: - - # Synchronous audio example using SDL's low-level API. +Example: + # Synchronous audio example import time import soundfile # pip install soundfile import tcod.sdl.audio - device = tcod.sdl.audio.open() # Open the default output device. - sound, sample_rate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile. - converted = device.convert(sound, sample_rate) # Convert this sample to the format expected by the device. - device.queue_audio(converted) # Play audio synchronously by appending it to the device buffer. - - while device.queued_samples: # Wait until device is done playing. - time.sleep(0.001) - -Example:: + device = tcod.sdl.get_default_playback().open() # Open the default output device - # Asynchronous audio example using BasicMixer. - import time + # AudioDevice's can be opened again to form a hierarchy + # This can be used to give music and sound effects their own configuration + device_music = device.open() + device_music.gain = 0 # Mute music + device_effects = device.open() + device_effects.gain = 10 ** (-6 / 10) # -6dB - import soundfile # pip install soundfile - import tcod.sdl.audio + sound, sample_rate = soundfile.read("example_sound.wav", dtype="float32") # Load an audio sample using SoundFile + stream = device_effects.new_stream(format=sound.dtype, frequency=sample_rate, channels=sound.shape[1]) + stream.queue_audio(sound) # Play audio by appending it to the audio stream + stream.flush() - mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) # Setup BasicMixer with the default audio output. - sound, sample_rate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile. - sound = mixer.device.convert(sound, sample_rate) # Convert this sample to the format expected by the device. - channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread. - while channel.busy: # Wait until the sample is done playing. + while stream.queued_samples: # Wait until stream is finished time.sleep(0.001) .. versionadded:: 13.5 @@ -44,27 +37,29 @@ from __future__ import annotations +import contextlib import enum import sys import threading -import time -from collections.abc import Callable, Hashable, Iterator -from typing import TYPE_CHECKING, Any, Final, Literal +import weakref +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, Final, Literal, NamedTuple import numpy as np -from typing_extensions import Self +from typing_extensions import Self, deprecated import tcod.sdl.sys from tcod.cffi import ffi, lib -from tcod.sdl._internal import _check, _get_error, _ProtectedContext +from tcod.sdl._internal import _check, _check_float, _check_int, _check_p if TYPE_CHECKING: + from collections.abc import Callable, Hashable, Iterable, Iterator from types import TracebackType from numpy.typing import ArrayLike, DTypeLike, NDArray -def _get_format(format: DTypeLike) -> int: +def _get_format(format: DTypeLike, /) -> int: # noqa: A002 """Return a SDL_AudioFormat bit-field from a NumPy dtype.""" dt: Any = np.dtype(format) assert dt.fields is None @@ -81,33 +76,33 @@ def _get_format(format: DTypeLike) -> int: return int( bitsize - | (lib.SDL_AUDIO_MASK_DATATYPE * is_float) - | (lib.SDL_AUDIO_MASK_ENDIAN * (byteorder == ">")) + | (lib.SDL_AUDIO_MASK_FLOAT * is_float) + | (lib.SDL_AUDIO_MASK_BIG_ENDIAN * (byteorder == ">")) | (lib.SDL_AUDIO_MASK_SIGNED * is_signed) ) -def _dtype_from_format(format: int) -> np.dtype[Any]: +def _dtype_from_format(format: int, /) -> np.dtype[Any]: # noqa: A002 """Return a dtype from a SDL_AudioFormat. - >>> _dtype_from_format(tcod.lib.AUDIO_F32LSB) + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_F32LE) dtype('float32') - >>> _dtype_from_format(tcod.lib.AUDIO_F32MSB) + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_F32BE) dtype('>f4') - >>> _dtype_from_format(tcod.lib.AUDIO_S16LSB) + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S16LE) dtype('int16') - >>> _dtype_from_format(tcod.lib.AUDIO_S16MSB) + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S16BE) dtype('>i2') - >>> _dtype_from_format(tcod.lib.AUDIO_U16LSB) - dtype('uint16') - >>> _dtype_from_format(tcod.lib.AUDIO_U16MSB) - dtype('>u2') + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_S8) + dtype('int8') + >>> _dtype_from_format(tcod.lib.SDL_AUDIO_U8) + dtype('uint8') """ bitsize = format & lib.SDL_AUDIO_MASK_BITSIZE assert bitsize % 8 == 0 byte_size = bitsize // 8 - byteorder = ">" if format & lib.SDL_AUDIO_MASK_ENDIAN else "<" - if format & lib.SDL_AUDIO_MASK_DATATYPE: + byteorder = ">" if format & lib.SDL_AUDIO_MASK_BIG_ENDIAN else "<" + if format & lib.SDL_AUDIO_MASK_FLOAT: kind = "f" elif format & lib.SDL_AUDIO_MASK_SIGNED: kind = "i" @@ -116,12 +111,34 @@ def _dtype_from_format(format: int) -> np.dtype[Any]: return np.dtype(f"{byteorder}{kind}{byte_size}") +def _silence_value_for_format(dtype: DTypeLike, /) -> int: + """Return the silence value for the given dtype format.""" + return int(lib.SDL_GetSilenceValueForFormat(_get_format(dtype))) + + +class _AudioSpec(NamedTuple): + """Named tuple for `SDL_AudioSpec`.""" + + format: int + channels: int + frequency: int + + @classmethod + def from_c(cls, c_spec_p: Any) -> Self: # noqa: ANN401 + return cls(int(c_spec_p.format), int(c_spec_p.channels), int(c_spec_p.freq)) + + @property + def _dtype(self) -> np.dtype[Any]: + return _dtype_from_format(self.format) + + def convert_audio( in_sound: ArrayLike, in_rate: int, *, out_rate: int, out_format: DTypeLike, out_channels: int -) -> NDArray[Any]: +) -> NDArray[np.number]: """Convert an audio sample into a format supported by this device. - Returns the converted array. This might be a reference to the input array if no conversion was needed. + Returns the converted array in the shape `(sample, channel)`. + This will reference the input array data if no conversion was needed. Args: in_sound: The input ArrayLike sound sample. Input format and channels are derived from the array. @@ -130,6 +147,14 @@ def convert_audio( out_format: The output format of the converted array. out_channels: The number of audio channels of the output array. + Examples:: + + >>> tcod.sdl.audio.convert_audio(np.zeros(5), 44100, out_rate=44100, out_format=np.uint8, out_channels=1).T + array([[128, 128, 128, 128, 128]], dtype=uint8) + >>> tcod.sdl.audio.convert_audio(np.zeros(3), 22050, out_rate=44100, out_format=np.int8, out_channels=2).T + array([[0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0]], dtype=int8) + .. versionadded:: 13.6 .. versionchanged:: 16.0 @@ -141,22 +166,33 @@ def convert_audio( in_array: NDArray[Any] = np.asarray(in_sound) if len(in_array.shape) == 1: in_array = in_array[:, np.newaxis] - if len(in_array.shape) != 2: # noqa: PLR2004 + elif len(in_array.shape) != 2: # noqa: PLR2004 msg = f"Expected a 1 or 2 ndim input, got {in_array.shape} instead." raise TypeError(msg) - cvt = ffi.new("SDL_AudioCVT*") - in_channels = in_array.shape[1] - in_format = _get_format(in_array.dtype) - out_sdl_format = _get_format(out_format) + in_spec = _AudioSpec(format=_get_format(in_array.dtype), channels=in_array.shape[1], frequency=in_rate) + out_spec = _AudioSpec(format=_get_format(out_format), channels=out_channels, frequency=out_rate) + if in_spec == out_spec: + return in_array # No conversion needed + + out_buffer = ffi.new("uint8_t**") + out_length = ffi.new("int*") try: - if ( - _check(lib.SDL_BuildAudioCVT(cvt, in_format, in_channels, in_rate, out_sdl_format, out_channels, out_rate)) - == 0 - ): - return in_array # No conversion needed. + _check( + lib.SDL_ConvertAudioSamples( + [in_spec], + ffi.from_buffer("const uint8_t*", in_array), + len(in_array) * in_array.itemsize, + [out_spec], + out_buffer, + out_length, + ) + ) + return ( # type: ignore[no-any-return] + np.frombuffer(ffi.buffer(out_buffer[0], out_length[0]), dtype=out_format).reshape(-1, out_channels).copy() + ) except RuntimeError as exc: if ( # SDL now only supports float32, but later versions may add more support for more formats. - exc.args[0] == "Invalid source format" + exc.args[0] == "Parameter 'src_spec->format' is invalid" and np.issubdtype(in_array.dtype, np.floating) and in_array.dtype != np.float32 ): @@ -168,68 +204,81 @@ def convert_audio( out_channels=out_channels, ) raise - # Upload to the SDL_AudioCVT buffer. - cvt.len = in_array.itemsize * in_array.size - out_buffer = cvt.buf = ffi.new("uint8_t[]", cvt.len * cvt.len_mult) - np.frombuffer(ffi.buffer(out_buffer[0 : cvt.len]), dtype=in_array.dtype).reshape(in_array.shape)[:] = in_array - - _check(lib.SDL_ConvertAudio(cvt)) - out_array: NDArray[Any] = ( - np.frombuffer(ffi.buffer(out_buffer[0 : cvt.len_cvt]), dtype=out_format).reshape(-1, out_channels).copy() - ) - return out_array + finally: + lib.SDL_free(out_buffer[0]) class AudioDevice: """An SDL audio device. - Open new audio devices using :any:`tcod.sdl.audio.open`. - - When you use this object directly the audio passed to :any:`queue_audio` is always played synchronously. - For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`. + Example: + device = tcod.sdl.audio.get_default_playback().open() # Open a common audio device .. versionchanged:: 16.0 Can now be used as a context which will close the device on exit. + + .. versionchanged:: Unreleased + Removed `spec` and `callback` attribute. + + `queued_samples`, `queue_audio`, and `dequeue_audio` moved to :any:`AudioStream` class. + """ + __slots__ = ( + "__weakref__", + "_device_id", + "buffer_bytes", + "buffer_samples", + "channels", + "device_id", + "format", + "frequency", + "is_capture", + "is_physical", + "silence", + ) + def __init__( self, - device_id: int, - capture: bool, - spec: Any, # SDL_AudioSpec* # noqa: ANN401 + device_id: Any, # noqa: ANN401 + /, ) -> None: + """Initialize the class from a raw `SDL_AudioDeviceID`.""" assert device_id >= 0 - assert ffi.typeof(spec) is ffi.typeof("SDL_AudioSpec*") - assert spec - self.device_id: Final[int] = device_id + assert ffi.typeof(device_id) is ffi.typeof("SDL_AudioDeviceID"), ffi.typeof(device_id) + spec = ffi.new("SDL_AudioSpec*") + samples = ffi.new("int*") + _check(lib.SDL_GetAudioDeviceFormat(device_id, spec, samples)) + self._device_id: object = device_id + self.device_id: Final[int] = int(device_id) """The SDL device identifier used for SDL C functions.""" - self.spec: Final[Any] = spec - """The SDL_AudioSpec as a CFFI object.""" self.frequency: Final[int] = spec.freq """The audio device sound frequency.""" - self.is_capture: Final[bool] = capture + self.is_capture: Final[bool] = bool(not lib.SDL_IsAudioDevicePlayback(device_id)) """True if this is a recording device instead of an output device.""" self.format: Final[np.dtype[Any]] = _dtype_from_format(spec.format) """The format used for audio samples with this device.""" self.channels: Final[int] = int(spec.channels) """The number of audio channels for this device.""" - self.silence: float = int(spec.silence) + self.silence: float = int(lib.SDL_GetSilenceValueForFormat(spec.format)) """The value of silence, according to SDL.""" - self.buffer_samples: Final[int] = int(spec.samples) + self.buffer_samples: Final[int] = int(samples[0]) """The size of the audio buffer in samples.""" - self.buffer_bytes: Final[int] = int(spec.size) + self.buffer_bytes: Final[int] = int(self.format.itemsize * self.channels * self.buffer_samples) """The size of the audio buffer in bytes.""" - self._handle: Any | None = None - self._callback: Callable[[AudioDevice, NDArray[Any]], None] = self.__default_callback + self.is_physical: Final[bool] = bool(lib.SDL_IsAudioDevicePhysical(device_id)) + """True of this is a physical device, or False if this is a logical device. + + .. versionadded:: Unreleased + """ def __repr__(self) -> str: """Return a representation of this device.""" - if self.stopped: - return f"<{self.__class__.__name__}() stopped=True>" items = [ f"{self.__class__.__name__}(device_id={self.device_id})", f"frequency={self.frequency}", f"is_capture={self.is_capture}", + f"is_physical={self.is_physical}", f"format={self.format}", f"channels={self.channels}", f"buffer_samples={self.buffer_samples}", @@ -239,24 +288,56 @@ def __repr__(self) -> str: if self.silence: items.append(f"silence={self.silence}") - if self._handle is not None: - items.append(f"callback={self._callback}") return f"""<{" ".join(items)}>""" @property - def callback(self) -> Callable[[AudioDevice, NDArray[Any]], None]: - """If the device was opened with a callback enabled, then you may get or set the callback with this attribute.""" - if self._handle is None: - msg = "This AudioDevice was opened without a callback." - raise TypeError(msg) - return self._callback + def name(self) -> str: + """Name of the device. - @callback.setter - def callback(self, new_callback: Callable[[AudioDevice, NDArray[Any]], None]) -> None: - if self._handle is None: - msg = "This AudioDevice was opened without a callback." - raise TypeError(msg) - self._callback = new_callback + .. versionadded:: Unreleased + """ + return str(ffi.string(_check_p(lib.SDL_GetAudioDeviceName(self.device_id))), encoding="utf-8") + + @property + def gain(self) -> float: + """Get or set the logical audio device gain. + + Default is 1.0 but can be set higher or zero. + + .. versionadded:: Unreleased + """ + return _check_float(lib.SDL_GetAudioDeviceGain(self.device_id), failure=-1.0) + + @gain.setter + def gain(self, value: float, /) -> None: + _check(lib.SDL_SetAudioDeviceGain(self.device_id, value)) + + def open( + self, + format: DTypeLike | None = None, # noqa: A002 + channels: int | None = None, + frequency: int | None = None, + ) -> Self: + """Open a new logical audio device for this device. + + .. versionadded:: Unreleased + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_OpenAudioDevice + """ + new_spec = _AudioSpec( + format=_get_format(format if format is not None else self.format), + channels=channels if channels is not None else self.channels, + frequency=frequency if frequency is not None else self.frequency, + ) + return self.__class__( + ffi.gc( + ffi.cast( + "SDL_AudioDeviceID", _check_int(lib.SDL_OpenAudioDevice(self.device_id, (new_spec,)), failure=0) + ), + lib.SDL_CloseAudioDevice, + ) + ) @property def _sample_size(self) -> int: @@ -264,20 +345,26 @@ def _sample_size(self) -> int: return self.format.itemsize * self.channels @property + @deprecated("This is no longer used by the SDL3 API") def stopped(self) -> bool: - """Is True if the device has failed or was closed.""" - if not hasattr(self, "device_id"): - return True - return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) == lib.SDL_AUDIO_STOPPED) + """Is True if the device has failed or was closed. + + .. deprecated:: Unreleased + No longer used by the SDL3 API. + """ + return bool(not hasattr(self, "device_id")) @property def paused(self) -> bool: """Get or set the device paused state.""" - return bool(lib.SDL_GetAudioDeviceStatus(self.device_id) != lib.SDL_AUDIO_PLAYING) + return bool(lib.SDL_AudioDevicePaused(self.device_id)) @paused.setter def paused(self, value: bool) -> None: - lib.SDL_PauseAudioDevice(self.device_id, value) + if value: + _check(lib.SDL_PauseAudioDevice(self.device_id)) + else: + _check(lib.SDL_ResumeAudioDevice(self.device_id)) def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]: if samples.dtype != self.format: @@ -285,15 +372,15 @@ def _verify_array_format(self, samples: NDArray[Any]) -> NDArray[Any]: raise TypeError(msg) return samples - def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]: + def _convert_array(self, samples_: ArrayLike) -> NDArray[np.number]: if isinstance(samples_, np.ndarray): samples_ = self._verify_array_format(samples_) - samples: NDArray[Any] = np.asarray(samples_, dtype=self.format) + samples: NDArray[np.number] = np.asarray(samples_, dtype=self.format) if len(samples.shape) < 2: # noqa: PLR2004 samples = samples[:, np.newaxis] return np.ascontiguousarray(np.broadcast_to(samples, (samples.shape[0], self.channels)), dtype=self.format) - def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[Any]: + def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[np.number]: """Convert an audio sample into a format supported by this device. Returns the converted array. This might be a reference to the input array if no conversion was needed. @@ -319,50 +406,27 @@ def convert(self, sound: ArrayLike, rate: int | None = None) -> NDArray[Any]: out_rate=self.frequency, ) - @property - def _queued_bytes(self) -> int: - """The current amount of bytes remaining in the audio queue.""" - return int(lib.SDL_GetQueuedAudioSize(self.device_id)) - - @property - def queued_samples(self) -> int: - """The current amount of samples remaining in the audio queue.""" - return self._queued_bytes // self._sample_size - - def queue_audio(self, samples: ArrayLike) -> None: - """Append audio samples to the audio data queue.""" - assert not self.is_capture - samples = self._convert_array(samples) - buffer = ffi.from_buffer(samples) - lib.SDL_QueueAudio(self.device_id, buffer, len(buffer)) - - def dequeue_audio(self) -> NDArray[Any]: - """Return the audio buffer from a capture stream.""" - assert self.is_capture - out_samples = self._queued_bytes // self._sample_size - out = np.empty((out_samples, self.channels), self.format) - buffer = ffi.from_buffer(out) - bytes_returned = lib.SDL_DequeueAudio(self.device_id, buffer, len(buffer)) - samples_returned = bytes_returned // self._sample_size - assert samples_returned == out_samples - return out - - def __del__(self) -> None: - self.close() - def close(self) -> None: """Close this audio device. Using this object after it has been closed is invalid.""" if not hasattr(self, "device_id"): return - lib.SDL_CloseAudioDevice(self.device_id) - del self.device_id + ffi.release(self._device_id) + del self._device_id + @deprecated("Use contextlib.closing if you want to close this device after a context.") def __enter__(self) -> Self: - """Return self and enter a managed context.""" + """Return self and enter a managed context. + + .. deprecated:: Unreleased + Use :func:`contextlib.closing` if you want to close this device after a context. + """ return self def __exit__( - self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None + self, + type: type[BaseException] | None, # noqa: A002 + value: BaseException | None, + traceback: TracebackType | None, ) -> None: """Close the device when exiting the context.""" self.close() @@ -371,6 +435,264 @@ def __exit__( def __default_callback(device: AudioDevice, stream: NDArray[Any]) -> None: stream[...] = device.silence + def new_stream( + self, + format: DTypeLike, # noqa: A002 + channels: int, + frequency: int, + ) -> AudioStream: + """Create, bind, and return a new :any:`AudioStream` for this device. + + .. versionadded:: Unreleased + """ + new_stream = AudioStream.new(format=format, channels=channels, frequency=frequency) + self.bind((new_stream,)) + return new_stream + + def bind(self, streams: Iterable[AudioStream], /) -> None: + """Bind one or more :any:`AudioStream`'s to this device. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_BindAudioStreams + """ + streams = list(streams) + _check(lib.SDL_BindAudioStreams(self.device_id, [s._stream_p for s in streams], len(streams))) + + +@dataclass(frozen=True) +class AudioStreamCallbackData: + """Data provided to AudioStream callbacks. + + .. versionadded:: Unreleased + """ + + additional_bytes: int + """Amount of bytes needed to fulfill the request of the caller. Can be zero.""" + additional_samples: int + """Amount of samples needed to fulfill the request of the caller. Can be zero.""" + total_bytes: int + """Amount of bytes requested or provided by the caller.""" + total_samples: int + """Amount of samples requested or provided by the caller.""" + + +_audio_stream_get_callbacks: dict[AudioStream, Callable[[AudioStream, AudioStreamCallbackData], Any]] = {} +_audio_stream_put_callbacks: dict[AudioStream, Callable[[AudioStream, AudioStreamCallbackData], Any]] = {} + +_audio_stream_registry: weakref.WeakValueDictionary[int, AudioStream] = weakref.WeakValueDictionary() + + +class AudioStream: + """An SDL audio stream. + + This class is commonly created with :any:`AudioDevice.new_stream` which creates a new stream bound to the device. + + ..versionadded:: Unreleased + """ + + __slots__ = ("__weakref__", "_stream_p") + + _stream_p: Any + + def __new__( # noqa: PYI034 + cls, + stream_p: Any, # noqa: ANN401 + /, + ) -> AudioStream: + """Return an AudioStream for the provided `SDL_AudioStream*` C pointer.""" + assert ffi.typeof(stream_p) is ffi.typeof("SDL_AudioStream*"), ffi.typeof(stream_p) + stream_int = int(ffi.cast("intptr_t", stream_p)) + self = super().__new__(cls) + self._stream_p = stream_p + return _audio_stream_registry.setdefault(stream_int, self) + + @classmethod + def new( # noqa: PLR0913 + cls, + format: DTypeLike, # noqa: A002 + channels: int, + frequency: int, + out_format: DTypeLike | None = None, + out_channels: int | None = None, + out_frequency: int | None = None, + ) -> Self: + """Create a new unbound AudioStream.""" + in_spec = _AudioSpec(format=_get_format(format), channels=channels, frequency=frequency) + out_spec = _AudioSpec( + format=_get_format(out_format) if out_format is not None else in_spec.format, + channels=out_channels if out_channels is not None else channels, + frequency=out_frequency if out_frequency is not None else frequency, + ) + return cls(ffi.gc(_check_p(lib.SDL_CreateAudioStream((in_spec,), (out_spec,))), lib.SDL_DestroyAudioStream)) + + def close(self) -> None: + """Close this AudioStream and release its resources.""" + if not hasattr(self, "_stream_p"): + return + self.getter_callback = None + self.putter_callback = None + ffi.release(self._stream_p) + + def unbind(self) -> None: + """Unbind this stream from its currently bound device.""" + lib.SDL_UnbindAudioStream(self._stream_p) + + @property + @contextlib.contextmanager + def _lock(self) -> Iterator[None]: + """Lock context for this stream.""" + try: + lib.SDL_LockAudioStream(self._stream_p) + yield + finally: + lib.SDL_UnlockAudioStream(self._stream_p) + + @property + def _src_spec(self) -> _AudioSpec: + c_spec = ffi.new("SDL_AudioSpec*") + _check(lib.SDL_GetAudioStreamFormat(self._stream_p, c_spec, ffi.NULL)) + return _AudioSpec.from_c(c_spec) + + @property + def _src_sample_size(self) -> int: + spec = self._src_spec + return spec._dtype.itemsize * spec.channels + + @property + def _dst_sample_size(self) -> int: + spec = self._dst_spec + return spec._dtype.itemsize * spec.channels + + @property + def _dst_spec(self) -> _AudioSpec: + c_spec = ffi.new("SDL_AudioSpec*") + _check(lib.SDL_GetAudioStreamFormat(self._stream_p, ffi.NULL, c_spec)) + return _AudioSpec.from_c(c_spec) + + @property + def queued_bytes(self) -> int: + """The current amount of bytes remaining in the audio queue.""" + return _check_int(lib.SDL_GetAudioStreamQueued(self._stream_p), failure=-1) + + @property + def queued_samples(self) -> int: + """The estimated amount of samples remaining in the audio queue.""" + return self.queued_bytes // self._src_sample_size + + @property + def available_bytes(self) -> int: + """The current amount of converted data in this audio stream.""" + return _check_int(lib.SDL_GetAudioStreamAvailable(self._stream_p), failure=-1) + + @property + def available_samples(self) -> int: + """The current amount of converted samples in this audio stream.""" + return self.available_bytes // self._dst_sample_size + + def queue_audio(self, samples: ArrayLike) -> None: + """Append audio samples to the audio data queue.""" + with self._lock: + src_spec = self._src_spec + src_format = _dtype_from_format(src_spec.format) + if isinstance(samples, np.ndarray) and samples.dtype != src_format: + msg = f"Expected an array of dtype {src_format}, got {samples.dtype} instead." + raise TypeError(msg) + samples = np.asarray(samples, dtype=src_format) + if len(samples.shape) < 2: # noqa: PLR2004 + samples = samples[:, np.newaxis] + samples = np.ascontiguousarray( + np.broadcast_to(samples, (samples.shape[0], src_spec.channels)), dtype=src_format + ) + buffer = ffi.from_buffer(samples) + _check(lib.SDL_PutAudioStreamData(self._stream_p, buffer, len(buffer))) + + def flush(self) -> None: + """Ensure all queued data is available. + + This may queue silence to the end of the stream. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_FlushAudioStream + """ + _check(lib.SDL_FlushAudioStream(self._stream_p)) + + def dequeue_audio(self) -> NDArray[Any]: + """Return the converted output audio from this stream.""" + with self._lock: + dst_spec = self._dst_spec + out_samples = self.available_samples + out = np.empty((out_samples, dst_spec.channels), _dtype_from_format(dst_spec.format)) + buffer = ffi.from_buffer(out) + bytes_returned = _check_int(lib.SDL_GetAudioStreamData(self._stream_p, buffer, len(buffer)), failure=-1) + samples_returned = bytes_returned // self._dst_sample_size + return out[:samples_returned] + + @property + def gain(self) -> float: + """Get or set the audio stream gain. + + Default is 1.0 but can be set higher or zero. + """ + return _check_float(lib.SDL_GetAudioStreamGain(self._stream_p), failure=-1.0) + + @gain.setter + def gain(self, value: float, /) -> None: + _check(lib.SDL_SetAudioStreamGain(self._stream_p, value)) + + @property + def frequency_ratio(self) -> float: + """Get or set the frequency ratio, affecting the speed and pitch of the stream. + + Higher values play the audio faster. + + Default is 1.0. + """ + return _check_float(lib.SDL_GetAudioStreamFrequencyRatio(self._stream_p), failure=-1.0) + + @frequency_ratio.setter + def frequency_ratio(self, value: float, /) -> None: + _check(lib.SDL_SetAudioStreamFrequencyRatio(self._stream_p, value)) + + @property + def getter_callback(self) -> Callable[[AudioStream, AudioStreamCallbackData], Any] | None: + """Get or assign the stream get-callback for this stream. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_SetAudioStreamGetCallback + """ + return _audio_stream_get_callbacks.get(self) + + @getter_callback.setter + def getter_callback(self, callback: Callable[[AudioStream, AudioStreamCallbackData], Any] | None, /) -> None: + if callback is None: + _check(lib.SDL_SetAudioStreamGetCallback(self._stream_p, ffi.NULL, ffi.NULL)) + _audio_stream_get_callbacks.pop(self, None) + else: + _audio_stream_get_callbacks[self] = callback + _check( + lib.SDL_SetAudioStreamGetCallback(self._stream_p, lib._sdl_audio_stream_callback, ffi.cast("void*", 0)) + ) + + @property + def putter_callback(self) -> Callable[[AudioStream, AudioStreamCallbackData], Any] | None: + """Get or assign the stream put-callback for this stream. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_SetAudioStreamPutCallback + """ + return _audio_stream_put_callbacks.get(self) + + @putter_callback.setter + def putter_callback(self, callback: Callable[[AudioStream, AudioStreamCallbackData], Any] | None, /) -> None: + if callback is None: + _check(lib.SDL_SetAudioStreamPutCallback(self._stream_p, ffi.NULL, ffi.NULL)) + _audio_stream_put_callbacks.pop(self, None) + else: + _audio_stream_put_callbacks[self] = callback + _check( + lib.SDL_SetAudioStreamPutCallback(self._stream_p, lib._sdl_audio_stream_callback, ffi.cast("void*", 1)) + ) + class _LoopSoundFunc: def __init__(self, sound: NDArray[Any], loops: int, on_end: Callable[[Channel], None] | None) -> None: @@ -398,6 +720,7 @@ class Channel: """The :any:`BasicMixer` is channel belongs to.""" def __init__(self) -> None: + """Initialize this channel with generic attributes.""" self._lock = threading.RLock() self.volume: float | tuple[float, ...] = 1.0 self.sound_queue: list[NDArray[Any]] = [] @@ -471,37 +794,53 @@ def stop(self) -> None: self.fadeout(0.0005) -class BasicMixer(threading.Thread): +@deprecated( + "Changes in the SDL3 API have made this classes usefulness questionable." + "\nThis class should be replaced with custom streams." +) +class BasicMixer: """An SDL sound mixer implemented in Python and Numpy. + Example:: + + import time + + import soundfile # pip install soundfile + import tcod.sdl.audio + + device = tcod.sdl.audio.get_default_playback().open() + mixer = tcod.sdl.audio.BasicMixer(device) # Setup BasicMixer with the default audio output + sound, sample_rate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile + sound = mixer.device.convert(sound, sample_rate) # Convert this sample to the format expected by the device + channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread + while channel.busy: # Wait until the sample is done playing + time.sleep(0.001) + + .. versionadded:: 13.6 + + .. versionchanged:: Unreleased + Added `frequency` and `channels` parameters. + + .. deprecated:: Unreleased + Changes in the SDL3 API have made this classes usefulness questionable. + This class should be replaced with custom streams. """ - def __init__(self, device: AudioDevice) -> None: + def __init__(self, device: AudioDevice, *, frequency: int | None = None, channels: int | None = None) -> None: + """Initialize this mixer using the provided device.""" self.channels: dict[Hashable, Channel] = {} - assert device.format == np.float32 - super().__init__(daemon=True) self.device = device """The :any:`AudioDevice`""" + self._frequency = frequency if frequency is not None else device.frequency + self._channels = channels if channels is not None else device.channels self._lock = threading.RLock() - self._running = True - self.start() - - def run(self) -> None: - buffer = np.full( - (self.device.buffer_samples, self.device.channels), self.device.silence, dtype=self.device.format - ) - while self._running: - if self.device._queued_bytes > 0: - time.sleep(0.001) - continue - self._on_stream(buffer) - self.device.queue_audio(buffer) - buffer[:] = self.device.silence + self._stream = device.new_stream(format=np.float32, frequency=self._frequency, channels=self._channels) + self._stream.getter_callback = self._on_stream def close(self) -> None: """Shutdown this mixer, all playing audio will be abruptly stopped.""" - self._running = False + self._stream.close() def get_channel(self, key: Hashable) -> Channel: """Return a channel tied to with the given key. @@ -554,47 +893,98 @@ def stop(self) -> None: for channel in self.channels.values(): channel.stop() - def _on_stream(self, stream: NDArray[Any]) -> None: + def _on_stream(self, audio_stream: AudioStream, data: AudioStreamCallbackData) -> None: """Called to fill the audio buffer.""" + if data.additional_samples <= 0: + return + stream: NDArray[np.float32] = np.zeros((data.additional_samples, self._channels), dtype=np.float32) with self._lock: for channel in list(self.channels.values()): channel._on_mix(stream) - - -class _AudioCallbackUserdata: - device: AudioDevice + audio_stream.queue_audio(stream) @ffi.def_extern() # type: ignore[misc] -def _sdl_audio_callback(userdata: Any, stream: Any, length: int) -> None: # noqa: ANN401 +def _sdl_audio_stream_callback(userdata: Any, stream_p: Any, additional_amount: int, total_amount: int, /) -> None: # noqa: ANN401 """Handle audio device callbacks.""" - data: _AudioCallbackUserdata = ffi.from_handle(userdata) - device = data.device - buffer = np.frombuffer(ffi.buffer(stream, length), dtype=device.format).reshape(-1, device.channels) - with _ProtectedContext(device): - device._callback(device, buffer) + stream = AudioStream(stream_p) + is_put_callback = bool(userdata) + callback = (_audio_stream_put_callbacks if is_put_callback else _audio_stream_get_callbacks).get(stream) + if callback is None: + return + sample_size = stream._dst_sample_size if is_put_callback else stream._src_sample_size + callback( + stream, + AudioStreamCallbackData( + additional_bytes=additional_amount, + additional_samples=additional_amount // sample_size, + total_bytes=total_amount, + total_samples=total_amount // sample_size, + ), + ) + +def get_devices() -> dict[str, AudioDevice]: + """Iterate over the available audio output devices. -def _get_devices(capture: bool) -> Iterator[str]: - """Get audio devices from SDL_GetAudioDeviceName.""" - with tcod.sdl.sys._ScopeInit(tcod.sdl.sys.Subsystem.AUDIO): - device_count = lib.SDL_GetNumAudioDevices(capture) - for i in range(device_count): - yield str(ffi.string(lib.SDL_GetAudioDeviceName(i, capture)), encoding="utf-8") + .. versionchanged:: Unreleased + Now returns a dictionary of :any:`AudioDevice`. + """ + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) + count = ffi.new("int[1]") + devices_array = ffi.gc(lib.SDL_GetAudioPlaybackDevices(count), lib.SDL_free) + return { + device.name: device + for device in (AudioDevice(ffi.cast("SDL_AudioDeviceID", p)) for p in devices_array[0 : count[0]]) + } -def get_devices() -> Iterator[str]: - """Iterate over the available audio output devices.""" - yield from _get_devices(capture=False) +def get_capture_devices() -> dict[str, AudioDevice]: + """Iterate over the available audio capture devices. + .. versionchanged:: Unreleased + Now returns a dictionary of :any:`AudioDevice`. + """ + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) + count = ffi.new("int[1]") + devices_array = ffi.gc(lib.SDL_GetAudioRecordingDevices(count), lib.SDL_free) + return { + device.name: device + for device in (AudioDevice(ffi.cast("SDL_AudioDeviceID", p)) for p in devices_array[0 : count[0]]) + } -def get_capture_devices() -> Iterator[str]: - """Iterate over the available audio capture devices.""" - yield from _get_devices(capture=True) +def get_default_playback() -> AudioDevice: + """Return the default playback device. + Example: + playback_device = tcod.sdl.audio.get_default_playback().open() + + .. versionadded:: Unreleased + """ + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) + return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)) + + +def get_default_recording() -> AudioDevice: + """Return the default recording device. + + Example: + recording_device = tcod.sdl.audio.get_default_recording().open() + + .. versionadded:: Unreleased + """ + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) + return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_RECORDING)) + + +@deprecated("This is no longer used", category=FutureWarning) class AllowedChanges(enum.IntFlag): - """Which parameters are allowed to be changed when the values given are not supported.""" + """Which parameters are allowed to be changed when the values given are not supported. + + .. deprecated:: Unreleased + This is no longer used. + """ NONE = 0 """""" @@ -610,15 +1000,18 @@ class AllowedChanges(enum.IntFlag): """""" -def open( # noqa: PLR0913 +@deprecated( + "This is an outdated method.\nUse 'tcod.sdl.audio.get_default_playback().open()' instead.", category=FutureWarning +) +def open( # noqa: A001, PLR0913 name: str | None = None, - capture: bool = False, + capture: bool = False, # noqa: FBT001, FBT002 *, frequency: int = 44100, - format: DTypeLike = np.float32, + format: DTypeLike = np.float32, # noqa: A002 channels: int = 2, - samples: int = 0, - allowed_changes: AllowedChanges = AllowedChanges.NONE, + samples: int = 0, # noqa: ARG001 + allowed_changes: AllowedChanges = AllowedChanges.NONE, # noqa: ARG001 paused: bool = False, callback: None | Literal[True] | Callable[[AudioDevice, NDArray[Any]], None] = None, ) -> AudioDevice: @@ -630,65 +1023,47 @@ def open( # noqa: PLR0913 frequency: The desired sample rate to open the device with. format: The data format to use for samples as a NumPy dtype. channels: The number of speakers for the device. 1, 2, 4, or 6 are typical options. - samples: The desired size of the audio buffer, must be a power of two. - allowed_changes: - By default if the hardware does not support the desired format than SDL will transparently convert between - formats for you. - Otherwise you can specify which parameters are allowed to be changed to fit the hardware better. + samples: This parameter is ignored. + allowed_changes: This parameter is ignored. paused: If True then the device will begin in a paused state. It can then be unpaused by assigning False to :any:`AudioDevice.paused`. - callback: - If None then this device will be opened in push mode and you'll have to use :any:`AudioDevice.queue_audio` - to send audio data or :any:`AudioDevice.dequeue_audio` to receive it. - If a callback is given then you can change it later, but you can not enable or disable the callback on an - opened device. - If True then a default callback which plays silence will be used, this is useful if you need the audio - device before your callback is ready. + callback: An optional callback to use, this is deprecated. If a callback is given then it will be called with the `AudioDevice` and a Numpy buffer of the data stream. This callback will be run on a separate thread. - Exceptions not handled by the callback become unraiseable and will be handled by :any:`sys.unraisablehook`. - .. seealso:: - https://wiki.libsdl.org/SDL_AudioSpec - https://wiki.libsdl.org/SDL_OpenAudioDevice + .. versionchanged:: Unreleased + SDL3 returns audio devices differently, exact formatting is set with :any:`AudioDevice.new_stream` instead. + + `samples` and `allowed_changes` are ignored. + .. deprecated:: Unreleased + This is an outdated method. + Use :any:`AudioDevice.open` instead, for example: + ``tcod.sdl.audio.get_default_playback().open()`` """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) - desired = ffi.new( - "SDL_AudioSpec*", - { - "freq": frequency, - "format": _get_format(format), - "channels": channels, - "samples": samples, - "callback": ffi.NULL, - "userdata": ffi.NULL, - }, - ) - callback_data = _AudioCallbackUserdata() - if callback is not None: - handle = ffi.new_handle(callback_data) - desired.callback = lib._sdl_audio_callback - desired.userdata = handle + if name is None: + device = get_default_playback() if not capture else get_default_recording() else: - handle = None - - obtained = ffi.new("SDL_AudioSpec*") - device_id: int = lib.SDL_OpenAudioDevice( - ffi.NULL if name is None else name.encode("utf-8"), - capture, - desired, - obtained, - allowed_changes, - ) - assert device_id >= 0, _get_error() - device = AudioDevice(device_id, capture, obtained) - if callback is not None: - callback_data.device = device - device._handle = handle - if callback is not True: - device._callback = callback + device = (get_devices() if not capture else get_capture_devices())[name] + assert device.is_capture is capture + device = device.open(frequency=frequency, format=format, channels=channels) device.paused = paused + + if callback is not None and callback is not True: + stream = device.new_stream(format=format, channels=channels, frequency=frequency) + + def _get_callback(stream: AudioStream, data: AudioStreamCallbackData) -> None: + if data.additional_samples <= 0: + return + buffer = np.full( + (data.additional_samples, channels), fill_value=_silence_value_for_format(format), dtype=format + ) + callback(device, buffer) + stream.queue_audio(buffer) + + stream.getter_callback = _get_callback + return device diff --git a/tcod/sdl/constants.py b/tcod/sdl/constants.py new file mode 100644 index 00000000..39d766f4 --- /dev/null +++ b/tcod/sdl/constants.py @@ -0,0 +1,489 @@ +"""SDL private constants.""" + +SDL_PRILL_PREFIX = "ll" +SDL_PRILLX = Ellipsis +SDL_FUNCTION = "???" +SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER = "SDL.thread.create.entry_function" +SDL_PROP_THREAD_CREATE_NAME_STRING = "SDL.thread.create.name" +SDL_PROP_THREAD_CREATE_USERDATA_POINTER = "SDL.thread.create.userdata" +SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER = "SDL.thread.create.stacksize" +SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER = "SDL.iostream.windows.handle" +SDL_PROP_IOSTREAM_STDIO_FILE_POINTER = "SDL.iostream.stdio.file" +SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER = "SDL.iostream.file_descriptor" +SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER = "SDL.iostream.android.aasset" +SDL_PROP_IOSTREAM_MEMORY_POINTER = "SDL.iostream.memory.base" +SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER = "SDL.iostream.memory.size" +SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER = "SDL.iostream.dynamic.memory" +SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER = "SDL.iostream.dynamic.chunksize" +SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT = "SDL.surface.SDR_white_point" +SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT = "SDL.surface.HDR_headroom" +SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING = "SDL.surface.tonemap" +SDL_PROP_SURFACE_HOTSPOT_X_NUMBER = "SDL.surface.hotspot.x" +SDL_PROP_SURFACE_HOTSPOT_Y_NUMBER = "SDL.surface.hotspot.y" +SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER = "SDL.video.wayland.wl_display" +SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN = "SDL.display.HDR_enabled" +SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER = "SDL.display.KMSDRM.panel_orientation" +SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN = "SDL.window.create.always_on_top" +SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN = "SDL.window.create.borderless" +SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN = "SDL.window.create.focusable" +SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN = "SDL.window.create.external_graphics_context" +SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER = "SDL.window.create.flags" +SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN = "SDL.window.create.fullscreen" +SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER = "SDL.window.create.height" +SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN = "SDL.window.create.hidden" +SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN = "SDL.window.create.high_pixel_density" +SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN = "SDL.window.create.maximized" +SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN = "SDL.window.create.menu" +SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN = "SDL.window.create.metal" +SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN = "SDL.window.create.minimized" +SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN = "SDL.window.create.modal" +SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN = "SDL.window.create.mouse_grabbed" +SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN = "SDL.window.create.opengl" +SDL_PROP_WINDOW_CREATE_PARENT_POINTER = "SDL.window.create.parent" +SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN = "SDL.window.create.resizable" +SDL_PROP_WINDOW_CREATE_TITLE_STRING = "SDL.window.create.title" +SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN = "SDL.window.create.transparent" +SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN = "SDL.window.create.tooltip" +SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN = "SDL.window.create.utility" +SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN = "SDL.window.create.vulkan" +SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER = "SDL.window.create.width" +SDL_PROP_WINDOW_CREATE_X_NUMBER = "SDL.window.create.x" +SDL_PROP_WINDOW_CREATE_Y_NUMBER = "SDL.window.create.y" +SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER = "SDL.window.create.cocoa.window" +SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER = "SDL.window.create.cocoa.view" +SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN = "SDL.window.create.wayland.surface_role_custom" +SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN = "SDL.window.create.wayland.create_egl_window" +SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER = "SDL.window.create.wayland.wl_surface" +SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER = "SDL.window.create.win32.hwnd" +SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER = "SDL.window.create.win32.pixel_format_hwnd" +SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER = "SDL.window.create.x11.window" +SDL_PROP_WINDOW_SHAPE_POINTER = "SDL.window.shape" +SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN = "SDL.window.HDR_enabled" +SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT = "SDL.window.SDR_white_level" +SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT = "SDL.window.HDR_headroom" +SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER = "SDL.window.android.window" +SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER = "SDL.window.android.surface" +SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER = "SDL.window.uikit.window" +SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER = "SDL.window.uikit.metal_view_tag" +SDL_PROP_WINDOW_UIKIT_OPENGL_FRAMEBUFFER_NUMBER = "SDL.window.uikit.opengl.framebuffer" +SDL_PROP_WINDOW_UIKIT_OPENGL_RENDERBUFFER_NUMBER = "SDL.window.uikit.opengl.renderbuffer" +SDL_PROP_WINDOW_UIKIT_OPENGL_RESOLVE_FRAMEBUFFER_NUMBER = "SDL.window.uikit.opengl.resolve_framebuffer" +SDL_PROP_WINDOW_KMSDRM_DEVICE_INDEX_NUMBER = "SDL.window.kmsdrm.dev_index" +SDL_PROP_WINDOW_KMSDRM_DRM_FD_NUMBER = "SDL.window.kmsdrm.drm_fd" +SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER = "SDL.window.kmsdrm.gbm_dev" +SDL_PROP_WINDOW_COCOA_WINDOW_POINTER = "SDL.window.cocoa.window" +SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER = "SDL.window.cocoa.metal_view_tag" +SDL_PROP_WINDOW_OPENVR_OVERLAY_ID = "SDL.window.openvr.overlay_id" +SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER = "SDL.window.vivante.display" +SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER = "SDL.window.vivante.window" +SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER = "SDL.window.vivante.surface" +SDL_PROP_WINDOW_WIN32_HWND_POINTER = "SDL.window.win32.hwnd" +SDL_PROP_WINDOW_WIN32_HDC_POINTER = "SDL.window.win32.hdc" +SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER = "SDL.window.win32.instance" +SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER = "SDL.window.wayland.display" +SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER = "SDL.window.wayland.surface" +SDL_PROP_WINDOW_WAYLAND_VIEWPORT_POINTER = "SDL.window.wayland.viewport" +SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER = "SDL.window.wayland.egl_window" +SDL_PROP_WINDOW_WAYLAND_XDG_SURFACE_POINTER = "SDL.window.wayland.xdg_surface" +SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER = "SDL.window.wayland.xdg_toplevel" +SDL_PROP_WINDOW_WAYLAND_XDG_TOPLEVEL_EXPORT_HANDLE_STRING = "SDL.window.wayland.xdg_toplevel_export_handle" +SDL_PROP_WINDOW_WAYLAND_XDG_POPUP_POINTER = "SDL.window.wayland.xdg_popup" +SDL_PROP_WINDOW_WAYLAND_XDG_POSITIONER_POINTER = "SDL.window.wayland.xdg_positioner" +SDL_PROP_WINDOW_X11_DISPLAY_POINTER = "SDL.window.x11.display" +SDL_PROP_WINDOW_X11_SCREEN_NUMBER = "SDL.window.x11.screen" +SDL_PROP_WINDOW_X11_WINDOW_NUMBER = "SDL.window.x11.window" +SDL_PROP_FILE_DIALOG_FILTERS_POINTER = "SDL.filedialog.filters" +SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER = "SDL.filedialog.nfilters" +SDL_PROP_FILE_DIALOG_WINDOW_POINTER = "SDL.filedialog.window" +SDL_PROP_FILE_DIALOG_LOCATION_STRING = "SDL.filedialog.location" +SDL_PROP_FILE_DIALOG_MANY_BOOLEAN = "SDL.filedialog.many" +SDL_PROP_FILE_DIALOG_TITLE_STRING = "SDL.filedialog.title" +SDL_PROP_FILE_DIALOG_ACCEPT_STRING = "SDL.filedialog.accept" +SDL_PROP_FILE_DIALOG_CANCEL_STRING = "SDL.filedialog.cancel" +SDL_PROP_JOYSTICK_CAP_MONO_LED_BOOLEAN = "SDL.joystick.cap.mono_led" +SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN = "SDL.joystick.cap.rgb_led" +SDL_PROP_JOYSTICK_CAP_PLAYER_LED_BOOLEAN = "SDL.joystick.cap.player_led" +SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN = "SDL.joystick.cap.rumble" +SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN = "SDL.joystick.cap.trigger_rumble" +SDL_PROP_GAMEPAD_CAP_MONO_LED_BOOLEAN = Ellipsis +SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN = Ellipsis +SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN = Ellipsis +SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN = Ellipsis +SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN = Ellipsis +SDL_PROP_TEXTINPUT_TYPE_NUMBER = "SDL.textinput.type" +SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER = "SDL.textinput.capitalization" +SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN = "SDL.textinput.autocorrect" +SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN = "SDL.textinput.multiline" +SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER = "SDL.textinput.android.inputtype" +SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN = "SDL.gpu.device.create.debugmode" +SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN = "SDL.gpu.device.create.preferlowpower" +SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING = "SDL.gpu.device.create.name" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN = "SDL.gpu.device.create.shaders.private" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN = "SDL.gpu.device.create.shaders.spirv" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN = "SDL.gpu.device.create.shaders.dxbc" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN = "SDL.gpu.device.create.shaders.dxil" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN = "SDL.gpu.device.create.shaders.msl" +SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN = "SDL.gpu.device.create.shaders.metallib" +SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING = "SDL.gpu.device.create.d3d12.semantic" +SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING = "SDL.gpu.computepipeline.create.name" +SDL_PROP_GPU_GRAPHICSPIPELINE_CREATE_NAME_STRING = "SDL.gpu.graphicspipeline.create.name" +SDL_PROP_GPU_SAMPLER_CREATE_NAME_STRING = "SDL.gpu.sampler.create.name" +SDL_PROP_GPU_SHADER_CREATE_NAME_STRING = "SDL.gpu.shader.create.name" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_R_FLOAT = "SDL.gpu.texture.create.d3d12.clear.r" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_G_FLOAT = "SDL.gpu.texture.create.d3d12.clear.g" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_B_FLOAT = "SDL.gpu.texture.create.d3d12.clear.b" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_A_FLOAT = "SDL.gpu.texture.create.d3d12.clear.a" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT = "SDL.gpu.texture.create.d3d12.clear.depth" +SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_STENCIL_NUMBER = "SDL.gpu.texture.create.d3d12.clear.stencil" +SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING = "SDL.gpu.texture.create.name" +SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING = "SDL.gpu.buffer.create.name" +SDL_PROP_GPU_TRANSFERBUFFER_CREATE_NAME_STRING = "SDL.gpu.transferbuffer.create.name" +SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED = "SDL_ALLOW_ALT_TAB_WHILE_GRABBED" +SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY = "SDL_ANDROID_ALLOW_RECREATE_ACTIVITY" +SDL_HINT_ANDROID_BLOCK_ON_PAUSE = "SDL_ANDROID_BLOCK_ON_PAUSE" +SDL_HINT_ANDROID_LOW_LATENCY_AUDIO = "SDL_ANDROID_LOW_LATENCY_AUDIO" +SDL_HINT_ANDROID_TRAP_BACK_BUTTON = "SDL_ANDROID_TRAP_BACK_BUTTON" +SDL_HINT_APP_ID = "SDL_APP_ID" +SDL_HINT_APP_NAME = "SDL_APP_NAME" +SDL_HINT_APPLE_TV_CONTROLLER_UI_EVENTS = "SDL_APPLE_TV_CONTROLLER_UI_EVENTS" +SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION = "SDL_APPLE_TV_REMOTE_ALLOW_ROTATION" +SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_DEVICE" +SDL_HINT_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_PLAYBACK_DEVICE" +SDL_HINT_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE = "SDL_AUDIO_ALSA_DEFAULT_RECORDING_DEVICE" +SDL_HINT_AUDIO_CATEGORY = "SDL_AUDIO_CATEGORY" +SDL_HINT_AUDIO_CHANNELS = "SDL_AUDIO_CHANNELS" +SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME = "SDL_AUDIO_DEVICE_APP_ICON_NAME" +SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES = "SDL_AUDIO_DEVICE_SAMPLE_FRAMES" +SDL_HINT_AUDIO_DEVICE_STREAM_NAME = "SDL_AUDIO_DEVICE_STREAM_NAME" +SDL_HINT_AUDIO_DEVICE_STREAM_ROLE = "SDL_AUDIO_DEVICE_STREAM_ROLE" +SDL_HINT_AUDIO_DISK_INPUT_FILE = "SDL_AUDIO_DISK_INPUT_FILE" +SDL_HINT_AUDIO_DISK_OUTPUT_FILE = "SDL_AUDIO_DISK_OUTPUT_FILE" +SDL_HINT_AUDIO_DISK_TIMESCALE = "SDL_AUDIO_DISK_TIMESCALE" +SDL_HINT_AUDIO_DRIVER = "SDL_AUDIO_DRIVER" +SDL_HINT_AUDIO_DUMMY_TIMESCALE = "SDL_AUDIO_DUMMY_TIMESCALE" +SDL_HINT_AUDIO_FORMAT = "SDL_AUDIO_FORMAT" +SDL_HINT_AUDIO_FREQUENCY = "SDL_AUDIO_FREQUENCY" +SDL_HINT_AUDIO_INCLUDE_MONITORS = "SDL_AUDIO_INCLUDE_MONITORS" +SDL_HINT_AUTO_UPDATE_JOYSTICKS = "SDL_AUTO_UPDATE_JOYSTICKS" +SDL_HINT_AUTO_UPDATE_SENSORS = "SDL_AUTO_UPDATE_SENSORS" +SDL_HINT_BMP_SAVE_LEGACY_FORMAT = "SDL_BMP_SAVE_LEGACY_FORMAT" +SDL_HINT_CAMERA_DRIVER = "SDL_CAMERA_DRIVER" +SDL_HINT_CPU_FEATURE_MASK = "SDL_CPU_FEATURE_MASK" +SDL_HINT_JOYSTICK_DIRECTINPUT = "SDL_JOYSTICK_DIRECTINPUT" +SDL_HINT_FILE_DIALOG_DRIVER = "SDL_FILE_DIALOG_DRIVER" +SDL_HINT_DISPLAY_USABLE_BOUNDS = "SDL_DISPLAY_USABLE_BOUNDS" +SDL_HINT_EMSCRIPTEN_ASYNCIFY = "SDL_EMSCRIPTEN_ASYNCIFY" +SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR = "SDL_EMSCRIPTEN_CANVAS_SELECTOR" +SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT = "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT" +SDL_HINT_ENABLE_SCREEN_KEYBOARD = "SDL_ENABLE_SCREEN_KEYBOARD" +SDL_HINT_EVDEV_DEVICES = "SDL_EVDEV_DEVICES" +SDL_HINT_EVENT_LOGGING = "SDL_EVENT_LOGGING" +SDL_HINT_FORCE_RAISEWINDOW = "SDL_FORCE_RAISEWINDOW" +SDL_HINT_FRAMEBUFFER_ACCELERATION = "SDL_FRAMEBUFFER_ACCELERATION" +SDL_HINT_GAMECONTROLLERCONFIG = "SDL_GAMECONTROLLERCONFIG" +SDL_HINT_GAMECONTROLLERCONFIG_FILE = "SDL_GAMECONTROLLERCONFIG_FILE" +SDL_HINT_GAMECONTROLLERTYPE = "SDL_GAMECONTROLLERTYPE" +SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES = "SDL_GAMECONTROLLER_IGNORE_DEVICES" +SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT = "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT" +SDL_HINT_GAMECONTROLLER_SENSOR_FUSION = "SDL_GAMECONTROLLER_SENSOR_FUSION" +SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT = "SDL_GDK_TEXTINPUT_DEFAULT_TEXT" +SDL_HINT_GDK_TEXTINPUT_DESCRIPTION = "SDL_GDK_TEXTINPUT_DESCRIPTION" +SDL_HINT_GDK_TEXTINPUT_MAX_LENGTH = "SDL_GDK_TEXTINPUT_MAX_LENGTH" +SDL_HINT_GDK_TEXTINPUT_SCOPE = "SDL_GDK_TEXTINPUT_SCOPE" +SDL_HINT_GDK_TEXTINPUT_TITLE = "SDL_GDK_TEXTINPUT_TITLE" +SDL_HINT_HIDAPI_LIBUSB = "SDL_HIDAPI_LIBUSB" +SDL_HINT_HIDAPI_LIBUSB_WHITELIST = "SDL_HIDAPI_LIBUSB_WHITELIST" +SDL_HINT_HIDAPI_UDEV = "SDL_HIDAPI_UDEV" +SDL_HINT_GPU_DRIVER = "SDL_GPU_DRIVER" +SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS = "SDL_HIDAPI_ENUMERATE_ONLY_CONTROLLERS" +SDL_HINT_HIDAPI_IGNORE_DEVICES = "SDL_HIDAPI_IGNORE_DEVICES" +SDL_HINT_IME_IMPLEMENTED_UI = "SDL_IME_IMPLEMENTED_UI" +SDL_HINT_IOS_HIDE_HOME_INDICATOR = "SDL_IOS_HIDE_HOME_INDICATOR" +SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS = "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" +SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES = "SDL_JOYSTICK_ARCADESTICK_DEVICES" +SDL_HINT_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED = "SDL_JOYSTICK_ARCADESTICK_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_BLACKLIST_DEVICES = "SDL_JOYSTICK_BLACKLIST_DEVICES" +SDL_HINT_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED = "SDL_JOYSTICK_BLACKLIST_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_DEVICE = "SDL_JOYSTICK_DEVICE" +SDL_HINT_JOYSTICK_ENHANCED_REPORTS = "SDL_JOYSTICK_ENHANCED_REPORTS" +SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES = "SDL_JOYSTICK_FLIGHTSTICK_DEVICES" +SDL_HINT_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED = "SDL_JOYSTICK_FLIGHTSTICK_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_GAMEINPUT = "SDL_JOYSTICK_GAMEINPUT" +SDL_HINT_JOYSTICK_GAMECUBE_DEVICES = "SDL_JOYSTICK_GAMECUBE_DEVICES" +SDL_HINT_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED = "SDL_JOYSTICK_GAMECUBE_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_HIDAPI = "SDL_JOYSTICK_HIDAPI" +SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS" +SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE = "SDL_JOYSTICK_HIDAPI_GAMECUBE" +SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE = "SDL_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE" +SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS = "SDL_JOYSTICK_HIDAPI_JOY_CONS" +SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED = "SDL_JOYSTICK_HIDAPI_JOYCON_HOME_LED" +SDL_HINT_JOYSTICK_HIDAPI_LUNA = "SDL_JOYSTICK_HIDAPI_LUNA" +SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC = "SDL_JOYSTICK_HIDAPI_NINTENDO_CLASSIC" +SDL_HINT_JOYSTICK_HIDAPI_PS3 = "SDL_JOYSTICK_HIDAPI_PS3" +SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER = "SDL_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER" +SDL_HINT_JOYSTICK_HIDAPI_PS4 = "SDL_JOYSTICK_HIDAPI_PS4" +SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL = "SDL_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL" +SDL_HINT_JOYSTICK_HIDAPI_PS5 = "SDL_JOYSTICK_HIDAPI_PS5" +SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED" +SDL_HINT_JOYSTICK_HIDAPI_SHIELD = "SDL_JOYSTICK_HIDAPI_SHIELD" +SDL_HINT_JOYSTICK_HIDAPI_STADIA = "SDL_JOYSTICK_HIDAPI_STADIA" +SDL_HINT_JOYSTICK_HIDAPI_STEAM = "SDL_JOYSTICK_HIDAPI_STEAM" +SDL_HINT_JOYSTICK_HIDAPI_STEAM_HOME_LED = "SDL_JOYSTICK_HIDAPI_STEAM_HOME_LED" +SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK = "SDL_JOYSTICK_HIDAPI_STEAMDECK" +SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI = "SDL_JOYSTICK_HIDAPI_STEAM_HORI" +SDL_HINT_JOYSTICK_HIDAPI_SWITCH = "SDL_JOYSTICK_HIDAPI_SWITCH" +SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED = "SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED" +SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED" +SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS = "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS" +SDL_HINT_JOYSTICK_HIDAPI_WII = "SDL_JOYSTICK_HIDAPI_WII" +SDL_HINT_JOYSTICK_HIDAPI_WII_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_WII_PLAYER_LED" +SDL_HINT_JOYSTICK_HIDAPI_XBOX = "SDL_JOYSTICK_HIDAPI_XBOX" +SDL_HINT_JOYSTICK_HIDAPI_XBOX_360 = "SDL_JOYSTICK_HIDAPI_XBOX_360" +SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED = "SDL_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED" +SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS = "SDL_JOYSTICK_HIDAPI_XBOX_360_WIRELESS" +SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE = "SDL_JOYSTICK_HIDAPI_XBOX_ONE" +SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED = "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED" +SDL_HINT_JOYSTICK_IOKIT = "SDL_JOYSTICK_IOKIT" +SDL_HINT_JOYSTICK_LINUX_CLASSIC = "SDL_JOYSTICK_LINUX_CLASSIC" +SDL_HINT_JOYSTICK_LINUX_DEADZONES = "SDL_JOYSTICK_LINUX_DEADZONES" +SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS = "SDL_JOYSTICK_LINUX_DIGITAL_HATS" +SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES = "SDL_JOYSTICK_LINUX_HAT_DEADZONES" +SDL_HINT_JOYSTICK_MFI = "SDL_JOYSTICK_MFI" +SDL_HINT_JOYSTICK_RAWINPUT = "SDL_JOYSTICK_RAWINPUT" +SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT = "SDL_JOYSTICK_RAWINPUT_CORRELATE_XINPUT" +SDL_HINT_JOYSTICK_ROG_CHAKRAM = "SDL_JOYSTICK_ROG_CHAKRAM" +SDL_HINT_JOYSTICK_THREAD = "SDL_JOYSTICK_THREAD" +SDL_HINT_JOYSTICK_THROTTLE_DEVICES = "SDL_JOYSTICK_THROTTLE_DEVICES" +SDL_HINT_JOYSTICK_THROTTLE_DEVICES_EXCLUDED = "SDL_JOYSTICK_THROTTLE_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_WGI = "SDL_JOYSTICK_WGI" +SDL_HINT_JOYSTICK_WHEEL_DEVICES = "SDL_JOYSTICK_WHEEL_DEVICES" +SDL_HINT_JOYSTICK_WHEEL_DEVICES_EXCLUDED = "SDL_JOYSTICK_WHEEL_DEVICES_EXCLUDED" +SDL_HINT_JOYSTICK_ZERO_CENTERED_DEVICES = "SDL_JOYSTICK_ZERO_CENTERED_DEVICES" +SDL_HINT_JOYSTICK_HAPTIC_AXES = "SDL_JOYSTICK_HAPTIC_AXES" +SDL_HINT_KEYCODE_OPTIONS = "SDL_KEYCODE_OPTIONS" +SDL_HINT_KMSDRM_DEVICE_INDEX = "SDL_KMSDRM_DEVICE_INDEX" +SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER = "SDL_KMSDRM_REQUIRE_DRM_MASTER" +SDL_HINT_LOGGING = "SDL_LOGGING" +SDL_HINT_MAC_BACKGROUND_APP = "SDL_MAC_BACKGROUND_APP" +SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK = "SDL_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK" +SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH = "SDL_MAC_OPENGL_ASYNC_DISPATCH" +SDL_HINT_MAC_OPTION_AS_ALT = "SDL_MAC_OPTION_AS_ALT" +SDL_HINT_MAC_SCROLL_MOMENTUM = "SDL_MAC_SCROLL_MOMENTUM" +SDL_HINT_MAIN_CALLBACK_RATE = "SDL_MAIN_CALLBACK_RATE" +SDL_HINT_MOUSE_AUTO_CAPTURE = "SDL_MOUSE_AUTO_CAPTURE" +SDL_HINT_MOUSE_DOUBLE_CLICK_RADIUS = "SDL_MOUSE_DOUBLE_CLICK_RADIUS" +SDL_HINT_MOUSE_DOUBLE_CLICK_TIME = "SDL_MOUSE_DOUBLE_CLICK_TIME" +SDL_HINT_MOUSE_DEFAULT_SYSTEM_CURSOR = "SDL_MOUSE_DEFAULT_SYSTEM_CURSOR" +SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE = "SDL_MOUSE_EMULATE_WARP_WITH_RELATIVE" +SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH = "SDL_MOUSE_FOCUS_CLICKTHROUGH" +SDL_HINT_MOUSE_NORMAL_SPEED_SCALE = "SDL_MOUSE_NORMAL_SPEED_SCALE" +SDL_HINT_MOUSE_RELATIVE_MODE_CENTER = "SDL_MOUSE_RELATIVE_MODE_CENTER" +SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE = "SDL_MOUSE_RELATIVE_SPEED_SCALE" +SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE = "SDL_MOUSE_RELATIVE_SYSTEM_SCALE" +SDL_HINT_MOUSE_RELATIVE_WARP_MOTION = "SDL_MOUSE_RELATIVE_WARP_MOTION" +SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE = "SDL_MOUSE_RELATIVE_CURSOR_VISIBLE" +SDL_HINT_MOUSE_TOUCH_EVENTS = "SDL_MOUSE_TOUCH_EVENTS" +SDL_HINT_MUTE_CONSOLE_KEYBOARD = "SDL_MUTE_CONSOLE_KEYBOARD" +SDL_HINT_NO_SIGNAL_HANDLERS = "SDL_NO_SIGNAL_HANDLERS" +SDL_HINT_OPENGL_LIBRARY = "SDL_OPENGL_LIBRARY" +SDL_HINT_EGL_LIBRARY = "SDL_EGL_LIBRARY" +SDL_HINT_OPENGL_ES_DRIVER = "SDL_OPENGL_ES_DRIVER" +SDL_HINT_OPENVR_LIBRARY = "SDL_OPENVR_LIBRARY" +SDL_HINT_ORIENTATIONS = "SDL_ORIENTATIONS" +SDL_HINT_POLL_SENTINEL = "SDL_POLL_SENTINEL" +SDL_HINT_PREFERRED_LOCALES = "SDL_PREFERRED_LOCALES" +SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE = "SDL_QUIT_ON_LAST_WINDOW_CLOSE" +SDL_HINT_RENDER_DIRECT3D_THREADSAFE = "SDL_RENDER_DIRECT3D_THREADSAFE" +SDL_HINT_RENDER_DIRECT3D11_DEBUG = "SDL_RENDER_DIRECT3D11_DEBUG" +SDL_HINT_RENDER_VULKAN_DEBUG = "SDL_RENDER_VULKAN_DEBUG" +SDL_HINT_RENDER_GPU_DEBUG = "SDL_RENDER_GPU_DEBUG" +SDL_HINT_RENDER_GPU_LOW_POWER = "SDL_RENDER_GPU_LOW_POWER" +SDL_HINT_RENDER_DRIVER = "SDL_RENDER_DRIVER" +SDL_HINT_RENDER_LINE_METHOD = "SDL_RENDER_LINE_METHOD" +SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE = "SDL_RENDER_METAL_PREFER_LOW_POWER_DEVICE" +SDL_HINT_RENDER_VSYNC = "SDL_RENDER_VSYNC" +SDL_HINT_RETURN_KEY_HIDES_IME = "SDL_RETURN_KEY_HIDES_IME" +SDL_HINT_ROG_GAMEPAD_MICE = "SDL_ROG_GAMEPAD_MICE" +SDL_HINT_ROG_GAMEPAD_MICE_EXCLUDED = "SDL_ROG_GAMEPAD_MICE_EXCLUDED" +SDL_HINT_RPI_VIDEO_LAYER = "SDL_RPI_VIDEO_LAYER" +SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME = "SDL_SCREENSAVER_INHIBIT_ACTIVITY_NAME" +SDL_HINT_SHUTDOWN_DBUS_ON_QUIT = "SDL_SHUTDOWN_DBUS_ON_QUIT" +SDL_HINT_STORAGE_TITLE_DRIVER = "SDL_STORAGE_TITLE_DRIVER" +SDL_HINT_STORAGE_USER_DRIVER = "SDL_STORAGE_USER_DRIVER" +SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL = "SDL_THREAD_FORCE_REALTIME_TIME_CRITICAL" +SDL_HINT_THREAD_PRIORITY_POLICY = "SDL_THREAD_PRIORITY_POLICY" +SDL_HINT_TIMER_RESOLUTION = "SDL_TIMER_RESOLUTION" +SDL_HINT_TOUCH_MOUSE_EVENTS = "SDL_TOUCH_MOUSE_EVENTS" +SDL_HINT_TRACKPAD_IS_TOUCH_ONLY = "SDL_TRACKPAD_IS_TOUCH_ONLY" +SDL_HINT_TV_REMOTE_AS_JOYSTICK = "SDL_TV_REMOTE_AS_JOYSTICK" +SDL_HINT_VIDEO_ALLOW_SCREENSAVER = "SDL_VIDEO_ALLOW_SCREENSAVER" +SDL_HINT_VIDEO_DISPLAY_PRIORITY = "SDL_VIDEO_DISPLAY_PRIORITY" +SDL_HINT_VIDEO_DOUBLE_BUFFER = "SDL_VIDEO_DOUBLE_BUFFER" +SDL_HINT_VIDEO_DRIVER = "SDL_VIDEO_DRIVER" +SDL_HINT_VIDEO_DUMMY_SAVE_FRAMES = "SDL_VIDEO_DUMMY_SAVE_FRAMES" +SDL_HINT_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK = "SDL_VIDEO_EGL_ALLOW_GETDISPLAY_FALLBACK" +SDL_HINT_VIDEO_FORCE_EGL = "SDL_VIDEO_FORCE_EGL" +SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES = "SDL_VIDEO_MAC_FULLSCREEN_SPACES" +SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY = "SDL_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY" +SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS = "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS" +SDL_HINT_VIDEO_OFFSCREEN_SAVE_FRAMES = "SDL_VIDEO_OFFSCREEN_SAVE_FRAMES" +SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS = "SDL_VIDEO_SYNC_WINDOW_OPERATIONS" +SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR = "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR" +SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION = "SDL_VIDEO_WAYLAND_MODE_EMULATION" +SDL_HINT_VIDEO_WAYLAND_MODE_SCALING = "SDL_VIDEO_WAYLAND_MODE_SCALING" +SDL_HINT_VIDEO_WAYLAND_PREFER_LIBDECOR = "SDL_VIDEO_WAYLAND_PREFER_LIBDECOR" +SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY = "SDL_VIDEO_WAYLAND_SCALE_TO_DISPLAY" +SDL_HINT_VIDEO_WIN_D3DCOMPILER = "SDL_VIDEO_WIN_D3DCOMPILER" +SDL_HINT_VIDEO_X11_EXTERNAL_WINDOW_INPUT = "SDL_VIDEO_X11_EXTERNAL_WINDOW_INPUT" +SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR = "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR" +SDL_HINT_VIDEO_X11_NET_WM_PING = "SDL_VIDEO_X11_NET_WM_PING" +SDL_HINT_VIDEO_X11_NODIRECTCOLOR = "SDL_VIDEO_X11_NODIRECTCOLOR" +SDL_HINT_VIDEO_X11_SCALING_FACTOR = "SDL_VIDEO_X11_SCALING_FACTOR" +SDL_HINT_VIDEO_X11_VISUALID = "SDL_VIDEO_X11_VISUALID" +SDL_HINT_VIDEO_X11_WINDOW_VISUALID = "SDL_VIDEO_X11_WINDOW_VISUALID" +SDL_HINT_VIDEO_X11_XRANDR = "SDL_VIDEO_X11_XRANDR" +SDL_HINT_VITA_ENABLE_BACK_TOUCH = "SDL_VITA_ENABLE_BACK_TOUCH" +SDL_HINT_VITA_ENABLE_FRONT_TOUCH = "SDL_VITA_ENABLE_FRONT_TOUCH" +SDL_HINT_VITA_MODULE_PATH = "SDL_VITA_MODULE_PATH" +SDL_HINT_VITA_PVR_INIT = "SDL_VITA_PVR_INIT" +SDL_HINT_VITA_RESOLUTION = "SDL_VITA_RESOLUTION" +SDL_HINT_VITA_PVR_OPENGL = "SDL_VITA_PVR_OPENGL" +SDL_HINT_VITA_TOUCH_MOUSE_DEVICE = "SDL_VITA_TOUCH_MOUSE_DEVICE" +SDL_HINT_VULKAN_DISPLAY = "SDL_VULKAN_DISPLAY" +SDL_HINT_VULKAN_LIBRARY = "SDL_VULKAN_LIBRARY" +SDL_HINT_WAVE_FACT_CHUNK = "SDL_WAVE_FACT_CHUNK" +SDL_HINT_WAVE_CHUNK_LIMIT = "SDL_WAVE_CHUNK_LIMIT" +SDL_HINT_WAVE_RIFF_CHUNK_SIZE = "SDL_WAVE_RIFF_CHUNK_SIZE" +SDL_HINT_WAVE_TRUNCATION = "SDL_WAVE_TRUNCATION" +SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED = "SDL_WINDOW_ACTIVATE_WHEN_RAISED" +SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN = "SDL_WINDOW_ACTIVATE_WHEN_SHOWN" +SDL_HINT_WINDOW_ALLOW_TOPMOST = "SDL_WINDOW_ALLOW_TOPMOST" +SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN = "SDL_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN" +SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4 = "SDL_WINDOWS_CLOSE_ON_ALT_F4" +SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS = "SDL_WINDOWS_ENABLE_MENU_MNEMONICS" +SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP = "SDL_WINDOWS_ENABLE_MESSAGELOOP" +SDL_HINT_WINDOWS_GAMEINPUT = "SDL_WINDOWS_GAMEINPUT" +SDL_HINT_WINDOWS_RAW_KEYBOARD = "SDL_WINDOWS_RAW_KEYBOARD" +SDL_HINT_WINDOWS_FORCE_SEMAPHORE_KERNEL = "SDL_WINDOWS_FORCE_SEMAPHORE_KERNEL" +SDL_HINT_WINDOWS_INTRESOURCE_ICON = "SDL_WINDOWS_INTRESOURCE_ICON" +SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL = "SDL_WINDOWS_INTRESOURCE_ICON_SMALL" +SDL_HINT_WINDOWS_USE_D3D9EX = "SDL_WINDOWS_USE_D3D9EX" +SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE = "SDL_WINDOWS_ERASE_BACKGROUND_MODE" +SDL_HINT_X11_FORCE_OVERRIDE_REDIRECT = "SDL_X11_FORCE_OVERRIDE_REDIRECT" +SDL_HINT_X11_WINDOW_TYPE = "SDL_X11_WINDOW_TYPE" +SDL_HINT_X11_XCB_LIBRARY = "SDL_X11_XCB_LIBRARY" +SDL_HINT_XINPUT_ENABLED = "SDL_XINPUT_ENABLED" +SDL_HINT_ASSERT = "SDL_ASSERT" +SDL_HINT_PEN_MOUSE_EVENTS = "SDL_PEN_MOUSE_EVENTS" +SDL_HINT_PEN_TOUCH_EVENTS = "SDL_PEN_TOUCH_EVENTS" +SDL_PROP_APP_METADATA_NAME_STRING = "SDL.app.metadata.name" +SDL_PROP_APP_METADATA_VERSION_STRING = "SDL.app.metadata.version" +SDL_PROP_APP_METADATA_IDENTIFIER_STRING = "SDL.app.metadata.identifier" +SDL_PROP_APP_METADATA_CREATOR_STRING = "SDL.app.metadata.creator" +SDL_PROP_APP_METADATA_COPYRIGHT_STRING = "SDL.app.metadata.copyright" +SDL_PROP_APP_METADATA_URL_STRING = "SDL.app.metadata.url" +SDL_PROP_APP_METADATA_TYPE_STRING = "SDL.app.metadata.type" +SDL_PROP_PROCESS_CREATE_ARGS_POINTER = "SDL.process.create.args" +SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER = "SDL.process.create.environment" +SDL_PROP_PROCESS_CREATE_STDIN_NUMBER = "SDL.process.create.stdin_option" +SDL_PROP_PROCESS_CREATE_STDIN_POINTER = "SDL.process.create.stdin_source" +SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER = "SDL.process.create.stdout_option" +SDL_PROP_PROCESS_CREATE_STDOUT_POINTER = "SDL.process.create.stdout_source" +SDL_PROP_PROCESS_CREATE_STDERR_NUMBER = "SDL.process.create.stderr_option" +SDL_PROP_PROCESS_CREATE_STDERR_POINTER = "SDL.process.create.stderr_source" +SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN = "SDL.process.create.stderr_to_stdout" +SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN = "SDL.process.create.background" +SDL_PROP_PROCESS_PID_NUMBER = "SDL.process.pid" +SDL_PROP_PROCESS_STDIN_POINTER = "SDL.process.stdin" +SDL_PROP_PROCESS_STDOUT_POINTER = "SDL.process.stdout" +SDL_PROP_PROCESS_STDERR_POINTER = "SDL.process.stderr" +SDL_PROP_PROCESS_BACKGROUND_BOOLEAN = "SDL.process.background" +SDL_SOFTWARE_RENDERER = "software" +SDL_PROP_RENDERER_CREATE_NAME_STRING = "SDL.renderer.create.name" +SDL_PROP_RENDERER_CREATE_WINDOW_POINTER = "SDL.renderer.create.window" +SDL_PROP_RENDERER_CREATE_SURFACE_POINTER = "SDL.renderer.create.surface" +SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER = "SDL.renderer.create.output_colorspace" +SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER = "SDL.renderer.create.present_vsync" +SDL_PROP_RENDERER_CREATE_VULKAN_INSTANCE_POINTER = "SDL.renderer.create.vulkan.instance" +SDL_PROP_RENDERER_CREATE_VULKAN_SURFACE_NUMBER = "SDL.renderer.create.vulkan.surface" +SDL_PROP_RENDERER_CREATE_VULKAN_PHYSICAL_DEVICE_POINTER = "SDL.renderer.create.vulkan.physical_device" +SDL_PROP_RENDERER_CREATE_VULKAN_DEVICE_POINTER = "SDL.renderer.create.vulkan.device" +SDL_PROP_RENDERER_CREATE_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER = ( + "SDL.renderer.create.vulkan.graphics_queue_family_index" +) +SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER = ( + "SDL.renderer.create.vulkan.present_queue_family_index" +) +SDL_PROP_RENDERER_NAME_STRING = "SDL.renderer.name" +SDL_PROP_RENDERER_WINDOW_POINTER = "SDL.renderer.window" +SDL_PROP_RENDERER_SURFACE_POINTER = "SDL.renderer.surface" +SDL_PROP_RENDERER_VSYNC_NUMBER = "SDL.renderer.vsync" +SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER = "SDL.renderer.max_texture_size" +SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER = "SDL.renderer.texture_formats" +SDL_PROP_RENDERER_OUTPUT_COLORSPACE_NUMBER = "SDL.renderer.output_colorspace" +SDL_PROP_RENDERER_HDR_ENABLED_BOOLEAN = "SDL.renderer.HDR_enabled" +SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT = "SDL.renderer.SDR_white_point" +SDL_PROP_RENDERER_HDR_HEADROOM_FLOAT = "SDL.renderer.HDR_headroom" +SDL_PROP_RENDERER_D3D9_DEVICE_POINTER = "SDL.renderer.d3d9.device" +SDL_PROP_RENDERER_D3D11_DEVICE_POINTER = "SDL.renderer.d3d11.device" +SDL_PROP_RENDERER_D3D11_SWAPCHAIN_POINTER = "SDL.renderer.d3d11.swap_chain" +SDL_PROP_RENDERER_D3D12_DEVICE_POINTER = "SDL.renderer.d3d12.device" +SDL_PROP_RENDERER_D3D12_SWAPCHAIN_POINTER = "SDL.renderer.d3d12.swap_chain" +SDL_PROP_RENDERER_D3D12_COMMAND_QUEUE_POINTER = "SDL.renderer.d3d12.command_queue" +SDL_PROP_RENDERER_VULKAN_INSTANCE_POINTER = "SDL.renderer.vulkan.instance" +SDL_PROP_RENDERER_VULKAN_SURFACE_NUMBER = "SDL.renderer.vulkan.surface" +SDL_PROP_RENDERER_VULKAN_PHYSICAL_DEVICE_POINTER = "SDL.renderer.vulkan.physical_device" +SDL_PROP_RENDERER_VULKAN_DEVICE_POINTER = "SDL.renderer.vulkan.device" +SDL_PROP_RENDERER_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER = "SDL.renderer.vulkan.graphics_queue_family_index" +SDL_PROP_RENDERER_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER = "SDL.renderer.vulkan.present_queue_family_index" +SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER = "SDL.renderer.vulkan.swapchain_image_count" +SDL_PROP_RENDERER_GPU_DEVICE_POINTER = "SDL.renderer.gpu.device" +SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER = "SDL.texture.create.colorspace" +SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER = "SDL.texture.create.format" +SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER = "SDL.texture.create.access" +SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER = "SDL.texture.create.width" +SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER = "SDL.texture.create.height" +SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT = "SDL.texture.create.SDR_white_point" +SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT = "SDL.texture.create.HDR_headroom" +SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_POINTER = "SDL.texture.create.d3d11.texture" +SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_U_POINTER = "SDL.texture.create.d3d11.texture_u" +SDL_PROP_TEXTURE_CREATE_D3D11_TEXTURE_V_POINTER = "SDL.texture.create.d3d11.texture_v" +SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_POINTER = "SDL.texture.create.d3d12.texture" +SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_U_POINTER = "SDL.texture.create.d3d12.texture_u" +SDL_PROP_TEXTURE_CREATE_D3D12_TEXTURE_V_POINTER = "SDL.texture.create.d3d12.texture_v" +SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER = "SDL.texture.create.metal.pixelbuffer" +SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_NUMBER = "SDL.texture.create.opengl.texture" +SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_UV_NUMBER = "SDL.texture.create.opengl.texture_uv" +SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_U_NUMBER = "SDL.texture.create.opengl.texture_u" +SDL_PROP_TEXTURE_CREATE_OPENGL_TEXTURE_V_NUMBER = "SDL.texture.create.opengl.texture_v" +SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_NUMBER = "SDL.texture.create.opengles2.texture" +SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_UV_NUMBER = "SDL.texture.create.opengles2.texture_uv" +SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_U_NUMBER = "SDL.texture.create.opengles2.texture_u" +SDL_PROP_TEXTURE_CREATE_OPENGLES2_TEXTURE_V_NUMBER = "SDL.texture.create.opengles2.texture_v" +SDL_PROP_TEXTURE_CREATE_VULKAN_TEXTURE_NUMBER = "SDL.texture.create.vulkan.texture" +SDL_PROP_TEXTURE_COLORSPACE_NUMBER = "SDL.texture.colorspace" +SDL_PROP_TEXTURE_FORMAT_NUMBER = "SDL.texture.format" +SDL_PROP_TEXTURE_ACCESS_NUMBER = "SDL.texture.access" +SDL_PROP_TEXTURE_WIDTH_NUMBER = "SDL.texture.width" +SDL_PROP_TEXTURE_HEIGHT_NUMBER = "SDL.texture.height" +SDL_PROP_TEXTURE_SDR_WHITE_POINT_FLOAT = "SDL.texture.SDR_white_point" +SDL_PROP_TEXTURE_HDR_HEADROOM_FLOAT = "SDL.texture.HDR_headroom" +SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER = "SDL.texture.d3d11.texture" +SDL_PROP_TEXTURE_D3D11_TEXTURE_U_POINTER = "SDL.texture.d3d11.texture_u" +SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER = "SDL.texture.d3d11.texture_v" +SDL_PROP_TEXTURE_D3D12_TEXTURE_POINTER = "SDL.texture.d3d12.texture" +SDL_PROP_TEXTURE_D3D12_TEXTURE_U_POINTER = "SDL.texture.d3d12.texture_u" +SDL_PROP_TEXTURE_D3D12_TEXTURE_V_POINTER = "SDL.texture.d3d12.texture_v" +SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER = "SDL.texture.opengl.texture" +SDL_PROP_TEXTURE_OPENGL_TEXTURE_UV_NUMBER = "SDL.texture.opengl.texture_uv" +SDL_PROP_TEXTURE_OPENGL_TEXTURE_U_NUMBER = "SDL.texture.opengl.texture_u" +SDL_PROP_TEXTURE_OPENGL_TEXTURE_V_NUMBER = "SDL.texture.opengl.texture_v" +SDL_PROP_TEXTURE_OPENGL_TEXTURE_TARGET_NUMBER = "SDL.texture.opengl.target" +SDL_PROP_TEXTURE_OPENGL_TEX_W_FLOAT = "SDL.texture.opengl.tex_w" +SDL_PROP_TEXTURE_OPENGL_TEX_H_FLOAT = "SDL.texture.opengl.tex_h" +SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER = "SDL.texture.opengles2.texture" +SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER = "SDL.texture.opengles2.texture_uv" +SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER = "SDL.texture.opengles2.texture_u" +SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_V_NUMBER = "SDL.texture.opengles2.texture_v" +SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER = "SDL.texture.opengles2.target" +SDL_PROP_TEXTURE_VULKAN_TEXTURE_NUMBER = "SDL.texture.vulkan.texture" diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index 9015605c..b658c510 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -11,7 +11,7 @@ import tcod.sdl.sys from tcod.cffi import ffi, lib -from tcod.sdl._internal import _check, _check_p +from tcod.sdl._internal import _check, _check_int, _check_p _HAT_DIRECTIONS: dict[int, tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]] = { int(lib.SDL_HAT_CENTERED): (0, 0), @@ -29,54 +29,54 @@ class ControllerAxis(enum.IntEnum): """The standard axes for a game controller.""" - INVALID = int(lib.SDL_CONTROLLER_AXIS_INVALID) - LEFTX = int(lib.SDL_CONTROLLER_AXIS_LEFTX) + INVALID = int(lib.SDL_GAMEPAD_AXIS_INVALID) + LEFTX = int(lib.SDL_GAMEPAD_AXIS_LEFTX) """""" - LEFTY = int(lib.SDL_CONTROLLER_AXIS_LEFTY) + LEFTY = int(lib.SDL_GAMEPAD_AXIS_LEFTY) """""" - RIGHTX = int(lib.SDL_CONTROLLER_AXIS_RIGHTX) + RIGHTX = int(lib.SDL_GAMEPAD_AXIS_RIGHTX) """""" - RIGHTY = int(lib.SDL_CONTROLLER_AXIS_RIGHTY) + RIGHTY = int(lib.SDL_GAMEPAD_AXIS_RIGHTY) """""" - TRIGGERLEFT = int(lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT) + TRIGGERLEFT = int(lib.SDL_GAMEPAD_AXIS_LEFT_TRIGGER) """""" - TRIGGERRIGHT = int(lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + TRIGGERRIGHT = int(lib.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) """""" class ControllerButton(enum.IntEnum): """The standard buttons for a game controller.""" - INVALID = int(lib.SDL_CONTROLLER_BUTTON_INVALID) - A = int(lib.SDL_CONTROLLER_BUTTON_A) + INVALID = int(lib.SDL_GAMEPAD_BUTTON_INVALID) + A = int(lib.SDL_GAMEPAD_BUTTON_SOUTH) """""" - B = int(lib.SDL_CONTROLLER_BUTTON_B) + B = int(lib.SDL_GAMEPAD_BUTTON_EAST) """""" - X = int(lib.SDL_CONTROLLER_BUTTON_X) + X = int(lib.SDL_GAMEPAD_BUTTON_WEST) """""" - Y = int(lib.SDL_CONTROLLER_BUTTON_Y) + Y = int(lib.SDL_GAMEPAD_BUTTON_NORTH) """""" - BACK = int(lib.SDL_CONTROLLER_BUTTON_BACK) + BACK = int(lib.SDL_GAMEPAD_BUTTON_BACK) """""" - GUIDE = int(lib.SDL_CONTROLLER_BUTTON_GUIDE) + GUIDE = int(lib.SDL_GAMEPAD_BUTTON_GUIDE) """""" - START = int(lib.SDL_CONTROLLER_BUTTON_START) + START = int(lib.SDL_GAMEPAD_BUTTON_START) """""" - LEFTSTICK = int(lib.SDL_CONTROLLER_BUTTON_LEFTSTICK) + LEFTSTICK = int(lib.SDL_GAMEPAD_BUTTON_LEFT_STICK) """""" - RIGHTSTICK = int(lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK) + RIGHTSTICK = int(lib.SDL_GAMEPAD_BUTTON_RIGHT_STICK) """""" - LEFTSHOULDER = int(lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) + LEFTSHOULDER = int(lib.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) """""" - RIGHTSHOULDER = int(lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) + RIGHTSHOULDER = int(lib.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) """""" - DPAD_UP = int(lib.SDL_CONTROLLER_BUTTON_DPAD_UP) + DPAD_UP = int(lib.SDL_GAMEPAD_BUTTON_DPAD_UP) """""" - DPAD_DOWN = int(lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN) + DPAD_DOWN = int(lib.SDL_GAMEPAD_BUTTON_DPAD_DOWN) """""" - DPAD_LEFT = int(lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT) + DPAD_LEFT = int(lib.SDL_GAMEPAD_BUTTON_DPAD_LEFT) """""" - DPAD_RIGHT = int(lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) + DPAD_RIGHT = int(lib.SDL_GAMEPAD_BUTTON_DPAD_RIGHT) """""" MISC1 = 15 """""" @@ -92,29 +92,6 @@ class ControllerButton(enum.IntEnum): """""" -class Power(enum.IntEnum): - """The possible power states of a controller. - - .. seealso:: - :any:`Joystick.get_current_power` - """ - - UNKNOWN = int(lib.SDL_JOYSTICK_POWER_UNKNOWN) - """Power state is unknown.""" - EMPTY = int(lib.SDL_JOYSTICK_POWER_EMPTY) - """<= 5% power.""" - LOW = int(lib.SDL_JOYSTICK_POWER_LOW) - """<= 20% power.""" - MEDIUM = int(lib.SDL_JOYSTICK_POWER_MEDIUM) - """<= 70% power.""" - FULL = int(lib.SDL_JOYSTICK_POWER_FULL) - """<= 100% power.""" - WIRED = int(lib.SDL_JOYSTICK_POWER_WIRED) - """""" - MAX = int(lib.SDL_JOYSTICK_POWER_MAX) - """""" - - class Joystick: """A low-level SDL joystick. @@ -128,19 +105,19 @@ class Joystick: def __init__(self, sdl_joystick_p: Any) -> None: # noqa: ANN401 self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" - self.axes: Final[int] = _check(lib.SDL_JoystickNumAxes(self.sdl_joystick_p)) + self.axes: Final[int] = _check_int(lib.SDL_GetNumJoystickAxes(self.sdl_joystick_p), failure=-1) """The total number of axes.""" - self.balls: Final[int] = _check(lib.SDL_JoystickNumBalls(self.sdl_joystick_p)) + self.balls: Final[int] = _check_int(lib.SDL_GetNumJoystickBalls(self.sdl_joystick_p), failure=-1) """The total number of trackballs.""" - self.buttons: Final[int] = _check(lib.SDL_JoystickNumButtons(self.sdl_joystick_p)) + self.buttons: Final[int] = _check_int(lib.SDL_GetNumJoystickButtons(self.sdl_joystick_p), failure=-1) """The total number of buttons.""" - self.hats: Final[int] = _check(lib.SDL_JoystickNumHats(self.sdl_joystick_p)) + self.hats: Final[int] = _check_int(lib.SDL_GetNumJoystickHats(self.sdl_joystick_p), failure=-1) """The total number of hats.""" - self.name: Final[str] = str(ffi.string(lib.SDL_JoystickName(self.sdl_joystick_p)), encoding="utf-8") + self.name: Final[str] = str(ffi.string(lib.SDL_GetJoystickName(self.sdl_joystick_p)), encoding="utf-8") """The name of this joystick.""" self.guid: Final[str] = self._get_guid() """The GUID of this joystick.""" - self.id: Final[int] = _check(lib.SDL_JoystickInstanceID(self.sdl_joystick_p)) + self.id: Final[int] = _check(lib.SDL_GetJoystickID(self.sdl_joystick_p)) """The instance ID of this joystick. This is not the same as the device ID.""" self._keep_alive: Any = None """The owner of this objects memory if this object does not own itself.""" @@ -150,7 +127,7 @@ def __init__(self, sdl_joystick_p: Any) -> None: # noqa: ANN401 @classmethod def _open(cls, device_index: int) -> Joystick: tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.JOYSTICK) - p = _check_p(ffi.gc(lib.SDL_JoystickOpen(device_index), lib.SDL_JoystickClose)) + p = _check_p(ffi.gc(lib.SDL_OpenJoystick(device_index), lib.SDL_CloseJoystick)) return cls(p) @classmethod @@ -167,30 +144,26 @@ def __hash__(self) -> int: def _get_guid(self) -> str: guid_str = ffi.new("char[33]") - lib.SDL_JoystickGetGUIDString(lib.SDL_JoystickGetGUID(self.sdl_joystick_p), guid_str, len(guid_str)) + lib.SDL_GUIDToString(lib.SDL_GetJoystickGUID(self.sdl_joystick_p), guid_str, len(guid_str)) return str(ffi.string(guid_str), encoding="ascii") - def get_current_power(self) -> Power: - """Return the power level/state of this joystick. See :any:`Power`.""" - return Power(lib.SDL_JoystickCurrentPowerLevel(self.sdl_joystick_p)) - def get_axis(self, axis: int) -> int: """Return the raw value of `axis` in the range -32768 to 32767.""" - return int(lib.SDL_JoystickGetAxis(self.sdl_joystick_p, axis)) + return int(lib.SDL_GetJoystickAxis(self.sdl_joystick_p, axis)) def get_ball(self, ball: int) -> tuple[int, int]: """Return the values (delta_x, delta_y) of `ball` since the last poll.""" xy = ffi.new("int[2]") - _check(lib.SDL_JoystickGetBall(ball, xy, xy + 1)) + _check(lib.SDL_GetJoystickBall(self.sdl_joystick_p, ball, xy, xy + 1)) return int(xy[0]), int(xy[1]) def get_button(self, button: int) -> bool: """Return True if `button` is currently held.""" - return bool(lib.SDL_JoystickGetButton(self.sdl_joystick_p, button)) + return bool(lib.SDL_GetJoystickButton(self.sdl_joystick_p, button)) def get_hat(self, hat: int) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: """Return the direction of `hat` as (x, y). With (-1, -1) being in the upper-left.""" - return _HAT_DIRECTIONS[lib.SDL_JoystickGetHat(self.sdl_joystick_p, hat)] + return _HAT_DIRECTIONS[lib.SDL_GetJoystickHat(self.sdl_joystick_p, hat)] class GameController: @@ -201,14 +174,14 @@ class GameController: def __init__(self, sdl_controller_p: Any) -> None: # noqa: ANN401 self.sdl_controller_p: Final = sdl_controller_p - self.joystick: Final = Joystick(lib.SDL_GameControllerGetJoystick(self.sdl_controller_p)) + self.joystick: Final = Joystick(lib.SDL_GetGamepadJoystick(self.sdl_controller_p)) """The :any:`Joystick` associated with this controller.""" self.joystick._keep_alive = self.sdl_controller_p # This objects real owner needs to be kept alive. self._by_instance_id[self.joystick.id] = self @classmethod def _open(cls, joystick_index: int) -> GameController: - return cls(_check_p(ffi.gc(lib.SDL_GameControllerOpen(joystick_index), lib.SDL_GameControllerClose))) + return cls(_check_p(ffi.gc(lib.SDL_OpenGamepad(joystick_index), lib.SDL_CloseGamepad))) @classmethod def _from_instance_id(cls, instance_id: int) -> GameController: @@ -216,7 +189,7 @@ def _from_instance_id(cls, instance_id: int) -> GameController: def get_button(self, button: ControllerButton) -> bool: """Return True if `button` is currently held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, button)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, button)) def get_axis(self, axis: ControllerAxis) -> int: """Return the state of the given `axis`. @@ -224,7 +197,7 @@ def get_axis(self, axis: ControllerAxis) -> int: The state is usually a value from -32768 to 32767, with positive values towards the lower-right direction. Triggers have the range of 0 to 32767 instead. """ - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, axis)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, axis)) def __eq__(self, other: object) -> bool: if isinstance(other, GameController): @@ -238,126 +211,126 @@ def __hash__(self) -> int: @property def _left_x(self) -> int: """Return the position of this axis (-32768 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTX)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFTX)) @property def _left_y(self) -> int: """Return the position of this axis (-32768 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_LEFTY)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFTY)) @property def _right_x(self) -> int: """Return the position of this axis (-32768 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTX)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHTX)) @property def _right_y(self) -> int: """Return the position of this axis (-32768 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_RIGHTY)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHTY)) @property def _trigger_left(self) -> int: """Return the position of this trigger (0 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) @property def _trigger_right(self) -> int: """Return the position of this trigger (0 to 32767).""" - return int(lib.SDL_GameControllerGetAxis(self.sdl_controller_p, lib.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) + return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, lib.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) @property def _a(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_A)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_SOUTH)) @property def _b(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_B)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_EAST)) @property def _x(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_X)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_WEST)) @property def _y(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_Y)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_NORTH)) @property def _back(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_BACK)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_BACK)) @property def _guide(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_GUIDE)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_GUIDE)) @property def _start(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_START)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_START)) @property def _left_stick(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSTICK)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_STICK)) @property def _right_stick(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSTICK)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_STICK)) @property def _left_shoulder(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) @property def _right_shoulder(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) @property def _dpad(self) -> tuple[Literal[-1, 0, 1], Literal[-1, 0, 1]]: return ( - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) - - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_LEFT), - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_DOWN) - - lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_DPAD_UP), - ) + lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_RIGHT) + - lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_LEFT), + lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_DOWN) + - lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_DPAD_UP), + ) # type: ignore[return-value] # Boolean math has predictable values @property def _misc1(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_MISC1)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_MISC1)) @property def _paddle1(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE1)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1)) @property def _paddle2(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE2)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1)) @property def _paddle3(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE3)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2)) @property def _paddle4(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_PADDLE4)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2)) @property def _touchpad(self) -> bool: """Return True if this button is held.""" - return bool(lib.SDL_GameControllerGetButton(self.sdl_controller_p, lib.SDL_CONTROLLER_BUTTON_TOUCHPAD)) + return bool(lib.SDL_GetGamepadButton(self.sdl_controller_p, lib.SDL_GAMEPAD_BUTTON_TOUCHPAD)) def init() -> None: @@ -371,7 +344,9 @@ def init() -> None: def _get_number() -> int: """Return the number of attached joysticks.""" init() - return _check(lib.SDL_NumJoysticks()) + count = ffi.new("int*") + lib.SDL_GetJoysticks(count) + return int(count[0]) def get_joysticks() -> list[Joystick]: @@ -384,7 +359,7 @@ def get_controllers() -> list[GameController]: This ignores joysticks without a game controller mapping. """ - return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGameController(i)] + return [GameController._open(i) for i in range(_get_number()) if lib.SDL_IsGamepad(i)] def _get_all() -> list[Joystick | GameController]: @@ -393,24 +368,30 @@ def _get_all() -> list[Joystick | GameController]: If the joystick has a controller mapping then it is returned as a :any:`GameController`. Otherwise it is returned as a :any:`Joystick`. """ - return [GameController._open(i) if lib.SDL_IsGameController(i) else Joystick._open(i) for i in range(_get_number())] + return [GameController._open(i) if lib.SDL_IsGamepad(i) else Joystick._open(i) for i in range(_get_number())] def joystick_event_state(new_state: bool | None = None) -> bool: """Check or set joystick event polling. .. seealso:: - https://wiki.libsdl.org/SDL_JoystickEventState + https://wiki.libsdl.org/SDL3/SDL_SetJoystickEventsEnabled """ - _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE} - return bool(_check(lib.SDL_JoystickEventState(_OPTIONS[new_state]))) + if new_state is True: + lib.SDL_SetJoystickEventsEnabled(True) # noqa: FBT003 + elif new_state is False: + lib.SDL_SetJoystickEventsEnabled(False) # noqa: FBT003 + return lib.SDL_JoystickEventsEnabled() def controller_event_state(new_state: bool | None = None) -> bool: """Check or set game controller event polling. .. seealso:: - https://wiki.libsdl.org/SDL_GameControllerEventState + https://wiki.libsdl.org/SDL3/SDL_SetGamepadEventsEnabled """ - _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_IGNORE, True: lib.SDL_ENABLE} - return bool(_check(lib.SDL_GameControllerEventState(_OPTIONS[new_state]))) + if new_state is True: + lib.SDL_SetGamepadEventsEnabled(True) # noqa: FBT003 + elif new_state is False: + lib.SDL_SetGamepadEventsEnabled(False) # noqa: FBT003 + return lib.SDL_GamepadEventsEnabled() diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index de481427..f4c16ed5 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any import numpy as np +from typing_extensions import deprecated import tcod.event import tcod.sdl.video @@ -41,7 +42,7 @@ def __eq__(self, other: object) -> bool: @classmethod def _claim(cls, sdl_cursor_p: Any) -> Cursor: """Verify and wrap this pointer in a garbage collector before returning a Cursor.""" - return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_FreeCursor)) + return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_DestroyCursor)) class SystemCursor(enum.IntEnum): @@ -174,19 +175,28 @@ def capture(enable: bool) -> None: _check(lib.SDL_CaptureMouse(enable)) +@deprecated("Set 'Window.relative_mouse_mode = value' instead.") def set_relative_mode(enable: bool) -> None: """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion. .. seealso:: :any:`tcod.sdl.mouse.capture` - https://wiki.libsdl.org/SDL_SetRelativeMouseMode + https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode + + .. deprecated:: Unreleased + Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` """ - _check(lib.SDL_SetRelativeMouseMode(enable)) + _check(lib.SDL_SetWindowRelativeMouseMode(lib.SDL_GetMouseFocus(), enable)) +@deprecated("Check 'Window.relative_mouse_mode' instead.") def get_relative_mode() -> bool: - """Return True if relative mouse mode is enabled.""" - return bool(lib.SDL_GetRelativeMouseMode()) + """Return True if relative mouse mode is enabled. + + .. deprecated:: Unreleased + Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` + """ + return bool(lib.SDL_GetWindowRelativeMouseMode(lib.SDL_GetMouseFocus())) def get_global_state() -> tcod.event.MouseState: @@ -249,5 +259,8 @@ def show(visible: bool | None = None) -> bool: .. versionadded:: 16.0 """ - _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_DISABLE, True: lib.SDL_ENABLE} - return _check(lib.SDL_ShowCursor(_OPTIONS[visible])) == int(lib.SDL_ENABLE) + if visible is True: + lib.SDL_ShowCursor() + elif visible is False: + lib.SDL_HideCursor() + return lib.SDL_CursorVisible() diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 83606a03..0a1052e1 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -10,10 +10,12 @@ from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np +from typing_extensions import deprecated +import tcod.sdl.constants import tcod.sdl.video from tcod.cffi import ffi, lib -from tcod.sdl._internal import _check, _check_p, _required_version +from tcod.sdl._internal import Properties, _check, _check_p if TYPE_CHECKING: from numpy.typing import NDArray @@ -41,6 +43,26 @@ class RendererFlip(enum.IntFlag): """Flip the image vertically.""" +class LogicalPresentation(enum.IntEnum): + """SDL logical presentation modes. + + See https://wiki.libsdl.org/SDL3/SDL_RendererLogicalPresentation + + .. versionadded:: Unreleased + """ + + DISABLED = 0 + """""" + STRETCH = 1 + """""" + LETTERBOX = 2 + """""" + OVERSCAN = 3 + """""" + INTEGER_SCALE = 4 + """""" + + class BlendFactor(enum.IntEnum): """SDL blend factors. @@ -155,18 +177,21 @@ def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: """Encapsulate an SDL_Texture pointer. This function is private.""" self.p = sdl_texture_p self._sdl_renderer_p = sdl_renderer_p # Keep alive. - query = self._query() - self.format: Final[int] = query[0] + + props = Properties(lib.SDL_GetTextureProperties(self.p)) + self.format: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_FORMAT_NUMBER, int)] """Texture format, read only.""" - self.access: Final[TextureAccess] = TextureAccess(query[1]) + self.access: Final[TextureAccess] = TextureAccess( + props[(tcod.sdl.constants.SDL_PROP_TEXTURE_ACCESS_NUMBER, int)] + ) """Texture access mode, read only. .. versionchanged:: 13.5 Attribute is now a :any:`TextureAccess` value. """ - self.width: Final[int] = query[2] + self.width: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_WIDTH_NUMBER, int)] """Texture pixel width, read only.""" - self.height: Final[int] = query[3] + self.height: Final[int] = props[(tcod.sdl.constants.SDL_PROP_TEXTURE_HEIGHT_NUMBER, int)] """Texture pixel height, read only.""" def __eq__(self, other: object) -> bool: @@ -175,13 +200,6 @@ def __eq__(self, other: object) -> bool: return bool(self.p == other.p) return NotImplemented - def _query(self) -> tuple[int, int, int, int]: - """Return (format, access, width, height).""" - format = ffi.new("uint32_t*") - buffer = ffi.new("int[3]") - lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2) - return int(format[0]), int(buffer[0]), int(buffer[1]), int(buffer[2]) - def update(self, pixels: NDArray[Any], rect: tuple[int, int, int, int] | None = None) -> None: """Update the pixel data of this texture. @@ -289,7 +307,7 @@ def copy( # noqa: PLR0913 Added the `angle`, `center`, and `flip` parameters. """ _check( - lib.SDL_RenderCopyExF( + lib.SDL_RenderTextureRotated( self.p, texture.p, (source,) if source is not None else ffi.NULL, @@ -393,7 +411,7 @@ def output_size(self) -> tuple[int, int]: .. versionadded:: 13.5 """ out = ffi.new("int[2]") - _check(lib.SDL_GetRendererOutputSize(self.p, out, out + 1)) + _check(lib.SDL_GetCurrentRenderOutputSize(self.p, out, out + 1)) return out[0], out[1] @property @@ -404,50 +422,51 @@ def clip_rect(self) -> tuple[int, int, int, int] | None: .. versionadded:: 13.5 """ - if not lib.SDL_RenderIsClipEnabled(self.p): + if not lib.SDL_RenderClipEnabled(self.p): return None rect = ffi.new("SDL_Rect*") - lib.SDL_RenderGetClipRect(self.p, rect) + lib.SDL_GetRenderClipRect(self.p, rect) return rect.x, rect.y, rect.w, rect.h @clip_rect.setter def clip_rect(self, rect: tuple[int, int, int, int] | None) -> None: rect_p = ffi.NULL if rect is None else ffi.new("SDL_Rect*", rect) - _check(lib.SDL_RenderSetClipRect(self.p, rect_p)) + _check(lib.SDL_SetRenderClipRect(self.p, rect_p)) - @property - def integer_scaling(self) -> bool: - """Get or set if this renderer enforces integer scaling. + def set_logical_presentation(self, resolution: tuple[int, int], mode: LogicalPresentation) -> None: + """Set this renderers device independent resolution. .. seealso:: - https://wiki.libsdl.org/SDL_RenderSetIntegerScale + https://wiki.libsdl.org/SDL3/SDL_SetRenderLogicalPresentation - .. versionadded:: 13.5 + .. versionadded:: Unreleased """ - return bool(lib.SDL_RenderGetIntegerScale(self.p)) - - @integer_scaling.setter - def integer_scaling(self, enable: bool) -> None: - _check(lib.SDL_RenderSetIntegerScale(self.p, enable)) + width, height = resolution + _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, mode)) @property def logical_size(self) -> tuple[int, int]: - """Get or set a device independent (width, height) resolution. + """Get current independent (width, height) resolution. Might be (0, 0) if a resolution was never assigned. .. seealso:: - https://wiki.libsdl.org/SDL_RenderSetLogicalSize + https://wiki.libsdl.org/SDL3/SDL_GetRenderLogicalPresentation .. versionadded:: 13.5 + + .. versionchanged:: Unreleased + Setter is deprecated, use :any:`set_logical_presentation` instead. """ out = ffi.new("int[2]") - lib.SDL_RenderGetLogicalSize(self.p, out, out + 1) + lib.SDL_GetRenderLogicalPresentation(self.p, out, out + 1, ffi.NULL) return out[0], out[1] @logical_size.setter + @deprecated("Use set_logical_presentation method to correctly setup logical size.") def logical_size(self, size: tuple[int, int]) -> None: - _check(lib.SDL_RenderSetLogicalSize(self.p, *size)) + width, height = size + _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, lib.SDL_LOGICAL_PRESENTATION_STRETCH)) @property def scale(self) -> tuple[float, float]: @@ -459,12 +478,12 @@ def scale(self) -> tuple[float, float]: .. versionadded:: 13.5 """ out = ffi.new("float[2]") - lib.SDL_RenderGetScale(self.p, out, out + 1) + lib.SDL_GetRenderScale(self.p, out, out + 1) return out[0], out[1] @scale.setter def scale(self, scale: tuple[float, float]) -> None: - _check(lib.SDL_RenderSetScale(self.p, *scale)) + _check(lib.SDL_SetRenderScale(self.p, *scale)) @property def viewport(self) -> tuple[int, int, int, int] | None: @@ -476,26 +495,25 @@ def viewport(self) -> tuple[int, int, int, int] | None: .. versionadded:: 13.5 """ rect = ffi.new("SDL_Rect*") - lib.SDL_RenderGetViewport(self.p, rect) + lib.SDL_GetRenderViewport(self.p, rect) return rect.x, rect.y, rect.w, rect.h @viewport.setter def viewport(self, rect: tuple[int, int, int, int] | None) -> None: - _check(lib.SDL_RenderSetViewport(self.p, (rect,))) + _check(lib.SDL_SetRenderViewport(self.p, (rect,))) - @_required_version((2, 0, 18)) def set_vsync(self, enable: bool) -> None: """Enable or disable VSync for this renderer. .. versionadded:: 13.5 """ - _check(lib.SDL_RenderSetVSync(self.p, enable)) + _check(lib.SDL_SetRenderVSync(self.p, enable)) def read_pixels( self, *, rect: tuple[int, int, int, int] | None = None, - format: int | Literal["RGB", "RGBA"] = "RGBA", + format: Literal["RGB", "RGBA"] = "RGBA", # noqa: A002 out: NDArray[np.uint8] | None = None, ) -> NDArray[np.uint8]: """Fetch the pixel contents of the current rendering target to an array. @@ -512,49 +530,37 @@ def read_pixels( This operation is slow due to coping from VRAM to RAM. When reading the main rendering target this should be called after rendering and before :any:`present`. - See https://wiki.libsdl.org/SDL2/SDL_RenderReadPixels + See https://wiki.libsdl.org/SDL3/SDL_RenderReadPixels Returns: The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels. .. versionadded:: 15.0 + + .. versionchanged:: Unreleased + `format` no longer accepts `int` values. """ - FORMATS: Final = {"RGB": lib.SDL_PIXELFORMAT_RGB24, "RGBA": lib.SDL_PIXELFORMAT_RGBA32} - sdl_format = FORMATS.get(format) if isinstance(format, str) else format - if rect is None: - texture_p = lib.SDL_GetRenderTarget(self.p) - if texture_p: - texture = Texture(texture_p) - rect = (0, 0, texture.width, texture.height) - else: - rect = (0, 0, *self.output_size) - width, height = rect[2:4] + surface = _check_p( + ffi.gc(lib.SDL_RenderReadPixels(self.p, (rect,) if rect is not None else ffi.NULL), lib.SDL_DestroySurface) + ) + width, height = rect[2:4] if rect is not None else (int(surface.w), int(surface.h)) + depth = {"RGB": 3, "RGBA": 4}.get(format) + if depth is None: + msg = f"Pixel format {format!r} not supported by tcod." + raise TypeError(msg) + expected_shape = height, width, depth if out is None: - if sdl_format == lib.SDL_PIXELFORMAT_RGBA32: - out = np.empty((height, width, 4), dtype=np.uint8) - elif sdl_format == lib.SDL_PIXELFORMAT_RGB24: - out = np.empty((height, width, 3), dtype=np.uint8) - else: - msg = f"Pixel format {format!r} not supported by tcod." - raise TypeError(msg) + out = np.empty(expected_shape, dtype=np.uint8) if out.dtype != np.uint8: msg = "`out` must be a uint8 array." raise TypeError(msg) - expected_shape = (height, width, {lib.SDL_PIXELFORMAT_RGB24: 3, lib.SDL_PIXELFORMAT_RGBA32: 4}[sdl_format]) if out.shape != expected_shape: msg = f"Expected `out` to be an array of shape {expected_shape}, got {out.shape} instead." raise TypeError(msg) if not out[0].flags.c_contiguous: msg = "`out` array must be C contiguous." - _check( - lib.SDL_RenderReadPixels( - self.p, - (rect,), - sdl_format, - ffi.cast("void*", out.ctypes.data), - out.strides[0], - ) - ) + out_surface = tcod.sdl.video._TempSurface(out) + _check(lib.SDL_BlitSurface(surface, ffi.NULL, out_surface.p, ffi.NULL)) return out def clear(self) -> None: @@ -569,14 +575,14 @@ def fill_rect(self, rect: tuple[float, float, float, float]) -> None: .. versionadded:: 13.5 """ - _check(lib.SDL_RenderFillRectF(self.p, (rect,))) + _check(lib.SDL_RenderFillRect(self.p, (rect,))) def draw_rect(self, rect: tuple[float, float, float, float]) -> None: """Draw a rectangle outline. .. versionadded:: 13.5 """ - _check(lib.SDL_RenderDrawRectF(self.p, (rect,))) + _check(lib.SDL_RenderFillRects(self.p, (rect,), 1)) def draw_point(self, xy: tuple[float, float]) -> None: """Draw a point. @@ -584,7 +590,7 @@ def draw_point(self, xy: tuple[float, float]) -> None: .. versionadded:: 13.5 """ x, y = xy - _check(lib.SDL_RenderDrawPointF(self.p, x, y)) + _check(lib.SDL_RenderPoint(self.p, x, y)) def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> None: """Draw a single line. @@ -593,20 +599,15 @@ def draw_line(self, start: tuple[float, float], end: tuple[float, float]) -> Non """ x1, y1 = start x2, y2 = end - _check(lib.SDL_RenderDrawLineF(self.p, x1, y1, x2, y2)) + _check(lib.SDL_RenderLine(self.p, x1, y1, x2, y2)) @staticmethod - def _convert_array( - array: NDArray[np.number] | Sequence[Sequence[float]], item_length: int - ) -> NDArray[np.intc] | NDArray[np.float32]: + def _convert_array(array: NDArray[np.number] | Sequence[Sequence[float]], item_length: int) -> NDArray[np.float32]: """Convert ndarray for a SDL function expecting a C contiguous array of either intc or float32. Array shape is enforced to be (n, item_length) """ - if getattr(array, "dtype", None) in (np.intc, np.int8, np.int16, np.int32, np.uint8, np.uint16): - out = np.ascontiguousarray(array, np.intc) - else: - out = np.ascontiguousarray(array, np.float32) + out = np.ascontiguousarray(array, np.float32) if len(out.shape) != 2: # noqa: PLR2004 msg = f"Array must have 2 axes, but shape is {out.shape!r}" raise TypeError(msg) @@ -624,10 +625,7 @@ def fill_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, fl .. versionadded:: 13.5 """ rects = self._convert_array(rects, item_length=4) - if rects.dtype == np.intc: - _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) - return - _check(lib.SDL_RenderFillRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None: """Draw multiple outlined rectangles from an array. @@ -640,10 +638,7 @@ def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, fl rects = self._convert_array(rects, item_length=4) assert len(rects.shape) == 2 # noqa: PLR2004 assert rects.shape[1] == 4 # noqa: PLR2004 - if rects.dtype == np.intc: - _check(lib.SDL_RenderDrawRects(self.p, tcod.ffi.from_buffer("SDL_Rect*", rects), rects.shape[0])) - return - _check(lib.SDL_RenderDrawRectsF(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + _check(lib.SDL_RenderRects(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw an array of points. @@ -654,10 +649,7 @@ def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]] .. versionadded:: 13.5 """ points = self._convert_array(points, item_length=2) - if points.dtype == np.intc: - _check(lib.SDL_RenderDrawPoints(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0])) - return - _check(lib.SDL_RenderDrawPointsF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) + _check(lib.SDL_RenderPoints(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw a connected series of lines from an array. @@ -668,17 +660,13 @@ def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) .. versionadded:: 13.5 """ points = self._convert_array(points, item_length=2) - if points.dtype == np.intc: - _check(lib.SDL_RenderDrawLines(self.p, tcod.ffi.from_buffer("SDL_Point*", points), points.shape[0] - 1)) - return - _check(lib.SDL_RenderDrawLinesF(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0] - 1)) + _check(lib.SDL_RenderLines(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) - @_required_version((2, 0, 18)) def geometry( self, texture: Texture | None, xy: NDArray[np.float32] | Sequence[tuple[float, float]], - color: NDArray[np.uint8] | Sequence[tuple[int, int, int, int]], + color: NDArray[np.float32] | Sequence[tuple[float, float, float, float]], uv: NDArray[np.float32] | Sequence[tuple[float, float]], indices: NDArray[np.uint8 | np.uint16 | np.uint32] | None = None, ) -> None: @@ -692,12 +680,15 @@ def geometry( indices: A sequence of indexes referring to the buffered data, every 3 indexes is a triangle to render. .. versionadded:: 13.5 + + .. versionchanged:: Unreleased + `color` now takes float values instead of 8-bit integers. """ xy = np.ascontiguousarray(xy, np.float32) assert len(xy.shape) == 2 # noqa: PLR2004 assert xy.shape[1] == 2 # noqa: PLR2004 - color = np.ascontiguousarray(color, np.uint8) + color = np.ascontiguousarray(color, np.float32) assert len(color.shape) == 2 # noqa: PLR2004 assert color.shape[1] == 4 # noqa: PLR2004 @@ -715,7 +706,7 @@ def geometry( texture.p if texture else ffi.NULL, ffi.cast("float*", xy.ctypes.data), xy.strides[0], - ffi.cast("SDL_Color*", color.ctypes.data), + ffi.cast("SDL_FColor*", color.ctypes.data), color.strides[0], ffi.cast("float*", uv.ctypes.data), uv.strides[0], @@ -730,36 +721,35 @@ def geometry( def new_renderer( window: tcod.sdl.video.Window, *, - driver: int | None = None, - software: bool = False, - vsync: bool = True, - target_textures: bool = False, + driver: str | None = None, + vsync: int = True, ) -> Renderer: """Initialize and return a new SDL Renderer. Args: window: The window that this renderer will be attached to. driver: Force SDL to use a specific video driver. - software: If True then a software renderer will be forced. By default a hardware renderer is used. vsync: If True then Vsync will be enabled. - target_textures: If True then target textures can be used by the renderer. Example:: # Start by creating a window. sdl_window = tcod.sdl.video.new_window(640, 480) # Create a renderer with target texture support. - sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True) + sdl_renderer = tcod.sdl.render.new_renderer(sdl_window) .. seealso:: :func:`tcod.sdl.video.new_window` + + .. versionchanged:: Unreleased + Removed `software` and `target_textures` parameters. + `vsync` now takes an integer. + `driver` now take a string. """ - driver = driver if driver is not None else -1 - flags = 0 - if vsync: - flags |= int(lib.SDL_RENDERER_PRESENTVSYNC) - if target_textures: - flags |= int(lib.SDL_RENDERER_TARGETTEXTURE) - flags |= int(lib.SDL_RENDERER_SOFTWARE) if software else int(lib.SDL_RENDERER_ACCELERATED) - renderer_p = _check_p(ffi.gc(lib.SDL_CreateRenderer(window.p, driver, flags), lib.SDL_DestroyRenderer)) + props = Properties() + props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, int)] = vsync + props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, tcod.sdl.video.Window)] = window + if driver is not None: + props[(tcod.sdl.constants.SDL_PROP_RENDERER_CREATE_NAME_STRING, str)] = driver + renderer_p = _check_p(ffi.gc(lib.SDL_CreateRendererWithProperties(props.p), lib.SDL_DestroyRenderer)) return Renderer(renderer_p) diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 7e7b2d88..869d6448 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -18,14 +18,16 @@ class Subsystem(enum.IntFlag): GAMECONTROLLER = 0x00002000 EVENTS = 0x00004000 SENSOR = 0x00008000 - EVERYTHING = int(lib.SDL_INIT_EVERYTHING) -def init(flags: int = Subsystem.EVERYTHING) -> None: +def init(flags: int) -> None: _check(lib.SDL_InitSubSystem(flags)) -def quit(flags: int = Subsystem.EVERYTHING) -> None: +def quit(flags: int | None = None) -> None: + if flags is None: + lib.SDL_Quit() + return lib.SDL_QuitSubSystem(flags) diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index b140aa17..cee02bf5 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -14,9 +14,11 @@ from typing import TYPE_CHECKING, Any import numpy as np +from typing_extensions import Self, deprecated +import tcod.sdl.constants from tcod.cffi import ffi, lib -from tcod.sdl._internal import _check, _check_p, _required_version, _version_at_least +from tcod.sdl._internal import Properties, _check, _check_p, _required_version if TYPE_CHECKING: from numpy.typing import ArrayLike, NDArray @@ -40,12 +42,8 @@ class WindowFlags(enum.IntFlag): FULLSCREEN = int(lib.SDL_WINDOW_FULLSCREEN) """""" - FULLSCREEN_DESKTOP = int(lib.SDL_WINDOW_FULLSCREEN_DESKTOP) - """""" OPENGL = int(lib.SDL_WINDOW_OPENGL) """""" - SHOWN = int(lib.SDL_WINDOW_SHOWN) - """""" HIDDEN = int(lib.SDL_WINDOW_HIDDEN) """""" BORDERLESS = int(lib.SDL_WINDOW_BORDERLESS) @@ -56,22 +54,18 @@ class WindowFlags(enum.IntFlag): """""" MAXIMIZED = int(lib.SDL_WINDOW_MAXIMIZED) """""" - MOUSE_GRABBED = int(lib.SDL_WINDOW_INPUT_GRABBED) + MOUSE_GRABBED = int(lib.SDL_WINDOW_MOUSE_GRABBED) """""" INPUT_FOCUS = int(lib.SDL_WINDOW_INPUT_FOCUS) """""" MOUSE_FOCUS = int(lib.SDL_WINDOW_MOUSE_FOCUS) """""" - FOREIGN = int(lib.SDL_WINDOW_FOREIGN) - """""" - ALLOW_HIGHDPI = int(lib.SDL_WINDOW_ALLOW_HIGHDPI) + ALLOW_HIGHDPI = int(lib.SDL_WINDOW_HIGH_PIXEL_DENSITY) """""" MOUSE_CAPTURE = int(lib.SDL_WINDOW_MOUSE_CAPTURE) """""" ALWAYS_ON_TOP = int(lib.SDL_WINDOW_ALWAYS_ON_TOP) """""" - SKIP_TASKBAR = int(lib.SDL_WINDOW_SKIP_TASKBAR) - """""" UTILITY = int(lib.SDL_WINDOW_UTILITY) """""" TOOLTIP = int(lib.SDL_WINDOW_TOOLTIP) @@ -107,18 +101,16 @@ def __init__(self, pixels: ArrayLike) -> None: msg = f"NumPy array must have RGB or RGBA channels. (got {self._array.shape})" raise TypeError(msg) self.p = ffi.gc( - lib.SDL_CreateRGBSurfaceFrom( - ffi.from_buffer("void*", self._array), - self._array.shape[1], # Width. - self._array.shape[0], # Height. - self._array.shape[2] * 8, # Bit depth. - self._array.strides[1], # Pitch. - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000 if self._array.shape[2] == 4 else 0, # noqa: PLR2004 + _check_p( + lib.SDL_CreateSurfaceFrom( + self._array.shape[1], + self._array.shape[0], + lib.SDL_PIXELFORMAT_RGBA32 if self._array.shape[2] == 4 else lib.SDL_PIXELFORMAT_RGB24, + ffi.from_buffer("void*", self._array), + self._array.strides[0], + ) ), - lib.SDL_FreeSurface, + lib.SDL_DestroySurface, ) @@ -141,6 +133,13 @@ def __eq__(self, other: object) -> bool: return NotImplemented return bool(self.p == other.p) + def _as_property_pointer(self) -> Any: # noqa: ANN401 + return self.p + + @classmethod + def _from_property_pointer(cls, raw_cffi_pointer: Any, /) -> Self: # noqa: ANN401 + return cls(raw_cffi_pointer) + def set_icon(self, pixels: ArrayLike) -> None: """Set the window icon from an image. @@ -222,24 +221,19 @@ def flags(self) -> WindowFlags: return WindowFlags(lib.SDL_GetWindowFlags(self.p)) @property - def fullscreen(self) -> int: + def fullscreen(self) -> bool: """Get or set the fullscreen status of this window. - Can be set to the :any:`WindowFlags.FULLSCREEN` or :any:`WindowFlags.FULLSCREEN_DESKTOP` flags. - Example:: # Toggle fullscreen. window: tcod.sdl.video.Window - if window.fullscreen: - window.fullscreen = False # Set windowed mode. - else: - window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP + window.fullscreen = not window.fullscreen """ - return self.flags & (WindowFlags.FULLSCREEN | WindowFlags.FULLSCREEN_DESKTOP) + return bool(self.flags & WindowFlags.FULLSCREEN) @fullscreen.setter - def fullscreen(self, value: int) -> None: + def fullscreen(self, value: bool) -> None: _check(lib.SDL_SetWindowFullscreen(self.p, value)) @property @@ -271,26 +265,51 @@ def opacity(self) -> float: Will error if you try to set this and opacity isn't supported. """ - out = ffi.new("float*") - _check(lib.SDL_GetWindowOpacity(self.p, out)) - return float(out[0]) + return float(lib.SDL_GetWindowOpacity(self.p)) @opacity.setter def opacity(self, value: float) -> None: _check(lib.SDL_SetWindowOpacity(self.p, value)) @property + @deprecated("This attribute as been split into mouse_grab and keyboard_grab") def grab(self) -> bool: """Get or set this windows input grab mode. - .. seealso:: - https://wiki.libsdl.org/SDL_SetWindowGrab + .. deprecated:: Unreleased + This attribute as been split into :any:`mouse_grab` and :any:`keyboard_grab`. """ - return bool(lib.SDL_GetWindowGrab(self.p)) + return self.mouse_grab @grab.setter def grab(self, value: bool) -> None: - lib.SDL_SetWindowGrab(self.p, value) + self.mouse_grab = value + + @property + def mouse_grab(self) -> bool: + """Get or set this windows mouse input grab mode. + + .. versionadded:: Unreleased + """ + return bool(lib.SDL_GetWindowMouseGrab(self.p)) + + @mouse_grab.setter + def mouse_grab(self, value: bool, /) -> None: + lib.SDL_SetWindowMouseGrab(self.p, value) + + @property + def keyboard_grab(self) -> bool: + """Get or set this windows keyboard input grab mode. + + https://wiki.libsdl.org/SDL3/SDL_SetWindowKeyboardGrab + + .. versionadded:: Unreleased + """ + return bool(lib.SDL_GetWindowKeyboardGrab(self.p)) + + @keyboard_grab.setter + def keyboard_grab(self, value: bool, /) -> None: + lib.SDL_SetWindowKeyboardGrab(self.p, value) @property def mouse_rect(self) -> tuple[int, int, int, int] | None: @@ -300,13 +319,11 @@ def mouse_rect(self) -> tuple[int, int, int, int] | None: .. versionadded:: 13.5 """ - _version_at_least((2, 0, 18)) rect = lib.SDL_GetWindowMouseRect(self.p) return (rect.x, rect.y, rect.w, rect.h) if rect else None @mouse_rect.setter def mouse_rect(self, rect: tuple[int, int, int, int] | None) -> None: - _version_at_least((2, 0, 18)) _check(lib.SDL_SetWindowMouseRect(self.p, (rect,) if rect else ffi.NULL)) @_required_version((2, 0, 16)) @@ -338,6 +355,20 @@ def hide(self) -> None: """Hide this window.""" lib.SDL_HideWindow(self.p) + @property + def relative_mouse_mode(self) -> bool: + """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion. + + .. seealso:: + :any:`tcod.sdl.mouse.capture` + https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode + """ + return bool(lib.SDL_GetWindowRelativeMouseMode(self.p)) + + @relative_mouse_mode.setter + def relative_mouse_mode(self, enable: bool, /) -> None: + _check(lib.SDL_SetWindowRelativeMouseMode(self.p, enable)) + def new_window( # noqa: PLR0913 width: int, @@ -368,11 +399,18 @@ def new_window( # noqa: PLR0913 .. seealso:: :func:`tcod.sdl.render.new_renderer` """ - x = x if x is not None else int(lib.SDL_WINDOWPOS_UNDEFINED) - y = y if y is not None else int(lib.SDL_WINDOWPOS_UNDEFINED) if title is None: title = sys.argv[0] - window_p = ffi.gc(lib.SDL_CreateWindow(title.encode("utf-8"), x, y, width, height, flags), lib.SDL_DestroyWindow) + window_props = Properties() + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, int)] = flags + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_TITLE_STRING, str)] = title + if x is not None: + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_X_NUMBER, int)] = x + if y is not None: + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_Y_NUMBER, int)] = y + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int)] = width + window_props[(tcod.sdl.constants.SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int)] = height + window_p = ffi.gc(lib.SDL_CreateWindowWithProperties(window_props.p), lib.SDL_DestroyWindow) return Window(_check_p(window_p)) @@ -406,4 +444,4 @@ def screen_saver_allowed(allow: bool | None = None) -> bool: lib.SDL_EnableScreenSaver() else: lib.SDL_DisableScreenSaver() - return bool(lib.SDL_IsScreenSaverEnabled()) + return bool(lib.SDL_ScreenSaverEnabled()) diff --git a/tcod/tcod.c b/tcod/tcod.c index 75994b85..91f7cc6f 100644 --- a/tcod/tcod.c +++ b/tcod/tcod.c @@ -18,7 +18,7 @@ */ int bresenham(int x1, int y1, int x2, int y2, int n, int* __restrict out) { // Bresenham length is Chebyshev distance. - int length = MAX(abs(x1 - x2), abs(y1 - y2)) + 1; + int length = TCOD_MAX(abs(x1 - x2), abs(y1 - y2)) + 1; if (!out) { return length; } if (n < length) { return TCOD_set_errorv("Bresenham output length mismatched."); } TCOD_bresenham_data_t bresenham; diff --git a/tcod/tileset.py b/tcod/tileset.py index b953cab3..6362c93d 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -192,15 +192,14 @@ def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: out: NDArray[np.uint8] = np.empty((height, width, 4), np.uint8) out[:] = 9 surface_p = ffi.gc( - lib.SDL_CreateRGBSurfaceWithFormatFrom( - ffi.from_buffer("void*", out), + lib.SDL_CreateSurfaceFrom( width, height, - 32, - out.strides[0], lib.SDL_PIXELFORMAT_RGBA32, + ffi.from_buffer("void*", out), + out.strides[0], ), - lib.SDL_FreeSurface, + lib.SDL_DestroySurface, ) with surface_p, ffi.new("SDL_Surface**", surface_p) as surface_p_p: _check( diff --git a/tests/test_sdl.py b/tests/test_sdl.py index e29554df..f33f4738 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -49,15 +49,15 @@ def test_sdl_window_bad_types() -> None: def test_sdl_screen_saver(uses_window: None) -> None: - tcod.sdl.sys.init() + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.VIDEO) assert tcod.sdl.video.screen_saver_allowed(False) is False assert tcod.sdl.video.screen_saver_allowed(True) is True assert tcod.sdl.video.screen_saver_allowed() is True def test_sdl_render(uses_window: None) -> None: - window = tcod.sdl.video.new_window(1, 1) - render = tcod.sdl.render.new_renderer(window, software=True, vsync=False, target_textures=True) + window = tcod.sdl.video.new_window(4, 4) + render = tcod.sdl.render.new_renderer(window, driver="software", vsync=False) render.clear() render.present() render.clear() @@ -74,9 +74,9 @@ def test_sdl_render(uses_window: None) -> None: with pytest.raises(TypeError): render.upload_texture(np.zeros((8, 8, 5), np.uint8)) - assert (render.read_pixels() == (0, 0, 0, 255)).all() + assert render.read_pixels(rect=(0, 0, 3, 4)).shape == (4, 3, 4) assert (render.read_pixels(format="RGB") == (0, 0, 0)).all() - assert render.read_pixels(rect=(1, 2, 3, 4)).shape == (4, 3, 4) + assert (render.read_pixels() == (0, 0, 0, 255)).all() render.draw_point((0, 0)) render.draw_line((0, 0), (1, 1)) @@ -102,7 +102,7 @@ def test_sdl_render(uses_window: None) -> None: render.geometry( None, np.zeros((1, 2), np.float32), - np.zeros((1, 4), np.uint8), + np.zeros((1, 4), np.float32), np.zeros((1, 2), np.float32), np.zeros((3,), np.uint8), ) diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index f7e55093..f8da6155 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -3,6 +3,7 @@ import contextlib import sys import time +from collections.abc import Callable from typing import Any import numpy as np @@ -14,11 +15,19 @@ # ruff: noqa: D103 +def device_works(device: Callable[[], tcod.sdl.audio.AudioDevice]) -> bool: + try: + device().open().close() + except RuntimeError: + return False + return True + + needs_audio_device = pytest.mark.xfail( - not list(tcod.sdl.audio.get_devices()), reason="This test requires an audio device" + not device_works(tcod.sdl.audio.get_default_playback), reason="This test requires an audio device" ) needs_audio_capture = pytest.mark.xfail( - not list(tcod.sdl.audio.get_capture_devices()), reason="This test requires an audio capture device" + not device_works(tcod.sdl.audio.get_default_recording), reason="This test requires an audio capture device" ) @@ -31,20 +40,14 @@ def test_devices() -> None: def test_audio_device() -> None: with tcod.sdl.audio.open(frequency=44100, format=np.float32, channels=2, paused=True) as device: assert not device.stopped - assert device.convert(np.zeros(4, dtype=np.float32), 22050).shape[0] == 8 # noqa: PLR2004 - assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape == (4, 2) - assert device.convert(np.zeros(4, dtype=np.int8)).shape[0] == 4 # noqa: PLR2004 + device.convert(np.zeros(4, dtype=np.float32), 22050) + assert device.convert(np.zeros((4, 4), dtype=np.float32)).shape[1] == device.channels + device.convert(np.zeros(4, dtype=np.int8)).shape[0] assert device.paused is True device.paused = False assert device.paused is False device.paused = True - assert device.queued_samples == 0 - with pytest.raises(TypeError): - device.callback # noqa: B018 - with pytest.raises(TypeError): - device.callback = lambda _device, _stream: None - with contextlib.closing(tcod.sdl.audio.BasicMixer(device)) as mixer: - assert mixer.daemon + with contextlib.closing(tcod.sdl.audio.BasicMixer(device, frequency=44100, channels=2)) as mixer: assert mixer.play(np.zeros(4, np.float32)).busy mixer.play(np.zeros(0, np.float32)) mixer.play(np.full(1, 0.01, np.float32), on_end=lambda _: None) @@ -59,18 +62,15 @@ def test_audio_device() -> None: @needs_audio_capture def test_audio_capture() -> None: - with tcod.sdl.audio.open(capture=True) as device: - assert not device.stopped - assert isinstance(device.dequeue_audio(), np.ndarray) + with contextlib.closing(tcod.sdl.audio.get_default_recording().open()) as device: + device.new_stream(np.float32, 1, 11025).dequeue_audio() @needs_audio_device def test_audio_device_repr() -> None: - with tcod.sdl.audio.open(format=np.uint16, paused=True, callback=True) as device: + with contextlib.closing(tcod.sdl.audio.get_default_playback().open()) as device: assert not device.stopped - assert "silence=" in repr(device) - assert "callback=" in repr(device) - assert "stopped=" in repr(device) + assert "paused=False" in repr(device) def test_convert_bad_shape() -> None: @@ -83,7 +83,7 @@ def test_convert_bad_shape() -> None: def test_convert_bad_type() -> None: with pytest.raises(TypeError, match=r".*bool"): tcod.sdl.audio.convert_audio(np.zeros(8, bool), 8000, out_rate=8000, out_format=np.float32, out_channels=1) - with pytest.raises(RuntimeError, match=r"Invalid source format"): + with pytest.raises(RuntimeError, match=r"Parameter 'src_spec->format' is invalid"): tcod.sdl.audio.convert_audio(np.zeros(8, np.int64), 8000, out_rate=8000, out_format=np.float32, out_channels=1) @@ -110,7 +110,6 @@ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> check_called = CheckCalled() with tcod.sdl.audio.open(callback=check_called, paused=False) as device: assert not device.stopped - device.callback = device.callback while not check_called.was_called: time.sleep(0.001) diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 8b910c3a..25f60d0e 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -179,7 +179,6 @@ def test_mouse_repr() -> None: def test_cffi_structs() -> None: # Make sure cffi structures are the correct size. tcod.ffi.new("SDL_Event*") - tcod.ffi.new("SDL_AudioCVT*") @pytest.mark.filterwarnings("ignore") From 869f8ed4c1f62df562261bd67dfe0b8b71fc3c50 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 00:25:14 -0700 Subject: [PATCH 0969/1101] Pre-commit update Use more specific ruff-check id --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7d3a915..43080c04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,8 +17,8 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.11.13 hooks: - - id: ruff + - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] - id: ruff-format From 03a9cd044b045a5931824a2fa73a253826e7496b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 00:36:24 -0700 Subject: [PATCH 0970/1101] Update SDL to 3.2.16 Note libtcod version as well --- .github/workflows/python-package.yml | 13 +++++++------ CHANGELOG.md | 3 ++- build_sdl.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 714520ec..28ef5efc 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,6 +14,7 @@ defaults: env: git-depth: 0 # Depth to search for tags. + sdl-version: "3.2.16" # SDL version to bundle jobs: ruff: @@ -48,7 +49,7 @@ jobs: with: install-linux-dependencies: true build-type: "Debug" - version: "3.2.14" + version: ${{ env.sdl-version }} - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} @@ -72,7 +73,7 @@ jobs: strategy: matrix: os: ["windows-latest", "macos-latest"] - sdl-version: ["3.2.10"] + sdl-version: ["3.2.16"] fail-fast: true steps: - uses: actions/checkout@v4 @@ -129,7 +130,7 @@ jobs: with: install-linux-dependencies: true build-type: "Release" - version: "3.2.14" + version: ${{ env.sdl-version }} - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -172,7 +173,7 @@ jobs: with: install-linux-dependencies: true build-type: "Debug" - version: "3.2.14" + version: ${{ env.sdl-version }} - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} @@ -214,7 +215,7 @@ jobs: with: install-linux-dependencies: true build-type: "Debug" - version: "3.2.14" + version: ${{ env.sdl-version }} - name: Run tox run: | tox -vv @@ -265,7 +266,7 @@ jobs: systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \ mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \ libdrm-devel mesa-libgbm-devel libusb-devel - git clone --depth 1 --branch release-3.2.10 https://github.com/libsdl-org/SDL.git sdl_repo && + git clone --depth 1 --branch release-${{env.sdl-version}} https://github.com/libsdl-org/SDL.git sdl_repo && cmake -S sdl_repo -B sdl_build && cmake --build sdl_build --config Release && cmake --install sdl_build --config Release --prefix /usr/local && diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3495f7..4cbd924b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Changed -- Switched to SDL3. +- Updated libtcod to 2.1.1 +- Updated SDL to 3.2.16 This will cause several breaking changes such as the names of keyboard constants and other SDL enums. - `tcod.sdl.video.Window.grab` has been split into `.mouse_grab` and `.keyboard_grab` attributes. - `tcod.event.KeySym` single letter symbols are now all uppercase. diff --git a/build_sdl.py b/build_sdl.py index 08a6f667..1f7d05ec 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -33,9 +33,9 @@ # Reject versions of SDL older than this, update the requirements in the readme if you change this. SDL_MIN_VERSION = (3, 2, 0) # The SDL version to parse and export symbols from. -SDL_PARSE_VERSION = os.environ.get("SDL_VERSION", "3.2.10") +SDL_PARSE_VERSION = os.environ.get("SDL_VERSION", "3.2.16") # The SDL version to include in binary distributions. -SDL_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "3.2.10") +SDL_BUNDLE_VERSION = os.environ.get("SDL_VERSION", "3.2.16") # Used to remove excessive newlines in debug outputs. From 50721e8e12a9b2395a5df28aa935b40cfe35335f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 12:30:05 -0700 Subject: [PATCH 0971/1101] Prepare 19.0.0 release. --- CHANGELOG.md | 5 +++++ tcod/sdl/audio.py | 38 +++++++++++++++++++------------------- tcod/sdl/mouse.py | 4 ++-- tcod/sdl/render.py | 12 ++++++------ tcod/sdl/video.py | 6 +++--- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cbd924b..2e1eb24b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.0.0] - 2025-06-13 + +Finished port to SDL3, this has caused several breaking changes from SDL such as lowercase key constants now being uppercase and mouse events returning `float` instead of `int`. +Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html) on your projects to catch any issues from this update. + ### Changed - Updated libtcod to 2.1.1 diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index d15abb32..390bc551 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -217,7 +217,7 @@ class AudioDevice: .. versionchanged:: 16.0 Can now be used as a context which will close the device on exit. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Removed `spec` and `callback` attribute. `queued_samples`, `queue_audio`, and `dequeue_audio` moved to :any:`AudioStream` class. @@ -269,7 +269,7 @@ def __init__( self.is_physical: Final[bool] = bool(lib.SDL_IsAudioDevicePhysical(device_id)) """True of this is a physical device, or False if this is a logical device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ def __repr__(self) -> str: @@ -294,7 +294,7 @@ def __repr__(self) -> str: def name(self) -> str: """Name of the device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return str(ffi.string(_check_p(lib.SDL_GetAudioDeviceName(self.device_id))), encoding="utf-8") @@ -304,7 +304,7 @@ def gain(self) -> float: Default is 1.0 but can be set higher or zero. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return _check_float(lib.SDL_GetAudioDeviceGain(self.device_id), failure=-1.0) @@ -320,7 +320,7 @@ def open( ) -> Self: """Open a new logical audio device for this device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 .. seealso:: https://wiki.libsdl.org/SDL3/SDL_OpenAudioDevice @@ -349,7 +349,7 @@ def _sample_size(self) -> int: def stopped(self) -> bool: """Is True if the device has failed or was closed. - .. deprecated:: Unreleased + .. deprecated:: 19.0 No longer used by the SDL3 API. """ return bool(not hasattr(self, "device_id")) @@ -417,7 +417,7 @@ def close(self) -> None: def __enter__(self) -> Self: """Return self and enter a managed context. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Use :func:`contextlib.closing` if you want to close this device after a context. """ return self @@ -443,7 +443,7 @@ def new_stream( ) -> AudioStream: """Create, bind, and return a new :any:`AudioStream` for this device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ new_stream = AudioStream.new(format=format, channels=channels, frequency=frequency) self.bind((new_stream,)) @@ -463,7 +463,7 @@ def bind(self, streams: Iterable[AudioStream], /) -> None: class AudioStreamCallbackData: """Data provided to AudioStream callbacks. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ additional_bytes: int @@ -487,7 +487,7 @@ class AudioStream: This class is commonly created with :any:`AudioDevice.new_stream` which creates a new stream bound to the device. - ..versionadded:: Unreleased + ..versionadded:: 19.0 """ __slots__ = ("__weakref__", "_stream_p") @@ -819,10 +819,10 @@ class BasicMixer: .. versionadded:: 13.6 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Added `frequency` and `channels` parameters. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Changes in the SDL3 API have made this classes usefulness questionable. This class should be replaced with custom streams. """ @@ -927,7 +927,7 @@ def _sdl_audio_stream_callback(userdata: Any, stream_p: Any, additional_amount: def get_devices() -> dict[str, AudioDevice]: """Iterate over the available audio output devices. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Now returns a dictionary of :any:`AudioDevice`. """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) @@ -942,7 +942,7 @@ def get_devices() -> dict[str, AudioDevice]: def get_capture_devices() -> dict[str, AudioDevice]: """Iterate over the available audio capture devices. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Now returns a dictionary of :any:`AudioDevice`. """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) @@ -960,7 +960,7 @@ def get_default_playback() -> AudioDevice: Example: playback_device = tcod.sdl.audio.get_default_playback().open() - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)) @@ -972,7 +972,7 @@ def get_default_recording() -> AudioDevice: Example: recording_device = tcod.sdl.audio.get_default_recording().open() - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_RECORDING)) @@ -982,7 +982,7 @@ def get_default_recording() -> AudioDevice: class AllowedChanges(enum.IntFlag): """Which parameters are allowed to be changed when the values given are not supported. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This is no longer used. """ @@ -1033,12 +1033,12 @@ def open( # noqa: A001, PLR0913 If a callback is given then it will be called with the `AudioDevice` and a Numpy buffer of the data stream. This callback will be run on a separate thread. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 SDL3 returns audio devices differently, exact formatting is set with :any:`AudioDevice.new_stream` instead. `samples` and `allowed_changes` are ignored. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This is an outdated method. Use :any:`AudioDevice.open` instead, for example: ``tcod.sdl.audio.get_default_playback().open()`` diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index f4c16ed5..9d9227f7 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -183,7 +183,7 @@ def set_relative_mode(enable: bool) -> None: :any:`tcod.sdl.mouse.capture` https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode - .. deprecated:: Unreleased + .. deprecated:: 19.0 Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` """ _check(lib.SDL_SetWindowRelativeMouseMode(lib.SDL_GetMouseFocus(), enable)) @@ -193,7 +193,7 @@ def set_relative_mode(enable: bool) -> None: def get_relative_mode() -> bool: """Return True if relative mouse mode is enabled. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` """ return bool(lib.SDL_GetWindowRelativeMouseMode(lib.SDL_GetMouseFocus())) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 0a1052e1..511b48fc 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -48,7 +48,7 @@ class LogicalPresentation(enum.IntEnum): See https://wiki.libsdl.org/SDL3/SDL_RendererLogicalPresentation - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ DISABLED = 0 @@ -439,7 +439,7 @@ def set_logical_presentation(self, resolution: tuple[int, int], mode: LogicalPre .. seealso:: https://wiki.libsdl.org/SDL3/SDL_SetRenderLogicalPresentation - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ width, height = resolution _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, mode)) @@ -455,7 +455,7 @@ def logical_size(self) -> tuple[int, int]: .. versionadded:: 13.5 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Setter is deprecated, use :any:`set_logical_presentation` instead. """ out = ffi.new("int[2]") @@ -537,7 +537,7 @@ def read_pixels( .. versionadded:: 15.0 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 `format` no longer accepts `int` values. """ surface = _check_p( @@ -681,7 +681,7 @@ def geometry( .. versionadded:: 13.5 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 `color` now takes float values instead of 8-bit integers. """ xy = np.ascontiguousarray(xy, np.float32) @@ -741,7 +741,7 @@ def new_renderer( .. seealso:: :func:`tcod.sdl.video.new_window` - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Removed `software` and `target_textures` parameters. `vsync` now takes an integer. `driver` now take a string. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index cee02bf5..4cc0c371 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -276,7 +276,7 @@ def opacity(self, value: float) -> None: def grab(self) -> bool: """Get or set this windows input grab mode. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This attribute as been split into :any:`mouse_grab` and :any:`keyboard_grab`. """ return self.mouse_grab @@ -289,7 +289,7 @@ def grab(self, value: bool) -> None: def mouse_grab(self) -> bool: """Get or set this windows mouse input grab mode. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return bool(lib.SDL_GetWindowMouseGrab(self.p)) @@ -303,7 +303,7 @@ def keyboard_grab(self) -> bool: https://wiki.libsdl.org/SDL3/SDL_SetWindowKeyboardGrab - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return bool(lib.SDL_GetWindowKeyboardGrab(self.p)) From 7054780028d598fb63f0a666df9ab38b9437b3f0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 12:51:40 -0700 Subject: [PATCH 0972/1101] Build SDL3 for ReadTheDocs workflow Note PKG_CONFIG_PATH on pkg-config errors to debug issues with the environment sent to Python setup scripts. --- .readthedocs.yaml | 12 +++++++++++- setup.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8d733f5e..098d76db 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,17 @@ build: tools: python: "3.11" apt_packages: - - libsdl3-dev + - build-essential + - make + - pkg-config + - cmake + - ninja-build + jobs: + pre_install: + - git clone --depth 1 --branch release-3.2.16 https://github.com/libsdl-org/SDL.git sdl_repo + - cmake -S sdl_repo -B sdl_build -D CMAKE_INSTALL_PREFIX=~/.local + - cmake --build sdl_build --config Debug + - cmake --install sdl_build submodules: include: all diff --git a/setup.py b/setup.py index 6a835e5b..534cca8b 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from __future__ import annotations +import os import platform import subprocess import sys @@ -55,6 +56,10 @@ def check_sdl_version() -> None: "\nsdl3-config must be on PATH." ) raise RuntimeError(msg) from exc + except subprocess.CalledProcessError as exc: + if sys.version_info >= (3, 11): + exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") + raise print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_VERSION_NEEDED: From 03fbcaabe901fcc1376631eb743de432976a16a5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 16:06:34 -0700 Subject: [PATCH 0973/1101] Remove leftover item from changelog This meant to mention changes to logical size, but that is already in the changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1eb24b..3327d110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html - `tcod.event.KeySym` single letter symbols are now all uppercase. - Relative mouse mode is set via `tcod.sdl.video.Window.relative_mouse_mode` instead of `tcod.sdl.mouse.set_relative_mode`. - `tcod.sdl.render.new_renderer`: Removed `software` and `target_textures` parameters, `vsync` takes `int`, `driver` takes `str` instead of `int`. -- SDL renderer logical - `tcod.sdl.render.Renderer`: `integer_scaling` and `logical_size` are now set with `set_logical_presentation` method. - `tcod.sdl.render.Renderer.geometry` now takes float values for `color` instead of 8-bit integers. - `tcod.event.Point` and other mouse/tile coordinate types now use `float` instead of `int`. From f2e03d0d61637742ac48463422bf7723b867bacc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 13:01:18 -0700 Subject: [PATCH 0974/1101] Update pre-commit Apply new Ruff fixes of unused ignores --- .pre-commit-config.yaml | 2 +- build_sdl.py | 2 +- examples/samples_tcod.py | 1 - setup.py | 2 +- tests/conftest.py | 2 -- tests/test_console.py | 2 -- tests/test_deprecated.py | 2 -- tests/test_libtcodpy.py | 2 -- tests/test_noise.py | 2 -- tests/test_parser.py | 2 -- tests/test_random.py | 2 -- tests/test_sdl.py | 2 -- tests/test_sdl_audio.py | 2 -- tests/test_tcod.py | 2 -- tests/test_tileset.py | 2 -- 15 files changed, 3 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43080c04..a294765c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.0 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] diff --git a/build_sdl.py b/build_sdl.py index 1f7d05ec..b073ab77 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -20,7 +20,7 @@ import requests # This script calls a lot of programs. -# ruff: noqa: S603, S607, T201 +# ruff: noqa: S603, S607 # Ignore f-strings in logging, these will eventually be replaced with t-strings. # ruff: noqa: G004 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 7c08906a..0955f2a0 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -37,7 +37,6 @@ if TYPE_CHECKING: from numpy.typing import NDArray -# ruff: noqa: S311 if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. diff --git a/setup.py b/setup.py index 534cca8b..eff99ff1 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def check_sdl_version() -> None: ).strip() except FileNotFoundError: try: - sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S603, S607 + sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S607 except FileNotFoundError as exc: msg = ( f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." diff --git a/tests/conftest.py b/tests/conftest.py index 182cb6d6..79891a38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,8 +11,6 @@ import tcod from tcod import libtcodpy -# ruff: noqa: D103 - def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") diff --git a/tests/test_console.py b/tests/test_console.py index 4b8f8435..18668ba6 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -9,8 +9,6 @@ import tcod import tcod.console -# ruff: noqa: D103 - def test_array_read_write() -> None: console = tcod.console.Console(width=12, height=10) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 82001d47..4a960bba 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -14,8 +14,6 @@ with pytest.warns(): import libtcodpy -# ruff: noqa: D103 - def test_deprecate_color() -> None: with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"): diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 7ace644a..61895c60 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -11,8 +11,6 @@ import tcod from tcod import libtcodpy -# ruff: noqa: D103 - pytestmark = [ pytest.mark.filterwarnings("ignore::DeprecationWarning"), pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"), diff --git a/tests/test_noise.py b/tests/test_noise.py index 80023f5f..28825328 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -9,8 +9,6 @@ import tcod.noise import tcod.random -# ruff: noqa: D103 - @pytest.mark.parametrize("implementation", tcod.noise.Implementation) @pytest.mark.parametrize("algorithm", tcod.noise.Algorithm) diff --git a/tests/test_parser.py b/tests/test_parser.py index 5494b786..73c54b63 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,8 +7,6 @@ import tcod as libtcod -# ruff: noqa: D103 - @pytest.mark.filterwarnings("ignore") def test_parser() -> None: diff --git a/tests/test_random.py b/tests/test_random.py index d20045cc..764ae988 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -8,8 +8,6 @@ import tcod.random -# ruff: noqa: D103 - SCRIPT_DIR = Path(__file__).parent diff --git a/tests/test_sdl.py b/tests/test_sdl.py index f33f4738..9d23d0c8 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -9,8 +9,6 @@ import tcod.sdl.sys import tcod.sdl.video -# ruff: noqa: D103 - def test_sdl_window(uses_window: None) -> None: assert tcod.sdl.video.get_grabbed_window() is None diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index f8da6155..38250758 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -12,8 +12,6 @@ import tcod.sdl.audio -# ruff: noqa: D103 - def device_works(device: Callable[[], tcod.sdl.audio.AudioDevice]) -> bool: try: diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 25f60d0e..02eb6dbb 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -12,8 +12,6 @@ import tcod.console from tcod import libtcodpy -# ruff: noqa: D103 - def raise_Exception(*_args: object) -> NoReturn: raise RuntimeError("testing exception") # noqa: TRY003, EM101 diff --git a/tests/test_tileset.py b/tests/test_tileset.py index dd272fe7..c7281cef 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -2,8 +2,6 @@ import tcod.tileset -# ruff: noqa: D103 - def test_proc_block_elements() -> None: tileset = tcod.tileset.Tileset(8, 8) From 740357d8afed162e22d5699486435d3211828e8a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 12:43:57 -0700 Subject: [PATCH 0975/1101] Upgrade cibuildwheel to 3.0.0 Switch to GitHub actions and remove outdated actions Enable PyPy wheels explicitly, required by latest cibuildwheel Configure compile warnings to show but not fail on zlib implicit functions --- .github/workflows/python-package.yml | 19 +++---------------- build_libtcod.py | 1 + pyproject.toml | 3 +++ 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 28ef5efc..4dcbd93a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -233,23 +233,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - - name: Set up QEMU - if: ${{ matrix.arch == 'aarch64' }} - uses: docker/setup-qemu-action@v3 - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install cibuildwheel==2.23.3 + run: git submodule update --init --recursive --depth 1 - name: Build wheels - run: | - python -m cibuildwheel --output-dir wheelhouse + uses: pypa/cibuildwheel@v3.0.0 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} @@ -312,7 +299,7 @@ jobs: # Downloads SDL for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.23.3 + uses: pypa/cibuildwheel@v3.0.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 diff --git a/build_libtcod.py b/build_libtcod.py index f9a5e3dd..df4527e0 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -199,6 +199,7 @@ def walk_sources(directory: str) -> Iterator[str]: "-fPIC", "-Wno-deprecated-declarations", "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers. + "-Wno-error=implicit-function-declaration", # From zlib sources ], } diff --git a/pyproject.toml b/pyproject.toml index 27a3dc16..6f741af8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,9 @@ filterwarnings = [ "ignore:'import tcod as libtcodpy' is preferred.", ] +[tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ +enable = ["pypy"] + [tool.mypy] files = ["."] python_version = "3.10" From 9392a486aa0e5a3bfbd126c5177f86db4ff83c44 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 20 Jun 2025 03:40:16 -0700 Subject: [PATCH 0976/1101] Fix zlib implicit declarations --- build_libtcod.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index df4527e0..6de9c2ff 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -180,8 +180,8 @@ def walk_sources(directory: str) -> Iterator[str]: include_dirs.append("libtcod/src/zlib/") -if sys.platform == "darwin": - # Fix "implicit declaration of function 'close'" in zlib. +if sys.platform != "win32": + # Fix implicit declaration of multiple functions in zlib. define_macros.append(("HAVE_UNISTD_H", 1)) @@ -199,7 +199,6 @@ def walk_sources(directory: str) -> Iterator[str]: "-fPIC", "-Wno-deprecated-declarations", "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers. - "-Wno-error=implicit-function-declaration", # From zlib sources ], } From fc0f5b34c73281779e13fd449da9f9d69a78fa50 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 12:25:52 -0700 Subject: [PATCH 0977/1101] Build Pyodide wheel Disable link flags to let Emscripten take over for SDL3 Disable Py_LIMITED_API definition to workaround issue with cffi Related to #123 --- .github/workflows/python-package.yml | 27 ++++++++++++++++++++++++++- .vscode/settings.json | 1 + build_libtcod.py | 7 ++++++- build_sdl.py | 12 +++++++----- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 4dcbd93a..b6f15916 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -321,8 +321,33 @@ jobs: retention-days: 7 compression-level: 0 + pyodide: + needs: [ruff, mypy, sdist] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: ${{ env.git-depth }} + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + with: + install-linux-dependencies: true + build-type: "Debug" + version: ${{ env.sdl-version }} + - uses: pypa/cibuildwheel@v3.0.0 + env: + CIBW_PLATFORM: pyodide + - name: Archive wheel + uses: actions/upload-artifact@v4 + with: + name: wheels-pyodide + path: wheelhouse/*.whl + retention-days: 30 + compression-level: 0 + publish: - needs: [sdist, build, build-macos, linux-wheels] + needs: [sdist, build, build-macos, linux-wheels, pyodide] runs-on: ubuntu-latest if: github.ref_type == 'tag' environment: diff --git a/.vscode/settings.json b/.vscode/settings.json index 446a6363..2ef44da6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -350,6 +350,7 @@ "pycall", "pycparser", "pyinstaller", + "pyodide", "pypa", "PYPI", "pypiwin", diff --git a/build_libtcod.py b/build_libtcod.py index 6de9c2ff..c7b0d577 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -162,7 +162,12 @@ def walk_sources(directory: str) -> Iterator[str]: libraries: list[str] = [*build_sdl.libraries] library_dirs: list[str] = [*build_sdl.library_dirs] -define_macros: list[tuple[str, Any]] = [("Py_LIMITED_API", Py_LIMITED_API)] +define_macros: list[tuple[str, Any]] = [] + +if "PYODIDE" not in os.environ: + # Unable to apply Py_LIMITED_API to Pyodide in cffi<=1.17.1 + # https://github.com/python-cffi/cffi/issues/179 + define_macros.append(("Py_LIMITED_API", Py_LIMITED_API)) sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") diff --git a/build_sdl.py b/build_sdl.py index b073ab77..459b37b4 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -350,8 +350,9 @@ def get_cdef() -> tuple[str, dict[str, str]]: libraries: list[str] = [] library_dirs: list[str] = [] - -if sys.platform == "darwin": +if "PYODIDE" in os.environ: + pass +elif sys.platform == "darwin": extra_link_args += ["-framework", "SDL3"] else: libraries += ["SDL3"] @@ -382,6 +383,7 @@ def get_cdef() -> tuple[str, dict[str, str]]: extra_compile_args += ( subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split() ) - extra_link_args += ( - subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() - ) + if "PYODIDE" not in os.environ: + extra_link_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() + ) From 6d2b2c7928627d4c6e2010c63e86b00690ca82a6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Jun 2025 21:18:22 -0700 Subject: [PATCH 0978/1101] Use alpha builds of Pyodide supporting SDL3 Stable version is too old and only supports SDL2 Try not to bundle win/mac binaries during Pyodide build --- .github/workflows/python-package.yml | 3 ++- .vscode/settings.json | 1 + build_sdl.py | 35 +++++++++++++++++++--------- pyproject.toml | 6 ++++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b6f15916..1545558e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -334,9 +334,10 @@ jobs: with: install-linux-dependencies: true build-type: "Debug" - version: ${{ env.sdl-version }} + version: "3.2.4" # Should be equal or less than the version used by Emscripten - uses: pypa/cibuildwheel@v3.0.0 env: + CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide - name: Archive wheel uses: actions/upload-artifact@v4 diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ef44da6..6dbcc428 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -141,6 +141,7 @@ "dunder", "DVLINE", "elif", + "Emscripten", "ENDCALL", "endianness", "epel", diff --git a/build_sdl.py b/build_sdl.py index 459b37b4..cc1fb787 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -249,16 +249,27 @@ def on_directive_handle( return super().on_directive_handle(directive, tokens, if_passthru, preceding_tokens) +def get_emscripten_include_dir() -> Path: + """Find and return the Emscripten include dir.""" + # None of the EMSDK environment variables exist! Search PATH for Emscripten as a workaround + for path in os.environ["PATH"].split(os.pathsep)[::-1]: + if Path(path).match("upstream/emscripten"): + return Path(path, "system/include").resolve(strict=True) + raise AssertionError(os.environ["PATH"]) + + check_sdl_version() -if sys.platform == "win32" or sys.platform == "darwin": +SDL_PARSE_PATH: Path | None = None +SDL_BUNDLE_PATH: Path | None = None +if (sys.platform == "win32" or sys.platform == "darwin") and "PYODIDE" not in os.environ: SDL_PARSE_PATH = unpack_sdl(SDL_PARSE_VERSION) SDL_BUNDLE_PATH = unpack_sdl(SDL_BUNDLE_VERSION) SDL_INCLUDE: Path -if sys.platform == "win32": +if sys.platform == "win32" and SDL_PARSE_PATH is not None: SDL_INCLUDE = SDL_PARSE_PATH / "include" -elif sys.platform == "darwin": +elif sys.platform == "darwin" and SDL_PARSE_PATH is not None: SDL_INCLUDE = SDL_PARSE_PATH / "Versions/A/Headers" else: # Unix matches = re.findall( @@ -275,6 +286,7 @@ def on_directive_handle( raise AssertionError(matches) assert SDL_INCLUDE +logger.info(f"{SDL_INCLUDE=}") EXTRA_CDEF = """ #define SDLK_SCANCODE_MASK ... @@ -358,7 +370,7 @@ def get_cdef() -> tuple[str, dict[str, str]]: libraries += ["SDL3"] # Bundle the Windows SDL DLL. -if sys.platform == "win32": +if sys.platform == "win32" and SDL_BUNDLE_PATH is not None: include_dirs.append(str(SDL_INCLUDE)) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) @@ -372,18 +384,19 @@ def get_cdef() -> tuple[str, dict[str, str]]: # Link to the SDL framework on MacOS. # Delocate will bundle the binaries in a later step. -if sys.platform == "darwin": +if sys.platform == "darwin" and SDL_BUNDLE_PATH is not None: include_dirs.append(SDL_INCLUDE) extra_link_args += [f"-F{SDL_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", f"{SDL_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] -# Use sdl-config to link to SDL on Linux. -if sys.platform not in ["win32", "darwin"]: +if "PYODIDE" in os.environ: + extra_compile_args += ["--use-port=sdl3"] +elif sys.platform not in ["win32", "darwin"]: + # Use sdl-config to link to SDL on Linux. extra_compile_args += ( subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split() ) - if "PYODIDE" not in os.environ: - extra_link_args += ( - subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() - ) + extra_link_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() + ) diff --git a/pyproject.toml b/pyproject.toml index 6f741af8..46a9285b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,11 @@ filterwarnings = [ ] [tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ -enable = ["pypy"] +enable = ["pypy", "pyodide-prerelease"] + +[tool.cibuildwheel.pyodide] +dependency-versions = "latest" # Until pyodide-version is stable on cibuildwheel +pyodide-version = "0.28.0a3" [tool.mypy] files = ["."] From 9c352c541019bd580408caed2ef2eafd98afa853 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 25 Jun 2025 15:58:29 -0700 Subject: [PATCH 0979/1101] Remove redundant SDL version check from setup.py This duplicates build_sdl.py and maybe isn't as useful as it used to be. I could import from that module if I really need the check in setup.py. Ensured updated code was moved to build_sdl.py --- build_sdl.py | 19 +++++++++++++------ setup.py | 33 --------------------------------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index cc1fb787..28069f2d 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -134,12 +134,19 @@ def check_sdl_version() -> None: sdl_version_str = subprocess.check_output( ["pkg-config", "sdl3", "--modversion"], universal_newlines=True ).strip() - except FileNotFoundError as exc: - msg = ( - "libsdl3-dev or equivalent must be installed on your system and must be at least version" - f" {needed_version}.\nsdl3-config must be on PATH." - ) - raise RuntimeError(msg) from exc + except FileNotFoundError: + try: + sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() + except FileNotFoundError as exc: + msg = ( + f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." + "\nsdl3-config must be on PATH." + ) + raise RuntimeError(msg) from exc + except subprocess.CalledProcessError as exc: + if sys.version_info >= (3, 11): + exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") + raise logger.info(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_MIN_VERSION: diff --git a/setup.py b/setup.py index eff99ff1..3786a1db 100755 --- a/setup.py +++ b/setup.py @@ -3,9 +3,7 @@ from __future__ import annotations -import os import platform -import subprocess import sys from pathlib import Path @@ -37,42 +35,11 @@ def get_package_data() -> list[str]: return files -def check_sdl_version() -> None: - """Check the local SDL version on Linux distributions.""" - if not sys.platform.startswith("linux"): - return - needed_version = "{}.{}.{}".format(*SDL_VERSION_NEEDED) - try: - sdl_version_str = subprocess.check_output( - ["pkg-config", "sdl3", "--modversion"], # noqa: S607 - universal_newlines=True, - ).strip() - except FileNotFoundError: - try: - sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S607 - except FileNotFoundError as exc: - msg = ( - f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." - "\nsdl3-config must be on PATH." - ) - raise RuntimeError(msg) from exc - except subprocess.CalledProcessError as exc: - if sys.version_info >= (3, 11): - exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") - raise - print(f"Found SDL {sdl_version_str}.") - sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) - if sdl_version < SDL_VERSION_NEEDED: - msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})" - raise RuntimeError(msg) - - if not (SETUP_DIR / "libtcod/src").exists(): print("Libtcod submodule is uninitialized.") print("Did you forget to run 'git submodule update --init'?") sys.exit(1) -check_sdl_version() setup( py_modules=["libtcodpy"], From 32553fc6b5dba4c937e7006572fe4971e639266d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 27 Jun 2025 16:53:04 -0700 Subject: [PATCH 0980/1101] Note that TextInput is no longer enabled by default Caused by SDL3 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3327d110..4bdb3102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html - Sound queueing methods were moved from `AudioDevice` to a new `AudioStream` class. - `BasicMixer` may require manually specifying `frequency` and `channels` to replicate old behavior. - `get_devices` and `get_capture_devices` now return `dict[str, AudioDevice]`. +- `TextInput` events are no longer enabled by default. ### Deprecated From b701e7de7b087fc53b6d2dd69e83feba02d5a2e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:55:49 +0000 Subject: [PATCH 0981/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.0 → v0.12.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.0...v0.12.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a294765c..8234ccf4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.0 + rev: v0.12.2 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] From acc775e68b5106be46ac12b2d88280cf4dff8291 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 8 Jul 2025 02:04:30 -0700 Subject: [PATCH 0982/1101] Update PyInstaller version in example --- examples/distribution/PyInstaller/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/distribution/PyInstaller/requirements.txt b/examples/distribution/PyInstaller/requirements.txt index 477fbbdc..24379896 100644 --- a/examples/distribution/PyInstaller/requirements.txt +++ b/examples/distribution/PyInstaller/requirements.txt @@ -1,3 +1,3 @@ tcod==16.2.3 -pyinstaller==6.9.0 +pyinstaller==6.14.2 pypiwin32; sys_platform=="win32" From 029ee45683b869b75881b6017e149fef46feaa87 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 8 Jul 2025 16:32:47 -0700 Subject: [PATCH 0983/1101] Add missing migration overload for Console.print --- CHANGELOG.md | 4 ++++ tcod/console.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdb3102..b8119181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- `Console.print` methods using `string` keyword were marked as invalid instead of deprecated. + ## [19.0.0] - 2025-06-13 Finished port to SDL3, this has caused several breaking changes from SDL such as lowercase key constants now being uppercase and mouse events returning `float` instead of `int`. diff --git a/tcod/console.py b/tcod/console.py index c4b24281..44ac3c61 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1038,6 +1038,24 @@ def print( string: str = "", ) -> int: ... + @overload + @deprecated( + "Replace text, fg, bg, bg_blend, and alignment with keyword arguments." + "\n'string' keyword should be renamed to `text`" + ) + def print( + self, + x: int, + y: int, + text: str = "", + fg: tuple[int, int, int] | None = None, + bg: tuple[int, int, int] | None = None, + bg_blend: int = tcod.constants.BKGND_SET, + alignment: int = tcod.constants.LEFT, + *, + string: str, + ) -> int: ... + def print( # noqa: PLR0913 self, x: int, From 622a8a92d6b9a26c7a878082765de69dd7966125 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 02:31:46 -0700 Subject: [PATCH 0984/1101] Fix SDL setup in CI Switch to custom composite action which fixes the issue Update build_sdl.py to handle SDL installed to the system path --- .github/workflows/python-package.yml | 10 +++++----- build_sdl.py | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1545558e..0b5462cb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -45,7 +45,7 @@ jobs: sdist: runs-on: ubuntu-latest steps: - - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + - uses: HexDecimal/my-setup-sdl-action@v1.0.0 with: install-linux-dependencies: true build-type: "Debug" @@ -125,7 +125,7 @@ jobs: run: | sudo apt-get update sudo apt-get install xvfb - - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + - uses: HexDecimal/my-setup-sdl-action@v1.0.0 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -168,7 +168,7 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ubuntu-latest steps: - - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + - uses: HexDecimal/my-setup-sdl-action@v1.0.0 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -210,7 +210,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip tox - - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + - uses: HexDecimal/my-setup-sdl-action@v1.0.0 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -330,7 +330,7 @@ jobs: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + - uses: HexDecimal/my-setup-sdl-action@v1.0.0 with: install-linux-dependencies: true build-type: "Debug" diff --git a/build_sdl.py b/build_sdl.py index 28069f2d..aca9fc25 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -212,6 +212,12 @@ def get_output(self) -> str: buffer.write(f"#define {name} ...\n") return buffer.getvalue() + def on_file_open(self, is_system_include: bool, includepath: str) -> Any: # noqa: ANN401, FBT001 + """Ignore includes other than SDL headers.""" + if not Path(includepath).parent.name == "SDL3": + raise FileNotFoundError + return super().on_file_open(is_system_include, includepath) + def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curdir: str, includepath: str) -> None: # noqa: ARG002, FBT001 """Remove bad includes such as stddef.h and stdarg.h.""" assert "SDL3/SDL" not in includepath, (includepath, curdir) @@ -283,7 +289,8 @@ def get_emscripten_include_dir() -> Path: r"-I(\S+)", subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True), ) - assert matches + if not matches: + matches = ["/usr/include"] for match in matches: if Path(match, "SDL3/SDL.h").is_file(): From 523262275dacd3b770f04b4c5ce57af2c1dfd411 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 05:19:01 -0700 Subject: [PATCH 0985/1101] Prepare 19.0.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8119181..4c7af1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.0.1] - 2025-07-11 + ### Fixed - `Console.print` methods using `string` keyword were marked as invalid instead of deprecated. From 7e0c595ec5feabf825ba71bea689eb84e7462fdf Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 05:40:42 -0700 Subject: [PATCH 0986/1101] Avoid trying to upload Pyodide wheels --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0b5462cb..0afe55d0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -342,7 +342,7 @@ jobs: - name: Archive wheel uses: actions/upload-artifact@v4 with: - name: wheels-pyodide + name: pyodide path: wheelhouse/*.whl retention-days: 30 compression-level: 0 From fbd9f78925bfaf2e4eeb75c7f8315e0be3c92ac2 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 05:42:05 -0700 Subject: [PATCH 0987/1101] Prepare 19.0.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c7af1d9..13870a5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.0.2] - 2025-07-11 + ## [19.0.1] - 2025-07-11 ### Fixed From 3131edbe1734f9e1c74ba8debe3946acc73c335e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 05:44:37 -0700 Subject: [PATCH 0988/1101] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13870a5d..1ce3bce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [19.0.2] - 2025-07-11 +Resolve wheel deployment issue. + ## [19.0.1] - 2025-07-11 ### Fixed From bebbb9a1f9078e13e13fbf3213f3ee4924929b77 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 20:01:16 -0700 Subject: [PATCH 0989/1101] Resolve Ruff warnings and add spelling --- .vscode/settings.json | 13 +++++++++++++ docs/conf.py | 4 ++-- scripts/generate_charmap_table.py | 2 +- scripts/tag_release.py | 4 ++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6dbcc428..f516971f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,6 +26,7 @@ "addy", "algo", "ALPH", + "alsa", "ALTERASE", "arange", "ARCHS", @@ -115,6 +116,7 @@ "DBLAMPERSAND", "DBLAPOSTROPHE", "DBLVERTICALBAR", + "dbus", "dcost", "DCROSS", "DECIMALSEPARATOR", @@ -190,6 +192,7 @@ "htmlhelp", "htmlzip", "IBEAM", + "ibus", "ifdef", "ifndef", "iinfo", @@ -255,9 +258,13 @@ "letterpaper", "LGUI", "LHYPER", + "libdrm", + "libgbm", "libsdl", "libtcod", "libtcodpy", + "libusb", + "libxkbcommon", "linspace", "liskin", "LMASK", @@ -347,6 +354,7 @@ "printn", "PRINTSCREEN", "propname", + "pulseaudio", "pushdown", "pycall", "pycparser", @@ -397,6 +405,7 @@ "scancodes", "scipy", "scoef", + "Scrn", "SCROLLLOCK", "sdist", "SDL's", @@ -498,7 +507,11 @@ "windowshown", "windowsizechanged", "windowtakefocus", + "Xcursor", "xdst", + "Xext", + "Xfixes", + "Xrandr", "xrel", "xvfb", "ydst", diff --git a/docs/conf.py b/docs/conf.py index 0fda406a..c107d387 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -"""Sphinx config file.""" +"""Sphinx config file.""" # noqa: INP001 # tdl documentation build configuration file, created by # sphinx-quickstart on Fri Nov 25 12:49:46 2016. # @@ -65,7 +65,7 @@ # General information about the project. project = "python-tcod" -copyright = "2009-2025, Kyle Benesch" +copyright = "2009-2025, Kyle Benesch" # noqa: A001 author = "Kyle Benesch" # The version info for the project you're documenting, acts as replacement for diff --git a/scripts/generate_charmap_table.py b/scripts/generate_charmap_table.py index 2a7814b7..bc7efb19 100755 --- a/scripts/generate_charmap_table.py +++ b/scripts/generate_charmap_table.py @@ -8,7 +8,7 @@ import argparse import unicodedata -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator # noqa: TC003 from tabulate import tabulate diff --git a/scripts/tag_release.py b/scripts/tag_release.py index 066eaeda..5f39e4b2 100755 --- a/scripts/tag_release.py +++ b/scripts/tag_release.py @@ -45,7 +45,7 @@ def parse_changelog(args: argparse.Namespace) -> tuple[str, str]: return f"{header}{tagged}{tail}", changes -def replace_unreleased_tags(tag: str, dry_run: bool) -> None: +def replace_unreleased_tags(tag: str, *, dry_run: bool) -> None: """Walk though sources and replace pending tags with the new tag.""" match = re.match(r"\d+\.\d+", tag) assert match @@ -77,7 +77,7 @@ def main() -> None: print("--- New changelog:") print(new_changelog) - replace_unreleased_tags(args.tag, args.dry_run) + replace_unreleased_tags(args.tag, dry_run=args.dry_run) if not args.dry_run: (PROJECT_DIR / "CHANGELOG.md").write_text(new_changelog, encoding="utf-8") From 84df0e7c1fe5b03857a588dabf2ad712a0cb0af7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 11 Jul 2025 21:12:17 -0700 Subject: [PATCH 0990/1101] Add SDL Window text input methods Text input events were missing since version 19.0.0 and had to be added again. --- .vscode/settings.json | 2 + CHANGELOG.md | 6 +++ examples/eventget.py | 2 + tcod/sdl/video.py | 118 ++++++++++++++++++++++++++++++++++++++++++ tests/test_sdl.py | 4 ++ 5 files changed, 132 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index f516971f..4b9f44a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,6 +45,7 @@ "AUDIOREWIND", "AUDIOSTOP", "autoclass", + "AUTOCORRECT", "autofunction", "autogenerated", "automodule", @@ -200,6 +201,7 @@ "imageio", "imread", "INCOL", + "INPUTTYPE", "INROW", "interactable", "intersphinx", diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce3bce3..8f77de53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- Added text input support to `tcod.sdl.video.Window` which was missing since the SDL3 update. + After creating a context use `assert context.sdl_window` or `if context.sdl_window:` to verify that an SDL window exists then use `context.sdl_window.start_text_input` to enable text input events. + Keep in mind that this can open an on-screen keyboard. + ## [19.0.2] - 2025-07-11 Resolve wheel deployment issue. diff --git a/examples/eventget.py b/examples/eventget.py index dad8649d..8610fae1 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -22,6 +22,8 @@ def main() -> None: joysticks: set[tcod.sdl.joystick.Joystick] = set() with tcod.context.new(width=WIDTH, height=HEIGHT) as context: + if context.sdl_window: + context.sdl_window.start_text_input() console = context.new_console() while True: # Display all event items. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 4cc0c371..75b9e560 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -24,7 +24,9 @@ from numpy.typing import ArrayLike, NDArray __all__ = ( + "Capitalization", "FlashOperation", + "TextInputType", "Window", "WindowFlags", "get_grabbed_window", @@ -89,6 +91,56 @@ class FlashOperation(enum.IntEnum): """Flash until focus is gained.""" +class TextInputType(enum.IntEnum): + """SDL input types for text input. + + .. seealso:: + :any:`Window.start_text_input` + https://wiki.libsdl.org/SDL3/SDL_TextInputType + + .. versionadded:: Unreleased + """ + + TEXT = lib.SDL_TEXTINPUT_TYPE_TEXT + """The input is text.""" + TEXT_NAME = lib.SDL_TEXTINPUT_TYPE_TEXT_NAME + """The input is a person's name.""" + TEXT_EMAIL = lib.SDL_TEXTINPUT_TYPE_TEXT_EMAIL + """The input is an e-mail address.""" + TEXT_USERNAME = lib.SDL_TEXTINPUT_TYPE_TEXT_USERNAME + """The input is a username.""" + TEXT_PASSWORD_HIDDEN = lib.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN + """The input is a secure password that is hidden.""" + TEXT_PASSWORD_VISIBLE = lib.SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE + """The input is a secure password that is visible.""" + NUMBER = lib.SDL_TEXTINPUT_TYPE_NUMBER + """The input is a number.""" + NUMBER_PASSWORD_HIDDEN = lib.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN + """The input is a secure PIN that is hidden.""" + NUMBER_PASSWORD_VISIBLE = lib.SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE + """The input is a secure PIN that is visible.""" + + +class Capitalization(enum.IntEnum): + """Text capitalization for text input. + + .. seealso:: + :any:`Window.start_text_input` + https://wiki.libsdl.org/SDL3/SDL_Capitalization + + .. versionadded:: Unreleased + """ + + NONE = lib.SDL_CAPITALIZE_NONE + """No auto-capitalization will be done.""" + SENTENCES = lib.SDL_CAPITALIZE_SENTENCES + """The first letter of sentences will be capitalized.""" + WORDS = lib.SDL_CAPITALIZE_WORDS + """The first letter of words will be capitalized.""" + LETTERS = lib.SDL_CAPITALIZE_LETTERS + """All letters will be capitalized.""" + + class _TempSurface: """Holds a temporary surface derived from a NumPy array.""" @@ -133,6 +185,9 @@ def __eq__(self, other: object) -> bool: return NotImplemented return bool(self.p == other.p) + def __hash__(self) -> int: + return hash(self.p) + def _as_property_pointer(self) -> Any: # noqa: ANN401 return self.p @@ -369,6 +424,69 @@ def relative_mouse_mode(self) -> bool: def relative_mouse_mode(self, enable: bool, /) -> None: _check(lib.SDL_SetWindowRelativeMouseMode(self.p, enable)) + def start_text_input( + self, + *, + type: TextInputType = TextInputType.TEXT, # noqa: A002 + capitalization: Capitalization | None = None, + autocorrect: bool = True, + multiline: bool | None = None, + android_type: int | None = None, + ) -> None: + """Start receiving text input events supporting Unicode. This may open an on-screen keyboard. + + This method is meant to be paired with :any:`set_text_input_area`. + + Args: + type: Type of text being inputted, see :any:`TextInputType` + capitalization: Capitalization hint, default is based on `type` given, see :any:`Capitalization`. + autocorrect: Enable auto completion and auto correction. + multiline: Allow multiple lines of text. + android_type: Input type for Android, see SDL docs. + + .. seealso:: + :any:`stop_text_input` + :any:`set_text_input_area` + https://wiki.libsdl.org/SDL3/SDL_StartTextInputWithProperties + + .. versionadded:: Unreleased + """ + props = Properties() + props[("SDL_PROP_TEXTINPUT_TYPE_NUMBER", int)] = int(type) + if capitalization is not None: + props[("SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER", int)] = int(capitalization) + props[("SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN", bool)] = autocorrect + if multiline is not None: + props[("SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN", bool)] = multiline + if android_type is not None: + props[("SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER", int)] = int(android_type) + _check(lib.SDL_StartTextInputWithProperties(self.p, props.p)) + + def set_text_input_area(self, rect: tuple[int, int, int, int], cursor: int) -> None: + """Assign the area used for entering Unicode text input. + + Args: + rect: `(x, y, width, height)` rectangle used for text input + cursor: Cursor X position, relative to `rect[0]` + + .. seealso:: + :any:`start_text_input` + https://wiki.libsdl.org/SDL3/SDL_SetTextInputArea + + .. versionadded:: Unreleased + """ + _check(lib.SDL_SetTextInputArea(self.p, (rect,), cursor)) + + def stop_text_input(self) -> None: + """Stop receiving text events for this window and close relevant on-screen keyboards. + + .. seealso:: + :any:`start_text_input` + + .. versionadded:: Unreleased + """ + _check(lib.SDL_StopTextInput(self.p)) + def new_window( # noqa: PLR0913 width: int, diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 9d23d0c8..33ccf8dc 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -38,6 +38,10 @@ def test_sdl_window(uses_window: None) -> None: window.opacity = window.opacity window.grab = window.grab + window.start_text_input(capitalization=tcod.sdl.video.Capitalization.NONE, multiline=False) + window.set_text_input_area((0, 0, 8, 8), 0) + window.stop_text_input() + def test_sdl_window_bad_types() -> None: with pytest.raises(TypeError): From 651f58f17e54f2701b4241b097edf6a6ebe4c68b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 12 Jul 2025 02:37:21 -0700 Subject: [PATCH 0991/1101] Prepare 19.1.0 release. --- CHANGELOG.md | 2 ++ tcod/sdl/video.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f77de53..c9ca828b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.1.0] - 2025-07-12 + ### Added - Added text input support to `tcod.sdl.video.Window` which was missing since the SDL3 update. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 75b9e560..4fb14e7d 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -98,7 +98,7 @@ class TextInputType(enum.IntEnum): :any:`Window.start_text_input` https://wiki.libsdl.org/SDL3/SDL_TextInputType - .. versionadded:: Unreleased + .. versionadded:: 19.1 """ TEXT = lib.SDL_TEXTINPUT_TYPE_TEXT @@ -128,7 +128,7 @@ class Capitalization(enum.IntEnum): :any:`Window.start_text_input` https://wiki.libsdl.org/SDL3/SDL_Capitalization - .. versionadded:: Unreleased + .. versionadded:: 19.1 """ NONE = lib.SDL_CAPITALIZE_NONE @@ -449,7 +449,7 @@ def start_text_input( :any:`set_text_input_area` https://wiki.libsdl.org/SDL3/SDL_StartTextInputWithProperties - .. versionadded:: Unreleased + .. versionadded:: 19.1 """ props = Properties() props[("SDL_PROP_TEXTINPUT_TYPE_NUMBER", int)] = int(type) @@ -473,7 +473,7 @@ def set_text_input_area(self, rect: tuple[int, int, int, int], cursor: int) -> N :any:`start_text_input` https://wiki.libsdl.org/SDL3/SDL_SetTextInputArea - .. versionadded:: Unreleased + .. versionadded:: 19.1 """ _check(lib.SDL_SetTextInputArea(self.p, (rect,), cursor)) @@ -483,7 +483,7 @@ def stop_text_input(self) -> None: .. seealso:: :any:`start_text_input` - .. versionadded:: Unreleased + .. versionadded:: 19.1 """ _check(lib.SDL_StopTextInput(self.p)) From d641bc1d64387a4bc1bcc80bc89438b5599b9e0d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 12 Jul 2025 05:22:50 -0700 Subject: [PATCH 0992/1101] Improve text input docs Cross reference with TextInput class Add examples --- tcod/event.py | 5 +++++ tcod/sdl/video.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index b780ffdf..d7fc0cc7 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -674,6 +674,11 @@ def __str__(self) -> str: class TextInput(Event): """SDL text input event. + .. warning:: + These events are not enabled by default since `19.0`. + + Use :any:`Window.start_text_input` to enable this event. + Attributes: type (str): Always "TEXTINPUT". text (str): A Unicode string with the input. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 4fb14e7d..75844823 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -167,7 +167,12 @@ def __init__(self, pixels: ArrayLike) -> None: class Window: - """An SDL2 Window object.""" + """An SDL2 Window object. + + Created from :any:`tcod.sdl.video.new_window` when working with SDL directly. + + When using the libtcod :any:`Context` you can access its `Window` via :any:`Context.sdl_window`. + """ def __init__(self, sdl_window_p: Any) -> None: # noqa: ANN401 if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): @@ -444,9 +449,21 @@ def start_text_input( multiline: Allow multiple lines of text. android_type: Input type for Android, see SDL docs. + Example:: + + context: tcod.context.Context # Assuming tcod context is used + + if context.sdl_window: + context.sdl_window.start_text_input() + + ... # Handle Unicode input using TextInput events + + context.sdl_window.stop_text_input() # Close on-screen keyboard when done + .. seealso:: :any:`stop_text_input` :any:`set_text_input_area` + :any:`TextInput` https://wiki.libsdl.org/SDL3/SDL_StartTextInputWithProperties .. versionadded:: 19.1 From ae49f329c9c1fb5c5350f1476e0eee596351268d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 13 Jul 2025 03:47:44 -0700 Subject: [PATCH 0993/1101] Add offset parameter to tcod.noise.grid `origin` parameter is unintuitive for sampling noise by chunks or by integer offsets. --- CHANGELOG.md | 4 ++++ tcod/noise.py | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ca828b..1bc36861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `tcod.noise.grid` now has the `offset` parameter for easier sampling of noise chunks. + ## [19.1.0] - 2025-07-12 ### Added diff --git a/tcod/noise.py b/tcod/noise.py index b4631455..3fba22a0 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -12,7 +12,7 @@ ... algorithm=tcod.noise.Algorithm.SIMPLEX, ... seed=42, ... ) - >>> samples = noise[tcod.noise.grid(shape=(5, 4), scale=0.25, origin=(0, 0))] + >>> samples = noise[tcod.noise.grid(shape=(5, 4), scale=0.25, offset=(0, 0))] >>> samples # Samples are a grid of floats between -1.0 and 1.0 array([[ 0. , -0.55046356, -0.76072866, -0.7088647 , -0.68165785], [-0.27523372, -0.7205134 , -0.74057037, -0.43919194, -0.29195625], @@ -412,8 +412,10 @@ def _setstate_old(self, state: tuple[Any, ...]) -> None: def grid( shape: tuple[int, ...], scale: tuple[float, ...] | float, - origin: tuple[int, ...] | None = None, + origin: tuple[float, ...] | None = None, indexing: Literal["ij", "xy"] = "xy", + *, + offset: tuple[float, ...] | None = None, ) -> tuple[NDArray[np.number], ...]: """Generate a mesh-grid of sample points to use with noise sampling. @@ -427,6 +429,11 @@ def grid( If `None` then the `origin` will be zero on each axis. `origin` is not scaled by the `scale` parameter. indexing: Passed to :any:`numpy.meshgrid`. + offset: The offset into the shape to generate. + Similar to `origin` but is scaled by the `scale` parameter. + Can be multiples of `shape` to index noise samples by chunk. + + .. versionadded:: Unreleased Returns: A sparse mesh-grid to be passed into a :class:`Noise` instance. @@ -435,14 +442,14 @@ def grid( >>> noise = tcod.noise.Noise(dimensions=2, seed=42) - # Common case for ij-indexed arrays. + # Common case for ij-indexed arrays >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij")] array([[ 0. , -0.27523372, -0.40398532, -0.50773406, -0.64945626], [-0.55046356, -0.7205134 , -0.57662135, -0.2643614 , -0.12529983], [-0.76072866, -0.74057037, -0.33160293, 0.24446318, 0.5346834 ]], dtype=float32) - # Transpose an xy-indexed array to get a standard order="F" result. + # Transpose an xy-indexed array to get a standard order="F" result >>> noise[tcod.noise.grid(shape=(4, 5), scale=(0.5, 0.25), origin=(1.0, 1.0))].T array([[ 0.52655405, 0.25038874, -0.03488023, -0.18455243, -0.16333057], [-0.5037453 , -0.75348294, -0.73630923, -0.35063767, 0.18149695], @@ -450,6 +457,23 @@ def grid( [-0.7057655 , -0.5817767 , -0.22774395, 0.02399864, -0.07006818]], dtype=float32) + # Can sample noise by chunk using the offset keyword + >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(0, 0))] + array([[ 0. , -0.27523372, -0.40398532, -0.50773406, -0.64945626], + [-0.55046356, -0.7205134 , -0.57662135, -0.2643614 , -0.12529983], + [-0.76072866, -0.74057037, -0.33160293, 0.24446318, 0.5346834 ]], + dtype=float32) + >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(3, 0))] + array([[-0.7088647 , -0.43919194, 0.12860827, 0.6390255 , 0.80402255], + [-0.68165785, -0.29195625, 0.2864191 , 0.5922846 , 0.52655405], + [-0.7841389 , -0.46131462, 0.0159424 , 0.17141782, -0.04198273]], + dtype=float32) + >>> noise[tcod.noise.grid(shape=(3, 5), scale=0.25, indexing="ij", offset=(6, 0))] + array([[-0.779634 , -0.60696834, -0.27446985, -0.23233278, -0.5037453 ], + [-0.5474089 , -0.54476213, -0.42235228, -0.49519652, -0.7101793 ], + [-0.28291094, -0.4326369 , -0.5227732 , -0.69655263, -0.81221616]], + dtype=float32) + .. versionadded:: 12.2 """ if isinstance(scale, (int, float)): @@ -462,6 +486,13 @@ def grid( if len(shape) != len(origin): msg = "shape must have the same length as origin" raise TypeError(msg) + if offset is not None: + if len(shape) != len(offset): + msg = "shape must have the same length as offset" + raise TypeError(msg) + origin = tuple( + i_origin + i_scale * i_offset for i_scale, i_offset, i_origin in zip(scale, offset, origin, strict=True) + ) indexes = ( np.arange(i_shape) * i_scale + i_origin for i_shape, i_scale, i_origin in zip(shape, scale, origin, strict=True) ) From 2d74caf3cb98d4e87d6df25d03e5fdad37122e9b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 19 Jul 2025 07:15:31 -0700 Subject: [PATCH 0994/1101] Fix documentation typo Event attributes are not positional --- tcod/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index d7fc0cc7..c10f90b8 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -66,7 +66,7 @@ match event: case tcod.event.Quit(): raise SystemExit() - case tcod.event.KeyDown(sym) if sym in KEY_COMMANDS: + case tcod.event.KeyDown(sym=sym) if sym in KEY_COMMANDS: print(f"Command: {KEY_COMMANDS[sym]}") case tcod.event.KeyDown(sym=sym, scancode=scancode, mod=mod, repeat=repeat): print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") From c19aacd49bca8e0062bb41495840150fe0a55f5d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 20 Jul 2025 07:42:14 -0700 Subject: [PATCH 0995/1101] Prepare 19.2.0 release. --- CHANGELOG.md | 2 ++ tcod/noise.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc36861..4f873882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.2.0] - 2025-07-20 + ### Added - `tcod.noise.grid` now has the `offset` parameter for easier sampling of noise chunks. diff --git a/tcod/noise.py b/tcod/noise.py index 3fba22a0..533df338 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -433,7 +433,7 @@ def grid( Similar to `origin` but is scaled by the `scale` parameter. Can be multiples of `shape` to index noise samples by chunk. - .. versionadded:: Unreleased + .. versionadded:: 19.2 Returns: A sparse mesh-grid to be passed into a :class:`Noise` instance. From eeb06fbf67dc3c02cf9719c75dce09cce68164a1 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 26 Jul 2025 03:23:13 -0700 Subject: [PATCH 0996/1101] Resolve Ruff warnings --- tcod/sdl/render.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 511b48fc..ccf31caf 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -6,7 +6,6 @@ from __future__ import annotations import enum -from collections.abc import Sequence from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np @@ -18,6 +17,8 @@ from tcod.sdl._internal import Properties, _check, _check_p if TYPE_CHECKING: + from collections.abc import Sequence + from numpy.typing import NDArray @@ -173,7 +174,7 @@ class Texture: Create a new texture using :any:`Renderer.new_texture` or :any:`Renderer.upload_texture`. """ - def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: + def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None: # noqa: ANN401 """Encapsulate an SDL_Texture pointer. This function is private.""" self.p = sdl_texture_p self._sdl_renderer_p = sdl_renderer_p # Keep alive. @@ -200,6 +201,10 @@ def __eq__(self, other: object) -> bool: return bool(self.p == other.p) return NotImplemented + def __hash__(self) -> int: + """Return hash for the owned pointer.""" + return hash(self.p) + def update(self, pixels: NDArray[Any], rect: tuple[int, int, int, int] | None = None) -> None: """Update the pixel data of this texture. @@ -267,7 +272,7 @@ def __exit__(self, *_: object) -> None: class Renderer: """SDL Renderer.""" - def __init__(self, sdl_renderer_p: Any) -> None: + def __init__(self, sdl_renderer_p: Any) -> None: # noqa: ANN401 """Encapsulate an SDL_Renderer pointer. This function is private.""" if ffi.typeof(sdl_renderer_p) is not ffi.typeof("struct SDL_Renderer*"): msg = f"Expected a {ffi.typeof('struct SDL_Window*')} type (was {ffi.typeof(sdl_renderer_p)})." @@ -283,6 +288,10 @@ def __eq__(self, other: object) -> bool: return bool(self.p == other.p) return NotImplemented + def __hash__(self) -> int: + """Return hash for the owned pointer.""" + return hash(self.p) + def copy( # noqa: PLR0913 self, texture: Texture, @@ -328,7 +337,7 @@ def set_render_target(self, texture: Texture) -> _RestoreTargetContext: _check(lib.SDL_SetRenderTarget(self.p, texture.p)) return restore - def new_texture(self, width: int, height: int, *, format: int | None = None, access: int | None = None) -> Texture: + def new_texture(self, width: int, height: int, *, format: int | None = None, access: int | None = None) -> Texture: # noqa: A002 """Allocate and return a new Texture for this renderer. Args: @@ -339,13 +348,13 @@ def new_texture(self, width: int, height: int, *, format: int | None = None, acc See :any:`TextureAccess` for more options. """ if format is None: - format = 0 + format = 0 # noqa: A001 if access is None: access = int(lib.SDL_TEXTUREACCESS_STATIC) texture_p = ffi.gc(lib.SDL_CreateTexture(self.p, format, access, width, height), lib.SDL_DestroyTexture) return Texture(texture_p, self.p) - def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, access: int | None = None) -> Texture: + def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, access: int | None = None) -> Texture: # noqa: A002 """Return a new Texture from an array of pixels. Args: @@ -358,9 +367,9 @@ def upload_texture(self, pixels: NDArray[Any], *, format: int | None = None, acc assert len(pixels.shape) == 3 # noqa: PLR2004 assert pixels.dtype == np.uint8 if pixels.shape[2] == 4: # noqa: PLR2004 - format = int(lib.SDL_PIXELFORMAT_RGBA32) + format = int(lib.SDL_PIXELFORMAT_RGBA32) # noqa: A001 elif pixels.shape[2] == 3: # noqa: PLR2004 - format = int(lib.SDL_PIXELFORMAT_RGB24) + format = int(lib.SDL_PIXELFORMAT_RGB24) # noqa: A001 else: msg = f"Can't determine the format required for an array of shape {pixels.shape}." raise TypeError(msg) @@ -502,7 +511,7 @@ def viewport(self) -> tuple[int, int, int, int] | None: def viewport(self, rect: tuple[int, int, int, int] | None) -> None: _check(lib.SDL_SetRenderViewport(self.p, (rect,))) - def set_vsync(self, enable: bool) -> None: + def set_vsync(self, enable: bool) -> None: # noqa: FBT001 """Enable or disable VSync for this renderer. .. versionadded:: 13.5 @@ -625,7 +634,7 @@ def fill_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, fl .. versionadded:: 13.5 """ rects = self._convert_array(rects, item_length=4) - _check(lib.SDL_RenderFillRects(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + _check(lib.SDL_RenderFillRects(self.p, ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, float, float]]) -> None: """Draw multiple outlined rectangles from an array. @@ -638,7 +647,7 @@ def draw_rects(self, rects: NDArray[np.number] | Sequence[tuple[float, float, fl rects = self._convert_array(rects, item_length=4) assert len(rects.shape) == 2 # noqa: PLR2004 assert rects.shape[1] == 4 # noqa: PLR2004 - _check(lib.SDL_RenderRects(self.p, tcod.ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) + _check(lib.SDL_RenderRects(self.p, ffi.from_buffer("SDL_FRect*", rects), rects.shape[0])) def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw an array of points. @@ -649,7 +658,7 @@ def draw_points(self, points: NDArray[np.number] | Sequence[tuple[float, float]] .. versionadded:: 13.5 """ points = self._convert_array(points, item_length=2) - _check(lib.SDL_RenderPoints(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) + _check(lib.SDL_RenderPoints(self.p, ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) -> None: """Draw a connected series of lines from an array. @@ -660,7 +669,7 @@ def draw_lines(self, points: NDArray[np.number] | Sequence[tuple[float, float]]) .. versionadded:: 13.5 """ points = self._convert_array(points, item_length=2) - _check(lib.SDL_RenderLines(self.p, tcod.ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) + _check(lib.SDL_RenderLines(self.p, ffi.from_buffer("SDL_FPoint*", points), points.shape[0])) def geometry( self, From 3b944f91c3b516b5b501f99c59e42053cd510d22 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 26 Jul 2025 03:23:00 -0700 Subject: [PATCH 0997/1101] Port SDL3 texture scale mode --- .vscode/settings.json | 2 ++ CHANGELOG.md | 4 ++++ tcod/sdl/render.py | 27 +++++++++++++++++++++++++++ tests/test_sdl.py | 1 + 4 files changed, 34 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b9f44a9..05971cbf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -348,6 +348,7 @@ "PERLIN", "PILCROW", "pilmode", + "PIXELART", "PIXELFORMAT", "PLUSMINUS", "pointsize", @@ -401,6 +402,7 @@ "rtype", "RWIN", "RWOPS", + "SCALEMODE", "scalex", "scaley", "Scancode", diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f873882..f00dfd41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `tcod.sdl.render`: Added `ScaleMode` enum and `Texture.scale_mode` attribute. + ## [19.2.0] - 2025-07-20 ### Added diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index ccf31caf..c8e29715 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -141,6 +141,19 @@ class BlendMode(enum.IntEnum): """""" +class ScaleMode(enum.IntEnum): + """Texture scaling modes. + + .. versionadded:: unreleased + """ + + NEAREST = lib.SDL_SCALEMODE_NEAREST + """Nearing neighbor.""" + LINEAR = lib.SDL_SCALEMODE_LINEAR + """Linier filtering.""" + # PIXELART = lib.SDL_SCALEMODE_PIXELART # Needs SDL 3.4 # noqa: ERA001 + + def compose_blend_mode( # noqa: PLR0913 source_color_factor: BlendFactor, dest_color_factor: BlendFactor, @@ -254,6 +267,20 @@ def color_mod(self) -> tuple[int, int, int]: def color_mod(self, rgb: tuple[int, int, int]) -> None: _check(lib.SDL_SetTextureColorMod(self.p, rgb[0], rgb[1], rgb[2])) + @property + def scale_mode(self) -> ScaleMode: + """Get or set this textures :any:`ScaleMode`. + + ..versionadded:: unreleased + """ + mode = ffi.new("SDL_ScaleMode*") + _check(lib.SDL_GetTextureScaleMode(self.p, mode)) + return ScaleMode(mode[0]) + + @scale_mode.setter + def scale_mode(self, value: ScaleMode, /) -> None: + _check(lib.SDL_SetTextureScaleMode(self.p, value)) + class _RestoreTargetContext: """A context manager which tracks the current render target and restores it on exiting.""" diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 33ccf8dc..93373a49 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -70,6 +70,7 @@ def test_sdl_render(uses_window: None) -> None: rgb.alpha_mod = rgb.alpha_mod rgb.blend_mode = rgb.blend_mode rgb.color_mod = rgb.color_mod + rgb.scale_mode = rgb.scale_mode rgba = render.upload_texture(np.zeros((8, 8, 4), np.uint8), access=tcod.sdl.render.TextureAccess.TARGET) with render.set_render_target(rgba): render.copy(rgb) From a58c4dc1810f41ce9489d222897078c3b0c08f26 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 26 Jul 2025 04:00:11 -0700 Subject: [PATCH 0998/1101] Prepare 19.3.0 release. --- CHANGELOG.md | 2 ++ tcod/sdl/render.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f00dfd41..30e49b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.3.0] - 2025-07-26 + ### Added - `tcod.sdl.render`: Added `ScaleMode` enum and `Texture.scale_mode` attribute. diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index c8e29715..f16714a2 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -144,7 +144,7 @@ class BlendMode(enum.IntEnum): class ScaleMode(enum.IntEnum): """Texture scaling modes. - .. versionadded:: unreleased + .. versionadded:: 19.3 """ NEAREST = lib.SDL_SCALEMODE_NEAREST @@ -271,7 +271,7 @@ def color_mod(self, rgb: tuple[int, int, int]) -> None: def scale_mode(self) -> ScaleMode: """Get or set this textures :any:`ScaleMode`. - ..versionadded:: unreleased + ..versionadded:: 19.3 """ mode = ffi.new("SDL_ScaleMode*") _check(lib.SDL_GetTextureScaleMode(self.p, mode)) From 616d84ccf0c4d1252b5739d4a8e1ccdee6f2a89c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 26 Jul 2025 04:21:50 -0700 Subject: [PATCH 0999/1101] Personalize changelog releases a little I should add small explanations for why a release was made. --- .vscode/settings.json | 1 + CHANGELOG.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 05971cbf..8bb3a0cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -466,6 +466,7 @@ "TRIGGERRIGHT", "tris", "truetype", + "tryddle", "typestr", "undoc", "Unifont", diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e49b99..c7c7e89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,17 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [19.3.0] - 2025-07-26 +Thanks to cr0ne for pointing out missing texture scaling options. +These options did not exist in SDL2. + ### Added - `tcod.sdl.render`: Added `ScaleMode` enum and `Texture.scale_mode` attribute. ## [19.2.0] - 2025-07-20 +Thanks to tryddle for demonstrating how bad the current API was with chunked world generation. + ### Added - `tcod.noise.grid` now has the `offset` parameter for easier sampling of noise chunks. From 01a810cb28460a9b7db72317c53d47ccc7a026c0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 31 Jul 2025 05:47:05 -0700 Subject: [PATCH 1000/1101] Remove TDL_ workarounds Also removes unnecessary SDL main stub Public API is unchanged, this is internal cleanup --- tcod/cffi.h | 1 - tcod/libtcodpy.py | 7 +- tcod/tdl.c | 201 ---------------------------------------------- tcod/tdl.h | 54 ------------- 4 files changed, 5 insertions(+), 258 deletions(-) delete mode 100644 tcod/tdl.c delete mode 100644 tcod/tdl.h diff --git a/tcod/cffi.h b/tcod/cffi.h index fc373dac..5f8fa494 100644 --- a/tcod/cffi.h +++ b/tcod/cffi.h @@ -9,4 +9,3 @@ #include "path.h" #include "random.h" #include "tcod.h" -#include "tdl.h" diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 2bff5397..fdfaeefe 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3584,8 +3584,11 @@ def _unpack_union(type_: int, union: Any) -> Any: # noqa: PLR0911 raise RuntimeError(msg) -def _convert_TCODList(c_list: Any, type_: int) -> Any: - return [_unpack_union(type_, lib.TDL_list_get_union(c_list, i)) for i in range(lib.TCOD_list_size(c_list))] +def _convert_TCODList(c_list: Any, type_: int) -> Any: # noqa: N802 + with ffi.new("TCOD_value_t[]", lib.TCOD_list_size(c_list)) as unions: + for i, union in enumerate(unions): + union.custom = lib.TCOD_list_get(c_list, i) + return [_unpack_union(type_, union) for union in unions] @deprecate("Parser functions have been deprecated.") diff --git a/tcod/tdl.c b/tcod/tdl.c deleted file mode 100644 index c293428f..00000000 --- a/tcod/tdl.c +++ /dev/null @@ -1,201 +0,0 @@ -/* extra functions provided for the python-tdl library */ -#include "tdl.h" - -#include "../libtcod/src/libtcod/wrappers.h" - -void SDL_main(void){}; - -TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx) { - TCOD_value_t item; - item.custom = TCOD_list_get(l, idx); - return item; -} - -bool TDL_list_get_bool(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).b; } - -char TDL_list_get_char(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).c; } - -int TDL_list_get_int(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).i; } - -float TDL_list_get_float(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).f; } - -char* TDL_list_get_string(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).s; } - -TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).col; } - -TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx) { return TDL_list_get_union(l, idx).dice; } - -/* get a TCOD color type from a 0xRRGGBB formatted integer */ -TCOD_color_t TDL_color_from_int(int color) { - TCOD_color_t tcod_color = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; - return tcod_color; -} - -int TDL_color_to_int(TCOD_color_t* color) { return (color->r << 16) | (color->g << 8) | color->b; } - -int* TDL_color_int_to_array(int color) { - static int array[3]; - array[0] = (color >> 16) & 0xff; - array[1] = (color >> 8) & 0xff; - array[2] = color & 0xff; - return array; -} - -int TDL_color_RGB(int r, int g, int b) { return ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } - -int TDL_color_HSV(float h, float s, float v) { - TCOD_color_t tcod_color = TCOD_color_HSV(h, s, v); - return TDL_color_to_int(&tcod_color); -} - -bool TDL_color_equals(int c1, int c2) { return (c1 == c2); } - -int TDL_color_add(int c1, int c2) { - TCOD_color_t tc1 = TDL_color_from_int(c1); - TCOD_color_t tc2 = TDL_color_from_int(c2); - tc1 = TCOD_color_add(tc1, tc2); - return TDL_color_to_int(&tc1); -} - -int TDL_color_subtract(int c1, int c2) { - TCOD_color_t tc1 = TDL_color_from_int(c1); - TCOD_color_t tc2 = TDL_color_from_int(c2); - tc1 = TCOD_color_subtract(tc1, tc2); - return TDL_color_to_int(&tc1); -} - -int TDL_color_multiply(int c1, int c2) { - TCOD_color_t tc1 = TDL_color_from_int(c1); - TCOD_color_t tc2 = TDL_color_from_int(c2); - tc1 = TCOD_color_multiply(tc1, tc2); - return TDL_color_to_int(&tc1); -} - -int TDL_color_multiply_scalar(int c, float value) { - TCOD_color_t tc = TDL_color_from_int(c); - tc = TCOD_color_multiply_scalar(tc, value); - return TDL_color_to_int(&tc); -} - -int TDL_color_lerp(int c1, int c2, float coef) { - TCOD_color_t tc1 = TDL_color_from_int(c1); - TCOD_color_t tc2 = TDL_color_from_int(c2); - tc1 = TCOD_color_lerp(tc1, tc2, coef); - return TDL_color_to_int(&tc1); -} - -float TDL_color_get_hue(int color) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - return TCOD_color_get_hue(tcod_color); -} -float TDL_color_get_saturation(int color) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - return TCOD_color_get_saturation(tcod_color); -} -float TDL_color_get_value(int color) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - return TCOD_color_get_value(tcod_color); -} -int TDL_color_set_hue(int color, float h) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_set_hue(&tcod_color, h); - return TDL_color_to_int(&tcod_color); -} -int TDL_color_set_saturation(int color, float h) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_set_saturation(&tcod_color, h); - return TDL_color_to_int(&tcod_color); -} -int TDL_color_set_value(int color, float h) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_set_value(&tcod_color, h); - return TDL_color_to_int(&tcod_color); -} -int TDL_color_shift_hue(int color, float hue_shift) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_shift_hue(&tcod_color, hue_shift); - return TDL_color_to_int(&tcod_color); -} -int TDL_color_scale_HSV(int color, float scoef, float vcoef) { - TCOD_color_t tcod_color = TDL_color_from_int(color); - TCOD_color_scale_HSV(&tcod_color, scoef, vcoef); - return TDL_color_to_int(&tcod_color); -} - -#define TRANSPARENT_BIT 1 -#define WALKABLE_BIT 2 -#define FOV_BIT 4 - -/* set map transparent and walkable flags from a buffer */ -void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer) { - int width = TCOD_map_get_width(map); - int height = TCOD_map_get_height(map); - int x; - int y; - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - int i = y * width + x; - TCOD_map_set_properties(map, x, y, (buffer[i] & TRANSPARENT_BIT) != 0, (buffer[i] & WALKABLE_BIT) != 0); - } - } -} - -/* get fov from tcod map */ -void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative) { - int width = TCOD_map_get_width(map); - int height = TCOD_map_get_height(map); - int x; - int y; - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - int i = y * width + x; - if (!cumulative) { buffer[i] &= ~FOV_BIT; } - if (TCOD_map_is_in_fov(map, x, y)) { buffer[i] |= FOV_BIT; } - } - } -} - -/* set functions are called conditionally for ch/fg/bg (-1 is ignored) - colors are converted to TCOD_color_t types in C and is much faster than in - Python. - Also Python indexing is used, negative x/y will index to (width-x, etc.) */ -int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t blend) { - int width = TCOD_console_get_width(console); - int height = TCOD_console_get_height(console); - TCOD_color_t color; - - if (x < -width || x >= width || y < -height || y >= height) { return -1; /* outside of console */ } - - /* normalize x, y */ - if (x < 0) { x += width; }; - if (y < 0) { y += height; }; - - if (ch != -1) { TCOD_console_set_char(console, x, y, ch); } - if (fg != -1) { - color = TDL_color_from_int(fg); - TCOD_console_set_char_foreground(console, x, y, color); - } - if (bg != -1) { - color = TDL_color_from_int(bg); - TCOD_console_set_char_background(console, x, y, color, blend); - } - return 0; -} - -int TDL_console_get_bg(TCOD_console_t console, int x, int y) { - TCOD_color_t tcod_color = TCOD_console_get_char_background(console, x, y); - return TDL_color_to_int(&tcod_color); -} - -int TDL_console_get_fg(TCOD_console_t console, int x, int y) { - TCOD_color_t tcod_color = TCOD_console_get_char_foreground(console, x, y); - return TDL_color_to_int(&tcod_color); -} - -void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag) { - TCOD_console_set_char_background(console, x, y, TDL_color_from_int(color), flag); -} - -void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color) { - TCOD_console_set_char_foreground(console, x, y, TDL_color_from_int(color)); -} diff --git a/tcod/tdl.h b/tcod/tdl.h deleted file mode 100644 index 71a7492e..00000000 --- a/tcod/tdl.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef PYTHON_TCOD_TDL_H_ -#define PYTHON_TCOD_TDL_H_ - -#include "../libtcod/src/libtcod/libtcod.h" - -/* TDL FUNCTIONS ---------------------------------------------------------- */ - -TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx); -bool TDL_list_get_bool(TCOD_list_t l, int idx); -char TDL_list_get_char(TCOD_list_t l, int idx); -int TDL_list_get_int(TCOD_list_t l, int idx); -float TDL_list_get_float(TCOD_list_t l, int idx); -char* TDL_list_get_string(TCOD_list_t l, int idx); -TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx); -TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx); -/*bool (*TDL_parser_new_property_func)(const char *propname, TCOD_value_type_t - * type, TCOD_value_t *value);*/ - -/* color functions modified to use integers instead of structs */ -TCOD_color_t TDL_color_from_int(int color); -int TDL_color_to_int(TCOD_color_t* color); -int* TDL_color_int_to_array(int color); -int TDL_color_RGB(int r, int g, int b); -int TDL_color_HSV(float h, float s, float v); -bool TDL_color_equals(int c1, int c2); -int TDL_color_add(int c1, int c2); -int TDL_color_subtract(int c1, int c2); -int TDL_color_multiply(int c1, int c2); -int TDL_color_multiply_scalar(int c, float value); -int TDL_color_lerp(int c1, int c2, float coef); -float TDL_color_get_hue(int color); -float TDL_color_get_saturation(int color); -float TDL_color_get_value(int color); -int TDL_color_set_hue(int color, float h); -int TDL_color_set_saturation(int color, float h); -int TDL_color_set_value(int color, float h); -int TDL_color_shift_hue(int color, float hue_shift); -int TDL_color_scale_HSV(int color, float scoef, float vcoef); - -/* map data functions using a bitmap of: - * 1 = is_transparant - * 2 = is_walkable - * 4 = in_fov - */ -void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t* buffer); -void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t* buffer, bool cumulative); - -int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t flag); -int TDL_console_get_bg(TCOD_console_t console, int x, int y); -int TDL_console_get_fg(TCOD_console_t console, int x, int y); -void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag); -void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color); - -#endif /* PYTHON_TCOD_TDL_H_ */ From beffd5b63b5f60c7eea8874296376dc21e76527d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 1 Aug 2025 20:01:04 -0700 Subject: [PATCH 1001/1101] Avoid deprecated code within Context.convert_event Promote mouse attribute deprecations to decorators --- CHANGELOG.md | 4 ++++ tcod/event.py | 55 ++++++++++++++++++++------------------------------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c7e89c..781fff5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- Silenced internal deprecation warning within `Context.convert_event`. + ## [19.3.0] - 2025-07-26 Thanks to cr0ne for pointing out missing texture scaling options. diff --git a/tcod/event.py b/tcod/event.py index c10f90b8..ca8195d7 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -418,12 +418,8 @@ def __init__( self.state = state @property + @deprecated("The mouse.pixel attribute is deprecated. Use mouse.position instead.") def pixel(self) -> Point: - warnings.warn( - "The mouse.pixel attribute is deprecated. Use mouse.position instead.", - DeprecationWarning, - stacklevel=2, - ) return self.position @pixel.setter @@ -431,26 +427,29 @@ def pixel(self, value: Point) -> None: self.position = value @property + @deprecated( + "The mouse.tile attribute is deprecated." + " Use mouse.position of the event returned by context.convert_event instead." + ) def tile(self) -> Point: - warnings.warn( - "The mouse.tile attribute is deprecated. Use mouse.position of the event returned by context.convert_event instead.", - DeprecationWarning, - stacklevel=2, - ) return _verify_tile_coordinates(self._tile) @tile.setter + @deprecated( + "The mouse.tile attribute is deprecated." + " Use mouse.position of the event returned by context.convert_event instead." + ) def tile(self, xy: tuple[float, float]) -> None: self._tile = Point(*xy) def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self.tile)!r}, state={MouseButtonMask(self.state)})" + return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self._tile or (0, 0))!r}, state={MouseButtonMask(self.state)})" def __str__(self) -> str: return ("<%s, position=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( super().__str__().strip("<>"), *self.position, - *self.tile, + *(self._tile or (0, 0)), MouseButtonMask(self.state), ) @@ -492,41 +491,29 @@ def __init__( self._tile_motion = Point(*tile_motion) if tile_motion is not None else None @property + @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") def pixel_motion(self) -> Point: - warnings.warn( - "The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.", - DeprecationWarning, - stacklevel=2, - ) return self.motion @pixel_motion.setter + @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") def pixel_motion(self, value: Point) -> None: - warnings.warn( - "The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.", - DeprecationWarning, - stacklevel=2, - ) self.motion = value @property + @deprecated( + "The mouse.tile_motion attribute is deprecated." + " Use mouse.motion of the event returned by context.convert_event instead." + ) def tile_motion(self) -> Point: - warnings.warn( - "The mouse.tile_motion attribute is deprecated." - " Use mouse.motion of the event returned by context.convert_event instead.", - DeprecationWarning, - stacklevel=2, - ) return _verify_tile_coordinates(self._tile_motion) @tile_motion.setter + @deprecated( + "The mouse.tile_motion attribute is deprecated." + " Use mouse.motion of the event returned by context.convert_event instead." + ) def tile_motion(self, xy: tuple[float, float]) -> None: - warnings.warn( - "The mouse.tile_motion attribute is deprecated." - " Use mouse.motion of the event returned by context.convert_event instead.", - DeprecationWarning, - stacklevel=2, - ) self._tile_motion = Point(*xy) @classmethod From 6ed474aad443a13cd47d63a230e3563b94e0d5ff Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 1 Aug 2025 20:18:12 -0700 Subject: [PATCH 1002/1101] Move Map deprecation warning to decorator --- tcod/map.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tcod/map.py b/tcod/map.py index 45b4561a..d3ec5015 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Literal import numpy as np +from typing_extensions import deprecated import tcod._internal import tcod.constants @@ -15,6 +16,7 @@ from numpy.typing import ArrayLike, NDArray +@deprecated("This class may perform poorly and is no longer needed.") class Map: """A map containing libtcod attributes. @@ -78,11 +80,6 @@ def __init__( order: Literal["C", "F"] = "C", ) -> None: """Initialize the map.""" - warnings.warn( - "This class may perform poorly and is no longer needed.", - DeprecationWarning, - stacklevel=2, - ) self.width = width self.height = height self._order = tcod._internal.verify_order(order) From e9559a932d422dde4e05ca937ee20287785e054a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 1 Aug 2025 20:29:38 -0700 Subject: [PATCH 1003/1101] Update cibuildwheel --- .github/workflows/python-package.yml | 6 +++--- pyproject.toml | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0afe55d0..6d43a222 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -236,7 +236,7 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Build wheels - uses: pypa/cibuildwheel@v3.0.0 + uses: pypa/cibuildwheel@v3.1.3 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} @@ -299,7 +299,7 @@ jobs: # Downloads SDL for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v3.0.0 + uses: pypa/cibuildwheel@v3.1.3 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 @@ -335,7 +335,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: "3.2.4" # Should be equal or less than the version used by Emscripten - - uses: pypa/cibuildwheel@v3.0.0 + - uses: pypa/cibuildwheel@v3.1.3 env: CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide diff --git a/pyproject.toml b/pyproject.toml index 46a9285b..ea098b4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,10 +102,6 @@ filterwarnings = [ [tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ enable = ["pypy", "pyodide-prerelease"] -[tool.cibuildwheel.pyodide] -dependency-versions = "latest" # Until pyodide-version is stable on cibuildwheel -pyodide-version = "0.28.0a3" - [tool.mypy] files = ["."] python_version = "3.10" From 1af4f65aef6c9c12e4f4628c474be7ccf9b6848d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 1 Aug 2025 20:46:08 -0700 Subject: [PATCH 1004/1101] Prepare 19.3.1 release. --- .vscode/settings.json | 1 + CHANGELOG.md | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8bb3a0cd..c45bb5e5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -218,6 +218,7 @@ "jhat", "jice", "jieba", + "jmccardle", "JOYAXISMOTION", "JOYBALLMOTION", "JOYBUTTONDOWN", diff --git a/CHANGELOG.md b/CHANGELOG.md index 781fff5e..f25ab027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.3.1] - 2025-08-02 + +Solved a deprecation warning which was internal to tcod and no doubt annoyed many devs. +Thanks to jmccardle for forcing me to resolve this. + ### Fixed -- Silenced internal deprecation warning within `Context.convert_event`. +- Silenced internal deprecation warnings within `Context.convert_event`. ## [19.3.0] - 2025-07-26 From cb573c245916112487c732ee6ae7537f38970b8a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 2 Aug 2025 10:57:18 -0700 Subject: [PATCH 1005/1101] Fix angle brackets in MouseButtonEvent and MouseWheel Fixes #165 --- CHANGELOG.md | 4 ++++ tcod/event.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f25ab027..360688f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- Corrected some inconsistent angle brackets in the `__str__` of Event subclasses. #165 + ## [19.3.1] - 2025-08-02 Solved a deprecation warning which was internal to tcod and no doubt annoyed many devs. diff --git a/tcod/event.py b/tcod/event.py index ca8195d7..946c7056 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -599,7 +599,7 @@ def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self.tile)!r}, button={MouseButton(self.button)!r})" def __str__(self) -> str: - return "" % ( self.type, *self.position, *self.tile, @@ -650,7 +650,7 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return "<%s, x=%i, y=%i, flipped=%r)" % ( + return "<%s, x=%i, y=%i, flipped=%r>" % ( super().__str__().strip("<>"), self.x, self.y, From 43038b2653204449da8b29d7eb4c6a7e1a384329 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 6 Aug 2025 15:19:21 -0700 Subject: [PATCH 1006/1101] Move self import to doctests --- tcod/event.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index 946c7056..08330dae 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -90,7 +90,6 @@ import numpy as np from typing_extensions import deprecated -import tcod.event import tcod.event_constants import tcod.sdl.joystick import tcod.sdl.sys @@ -194,6 +193,7 @@ class Modifier(enum.IntFlag): Example:: + >>> import tcod.event >>> mod = tcod.event.Modifier(4098) >>> mod & tcod.event.Modifier.SHIFT # Check if any shift key is held. @@ -2738,6 +2738,7 @@ def label(self) -> str: Example:: + >>> import tcod.event >>> tcod.event.KeySym.F1.label 'F1' >>> tcod.event.KeySym.BACKSPACE.label From 4822c2bbbaebea889d0b6f99a073d151a141be9b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 6 Aug 2025 15:17:13 -0700 Subject: [PATCH 1007/1101] Resolve window event regressions --- CHANGELOG.md | 6 +++++ docs/tcod/getting-started.rst | 2 +- examples/eventget.py | 2 +- examples/ttf.py | 2 +- tcod/event.py | 50 +++++++++++++++++++++-------------- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 360688f2..f76b4a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,15 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- Checking "WindowSizeChanged" was not valid since SDL 3 and was also not valid in previous examples. + You must no longer check the type of the `WindowResized` event. + ### Fixed - Corrected some inconsistent angle brackets in the `__str__` of Event subclasses. #165 +- Fix regression with window events causing them to be `Unknown` and uncheckable. ## [19.3.1] - 2025-08-02 diff --git a/docs/tcod/getting-started.rst b/docs/tcod/getting-started.rst index add5d9f0..aabb4f7f 100644 --- a/docs/tcod/getting-started.rst +++ b/docs/tcod/getting-started.rst @@ -114,7 +114,7 @@ clearing the console every frame and replacing it only on resizing the window. match event: case tcod.event.Quit(): raise SystemExit - case tcod.event.WindowResized(type="WindowSizeChanged"): + case tcod.event.WindowResized(width=width, height=height): # Size in pixels pass # The next call to context.new_console may return a different size. diff --git a/examples/eventget.py b/examples/eventget.py index 8610fae1..7f4f9cb3 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -42,7 +42,7 @@ def main() -> None: print(repr(event)) if isinstance(event, tcod.event.Quit): raise SystemExit - if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": + if isinstance(event, tcod.event.WindowResized) and event.type == "WindowResized": console = context.new_console() if isinstance(event, tcod.event.ControllerDevice): if event.type == "CONTROLLERDEVICEADDED": diff --git a/examples/ttf.py b/examples/ttf.py index fb1e35f6..4a6041a8 100755 --- a/examples/ttf.py +++ b/examples/ttf.py @@ -84,7 +84,7 @@ def main() -> None: for event in tcod.event.wait(): if isinstance(event, tcod.event.Quit): raise SystemExit - if isinstance(event, tcod.event.WindowResized) and event.type == "WindowSizeChanged": + if isinstance(event, tcod.event.WindowResized): # Resize the Tileset to match the new screen size. context.change_tileset( load_ttf( diff --git a/tcod/event.py b/tcod/event.py index 08330dae..61aef24e 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -83,6 +83,7 @@ from __future__ import annotations import enum +import functools import warnings from collections.abc import Callable, Iterator, Mapping from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar @@ -698,7 +699,6 @@ class WindowEvent(Event): "WindowExposed", "WindowMoved", "WindowResized", - "WindowSizeChanged", "WindowMinimized", "WindowMaximized", "WindowRestored", @@ -715,13 +715,13 @@ class WindowEvent(Event): @classmethod def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: - if sdl_event.window.event not in cls.__WINDOW_TYPES: + if sdl_event.type not in cls._WINDOW_TYPES: return Undefined.from_sdl_event(sdl_event) - event_type: Final = cls.__WINDOW_TYPES[sdl_event.window.event] + event_type: Final = cls._WINDOW_TYPES[sdl_event.type] self: WindowEvent - if sdl_event.window.event == lib.SDL_EVENT_WINDOW_MOVED: + if sdl_event.type == lib.SDL_EVENT_WINDOW_MOVED: self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) - elif sdl_event.window.event in ( + elif sdl_event.type in ( lib.SDL_EVENT_WINDOW_RESIZED, lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, ): @@ -734,7 +734,7 @@ def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}(type={self.type!r})" - __WINDOW_TYPES: Final = { + _WINDOW_TYPES: Final = { lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown", lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden", lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed", @@ -785,10 +785,13 @@ class WindowResized(WindowEvent): Attributes: width (int): The current width of the window. height (int): The current height of the window. + + .. versionchanged:: Unreleased + Removed "WindowSizeChanged" type. """ - type: Final[Literal["WindowResized", "WindowSizeChanged"]] # type: ignore[misc] - """WindowResized" or "WindowSizeChanged""" + type: Final[Literal["WindowResized"]] # type: ignore[misc] + """Always "WindowResized".""" def __init__(self, type: str, width: int, height: int) -> None: super().__init__(type) @@ -1130,6 +1133,15 @@ def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: return cls(type, sdl_event.cdevice.which) +@functools.cache +def _find_event_name(index: int, /) -> str: + """Return the SDL event name for this index.""" + for attr in dir(lib): + if attr.startswith("SDL_EVENT_") and getattr(lib, attr) == index: + return attr + return "???" + + class Undefined(Event): """This class is a place holder for SDL events without their own tcod.event class.""" @@ -1144,9 +1156,12 @@ def from_sdl_event(cls, sdl_event: Any) -> Undefined: def __str__(self) -> str: if self.sdl_event: - return "" % self.sdl_event.type + return f"" return "" + def __repr__(self) -> str: + return self.__str__() + _SDL_TO_CLASS_TABLE: dict[int, type[Event]] = { lib.SDL_EVENT_QUIT: Quit, @@ -1157,7 +1172,6 @@ def __str__(self) -> str: lib.SDL_EVENT_MOUSE_BUTTON_UP: MouseButtonUp, lib.SDL_EVENT_MOUSE_WHEEL: MouseWheel, lib.SDL_EVENT_TEXT_INPUT: TextInput, - # lib.SDL_EVENT_WINDOW_EVENT: WindowEvent, lib.SDL_EVENT_JOYSTICK_AXIS_MOTION: JoystickAxis, lib.SDL_EVENT_JOYSTICK_BALL_MOTION: JoystickBall, lib.SDL_EVENT_JOYSTICK_HAT_MOTION: JoystickHat, @@ -1176,9 +1190,11 @@ def __str__(self) -> str: def _parse_event(sdl_event: Any) -> Event: """Convert a C SDL_Event* type into a tcod Event sub-class.""" - if sdl_event.type not in _SDL_TO_CLASS_TABLE: - return Undefined.from_sdl_event(sdl_event) - return _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) + if sdl_event.type in _SDL_TO_CLASS_TABLE: + return _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) + if sdl_event.type in WindowEvent._WINDOW_TYPES: + return WindowEvent.from_sdl_event(sdl_event) + return Undefined.from_sdl_event(sdl_event) def get() -> Iterator[Any]: @@ -1198,10 +1214,7 @@ def get() -> Iterator[Any]: return sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): - if sdl_event.type in _SDL_TO_CLASS_TABLE: - yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) - else: - yield Undefined.from_sdl_event(sdl_event) + yield _parse_event(sdl_event) def wait(timeout: float | None = None) -> Iterator[Any]: @@ -1425,9 +1438,6 @@ def ev_windowmoved(self, event: tcod.event.WindowMoved, /) -> T | None: def ev_windowresized(self, event: tcod.event.WindowResized, /) -> T | None: """Called when the window is resized.""" - def ev_windowsizechanged(self, event: tcod.event.WindowResized, /) -> T | None: - """Called when the system or user changes the size of the window.""" - def ev_windowminimized(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window is minimized.""" From 33a24b65449eb61c7905d0181e19c0646a233494 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 6 Aug 2025 15:41:39 -0700 Subject: [PATCH 1008/1101] Prepare 19.4.0 release. --- CHANGELOG.md | 4 ++++ tcod/event.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f76b4a75..bd9e618d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.4.0] - 2025-08-06 + ### Changed - Checking "WindowSizeChanged" was not valid since SDL 3 and was also not valid in previous examples. @@ -101,6 +103,8 @@ Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html - `WindowFlags.FULLSCREEN_DESKTOP` is now just `WindowFlags.FULLSCREEN` - `tcod.sdl.render.Renderer.integer_scaling` removed. - Removed `callback`, `spec`, `queued_samples`, `queue_audio`, and `dequeue_audio` attributes from `tcod.sdl.audio.AudioDevice`. +- `tcod.event.WindowResized`: `type="WindowSizeChanged"` removed and must no longer be checked for. + `EventDispatch.ev_windowsizechanged` is no longer called. ### Fixed diff --git a/tcod/event.py b/tcod/event.py index 61aef24e..5a2bceb8 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -786,7 +786,7 @@ class WindowResized(WindowEvent): width (int): The current width of the window. height (int): The current height of the window. - .. versionchanged:: Unreleased + .. versionchanged:: 19.4 Removed "WindowSizeChanged" type. """ From 1864c16014dd374ed51aeddb6554d8925555817f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 19 Aug 2025 22:01:52 -0700 Subject: [PATCH 1009/1101] Update cibuildwheel --- .github/workflows/python-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6d43a222..a9847a08 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -236,7 +236,7 @@ jobs: - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Build wheels - uses: pypa/cibuildwheel@v3.1.3 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} @@ -299,7 +299,7 @@ jobs: # Downloads SDL for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v3.1.3 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 @@ -335,7 +335,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: "3.2.4" # Should be equal or less than the version used by Emscripten - - uses: pypa/cibuildwheel@v3.1.3 + - uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide From dd001c87abc473a5908d35db9959a41aa73c7892 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 27 Aug 2025 04:30:49 -0700 Subject: [PATCH 1010/1101] Fix dangling pointer with Pathfinder._travel_p Storing these CFFI pointers was a bad idea, it's better to drop them and then recreate them as they are needed. --- CHANGELOG.md | 4 ++++ tcod/path.py | 31 ++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9e618d..d7945eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- Fixed dangling pointer in `Pathfinder.clear` method. + ## [19.4.0] - 2025-08-06 ### Changed diff --git a/tcod/path.py b/tcod/path.py index f16c0210..a1efc249 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1002,8 +1002,8 @@ def _resolve(self, pathfinder: Pathfinder) -> None: _check( lib.path_compute( pathfinder._frontier_p, - pathfinder._distance_p, - pathfinder._travel_p, + _export(pathfinder._distance), + _export(pathfinder._travel), len(rules), rules, pathfinder._heuristic_p, @@ -1110,8 +1110,6 @@ def __init__(self, graph: CustomGraph | SimpleGraph) -> None: self._frontier_p = ffi.gc(lib.TCOD_frontier_new(self._graph._ndim), lib.TCOD_frontier_delete) self._distance = maxarray(self._graph._shape_c) self._travel = _world_array(self._graph._shape_c) - self._distance_p = _export(self._distance) - self._travel_p = _export(self._travel) self._heuristic: tuple[int, int, int, int, tuple[int, ...]] | None = None self._heuristic_p: Any = ffi.NULL @@ -1180,8 +1178,22 @@ def traversal(self) -> NDArray[Any]: def clear(self) -> None: """Reset the pathfinder to its initial state. - This sets all values on the :any:`distance` array to their maximum - value. + This sets all values on the :any:`distance` array to their maximum value. + + Example:: + + >>> import tcod.path + >>> graph = tcod.path.SimpleGraph( + ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, + ... ) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.add_root((0, 0)) + >>> pf.path_to((2, 2)).tolist() + [[0, 0], [1, 1], [2, 2]] + >>> pf.clear() # Reset Pathfinder to its initial state + >>> pf.add_root((0, 2)) + >>> pf.path_to((2, 2)).tolist() + [[0, 2], [1, 2], [2, 2]] """ self._distance[...] = np.iinfo(self._distance.dtype).max self._travel = _world_array(self._graph._shape_c) @@ -1237,7 +1249,7 @@ def rebuild_frontier(self) -> None: """ lib.TCOD_frontier_clear(self._frontier_p) self._update_heuristic(None) - _check(lib.rebuild_frontier_from_distance(self._frontier_p, self._distance_p)) + _check(lib.rebuild_frontier_from_distance(self._frontier_p, _export(self._distance))) def resolve(self, goal: tuple[int, ...] | None = None) -> None: """Manually run the pathfinder algorithm. @@ -1341,12 +1353,13 @@ def path_from(self, index: tuple[int, ...]) -> NDArray[np.intc]: self.resolve(index) if self._order == "F": # Convert to ij indexing order. index = index[::-1] - length = _check(lib.get_travel_path(self._graph._ndim, self._travel_p, index, ffi.NULL)) + _travel_p = _export(self._travel) + length = _check(lib.get_travel_path(self._graph._ndim, _travel_p, index, ffi.NULL)) path: np.ndarray[Any, np.dtype[np.intc]] = np.ndarray((length, self._graph._ndim), dtype=np.intc) _check( lib.get_travel_path( self._graph._ndim, - self._travel_p, + _travel_p, index, ffi.from_buffer("int*", path), ) From 254ac30c47e0b6eace332cd9400dd49377fc3065 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 27 Aug 2025 04:51:31 -0700 Subject: [PATCH 1011/1101] Fix missing for-loop increment in C frontier updater --- CHANGELOG.md | 1 + tcod/path.c | 2 +- tcod/path.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7945eec..c6c34c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - Fixed dangling pointer in `Pathfinder.clear` method. +- Fixed hang in `Pathfinder.rebuild_frontier` method. ## [19.4.0] - 2025-08-06 diff --git a/tcod/path.c b/tcod/path.c index 61a09260..8f4f2fc6 100644 --- a/tcod/path.c +++ b/tcod/path.c @@ -454,7 +454,7 @@ static int update_frontier_from_distance_iterator( int dist = get_array_int(dist_map, dimension, index); return TCOD_frontier_push(frontier, index, dist, dist); } - for (int i = 0; i < dist_map->shape[dimension];) { + for (int i = 0; i < dist_map->shape[dimension]; ++i) { index[dimension] = i; int err = update_frontier_from_distance_iterator(frontier, dist_map, dimension + 1, index); if (err) { return err; } diff --git a/tcod/path.py b/tcod/path.py index a1efc249..02db86ef 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -1246,6 +1246,20 @@ def rebuild_frontier(self) -> None: After you are finished editing :any:`distance` you must call this function before calling :any:`resolve` or any function which calls :any:`resolve` implicitly such as :any:`path_from` or :any:`path_to`. + + Example:: + + >>> import tcod.path + >>> graph = tcod.path.SimpleGraph( + ... cost=np.ones((5, 5), np.int8), cardinal=2, diagonal=3, + ... ) + >>> pf = tcod.path.Pathfinder(graph) + >>> pf.distance[:, 0] = 0 # Set roots along entire left edge + >>> pf.rebuild_frontier() + >>> pf.path_to((0, 2)).tolist() # Finds best path from [:, 0] + [[0, 0], [0, 1], [0, 2]] + >>> pf.path_to((4, 2)).tolist() + [[4, 0], [4, 1], [4, 2]] """ lib.TCOD_frontier_clear(self._frontier_p) self._update_heuristic(None) From 30f7834414e6190747c019ef9e800a7c5d657f41 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 27 Aug 2025 05:05:30 -0700 Subject: [PATCH 1012/1101] Prepare 19.4.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c34c61..ca8f048b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.4.1] - 2025-08-27 + ### Fixed - Fixed dangling pointer in `Pathfinder.clear` method. From 9368809746cd00cd0eed2280c0fd122086957e40 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 27 Aug 2025 06:01:56 -0700 Subject: [PATCH 1013/1101] Remove PyPy x86 builds This is creating amd64 wheels so I do not trust it There are issues with ZIP uploads on PyPI which might be caused by this but I'm unsure. --- .github/workflows/python-package.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a9847a08..af046082 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -103,9 +103,6 @@ jobs: - os: "windows-latest" python-version: "3.10" architecture: "x86" - - os: "windows-latest" - python-version: "pypy-3.10" - architecture: "x86" fail-fast: false steps: From e8c615ad4757af1ece1c22efd6fc1571968d9ce3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 5 Sep 2025 04:54:31 -0700 Subject: [PATCH 1014/1101] Update to libtcod 2.2.1 --- CHANGELOG.md | 8 +++ build_libtcod.py | 1 + libtcod | 2 +- tcod/_libtcod.pyi | 138 +--------------------------------------------- 4 files changed, 11 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8f048b..015ea936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- Update to libtcod 2.2.1. + +### Fixed + +- `SDL_RENDER_SCALE_QUALITY` is now respected again since the change to SDL3. + ## [19.4.1] - 2025-08-27 ### Fixed diff --git a/build_libtcod.py b/build_libtcod.py index c7b0d577..73d86ea3 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -43,6 +43,7 @@ r"TCODLIB_C?API|TCOD_PUBLIC|TCOD_NODISCARD|TCOD_DEPRECATED_NOMESSAGE|TCOD_DEPRECATED_ENUM" r"|(TCOD_DEPRECATED\(\".*?\"\))" r"|(TCOD_DEPRECATED|TCODLIB_FORMAT)\([^)]*\)|__restrict" + r"|TCODLIB_(BEGIN|END)_IGNORE_DEPRECATIONS" ) RE_VAFUNC = re.compile(r"^[^;]*\([^;]*va_list.*\);", re.MULTILINE) RE_INLINE = re.compile(r"(^.*?inline.*?\(.*?\))\s*\{.*?\}$", re.DOTALL | re.MULTILINE) diff --git a/libtcod b/libtcod index ffa44720..ca8efa70 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ffa447202e9b354691386e91f1288fd69dc1eaba +Subproject commit ca8efa70c50175c6d24336f9bc84cf995f4dbef4 diff --git a/tcod/_libtcod.pyi b/tcod/_libtcod.pyi index 4b8ed0fe..04fba29b 100644 --- a/tcod/_libtcod.pyi +++ b/tcod/_libtcod.pyi @@ -7528,142 +7528,6 @@ class _lib: def TCOD_zip_skip_bytes(zip: Any, nbBytes: Any, /) -> None: """void TCOD_zip_skip_bytes(TCOD_zip_t zip, uint32_t nbBytes)""" - @staticmethod - def TDL_color_HSV(h: float, s: float, v: float, /) -> int: - """int TDL_color_HSV(float h, float s, float v)""" - - @staticmethod - def TDL_color_RGB(r: int, g: int, b: int, /) -> int: - """int TDL_color_RGB(int r, int g, int b)""" - - @staticmethod - def TDL_color_add(c1: int, c2: int, /) -> int: - """int TDL_color_add(int c1, int c2)""" - - @staticmethod - def TDL_color_equals(c1: int, c2: int, /) -> bool: - """bool TDL_color_equals(int c1, int c2)""" - - @staticmethod - def TDL_color_from_int(color: int, /) -> Any: - """TCOD_color_t TDL_color_from_int(int color)""" - - @staticmethod - def TDL_color_get_hue(color: int, /) -> float: - """float TDL_color_get_hue(int color)""" - - @staticmethod - def TDL_color_get_saturation(color: int, /) -> float: - """float TDL_color_get_saturation(int color)""" - - @staticmethod - def TDL_color_get_value(color: int, /) -> float: - """float TDL_color_get_value(int color)""" - - @staticmethod - def TDL_color_int_to_array(color: int, /) -> Any: - """int *TDL_color_int_to_array(int color)""" - - @staticmethod - def TDL_color_lerp(c1: int, c2: int, coef: float, /) -> int: - """int TDL_color_lerp(int c1, int c2, float coef)""" - - @staticmethod - def TDL_color_multiply(c1: int, c2: int, /) -> int: - """int TDL_color_multiply(int c1, int c2)""" - - @staticmethod - def TDL_color_multiply_scalar(c: int, value: float, /) -> int: - """int TDL_color_multiply_scalar(int c, float value)""" - - @staticmethod - def TDL_color_scale_HSV(color: int, scoef: float, vcoef: float, /) -> int: - """int TDL_color_scale_HSV(int color, float scoef, float vcoef)""" - - @staticmethod - def TDL_color_set_hue(color: int, h: float, /) -> int: - """int TDL_color_set_hue(int color, float h)""" - - @staticmethod - def TDL_color_set_saturation(color: int, h: float, /) -> int: - """int TDL_color_set_saturation(int color, float h)""" - - @staticmethod - def TDL_color_set_value(color: int, h: float, /) -> int: - """int TDL_color_set_value(int color, float h)""" - - @staticmethod - def TDL_color_shift_hue(color: int, hue_shift: float, /) -> int: - """int TDL_color_shift_hue(int color, float hue_shift)""" - - @staticmethod - def TDL_color_subtract(c1: int, c2: int, /) -> int: - """int TDL_color_subtract(int c1, int c2)""" - - @staticmethod - def TDL_color_to_int(color: Any, /) -> int: - """int TDL_color_to_int(TCOD_color_t *color)""" - - @staticmethod - def TDL_console_get_bg(console: Any, x: int, y: int, /) -> int: - """int TDL_console_get_bg(TCOD_console_t console, int x, int y)""" - - @staticmethod - def TDL_console_get_fg(console: Any, x: int, y: int, /) -> int: - """int TDL_console_get_fg(TCOD_console_t console, int x, int y)""" - - @staticmethod - def TDL_console_put_char_ex(console: Any, x: int, y: int, ch: int, fg: int, bg: int, flag: Any, /) -> int: - """int TDL_console_put_char_ex(TCOD_console_t console, int x, int y, int ch, int fg, int bg, TCOD_bkgnd_flag_t flag)""" - - @staticmethod - def TDL_console_set_bg(console: Any, x: int, y: int, color: int, flag: Any, /) -> None: - """void TDL_console_set_bg(TCOD_console_t console, int x, int y, int color, TCOD_bkgnd_flag_t flag)""" - - @staticmethod - def TDL_console_set_fg(console: Any, x: int, y: int, color: int, /) -> None: - """void TDL_console_set_fg(TCOD_console_t console, int x, int y, int color)""" - - @staticmethod - def TDL_list_get_bool(l: Any, idx: int, /) -> bool: - """bool TDL_list_get_bool(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_char(l: Any, idx: int, /) -> Any: - """char TDL_list_get_char(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_color(l: Any, idx: int, /) -> Any: - """TCOD_color_t TDL_list_get_color(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_dice(l: Any, idx: int, /) -> Any: - """TCOD_dice_t TDL_list_get_dice(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_float(l: Any, idx: int, /) -> float: - """float TDL_list_get_float(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_int(l: Any, idx: int, /) -> int: - """int TDL_list_get_int(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_string(l: Any, idx: int, /) -> Any: - """char *TDL_list_get_string(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_list_get_union(l: Any, idx: int, /) -> Any: - """TCOD_value_t TDL_list_get_union(TCOD_list_t l, int idx)""" - - @staticmethod - def TDL_map_data_from_buffer(map: Any, buffer: Any, /) -> None: - """void TDL_map_data_from_buffer(TCOD_map_t map, uint8_t *buffer)""" - - @staticmethod - def TDL_map_fov_to_buffer(map: Any, buffer: Any, cumulative: bool, /) -> None: - """void TDL_map_fov_to_buffer(TCOD_map_t map, uint8_t *buffer, bool cumulative)""" - @staticmethod def _libtcod_log_watcher(message: Any, userdata: Any, /) -> None: """void _libtcod_log_watcher(const TCOD_LogMessage *message, void *userdata)""" @@ -9648,7 +9512,7 @@ class _lib: TCOD_LOG_INFO: Final[Literal[20]] = 20 TCOD_LOG_WARNING: Final[Literal[30]] = 30 TCOD_MAJOR_VERSION: Final[Literal[2]] = 2 - TCOD_MINOR_VERSION: Final[Literal[1]] = 1 + TCOD_MINOR_VERSION: Final[Literal[2]] = 2 TCOD_NB_RENDERERS: Final[int] TCOD_NOISE_DEFAULT: Final[Literal[0]] = 0 TCOD_NOISE_MAX_DIMENSIONS: Final[Literal[4]] = 4 From c422bc7909291e63b1d36acf1676ea66e9c43aa8 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 13 Sep 2025 09:50:33 -0700 Subject: [PATCH 1015/1101] Use SDL3 struct attributes for controller events Names were changed from SDL2 and were causing crashes --- .vscode/settings.json | 6 +++--- CHANGELOG.md | 1 + tcod/event.py | 14 +++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c45bb5e5..c4b4d82a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,11 +71,8 @@ "bysource", "caeldera", "CAPSLOCK", - "caxis", - "cbutton", "ccoef", "cdef", - "cdevice", "cffi", "cflags", "CFLAGS", @@ -169,6 +166,9 @@ "fwidth", "GAMECONTROLLER", "gamepad", + "gaxis", + "gbutton", + "gdevice", "genindex", "getbbox", "GFORCE", diff --git a/CHANGELOG.md b/CHANGELOG.md index 015ea936..11ae529d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Fixed - `SDL_RENDER_SCALE_QUALITY` is now respected again since the change to SDL3. +- Fixed crash on controller events. ## [19.4.1] - 2025-08-27 diff --git a/tcod/event.py b/tcod/event.py index 5a2bceb8..3859547d 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1060,9 +1060,9 @@ def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis def from_sdl_event(cls, sdl_event: Any) -> ControllerAxis: return cls( "CONTROLLERAXISMOTION", - sdl_event.caxis.which, - tcod.sdl.joystick.ControllerAxis(sdl_event.caxis.axis), - sdl_event.caxis.value, + sdl_event.gaxis.which, + tcod.sdl.joystick.ControllerAxis(sdl_event.gaxis.axis), + sdl_event.gaxis.value, ) def __repr__(self) -> str: @@ -1099,9 +1099,9 @@ def from_sdl_event(cls, sdl_event: Any) -> ControllerButton: }[sdl_event.type] return cls( type, - sdl_event.cbutton.which, - tcod.sdl.joystick.ControllerButton(sdl_event.cbutton.button), - bool(sdl_event.cbutton.down), + sdl_event.gbutton.which, + tcod.sdl.joystick.ControllerButton(sdl_event.gbutton.button), + bool(sdl_event.gbutton.down), ) def __repr__(self) -> str: @@ -1130,7 +1130,7 @@ def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: lib.SDL_EVENT_GAMEPAD_REMOVED: "CONTROLLERDEVICEREMOVED", lib.SDL_EVENT_GAMEPAD_REMAPPED: "CONTROLLERDEVICEREMAPPED", }[sdl_event.type] - return cls(type, sdl_event.cdevice.which) + return cls(type, sdl_event.gdevice.which) @functools.cache From ca3475e1fddcde8c99bee9c4a821c9b48cab7747 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 13 Sep 2025 10:03:02 -0700 Subject: [PATCH 1016/1101] Suppress Mypy error Old workaround cases an error in latest Mypy A real fix is a breaking change for later --- tcod/path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcod/path.py b/tcod/path.py index 02db86ef..468bd95f 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -473,7 +473,7 @@ def dijkstra2d( # noqa: PLR0913 Added `out` parameter. Now returns the output array. """ dist: NDArray[Any] = np.asarray(distance) - if out is ...: + if out is ...: # type: ignore[comparison-overlap] out = dist warnings.warn( "No `out` parameter was given. " From 59d5923829f5e6f20ac2776fed843db99eda812e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 13 Sep 2025 12:29:26 -0700 Subject: [PATCH 1017/1101] Prepare 19.5.0 release. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ae529d..680841c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,12 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.5.0] - 2025-09-13 + ### Changed - Update to libtcod 2.2.1. +- Scaling defaults to nearest, set `os.environ["SDL_RENDER_SCALE_QUALITY"] = "linear"` if linear scaling was preferred. ### Fixed From 1bd481450d10b7d2af2ca853221e1a9348fae97f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:51:33 +0000 Subject: [PATCH 1018/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) - [github.com/astral-sh/ruff-pre-commit: v0.12.2 → v0.13.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.2...v0.13.3) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8234ccf4..e7a67182 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: autoupdate_schedule: quarterly repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.13.3 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] From f88862bf80da53b7f683c5ed617d884f2a014483 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 6 Oct 2025 14:39:17 -0700 Subject: [PATCH 1019/1101] Test libsdl-org/setup-sdl update Checking if latest version on main no longer needs a workaround --- .github/workflows/python-package.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index af046082..229c44eb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -45,7 +45,7 @@ jobs: sdist: runs-on: ubuntu-latest steps: - - uses: HexDecimal/my-setup-sdl-action@v1.0.0 + - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 with: install-linux-dependencies: true build-type: "Debug" @@ -122,7 +122,7 @@ jobs: run: | sudo apt-get update sudo apt-get install xvfb - - uses: HexDecimal/my-setup-sdl-action@v1.0.0 + - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -165,7 +165,7 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ubuntu-latest steps: - - uses: HexDecimal/my-setup-sdl-action@v1.0.0 + - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -207,7 +207,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip tox - - uses: HexDecimal/my-setup-sdl-action@v1.0.0 + - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 if: runner.os == 'Linux' with: install-linux-dependencies: true @@ -327,7 +327,7 @@ jobs: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - uses: HexDecimal/my-setup-sdl-action@v1.0.0 + - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 with: install-linux-dependencies: true build-type: "Debug" From 87ecd62f9f2f8aa81c319ad1cc1e4feb96f3cb6f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 9 Oct 2025 18:03:54 -0700 Subject: [PATCH 1020/1101] Fix lowercase letter KeySym regressions Add single number aliases to KeySym Partially update KeySym docs --- CHANGELOG.md | 11 +++++ tcod/event.py | 110 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 680841c7..5b245ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- Alternative syntax for number symbols with `KeySym`, can now specify `KeySym["3"]`, etc. + Only available on Python 3.13 or later. + +### Fixed + +- Fixed regression with lowercase key symbols with `tcod.event.K_*` and `KeySym.*` constants, these are still deprecated. + Event constants are only fixed for `tcod.event.K_*`, not the undocumented `tcod.event_constants` module. + Lowercase `KeySym.*` constants are only available on Python 3.13 or later. + ## [19.5.0] - 2025-09-13 ### Changed diff --git a/tcod/event.py b/tcod/event.py index 3859547d..6b52a2a1 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -84,6 +84,7 @@ import enum import functools +import sys import warnings from collections.abc import Callable, Iterator, Mapping from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar @@ -2230,11 +2231,19 @@ def __repr__(self) -> str: class KeySym(enum.IntEnum): """Keyboard constants based on their symbol. - These names are derived from SDL except for the numbers which are prefixed - with ``N`` (since raw numbers can not be a Python name.) + These names are derived from SDL except for numbers which are prefixed with ``N`` (since raw numbers can not be a Python name). + Alternatively ``KeySym["9"]`` can be used to represent numbers (since Python 3.13). .. versionadded:: 12.3 + .. versionchanged:: 19.0 + SDL backend was updated to 3.x, which means some enums have been renamed. + Single letters are now uppercase. + + .. versionchanged:: Unreleased + Number symbols can now be fetched with ``KeySym["9"]``, etc. + With Python 3.13 or later. + ================== ========== UNKNOWN 0 BACKSPACE 8 @@ -2280,32 +2289,32 @@ class KeySym(enum.IntEnum): CARET 94 UNDERSCORE 95 BACKQUOTE 96 - a 97 - b 98 - c 99 - d 100 - e 101 - f 102 - g 103 - h 104 - i 105 - j 106 - k 107 - l 108 - m 109 - n 110 - o 111 - p 112 - q 113 - r 114 - s 115 - t 116 - u 117 - v 118 - w 119 - x 120 - y 121 - z 122 + A 97 + B 98 + C 99 + D 100 + E 101 + F 102 + G 103 + H 104 + I 105 + J 106 + K 107 + L 108 + M 109 + N 110 + O 111 + P 112 + Q 113 + R 114 + S 115 + T 116 + U 117 + V 118 + W 119 + X 120 + Y 121 + Z 122 DELETE 127 SCANCODE_MASK 1073741824 CAPSLOCK 1073741881 @@ -2799,6 +2808,48 @@ def __repr__(self) -> str: return f"tcod.event.{self.__class__.__name__}.{self.name}" +if sys.version_info >= (3, 13): + # Alias for lower case letters removed from SDL3 + KeySym.A._add_alias_("a") + KeySym.B._add_alias_("b") + KeySym.C._add_alias_("c") + KeySym.D._add_alias_("d") + KeySym.E._add_alias_("e") + KeySym.F._add_alias_("f") + KeySym.G._add_alias_("g") + KeySym.H._add_alias_("h") + KeySym.I._add_alias_("i") + KeySym.J._add_alias_("j") + KeySym.K._add_alias_("k") + KeySym.L._add_alias_("l") + KeySym.M._add_alias_("m") + KeySym.N._add_alias_("n") + KeySym.O._add_alias_("o") + KeySym.P._add_alias_("p") + KeySym.Q._add_alias_("q") + KeySym.R._add_alias_("r") + KeySym.S._add_alias_("s") + KeySym.T._add_alias_("t") + KeySym.U._add_alias_("u") + KeySym.V._add_alias_("v") + KeySym.W._add_alias_("w") + KeySym.X._add_alias_("x") + KeySym.Y._add_alias_("y") + KeySym.Z._add_alias_("z") + + # Alias for numbers, since Python enum names can not be number literals + KeySym.N0._add_alias_("0") + KeySym.N1._add_alias_("1") + KeySym.N2._add_alias_("2") + KeySym.N3._add_alias_("3") + KeySym.N4._add_alias_("4") + KeySym.N5._add_alias_("5") + KeySym.N6._add_alias_("6") + KeySym.N7._add_alias_("7") + KeySym.N8._add_alias_("8") + KeySym.N9._add_alias_("9") + + def __getattr__(name: str) -> int: """Migrate deprecated access of event constants.""" if name.startswith("BUTTON_"): @@ -2822,6 +2873,9 @@ def __getattr__(name: str) -> int: ) return replacement + if name.startswith("K_") and len(name) == 3: # noqa: PLR2004 + name = name.upper() # Silently fix single letter key symbols removed from SDL3, these are still deprecated + value: int | None = getattr(tcod.event_constants, name, None) if not value: msg = f"module {__name__!r} has no attribute {name!r}" From 05eda1cdee395c40867e896c260efed3e81aeb1a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 19 Oct 2025 20:52:41 -0700 Subject: [PATCH 1021/1101] Fix BSP.split_recursive crash when passing Random Random was passed directly to CFFI instead of fetching its C pointer Fixes #168 --- CHANGELOG.md | 1 + tcod/bsp.py | 17 +++++++---------- tests/test_tcod.py | 9 +++++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b245ac8..022493d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Fixed regression with lowercase key symbols with `tcod.event.K_*` and `KeySym.*` constants, these are still deprecated. Event constants are only fixed for `tcod.event.K_*`, not the undocumented `tcod.event_constants` module. Lowercase `KeySym.*` constants are only available on Python 3.13 or later. +- `BSP.split_recursive` did not accept a `Random` class as the seed. #168 ## [19.5.0] - 2025-09-13 diff --git a/tcod/bsp.py b/tcod/bsp.py index f4d7204d..cfb5d15c 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -148,20 +148,17 @@ def split_recursive( # noqa: PLR0913 """Divide this partition recursively. Args: - depth (int): The maximum depth to divide this object recursively. - min_width (int): The minimum width of any individual partition. - min_height (int): The minimum height of any individual partition. - max_horizontal_ratio (float): - Prevent creating a horizontal ratio more extreme than this. - max_vertical_ratio (float): - Prevent creating a vertical ratio more extreme than this. - seed (Optional[tcod.random.Random]): - The random number generator to use. + depth: The maximum depth to divide this object recursively. + min_width: The minimum width of any individual partition. + min_height: The minimum height of any individual partition. + max_horizontal_ratio: Prevent creating a horizontal ratio more extreme than this. + max_vertical_ratio: Prevent creating a vertical ratio more extreme than this. + seed: The random number generator to use. """ cdata = self._as_cdata() lib.TCOD_bsp_split_recursive( cdata, - seed or ffi.NULL, + seed.random_c if seed is not None else ffi.NULL, depth, min_width, min_height, diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 02eb6dbb..74e24c91 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -9,7 +9,12 @@ from numpy.typing import DTypeLike, NDArray import tcod +import tcod.bsp import tcod.console +import tcod.context +import tcod.map +import tcod.path +import tcod.random from tcod import libtcodpy @@ -20,7 +25,7 @@ def raise_Exception(*_args: object) -> NoReturn: def test_line_error() -> None: """Test exception propagation.""" with pytest.raises(RuntimeError), pytest.warns(): - tcod.line(0, 0, 10, 10, py_callback=raise_Exception) + libtcodpy.line(0, 0, 10, 10, py_callback=raise_Exception) @pytest.mark.filterwarnings("ignore:Iterate over nodes using") @@ -44,7 +49,7 @@ def test_tcod_bsp() -> None: # test that operations on deep BSP nodes preserve depth sub_bsp = bsp.children[0] - sub_bsp.split_recursive(3, 2, 2, 1, 1) + sub_bsp.split_recursive(3, 2, 2, 1, 1, seed=tcod.random.Random(seed=42)) assert sub_bsp.children[0].level == 2 # noqa: PLR2004 # cover find_node method From ebdd0e4cf80339e595cf0a51ffb3585e9d43411c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 19 Oct 2025 21:03:38 -0700 Subject: [PATCH 1022/1101] Resolve lint warnings --- tcod/bsp.py | 19 ++++++++++++------- tcod/random.py | 6 ++++-- tests/test_tcod.py | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/tcod/bsp.py b/tcod/bsp.py index cfb5d15c..21aa093d 100644 --- a/tcod/bsp.py +++ b/tcod/bsp.py @@ -27,7 +27,6 @@ from __future__ import annotations -from collections.abc import Iterator from typing import TYPE_CHECKING, Any from typing_extensions import deprecated @@ -35,6 +34,8 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Iterator + import tcod.random @@ -125,7 +126,11 @@ def _unpack_bsp_tree(self, cdata: Any) -> None: # noqa: ANN401 self.children[1].parent = self self.children[1]._unpack_bsp_tree(lib.TCOD_bsp_right(cdata)) - def split_once(self, horizontal: bool, position: int) -> None: + def split_once( + self, + horizontal: bool, # noqa: FBT001 + position: int, + ) -> None: """Split this partition into 2 sub-partitions. Args: @@ -211,13 +216,13 @@ def level_order(self) -> Iterator[BSP]: .. versionadded:: 8.3 """ - next = [self] - while next: - level = next - next = [] + next_ = [self] + while next_: + level = next_ + next_ = [] yield from level for node in level: - next.extend(node.children) + next_.extend(node.children) def inverted_level_order(self) -> Iterator[BSP]: """Iterate over this BSP's hierarchy in inverse level order. diff --git a/tcod/random.py b/tcod/random.py index 6d332b86..1b7b16c7 100644 --- a/tcod/random.py +++ b/tcod/random.py @@ -12,14 +12,16 @@ import os import random import warnings -from collections.abc import Hashable -from typing import Any +from typing import TYPE_CHECKING, Any from typing_extensions import deprecated import tcod.constants from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from collections.abc import Hashable + MERSENNE_TWISTER = tcod.constants.RNG_MT COMPLEMENTARY_MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC MULTIPLY_WITH_CARRY = tcod.constants.RNG_CMWC diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 74e24c91..23bb7d6b 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -18,14 +18,14 @@ from tcod import libtcodpy -def raise_Exception(*_args: object) -> NoReturn: +def raise_exception(*_args: object) -> NoReturn: raise RuntimeError("testing exception") # noqa: TRY003, EM101 def test_line_error() -> None: """Test exception propagation.""" with pytest.raises(RuntimeError), pytest.warns(): - libtcodpy.line(0, 0, 10, 10, py_callback=raise_Exception) + libtcodpy.line(0, 0, 10, 10, py_callback=raise_exception) @pytest.mark.filterwarnings("ignore:Iterate over nodes using") @@ -39,7 +39,7 @@ def test_tcod_bsp() -> None: assert not bsp.children with pytest.raises(RuntimeError): - libtcodpy.bsp_traverse_pre_order(bsp, raise_Exception) + libtcodpy.bsp_traverse_pre_order(bsp, raise_exception) bsp.split_recursive(3, 4, 4, 1, 1) for node in bsp.walk(): @@ -102,7 +102,7 @@ def test_tcod_map_pickle() -> None: def test_tcod_map_pickle_fortran() -> None: map_ = tcod.map.Map(2, 3, order="F") map2: tcod.map.Map = pickle.loads(pickle.dumps(copy.copy(map_))) - assert map_._Map__buffer.strides == map2._Map__buffer.strides # type: ignore + assert map_._Map__buffer.strides == map2._Map__buffer.strides # type: ignore[attr-defined] assert map_.transparent.strides == map2.transparent.strides assert map_.walkable.strides == map2.walkable.strides assert map_.fov.strides == map2.fov.strides @@ -148,7 +148,7 @@ def test_path_numpy(dtype: DTypeLike) -> None: tcod.path.AStar(np.ones((2, 2), dtype=np.float64)) -def path_cost(this_x: int, this_y: int, dest_x: int, dest_y: int) -> bool: +def path_cost(_this_x: int, _this_y: int, _dest_x: int, _dest_y: int) -> bool: return True @@ -160,7 +160,7 @@ def test_path_callback() -> None: def test_key_repr() -> None: - Key = libtcodpy.Key + Key = libtcodpy.Key # noqa: N806 key = Key(vk=1, c=2, shift=True) assert key.vk == 1 assert key.c == 2 # noqa: PLR2004 @@ -172,7 +172,7 @@ def test_key_repr() -> None: def test_mouse_repr() -> None: - Mouse = libtcodpy.Mouse + Mouse = libtcodpy.Mouse # noqa: N806 mouse = Mouse(x=1, lbutton=True) mouse_copy = eval(repr(mouse)) # noqa: S307 assert mouse.x == mouse_copy.x @@ -185,16 +185,16 @@ def test_cffi_structs() -> None: @pytest.mark.filterwarnings("ignore") -def test_recommended_size(console: tcod.console.Console) -> None: +def test_recommended_size(console: tcod.console.Console) -> None: # noqa: ARG001 tcod.console.recommended_size() @pytest.mark.filterwarnings("ignore") -def test_context(uses_window: None) -> None: +def test_context(uses_window: None) -> None: # noqa: ARG001 with tcod.context.new_window(32, 32, renderer=libtcodpy.RENDERER_SDL2): pass - WIDTH, HEIGHT = 16, 4 - with tcod.context.new_terminal(columns=WIDTH, rows=HEIGHT, renderer=libtcodpy.RENDERER_SDL2) as context: + width, height = 16, 4 + with tcod.context.new_terminal(columns=width, rows=height, renderer=libtcodpy.RENDERER_SDL2) as context: console = tcod.console.Console(*context.recommended_console_size()) context.present(console) assert context.sdl_window_p is not None @@ -202,5 +202,5 @@ def test_context(uses_window: None) -> None: context.change_tileset(tcod.tileset.Tileset(16, 16)) context.pixel_to_tile(0, 0) context.pixel_to_subtile(0, 0) - with pytest.raises(RuntimeError, match=".*context has been closed"): + with pytest.raises(RuntimeError, match=r".*context has been closed"): context.present(console) From 37e1b3401efceab1960d539f5c79a0385fb4bed9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 19 Oct 2025 21:19:42 -0700 Subject: [PATCH 1023/1101] Prepare 19.6.0 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 022493d6..23f96318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.6.0] - 2025-10-20 + ### Added - Alternative syntax for number symbols with `KeySym`, can now specify `KeySym["3"]`, etc. diff --git a/tcod/event.py b/tcod/event.py index 6b52a2a1..b7657eb2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -2240,7 +2240,7 @@ class KeySym(enum.IntEnum): SDL backend was updated to 3.x, which means some enums have been renamed. Single letters are now uppercase. - .. versionchanged:: Unreleased + .. versionchanged:: 19.6 Number symbols can now be fetched with ``KeySym["9"]``, etc. With Python 3.13 or later. From 770ec4610489f2d22d335ee22aea06ac17214bf5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 3 Nov 2025 12:36:44 +0100 Subject: [PATCH 1024/1101] Keep GitHub Actions up to date with GitHub's Dependabot * [Keeping your software supply chain secure with Dependabot](https://docs.github.com/en/code-security/dependabot) * [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot) * [Configuration options for the `dependabot.yml` file - package-ecosystem](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem) To see all GitHub Actions dependencies, type: % `git grep 'uses: ' .github/workflows/` --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..be006de9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly From 5d3d85c0019ad2edf570f5f0024a05cccfad6670 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:23:53 +0000 Subject: [PATCH 1025/1101] Bump the github-actions group across 1 directory with 5 updates Bumps the github-actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `5` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4` | `5` | | [actions/setup-python](https://github.com/actions/setup-python) | `5` | `6` | | [codecov/codecov-action](https://github.com/codecov/codecov-action) | `4` | `5` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `4` | `6` | Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) Updates `codecov/codecov-action` from 4 to 5 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) Updates `actions/download-artifact` from 4 to 6 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: codecov/codecov-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yml | 44 ++++++++++++++-------------- .github/workflows/release-on-tag.yml | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 229c44eb..d328cefd 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -20,7 +20,7 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Ruff run: pip install ruff - name: Ruff Check @@ -31,7 +31,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install typing dependencies @@ -50,7 +50,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: ${{ env.sdl-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -59,7 +59,7 @@ jobs: run: pip install build - name: Build source distribution run: python -m build --sdist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: sdist path: dist/tcod-*.tar.gz @@ -76,12 +76,12 @@ jobs: sdl-version: ["3.2.16"] fail-fast: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" - name: Install build dependencies @@ -106,14 +106,14 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: | git submodule update --init --recursive --depth 1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} @@ -150,10 +150,10 @@ jobs: - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 if: runner.os == 'Windows' with: name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }} @@ -171,7 +171,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: ${{ env.sdl-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -195,13 +195,13 @@ jobs: matrix: os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --depth 1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Install Python dependencies @@ -227,7 +227,7 @@ jobs: env: BUILD_DESC: "" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -265,7 +265,7 @@ jobs: BUILD_DESC=${BUILD_DESC//\*} echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }} path: wheelhouse/*.whl @@ -282,12 +282,12 @@ jobs: env: PYTHON_DESC: "" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" - name: Install Python dependencies @@ -311,7 +311,7 @@ jobs: PYTHON_DESC=${PYTHON_DESC//\*/X} echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: wheels-macos-${{ env.PYTHON_DESC }} path: wheelhouse/*.whl @@ -322,7 +322,7 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -337,7 +337,7 @@ jobs: CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide - name: Archive wheel - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: pyodide path: wheelhouse/*.whl @@ -354,11 +354,11 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: name: sdist path: dist/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v6 with: pattern: wheels-* path: dist/ diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 2171ddbf..87b42c39 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Generate body run: | scripts/get_release_description.py | tee release_body.md From 4ccd7e1925c12972a482bfc4f2e3b00e199a5e74 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Nov 2025 07:35:08 -0800 Subject: [PATCH 1026/1101] Fix docs implying the SDL ports were still on SDL2 --- tcod/sdl/audio.py | 2 +- tcod/sdl/render.py | 2 +- tcod/sdl/video.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 390bc551..41329a61 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -1,4 +1,4 @@ -"""SDL2 audio playback and recording tools. +"""SDL audio playback and recording tools. This module includes SDL's low-level audio API and a naive implementation of an SDL mixer. If you have experience with audio mixing then you might be better off writing your own mixer or diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index f16714a2..f3f7edf3 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -1,4 +1,4 @@ -"""SDL2 Rendering functionality. +"""SDL Rendering functionality. .. versionadded:: 13.4 """ diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 75844823..4895e0f7 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -1,4 +1,4 @@ -"""SDL2 Window and Display handling. +"""SDL Window and Display handling. There are two main ways to access the SDL window. Either you can use this module to open a window yourself bypassing libtcod's context, @@ -167,7 +167,7 @@ def __init__(self, pixels: ArrayLike) -> None: class Window: - """An SDL2 Window object. + """An SDL Window object. Created from :any:`tcod.sdl.video.new_window` when working with SDL directly. From 3ae49b4ad6183e762e8be1882b6df712d6cad393 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Nov 2025 07:44:01 -0800 Subject: [PATCH 1027/1101] Fix code example directives Apparently `Example:` (single colon) works for doctests but `Example::` (2 colons) is required for non-doctest examples. --- tcod/sdl/audio.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 41329a61..b404f94b 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -8,7 +8,8 @@ It leaves the loading to sound samples to other libraries like `SoundFile `_. -Example: +Example:: + # Synchronous audio example import time @@ -211,7 +212,8 @@ def convert_audio( class AudioDevice: """An SDL audio device. - Example: + Example:: + device = tcod.sdl.audio.get_default_playback().open() # Open a common audio device .. versionchanged:: 16.0 @@ -957,7 +959,8 @@ def get_capture_devices() -> dict[str, AudioDevice]: def get_default_playback() -> AudioDevice: """Return the default playback device. - Example: + Example:: + playback_device = tcod.sdl.audio.get_default_playback().open() .. versionadded:: 19.0 @@ -969,7 +972,8 @@ def get_default_playback() -> AudioDevice: def get_default_recording() -> AudioDevice: """Return the default recording device. - Example: + Example:: + recording_device = tcod.sdl.audio.get_default_recording().open() .. versionadded:: 19.0 From eb760a252990e0facab07720cee41b0efa96073b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Nov 2025 08:05:08 -0800 Subject: [PATCH 1028/1101] Fixed missing space in versionadded directives --- tcod/sdl/audio.py | 2 +- tcod/sdl/render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index b404f94b..0a7b9ea7 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -489,7 +489,7 @@ class AudioStream: This class is commonly created with :any:`AudioDevice.new_stream` which creates a new stream bound to the device. - ..versionadded:: 19.0 + .. versionadded:: 19.0 """ __slots__ = ("__weakref__", "_stream_p") diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index f3f7edf3..6833330f 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -271,7 +271,7 @@ def color_mod(self, rgb: tuple[int, int, int]) -> None: def scale_mode(self) -> ScaleMode: """Get or set this textures :any:`ScaleMode`. - ..versionadded:: 19.3 + .. versionadded:: 19.3 """ mode = ffi.new("SDL_ScaleMode*") _check(lib.SDL_GetTextureScaleMode(self.p, mode)) From 2acfae20b94704f64a2a094e32a5c985e73eaf06 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 23 Nov 2025 08:26:23 -0800 Subject: [PATCH 1029/1101] Fix link to soundfile docs Old link was pointing to a broken URL, these docs were moved. --- tcod/sdl/audio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index 0a7b9ea7..e4a84c96 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -5,8 +5,8 @@ modifying the existing one which was written using Python/Numpy. This module is designed to integrate with the wider Python ecosystem. -It leaves the loading to sound samples to other libraries like -`SoundFile `_. +It leaves the loading to sound samples to other libraries such as +`soundfile `_. Example:: From f32d376a24266a83395c2e4eb826d0abc2bf1bbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:59:00 +0000 Subject: [PATCH 1030/1101] Bump actions/checkout from 5 to 6 in the github-actions group Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yml | 20 ++++++++++---------- .github/workflows/release-on-tag.yml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d328cefd..a553edf3 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -20,7 +20,7 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install Ruff run: pip install ruff - name: Ruff Check @@ -31,7 +31,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install typing dependencies @@ -50,7 +50,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: ${{ env.sdl-version }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -76,7 +76,7 @@ jobs: sdl-version: ["3.2.16"] fail-fast: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -106,7 +106,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -171,7 +171,7 @@ jobs: install-linux-dependencies: true build-type: "Debug" version: ${{ env.sdl-version }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -195,7 +195,7 @@ jobs: matrix: os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -227,7 +227,7 @@ jobs: env: BUILD_DESC: "" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -282,7 +282,7 @@ jobs: env: PYTHON_DESC: "" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules @@ -322,7 +322,7 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: ${{ env.git-depth }} - name: Checkout submodules diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 87b42c39..0e89b0c0 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Generate body run: | scripts/get_release_description.py | tee release_body.md From ba9ca9664f77b886b90575358b432557dc3fd267 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Dec 2025 22:31:00 -0800 Subject: [PATCH 1031/1101] Add cooldown to dependabot config Reduces risk of supply chain attacks --- .github/dependabot.yml | 4 +++- .vscode/settings.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index be006de9..2b2eb8c4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,8 @@ updates: groups: github-actions: patterns: - - "*" # Group all Actions updates into a single larger pull request + - "*" # Group all Actions updates into a single larger pull request schedule: interval: weekly + cooldown: + default-days: 7 diff --git a/.vscode/settings.json b/.vscode/settings.json index c4b4d82a..71b8b1cf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -102,6 +102,7 @@ "CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMAPPED", "CONTROLLERDEVICEREMOVED", + "cooldown", "cplusplus", "CPLUSPLUS", "CRSEL", From ddbdc1aec1aebbb5510fabb24e9d874527a30e7d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Dec 2025 22:39:48 -0800 Subject: [PATCH 1032/1101] Update ignore tags on untyped decorators Changed from `misc` to `untyped-decorator` in the latest Mypy --- tcod/cffi.py | 2 +- tcod/context.py | 2 +- tcod/event.py | 2 +- tcod/libtcodpy.py | 10 +++++----- tcod/path.py | 8 ++++---- tcod/sdl/_internal.py | 2 +- tcod/sdl/audio.py | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tcod/cffi.py b/tcod/cffi.py index b0590d0e..97a37e44 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -60,7 +60,7 @@ def get_sdl_version() -> str: __sdl_version__ = get_sdl_version() -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _libtcod_log_watcher(message: Any, _userdata: None) -> None: # noqa: ANN401 text = str(ffi.string(message.message), encoding="utf-8") source = str(ffi.string(message.source), encoding="utf-8") diff --git a/tcod/context.py b/tcod/context.py index 769f84d2..2668a120 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -444,7 +444,7 @@ def __reduce__(self) -> NoReturn: raise pickle.PicklingError(msg) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_cli_output(catch_reference: Any, output: Any) -> None: # noqa: ANN401 """Callback for the libtcod context CLI. diff --git a/tcod/event.py b/tcod/event.py index b7657eb2..f693291a 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1564,7 +1564,7 @@ def get_mouse_state() -> MouseState: return MouseState((xy[0], xy[1]), (int(tile[0]), int(tile[1])), buttons) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: callback: Callable[[Event], None] = ffi.from_handle(userdata) callback(_parse_event(sdl_event)) diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index fdfaeefe..c6b591cc 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -3607,27 +3607,27 @@ def parser_new_struct(parser: Any, name: str) -> Any: _parser_listener: Any = None -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_parser_new_struct(struct: Any, name: str) -> Any: return _parser_listener.new_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_parser_new_flag(name: str) -> Any: return _parser_listener.new_flag(_unpack_char_p(name)) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: return _parser_listener.new_property(_unpack_char_p(propname), type, _unpack_union(type, value)) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_parser_end_struct(struct: Any, name: Any) -> Any: return _parser_listener.end_struct(struct, _unpack_char_p(name)) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_parser_error(msg: Any) -> None: _parser_listener.error(_unpack_char_p(msg)) diff --git a/tcod/path.py b/tcod/path.py index 468bd95f..9d759371 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -35,26 +35,26 @@ from numpy.typing import ArrayLike, DTypeLike, NDArray -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_path_old(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """Libtcodpy style callback, needs to preserve the old userdata issue.""" func, userdata = ffi.from_handle(handle) return func(x1, y1, x2, y2, userdata) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_path_simple(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """Does less and should run faster, just calls the handle function.""" return ffi.from_handle(handle)(x1, y1, x2, y2) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_path_swap_src_dest(x1: int, y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function dest comes first to match up with a dest only call.""" return ffi.from_handle(handle)(x2, y2, x1, y1) # type: ignore[no-any-return] -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _pycall_path_dest_only(_x1: int, _y1: int, x2: int, y2: int, handle: Any) -> float: # noqa: ANN401 """A TDL function which samples the dest coordinate only.""" return ffi.from_handle(handle)(x2, y2) # type: ignore[no-any-return] diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index a671331e..4783d551 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -149,7 +149,7 @@ def __setitem__(self, key: tuple[str, type[T]], value: T, /) -> None: lib.SDL_SetPointerProperty(self.p, name, value._as_property_pointer()) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _sdl_log_output_function(_userdata: None, category: int, priority: int, message_p: Any) -> None: # noqa: ANN401 """Pass logs sent by SDL to Python's logging system.""" message = str(ffi.string(message_p), encoding="utf-8") diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index e4a84c96..a2aa62d7 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -906,7 +906,7 @@ def _on_stream(self, audio_stream: AudioStream, data: AudioStreamCallbackData) - audio_stream.queue_audio(stream) -@ffi.def_extern() # type: ignore[misc] +@ffi.def_extern() # type: ignore[untyped-decorator] def _sdl_audio_stream_callback(userdata: Any, stream_p: Any, additional_amount: int, total_amount: int, /) -> None: # noqa: ANN401 """Handle audio device callbacks.""" stream = AudioStream(stream_p) From 5b86dfe6fdd76d50c2cfcb5cf23463eb0dee0b1d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Dec 2025 22:47:21 -0800 Subject: [PATCH 1033/1101] Remove codecov token Codecov configured to no longer require tokens on public repos --- .github/workflows/python-package.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a553edf3..1188a77e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -151,8 +151,6 @@ jobs: if: runner.os != 'Windows' run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - uses: actions/upload-artifact@v5 if: runner.os == 'Windows' with: From 1c973756b9463035f4acac1717ae90058821112c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 7 Dec 2025 22:54:18 -0800 Subject: [PATCH 1034/1101] Add workflow timeouts and concurrency settings Reduces unnecessary usage of public CI resources --- .github/workflows/python-package.yml | 15 +++++++++++++++ .github/workflows/release-on-tag.yml | 1 + 2 files changed, 16 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1188a77e..c97b2c06 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -8,6 +8,10 @@ on: pull_request: types: [opened, reopened] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + defaults: run: shell: bash @@ -19,6 +23,7 @@ env: jobs: ruff: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v6 - name: Install Ruff @@ -30,6 +35,7 @@ jobs: mypy: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v6 - name: Checkout submodules @@ -44,6 +50,7 @@ jobs: sdist: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 with: @@ -70,6 +77,7 @@ jobs: parse_sdl: needs: [ruff, mypy] runs-on: ${{ matrix.os }} + timeout-minutes: 5 strategy: matrix: os: ["windows-latest", "macos-latest"] @@ -94,6 +102,7 @@ jobs: build: needs: [ruff, mypy, sdist] runs-on: ${{ matrix.os }} + timeout-minutes: 15 strategy: matrix: os: ["ubuntu-latest", "windows-latest"] @@ -162,6 +171,7 @@ jobs: test-docs: needs: [ruff, mypy, sdist] runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: libsdl-org/setup-sdl@1894460666e4fe0c6603ea0cce5733f1cca56ba1 if: runner.os == 'Linux' @@ -189,6 +199,7 @@ jobs: tox: needs: [ruff, sdist] runs-on: ${{ matrix.os }} + timeout-minutes: 15 strategy: matrix: os: ["ubuntu-latest"] # "windows-latest" disabled due to free-threaded build issues @@ -218,6 +229,7 @@ jobs: linux-wheels: needs: [ruff, mypy, sdist] runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-latest'}} + timeout-minutes: 15 strategy: matrix: arch: ["x86_64", "aarch64"] @@ -273,6 +285,7 @@ jobs: build-macos: needs: [ruff, mypy, sdist] runs-on: "macos-14" + timeout-minutes: 15 strategy: fail-fast: true matrix: @@ -319,6 +332,7 @@ jobs: pyodide: needs: [ruff, mypy, sdist] runs-on: ubuntu-24.04 + timeout-minutes: 15 steps: - uses: actions/checkout@v6 with: @@ -345,6 +359,7 @@ jobs: publish: needs: [sdist, build, build-macos, linux-wheels, pyodide] runs-on: ubuntu-latest + timeout-minutes: 5 if: github.ref_type == 'tag' environment: name: pypi diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 0e89b0c0..921ac17d 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -9,6 +9,7 @@ jobs: build: name: Create Release runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: write steps: From 2180a750372587716e1db984182e310849c6d6d5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 14 Dec 2025 21:10:23 -0800 Subject: [PATCH 1035/1101] Fix _sdl_event_watcher cdef Type did not match the function def demanded by SDL for event watching and was crashing Add tests to ensure that this function works --- CHANGELOG.md | 4 ++++ build_sdl.py | 2 +- tests/test_tcod.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f96318..09a36973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Fixed + +- `tcod.event.add_watch` was crashing due to a cdef type mismatch. + ## [19.6.0] - 2025-10-20 ### Added diff --git a/build_sdl.py b/build_sdl.py index aca9fc25..2fb39579 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -311,7 +311,7 @@ def get_emscripten_include_dir() -> Path: // SDL to Python log function. void _sdl_log_output_function(void *userdata, int category, SDL_LogPriority priority, const char *message); // Generic event watcher callback. -int _sdl_event_watcher(void* userdata, SDL_Event* event); +bool _sdl_event_watcher(void* userdata, SDL_Event* event); } """ diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 23bb7d6b..c41da899 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -204,3 +204,16 @@ def test_context(uses_window: None) -> None: # noqa: ARG001 context.pixel_to_subtile(0, 0) with pytest.raises(RuntimeError, match=r".*context has been closed"): context.present(console) + + +def test_event_watch() -> None: + def handle_events(_event: tcod.event.Event) -> None: + pass + + tcod.event.add_watch(handle_events) + with pytest.warns(RuntimeWarning, match=r"nothing was added"): + tcod.event.add_watch(handle_events) + + tcod.event.remove_watch(handle_events) + with pytest.warns(RuntimeWarning, match=r"nothing was removed"): + tcod.event.remove_watch(handle_events) From 288eda47fffe3eb126ee7cbf84cf041acc77c155 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 14 Dec 2025 21:53:57 -0800 Subject: [PATCH 1036/1101] Update samples Keep window responsive during resize events via event watchers Workaround tile coords not respecting logical size --- examples/samples_tcod.py | 90 +++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 0955f2a0..41cc1e05 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1348,43 +1348,14 @@ def init_context(renderer: int) -> None: def main() -> None: - global context, tileset + global tileset tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD) init_context(libtcodpy.RENDERER_SDL2) try: SAMPLES[cur_sample].on_enter() while True: - root_console.clear() - draw_samples_menu() - draw_renderer_menu() - - # render the sample - SAMPLES[cur_sample].on_draw() - sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) - draw_stats() - if context.sdl_renderer: - # Clear the screen to ensure no garbage data outside of the logical area is displayed - context.sdl_renderer.draw_color = (0, 0, 0, 255) - context.sdl_renderer.clear() - # SDL renderer support, upload the sample console background to a minimap texture. - sample_minimap.update(sample_console.rgb.T["bg"]) - # Render the root_console normally, this is the drawing step of context.present without presenting. - context.sdl_renderer.copy(console_render.render(root_console)) - # Render the minimap to the screen. - context.sdl_renderer.copy( - sample_minimap, - dest=( - tileset.tile_width * 24, - tileset.tile_height * 36, - SAMPLE_SCREEN_WIDTH * 3, - SAMPLE_SCREEN_HEIGHT * 3, - ), - ) - context.sdl_renderer.present() - else: # No SDL renderer, just use plain context rendering. - context.present(root_console) - + redraw_display() handle_time() handle_events() finally: @@ -1394,6 +1365,39 @@ def main() -> None: context.close() +def redraw_display() -> None: + """Full clear-draw-present of the screen.""" + root_console.clear() + draw_samples_menu() + draw_renderer_menu() + + # render the sample + SAMPLES[cur_sample].on_draw() + sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) + draw_stats() + if context.sdl_renderer: + # Clear the screen to ensure no garbage data outside of the logical area is displayed + context.sdl_renderer.draw_color = (0, 0, 0, 255) + context.sdl_renderer.clear() + # SDL renderer support, upload the sample console background to a minimap texture. + sample_minimap.update(sample_console.rgb.T["bg"]) + # Render the root_console normally, this is the drawing step of context.present without presenting. + context.sdl_renderer.copy(console_render.render(root_console)) + # Render the minimap to the screen. + context.sdl_renderer.copy( + sample_minimap, + dest=( + tileset.tile_width * 24, + tileset.tile_height * 36, + SAMPLE_SCREEN_WIDTH * 3, + SAMPLE_SCREEN_HEIGHT * 3, + ), + ) + context.sdl_renderer.present() + else: # No SDL renderer, just use plain context rendering. + context.present(root_console) + + def handle_time() -> None: if len(frame_times) > 100: frame_times.pop(0) @@ -1404,16 +1408,17 @@ def handle_time() -> None: def handle_events() -> None: for event in tcod.event.get(): - if context.sdl_renderer: - # Manual handing of tile coordinates since context.present is skipped. + if context.sdl_renderer: # Manual handing of tile coordinates since context.present is skipped + assert context.sdl_window + tile_width = context.sdl_window.size[0] / root_console.width + tile_height = context.sdl_window.size[1] / root_console.height + if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): - event.tile = tcod.event.Point( - event.position.x // tileset.tile_width, event.position.y // tileset.tile_height - ) + event.tile = tcod.event.Point(event.position.x // tile_width, event.position.y // tile_height) if isinstance(event, tcod.event.MouseMotion): prev_tile = ( - (event.position[0] - event.motion[0]) // tileset.tile_width, - (event.position[1] - event.motion[1]) // tileset.tile_height, + (event.position[0] - event.motion[0]) // tile_width, + (event.position[1] - event.motion[1]) // tile_height, ) event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) else: @@ -1484,4 +1489,13 @@ def draw_renderer_menu() -> None: if __name__ == "__main__": + + @tcod.event.add_watch + def _handle_events(event: tcod.event.Event) -> None: + """Keep window responsive during resize events.""" + match event: + case tcod.event.WindowEvent(type="WindowExposed"): + redraw_display() + handle_time() + main() From cba0c75552ecec4b37180374ce469e353d50dc6b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 14 Dec 2025 23:17:20 -0800 Subject: [PATCH 1037/1101] Prepare 19.6.1 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a36973..9836d283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.6.1] - 2025-12-15 + ### Fixed - `tcod.event.add_watch` was crashing due to a cdef type mismatch. From 24882ffa780ff9f19e27719d733c8e304970b05e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 15 Dec 2025 03:58:02 -0800 Subject: [PATCH 1038/1101] Refactor TrueColorSample Switch to a frame rate agnostic color sampler Move name attributes to be class variables Update deprecations on Console.print_box to be more clear Add missing deprecation directive --- examples/samples_tcod.py | 122 +++++++++++++++++++-------------------- tcod/console.py | 7 ++- 2 files changed, 67 insertions(+), 62 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 41cc1e05..d2d8f155 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -77,8 +77,7 @@ def _get_elapsed_time() -> float: class Sample(tcod.event.EventDispatch[None]): - def __init__(self, name: str = "") -> None: - self.name = name + name: str = "???" def on_enter(self) -> None: pass @@ -114,74 +113,73 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class TrueColorSample(Sample): + name = "True colors" + def __init__(self) -> None: - self.name = "True colors" - # corner colors - self.colors: NDArray[np.int16] = np.array( - [(50, 40, 150), (240, 85, 5), (50, 35, 240), (10, 200, 130)], - dtype=np.int16, - ) - # color shift direction - self.slide_dir: NDArray[np.int16] = np.array([[1, 1, 1], [-1, -1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.int16) - # corner indexes - self.corners: NDArray[np.int16] = np.array([0, 1, 2, 3], dtype=np.int16) + self.noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX) + """Noise for generating color.""" + + self.generator = np.random.default_rng() + """Numpy generator for random text.""" def on_draw(self) -> None: - self.slide_corner_colors() self.interpolate_corner_colors() self.darken_background_characters() self.randomize_sample_console() - self.print_banner() - - def slide_corner_colors(self) -> None: - # pick random RGB channels for each corner - rand_channels = np.random.randint(low=0, high=3, size=4) - - # shift picked color channels in the direction of slide_dir - self.colors[self.corners, rand_channels] += self.slide_dir[self.corners, rand_channels] * 5 + sample_console.print( + x=1, + y=5, + width=sample_console.width - 2, + height=sample_console.height - 1, + text="The Doryen library uses 24 bits colors, for both background and foreground.", + fg=WHITE, + bg=GREY, + bg_blend=libtcodpy.BKGND_MULTIPLY, + alignment=libtcodpy.CENTER, + ) - # reverse slide_dir values when limits are reached - self.slide_dir[self.colors[:] == 255] = -1 - self.slide_dir[self.colors[:] == 0] = 1 + def get_corner_colors(self) -> NDArray[np.uint8]: + """Return 4 random 8-bit colors, smoothed over time.""" + noise_samples_ij = ( + [ # i coordinates are per color channel per color + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + ], + time.perf_counter(), # j coordinate is time broadcast to all samples + ) + colors = self.noise[noise_samples_ij] + colors = ((colors + 1.0) * (0.5 * 255.0)).clip(min=0, max=255) # Convert -1..1 to 0..255 + return colors.astype(np.uint8) def interpolate_corner_colors(self) -> None: - # interpolate corner colors across the sample console - left = np.linspace(self.colors[0], self.colors[2], SAMPLE_SCREEN_HEIGHT) - right = np.linspace(self.colors[1], self.colors[3], SAMPLE_SCREEN_HEIGHT) + """Interpolate corner colors across the sample console.""" + colors = self.get_corner_colors() + left = np.linspace(colors[0], colors[2], SAMPLE_SCREEN_HEIGHT) + right = np.linspace(colors[1], colors[3], SAMPLE_SCREEN_HEIGHT) sample_console.bg[:] = np.linspace(left, right, SAMPLE_SCREEN_WIDTH) def darken_background_characters(self) -> None: - # darken background characters + """Darken background characters.""" sample_console.fg[:] = sample_console.bg[:] sample_console.fg[:] //= 2 def randomize_sample_console(self) -> None: - # randomize sample console characters - sample_console.ch[:] = np.random.randint( + """Randomize sample console characters.""" + sample_console.ch[:] = self.generator.integers( low=ord("a"), - high=ord("z") + 1, + high=ord("z"), + endpoint=True, size=sample_console.ch.size, dtype=np.intc, ).reshape(sample_console.ch.shape) - def print_banner(self) -> None: - # print text on top of samples - sample_console.print_box( - x=1, - y=5, - width=sample_console.width - 2, - height=sample_console.height - 1, - string="The Doryen library uses 24 bits colors, for both background and foreground.", - fg=WHITE, - bg=GREY, - bg_blend=libtcodpy.BKGND_MULTIPLY, - alignment=libtcodpy.CENTER, - ) - class OffscreenConsoleSample(Sample): + name = "Offscreen console" + def __init__(self) -> None: - self.name = "Offscreen console" self.secondary = tcod.console.Console(sample_console.width // 2, sample_console.height // 2) self.screenshot = tcod.console.Console(sample_console.width, sample_console.height) self.counter = 0.0 @@ -245,6 +243,8 @@ def on_draw(self) -> None: class LineDrawingSample(Sample): + name = "Line drawing" + FLAG_NAMES = ( "BKGND_NONE", "BKGND_SET", @@ -262,7 +262,6 @@ class LineDrawingSample(Sample): ) def __init__(self) -> None: - self.name = "Line drawing" self.mk_flag = libtcodpy.BKGND_SET self.bk_flag = libtcodpy.BKGND_SET @@ -322,6 +321,8 @@ def on_draw(self) -> None: class NoiseSample(Sample): + name = "Noise" + NOISE_OPTIONS = ( # (name, algorithm, implementation) ( "perlin noise", @@ -371,7 +372,6 @@ class NoiseSample(Sample): ) def __init__(self) -> None: - self.name = "Noise" self.func = 0 self.dx = 0.0 self.dy = 0.0 @@ -548,9 +548,9 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class FOVSample(Sample): - def __init__(self) -> None: - self.name = "Field of view" + name = "Field of view" + def __init__(self) -> None: self.player_x = 20 self.player_y = 10 self.torch = False @@ -674,10 +674,10 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class PathfindingSample(Sample): + name = "Path finding" + def __init__(self) -> None: """Initialize this sample.""" - self.name = "Path finding" - self.player_x = 20 self.player_y = 10 self.dest_x = 24 @@ -873,8 +873,9 @@ def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: class BSPSample(Sample): + name = "Bsp toolkit" + def __init__(self) -> None: - self.name = "Bsp toolkit" self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1) self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F") self.bsp_generate() @@ -956,9 +957,9 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class ImageSample(Sample): - def __init__(self) -> None: - self.name = "Image toolkit" + name = "Image toolkit" + def __init__(self) -> None: self.img = tcod.image.Image.from_file(DATA_DIR / "img/skull.png") self.img.set_key_color(BLACK) self.circle = tcod.image.Image.from_file(DATA_DIR / "img/circle.png") @@ -990,9 +991,9 @@ def on_draw(self) -> None: class MouseSample(Sample): - def __init__(self) -> None: - self.name = "Mouse support" + name = "Mouse support" + def __init__(self) -> None: self.motion = tcod.event.MouseMotion() self.mouse_left = self.mouse_middle = self.mouse_right = 0 self.log: list[str] = [] @@ -1054,9 +1055,9 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: class NameGeneratorSample(Sample): - def __init__(self) -> None: - self.name = "Name generator" + name = "Name generator" + def __init__(self) -> None: self.current_set = 0 self.delay = 0.0 self.names: list[str] = [] @@ -1160,8 +1161,7 @@ def __init__( class FastRenderSample(Sample): - def __init__(self) -> None: - self.name = "Python fast render" + name = "Python fast render" def on_enter(self) -> None: sample_console.clear() # render status message diff --git a/tcod/console.py b/tcod/console.py index 44ac3c61..b42c27a1 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -1182,7 +1182,9 @@ def print( # noqa: PLR0913 ) ) - @deprecated("Switch to using keywords and then replace with 'console.print(...)'") + @deprecated( + "Switch parameters to keywords, then replace method with 'console.print(...)', then replace 'string=' with 'text='" + ) def print_box( # noqa: PLR0913 self, x: int, @@ -1224,6 +1226,9 @@ def print_box( # noqa: PLR0913 .. versionchanged:: 13.0 `x` and `y` are now always used as an absolute position for negative values. + + .. deprecated:: 18.0 + This method was replaced by more functional :any:`Console.print` method. """ string_ = string.encode("utf-8") return int( From 0d7949178962027f881712947bea2f9cc1c13c9b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 17 Dec 2025 17:42:00 -0800 Subject: [PATCH 1039/1101] Remove order="F" from samples --- examples/samples_tcod.py | 96 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index d2d8f155..a021cd14 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -63,8 +63,8 @@ tileset: tcod.tileset.Tileset console_render: tcod.render.SDLConsoleRender # Optional SDL renderer. sample_minimap: tcod.sdl.render.Texture # Optional minimap texture. -root_console = tcod.console.Console(80, 50, order="F") -sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F") +root_console = tcod.console.Console(80, 50) +sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) cur_sample = 0 # Current selected sample. frame_times = [time.perf_counter()] frame_length = [0.0] @@ -156,9 +156,9 @@ def get_corner_colors(self) -> NDArray[np.uint8]: def interpolate_corner_colors(self) -> None: """Interpolate corner colors across the sample console.""" colors = self.get_corner_colors() - left = np.linspace(colors[0], colors[2], SAMPLE_SCREEN_HEIGHT) - right = np.linspace(colors[1], colors[3], SAMPLE_SCREEN_HEIGHT) - sample_console.bg[:] = np.linspace(left, right, SAMPLE_SCREEN_WIDTH) + top = np.linspace(colors[0], colors[1], SAMPLE_SCREEN_WIDTH) + bottom = np.linspace(colors[2], colors[3], SAMPLE_SCREEN_WIDTH) + sample_console.bg[:] = np.linspace(top, bottom, SAMPLE_SCREEN_HEIGHT) def darken_background_characters(self) -> None: """Darken background characters.""" @@ -265,12 +265,12 @@ def __init__(self) -> None: self.mk_flag = libtcodpy.BKGND_SET self.bk_flag = libtcodpy.BKGND_SET - self.bk = tcod.console.Console(sample_console.width, sample_console.height, order="F") + self.background = tcod.console.Console(sample_console.width, sample_console.height) # initialize the colored background - self.bk.bg[:, :, 0] = np.linspace(0, 255, self.bk.width)[:, np.newaxis] - self.bk.bg[:, :, 2] = np.linspace(0, 255, self.bk.height) - self.bk.bg[:, :, 1] = (self.bk.bg[:, :, 0].astype(int) + self.bk.bg[:, :, 2]) / 2 - self.bk.ch[:] = ord(" ") + self.background.bg[:, :, 0] = np.linspace(0, 255, self.background.width) + self.background.bg[:, :, 2] = np.linspace(0, 255, self.background.height)[:, np.newaxis] + self.background.bg[:, :, 1] = (self.background.bg[:, :, 0].astype(int) + self.background.bg[:, :, 2]) / 2 + self.background.ch[:] = ord(" ") def ev_keydown(self, event: tcod.event.KeyDown) -> None: if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): @@ -291,7 +291,7 @@ def on_draw(self) -> None: alpha = (1.0 + math.cos(time.time() * 2)) / 2.0 self.bk_flag = libtcodpy.BKGND_ADDALPHA(int(alpha)) - self.bk.blit(sample_console) + self.background.blit(sample_console) rect_y = int((sample_console.height - 2) * ((1.0 + math.cos(time.time())) / 2.0)) for x in range(sample_console.width): value = x * 255 // sample_console.width @@ -429,8 +429,8 @@ def on_draw(self) -> None: bg=GREY, bg_blend=libtcodpy.BKGND_MULTIPLY, ) - sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] = ( - sample_console.fg[2 : 2 + rect_w, 2 : 2 + rect_h] * GREY / 255 + sample_console.fg[2 : 2 + rect_h, 2 : 2 + rect_w] = ( + sample_console.fg[2 : 2 + rect_h, 2 : 2 + rect_w] * GREY / 255 ) for cur_func in range(len(self.NOISE_OPTIONS)): @@ -524,7 +524,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: "##############################################", ) -SAMPLE_MAP: NDArray[Any] = np.array([[ord(c) for c in line] for line in SAMPLE_MAP_]).transpose() +SAMPLE_MAP: NDArray[Any] = np.array([[ord(c) for c in line] for line in SAMPLE_MAP_]) FOV_ALGO_NAMES = ( "BASIC ", @@ -558,12 +558,12 @@ def __init__(self) -> None: self.algo_num = libtcodpy.FOV_SYMMETRIC_SHADOWCAST self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering. - map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT) + map_shape = (SAMPLE_SCREEN_HEIGHT, SAMPLE_SCREEN_WIDTH) - self.walkable: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") + self.walkable: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool) self.walkable[:] = SAMPLE_MAP[:] == ord(" ") - self.transparent: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool, order="F") + self.transparent: NDArray[np.bool_] = np.zeros(map_shape, dtype=bool) self.transparent[:] = self.walkable[:] | (SAMPLE_MAP[:] == ord("=")) # Lit background colors for the map. @@ -598,7 +598,7 @@ def on_draw(self) -> None: # Get a 2D boolean array of visible cells. fov = tcod.map.compute_fov( transparency=self.transparent, - pov=(self.player_x, self.player_y), + pov=(self.player_y, self.player_x), radius=TORCH_RADIUS if self.torch else 0, light_walls=self.light_walls, algorithm=self.algo_num, @@ -614,7 +614,7 @@ def on_draw(self) -> None: brightness = 0.2 * self.noise.get_point(torch_t + 17) # Get the squared distance using a mesh grid. - x, y = np.mgrid[:SAMPLE_SCREEN_WIDTH, :SAMPLE_SCREEN_HEIGHT] + y, x = np.mgrid[:SAMPLE_SCREEN_HEIGHT, :SAMPLE_SCREEN_WIDTH] # Center the mesh grid on the torch position. x = x.astype(np.float32) - torch_x y = y.astype(np.float32) - torch_y @@ -659,7 +659,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: } if event.sym in MOVE_KEYS: x, y = MOVE_KEYS[event.sym] - if self.walkable[self.player_x + x, self.player_y + y]: + if self.walkable[self.player_y + y, self.player_x + x]: self.player_x += x self.player_y += y elif event.sym == tcod.event.KeySym.T: @@ -684,7 +684,7 @@ def __init__(self) -> None: self.dest_y = 1 self.using_astar = True self.busy = 0.0 - self.cost = SAMPLE_MAP.T[:] == ord(" ") + self.cost = SAMPLE_MAP[:] == ord(" ") self.graph = tcod.path.SimpleGraph(cost=self.cost, cardinal=70, diagonal=99) self.pathfinder = tcod.path.Pathfinder(graph=self.graph) @@ -693,8 +693,8 @@ def __init__(self) -> None: # draw the dungeon self.background_console.rgb["fg"] = BLACK self.background_console.rgb["bg"] = DARK_GROUND - self.background_console.rgb["bg"][SAMPLE_MAP.T[:] == ord("#")] = DARK_WALL - self.background_console.rgb["ch"][SAMPLE_MAP.T[:] == ord("=")] = ord("═") + self.background_console.rgb["bg"][SAMPLE_MAP[:] == ord("#")] = DARK_WALL + self.background_console.rgb["ch"][SAMPLE_MAP[:] == ord("=")] = ord("═") def on_enter(self) -> None: """Do nothing.""" @@ -722,10 +722,10 @@ def on_draw(self) -> None: np.array(self.pathfinder.distance, copy=True, dtype=np.float32) interpolate = self.pathfinder.distance[reachable] * 0.9 / dijkstra_max_dist color_delta = (np.array(DARK_GROUND) - np.array(LIGHT_GROUND)).astype(np.float32) - sample_console.rgb.T["bg"][reachable] = np.array(LIGHT_GROUND) + interpolate[:, np.newaxis] * color_delta + sample_console.rgb["bg"][reachable] = np.array(LIGHT_GROUND) + interpolate[:, np.newaxis] * color_delta # draw the path - path = self.pathfinder.path_to((self.dest_y, self.dest_x))[1:, ::-1] + path = self.pathfinder.path_to((self.dest_y, self.dest_x))[1:] sample_console.rgb["bg"][tuple(path.T)] = LIGHT_GROUND # move the creature @@ -733,8 +733,8 @@ def on_draw(self) -> None: if self.busy <= 0.0: self.busy = 0.2 if len(path): - self.player_x = int(path.item(0, 0)) - self.player_y = int(path.item(0, 1)) + self.player_y = int(path.item(0, 0)) + self.player_x = int(path.item(0, 1)) def ev_keydown(self, event: tcod.event.KeyDown) -> None: """Handle movement and UI.""" @@ -772,46 +772,46 @@ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: # draw a vertical line -def vline(m: NDArray[np.bool_], x: int, y1: int, y2: int) -> None: +def vline(map_: NDArray[np.bool_], x: int, y1: int, y2: int) -> None: if y1 > y2: y1, y2 = y2, y1 for y in range(y1, y2 + 1): - m[x, y] = True + map_[y, x] = True # draw a vertical line up until we reach an empty space -def vline_up(m: NDArray[np.bool_], x: int, y: int) -> None: - while y >= 0 and not m[x, y]: - m[x, y] = True +def vline_up(map_: NDArray[np.bool_], x: int, y: int) -> None: + while y >= 0 and not map_[y, x]: + map_[y, x] = True y -= 1 # draw a vertical line down until we reach an empty space -def vline_down(m: NDArray[np.bool_], x: int, y: int) -> None: - while y < SAMPLE_SCREEN_HEIGHT and not m[x, y]: - m[x, y] = True +def vline_down(map_: NDArray[np.bool_], x: int, y: int) -> None: + while y < SAMPLE_SCREEN_HEIGHT and not map_[y, x]: + map_[y, x] = True y += 1 # draw a horizontal line -def hline(m: NDArray[np.bool_], x1: int, y: int, x2: int) -> None: +def hline(map_: NDArray[np.bool_], x1: int, y: int, x2: int) -> None: if x1 > x2: x1, x2 = x2, x1 for x in range(x1, x2 + 1): - m[x, y] = True + map_[y, x] = True # draw a horizontal line left until we reach an empty space -def hline_left(m: NDArray[np.bool_], x: int, y: int) -> None: - while x >= 0 and not m[x, y]: - m[x, y] = True +def hline_left(map_: NDArray[np.bool_], x: int, y: int) -> None: + while x >= 0 and not map_[y, x]: + map_[y, x] = True x -= 1 # draw a horizontal line right until we reach an empty space -def hline_right(m: NDArray[np.bool_], x: int, y: int) -> None: - while x < SAMPLE_SCREEN_WIDTH and not m[x, y]: - m[x, y] = True +def hline_right(map_: NDArray[np.bool_], x: int, y: int) -> None: + while x < SAMPLE_SCREEN_WIDTH and not map_[y, x]: + map_[y, x] = True x += 1 @@ -829,7 +829,7 @@ def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: node.y += random.randint(0, node.height - new_height) node.width, node.height = new_width, new_height # dig the room - bsp_map[node.x : node.x + node.width, node.y : node.y + node.height] = True + bsp_map[node.y : node.y + node.height, node.x : node.x + node.width] = True else: # resize the node to fit its sons left, right = node.children @@ -877,7 +877,7 @@ class BSPSample(Sample): def __init__(self) -> None: self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1) - self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT), dtype=bool, order="F") + self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_HEIGHT, SAMPLE_SCREEN_WIDTH), dtype=bool) self.bsp_generate() def bsp_generate(self) -> None: @@ -923,7 +923,7 @@ def on_draw(self) -> None: # render the level for y in range(SAMPLE_SCREEN_HEIGHT): for x in range(SAMPLE_SCREEN_WIDTH): - color = DARK_GROUND if self.bsp_map[x][y] else DARK_WALL + color = DARK_GROUND if self.bsp_map[y, x] else DARK_WALL libtcodpy.console_set_char_background(sample_console, x, y, color, libtcodpy.BKGND_SET) def ev_keydown(self, event: tcod.event.KeyDown) -> None: @@ -1274,7 +1274,7 @@ def on_draw(self) -> None: bb = bb.clip(0, 255) # fill the screen with these background colors - sample_console.bg.transpose(2, 1, 0)[...] = (rr, gg, bb) + sample_console.bg.transpose(2, 0, 1)[...] = (rr, gg, bb) ############################################# @@ -1380,7 +1380,7 @@ def redraw_display() -> None: context.sdl_renderer.draw_color = (0, 0, 0, 255) context.sdl_renderer.clear() # SDL renderer support, upload the sample console background to a minimap texture. - sample_minimap.update(sample_console.rgb.T["bg"]) + sample_minimap.update(sample_console.rgb["bg"]) # Render the root_console normally, this is the drawing step of context.present without presenting. context.sdl_renderer.copy(console_render.render(root_console)) # Render the minimap to the screen. From c102de3c775a2146b248c4b1ae759d0e5f02bae6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 17 Dec 2025 17:56:42 -0800 Subject: [PATCH 1040/1101] Clean up FastRenderSample attributes Move names for mutable variables into the class Refactor Light into a dataclass Numpy is not optional with latest tcod --- examples/samples_tcod.py | 58 +++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index a021cd14..bce9dda9 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -13,6 +13,7 @@ import sys import time import warnings +from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Any @@ -1111,9 +1112,6 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: ############################################# # python fast render sample ############################################# -numpy_available = True - -use_numpy = numpy_available # default option SCREEN_W = SAMPLE_SCREEN_WIDTH SCREEN_H = SAMPLE_SCREEN_HEIGHT HALF_W = SCREEN_W // 2 @@ -1133,36 +1131,32 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: # example: (4x3 pixels screen) # xc = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]] # noqa: ERA001 # yc = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]] # noqa: ERA001 -if numpy_available: - (xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) - # translate coordinates of all pixels to center - xc = xc - HALF_W - yc = yc - HALF_H - -noise2d = tcod.noise.Noise(2, hurst=0.5, lacunarity=2.0) -if numpy_available: # the texture starts empty - texture = np.zeros((RES_U, RES_V)) +(xc, yc) = np.meshgrid(range(SCREEN_W), range(SCREEN_H)) +# translate coordinates of all pixels to center +xc = xc - HALF_W +yc = yc - HALF_H +@dataclass(frozen=False, slots=True) class Light: - def __init__( - self, - x: float, - y: float, - z: float, - r: int, - g: int, - b: int, - strength: float, - ) -> None: - self.x, self.y, self.z = x, y, z # pos. - self.r, self.g, self.b = r, g, b # color - self.strength = strength # between 0 and 1, defines brightness + """Lighting effect entity.""" + + x: float # pos + y: float + z: float + r: int # color + g: int + b: int + strength: float # between 0 and 1, defines brightness class FastRenderSample(Sample): name = "Python fast render" + def __init__(self) -> None: + self.texture = np.zeros((RES_U, RES_V)) + self.noise2d = tcod.noise.Noise(2, hurst=0.5, lacunarity=2.0) + def on_enter(self) -> None: sample_console.clear() # render status message sample_console.print(1, SCREEN_H - 3, "Renderer: NumPy", fg=WHITE, bg=None) @@ -1178,8 +1172,6 @@ def on_enter(self) -> None: self.tex_b = 0.0 def on_draw(self) -> None: - global texture - time_delta = frame_length[-1] * SPEED # advance time self.frac_t += time_delta # increase fractional (always < 1.0) time self.abs_t += time_delta # increase absolute elapsed time @@ -1207,14 +1199,14 @@ def on_draw(self) -> None: # new pixels are based on absolute elapsed time int_abs_t = int(self.abs_t) - texture = np.roll(texture, -int_t, 1) + self.texture = np.roll(self.texture, -int_t, 1) # replace new stretch of texture with new values for v in range(RES_V - int_t, RES_V): for u in range(RES_U): tex_v = (v + int_abs_t) / float(RES_V) - texture[u, v] = libtcodpy.noise_get_fbm( - noise2d, [u / float(RES_U), tex_v], 32.0 - ) + libtcodpy.noise_get_fbm(noise2d, [1 - u / float(RES_U), tex_v], 32.0) + self.texture[u, v] = libtcodpy.noise_get_fbm( + self.noise2d, [u / float(RES_U), tex_v], 32.0 + ) + libtcodpy.noise_get_fbm(self.noise2d, [1 - u / float(RES_U), tex_v], 32.0) # squared distance from center, # clipped to sensible minimum and maximum values @@ -1229,7 +1221,7 @@ def on_draw(self) -> None: uu = np.mod(RES_U * (np.arctan2(yc, xc) / (2 * np.pi) + 0.5), RES_U) # retrieve corresponding pixels from texture - brightness = texture[uu.astype(int), vv.astype(int)] / 4.0 + 0.5 + brightness = self.texture[uu.astype(int), vv.astype(int)] / 4.0 + 0.5 # use the brightness map to compose the final color of the tunnel rr = brightness * self.tex_r @@ -1253,7 +1245,7 @@ def on_draw(self) -> None: for light in self.lights: # render lights # move light's Z coordinate with time, then project its XYZ # coordinates to screen-space - light.z -= float(time_delta) / TEX_STRETCH + light.z -= time_delta / TEX_STRETCH xl = light.x / light.z * SCREEN_H yl = light.y / light.z * SCREEN_H From cb0d7d6cdce0f534c5f195288631083c35e4be7c Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 17 Dec 2025 18:13:16 -0800 Subject: [PATCH 1041/1101] Refactor eventget.py sample to use pattern matching --- examples/eventget.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/examples/eventget.py b/examples/eventget.py index 7f4f9cb3..dd3e4058 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -8,12 +8,11 @@ import tcod.context import tcod.event import tcod.sdl.joystick -import tcod.sdl.sys -WIDTH, HEIGHT = 720, 480 +WIDTH, HEIGHT = 1280, 720 -def main() -> None: +def main() -> None: # noqa: C901, PLR0912 """Example program for tcod.event.""" event_log: list[str] = [] motion_desc = "" @@ -40,24 +39,23 @@ def main() -> None: for event in tcod.event.wait(): context.convert_event(event) # Set tile coordinates for event. print(repr(event)) - if isinstance(event, tcod.event.Quit): - raise SystemExit - if isinstance(event, tcod.event.WindowResized) and event.type == "WindowResized": - console = context.new_console() - if isinstance(event, tcod.event.ControllerDevice): - if event.type == "CONTROLLERDEVICEADDED": - controllers.add(event.controller) - elif event.type == "CONTROLLERDEVICEREMOVED": - controllers.remove(event.controller) - if isinstance(event, tcod.event.JoystickDevice): - if event.type == "JOYDEVICEADDED": - joysticks.add(event.joystick) - elif event.type == "JOYDEVICEREMOVED": - joysticks.remove(event.joystick) - if isinstance(event, tcod.event.MouseMotion): - motion_desc = str(event) - else: # Log all events other than MouseMotion. - event_log.append(str(event)) + match event: + case tcod.event.Quit(): + raise SystemExit + case tcod.event.WindowResized(type="WindowResized"): + console = context.new_console() + case tcod.event.ControllerDevice(type="CONTROLLERDEVICEADDED", controller=controller): + controllers.add(controller) + case tcod.event.ControllerDevice(type="CONTROLLERDEVICEREMOVED", controller=controller): + controllers.remove(controller) + case tcod.event.JoystickDevice(type="JOYDEVICEADDED", joystick=joystick): + joysticks.add(joystick) + case tcod.event.JoystickDevice(type="JOYDEVICEREMOVED", joystick=joystick): + joysticks.remove(joystick) + case tcod.event.MouseMotion(): + motion_desc = str(event) + case _: # Log all events other than MouseMotion. + event_log.append(repr(event)) if __name__ == "__main__": From 1b1e9f012d2f4077b778a737eccdaf35e0eed06b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 21 Dec 2025 22:13:41 -0800 Subject: [PATCH 1042/1101] Remove deprecated EventDispatch from tcod samples Make BSP variables local --- examples/samples_tcod.py | 380 ++++++++++++++++++++------------------- 1 file changed, 195 insertions(+), 185 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index bce9dda9..d8d79bea 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -34,14 +34,12 @@ import tcod.sdl.render import tcod.tileset from tcod import libtcodpy +from tcod.event import KeySym if TYPE_CHECKING: from numpy.typing import NDArray -if not sys.warnoptions: - warnings.simplefilter("default") # Show all warnings. - DATA_DIR = Path(__file__).parent / "../libtcod/data" """Path of the samples data directory.""" @@ -77,7 +75,7 @@ def _get_elapsed_time() -> float: return time.perf_counter() - START_TIME -class Sample(tcod.event.EventDispatch[None]): +class Sample: name: str = "???" def on_enter(self) -> None: @@ -86,31 +84,33 @@ def on_enter(self) -> None: def on_draw(self) -> None: pass - def ev_keydown(self, event: tcod.event.KeyDown) -> None: + def on_event(self, event: tcod.event.Event) -> None: global cur_sample - if event.sym == tcod.event.KeySym.DOWN: - cur_sample = (cur_sample + 1) % len(SAMPLES) - SAMPLES[cur_sample].on_enter() - draw_samples_menu() - elif event.sym == tcod.event.KeySym.UP: - cur_sample = (cur_sample - 1) % len(SAMPLES) - SAMPLES[cur_sample].on_enter() - draw_samples_menu() - elif event.sym == tcod.event.KeySym.RETURN and event.mod & tcod.event.Modifier.ALT: - sdl_window = context.sdl_window - if sdl_window: - sdl_window.fullscreen = not sdl_window.fullscreen - elif event.sym in (tcod.event.KeySym.PRINTSCREEN, tcod.event.KeySym.P): - print("screenshot") - if event.mod & tcod.event.Modifier.ALT: - libtcodpy.console_save_apf(root_console, "samples.apf") - print("apf") - else: - libtcodpy.sys_save_screenshot() - print("png") - elif event.sym in RENDERER_KEYS: - # Swap the active context for one with a different renderer. - init_context(RENDERER_KEYS[event.sym]) + match event: + case tcod.event.Quit() | tcod.event.KeyDown(sym=KeySym.ESCAPE): + raise SystemExit + case tcod.event.KeyDown(sym=KeySym.DOWN): + cur_sample = (cur_sample + 1) % len(SAMPLES) + SAMPLES[cur_sample].on_enter() + draw_samples_menu() + case tcod.event.KeyDown(sym=KeySym.UP): + cur_sample = (cur_sample - 1) % len(SAMPLES) + SAMPLES[cur_sample].on_enter() + draw_samples_menu() + case tcod.event.KeyDown(sym=KeySym.RETURN, mod=mod) if mod & tcod.event.Modifier.ALT: + sdl_window = context.sdl_window + if sdl_window: + sdl_window.fullscreen = not sdl_window.fullscreen + case tcod.event.KeyDown(sym=tcod.event.KeySym.PRINTSCREEN | tcod.event.KeySym.P): + print("screenshot") + if event.mod & tcod.event.Modifier.ALT: + libtcodpy.console_save_apf(root_console, "samples.apf") + print("apf") + else: + libtcodpy.sys_save_screenshot() + print("png") + case tcod.event.KeyDown(sym=sym) if sym in RENDERER_KEYS: + init_context(RENDERER_KEYS[sym]) # Swap the active context for one with a different renderer class TrueColorSample(Sample): @@ -273,13 +273,14 @@ def __init__(self) -> None: self.background.bg[:, :, 1] = (self.background.bg[:, :, 0].astype(int) + self.background.bg[:, :, 2]) / 2 self.background.ch[:] = ord(" ") - def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): - self.bk_flag += 1 - if (self.bk_flag & 0xFF) > libtcodpy.BKGND_ALPH: - self.bk_flag = libtcodpy.BKGND_NONE - else: - super().ev_keydown(event) + def on_event(self, event: tcod.event.Event) -> None: + match event: + case tcod.event.KeyDown(sym=KeySym.RETURN | KeySym.KP_ENTER): + self.bk_flag += 1 + if (self.bk_flag & 0xFF) > libtcodpy.BKGND_ALPH: + self.bk_flag = libtcodpy.BKGND_NONE + case _: + super().on_event(event) def on_draw(self) -> None: alpha = 0.0 @@ -464,34 +465,35 @@ def on_draw(self) -> None: bg=None, ) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if tcod.event.KeySym.N9 >= event.sym >= tcod.event.KeySym.N1: - self.func = event.sym - tcod.event.KeySym.N1 - self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.E: - self.hurst += 0.1 - self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.D: - self.hurst -= 0.1 - self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.R: - self.lacunarity += 0.5 - self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.F: - self.lacunarity -= 0.5 - self.noise = self.get_noise() - elif event.sym == tcod.event.KeySym.T: - self.octaves += 0.5 - self.noise.octaves = self.octaves - elif event.sym == tcod.event.KeySym.G: - self.octaves -= 0.5 - self.noise.octaves = self.octaves - elif event.sym == tcod.event.KeySym.Y: - self.zoom += 0.2 - elif event.sym == tcod.event.KeySym.H: - self.zoom -= 0.2 - else: - super().ev_keydown(event) + def on_event(self, event: tcod.event.Event) -> None: + match event: + case tcod.event.KeyDown(sym=sym) if KeySym.N9 >= sym >= KeySym.N1: + self.func = sym - tcod.event.KeySym.N1 + self.noise = self.get_noise() + case tcod.event.KeyDown(sym=KeySym.E): + self.hurst += 0.1 + self.noise = self.get_noise() + case tcod.event.KeyDown(sym=KeySym.D): + self.hurst -= 0.1 + self.noise = self.get_noise() + case tcod.event.KeyDown(sym=KeySym.R): + self.lacunarity += 0.5 + self.noise = self.get_noise() + case tcod.event.KeyDown(sym=KeySym.F): + self.lacunarity -= 0.5 + self.noise = self.get_noise() + case tcod.event.KeyDown(sym=KeySym.T): + self.octaves += 0.5 + self.noise.octaves = self.octaves + case tcod.event.KeyDown(sym=KeySym.G): + self.octaves -= 0.5 + self.noise.octaves = self.octaves + case tcod.event.KeyDown(sym=KeySym.Y): + self.zoom += 0.2 + case tcod.event.KeyDown(sym=KeySym.H): + self.zoom -= 0.2 + case _: + super().on_event(event) ############################################# @@ -645,7 +647,7 @@ def on_draw(self) -> None: default=self.dark_map_bg, ) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: + def on_event(self, event: tcod.event.Event) -> None: MOVE_KEYS = { # noqa: N806 tcod.event.KeySym.I: (0, -1), tcod.event.KeySym.J: (-1, 0), @@ -658,20 +660,21 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None: tcod.event.KeySym.KP_MINUS: -1, tcod.event.KeySym.KP_PLUS: 1, } - if event.sym in MOVE_KEYS: - x, y = MOVE_KEYS[event.sym] - if self.walkable[self.player_y + y, self.player_x + x]: - self.player_x += x - self.player_y += y - elif event.sym == tcod.event.KeySym.T: - self.torch = not self.torch - elif event.sym == tcod.event.KeySym.W: - self.light_walls = not self.light_walls - elif event.sym in FOV_SELECT_KEYS: - self.algo_num += FOV_SELECT_KEYS[event.sym] - self.algo_num %= len(FOV_ALGO_NAMES) - else: - super().ev_keydown(event) + match event: + case tcod.event.KeyDown(sym=sym) if sym in MOVE_KEYS: + x, y = MOVE_KEYS[sym] + if self.walkable[self.player_y + y, self.player_x + x]: + self.player_x += x + self.player_y += y + case tcod.event.KeyDown(sym=KeySym.T): + self.torch = not self.torch + case tcod.event.KeyDown(sym=KeySym.W): + self.light_walls = not self.light_walls + case tcod.event.KeyDown(sym=sym) if sym in FOV_SELECT_KEYS: + self.algo_num += FOV_SELECT_KEYS[sym] + self.algo_num %= len(FOV_ALGO_NAMES) + case _: + super().on_event(event) class PathfindingSample(Sample): @@ -737,39 +740,32 @@ def on_draw(self) -> None: self.player_y = int(path.item(0, 0)) self.player_x = int(path.item(0, 1)) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: + def on_event(self, event: tcod.event.Event) -> None: """Handle movement and UI.""" - if event.sym == tcod.event.KeySym.I and self.dest_y > 0: # destination move north - self.dest_y -= 1 - elif event.sym == tcod.event.KeySym.K and self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south - self.dest_y += 1 - elif event.sym == tcod.event.KeySym.J and self.dest_x > 0: # destination move west - self.dest_x -= 1 - elif event.sym == tcod.event.KeySym.L and self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east - self.dest_x += 1 - elif event.sym == tcod.event.KeySym.TAB: - self.using_astar = not self.using_astar - else: - super().ev_keydown(event) - - def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: - """Move destination via mouseover.""" - mx = event.tile.x - SAMPLE_SCREEN_X - my = event.tile.y - SAMPLE_SCREEN_Y - if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT: - self.dest_x = int(mx) - self.dest_y = int(my) + match event: + case tcod.event.KeyDown(sym=KeySym.I) if self.dest_y > 0: # destination move north + self.dest_y -= 1 + case tcod.event.KeyDown(sym=KeySym.K) if self.dest_y < SAMPLE_SCREEN_HEIGHT - 1: # destination move south + self.dest_y += 1 + case tcod.event.KeyDown(sym=KeySym.J) if self.dest_x > 0: # destination move west + self.dest_x -= 1 + case tcod.event.KeyDown(sym=KeySym.L) if self.dest_x < SAMPLE_SCREEN_WIDTH - 1: # destination move east + self.dest_x += 1 + case tcod.event.KeyDown(sym=KeySym.TAB): + self.using_astar = not self.using_astar + case tcod.event.MouseMotion(): # Move destination via mouseover + mx = event.tile.x - SAMPLE_SCREEN_X + my = event.tile.y - SAMPLE_SCREEN_Y + if 0 <= mx < SAMPLE_SCREEN_WIDTH and 0 <= my < SAMPLE_SCREEN_HEIGHT: + self.dest_x = int(mx) + self.dest_y = int(my) + case _: + super().on_event(event) ############################################# # bsp sample ############################################# -bsp_depth = 8 -bsp_min_room_size = 4 -# a room fills a random part of the node or the maximum available space ? -bsp_random_room = False -# if true, there is always a wall on north & west side of a room -bsp_room_walls = True # draw a vertical line @@ -817,7 +813,14 @@ def hline_right(map_: NDArray[np.bool_], x: int, y: int) -> None: # the class building the dungeon from the bsp nodes -def traverse_node(bsp_map: NDArray[np.bool_], node: tcod.bsp.BSP) -> None: +def traverse_node( + bsp_map: NDArray[np.bool_], + node: tcod.bsp.BSP, + *, + bsp_min_room_size: int, + bsp_random_room: bool, + bsp_room_walls: bool, +) -> None: if not node.children: # calculate the room size if bsp_room_walls: @@ -879,46 +882,58 @@ class BSPSample(Sample): def __init__(self) -> None: self.bsp = tcod.bsp.BSP(1, 1, SAMPLE_SCREEN_WIDTH - 1, SAMPLE_SCREEN_HEIGHT - 1) self.bsp_map: NDArray[np.bool_] = np.zeros((SAMPLE_SCREEN_HEIGHT, SAMPLE_SCREEN_WIDTH), dtype=bool) + + self.bsp_depth = 8 + self.bsp_min_room_size = 4 + self.bsp_random_room = False # a room fills a random part of the node or the maximum available space ? + self.bsp_room_walls = True # if true, there is always a wall on north & west side of a room + self.bsp_generate() def bsp_generate(self) -> None: self.bsp.children = () - if bsp_room_walls: + if self.bsp_room_walls: self.bsp.split_recursive( - bsp_depth, - bsp_min_room_size + 1, - bsp_min_room_size + 1, + self.bsp_depth, + self.bsp_min_room_size + 1, + self.bsp_min_room_size + 1, 1.5, 1.5, ) else: - self.bsp.split_recursive(bsp_depth, bsp_min_room_size, bsp_min_room_size, 1.5, 1.5) + self.bsp.split_recursive(self.bsp_depth, self.bsp_min_room_size, self.bsp_min_room_size, 1.5, 1.5) self.bsp_refresh() def bsp_refresh(self) -> None: self.bsp_map[...] = False for node in copy.deepcopy(self.bsp).inverted_level_order(): - traverse_node(self.bsp_map, node) + traverse_node( + self.bsp_map, + node, + bsp_min_room_size=self.bsp_min_room_size, + bsp_random_room=self.bsp_random_room, + bsp_room_walls=self.bsp_room_walls, + ) def on_draw(self) -> None: sample_console.clear() rooms = "OFF" - if bsp_random_room: + if self.bsp_random_room: rooms = "ON" sample_console.print( 1, 1, "ENTER : rebuild bsp\n" "SPACE : rebuild dungeon\n" - f"+-: bsp depth {bsp_depth}\n" - f"*/: room size {bsp_min_room_size}\n" + f"+-: bsp depth {self.bsp_depth}\n" + f"*/: room size {self.bsp_min_room_size}\n" f"1 : random room size {rooms}", fg=WHITE, bg=None, ) - if bsp_random_room: + if self.bsp_random_room: walls = "OFF" - if bsp_room_walls: + if self.bsp_room_walls: walls = "ON" sample_console.print(1, 6, f"2 : room walls {walls}", fg=WHITE, bg=None) # render the level @@ -927,34 +942,34 @@ def on_draw(self) -> None: color = DARK_GROUND if self.bsp_map[y, x] else DARK_WALL libtcodpy.console_set_char_background(sample_console, x, y, color, libtcodpy.BKGND_SET) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: - global bsp_random_room, bsp_room_walls, bsp_depth, bsp_min_room_size - if event.sym in (tcod.event.KeySym.RETURN, tcod.event.KeySym.KP_ENTER): - self.bsp_generate() - elif event.sym == tcod.event.KeySym.SPACE: - self.bsp_refresh() - elif event.sym in (tcod.event.KeySym.EQUALS, tcod.event.KeySym.KP_PLUS): - bsp_depth += 1 - self.bsp_generate() - elif event.sym in (tcod.event.KeySym.MINUS, tcod.event.KeySym.KP_MINUS): - bsp_depth = max(1, bsp_depth - 1) - self.bsp_generate() - elif event.sym in (tcod.event.KeySym.N8, tcod.event.KeySym.KP_MULTIPLY): - bsp_min_room_size += 1 - self.bsp_generate() - elif event.sym in (tcod.event.KeySym.SLASH, tcod.event.KeySym.KP_DIVIDE): - bsp_min_room_size = max(2, bsp_min_room_size - 1) - self.bsp_generate() - elif event.sym in (tcod.event.KeySym.N1, tcod.event.KeySym.KP_1): - bsp_random_room = not bsp_random_room - if not bsp_random_room: - bsp_room_walls = True - self.bsp_refresh() - elif event.sym in (tcod.event.KeySym.N2, tcod.event.KeySym.KP_2): - bsp_room_walls = not bsp_room_walls - self.bsp_refresh() - else: - super().ev_keydown(event) + def on_event(self, event: tcod.event.Event) -> None: + match event: + case tcod.event.KeyDown(sym=KeySym.RETURN | KeySym.KP_ENTER): + self.bsp_generate() + case tcod.event.KeyDown(sym=KeySym.SPACE): + self.bsp_refresh() + case tcod.event.KeyDown(sym=KeySym.EQUALS | KeySym.KP_PLUS): + self.bsp_depth += 1 + self.bsp_generate() + case tcod.event.KeyDown(sym=KeySym.MINUS | KeySym.KP_MINUS): + self.bsp_depth = max(1, self.bsp_depth - 1) + self.bsp_generate() + case tcod.event.KeyDown(sym=KeySym.N8 | KeySym.KP_MULTIPLY): + self.bsp_min_room_size += 1 + self.bsp_generate() + case tcod.event.KeyDown(sym=KeySym.SLASH | KeySym.KP_DIVIDE): + self.bsp_min_room_size = max(2, self.bsp_min_room_size - 1) + self.bsp_generate() + case tcod.event.KeyDown(sym=KeySym.N1 | KeySym.KP_1): + self.bsp_random_room = not self.bsp_random_room + if not self.bsp_random_room: + self.bsp_room_walls = True + self.bsp_refresh() + case tcod.event.KeyDown(sym=KeySym.N2 | KeySym.KP_2): + self.bsp_room_walls = not self.bsp_room_walls + self.bsp_refresh() + case _: + super().on_event(event) class ImageSample(Sample): @@ -1005,25 +1020,6 @@ def on_enter(self) -> None: tcod.sdl.mouse.warp_in_window(sdl_window, 320, 200) tcod.sdl.mouse.show(True) - def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None: - self.motion = event - - def ev_mousebuttondown(self, event: tcod.event.MouseButtonDown) -> None: - if event.button == tcod.event.BUTTON_LEFT: - self.mouse_left = True - elif event.button == tcod.event.BUTTON_MIDDLE: - self.mouse_middle = True - elif event.button == tcod.event.BUTTON_RIGHT: - self.mouse_right = True - - def ev_mousebuttonup(self, event: tcod.event.MouseButtonUp) -> None: - if event.button == tcod.event.BUTTON_LEFT: - self.mouse_left = False - elif event.button == tcod.event.BUTTON_MIDDLE: - self.mouse_middle = False - elif event.button == tcod.event.BUTTON_RIGHT: - self.mouse_right = False - def on_draw(self) -> None: sample_console.clear(bg=GREY) sample_console.print( @@ -1046,13 +1042,28 @@ def on_draw(self) -> None: bg=None, ) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == tcod.event.KeySym.N1: - tcod.sdl.mouse.show(False) - elif event.sym == tcod.event.KeySym.N2: - tcod.sdl.mouse.show(True) - else: - super().ev_keydown(event) + def on_event(self, event: tcod.event.Event) -> None: + match event: + case tcod.event.MouseMotion(): + self.motion = event + case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.LEFT): + self.mouse_left = True + case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.MIDDLE): + self.mouse_middle = True + case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.RIGHT): + self.mouse_right = True + case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT): + self.mouse_left = False + case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.MIDDLE): + self.mouse_middle = False + case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT): + self.mouse_right = False + case tcod.event.KeyDown(sym=KeySym.N1): + tcod.sdl.mouse.show(visible=False) + case tcod.event.KeyDown(sym=KeySym.N2): + tcod.sdl.mouse.show(visible=True) + case _: + super().on_event(event) class NameGeneratorSample(Sample): @@ -1097,15 +1108,16 @@ def on_draw(self) -> None: self.delay -= 0.5 self.names.append(libtcodpy.namegen_generate(self.sets[self.current_set])) - def ev_keydown(self, event: tcod.event.KeyDown) -> None: - if event.sym == tcod.event.KeySym.EQUALS: - self.current_set += 1 - self.names.append("======") - elif event.sym == tcod.event.KeySym.MINUS: - self.current_set -= 1 - self.names.append("======") - else: - super().ev_keydown(event) + def on_event(self, event: tcod.event.Event) -> None: + match event: + case tcod.event.KeyDown(sym=KeySym.EQUALS): + self.current_set += 1 + self.names.append("======") + case tcod.event.KeyDown(sym=KeySym.MINUS): + self.current_set -= 1 + self.names.append("======") + case _: + super().on_event(event) self.current_set %= len(self.sets) @@ -1416,11 +1428,7 @@ def handle_events() -> None: else: context.convert_event(event) - SAMPLES[cur_sample].dispatch(event) - if isinstance(event, tcod.event.Quit): - raise SystemExit - if isinstance(event, tcod.event.KeyDown) and event.sym == tcod.event.KeySym.ESCAPE: - raise SystemExit + SAMPLES[cur_sample].on_event(event) def draw_samples_menu() -> None: @@ -1481,6 +1489,8 @@ def draw_renderer_menu() -> None: if __name__ == "__main__": + if not sys.warnoptions: + warnings.simplefilter("default") # Show all warnings. @tcod.event.add_watch def _handle_events(event: tcod.event.Event) -> None: From 4f8125e8315ac5777752f454c82360bf31b9de55 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 Dec 2025 00:14:47 -0800 Subject: [PATCH 1043/1101] Mark Numpy type regression --- examples/samples_tcod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index d8d79bea..0afad3ba 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -644,7 +644,7 @@ def on_draw(self) -> None: sample_console.bg[...] = np.select( condlist=[fov[:, :, np.newaxis]], choicelist=[self.light_map_bg], - default=self.dark_map_bg, + default=self.dark_map_bg, # type: ignore[call-overload] # Numpy regression https://github.com/numpy/numpy/issues/30497 ) def on_event(self, event: tcod.event.Event) -> None: From 730cde733f4e32f6b6f09e678e62e6aea02cc571 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 22 Dec 2025 19:36:16 -0800 Subject: [PATCH 1044/1101] Fix ambiguous cross reference of `type` Sphinx thinks this might be one of the Event attributes when it is actually a builtin type. Being more explicit seems to work for this simple case. Also switched `__exit__` parameters to be positional. --- tcod/sdl/audio.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index a2aa62d7..0cb7a9e9 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -54,6 +54,7 @@ from tcod.sdl._internal import _check, _check_float, _check_int, _check_p if TYPE_CHECKING: + import builtins from collections.abc import Callable, Hashable, Iterable, Iterator from types import TracebackType @@ -426,9 +427,10 @@ def __enter__(self) -> Self: def __exit__( self, - type: type[BaseException] | None, # noqa: A002 - value: BaseException | None, - traceback: TracebackType | None, + _type: builtins.type[BaseException] | None, # Explicit builtins prefix to disambiguate Sphinx cross-reference + _value: BaseException | None, + _traceback: TracebackType | None, + /, ) -> None: """Close the device when exiting the context.""" self.close() From e3b03e3b5cc8f56d5452e6adf6dae9d3e3f90a1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 04:02:32 +0000 Subject: [PATCH 1045/1101] Bump the github-actions group with 2 updates Bumps the github-actions group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/download-artifact` from 6 to 7 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c97b2c06..8b1c36dd 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -66,7 +66,7 @@ jobs: run: pip install build - name: Build source distribution run: python -m build --sdist - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: sdist path: dist/tcod-*.tar.gz @@ -160,7 +160,7 @@ jobs: if: runner.os != 'Windows' run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v5 - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 if: runner.os == 'Windows' with: name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }} @@ -275,7 +275,7 @@ jobs: BUILD_DESC=${BUILD_DESC//\*} echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }} path: wheelhouse/*.whl @@ -322,7 +322,7 @@ jobs: PYTHON_DESC=${PYTHON_DESC//\*/X} echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: wheels-macos-${{ env.PYTHON_DESC }} path: wheelhouse/*.whl @@ -349,7 +349,7 @@ jobs: CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide - name: Archive wheel - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: pyodide path: wheelhouse/*.whl @@ -367,11 +367,11 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: sdist path: dist/ - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: pattern: wheels-* path: dist/ From 740c432bc24d0458e0736bf876163a7a5bade59a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 5 Jan 2026 11:34:56 -0800 Subject: [PATCH 1046/1101] Update copyright year [skip ci] --- LICENSE.txt | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index ed980457..8af4c736 100755 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2009-2025, Kyle Benesch and the python-tcod contributors. +Copyright (c) 2009-2026, Kyle Benesch and the python-tcod contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index c107d387..ebca4ee1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,7 @@ # General information about the project. project = "python-tcod" -copyright = "2009-2025, Kyle Benesch" # noqa: A001 +copyright = "2009-2026, Kyle Benesch" # noqa: A001 author = "Kyle Benesch" # The version info for the project you're documenting, acts as replacement for From a778c2a0746b1da2d6402cad355b5d388bf47704 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:18:15 +0000 Subject: [PATCH 1047/1101] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.10) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e7a67182..f04621d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.10 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] From 4d58eb4a9889f37529a45d9a88098142ca933965 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 6 Jan 2026 04:11:32 -0800 Subject: [PATCH 1048/1101] Update to libtcod 2.2.2 --- CHANGELOG.md | 8 ++++++++ libtcod | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9836d283..da9a3c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Changed + +- Update to libtcod 2.2.2 + +### Fixed + +- Mouse coordinate to tile conversions now support SDL renderer logical size and scaling. + ## [19.6.1] - 2025-12-15 ### Fixed diff --git a/libtcod b/libtcod index ca8efa70..27c2dbc9 160000 --- a/libtcod +++ b/libtcod @@ -1 +1 @@ -Subproject commit ca8efa70c50175c6d24336f9bc84cf995f4dbef4 +Subproject commit 27c2dbc9d97bacb18b9fd43a5c7f070dc34339ed From 440b7b520cc8a58fd709164b55dd31f671877a60 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 11 Jan 2026 17:13:39 -0800 Subject: [PATCH 1049/1101] Prepare 19.6.2 release. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da9a3c46..7bab34c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.6.2] - 2026-01-12 + ### Changed - Update to libtcod 2.2.2 From 63605bb11a60095d8d3fcef19ce8dc624a70e10e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 11 Jan 2026 17:21:03 -0800 Subject: [PATCH 1050/1101] Numpy type issue was resolved upstream --- examples/samples_tcod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 0afad3ba..d8d79bea 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -644,7 +644,7 @@ def on_draw(self) -> None: sample_console.bg[...] = np.select( condlist=[fov[:, :, np.newaxis]], choicelist=[self.light_map_bg], - default=self.dark_map_bg, # type: ignore[call-overload] # Numpy regression https://github.com/numpy/numpy/issues/30497 + default=self.dark_map_bg, ) def on_event(self, event: tcod.event.Event) -> None: From cfefe20f04952d2e6a091173aef16095fae3a7bb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sun, 11 Jan 2026 17:26:37 -0800 Subject: [PATCH 1051/1101] Prepare 19.6.3 release. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bab34c5..e124797c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.6.3] - 2026-01-12 + +Fix missing deployment + ## [19.6.2] - 2026-01-12 ### Changed From 407b3e2ee3b3586cb9df1da8f52f2e6503dbb882 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 12 Jan 2026 03:15:57 -0800 Subject: [PATCH 1052/1101] Update classifiers Python 3 is generally supported but specific versions are not tested Add pyproject.toml spelling --- .vscode/settings.json | 12 ++++++++++++ pyproject.toml | 9 ++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 71b8b1cf..27be3c92 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "aarch", "ADDA", "ADDALPHA", + "addopts", "addressof", "addsub", "addx", @@ -128,8 +129,10 @@ "devel", "DHLINE", "DISPLAYSWITCH", + "distutils", "dlopen", "docstrings", + "doctest", "documentclass", "Doryen", "DPAD", @@ -151,10 +154,12 @@ "errorvf", "EXCLAM", "EXSEL", + "faulthandler", "favicon", "ffade", "fgcolor", "fheight", + "filterwarnings", "Flecs", "flto", "fmean", @@ -166,6 +171,7 @@ "furo", "fwidth", "GAMECONTROLLER", + "gamedev", "gamepad", "gaxis", "gbutton", @@ -296,6 +302,7 @@ "mgrid", "milli", "minmax", + "minversion", "mipmap", "mipmaps", "MMASK", @@ -363,6 +370,7 @@ "pushdown", "pycall", "pycparser", + "pydocstyle", "pyinstaller", "pyodide", "pypa", @@ -399,6 +407,7 @@ "RMASK", "rmeta", "roguelike", + "roguelikedev", "rpath", "RRGGBB", "rtype", @@ -441,6 +450,7 @@ "stdeb", "struct", "structs", + "subclassing", "SUBP", "SYSREQ", "tablefmt", @@ -451,6 +461,8 @@ "TCODLIB", "TEEE", "TEEW", + "termbox", + "testpaths", "TEXTUREACCESS", "thirdparty", "THOUSANDSSEPARATOR", diff --git a/pyproject.toml b/pyproject.toml index ea098b4e..11ed76e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,9 +34,11 @@ dependencies = [ ] keywords = [ "roguelike", - "cffi", + "roguelikedev", + "gamedev", "Unicode", "libtcod", + "libtcodpy", "field-of-view", "pathfinding", ] @@ -46,15 +48,12 @@ classifiers = [ "Environment :: MacOS X", "Environment :: X11 Applications", "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", From 043637d8d32548272674c8496839f53305e1d341 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 12 Jan 2026 03:20:59 -0800 Subject: [PATCH 1053/1101] Remove outdated setting --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 27be3c92..8f592ed3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -542,7 +542,4 @@ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, - "cSpell.enableFiletypes": [ - "github-actions-workflow" - ] } From a0bcee845e46bd9d50ec14e76420be3535063087 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 12 Jan 2026 03:30:10 -0800 Subject: [PATCH 1054/1101] Fix classifiers The license classifier exists but is not meant to be used anymore, I'll probably make this mistake again. --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 11ed76e6..704dc0c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ classifiers = [ "Environment :: MacOS X", "Environment :: X11 Applications", "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", From 10831856044543dd769c584f4d1e737a12806906 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 13 Jan 2026 10:29:12 -0800 Subject: [PATCH 1055/1101] Clean up more warnings Mostly suppressing old unfixable API issues Minor type improvements with map copying and tile accessing --- .vscode/settings.json | 14 ++++ build_libtcod.py | 27 ++++--- build_sdl.py | 10 +-- examples/cavegen.py | 2 +- tcod/color.py | 2 + tcod/console.py | 2 +- tcod/context.py | 6 +- tcod/image.py | 2 +- tcod/libtcodpy.py | 156 ++++++++++++++++++++-------------------- tcod/map.py | 29 ++++---- tcod/noise.py | 7 +- tcod/path.py | 11 +-- tcod/sdl/_internal.py | 4 +- tcod/sdl/joystick.py | 10 +-- tcod/sdl/mouse.py | 11 +-- tcod/sdl/sys.py | 2 +- tcod/sdl/video.py | 4 +- tcod/tileset.py | 2 +- tests/conftest.py | 23 +++--- tests/test_console.py | 10 +-- tests/test_libtcodpy.py | 74 +++++++++---------- tests/test_parser.py | 26 +++---- tests/test_sdl.py | 10 +-- tests/test_sdl_audio.py | 4 +- tests/test_tcod.py | 2 +- 25 files changed, 242 insertions(+), 208 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8f592ed3..8226a81d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "aarch", "ADDA", "ADDALPHA", + "addoption", "addopts", "addressof", "addsub", @@ -146,6 +147,7 @@ "DVLINE", "elif", "Emscripten", + "EMSDK", "ENDCALL", "endianness", "epel", @@ -207,6 +209,7 @@ "IJKL", "imageio", "imread", + "includepath", "INCOL", "INPUTTYPE", "INROW", @@ -308,6 +311,7 @@ "MMASK", "modindex", "moduleauthor", + "modversion", "MOUSEBUTTONDOWN", "MOUSEBUTTONUP", "MOUSEMOTION", @@ -328,6 +332,7 @@ "neww", "noarchive", "NODISCARD", + "NOLONGLONG", "NOMESSAGE", "Nonrepresentable", "NONUSBACKSLASH", @@ -350,6 +355,7 @@ "pagerefs", "PAGEUP", "papersize", + "passthru", "PATCHLEVEL", "pathfinding", "pathlib", @@ -361,6 +367,7 @@ "PIXELFORMAT", "PLUSMINUS", "pointsize", + "popleft", "PRESENTVSYNC", "PRINTF", "printn", @@ -378,6 +385,7 @@ "pypiwin", "pypy", "pytest", + "pytestmark", "PYTHONHASHSEED", "PYTHONOPTIMIZE", "Pyup", @@ -393,6 +401,7 @@ "Redistributable", "redistributables", "repr", + "rexpaint", "rgba", "RGUI", "RHYPER", @@ -447,6 +456,8 @@ "sphinxstrong", "sphinxtitleref", "staticmethod", + "stdarg", + "stddef", "stdeb", "struct", "structs", @@ -498,6 +509,7 @@ "VERTICALBAR", "vflip", "viewcode", + "VITAFILE", "vline", "VOLUMEDOWN", "VOLUMEUP", @@ -508,6 +520,7 @@ "WAITARROW", "WASD", "waterlevel", + "WINAPI", "windowclose", "windowenter", "WINDOWEVENT", @@ -526,6 +539,7 @@ "windowshown", "windowsizechanged", "windowtakefocus", + "xcframework", "Xcursor", "xdst", "Xext", diff --git a/build_libtcod.py b/build_libtcod.py index 73d86ea3..2b9d03d6 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -11,9 +11,8 @@ import re import subprocess import sys -from collections.abc import Iterable, Iterator from pathlib import Path -from typing import Any, ClassVar +from typing import TYPE_CHECKING, Any, ClassVar, Final import attrs import pycparser # type: ignore[import-untyped] @@ -27,6 +26,9 @@ import build_sdl +if TYPE_CHECKING: + from collections.abc import Iterable, Iterator + Py_LIMITED_API = 0x03100000 HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") @@ -269,7 +271,7 @@ def find_sdl_attrs(prefix: str) -> Iterator[tuple[str, int | str | Any]]: `prefix` is used to filter out which names to copy. """ - from tcod._libtcod import lib + from tcod._libtcod import lib # noqa: PLC0415 if prefix.startswith("SDL_"): name_starts_at = 4 @@ -320,12 +322,14 @@ def parse_sdl_attrs(prefix: str, all_names: list[str] | None) -> tuple[str, str] ] +RE_CONSTANTS_ALL: Final = re.compile( + r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)", + re.DOTALL, +) + + def update_module_all(filename: Path, new_all: str) -> None: """Update the __all__ of a file with the constants from new_all.""" - RE_CONSTANTS_ALL = re.compile( - r"(.*# --- From constants.py ---).*(# --- End constants.py ---.*)", - re.DOTALL, - ) match = RE_CONSTANTS_ALL.match(filename.read_text(encoding="utf-8")) assert match, f"Can't determine __all__ subsection in {filename}!" header, footer = match.groups() @@ -346,8 +350,8 @@ def generate_enums(prefix: str) -> Iterator[str]: def write_library_constants() -> None: """Write libtcod constants into the tcod.constants module.""" - import tcod.color - from tcod._libtcod import ffi, lib + import tcod.color # noqa: PLC0415 + from tcod._libtcod import ffi, lib # noqa: PLC0415 with Path("tcod/constants.py").open("w", encoding="utf-8") as f: all_names = [] @@ -441,6 +445,8 @@ def _fix_reserved_name(name: str) -> str: @attrs.define(frozen=True) class ConvertedParam: + """Converted type parameter from C types into Python type-hints.""" + name: str = attrs.field(converter=_fix_reserved_name) hint: str original: str @@ -460,7 +466,8 @@ def _type_from_names(names: list[str]) -> str: return "Any" -def _param_as_hint(node: pycparser.c_ast.Node, default_name: str) -> ConvertedParam: +def _param_as_hint(node: pycparser.c_ast.Node, default_name: str) -> ConvertedParam: # noqa: PLR0911 + """Return a Python type-hint from a C AST node.""" original = pycparser.c_generator.CGenerator().visit(node) name: str names: list[str] diff --git a/build_sdl.py b/build_sdl.py index 2fb39579..dbd49bd5 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -139,8 +139,8 @@ def check_sdl_version() -> None: sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() except FileNotFoundError as exc: msg = ( - f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." - "\nsdl3-config must be on PATH." + f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}.\n" + "sdl3-config must be on PATH." ) raise RuntimeError(msg) from exc except subprocess.CalledProcessError as exc: @@ -223,8 +223,8 @@ def on_include_not_found(self, is_malformed: bool, is_system_include: bool, curd assert "SDL3/SDL" not in includepath, (includepath, curdir) raise pcpp.OutputDirective(pcpp.Action.IgnoreAndRemove) - def _should_track_define(self, tokens: list[Any]) -> bool: - if len(tokens) < 3: + def _should_track_define(self, tokens: list[Any]) -> bool: # noqa: PLR0911 + if len(tokens) < 3: # noqa: PLR2004 return False if tokens[0].value in IGNORE_DEFINES: return False @@ -236,7 +236,7 @@ def _should_track_define(self, tokens: list[Any]) -> bool: return False # Likely calls a private function. if tokens[1].type == "CPP_LPAREN": return False # Function-like macro. - if len(tokens) >= 4 and tokens[2].type == "CPP_INTEGER" and tokens[3].type == "CPP_DOT": + if len(tokens) >= 4 and tokens[2].type == "CPP_INTEGER" and tokens[3].type == "CPP_DOT": # noqa: PLR2004 return False # Value is a floating point number. if tokens[0].value.startswith("SDL_PR") and (tokens[0].value.endswith("32") or tokens[0].value.endswith("64")): return False # Data type for printing, which is not needed. diff --git a/examples/cavegen.py b/examples/cavegen.py index 8b95d3fc..ace6bb98 100755 --- a/examples/cavegen.py +++ b/examples/cavegen.py @@ -10,7 +10,7 @@ from typing import Any import numpy as np -import scipy.signal # type: ignore +import scipy.signal # type: ignore[import-untyped] from numpy.typing import NDArray diff --git a/tcod/color.py b/tcod/color.py index 9fc6f260..bf9dabb3 100644 --- a/tcod/color.py +++ b/tcod/color.py @@ -89,6 +89,8 @@ def __setitem__(self, index: Any, value: Any) -> None: # noqa: ANN401, D105 else: super().__setitem__(index, value) + __hash__ = None + def __eq__(self, other: object) -> bool: """Compare equality between colors. diff --git a/tcod/console.py b/tcod/console.py index b42c27a1..bfcbce00 100644 --- a/tcod/console.py +++ b/tcod/console.py @@ -8,7 +8,6 @@ from __future__ import annotations import warnings -from collections.abc import Iterable from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, overload @@ -22,6 +21,7 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Iterable from os import PathLike from numpy.typing import ArrayLike, NDArray diff --git a/tcod/context.py b/tcod/context.py index 2668a120..f152b9d0 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -29,9 +29,8 @@ import pickle import sys import warnings -from collections.abc import Iterable from pathlib import Path -from typing import Any, Literal, NoReturn, TypeVar +from typing import TYPE_CHECKING, Any, Literal, NoReturn, TypeVar from typing_extensions import Self, deprecated @@ -44,6 +43,9 @@ from tcod._internal import _check, _check_warn from tcod.cffi import ffi, lib +if TYPE_CHECKING: + from collections.abc import Iterable + __all__ = ( "RENDERER_OPENGL", "RENDERER_OPENGL2", diff --git a/tcod/image.py b/tcod/image.py index 6b4a1083..0f154450 100644 --- a/tcod/image.py +++ b/tcod/image.py @@ -71,7 +71,7 @@ def from_array(cls, array: ArrayLike) -> Image: .. versionadded:: 11.4 """ array = np.asarray(array, dtype=np.uint8) - height, width, depth = array.shape + height, width, _depth = array.shape image = cls(width, height) image_array: NDArray[np.uint8] = np.asarray(image) image_array[...] = array diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index c6b591cc..1948b550 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -6,7 +6,6 @@ import sys import threading import warnings -from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence from pathlib import Path from typing import TYPE_CHECKING, Any, Literal @@ -54,6 +53,7 @@ ) if TYPE_CHECKING: + from collections.abc import Callable, Hashable, Iterable, Iterator, Sequence from os import PathLike from numpy.typing import NDArray @@ -69,15 +69,15 @@ NOISE_DEFAULT_LACUNARITY = 2.0 -def FOV_PERMISSIVE(p: int) -> int: +def FOV_PERMISSIVE(p: int) -> int: # noqa: N802 return FOV_PERMISSIVE_0 + p -def BKGND_ALPHA(a: int) -> int: +def BKGND_ALPHA(a: int) -> int: # noqa: N802 return BKGND_ALPH | (int(a * 255) << 8) -def BKGND_ADDALPHA(a: int) -> int: +def BKGND_ADDALPHA(a: int) -> int: # noqa: N802 return BKGND_ADDA | (int(a * 255) << 8) @@ -364,14 +364,14 @@ def __init__( vk: int = 0, c: int = 0, text: str = "", - pressed: bool = False, - lalt: bool = False, - lctrl: bool = False, - lmeta: bool = False, - ralt: bool = False, - rctrl: bool = False, - rmeta: bool = False, - shift: bool = False, + pressed: bool = False, # noqa: FBT001, FBT002 + lalt: bool = False, # noqa: FBT001, FBT002 + lctrl: bool = False, # noqa: FBT001, FBT002 + lmeta: bool = False, # noqa: FBT001, FBT002 + ralt: bool = False, # noqa: FBT001, FBT002 + rctrl: bool = False, # noqa: FBT001, FBT002 + rmeta: bool = False, # noqa: FBT001, FBT002 + shift: bool = False, # noqa: FBT001, FBT002 ) -> None: if isinstance(vk, ffi.CData): self.cdata = vk @@ -424,7 +424,7 @@ def __repr__(self) -> str: "rmeta", ]: if getattr(self, attr): - params.append(f"{attr}={getattr(self, attr)!r}") + params.append(f"{attr}={getattr(self, attr)!r}") # noqa: PERF401 return "libtcodpy.Key({})".format(", ".join(params)) @property @@ -502,7 +502,7 @@ def __repr__(self) -> str: "wheel_down", ]: if getattr(self, attr): - params.append(f"{attr}={getattr(self, attr)!r}") + params.append(f"{attr}={getattr(self, attr)!r}") # noqa: PERF401 return "libtcodpy.Mouse({})".format(", ".join(params)) @property @@ -530,7 +530,7 @@ def bsp_new_with_size(x: int, y: int, w: int, h: int) -> tcod.bsp.BSP: @deprecate("Call node.split_once instead.", category=FutureWarning) -def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: +def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: # noqa: FBT001 """Deprecated function. .. deprecated:: 2.0 @@ -544,10 +544,10 @@ def bsp_split_recursive( node: tcod.bsp.BSP, randomizer: tcod.random.Random | None, nb: int, - minHSize: int, - minVSize: int, - maxHRatio: float, - maxVRatio: float, + minHSize: int, # noqa: N803 + minVSize: int, # noqa: N803 + maxHRatio: float, # noqa: N803 + maxVRatio: float, # noqa: N803 ) -> None: """Deprecated function. @@ -633,7 +633,7 @@ def bsp_find_node(node: tcod.bsp.BSP, cx: int, cy: int) -> tcod.bsp.BSP | None: def _bsp_traverse( node_iter: Iterable[tcod.bsp.BSP], callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any, + userData: Any, # noqa: N803 ) -> None: """Pack callback into a handle for use with the callback _pycall_bsp_callback.""" for node in node_iter: @@ -644,7 +644,7 @@ def _bsp_traverse( def bsp_traverse_pre_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any = 0, + userData: Any = 0, # noqa: N803 ) -> None: """Traverse this nodes hierarchy with a callback. @@ -658,7 +658,7 @@ def bsp_traverse_pre_order( def bsp_traverse_in_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any = 0, + userData: Any = 0, # noqa: N803 ) -> None: """Traverse this nodes hierarchy with a callback. @@ -672,7 +672,7 @@ def bsp_traverse_in_order( def bsp_traverse_post_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any = 0, + userData: Any = 0, # noqa: N803 ) -> None: """Traverse this nodes hierarchy with a callback. @@ -686,7 +686,7 @@ def bsp_traverse_post_order( def bsp_traverse_level_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any = 0, + userData: Any = 0, # noqa: N803 ) -> None: """Traverse this nodes hierarchy with a callback. @@ -700,7 +700,7 @@ def bsp_traverse_level_order( def bsp_traverse_inverted_level_order( node: tcod.bsp.BSP, callback: Callable[[tcod.bsp.BSP, Any], None], - userData: Any = 0, + userData: Any = 0, # noqa: N803 ) -> None: """Traverse this nodes hierarchy with a callback. @@ -792,7 +792,7 @@ def color_get_hsv(c: tuple[int, int, int]) -> tuple[float, float, float]: @pending_deprecate() -def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: +def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: # noqa: N802 """Scale a color's saturation and value. Does not return a new Color. ``c`` is modified in-place. @@ -848,10 +848,10 @@ def console_init_root( w: int, h: int, title: str | None = None, - fullscreen: bool = False, + fullscreen: bool = False, # noqa: FBT001, FBT002 renderer: int | None = None, order: Literal["C", "F"] = "C", - vsync: bool | None = None, + vsync: bool | None = None, # noqa: FBT001 ) -> tcod.console.Console: """Set up the primary display and return the root console. @@ -947,7 +947,7 @@ def console_init_root( https://python-tcod.readthedocs.io/en/latest/tcod/getting-started.html""" ) def console_set_custom_font( - fontFile: str | PathLike[str], + fontFile: str | PathLike[str], # noqa: N803 flags: int = FONT_LAYOUT_ASCII_INCOL, nb_char_horiz: int = 0, nb_char_vertic: int = 0, @@ -980,7 +980,7 @@ def console_set_custom_font( .. versionchanged:: 16.0 Added PathLike support. `fontFile` no longer takes bytes. """ - fontFile = Path(fontFile).resolve(strict=True) + fontFile = Path(fontFile).resolve(strict=True) # noqa: N806 _check(lib.TCOD_console_set_custom_font(_path_encode(fontFile), flags, nb_char_horiz, nb_char_vertic)) @@ -1017,7 +1017,7 @@ def console_get_height(con: tcod.console.Console) -> int: @deprecate("Setup fonts using the tcod.tileset module.") -def console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int) -> None: +def console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: int) -> None: # noqa: N803 """Set a character code to new coordinates on the tile-set. `asciiCode` should be any Unicode codepoint. @@ -1037,7 +1037,7 @@ def console_map_ascii_code_to_font(asciiCode: int, fontCharX: int, fontCharY: in @deprecate("Setup fonts using the tcod.tileset module.") -def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int) -> None: +def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX: int, fontCharY: int) -> None: # noqa: N803 """Remap a contiguous set of codes to a contiguous set of tiles. Both the tile-set and character codes must be contiguous to use this @@ -1061,7 +1061,7 @@ def console_map_ascii_codes_to_font(firstAsciiCode: int, nbCodes: int, fontCharX @deprecate("Setup fonts using the tcod.tileset module.") -def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: +def console_map_string_to_font(s: str, fontCharX: int, fontCharY: int) -> None: # noqa: N803 r"""Remap a string of codes to a contiguous set of tiles. Args: @@ -1093,7 +1093,7 @@ def console_is_fullscreen() -> bool: @deprecate("This function is not supported if contexts are being used.") -def console_set_fullscreen(fullscreen: bool) -> None: +def console_set_fullscreen(fullscreen: bool) -> None: # noqa: FBT001 """Change the display to be fullscreen or windowed. Args: @@ -1158,7 +1158,7 @@ def console_credits_reset() -> None: lib.TCOD_console_credits_reset() -def console_credits_render(x: int, y: int, alpha: bool) -> bool: +def console_credits_render(x: int, y: int, alpha: bool) -> bool: # noqa: FBT001 return bool(lib.TCOD_console_credits_render(x, y, alpha)) @@ -1537,7 +1537,7 @@ def console_rect( y: int, w: int, h: int, - clr: bool, + clr: bool, # noqa: FBT001 flag: int = BKGND_DEFAULT, ) -> None: """Draw a the background color on a rect optionally clearing the text. @@ -1593,7 +1593,7 @@ def console_print_frame( y: int, w: int, h: int, - clear: bool = True, + clear: bool = True, # noqa: FBT001, FBT002 flag: int = BKGND_DEFAULT, fmt: str = "", ) -> None: @@ -1683,7 +1683,7 @@ def console_get_char(con: tcod.console.Console, x: int, y: int) -> int: @deprecate("This function is not supported if contexts are being used.", category=FutureWarning) -def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: +def console_set_fade(fade: int, fadingColor: tuple[int, int, int]) -> None: # noqa: N803 """Deprecated function. .. deprecated:: 11.13 @@ -1714,7 +1714,7 @@ def console_get_fading_color() -> Color: # handling keyboard input @deprecate("Use the tcod.event.wait function to wait for events.") -def console_wait_for_keypress(flush: bool) -> Key: +def console_wait_for_keypress(flush: bool) -> Key: # noqa: FBT001 """Block until the user presses a key, then returns a new Key. Args: @@ -2119,7 +2119,7 @@ def path_new_using_function( w: int, h: int, func: Callable[[int, int, int, int, Any], float], - userData: Any = 0, + userData: Any = 0, # noqa: N803 dcost: float = 1.41, ) -> tcod.path.AStar: """Return a new AStar using the given callable function. @@ -2242,7 +2242,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: @pending_deprecate() -def path_walk(p: tcod.path.AStar, recompute: bool) -> tuple[int, int] | tuple[None, None]: +def path_walk(p: tcod.path.AStar, recompute: bool) -> tuple[int, int] | tuple[None, None]: # noqa: FBT001 """Return the next (x, y) point in a path, or (None, None) if it's empty. When ``recompute`` is True and a previously valid path reaches a point @@ -2281,7 +2281,7 @@ def dijkstra_new_using_function( w: int, h: int, func: Callable[[int, int, int, int, Any], float], - userData: Any = 0, + userData: Any = 0, # noqa: N803 dcost: float = 1.41, ) -> tcod.path.Dijkstra: return tcod.path.Dijkstra(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost) @@ -2344,7 +2344,7 @@ def dijkstra_delete(p: tcod.path.Dijkstra) -> None: """ -def _heightmap_cdata(array: NDArray[np.float32]) -> ffi.CData: +def _heightmap_cdata(array: NDArray[np.float32]) -> Any: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. @@ -2599,9 +2599,9 @@ def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: floa @pending_deprecate() def heightmap_rain_erosion( hm: NDArray[np.float32], - nbDrops: int, - erosionCoef: float, - sedimentationCoef: float, + nbDrops: int, # noqa: N803 + erosionCoef: float, # noqa: N803 + sedimentationCoef: float, # noqa: N803 rnd: tcod.random.Random | None = None, ) -> None: """Simulate the effect of rain drops on the terrain, resulting in erosion. @@ -2632,8 +2632,8 @@ def heightmap_kernel_transform( dx: Sequence[int], dy: Sequence[int], weight: Sequence[float], - minLevel: float, - maxLevel: float, + minLevel: float, # noqa: N803 + maxLevel: float, # noqa: N803 ) -> None: """Apply a generic transformation on the map, so that each resulting cell value is the weighted sum of several neighbor cells. @@ -2684,8 +2684,8 @@ def heightmap_kernel_transform( @pending_deprecate() def heightmap_add_voronoi( hm: NDArray[np.float32], - nbPoints: Any, - nbCoef: int, + nbPoints: Any, # noqa: N803 + nbCoef: int, # noqa: N803 coef: Sequence[float], rnd: tcod.random.Random | None = None, ) -> None: @@ -2702,7 +2702,7 @@ def heightmap_add_voronoi( second closest site : coef[1], ... rnd (Optional[Random]): A Random instance, or None. """ - nbPoints = len(coef) + nbPoints = len(coef) # noqa: N806 ccoef = ffi.new("float[]", coef) lib.TCOD_heightmap_add_voronoi( _heightmap_cdata(hm), @@ -2809,10 +2809,10 @@ def heightmap_dig_bezier( hm: NDArray[np.float32], px: tuple[int, int, int, int], py: tuple[int, int, int, int], - startRadius: float, - startDepth: float, - endRadius: float, - endDepth: float, + startRadius: float, # noqa: N803 + startDepth: float, # noqa: N803 + endRadius: float, # noqa: N803 + endDepth: float, # noqa: N803 ) -> None: """Carve a path along a cubic Bezier curve. @@ -2851,14 +2851,14 @@ def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: DeprecationWarning, stacklevel=2, ) - return hm[y, x] # type: ignore + return hm.item(y, x) if hm.flags["F_CONTIGUOUS"]: warnings.warn( "Get a value from this heightmap with hm[x,y]", DeprecationWarning, stacklevel=2, ) - return hm[x, y] # type: ignore + return hm.item(x, y) msg = "This array is not contiguous." raise ValueError(msg) @@ -2894,7 +2894,7 @@ def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float: @pending_deprecate() -def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> tuple[float, float, float]: +def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> tuple[float, float, float]: # noqa: N803 """Return the map normal at given coordinates. Args: @@ -3250,7 +3250,7 @@ def line_iter(xo: int, yo: int, xd: int, yd: int) -> Iterator[tuple[int, int]]: @deprecate("This function has been replaced by tcod.los.bresenham.", category=FutureWarning) -def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: +def line_where(x1: int, y1: int, x2: int, y2: int, inclusive: bool = True) -> tuple[NDArray[np.intc], NDArray[np.intc]]: # noqa: FBT001, FBT002 """Return a NumPy index array following a Bresenham line. If `inclusive` is true then the start point is included in the result. @@ -3287,12 +3287,12 @@ def map_copy(source: tcod.map.Map, dest: tcod.map.Map) -> None: array attributes manually. """ if source.width != dest.width or source.height != dest.height: - dest.__init__(source.width, source.height, source._order) # type: ignore - dest._Map__buffer[:] = source._Map__buffer[:] # type: ignore + tcod.map.Map.__init__(dest, source.width, source.height, source._order) + dest._buffer[:] = source._buffer[:] @deprecate("Set properties using the m.transparent and m.walkable arrays.", category=FutureWarning) -def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: +def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: bool) -> None: # noqa: FBT001, N803 """Set the properties of a single cell. .. note:: @@ -3305,7 +3305,7 @@ def map_set_properties(m: tcod.map.Map, x: int, y: int, isTrans: bool, isWalk: b @deprecate("Clear maps using NumPy broadcast rules instead.", category=FutureWarning) -def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: +def map_clear(m: tcod.map.Map, transparent: bool = False, walkable: bool = False) -> None: # noqa: FBT001, FBT002 """Change all map cells to a specific value. .. deprecated:: 4.5 @@ -3322,7 +3322,7 @@ def map_compute_fov( x: int, y: int, radius: int = 0, - light_walls: bool = True, + light_walls: bool = True, # noqa: FBT001, FBT002 algo: int = FOV_RESTRICTIVE, ) -> None: """Compute the field-of-view for a map instance. @@ -3378,7 +3378,7 @@ def map_delete(m: tcod.map.Map) -> None: @deprecate("Check the map.width attribute instead.", category=FutureWarning) -def map_get_width(map: tcod.map.Map) -> int: +def map_get_width(map: tcod.map.Map) -> int: # noqa: A002 """Return the width of a map. .. deprecated:: 4.5 @@ -3388,7 +3388,7 @@ def map_get_width(map: tcod.map.Map) -> int: @deprecate("Check the map.height attribute instead.", category=FutureWarning) -def map_get_height(map: tcod.map.Map) -> int: +def map_get_height(map: tcod.map.Map) -> int: # noqa: A002 """Return the height of a map. .. deprecated:: 4.5 @@ -3398,7 +3398,7 @@ def map_get_height(map: tcod.map.Map) -> int: @deprecate("Use `tcod.sdl.mouse.show(visible)` instead.", category=FutureWarning) -def mouse_show_cursor(visible: bool) -> None: +def mouse_show_cursor(visible: bool) -> None: # noqa: FBT001 """Change the visibility of the mouse cursor. .. deprecated:: 16.0 @@ -3434,12 +3434,12 @@ def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | No @pending_deprecate() def namegen_generate(name: str) -> str: - return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) + return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) # noqa: FBT003 @pending_deprecate() def namegen_generate_custom(name: str, rule: str) -> str: - return _unpack_char_p(lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False)) + return _unpack_char_p(lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False)) # noqa: FBT003 @pending_deprecate() @@ -3618,7 +3618,7 @@ def _pycall_parser_new_flag(name: str) -> Any: @ffi.def_extern() # type: ignore[untyped-decorator] -def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: +def _pycall_parser_new_property(propname: Any, type: Any, value: Any) -> Any: # noqa: A002 return _parser_listener.new_property(_unpack_char_p(propname), type, _unpack_union(type, value)) @@ -3706,7 +3706,7 @@ def parser_get_dice_property(parser: Any, name: str) -> Dice: @deprecate("Parser functions have been deprecated.") -def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: +def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: # noqa: A002 c_list = lib.TCOD_parser_get_list_property(parser, _bytes(name), type) return _convert_TCODList(c_list, type) @@ -3909,19 +3909,19 @@ def struct_add_flag(struct: Any, name: str) -> None: @deprecate("This function is deprecated.") -def struct_add_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: +def struct_add_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: # noqa: FBT001 lib.TCOD_struct_add_property(struct, _bytes(name), typ, mandatory) @deprecate("This function is deprecated.") -def struct_add_value_list(struct: Any, name: str, value_list: Iterable[str], mandatory: bool) -> None: +def struct_add_value_list(struct: Any, name: str, value_list: Iterable[str], mandatory: bool) -> None: # noqa: FBT001 c_strings = [ffi.new("char[]", value.encode("utf-8")) for value in value_list] c_value_list = ffi.new("char*[]", c_strings) lib.TCOD_struct_add_value_list(struct, name, c_value_list, mandatory) @deprecate("This function is deprecated.") -def struct_add_list_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: +def struct_add_list_property(struct: Any, name: str, typ: int, mandatory: bool) -> None: # noqa: FBT001 lib.TCOD_struct_add_list_property(struct, _bytes(name), typ, mandatory) @@ -4134,7 +4134,7 @@ def sys_get_char_size() -> tuple[int, int]: # update font bitmap @deprecate("This function is not supported if contexts are being used.") def sys_update_char( - asciiCode: int, + asciiCode: int, # noqa: N803 fontx: int, fonty: int, img: tcod.image.Image, @@ -4164,7 +4164,7 @@ def sys_update_char( @deprecate("This function is not supported if contexts are being used.") -def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: +def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: # noqa: N802 """Register a custom rendering function with libtcod. Note: @@ -4183,7 +4183,7 @@ def sys_register_SDL_renderer(callback: Callable[[Any], None]) -> None: """ with _PropagateException() as propagate: - @ffi.def_extern(onerror=propagate) # type: ignore + @ffi.def_extern(onerror=propagate) # type: ignore[untyped-decorator] def _pycall_sdl_hook(sdl_surface: Any) -> None: callback(sdl_surface) @@ -4208,7 +4208,7 @@ def sys_check_for_event(mask: int, k: Key | None, m: Mouse | None) -> int: @deprecate("Use tcod.event.wait to wait for events.") -def sys_wait_for_event(mask: int, k: Key | None, m: Mouse | None, flush: bool) -> int: +def sys_wait_for_event(mask: int, k: Key | None, m: Mouse | None, flush: bool) -> int: # noqa: FBT001 """Wait for an event then return. If flush is True then the buffer will be cleared before waiting. Otherwise diff --git a/tcod/map.py b/tcod/map.py index d3ec5015..5054226b 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -82,9 +82,9 @@ def __init__( """Initialize the map.""" self.width = width self.height = height - self._order = tcod._internal.verify_order(order) + self._order: Literal["C", "F"] = tcod._internal.verify_order(order) - self.__buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_) + self._buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_) self.map_c = self.__as_cdata() def __as_cdata(self) -> Any: # noqa: ANN401 @@ -94,23 +94,23 @@ def __as_cdata(self) -> Any: # noqa: ANN401 self.width, self.height, self.width * self.height, - ffi.from_buffer("struct TCOD_MapCell*", self.__buffer), + ffi.from_buffer("struct TCOD_MapCell*", self._buffer), ), ) @property def transparent(self) -> NDArray[np.bool_]: - buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 0] + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property def walkable(self) -> NDArray[np.bool_]: - buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 1] + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property def fov(self) -> NDArray[np.bool_]: - buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2] + buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 2] return buffer.T if self._order == "F" else buffer def compute_fov( @@ -118,7 +118,7 @@ def compute_fov( x: int, y: int, radius: int = 0, - light_walls: bool = True, + light_walls: bool = True, # noqa: FBT001, FBT002 algorithm: int = tcod.constants.FOV_RESTRICTIVE, ) -> None: """Compute a field-of-view on the current instance. @@ -146,12 +146,13 @@ def compute_fov( lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm) def __setstate__(self, state: dict[str, Any]) -> None: - if "_Map__buffer" not in state: # deprecated - # remove this check on major version update - self.__buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_) - self.__buffer[:, :, 0] = state["buffer"] & 0x01 - self.__buffer[:, :, 1] = state["buffer"] & 0x02 - self.__buffer[:, :, 2] = state["buffer"] & 0x04 + if "_Map__buffer" in state: # Deprecated since 19.6 + state["_buffer"] = state.pop("_Map__buffer") + if "buffer" in state: # Deprecated + self._buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_) + self._buffer[:, :, 0] = state["buffer"] & 0x01 + self._buffer[:, :, 1] = state["buffer"] & 0x02 + self._buffer[:, :, 2] = state["buffer"] & 0x04 del state["buffer"] state["_order"] = "F" self.__dict__.update(state) @@ -167,7 +168,7 @@ def compute_fov( transparency: ArrayLike, pov: tuple[int, int], radius: int = 0, - light_walls: bool = True, + light_walls: bool = True, # noqa: FBT001, FBT002 algorithm: int = tcod.constants.FOV_RESTRICTIVE, ) -> NDArray[np.bool_]: """Return a boolean mask of the area covered by a field-of-view. diff --git a/tcod/noise.py b/tcod/noise.py index 533df338..e1295037 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -36,7 +36,6 @@ import enum import warnings -from collections.abc import Sequence from typing import TYPE_CHECKING, Any, Literal import numpy as np @@ -46,6 +45,8 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Sequence + from numpy.typing import ArrayLike, NDArray @@ -350,9 +351,9 @@ def __getstate__(self) -> dict[str, Any]: self.get_point() self.algorithm = saved_algo - waveletTileData = None + waveletTileData = None # noqa: N806 if self.noise_c.waveletTileData != ffi.NULL: - waveletTileData = list(self.noise_c.waveletTileData[0 : 32 * 32 * 32]) + waveletTileData = list(self.noise_c.waveletTileData[0 : 32 * 32 * 32]) # noqa: N806 state["_waveletTileData"] = waveletTileData state["noise_c"] = { diff --git a/tcod/path.py b/tcod/path.py index 9d759371..d68fa8e1 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -21,7 +21,6 @@ import functools import itertools import warnings -from collections.abc import Callable, Sequence from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np @@ -32,6 +31,8 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from numpy.typing import ArrayLike, DTypeLike, NDArray @@ -148,7 +149,7 @@ def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: msg = f"dtype must be one of {self._C_ARRAY_CALLBACKS.keys()!r}, dtype is {self.dtype.type!r}" raise ValueError(msg) - array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type] + _array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type] userdata = ffi.new( "struct PathCostArray*", (ffi.cast("char*", self.ctypes.data), self.strides), @@ -525,8 +526,8 @@ def _compile_bool_edges(edge_map: ArrayLike) -> tuple[Any, int]: def hillclimb2d( distance: ArrayLike, start: tuple[int, int], - cardinal: bool | None = None, - diagonal: bool | None = None, + cardinal: bool | None = None, # noqa: FBT001 + diagonal: bool | None = None, # noqa: FBT001 *, edge_map: ArrayLike | None = None, ) -> NDArray[np.intc]: @@ -998,7 +999,7 @@ def _compile_rules(self) -> Any: # noqa: ANN401 def _resolve(self, pathfinder: Pathfinder) -> None: """Run the pathfinding algorithm for this graph.""" - rules, keep_alive = self._compile_rules() + rules, _keep_alive = self._compile_rules() _check( lib.path_compute( pathfinder._frontier_p, diff --git a/tcod/sdl/_internal.py b/tcod/sdl/_internal.py index 4783d551..db67fa2e 100644 --- a/tcod/sdl/_internal.py +++ b/tcod/sdl/_internal.py @@ -4,7 +4,6 @@ import logging import sys -from collections.abc import Callable from dataclasses import dataclass from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar, overload, runtime_checkable @@ -13,6 +12,7 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Callable from types import TracebackType T = TypeVar("T") @@ -161,7 +161,7 @@ def _get_error() -> str: return str(ffi.string(lib.SDL_GetError()), encoding="utf-8") -def _check(result: bool, /) -> bool: +def _check(result: bool, /) -> bool: # noqa: FBT001 """Check if an SDL function returned without errors, and raise an exception if it did.""" if not result: raise RuntimeError(_get_error()) diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index b658c510..e7e1bec0 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -335,10 +335,10 @@ def _touchpad(self) -> bool: def init() -> None: """Initialize SDL's joystick and game controller subsystems.""" - CONTROLLER_SYSTEMS = tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER - if tcod.sdl.sys.Subsystem(lib.SDL_WasInit(CONTROLLER_SYSTEMS)) == CONTROLLER_SYSTEMS: + controller_systems = tcod.sdl.sys.Subsystem.JOYSTICK | tcod.sdl.sys.Subsystem.GAMECONTROLLER + if tcod.sdl.sys.Subsystem(lib.SDL_WasInit(controller_systems)) == controller_systems: return # Already initialized - tcod.sdl.sys.init(CONTROLLER_SYSTEMS) + tcod.sdl.sys.init(controller_systems) def _get_number() -> int: @@ -371,7 +371,7 @@ def _get_all() -> list[Joystick | GameController]: return [GameController._open(i) if lib.SDL_IsGamepad(i) else Joystick._open(i) for i in range(_get_number())] -def joystick_event_state(new_state: bool | None = None) -> bool: +def joystick_event_state(new_state: bool | None = None) -> bool: # noqa: FBT001 """Check or set joystick event polling. .. seealso:: @@ -384,7 +384,7 @@ def joystick_event_state(new_state: bool | None = None) -> bool: return lib.SDL_JoystickEventsEnabled() -def controller_event_state(new_state: bool | None = None) -> bool: +def controller_event_state(new_state: bool | None = None) -> bool: # noqa: FBT001 """Check or set game controller event polling. .. seealso:: diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 9d9227f7..1e0c1438 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -39,8 +39,11 @@ def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 def __eq__(self, other: object) -> bool: return bool(self.p == getattr(other, "p", None)) + def __hash__(self) -> int: + return hash(self.p) + @classmethod - def _claim(cls, sdl_cursor_p: Any) -> Cursor: + def _claim(cls, sdl_cursor_p: Any) -> Cursor: # noqa: ANN401 """Verify and wrap this pointer in a garbage collector before returning a Cursor.""" return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_DestroyCursor)) @@ -149,7 +152,7 @@ def get_cursor() -> Cursor | None: return Cursor(cursor_p) if cursor_p else None -def capture(enable: bool) -> None: +def capture(enable: bool) -> None: # noqa: FBT001 """Enable or disable mouse capture to track the mouse outside of a window. It is highly recommended to read the related remarks section in the SDL docs before using this. @@ -176,7 +179,7 @@ def capture(enable: bool) -> None: @deprecated("Set 'Window.relative_mouse_mode = value' instead.") -def set_relative_mode(enable: bool) -> None: +def set_relative_mode(enable: bool) -> None: # noqa: FBT001 """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion. .. seealso:: @@ -248,7 +251,7 @@ def warp_in_window(window: tcod.sdl.video.Window, x: int, y: int) -> None: lib.SDL_WarpMouseInWindow(window.p, x, y) -def show(visible: bool | None = None) -> bool: +def show(visible: bool | None = None) -> bool: # noqa: FBT001 """Optionally show or hide the mouse cursor then return the state of the cursor. Args: diff --git a/tcod/sdl/sys.py b/tcod/sdl/sys.py index 869d6448..c4056c69 100644 --- a/tcod/sdl/sys.py +++ b/tcod/sdl/sys.py @@ -24,7 +24,7 @@ def init(flags: int) -> None: _check(lib.SDL_InitSubSystem(flags)) -def quit(flags: int | None = None) -> None: +def quit(flags: int | None = None) -> None: # noqa: A001 if flags is None: lib.SDL_Quit() return diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index 4895e0f7..a7d6c330 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -157,7 +157,7 @@ def __init__(self, pixels: ArrayLike) -> None: lib.SDL_CreateSurfaceFrom( self._array.shape[1], self._array.shape[0], - lib.SDL_PIXELFORMAT_RGBA32 if self._array.shape[2] == 4 else lib.SDL_PIXELFORMAT_RGB24, + lib.SDL_PIXELFORMAT_RGBA32 if self._array.shape[2] == 4 else lib.SDL_PIXELFORMAT_RGB24, # noqa: PLR2004 ffi.from_buffer("void*", self._array), self._array.strides[0], ) @@ -555,7 +555,7 @@ def get_grabbed_window() -> Window | None: return Window(sdl_window_p) if sdl_window_p else None -def screen_saver_allowed(allow: bool | None = None) -> bool: +def screen_saver_allowed(allow: bool | None = None) -> bool: # noqa: FBT001 """Allow or prevent a screen saver from being displayed and return the current allowed status. If `allow` is `None` then only the current state is returned. diff --git a/tcod/tileset.py b/tcod/tileset.py index 6362c93d..4b3935ac 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -16,7 +16,6 @@ from __future__ import annotations import itertools -from collections.abc import Iterable from pathlib import Path from typing import TYPE_CHECKING, Any @@ -27,6 +26,7 @@ from tcod.cffi import ffi, lib if TYPE_CHECKING: + from collections.abc import Iterable from os import PathLike from numpy.typing import ArrayLike, NDArray diff --git a/tests/conftest.py b/tests/conftest.py index 79891a38..580f16bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,13 +4,16 @@ import random import warnings -from collections.abc import Callable, Iterator +from typing import TYPE_CHECKING import pytest import tcod from tcod import libtcodpy +if TYPE_CHECKING: + from collections.abc import Callable, Iterator + def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") @@ -29,15 +32,15 @@ def uses_window(request: pytest.FixtureRequest) -> Iterator[None]: def session_console(request: pytest.FixtureRequest) -> Iterator[tcod.console.Console]: if request.config.getoption("--no-window"): pytest.skip("This test needs a rendering context.") - FONT_FILE = "libtcod/terminal.png" - WIDTH = 12 - HEIGHT = 10 - TITLE = "libtcod-cffi tests" - FULLSCREEN = False - RENDERER = getattr(libtcodpy, "RENDERER_" + request.param) - - libtcodpy.console_set_custom_font(FONT_FILE) - with libtcodpy.console_init_root(WIDTH, HEIGHT, TITLE, FULLSCREEN, RENDERER, vsync=False) as con: + font_file = "libtcod/terminal.png" + width = 12 + height = 10 + title = "libtcod-cffi tests" + fullscreen = False + renderer = getattr(libtcodpy, "RENDERER_" + request.param) + + libtcodpy.console_set_custom_font(font_file) + with libtcodpy.console_init_root(width, height, title, fullscreen, renderer, vsync=False) as con: yield con diff --git a/tests/test_console.py b/tests/test_console.py index 18668ba6..873a7691 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -12,9 +12,9 @@ def test_array_read_write() -> None: console = tcod.console.Console(width=12, height=10) - FG = (255, 254, 253) - BG = (1, 2, 3) - CH = ord("&") + FG = (255, 254, 253) # noqa: N806 + BG = (1, 2, 3) # noqa: N806 + CH = ord("&") # noqa: N806 with pytest.warns(): tcod.console_put_char_ex(console, 0, 0, CH, FG, BG) assert console.ch[0, 0] == CH @@ -74,7 +74,7 @@ def test_console_methods() -> None: console.print_rect(0, 0, 2, 8, "a b c d e f") console.get_height_rect(0, 0, 2, 8, "a b c d e f") with pytest.deprecated_call(): - console.rect(0, 0, 2, 2, True) + console.rect(0, 0, 2, 2, True) # noqa: FBT003 with pytest.deprecated_call(): console.hline(0, 1, 10) with pytest.deprecated_call(): @@ -107,7 +107,7 @@ def test_console_pickle_fortran() -> None: def test_console_repr() -> None: - from numpy import array # noqa: F401 # Used for eval + from numpy import array # Used for eval # noqa: F401, PLC0415 eval(repr(tcod.console.Console(10, 2))) # noqa: S307 diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 61895c60..ca1ab1a2 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -23,12 +23,12 @@ def test_console_behavior(console: tcod.console.Console) -> None: @pytest.mark.skip("takes too long") @pytest.mark.filterwarnings("ignore") -def test_credits_long(console: tcod.console.Console) -> None: +def test_credits_long(console: tcod.console.Console) -> None: # noqa: ARG001 libtcodpy.console_credits() -def test_credits(console: tcod.console.Console) -> None: - libtcodpy.console_credits_render(0, 0, True) +def test_credits(console: tcod.console.Console) -> None: # noqa: ARG001 + libtcodpy.console_credits_render(0, 0, True) # noqa: FBT003 libtcodpy.console_credits_reset() @@ -112,7 +112,7 @@ def test_console_printing(console: tcod.console.Console, fg: tuple[int, int, int @pytest.mark.filterwarnings("ignore") def test_console_rect(console: tcod.console.Console) -> None: - libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) + libtcodpy.console_rect(console, 0, 0, 4, 4, False, libtcodpy.BKGND_SET) # noqa: FBT003 @pytest.mark.filterwarnings("ignore") @@ -127,13 +127,13 @@ def test_console_print_frame(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_console_fade(console: tcod.console.Console) -> None: +def test_console_fade(console: tcod.console.Console) -> None: # noqa: ARG001 libtcodpy.console_set_fade(0, (0, 0, 0)) libtcodpy.console_get_fade() libtcodpy.console_get_fading_color() -def assertConsolesEqual(a: tcod.console.Console, b: tcod.console.Console) -> bool: +def assert_consoles_equal(a: tcod.console.Console, b: tcod.console.Console, /) -> bool: return bool((a.fg[:] == b.fg[:]).all() and (a.bg[:] == b.bg[:]).all() and (a.ch[:] == b.ch[:]).all()) @@ -141,7 +141,7 @@ def assertConsolesEqual(a: tcod.console.Console, b: tcod.console.Console) -> boo def test_console_blit(console: tcod.console.Console, offscreen: tcod.console.Console) -> None: libtcodpy.console_print(offscreen, 0, 0, "test") libtcodpy.console_blit(offscreen, 0, 0, 0, 0, console, 0, 0, 1, 1) - assertConsolesEqual(console, offscreen) + assert_consoles_equal(console, offscreen) libtcodpy.console_set_key_color(offscreen, (0, 0, 0)) @@ -152,7 +152,7 @@ def test_console_asc_read_write(console: tcod.console.Console, offscreen: tcod.c asc_file = tmp_path / "test.asc" assert libtcodpy.console_save_asc(console, asc_file) assert libtcodpy.console_load_asc(offscreen, asc_file) - assertConsolesEqual(console, offscreen) + assert_consoles_equal(console, offscreen) @pytest.mark.filterwarnings("ignore") @@ -162,11 +162,11 @@ def test_console_apf_read_write(console: tcod.console.Console, offscreen: tcod.c apf_file = tmp_path / "test.apf" assert libtcodpy.console_save_apf(console, apf_file) assert libtcodpy.console_load_apf(offscreen, apf_file) - assertConsolesEqual(console, offscreen) + assert_consoles_equal(console, offscreen) @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_load_test_file(console: tcod.console.Console) -> None: +def test_console_rexpaint_load_test_file(console: tcod.console.Console) -> None: # noqa: ARG001 xp_console = libtcodpy.console_from_xp("libtcod/data/rexpaint/test.xp") assert xp_console assert libtcodpy.console_get_char(xp_console, 0, 0) == ord("T") @@ -190,13 +190,13 @@ def test_console_rexpaint_save_load( assert libtcodpy.console_save_xp(console, xp_file, 1) xp_console = libtcodpy.console_from_xp(xp_file) assert xp_console - assertConsolesEqual(console, xp_console) - assert libtcodpy.console_load_xp(None, xp_file) # type: ignore - assertConsolesEqual(console, xp_console) + assert_consoles_equal(console, xp_console) + assert libtcodpy.console_load_xp(None, xp_file) # type: ignore[arg-type] + assert_consoles_equal(console, xp_console) @pytest.mark.filterwarnings("ignore") -def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path: Path) -> None: +def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path: Path) -> None: # noqa: ARG001 con1 = libtcodpy.console_new(8, 2) con2 = libtcodpy.console_new(8, 2) libtcodpy.console_print(con1, 0, 0, "hello") @@ -206,18 +206,18 @@ def test_console_rexpaint_list_save_load(console: tcod.console.Console, tmp_path loaded_consoles = libtcodpy.console_list_load_xp(xp_file) assert loaded_consoles for a, b in zip([con1, con2], loaded_consoles, strict=True): - assertConsolesEqual(a, b) + assert_consoles_equal(a, b) libtcodpy.console_delete(a) libtcodpy.console_delete(b) @pytest.mark.filterwarnings("ignore") -def test_console_fullscreen(console: tcod.console.Console) -> None: - libtcodpy.console_set_fullscreen(False) +def test_console_fullscreen(console: tcod.console.Console) -> None: # noqa: ARG001 + libtcodpy.console_set_fullscreen(False) # noqa: FBT003 @pytest.mark.filterwarnings("ignore") -def test_console_key_input(console: tcod.console.Console) -> None: +def test_console_key_input(console: tcod.console.Console) -> None: # noqa: ARG001 libtcodpy.console_check_for_keypress() libtcodpy.console_is_key_pressed(libtcodpy.KEY_ENTER) @@ -299,15 +299,15 @@ def test_console_buffer_error(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_console_font_mapping(console: tcod.console.Console) -> None: +def test_console_font_mapping(console: tcod.console.Console) -> None: # noqa: ARG001 libtcodpy.console_map_ascii_code_to_font(ord("@"), 1, 1) libtcodpy.console_map_ascii_codes_to_font(ord("@"), 1, 0, 0) libtcodpy.console_map_string_to_font("@", 0, 0) @pytest.mark.filterwarnings("ignore") -def test_mouse(console: tcod.console.Console) -> None: - libtcodpy.mouse_show_cursor(True) +def test_mouse(console: tcod.console.Console) -> None: # noqa: ARG001 + libtcodpy.mouse_show_cursor(True) # noqa: FBT003 libtcodpy.mouse_is_cursor_visible() mouse = libtcodpy.mouse_get_status() repr(mouse) @@ -315,7 +315,7 @@ def test_mouse(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_sys_time(console: tcod.console.Console) -> None: +def test_sys_time(console: tcod.console.Console) -> None: # noqa: ARG001 libtcodpy.sys_set_fps(0) libtcodpy.sys_get_fps() libtcodpy.sys_get_last_frame_length() @@ -325,18 +325,18 @@ def test_sys_time(console: tcod.console.Console) -> None: @pytest.mark.filterwarnings("ignore") -def test_sys_screenshot(console: tcod.console.Console, tmp_path: Path) -> None: +def test_sys_screenshot(console: tcod.console.Console, tmp_path: Path) -> None: # noqa: ARG001 libtcodpy.sys_save_screenshot(tmp_path / "test.png") @pytest.mark.filterwarnings("ignore") -def test_sys_custom_render(console: tcod.console.Console) -> None: +def test_sys_custom_render(console: tcod.console.Console) -> None: # noqa: ARG001 if libtcodpy.sys_get_renderer() != libtcodpy.RENDERER_SDL: pytest.xfail(reason="Only supports SDL") escape = [] - def sdl_callback(sdl_surface: object) -> None: + def sdl_callback(_sdl_surface: object) -> None: escape.append(True) libtcodpy.sys_register_SDL_renderer(sdl_callback) @@ -376,7 +376,7 @@ def test_image(console: tcod.console.Console, tmp_path: Path) -> None: @pytest.mark.parametrize("sample", ["@", "\u2603"]) # Unicode snowman @pytest.mark.xfail(reason="Unreliable") @pytest.mark.filterwarnings("ignore") -def test_clipboard(console: tcod.console.Console, sample: str) -> None: +def test_clipboard(console: tcod.console.Console, sample: str) -> None: # noqa: ARG001 saved = libtcodpy.sys_clipboard_get() try: libtcodpy.sys_clipboard_set(sample) @@ -457,9 +457,9 @@ def test_bsp() -> None: assert libtcodpy.bsp_find_node(bsp, 1, 1) == bsp assert not libtcodpy.bsp_find_node(bsp, -1, -1) - libtcodpy.bsp_split_once(bsp, False, 4) + libtcodpy.bsp_split_once(bsp, False, 4) # noqa: FBT003 repr(bsp) # test __repr__ with parent - libtcodpy.bsp_split_once(bsp, True, 4) + libtcodpy.bsp_split_once(bsp, True, 4) # noqa: FBT003 repr(bsp) # cover functions on parent @@ -473,7 +473,7 @@ def test_bsp() -> None: libtcodpy.bsp_split_recursive(bsp, None, 4, 2, 2, 1.0, 1.0) # cover bsp_traverse - def traverse(node: tcod.bsp.BSP, user_data: object) -> None: + def traverse(_node: tcod.bsp.BSP, _user_data: object) -> None: return None libtcodpy.bsp_traverse_pre_order(bsp, traverse) @@ -492,13 +492,13 @@ def traverse(node: tcod.bsp.BSP, user_data: object) -> None: @pytest.mark.filterwarnings("ignore") def test_map() -> None: - WIDTH, HEIGHT = 13, 17 - map = libtcodpy.map_new(WIDTH, HEIGHT) + WIDTH, HEIGHT = 13, 17 # noqa: N806 + map = libtcodpy.map_new(WIDTH, HEIGHT) # noqa: A001 assert libtcodpy.map_get_width(map) == WIDTH assert libtcodpy.map_get_height(map) == HEIGHT libtcodpy.map_copy(map, map) libtcodpy.map_clear(map) - libtcodpy.map_set_properties(map, 0, 0, True, True) + libtcodpy.map_set_properties(map, 0, 0, True, True) # noqa: FBT003 assert libtcodpy.map_is_transparent(map, 0, 0) assert libtcodpy.map_is_walkable(map, 0, 0) libtcodpy.map_is_in_fov(map, 0, 0) @@ -522,14 +522,14 @@ def test_color() -> None: color_b = libtcodpy.Color(255, 255, 255) assert color_a != color_b - color = libtcodpy.color_lerp(color_a, color_b, 0.5) # type: ignore + color = libtcodpy.color_lerp(color_a, color_b, 0.5) # type: ignore[arg-type] libtcodpy.color_set_hsv(color, 0, 0, 0) - libtcodpy.color_get_hsv(color) # type: ignore + libtcodpy.color_get_hsv(color) # type: ignore[arg-type] libtcodpy.color_scale_HSV(color, 0, 0) def test_color_repr() -> None: - Color = libtcodpy.Color + Color = libtcodpy.Color # noqa: N806 col = Color(0, 1, 2) assert eval(repr(col)) == col # noqa: S307 @@ -668,7 +668,7 @@ def map_() -> Iterator[tcod.map.Map]: @pytest.fixture def path_callback(map_: tcod.map.Map) -> Callable[[int, int, int, int, None], bool]: - def callback(ox: int, oy: int, dx: int, dy: int, user_data: None) -> bool: + def callback(_ox: int, _oy: int, dx: int, dy: int, _user_data: None) -> bool: return bool(map_.walkable[dy, dx]) return callback @@ -703,7 +703,7 @@ def test_astar(map_: tcod.map.Map) -> None: x, y = libtcodpy.path_get(astar, i) while (x, y) != (None, None): - x, y = libtcodpy.path_walk(astar, False) + x, y = libtcodpy.path_walk(astar, False) # noqa: FBT003 libtcodpy.path_delete(astar) diff --git a/tests/test_parser.py b/tests/test_parser.py index 73c54b63..67cd10e3 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -13,19 +13,19 @@ def test_parser() -> None: print("***** File Parser test *****") parser = libtcod.parser_new() struct = libtcod.parser_new_struct(parser, "myStruct") - libtcod.struct_add_property(struct, "bool_field", libtcod.TYPE_BOOL, True) - libtcod.struct_add_property(struct, "char_field", libtcod.TYPE_CHAR, True) - libtcod.struct_add_property(struct, "int_field", libtcod.TYPE_INT, True) - libtcod.struct_add_property(struct, "float_field", libtcod.TYPE_FLOAT, True) - libtcod.struct_add_property(struct, "color_field", libtcod.TYPE_COLOR, True) - libtcod.struct_add_property(struct, "dice_field", libtcod.TYPE_DICE, True) - libtcod.struct_add_property(struct, "string_field", libtcod.TYPE_STRING, True) - libtcod.struct_add_list_property(struct, "bool_list", libtcod.TYPE_BOOL, True) - libtcod.struct_add_list_property(struct, "char_list", libtcod.TYPE_CHAR, True) - libtcod.struct_add_list_property(struct, "integer_list", libtcod.TYPE_INT, True) - libtcod.struct_add_list_property(struct, "float_list", libtcod.TYPE_FLOAT, True) - libtcod.struct_add_list_property(struct, "string_list", libtcod.TYPE_STRING, True) - libtcod.struct_add_list_property(struct, "color_list", libtcod.TYPE_COLOR, True) + libtcod.struct_add_property(struct, "bool_field", libtcod.TYPE_BOOL, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "char_field", libtcod.TYPE_CHAR, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "int_field", libtcod.TYPE_INT, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "float_field", libtcod.TYPE_FLOAT, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "color_field", libtcod.TYPE_COLOR, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "dice_field", libtcod.TYPE_DICE, True) # noqa: FBT003 + libtcod.struct_add_property(struct, "string_field", libtcod.TYPE_STRING, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "bool_list", libtcod.TYPE_BOOL, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "char_list", libtcod.TYPE_CHAR, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "integer_list", libtcod.TYPE_INT, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "float_list", libtcod.TYPE_FLOAT, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "string_list", libtcod.TYPE_STRING, True) # noqa: FBT003 + libtcod.struct_add_list_property(struct, "color_list", libtcod.TYPE_COLOR, True) # noqa: FBT003 # default listener print("***** Default listener *****") diff --git a/tests/test_sdl.py b/tests/test_sdl.py index 93373a49..42433510 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -10,7 +10,7 @@ import tcod.sdl.video -def test_sdl_window(uses_window: None) -> None: +def test_sdl_window(uses_window: None) -> None: # noqa: ARG001 assert tcod.sdl.video.get_grabbed_window() is None window = tcod.sdl.video.new_window(1, 1) window.raise_window() @@ -50,14 +50,14 @@ def test_sdl_window_bad_types() -> None: tcod.sdl.video.Window(tcod.ffi.new("SDL_Rect*")) -def test_sdl_screen_saver(uses_window: None) -> None: +def test_sdl_screen_saver(uses_window: None) -> None: # noqa: ARG001 tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.VIDEO) - assert tcod.sdl.video.screen_saver_allowed(False) is False - assert tcod.sdl.video.screen_saver_allowed(True) is True + assert tcod.sdl.video.screen_saver_allowed(False) is False # noqa: FBT003 + assert tcod.sdl.video.screen_saver_allowed(True) is True # noqa: FBT003 assert tcod.sdl.video.screen_saver_allowed() is True -def test_sdl_render(uses_window: None) -> None: +def test_sdl_render(uses_window: None) -> None: # noqa: ARG001 window = tcod.sdl.video.new_window(4, 4) render = tcod.sdl.render.new_renderer(window, driver="software", vsync=False) render.clear() diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index 38250758..3cc9204b 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -124,9 +124,9 @@ def test_audio_callback_unraisable() -> None: class CheckCalled: was_called: bool = False - def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> None: + def __call__(self, _device: tcod.sdl.audio.AudioDevice, _stream: NDArray[Any]) -> None: self.was_called = True - raise Exception("Test unraisable error") # noqa + raise Exception("Test unraisable error") # noqa: EM101, TRY002, TRY003 check_called = CheckCalled() with tcod.sdl.audio.open(callback=check_called, paused=False) as device: diff --git a/tests/test_tcod.py b/tests/test_tcod.py index c41da899..3f8c1d9b 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -102,7 +102,7 @@ def test_tcod_map_pickle() -> None: def test_tcod_map_pickle_fortran() -> None: map_ = tcod.map.Map(2, 3, order="F") map2: tcod.map.Map = pickle.loads(pickle.dumps(copy.copy(map_))) - assert map_._Map__buffer.strides == map2._Map__buffer.strides # type: ignore[attr-defined] + assert map_._buffer.strides == map2._buffer.strides assert map_.transparent.strides == map2.transparent.strides assert map_.walkable.strides == map2.walkable.strides assert map_.fov.strides == map2.fov.strides From 89866837dd1aef5b1f545bb461ccedfcb53518b0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 13 Jan 2026 10:57:30 -0800 Subject: [PATCH 1056/1101] Skip unraisable exception test Not reliable enough to test Strange issues locally --- tests/test_sdl_audio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index 3cc9204b..94bd151e 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -114,6 +114,7 @@ def __call__(self, device: tcod.sdl.audio.AudioDevice, stream: NDArray[Any]) -> @pytest.mark.skipif(sys.version_info < (3, 8), reason="Needs sys.unraisablehook support") @pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning") +@pytest.mark.skip(reason="Unsupported, causes too many issues") @needs_audio_device def test_audio_callback_unraisable() -> None: """Test unraisable error in audio callback. From 186cec1884e1f875634e18574c1f9d4271443a6e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 13 Jan 2026 11:06:40 -0800 Subject: [PATCH 1057/1101] Add coverage exclusions for uncoverable lines and files --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 704dc0c6..17977d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,10 @@ filterwarnings = [ "ignore:'import tcod as libtcodpy' is preferred.", ] +[tool.coverage.report] # https://coverage.readthedocs.io/en/latest/config.html +exclude_lines = ['^\s*\.\.\.', "if TYPE_CHECKING:", "# pragma: no cover"] +omit = ["tcod/__pyinstaller/*"] + [tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ enable = ["pypy", "pyodide-prerelease"] From 57cb18f59c7d13483457f2c1488d9defa5f96e87 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 14 Jan 2026 10:25:08 -0800 Subject: [PATCH 1058/1101] Assume Event types with get and wait event iterators Any was meant for more advanced cases which are now being discouraged --- tcod/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index f693291a..d364150f 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1198,7 +1198,7 @@ def _parse_event(sdl_event: Any) -> Event: return Undefined.from_sdl_event(sdl_event) -def get() -> Iterator[Any]: +def get() -> Iterator[Event]: """Return an iterator for all pending events. Events are processed as the iterator is consumed. @@ -1218,7 +1218,7 @@ def get() -> Iterator[Any]: yield _parse_event(sdl_event) -def wait(timeout: float | None = None) -> Iterator[Any]: +def wait(timeout: float | None = None) -> Iterator[Event]: """Block until events exist, then return an event iterator. `timeout` is the maximum number of seconds to wait as a floating point From 76c3786a853c8cab27418fd308baa5a01c06bcfd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 14 Jan 2026 10:26:27 -0800 Subject: [PATCH 1059/1101] Add better mouse coordinate conversion functions Update logical_size property to return None instead of (0, 0), None is a more expected return value for this state. Update samples to highlight the tile under the mouse --- CHANGELOG.md | 9 +++++ examples/samples_tcod.py | 31 ++++++++-------- tcod/event.py | 77 +++++++++++++++++++++++++++++++++++++++- tcod/sdl/render.py | 48 +++++++++++++++++++------ 4 files changed, 137 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e124797c..e59d96e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- Added methods: `Renderer.coordinates_from_window` and `Renderer.coordinates_to_window` +- Added `tcod.event.convert_coordinates_from_window`. + +### Changed + +- `Renderer.logical_size` now returns `None` instead of `(0, 0)` when logical size is unset. + ## [19.6.3] - 2026-01-12 Fix missing deployment diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index d8d79bea..32107d0d 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1379,6 +1379,8 @@ def redraw_display() -> None: SAMPLES[cur_sample].on_draw() sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y) draw_stats() + if 0 <= mouse_tile_xy[0] < root_console.width and 0 <= mouse_tile_xy[1] < root_console.height: + root_console.rgb[["fg", "bg"]].T[mouse_tile_xy] = (0, 0, 0), (255, 255, 255) # Highlight mouse tile if context.sdl_renderer: # Clear the screen to ensure no garbage data outside of the logical area is displayed context.sdl_renderer.draw_color = (0, 0, 0, 255) @@ -1410,25 +1412,20 @@ def handle_time() -> None: frame_length.append(frame_times[-1] - frame_times[-2]) +mouse_tile_xy = (-1, -1) +"""Last known mouse tile position.""" + + def handle_events() -> None: + global mouse_tile_xy for event in tcod.event.get(): - if context.sdl_renderer: # Manual handing of tile coordinates since context.present is skipped - assert context.sdl_window - tile_width = context.sdl_window.size[0] / root_console.width - tile_height = context.sdl_window.size[1] / root_console.height - - if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): - event.tile = tcod.event.Point(event.position.x // tile_width, event.position.y // tile_height) - if isinstance(event, tcod.event.MouseMotion): - prev_tile = ( - (event.position[0] - event.motion[0]) // tile_width, - (event.position[1] - event.motion[1]) // tile_height, - ) - event.tile_motion = tcod.event.Point(event.tile[0] - prev_tile[0], event.tile[1] - prev_tile[1]) - else: - context.convert_event(event) - - SAMPLES[cur_sample].on_event(event) + tile_event = tcod.event.convert_coordinates_from_window(event, context, root_console) + SAMPLES[cur_sample].on_event(tile_event) + match tile_event: + case tcod.event.MouseMotion(position=(x, y)): + mouse_tile_xy = int(x), int(y) + case tcod.event.WindowEvent(type="WindowLeave"): + mouse_tile_xy = -1, -1 def draw_samples_menu() -> None: diff --git a/tcod/event.py b/tcod/event.py index d364150f..63a007c4 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -87,13 +87,15 @@ import sys import warnings from collections.abc import Callable, Iterator, Mapping -from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar +from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar, overload import numpy as np from typing_extensions import deprecated +import tcod.context import tcod.event_constants import tcod.sdl.joystick +import tcod.sdl.render import tcod.sdl.sys from tcod.cffi import ffi, lib from tcod.event_constants import * # noqa: F403 @@ -103,6 +105,7 @@ from numpy.typing import NDArray T = TypeVar("T") +_EventType = TypeVar("_EventType", bound="Event") class _ConstantsWithPrefix(Mapping[int, str]): @@ -1564,6 +1567,78 @@ def get_mouse_state() -> MouseState: return MouseState((xy[0], xy[1]), (int(tile[0]), int(tile[1])), buttons) +@overload +def convert_coordinates_from_window( + event: _EventType, + /, + context: tcod.context.Context | tcod.sdl.render.Renderer, + console: tcod.console.Console | tuple[int, int], + dest_rect: tuple[int, int, int, int] | None = None, +) -> _EventType: ... +@overload +def convert_coordinates_from_window( + xy: tuple[float, float], + /, + context: tcod.context.Context | tcod.sdl.render.Renderer, + console: tcod.console.Console | tuple[int, int], + dest_rect: tuple[int, int, int, int] | None = None, +) -> tuple[float, float]: ... +def convert_coordinates_from_window( + event: _EventType | tuple[float, float], + /, + context: tcod.context.Context | tcod.sdl.render.Renderer, + console: tcod.console.Console | tuple[int, int], + dest_rect: tuple[int, int, int, int] | None = None, +) -> _EventType | tuple[float, float]: + """Return an event or position with window mouse coordinates converted into console tile coordinates. + + Args: + event: :any:`Event` to convert, or the `(x, y)` coordinates to convert. + context: Context or Renderer to fetch the SDL renderer from for reference with conversions. + console: A console used as a size reference. + Otherwise the `(columns, rows)` can be given directly as a tuple. + dest_rect: The consoles rendering destination as `(x, y, width, height)`. + If None is given then the whole rendering target is assumed. + + .. versionadded:: Unreleased + """ + if isinstance(context, tcod.context.Context): + maybe_renderer: Final = context.sdl_renderer + if maybe_renderer is None: + return event + context = maybe_renderer + + if isinstance(console, tcod.console.Console): + console = console.width, console.height + + if dest_rect is None: + dest_rect = (0, 0, *(context.logical_size or context.output_size)) + + x_scale: Final = console[0] / dest_rect[2] + y_scale: Final = console[1] / dest_rect[3] + x_offset: Final = dest_rect[0] + y_offset: Final = dest_rect[1] + + if not isinstance(event, Event): + x, y = context.coordinates_from_window(event) + return (x - x_offset) * x_scale, (y - y_offset) * y_scale + + if isinstance(event, MouseMotion): + previous_position = convert_coordinates_from_window( + ((event.position[0] - event.motion[0]), (event.position[1] - event.motion[1])), context, console, dest_rect + ) + position = convert_coordinates_from_window(event.position, context, console, dest_rect) + event.motion = tcod.event.Point(position[0] - previous_position[0], position[1] - previous_position[1]) + event._tile_motion = tcod.event.Point( + int(position[0]) - int(previous_position[0]), int(position[1]) - int(previous_position[1]) + ) + if isinstance(event, (MouseState, MouseMotion)): + event.position = event._tile = tcod.event.Point( + *convert_coordinates_from_window(event.position, context, console, dest_rect) + ) + return event + + @ffi.def_extern() # type: ignore[untyped-decorator] def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: callback: Callable[[Event], None] = ffi.from_handle(userdata) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 6833330f..496149e4 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -439,16 +439,16 @@ def draw_blend_mode(self, value: int) -> None: @property def output_size(self) -> tuple[int, int]: - """Get the (width, height) pixel resolution of the rendering context. + """Get the (width, height) pixel resolution of the current rendering context. .. seealso:: - https://wiki.libsdl.org/SDL_GetRendererOutputSize + https://wiki.libsdl.org/SDL3/SDL_GetCurrentRenderOutputSize .. versionadded:: 13.5 """ out = ffi.new("int[2]") _check(lib.SDL_GetCurrentRenderOutputSize(self.p, out, out + 1)) - return out[0], out[1] + return tuple(out) @property def clip_rect(self) -> tuple[int, int, int, int] | None: @@ -481,10 +481,8 @@ def set_logical_presentation(self, resolution: tuple[int, int], mode: LogicalPre _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, mode)) @property - def logical_size(self) -> tuple[int, int]: - """Get current independent (width, height) resolution. - - Might be (0, 0) if a resolution was never assigned. + def logical_size(self) -> tuple[int, int] | None: + """Get current independent (width, height) resolution, or None if logical size is unset. .. seealso:: https://wiki.libsdl.org/SDL3/SDL_GetRenderLogicalPresentation @@ -493,10 +491,14 @@ def logical_size(self) -> tuple[int, int]: .. versionchanged:: 19.0 Setter is deprecated, use :any:`set_logical_presentation` instead. + + .. versionchanged:: Unreleased + Return ``None`` instead of ``(0, 0)`` when logical size is disabled. """ out = ffi.new("int[2]") lib.SDL_GetRenderLogicalPresentation(self.p, out, out + 1, ffi.NULL) - return out[0], out[1] + out_tuple = tuple(out) + return None if out_tuple == (0, 0) else out_tuple @logical_size.setter @deprecated("Use set_logical_presentation method to correctly setup logical size.") @@ -509,7 +511,7 @@ def scale(self) -> tuple[float, float]: """Get or set an (x_scale, y_scale) multiplier for drawing. .. seealso:: - https://wiki.libsdl.org/SDL_RenderSetScale + https://wiki.libsdl.org/SDL3/SDL_SetRenderScale .. versionadded:: 13.5 """ @@ -526,7 +528,7 @@ def viewport(self) -> tuple[int, int, int, int] | None: """Get or set the drawing area for the current rendering target. .. seealso:: - https://wiki.libsdl.org/SDL_RenderSetViewport + https://wiki.libsdl.org/SDL3/SDL_SetRenderViewport .. versionadded:: 13.5 """ @@ -753,6 +755,32 @@ def geometry( ) ) + def coordinates_from_window(self, xy: tuple[float, float], /) -> tuple[float, float]: + """Return the renderer coordinates from the given windows coordinates. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesFromWindow + + .. versionadded:: Unreleased + """ + x, y = xy + out_xy = ffi.new("float[2]") + _check(lib.SDL_RenderCoordinatesFromWindow(self.p, x, y, out_xy, out_xy + 1)) + return tuple(out_xy) + + def coordinates_to_window(self, xy: tuple[float, float], /) -> tuple[float, float]: + """Return the window coordinates from the given render coordinates. + + .. seealso:: + https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesToWindow + + .. versionadded:: Unreleased + """ + x, y = xy + out_xy = ffi.new("float[2]") + _check(lib.SDL_RenderCoordinatesToWindow(self.p, x, y, out_xy, out_xy + 1)) + return tuple(out_xy) + def new_renderer( window: tcod.sdl.video.Window, From 38978dba43dae00472dbd70f57669d887bc91a73 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 21 Jan 2026 10:07:17 -0800 Subject: [PATCH 1060/1101] Clean up workflows Add Zizmor and apply easy fixes Clean up one-line jobs Update pre-commit --- .github/workflows/python-package.yml | 35 +++++++++++++++++----------- .github/workflows/release-on-tag.yml | 14 +++++++---- .github/zizmor.yaml | 7 ++++++ .pre-commit-config.yaml | 6 ++++- .vscode/settings.json | 7 +++--- 5 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 .github/zizmor.yaml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8b1c36dd..b19f6624 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,6 +26,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Install Ruff run: pip install ruff - name: Ruff Check @@ -38,6 +40,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Checkout submodules run: git submodule update --init --recursive --depth 1 - name: Install typing dependencies @@ -59,6 +63,7 @@ jobs: version: ${{ env.sdl-version }} - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -86,6 +91,7 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -117,10 +123,10 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 + run: git submodule update --init --recursive --depth 1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: @@ -143,19 +149,15 @@ jobs: pip install pytest pytest-cov pytest-benchmark pytest-timeout build if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Initialize package - run: | - pip install -e . # Install the package in-place. + run: pip install -e . # Install the package in-place. - name: Build package - run: | - python -m build + run: python -m build - name: Test with pytest if: runner.os == 'Windows' - run: | - pytest --cov-report=xml --timeout=300 + run: pytest --cov-report=xml --timeout=300 - name: Test with pytest (Xvfb) if: always() && runner.os != 'Windows' - run: | - xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300 + run: xvfb-run -e /tmp/xvfb.log --server-num=$RANDOM --auto-servernum pytest --cov-report=xml --timeout=300 - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log @@ -181,6 +183,7 @@ jobs: version: ${{ env.sdl-version }} - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -206,6 +209,7 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --depth 1 @@ -239,6 +243,7 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -270,8 +275,9 @@ jobs: # Skip test on emulated architectures CIBW_TEST_SKIP: "*_aarch64" - name: Remove asterisk from label + env: + BUILD_DESC: ${{ matrix.build }} run: | - BUILD_DESC=${{ matrix.build }} BUILD_DESC=${BUILD_DESC//\*} echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV - name: Archive wheel @@ -295,6 +301,7 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -317,8 +324,9 @@ jobs: CIBW_TEST_SKIP: "pp* *-macosx_arm64 *-macosx_universal2:arm64" MACOSX_DEPLOYMENT_TARGET: "10.13" - name: Remove asterisk from label + env: + PYTHON_DESC: ${{ matrix.python }} run: | - PYTHON_DESC=${{ matrix.python }} PYTHON_DESC=${PYTHON_DESC//\*/X} echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV - name: Archive wheel @@ -336,6 +344,7 @@ jobs: steps: - uses: actions/checkout@v6 with: + persist-credentials: false fetch-depth: ${{ env.git-depth }} - name: Checkout submodules run: git submodule update --init --recursive --depth 1 @@ -365,7 +374,7 @@ jobs: name: pypi url: https://pypi.org/project/tcod/${{ github.ref_name }} permissions: - id-token: write + id-token: write # Attestation steps: - uses: actions/download-artifact@v7 with: diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 921ac17d..0b9f07b0 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -5,19 +5,23 @@ on: name: Create Release +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: build: name: Create Release runs-on: ubuntu-latest timeout-minutes: 5 permissions: - contents: write + contents: write # Publish GitHub Releases steps: - - name: Checkout code - uses: actions/checkout@v6 + - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Generate body - run: | - scripts/get_release_description.py | tee release_body.md + run: scripts/get_release_description.py | tee release_body.md - name: Create Release id: create_release uses: ncipollo/release-action@v1 diff --git a/.github/zizmor.yaml b/.github/zizmor.yaml new file mode 100644 index 00000000..b247c2d3 --- /dev/null +++ b/.github/zizmor.yaml @@ -0,0 +1,7 @@ +rules: + anonymous-definition: + disable: true + excessive-permissions: + disable: true + unpinned-uses: + disable: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f04621d0..aff2ef24 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,8 +17,12 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 + rev: v0.14.13 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] - id: ruff-format + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.22.0 + hooks: + - id: zizmor diff --git a/.vscode/settings.json b/.vscode/settings.json index 8226a81d..c1555fa8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,7 @@ "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "files.associations": { - "*.spec": "python", + "*.spec": "python" }, "mypy-type-checker.importStrategy": "fromEnvironment", "cSpell.words": [ @@ -548,12 +548,13 @@ "xrel", "xvfb", "ydst", - "yrel" + "yrel", + "zizmor" ], "python.testing.pytestArgs": [], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" - }, + } } From a40049c46246bf285ed8009d2f3907ac36e879d9 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 21 Jan 2026 09:36:43 -0800 Subject: [PATCH 1061/1101] Add ARM64 Windows wheels to be tested and published Update Windows platform detection, unsure if this is better in any way but I need to sort x64 from arm64. This architecture may become more common in the future, I'd rather support it now just in case. --- .github/workflows/python-package.yml | 3 +++ build_sdl.py | 23 ++++++++++++++++++----- setup.py | 12 ++++++------ tcod/cffi.py | 8 +++++--- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b19f6624..595ff7a7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -118,6 +118,9 @@ jobs: - os: "windows-latest" python-version: "3.10" architecture: "x86" + - os: "windows-11-arm" + python-version: "3.11" + architecture: "arm64" fail-fast: false steps: diff --git a/build_sdl.py b/build_sdl.py index dbd49bd5..a1b8f6a2 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -3,10 +3,10 @@ from __future__ import annotations +import functools import io import logging import os -import platform import re import shutil import subprocess @@ -28,7 +28,20 @@ logger = logging.getLogger(__name__) -BIT_SIZE, LINKAGE = platform.architecture() +RE_MACHINE = re.compile(r".*\((.+)\)\]", re.DOTALL) + + +@functools.cache +def python_machine() -> str: + """Return the Python machine architecture (e.g. 'i386', 'AMD64', 'ARM64').""" + # Only needs to function correctly for Windows platforms. + match = RE_MACHINE.match(sys.version) + assert match, repr(sys.version) + (machine,) = match.groups() + machine = {"Intel": "i386"}.get(machine, machine) + logger.info(f"python_machine: {machine}") + return machine + # Reject versions of SDL older than this, update the requirements in the readme if you change this. SDL_MIN_VERSION = (3, 2, 0) @@ -386,10 +399,10 @@ def get_cdef() -> tuple[str, dict[str, str]]: # Bundle the Windows SDL DLL. if sys.platform == "win32" and SDL_BUNDLE_PATH is not None: include_dirs.append(str(SDL_INCLUDE)) - ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} - SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) + ARCH_MAPPING = {"i386": "x86", "AMD64": "x64", "ARM64": "arm64"} + SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[python_machine()]) library_dirs.append(str(SDL_LIB_DIR)) - SDL_LIB_DEST = Path("tcod", ARCH_MAPPING[BIT_SIZE]) + SDL_LIB_DEST = Path("tcod", ARCH_MAPPING[python_machine()]) SDL_LIB_DEST.mkdir(exist_ok=True) SDL_LIB_DEST_FILE = SDL_LIB_DEST / "SDL3.dll" SDL_LIB_FILE = SDL_LIB_DIR / "SDL3.dll" diff --git a/setup.py b/setup.py index 3786a1db..35c94af0 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ from __future__ import annotations -import platform import sys from pathlib import Path @@ -18,18 +17,19 @@ def get_package_data() -> list[str]: """Get data files which will be included in the main tcod/ directory.""" - bit_size, _ = platform.architecture() files = [ "py.typed", "lib/LIBTCOD-CREDITS.txt", "lib/LIBTCOD-LICENSE.txt", "lib/README-SDL.txt", ] - if "win32" in sys.platform: - if bit_size == "32bit": - files += ["x86/SDL3.dll"] - else: + if sys.platform == "win32": + if "ARM64" in sys.version: + files += ["arm64/SDL3.dll"] + elif "AMD64" in sys.version: files += ["x64/SDL3.dll"] + else: + files += ["x86/SDL3.dll"] if sys.platform == "darwin": files += ["SDL3.framework/Versions/A/SDL3"] return files diff --git a/tcod/cffi.py b/tcod/cffi.py index 97a37e44..57f4039b 100644 --- a/tcod/cffi.py +++ b/tcod/cffi.py @@ -7,7 +7,7 @@ import platform import sys from pathlib import Path -from typing import Any +from typing import Any, Literal import cffi @@ -39,8 +39,10 @@ def verify_dependencies() -> None: raise RuntimeError(msg) -def get_architecture() -> str: - """Return the Windows architecture, one of "x86" or "x64".""" +def get_architecture() -> Literal["x86", "x64", "arm64"]: + """Return the Windows architecture.""" + if "(ARM64)" in sys.version: + return "arm64" return "x86" if platform.architecture()[0] == "32bit" else "x64" From 15b5eda8445b2ba1a80e13f05ff0909624c14e1d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 23 Jan 2026 15:44:02 -0800 Subject: [PATCH 1062/1101] Remove internal pending_deprecate decorator Was too complex for linters to pick up on and was preventing warnings from showing correctly --- tcod/_internal.py | 12 +---- tcod/libtcodpy.py | 120 ++++++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 69 deletions(-) diff --git a/tcod/_internal.py b/tcod/_internal.py index 3391a602..b67242b8 100644 --- a/tcod/_internal.py +++ b/tcod/_internal.py @@ -8,7 +8,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING, Any, AnyStr, Literal, NoReturn, SupportsInt, TypeVar -from typing_extensions import LiteralString, deprecated +from typing_extensions import deprecated from tcod.cffi import ffi, lib @@ -40,16 +40,6 @@ def decorator(func: F) -> F: deprecate = deprecated if __debug__ or TYPE_CHECKING else _deprecate_passthrough -def pending_deprecate( - message: LiteralString = "This function may be deprecated in the future." - " Consider raising an issue on GitHub if you need this feature.", - category: type[Warning] = PendingDeprecationWarning, - stacklevel: int = 0, -) -> Callable[[F], F]: - """Like deprecate, but the default parameters are filled out for a generic pending deprecation warning.""" - return deprecate(message, category=category, stacklevel=stacklevel) - - def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]: """Verify and return a Numpy order string.""" order = order.upper() # type: ignore[assignment] diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 1948b550..7da9ff30 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -7,7 +7,7 @@ import threading import warnings from pathlib import Path -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np from typing_extensions import deprecated @@ -35,7 +35,6 @@ _unicode, _unpack_char_p, deprecate, - pending_deprecate, ) from tcod.cffi import ffi, lib from tcod.color import Color @@ -81,6 +80,11 @@ def BKGND_ADDALPHA(a: int) -> int: # noqa: N802 return BKGND_ADDA | (int(a * 255) << 8) +_PENDING_DEPRECATE_MSG: Final = ( + "This function may be deprecated in the future. Consider raising an issue on GitHub if you need this feature." +) + + @deprecated("Console array attributes perform better than this class.") class ConsoleBuffer: """Simple console that allows direct (fast) access to cells. Simplifies use of the "fill" functions. @@ -737,7 +741,7 @@ def bsp_delete(node: tcod.bsp.BSP) -> None: """ -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def color_lerp(c1: tuple[int, int, int], c2: tuple[int, int, int], a: float) -> Color: """Return the linear interpolation between two colors. @@ -757,7 +761,7 @@ def color_lerp(c1: tuple[int, int, int], c2: tuple[int, int, int], a: float) -> return Color._new_from_cdata(lib.TCOD_color_lerp(c1, c2, a)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: """Set a color using: hue, saturation, and value parameters. @@ -774,7 +778,7 @@ def color_set_hsv(c: Color, h: float, s: float, v: float) -> None: c[:] = new_color.r, new_color.g, new_color.b -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def color_get_hsv(c: tuple[int, int, int]) -> tuple[float, float, float]: """Return the (hue, saturation, value) of a color. @@ -791,7 +795,7 @@ def color_get_hsv(c: tuple[int, int, int]) -> tuple[float, float, float]: return hsv[0], hsv[1], hsv[2] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: # noqa: N802 """Scale a color's saturation and value. @@ -810,7 +814,7 @@ def color_scale_HSV(c: Color, scoef: float, vcoef: float) -> None: # noqa: N802 c[:] = color_p.r, color_p.g, color_p.b -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def color_gen_map(colors: Iterable[tuple[int, int, int]], indexes: Iterable[int]) -> list[Color]: """Return a smoothly defined scale of colors. @@ -1149,7 +1153,7 @@ def console_set_window_title(title: str) -> None: lib.TCOD_console_set_window_title(_bytes(title)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def console_credits() -> None: lib.TCOD_console_credits() @@ -1277,7 +1281,7 @@ def console_clear(con: tcod.console.Console) -> None: lib.TCOD_console_clear(_console(con)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def console_put_char( con: tcod.console.Console, x: int, @@ -1297,7 +1301,7 @@ def console_put_char( lib.TCOD_console_put_char(_console(con), x, y, _int(c), flag) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def console_put_char_ex( con: tcod.console.Console, x: int, @@ -1321,7 +1325,7 @@ def console_put_char_ex( lib.TCOD_console_put_char_ex(_console(con), x, y, _int(c), fore, back) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def console_set_char_background( con: tcod.console.Console, x: int, @@ -1615,7 +1619,7 @@ def console_print_frame( _check(lib.TCOD_console_printf_frame(_console(con), x, y, w, h, clear, flag, fmt_)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def console_set_color_control(con: int, fore: tuple[int, int, int], back: tuple[int, int, int]) -> None: """Configure :term:`color controls`. @@ -2099,7 +2103,7 @@ def console_list_save_xp( lib.TCOD_list_delete(tcod_list) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_new_using_map(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.AStar: """Return a new AStar using the given Map. @@ -2114,7 +2118,7 @@ def path_new_using_map(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.AStar: return tcod.path.AStar(m, dcost) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_new_using_function( w: int, h: int, @@ -2138,7 +2142,7 @@ def path_new_using_function( return tcod.path.AStar(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool: """Find a path from (ox, oy) to (dx, dy). Return True if path is found. @@ -2155,7 +2159,7 @@ def path_compute(p: tcod.path.AStar, ox: int, oy: int, dx: int, dy: int) -> bool return bool(lib.TCOD_path_compute(p._path_c, ox, oy, dx, dy)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_get_origin(p: tcod.path.AStar) -> tuple[int, int]: """Get the current origin position. @@ -2173,7 +2177,7 @@ def path_get_origin(p: tcod.path.AStar) -> tuple[int, int]: return x[0], y[0] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_get_destination(p: tcod.path.AStar) -> tuple[int, int]: """Get the current destination position. @@ -2189,7 +2193,7 @@ def path_get_destination(p: tcod.path.AStar) -> tuple[int, int]: return x[0], y[0] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_size(p: tcod.path.AStar) -> int: """Return the current length of the computed path. @@ -2202,7 +2206,7 @@ def path_size(p: tcod.path.AStar) -> int: return int(lib.TCOD_path_size(p._path_c)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_reverse(p: tcod.path.AStar) -> None: """Reverse the direction of a path. @@ -2214,7 +2218,7 @@ def path_reverse(p: tcod.path.AStar) -> None: lib.TCOD_path_reverse(p._path_c) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_get(p: tcod.path.AStar, idx: int) -> tuple[int, int]: """Get a point on a path. @@ -2228,7 +2232,7 @@ def path_get(p: tcod.path.AStar, idx: int) -> tuple[int, int]: return x[0], y[0] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_is_empty(p: tcod.path.AStar) -> bool: """Return True if a path is empty. @@ -2241,7 +2245,7 @@ def path_is_empty(p: tcod.path.AStar) -> bool: return bool(lib.TCOD_path_is_empty(p._path_c)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def path_walk(p: tcod.path.AStar, recompute: bool) -> tuple[int, int] | tuple[None, None]: # noqa: FBT001 """Return the next (x, y) point in a path, or (None, None) if it's empty. @@ -2271,12 +2275,12 @@ def path_delete(p: tcod.path.AStar) -> None: """ -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_new(m: tcod.map.Map, dcost: float = 1.41) -> tcod.path.Dijkstra: return tcod.path.Dijkstra(m, dcost) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_new_using_function( w: int, h: int, @@ -2287,32 +2291,32 @@ def dijkstra_new_using_function( return tcod.path.Dijkstra(tcod.path._EdgeCostFunc((func, userData), (w, h)), dcost) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_compute(p: tcod.path.Dijkstra, ox: int, oy: int) -> None: lib.TCOD_dijkstra_compute(p._path_c, ox, oy) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_path_set(p: tcod.path.Dijkstra, x: int, y: int) -> bool: return bool(lib.TCOD_dijkstra_path_set(p._path_c, x, y)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_get_distance(p: tcod.path.Dijkstra, x: int, y: int) -> int: return int(lib.TCOD_dijkstra_get_distance(p._path_c, x, y)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_size(p: tcod.path.Dijkstra) -> int: return int(lib.TCOD_dijkstra_size(p._path_c)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_reverse(p: tcod.path.Dijkstra) -> None: lib.TCOD_dijkstra_reverse(p._path_c) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> tuple[int, int]: x = ffi.new("int *") y = ffi.new("int *") @@ -2320,12 +2324,12 @@ def dijkstra_get(p: tcod.path.Dijkstra, idx: int) -> tuple[int, int]: return x[0], y[0] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_is_empty(p: tcod.path.Dijkstra) -> bool: return bool(lib.TCOD_dijkstra_is_empty(p._path_c)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def dijkstra_path_walk( p: tcod.path.Dijkstra, ) -> tuple[int, int] | tuple[None, None]: @@ -2362,7 +2366,7 @@ def _heightmap_cdata(array: NDArray[np.float32]) -> Any: return ffi.new("TCOD_heightmap_t *", (width, height, pointer)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_new(w: int, h: int, order: str = "C") -> NDArray[np.float32]: """Return a new numpy.ndarray formatted for use with heightmap functions. @@ -2486,7 +2490,7 @@ def heightmap_copy(hm1: NDArray[np.float32], hm2: NDArray[np.float32]) -> None: hm2[:] = hm1[:] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_normalize(hm: NDArray[np.float32], mi: float = 0.0, ma: float = 1.0) -> None: """Normalize heightmap values between ``mi`` and ``ma``. @@ -2498,7 +2502,7 @@ def heightmap_normalize(hm: NDArray[np.float32], mi: float = 0.0, ma: float = 1. lib.TCOD_heightmap_normalize(_heightmap_cdata(hm), mi, ma) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_lerp_hm( hm1: NDArray[np.float32], hm2: NDArray[np.float32], @@ -2562,7 +2566,7 @@ def heightmap_multiply_hm( hm3[:] = hm1[:] * hm2[:] -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_add_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None: """Add a hill (a half spheroid) at given position. @@ -2578,7 +2582,7 @@ def heightmap_add_hill(hm: NDArray[np.float32], x: float, y: float, radius: floa lib.TCOD_heightmap_add_hill(_heightmap_cdata(hm), x, y, radius, height) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: float, height: float) -> None: """Dig a hill in a heightmap. @@ -2596,7 +2600,7 @@ def heightmap_dig_hill(hm: NDArray[np.float32], x: float, y: float, radius: floa lib.TCOD_heightmap_dig_hill(_heightmap_cdata(hm), x, y, radius, height) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_rain_erosion( hm: NDArray[np.float32], nbDrops: int, # noqa: N803 @@ -2625,7 +2629,7 @@ def heightmap_rain_erosion( ) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_kernel_transform( hm: NDArray[np.float32], kernelsize: int, @@ -2681,7 +2685,7 @@ def heightmap_kernel_transform( lib.TCOD_heightmap_kernel_transform(_heightmap_cdata(hm), kernelsize, c_dx, c_dy, c_weight, minLevel, maxLevel) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_add_voronoi( hm: NDArray[np.float32], nbPoints: Any, # noqa: N803 @@ -2804,7 +2808,7 @@ def heightmap_scale_fbm( ) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_dig_bezier( hm: NDArray[np.float32], px: tuple[int, int, int, int], @@ -2863,7 +2867,7 @@ def heightmap_get_value(hm: NDArray[np.float32], x: int, y: int) -> float: raise ValueError(msg) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_get_interpolated_value(hm: NDArray[np.float32], x: float, y: float) -> float: """Return the interpolated height at non integer coordinates. @@ -2878,7 +2882,7 @@ def heightmap_get_interpolated_value(hm: NDArray[np.float32], x: float, y: float return float(lib.TCOD_heightmap_get_interpolated_value(_heightmap_cdata(hm), x, y)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float: """Return the slope between 0 and (pi / 2) at given coordinates. @@ -2893,7 +2897,7 @@ def heightmap_get_slope(hm: NDArray[np.float32], x: int, y: int) -> float: return float(lib.TCOD_heightmap_get_slope(_heightmap_cdata(hm), x, y)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_get_normal(hm: NDArray[np.float32], x: float, y: float, waterLevel: float) -> tuple[float, float, float]: # noqa: N803 """Return the map normal at given coordinates. @@ -2930,7 +2934,7 @@ def heightmap_count_cells(hm: NDArray[np.float32], mi: float, ma: float) -> int: return int(lib.TCOD_heightmap_count_cells(_heightmap_cdata(hm), mi, ma)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def heightmap_has_land_on_border(hm: NDArray[np.float32], waterlevel: float) -> bool: """Returns True if the map edges are below ``waterlevel``, otherwise False. @@ -3427,22 +3431,22 @@ def mouse_get_status() -> Mouse: return Mouse(lib.TCOD_mouse_get_status()) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def namegen_parse(filename: str | PathLike[str], random: tcod.random.Random | None = None) -> None: lib.TCOD_namegen_parse(_path_encode(Path(filename).resolve(strict=True)), random or ffi.NULL) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def namegen_generate(name: str) -> str: return _unpack_char_p(lib.TCOD_namegen_generate(_bytes(name), False)) # noqa: FBT003 -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def namegen_generate_custom(name: str, rule: str) -> str: return _unpack_char_p(lib.TCOD_namegen_generate_custom(_bytes(name), _bytes(rule), False)) # noqa: FBT003 -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def namegen_get_sets() -> list[str]: sets = lib.TCOD_namegen_get_sets() try: @@ -3454,7 +3458,7 @@ def namegen_get_sets() -> list[str]: return lst -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def namegen_destroy() -> None: lib.TCOD_namegen_destroy() @@ -3721,7 +3725,7 @@ def parser_get_list_property(parser: Any, name: str, type: Any) -> Any: # noqa: DISTRIBUTION_GAUSSIAN_RANGE_INVERSE = 4 -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_get_instance() -> tcod.random.Random: """Return the default Random instance. @@ -3731,7 +3735,7 @@ def random_get_instance() -> tcod.random.Random: return tcod.random.Random._new_from_cdata(lib.TCOD_random_get_instance()) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: """Return a new Random instance. Using ``algo``. @@ -3744,7 +3748,7 @@ def random_new(algo: int = RNG_CMWC) -> tcod.random.Random: return tcod.random.Random(algo) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_new_from_seed(seed: Hashable, algo: int = RNG_CMWC) -> tcod.random.Random: """Return a new Random instance. Using the given ``seed`` and ``algo``. @@ -3759,7 +3763,7 @@ def random_new_from_seed(seed: Hashable, algo: int = RNG_CMWC) -> tcod.random.Ra return tcod.random.Random(algo, seed) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_set_distribution(rnd: tcod.random.Random | None, dist: int) -> None: """Change the distribution mode of a random number generator. @@ -3770,7 +3774,7 @@ def random_set_distribution(rnd: tcod.random.Random | None, dist: int) -> None: lib.TCOD_random_set_distribution(rnd.random_c if rnd else ffi.NULL, dist) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_get_int(rnd: tcod.random.Random | None, mi: int, ma: int) -> int: """Return a random integer in the range: ``mi`` <= n <= ``ma``. @@ -3787,7 +3791,7 @@ def random_get_int(rnd: tcod.random.Random | None, mi: int, ma: int) -> int: return int(lib.TCOD_random_get_int(rnd.random_c if rnd else ffi.NULL, mi, ma)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_get_float(rnd: tcod.random.Random | None, mi: float, ma: float) -> float: """Return a random float in the range: ``mi`` <= n <= ``ma``. @@ -3816,7 +3820,7 @@ def random_get_double(rnd: tcod.random.Random | None, mi: float, ma: float) -> f return float(lib.TCOD_random_get_double(rnd.random_c if rnd else ffi.NULL, mi, ma)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_get_int_mean(rnd: tcod.random.Random | None, mi: int, ma: int, mean: int) -> int: """Return a random weighted integer in the range: ``mi`` <= n <= ``ma``. @@ -3834,7 +3838,7 @@ def random_get_int_mean(rnd: tcod.random.Random | None, mi: int, ma: int, mean: return int(lib.TCOD_random_get_int_mean(rnd.random_c if rnd else ffi.NULL, mi, ma, mean)) -@pending_deprecate() +@deprecate(_PENDING_DEPRECATE_MSG, category=PendingDeprecationWarning) def random_get_float_mean(rnd: tcod.random.Random | None, mi: float, ma: float, mean: float) -> float: """Return a random weighted float in the range: ``mi`` <= n <= ``ma``. From a93ca11691394e13a93deab53f7269fe1e69b354 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 24 Jan 2026 17:22:56 -0800 Subject: [PATCH 1063/1101] Add Giscus widget to documentation Might help with feedback --- .vscode/settings.json | 3 +++ docs/_templates/page.html | 21 +++++++++++++++++++++ docs/conf.py | 12 ++++++------ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 docs/_templates/page.html diff --git a/.vscode/settings.json b/.vscode/settings.json index c1555fa8..dd3ea0db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -556,5 +556,8 @@ "python.testing.pytestEnabled": true, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" + }, + "[html]": { + "editor.formatOnSave": false } } diff --git a/docs/_templates/page.html b/docs/_templates/page.html new file mode 100644 index 00000000..213c761f --- /dev/null +++ b/docs/_templates/page.html @@ -0,0 +1,21 @@ +{% extends "!page.html" %} +{% block content %} + {{ super() }} + + +{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index ebca4ee1..70be25bf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,5 @@ """Sphinx config file.""" # noqa: INP001 -# tdl documentation build configuration file, created by +# python-tcod documentation build configuration file, created by # sphinx-quickstart on Fri Nov 25 12:49:46 2016. # # This file is execfile()d with the current directory set to its @@ -73,13 +73,12 @@ # built documents. # # The full version, including alpha/beta/rc tags. -git_describe = subprocess.run( +release = subprocess.run( ["git", "describe", "--abbrev=0"], # noqa: S607 stdout=subprocess.PIPE, text=True, check=True, -) -release = git_describe.stdout.strip() +).stdout.strip() assert release print(f"release version: {release!r}") @@ -87,6 +86,7 @@ match_version = re.match(r"([0-9]+\.[0-9]+).*?", release) assert match_version version = match_version.group() +print(f"short version: {version!r}") # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -167,9 +167,9 @@ # html_theme_path = [] # The name for this set of Sphinx documents. -# " v documentation" by default. +# " documentation" by default. # -# html_title = u'tdl v1' +html_title = f"{project} {release} documentation" # A shorter title for the navigation bar. Default is the same as html_title. # From 9d4d28f31a23b6da6d1ee3935122f070644d508a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 2 Feb 2026 14:02:07 -0800 Subject: [PATCH 1064/1101] Build free-threaded CPython 3.14t wheels Resolves #161 --- .github/workflows/python-package.yml | 13 ++++++++++--- .vscode/settings.json | 2 ++ build_libtcod.py | 9 +++++++-- pyproject.toml | 6 ++---- setup.py | 8 ++++++++ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 595ff7a7..90c6d21f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -109,18 +109,25 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ${{ matrix.os }} timeout-minutes: 15 + continue-on-error: ${{ matrix.python-version == '3.14t' && matrix.os == 'windows-11-arm' }} # Until issues are resolved: https://github.com/actions/setup-python/issues/1267 strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - python-version: ["3.10", "pypy-3.10"] + python-version: ["3.10", "3.14t", "pypy-3.10"] architecture: ["x64"] include: - os: "windows-latest" python-version: "3.10" architecture: "x86" + - os: "windows-latest" + python-version: "3.14t" + architecture: "x86" - os: "windows-11-arm" python-version: "3.11" architecture: "arm64" + - os: "windows-11-arm" + python-version: "3.14t" + architecture: "arm64" fail-fast: false steps: @@ -240,7 +247,7 @@ jobs: strategy: matrix: arch: ["x86_64", "aarch64"] - build: ["cp310-manylinux*", "pp310-manylinux*"] + build: ["cp310-manylinux*", "pp310-manylinux*", "cp314t-manylinux*"] env: BUILD_DESC: "" steps: @@ -298,7 +305,7 @@ jobs: strategy: fail-fast: true matrix: - python: ["cp310-*_universal2", "pp310-*"] + python: ["cp310-*_universal2", "cp314t-*_universal2", "pp310-*"] env: PYTHON_DESC: "" steps: diff --git a/.vscode/settings.json b/.vscode/settings.json index dd3ea0db..7428f839 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -107,6 +107,7 @@ "cooldown", "cplusplus", "CPLUSPLUS", + "cpython", "CRSEL", "ctypes", "CURRENCYSUBUNIT", @@ -167,6 +168,7 @@ "fmean", "fontx", "fonty", + "freethreading", "freetype", "frombuffer", "fullscreen", diff --git a/build_libtcod.py b/build_libtcod.py index 2b9d03d6..14a46710 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator -Py_LIMITED_API = 0x03100000 +Py_LIMITED_API: None | int = 0x03100000 HEADER_PARSE_PATHS = ("tcod/", "libtcod/src/libtcod/") HEADER_PARSE_EXCLUDES = ("gl2_ext_.h", "renderer_gl_internal.h", "event.h") @@ -167,9 +167,14 @@ def walk_sources(directory: str) -> Iterator[str]: library_dirs: list[str] = [*build_sdl.library_dirs] define_macros: list[tuple[str, Any]] = [] -if "PYODIDE" not in os.environ: +if "free-threading build" in sys.version: + Py_LIMITED_API = None +if "PYODIDE" in os.environ: # Unable to apply Py_LIMITED_API to Pyodide in cffi<=1.17.1 # https://github.com/python-cffi/cffi/issues/179 + Py_LIMITED_API = None + +if Py_LIMITED_API: define_macros.append(("Py_LIMITED_API", Py_LIMITED_API)) sources += walk_sources("tcod/") diff --git a/pyproject.toml b/pyproject.toml index 17977d1a..5bed1e4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ keywords = [ "field-of-view", "pathfinding", ] -classifiers = [ +classifiers = [ # https://pypi.org/classifiers/ "Development Status :: 5 - Production/Stable", "Environment :: Win32 (MS Windows)", "Environment :: MacOS X", @@ -53,6 +53,7 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Games/Entertainment", @@ -72,9 +73,6 @@ Source = "https://github.com/libtcod/python-tcod" Tracker = "https://github.com/libtcod/python-tcod/issues" Forum = "https://github.com/libtcod/python-tcod/discussions" -[tool.distutils.bdist_wheel] -py-limited-api = "cp310" - [tool.setuptools_scm] write_to = "tcod/version.py" diff --git a/setup.py b/setup.py index 35c94af0..3ff98114 100755 --- a/setup.py +++ b/setup.py @@ -40,6 +40,13 @@ def get_package_data() -> list[str]: print("Did you forget to run 'git submodule update --init'?") sys.exit(1) +options = { + "bdist_wheel": { + "py_limited_api": "cp310", + } +} +if "free-threading build" in sys.version: + del options["bdist_wheel"]["py_limited_api"] setup( py_modules=["libtcodpy"], @@ -47,4 +54,5 @@ def get_package_data() -> list[str]: package_data={"tcod": get_package_data()}, cffi_modules=["build_libtcod.py:ffi"], platforms=["Windows", "MacOS", "Linux"], + options=options, ) From 32cf62eae466ccbd3c3df31a2ad83e5d1349579d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 2 Feb 2026 14:16:01 -0800 Subject: [PATCH 1065/1101] Pull request limits are already handled by concurrency --- .github/workflows/python-package.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 90c6d21f..69d9c49b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -6,7 +6,6 @@ name: Package on: push: pull_request: - types: [opened, reopened] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} From 8f45aabec94e69cc03b917b6b281f425ff4a76d7 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 2 Feb 2026 14:16:34 -0800 Subject: [PATCH 1066/1101] Allow manual workflow dispatch --- .github/workflows/python-package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 69d9c49b..d6df14a6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -4,6 +4,7 @@ name: Package on: + workflow_dispatch: push: pull_request: From 508792a2cd8951c11b6f67a26bcccd546f9a2d5f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 5 Feb 2026 13:27:41 -0800 Subject: [PATCH 1067/1101] Upstream issues with installing Python 3.14t on Windows ARM were fixed --- .github/workflows/python-package.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d6df14a6..d52a4140 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -109,7 +109,6 @@ jobs: needs: [ruff, mypy, sdist] runs-on: ${{ matrix.os }} timeout-minutes: 15 - continue-on-error: ${{ matrix.python-version == '3.14t' && matrix.os == 'windows-11-arm' }} # Until issues are resolved: https://github.com/actions/setup-python/issues/1267 strategy: matrix: os: ["ubuntu-latest", "windows-latest"] From bca7e5fef0caf2fa86c1f3e6bfe743528e76273e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 5 Feb 2026 18:50:48 -0800 Subject: [PATCH 1068/1101] Prepare 20.0.0 release. --- CHANGELOG.md | 3 +++ tcod/event.py | 2 +- tcod/sdl/render.py | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59d96e9..6a4c8ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [20.0.0] - 2026-02-06 + ### Added +- Now supports free-threaded Python, deploys with `cp314t` wheels. - Added methods: `Renderer.coordinates_from_window` and `Renderer.coordinates_to_window` - Added `tcod.event.convert_coordinates_from_window`. diff --git a/tcod/event.py b/tcod/event.py index 63a007c4..53b06cb5 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1600,7 +1600,7 @@ def convert_coordinates_from_window( dest_rect: The consoles rendering destination as `(x, y, width, height)`. If None is given then the whole rendering target is assumed. - .. versionadded:: Unreleased + .. versionadded:: 20.0 """ if isinstance(context, tcod.context.Context): maybe_renderer: Final = context.sdl_renderer diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 496149e4..7e4a5df6 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -492,7 +492,7 @@ def logical_size(self) -> tuple[int, int] | None: .. versionchanged:: 19.0 Setter is deprecated, use :any:`set_logical_presentation` instead. - .. versionchanged:: Unreleased + .. versionchanged:: 20.0 Return ``None`` instead of ``(0, 0)`` when logical size is disabled. """ out = ffi.new("int[2]") @@ -761,7 +761,7 @@ def coordinates_from_window(self, xy: tuple[float, float], /) -> tuple[float, fl .. seealso:: https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesFromWindow - .. versionadded:: Unreleased + .. versionadded:: 20.0 """ x, y = xy out_xy = ffi.new("float[2]") @@ -774,7 +774,7 @@ def coordinates_to_window(self, xy: tuple[float, float], /) -> tuple[float, floa .. seealso:: https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesToWindow - .. versionadded:: Unreleased + .. versionadded:: 20.0 """ x, y = xy out_xy = ffi.new("float[2]") From bb4013bc387f8de0cc1652ad985202dc3ecd6325 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 11 Feb 2026 16:05:59 -0800 Subject: [PATCH 1069/1101] Classify as being partially C --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5bed1e4e..25cc234f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ classifiers = [ # https://pypi.org/classifiers/ "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", + "Programming Language :: C", "Programming Language :: Python :: 3", "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", From 37dccbae52a5a6a8c88328b41d629eeaef2a570d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 20 Feb 2026 04:30:03 -0800 Subject: [PATCH 1070/1101] Document mouse coordinates changed to float in tcod 19.0 --- tcod/event.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tcod/event.py b/tcod/event.py index 53b06cb5..ca3a3eb1 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -156,6 +156,9 @@ class Point(NamedTuple): .. seealso:: :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp` + + .. versionchanged:: 19.0 + Now uses floating point coordinates due to the port to SDL3. """ x: float @@ -481,6 +484,9 @@ class MouseMotion(MouseState): .. versionchanged:: 15.0 Renamed `pixel` attribute to `position`. Renamed `pixel_motion` attribute to `motion`. + + .. versionchanged:: 19.0 + `position` and `motion` now use floating point coordinates. """ def __init__( @@ -558,7 +564,7 @@ class MouseButtonEvent(MouseState): type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", depending on the event. position (Point): The pixel coordinates of the mouse. - tile (Point): The integer tile coordinates of the mouse on the screen. + tile (Point): The tile coordinates of the mouse on the screen. button (int): Which mouse button. This will be one of the following names: @@ -569,6 +575,8 @@ class MouseButtonEvent(MouseState): * tcod.event.BUTTON_X1 * tcod.event.BUTTON_X2 + .. versionchanged:: 19.0 + `position` and `tile` now use floating point coordinates. """ def __init__( From 993e3184c1fd9a2081f22c21fbfed7b77e394d2e Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 23 Feb 2026 22:21:33 -0800 Subject: [PATCH 1071/1101] Convert Tileset into a MutableMapping Add related tileset tests --- .vscode/settings.json | 1 + CHANGELOG.md | 13 +++ tcod/context.py | 2 +- tcod/tileset.py | 237 +++++++++++++++++++++++++++++++++++------- tests/test_tileset.py | 99 +++++++++++++++++- 5 files changed, 309 insertions(+), 43 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7428f839..41cffd1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -467,6 +467,7 @@ "SUBP", "SYSREQ", "tablefmt", + "Tamzen", "TARGETTEXTURE", "tcod", "tcoddoc", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4c8ea5..68d526b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `Tileset` now supports `MutableMapping` semantics. + Can get, set, or iterate over tiles as if it were a dictionary containing tile glyph arrays. + Also supports `+=` and `|=` with other tilesets or mappings. +- `tcod.tileset.procedural_block_elements` can take a tile shape and return a tileset. + +### Deprecated + +- `Tileset.set_tile(codepoint, tile)` was replaced with `tileset[codepoint] = tile` syntax. +- `Tileset.get_tile(codepoint)` was soft replaced with `tileset[codepoint]` syntax. +- `tcod.tileset.procedural_block_elements` should be used with dictionary semantics instead of passing in a tileset. + ## [20.0.0] - 2026-02-06 ### Added diff --git a/tcod/context.py b/tcod/context.py index f152b9d0..5b1bb214 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -125,7 +125,7 @@ def _handle_tileset(tileset: tcod.tileset.Tileset | None) -> Any: # noqa: ANN401 """Get the TCOD_Tileset pointer from a Tileset or return a NULL pointer.""" - return tileset._tileset_p if tileset else ffi.NULL + return tileset._tileset_p if tileset is not None else ffi.NULL def _handle_title(title: str | None) -> Any: # noqa: ANN401 diff --git a/tcod/tileset.py b/tcod/tileset.py index 4b3935ac..9ea4b1d7 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -2,25 +2,25 @@ If you want to load a tileset from a common tileset image then you only need :any:`tcod.tileset.load_tilesheet`. -Tilesets can be loaded as a whole from tile-sheets or True-Type fonts, or they -can be put together from multiple tile images by loading them separately -using :any:`Tileset.set_tile`. +Tilesets can be loaded as a whole from tile-sheets or True-Type fonts, +or they can be put together from multiple tile images by loading them separately using :any:`Tileset.__setitem__`. -A major restriction with libtcod is that all tiles must be the same size and -tiles can't overlap when rendered. For sprite-based rendering it can be -useful to use `an alternative library for graphics rendering +A major restriction with libtcod is that all tiles must be the same size and tiles can't overlap when rendered. +For sprite-based rendering it can be useful to use `an alternative library for graphics rendering `_ while continuing to use python-tcod's pathfinding and field-of-view algorithms. """ from __future__ import annotations +import copy import itertools +from collections.abc import Iterator, Mapping, MutableMapping from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, SupportsInt, overload import numpy as np -from typing_extensions import deprecated +from typing_extensions import Self, deprecated from tcod._internal import _check, _check_p, _console, _path_encode, _raise_tcod_error from tcod.cffi import ffi, lib @@ -34,10 +34,11 @@ import tcod.console -class Tileset: +class Tileset(MutableMapping[int, "NDArray[np.uint8]"]): """A collection of graphical tiles. - This class is provisional, the API may change in the future. + .. versionchanged:: Unreleased + Is now a :class:`collections.abc.MutableMapping` type. """ def __init__(self, tile_width: int, tile_height: int) -> None: @@ -80,28 +81,105 @@ def tile_shape(self) -> tuple[int, int]: """Shape (height, width) of the tile in pixels.""" return self.tile_height, self.tile_width - def __contains__(self, codepoint: int) -> bool: + def __copy__(self) -> Self: + """Return a clone of this tileset. + + This is not an exact copy. :any:`remap` will not work on the clone. + + .. versionadded:: Unreleased + """ + clone = self.__class__(self.tile_width, self.tile_height) + for codepoint, tile in self.items(): + clone[codepoint] = tile + return clone + + @staticmethod + def _iter_items( + tiles: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], / + ) -> Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]]: + """Convert a potential mapping to an iterator.""" + if isinstance(tiles, Mapping): + return tiles.items() # pyright: ignore[reportReturnType] + return tiles + + def __iadd__( + self, + other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], + /, + ) -> Self: + """Add tiles to this tileset inplace, prefers replacing tiles. + + .. versionadded:: Unreleased + """ + for codepoint, tile in self._iter_items(other): + self[codepoint] = tile + return self + + def __add__( + self, + other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], + /, + ) -> Self: + """Combine tiles with this tileset, prefers replacing tiles. + + .. versionadded:: Unreleased + """ + clone = copy.copy(self) + clone += other + return clone + + def __ior__( + self, + other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], + /, + ) -> Self: + """Add tiles to this tileset inplace, keeps existing tiles instead of replacing them. + + .. versionadded:: Unreleased + """ + for codepoint, tile in self._iter_items(other): + if codepoint not in self: + self[codepoint] = tile + return self + + def __or__( + self, + other: Iterable[tuple[int, ArrayLike | NDArray[np.uint8]]] | Mapping[int, ArrayLike | NDArray[np.uint8]], + /, + ) -> Self: + """Combine tiles with this tileset, prefers keeping existing tiles instead of replacing them. + + .. versionadded:: Unreleased + """ + clone = copy.copy(self) + clone |= other + return clone + + def __contains__(self, codepoint: object, /) -> bool: """Test if a tileset has a codepoint with ``n in tileset``.""" - return bool(lib.TCOD_tileset_get_tile_(self._tileset_p, codepoint, ffi.NULL) == 0) + if not isinstance(codepoint, SupportsInt): + return False + codepoint = int(codepoint) + if not 0 <= codepoint < self._tileset_p.character_map_length: + return False + return bool(self._get_character_map()[int(codepoint)] > 0) - def get_tile(self, codepoint: int) -> NDArray[np.uint8]: - """Return a copy of a tile for the given codepoint. + def __getitem__(self, codepoint: int, /) -> NDArray[np.uint8]: + """Return the RGBA tile data for the given codepoint. - If the tile does not exist yet then a blank array will be returned. + The tile will have a shape of (height, width, rgba) and a dtype of uint8. + Note that most grey-scale tilesets will only use the alpha channel with a solid white color channel. - The tile will have a shape of (height, width, rgba) and a dtype of - uint8. Note that most grey-scale tiles will only use the alpha - channel and will usually have a solid white color channel. + .. versionadded:: Unreleased """ - tile: NDArray[np.uint8] = np.zeros((*self.tile_shape, 4), dtype=np.uint8) - lib.TCOD_tileset_get_tile_( - self._tileset_p, - codepoint, - ffi.from_buffer("struct TCOD_ColorRGBA*", tile), + if codepoint not in self: + raise KeyError(codepoint) + tile_p = lib.TCOD_tileset_get_tile(self._tileset_p, codepoint) + return np.frombuffer(ffi.buffer(tile_p[0 : self.tile_shape[0] * self.tile_shape[1]]), dtype=np.uint8).reshape( + *self.tile_shape, 4, copy=True ) - return tile - def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: + def __setitem__(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8], /) -> None: """Upload a tile into this array. Args: @@ -127,14 +205,14 @@ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: # Normal usage when a tile already has its own alpha channel. # The loaded tile must be the correct shape for the tileset you assign it to. # The tile is assigned to a private use area and will not conflict with any exiting codepoint. - tileset.set_tile(0x100000, imageio.imread("rgba_tile.png")) + tileset[0x100000] = imageio.imread("rgba_tile.png") # Load a greyscale tile. - tileset.set_tile(0x100001, imageio.imread("greyscale_tile.png"), mode="L") + tileset[0x100001] = imageio.imread("greyscale_tile.png", mode="L") # If you are stuck with an RGB array then you can use the red channel as the input: `rgb[:, :, 0]` # Loads an RGB sprite without a background. - tileset.set_tile(0x100002, imageio.imread("rgb_no_background.png", mode="RGBA")) + tileset[0x100002] = imageio.imread("rgb_no_background.png", mode="RGBA") # If you're stuck with an RGB array then you can pad the channel axis with an alpha of 255: # rgba = np.pad(rgb, pad_width=((0, 0), (0, 0), (0, 1)), constant_values=255) @@ -147,8 +225,9 @@ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: sprite_alpha = sprite_mask.astype(np.uint8) * 255 # Combine the RGB and alpha arrays into an RGBA array. sprite_rgba = np.append(sprite_rgb, sprite_alpha, axis=2) - tileset.set_tile(0x100003, sprite_rgba) + tileset[0x100003] = sprite_rgba + .. versionadded:: Unreleased """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: @@ -173,11 +252,71 @@ def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: ) return None + def _get_character_map(self) -> NDArray[np.intc]: + """Return the internal character mapping as an array. + + This reference will break if the tileset is modified. + """ + return np.frombuffer( + ffi.buffer(self._tileset_p.character_map[0 : self._tileset_p.character_map_length]), dtype=np.intc + ) + + def __delitem__(self, codepoint: int, /) -> None: + """Unmap a `codepoint` from this tileset. + + Tilesets are optimized for set-and-forget, deleting a tile may not free up memory. + + .. versionadded:: Unreleased + """ + if codepoint not in self: + raise KeyError(codepoint) + self._get_character_map()[codepoint] = 0 + + def __len__(self) -> int: + """Return the total count of codepoints assigned in this tileset. + + .. versionadded:: Unreleased + """ + return int((self._get_character_map() > 0).sum()) + + def __iter__(self) -> Iterator[int]: + """Iterate over the assigned codepoints of this tileset. + + .. versionadded:: Unreleased + """ + # tolist makes a copy, otherwise the reference to character_map can dangle during iteration + for i, v in enumerate(self._get_character_map().tolist()): + if v: + yield i + + def get_tile(self, codepoint: int) -> NDArray[np.uint8]: + """Return a copy of a tile for the given codepoint. + + If the tile does not exist then a blank zero array will be returned. + + The tile will have a shape of (height, width, rgba) and a dtype of + uint8. Note that most grey-scale tiles will only use the alpha + channel and will usually have a solid white color channel. + """ + try: + return self[codepoint] + except KeyError: + return np.zeros((*self.tile_shape, 4), dtype=np.uint8) + + @deprecated("Assign to a tile using 'tileset[codepoint] = tile' instead.") + def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: + """Upload a tile into this array. + + .. deprecated:: Unreleased + Was replaced with :any:`Tileset.__setitem__`. + Use ``tileset[codepoint] = tile`` syntax instead of this method. + """ + self[codepoint] = tile + def render(self, console: tcod.console.Console) -> NDArray[np.uint8]: """Render an RGBA array, using console with this tileset. - `console` is the Console object to render, this can not be the root - console. + `console` is the Console object to render, this can not be the root console. The output array will be a np.uint8 array with the shape of: ``(con_height * tile_height, con_width * tile_width, 4)``. @@ -222,8 +361,8 @@ def remap(self, codepoint: int, x: int, y: int = 0) -> None: Large values of `x` will wrap to the next row, so using `x` by itself is equivalent to `Tile Index` in the :any:`charmap-reference`. - This is normally used on loaded tilesheets. Other methods of Tileset - creation won't have reliable tile indexes. + This is typically used on tilesets loaded with :any:`load_tilesheet`. + Other methods of Tileset creation will not have reliable tile indexes. .. versionadded:: 11.12 """ @@ -379,11 +518,23 @@ def load_tilesheet(path: str | PathLike[str], columns: int, rows: int, charmap: return Tileset._claim(cdata) -def procedural_block_elements(*, tileset: Tileset) -> None: - """Overwrite the block element codepoints in `tileset` with procedurally generated glyphs. +@overload +@deprecated( + "Prefer assigning tiles using dictionary semantics:\n" + "'tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape)'" +) +def procedural_block_elements(*, tileset: Tileset) -> Tileset: ... +@overload +def procedural_block_elements(*, shape: tuple[int, int]) -> Tileset: ... + + +def procedural_block_elements(*, tileset: Tileset | None = None, shape: tuple[int, int] | None = None) -> Tileset: + """Generate and return a :any:`Tileset` with procedurally generated block elements. Args: - tileset (Tileset): A :any:`Tileset` with tiles of any shape. + tileset: A :any:`Tileset` with tiles of any shape. The codepoints of this tileset will be overwritten. + This parameter is deprecated and only shape `should` be used. + shape: The ``(height, width)`` tile size to generate. This will overwrite all of the codepoints `listed here `_ except for the shade glyphs. @@ -393,11 +544,15 @@ def procedural_block_elements(*, tileset: Tileset) -> None: .. versionadded:: 13.1 + .. versionchanged:: Unreleased + Added `shape` parameter, now returns a `Tileset`. + `tileset` parameter is deprecated. + Example:: >>> import tcod.tileset >>> tileset = tcod.tileset.Tileset(8, 8) - >>> tcod.tileset.procedural_block_elements(tileset=tileset) + >>> tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape) >>> tileset.get_tile(0x259E)[:, :, 3] # "▞" Quadrant upper right and lower left. array([[ 0, 0, 0, 0, 255, 255, 255, 255], [ 0, 0, 0, 0, 255, 255, 255, 255], @@ -426,6 +581,9 @@ def procedural_block_elements(*, tileset: Tileset) -> None: [255, 255, 255, 0, 0, 0, 0, 0], [255, 255, 255, 0, 0, 0, 0, 0]], dtype=uint8) """ + if tileset is None: + assert shape is not None + tileset = Tileset(shape[1], shape[0]) quadrants: NDArray[np.uint8] = np.zeros(tileset.tile_shape, dtype=np.uint8) half_height = tileset.tile_height // 2 half_width = tileset.tile_width // 2 @@ -453,7 +611,7 @@ def procedural_block_elements(*, tileset: Tileset) -> None: ): alpha: NDArray[np.uint8] = np.asarray((quadrants & quad_mask) != 0, dtype=np.uint8) alpha *= 255 - tileset.set_tile(codepoint, alpha) + tileset[codepoint] = alpha for codepoint, axis, fraction, negative in ( (0x2581, 0, 7, True), # "▁" Lower one eighth block. @@ -477,7 +635,8 @@ def procedural_block_elements(*, tileset: Tileset) -> None: indexes[axis] = slice(divide, None) if negative else slice(None, divide) alpha = np.zeros(tileset.tile_shape, dtype=np.uint8) alpha[tuple(indexes)] = 255 - tileset.set_tile(codepoint, alpha) + tileset[codepoint] = alpha + return tileset CHARMAP_CP437 = [ diff --git a/tests/test_tileset.py b/tests/test_tileset.py index c7281cef..78b458dd 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -1,10 +1,103 @@ """Test for tcod.tileset module.""" +from pathlib import Path + +import pytest + +import tcod.console import tcod.tileset +PROJECT_DIR = Path(__file__).parent / ".." + +TERMINAL_FONT = PROJECT_DIR / "fonts/libtcod/terminal8x8_aa_ro.png" +BDF_FONT = PROJECT_DIR / "libtcod/data/fonts/Tamzen5x9r.bdf" + +BAD_FILE = PROJECT_DIR / "CHANGELOG.md" # Any existing non-font file + def test_proc_block_elements() -> None: - tileset = tcod.tileset.Tileset(8, 8) - tcod.tileset.procedural_block_elements(tileset=tileset) tileset = tcod.tileset.Tileset(0, 0) - tcod.tileset.procedural_block_elements(tileset=tileset) + with pytest.deprecated_call(): + tcod.tileset.procedural_block_elements(tileset=tileset) + tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape) + + tileset = tcod.tileset.Tileset(8, 8) + with pytest.deprecated_call(): + tcod.tileset.procedural_block_elements(tileset=tileset) + tileset += tcod.tileset.procedural_block_elements(shape=tileset.tile_shape) + + +def test_tileset_mix() -> None: + tileset1 = tcod.tileset.Tileset(1, 1) + tileset1[ord("a")] = [[0]] + + tileset2 = tcod.tileset.Tileset(1, 1) + tileset2[ord("a")] = [[1]] + tileset2[ord("b")] = [[1]] + + assert (tileset1 + tileset2)[ord("a")].tolist() == [[[255, 255, 255, 1]]] # Replaces tile + assert (tileset1 | tileset2)[ord("a")].tolist() == [[[255, 255, 255, 0]]] # Skips existing tile + + +def test_tileset_contains() -> None: + tileset = tcod.tileset.Tileset(1, 1) + + # Missing keys + assert None not in tileset + assert ord("x") not in tileset + assert -1 not in tileset + with pytest.raises(KeyError, match=rf"{ord('x')}"): + tileset[ord("x")] + with pytest.raises(KeyError, match=rf"{ord('x')}"): + del tileset[ord("x")] + assert len(tileset) == 0 + + # Assigned tile is found + tileset[ord("x")] = [[255]] + assert ord("x") in tileset + assert len(tileset) == 1 + + # Can be deleted and reassigned + del tileset[ord("x")] + assert ord("x") not in tileset + assert len(tileset) == 0 + tileset[ord("x")] = [[255]] + assert ord("x") in tileset + assert len(tileset) == 1 + + +def test_tileset_assignment() -> None: + tileset = tcod.tileset.Tileset(1, 2) + tileset[ord("a")] = [[1], [1]] + tileset[ord("b")] = [[[255, 255, 255, 2]], [[255, 255, 255, 2]]] + + with pytest.raises(ValueError, match=r".*must be \(2, 1, 4\) or \(2, 1\), got \(2, 1, 3\)"): + tileset[ord("c")] = [[[255, 255, 255]], [[255, 255, 255]]] + + assert tileset.get_tile(ord("d")).shape == (2, 1, 4) + + +def test_tileset_render() -> None: + tileset = tcod.tileset.Tileset(1, 2) + tileset[ord("x")] = [[255], [0]] + console = tcod.console.Console(3, 2) + console.rgb[0, 0] = (ord("x"), (255, 0, 0), (0, 255, 0)) + output = tileset.render(console) + assert output.shape == (4, 3, 4) + assert output[0:2, 0].tolist() == [[255, 0, 0, 255], [0, 255, 0, 255]] + + +def test_tileset_tilesheet() -> None: + tileset = tcod.tileset.load_tilesheet(TERMINAL_FONT, 16, 16, tcod.tileset.CHARMAP_CP437) + assert tileset.tile_shape == (8, 8) + + with pytest.raises(RuntimeError): + tcod.tileset.load_tilesheet(BAD_FILE, 16, 16, tcod.tileset.CHARMAP_CP437) + + +def test_tileset_bdf() -> None: + tileset = tcod.tileset.load_bdf(BDF_FONT) + assert tileset.tile_shape == (9, 5) + + with pytest.raises(RuntimeError): + tileset = tcod.tileset.load_bdf(BAD_FILE) From 0e4275786a4dffae417993dae234365ea20f005a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Feb 2026 01:03:48 -0800 Subject: [PATCH 1072/1101] Remove unnecessary Ruff ignores Ignores no longer needed, likely due to the convention being provided --- pyproject.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 25cc234f..211448c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,20 +153,12 @@ line-length = 120 select = ["ALL"] ignore = [ "COM", # flake8-commas - "D203", # one-blank-line-before-class - "D204", # one-blank-line-after-class - "D213", # multi-line-summary-second-line - "D407", # dashed-underline-after-section - "D408", # section-underline-after-name - "D409", # section-underline-matches-section-length - "D206", # indent-with-spaces "E501", # line-too-long "PYI064", # redundant-final-literal "S101", # assert "S301", # suspicious-pickle-usage "S311", # suspicious-non-cryptographic-random-usage "SLF001", # private-member-access - "W191", # tab-indentation ] [tool.ruff.lint.per-file-ignores] "**/{tests}/*" = [ From 4e50f3347a6da51e2cc5d1760489909249401506 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Feb 2026 01:15:13 -0800 Subject: [PATCH 1073/1101] Remove old Python 2 hack --- .gitattributes | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 740e182d..2dc9f280 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,3 @@ # Custom for this project *.bat eol=crlf *.txt eol=crlf - -# Fix an issue with Python 2 on Linux -tcod/libtcod_cdef.h eol=lf From 22d80553b585b25c68567f74ac252f4e4cf65ab4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Feb 2026 17:32:00 -0800 Subject: [PATCH 1074/1101] Prepare 20.1.0 release. --- CHANGELOG.md | 4 +++- tcod/tileset.py | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d526b9..39667759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [20.1.0] - 2026-02-25 + ### Added - `Tileset` now supports `MutableMapping` semantics. Can get, set, or iterate over tiles as if it were a dictionary containing tile glyph arrays. - Also supports `+=` and `|=` with other tilesets or mappings. + Also supports `+`, `|`, `+=`, and `|=` with other tilesets or mappings to merge them into a single Tileset. - `tcod.tileset.procedural_block_elements` can take a tile shape and return a tileset. ### Deprecated diff --git a/tcod/tileset.py b/tcod/tileset.py index 9ea4b1d7..58ddf058 100644 --- a/tcod/tileset.py +++ b/tcod/tileset.py @@ -37,7 +37,7 @@ class Tileset(MutableMapping[int, "NDArray[np.uint8]"]): """A collection of graphical tiles. - .. versionchanged:: Unreleased + .. versionchanged:: 20.1 Is now a :class:`collections.abc.MutableMapping` type. """ @@ -86,7 +86,7 @@ def __copy__(self) -> Self: This is not an exact copy. :any:`remap` will not work on the clone. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ clone = self.__class__(self.tile_width, self.tile_height) for codepoint, tile in self.items(): @@ -109,7 +109,7 @@ def __iadd__( ) -> Self: """Add tiles to this tileset inplace, prefers replacing tiles. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ for codepoint, tile in self._iter_items(other): self[codepoint] = tile @@ -122,7 +122,7 @@ def __add__( ) -> Self: """Combine tiles with this tileset, prefers replacing tiles. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ clone = copy.copy(self) clone += other @@ -135,7 +135,7 @@ def __ior__( ) -> Self: """Add tiles to this tileset inplace, keeps existing tiles instead of replacing them. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ for codepoint, tile in self._iter_items(other): if codepoint not in self: @@ -149,7 +149,7 @@ def __or__( ) -> Self: """Combine tiles with this tileset, prefers keeping existing tiles instead of replacing them. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ clone = copy.copy(self) clone |= other @@ -170,7 +170,7 @@ def __getitem__(self, codepoint: int, /) -> NDArray[np.uint8]: The tile will have a shape of (height, width, rgba) and a dtype of uint8. Note that most grey-scale tilesets will only use the alpha channel with a solid white color channel. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ if codepoint not in self: raise KeyError(codepoint) @@ -227,7 +227,7 @@ def __setitem__(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8], /) -> sprite_rgba = np.append(sprite_rgb, sprite_alpha, axis=2) tileset[0x100003] = sprite_rgba - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ tile = np.ascontiguousarray(tile, dtype=np.uint8) if tile.shape == self.tile_shape: @@ -266,7 +266,7 @@ def __delitem__(self, codepoint: int, /) -> None: Tilesets are optimized for set-and-forget, deleting a tile may not free up memory. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ if codepoint not in self: raise KeyError(codepoint) @@ -275,14 +275,14 @@ def __delitem__(self, codepoint: int, /) -> None: def __len__(self) -> int: """Return the total count of codepoints assigned in this tileset. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ return int((self._get_character_map() > 0).sum()) def __iter__(self) -> Iterator[int]: """Iterate over the assigned codepoints of this tileset. - .. versionadded:: Unreleased + .. versionadded:: 20.1 """ # tolist makes a copy, otherwise the reference to character_map can dangle during iteration for i, v in enumerate(self._get_character_map().tolist()): @@ -307,7 +307,7 @@ def get_tile(self, codepoint: int) -> NDArray[np.uint8]: def set_tile(self, codepoint: int, tile: ArrayLike | NDArray[np.uint8]) -> None: """Upload a tile into this array. - .. deprecated:: Unreleased + .. deprecated:: 20.1 Was replaced with :any:`Tileset.__setitem__`. Use ``tileset[codepoint] = tile`` syntax instead of this method. """ @@ -544,7 +544,7 @@ def procedural_block_elements(*, tileset: Tileset | None = None, shape: tuple[in .. versionadded:: 13.1 - .. versionchanged:: Unreleased + .. versionchanged:: 20.1 Added `shape` parameter, now returns a `Tileset`. `tileset` parameter is deprecated. From 34a4ab0c7845187365ef1edc29ee3944c1c8548f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:01:50 +0000 Subject: [PATCH 1075/1101] Bump the github-actions group with 2 updates Bumps the github-actions group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 6 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) Updates `actions/download-artifact` from 7 to 8 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d52a4140..0ae29156 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -71,7 +71,7 @@ jobs: run: pip install build - name: Build source distribution run: python -m build --sdist - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: sdist path: dist/tcod-*.tar.gz @@ -171,7 +171,7 @@ jobs: if: runner.os != 'Windows' run: cat /tmp/xvfb.log - uses: codecov/codecov-action@v5 - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 if: runner.os == 'Windows' with: name: wheels-windows-${{ matrix.architecture }}-${{ matrix.python-version }} @@ -290,7 +290,7 @@ jobs: BUILD_DESC=${BUILD_DESC//\*} echo BUILD_DESC=${BUILD_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: wheels-linux-${{ matrix.arch }}-${{ env.BUILD_DESC }} path: wheelhouse/*.whl @@ -339,7 +339,7 @@ jobs: PYTHON_DESC=${PYTHON_DESC//\*/X} echo PYTHON_DESC=${PYTHON_DESC} >> $GITHUB_ENV - name: Archive wheel - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: wheels-macos-${{ env.PYTHON_DESC }} path: wheelhouse/*.whl @@ -367,7 +367,7 @@ jobs: CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide - name: Archive wheel - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: pyodide path: wheelhouse/*.whl @@ -385,11 +385,11 @@ jobs: permissions: id-token: write # Attestation steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: sdist path: dist/ - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: pattern: wheels-* path: dist/ From 936b4585277e6ce457006b4abae159e8e8db1cf3 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 9 Mar 2026 09:24:46 -0700 Subject: [PATCH 1076/1101] Allow wrapping SDL windows via WindowID --- CHANGELOG.md | 4 ++++ tcod/sdl/video.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39667759..3a8272d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `tcod.sdl.video.Window` now accepts an SDL WindowID. + ## [20.1.0] - 2026-02-25 ### Added diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index a7d6c330..b15d8d0b 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -174,7 +174,14 @@ class Window: When using the libtcod :any:`Context` you can access its `Window` via :any:`Context.sdl_window`. """ - def __init__(self, sdl_window_p: Any) -> None: # noqa: ANN401 + def __init__(self, sdl_window_p: Any | int) -> None: # noqa: ANN401 + """Wrap a SDL_Window pointer or SDL WindowID. + + .. versionchanged:: Unreleased + Now accepts `int` types as an SDL WindowID. + """ + if isinstance(sdl_window_p, int): + sdl_window_p = _check_p(lib.SDL_GetWindowFromID(sdl_window_p)) if ffi.typeof(sdl_window_p) is not ffi.typeof("struct SDL_Window*"): msg = "sdl_window_p must be {!r} type (was {!r}).".format( ffi.typeof("struct SDL_Window*"), ffi.typeof(sdl_window_p) @@ -186,11 +193,13 @@ def __init__(self, sdl_window_p: Any) -> None: # noqa: ANN401 self.p = sdl_window_p def __eq__(self, other: object) -> bool: + """Return True if `self` and `other` wrap the same window.""" if not isinstance(other, Window): return NotImplemented return bool(self.p == other.p) def __hash__(self) -> int: + """Return the hash of this instances SDL window pointer.""" return hash(self.p) def _as_property_pointer(self) -> Any: # noqa: ANN401 From 1ab50f7ca01495f672ad71d97f30aa6717fc47cd Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 11 Mar 2026 10:14:13 -0700 Subject: [PATCH 1077/1101] Refactor event classes into dataclasses Removes tons of boilerplate Improve docs by inheriting members in event subclasses Fix classes being undocumented due to being missing from `__all__` Begin to phase out `Event.type` attribute Hide linter warnings for unmaintained EventDispatch --- CHANGELOG.md | 12 + docs/tcod/event.rst | 1 + pyproject.toml | 1 + tcod/event.py | 799 ++++++++++++++++---------------------------- tcod/sdl/mouse.py | 6 +- 5 files changed, 310 insertions(+), 509 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8272d7..c612d322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `tcod.sdl.video.Window` now accepts an SDL WindowID. +### Changed + +- Event classes are now more strict with attribute types +- Event class initializers are keyword-only and no longer take a type parameter, with exceptions. + Generally event class initialization is an internal process. +- `MouseButtonEvent` no longer a subclass of `MouseState`. + +### Deprecated + +- `Event.type` is deprecated except for special cases such as `ControllerDevice`, `WindowEvent`, etc. +- `MouseButtonEvent.state` is deprecated, replaced by the existing `.button` attribute. + ## [20.1.0] - 2026-02-25 ### Added diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 7f2f059e..3f2d3eeb 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -3,6 +3,7 @@ SDL Event Handling ``tcod.event`` .. automodule:: tcod.event :members: + :inherited-members: object, int, str, tuple, Event :member-order: bysource :exclude-members: KeySym, Scancode, Modifier, get, wait diff --git a/pyproject.toml b/pyproject.toml index 211448c4..b8c7cc34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ license-files = [ "libtcod/LIBTCOD-CREDITS.txt", ] dependencies = [ + "attrs>=25.2.0", "cffi>=1.15", 'numpy>=1.21.4; implementation_name != "pypy"', "typing_extensions>=4.12.2", diff --git a/tcod/event.py b/tcod/event.py index ca3a3eb1..d6cf19e2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -87,10 +87,11 @@ import sys import warnings from collections.abc import Callable, Iterator, Mapping -from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeVar, overload +from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypeVar, overload +import attrs import numpy as np -from typing_extensions import deprecated +from typing_extensions import Self, deprecated import tcod.context import tcod.event_constants @@ -107,6 +108,12 @@ T = TypeVar("T") _EventType = TypeVar("_EventType", bound="Event") +_C_SDL_Event: TypeAlias = Any +"""A CFFI pointer to an SDL_Event union. + +See SDL docs: https://wiki.libsdl.org/SDL3/SDL_Event +""" + class _ConstantsWithPrefix(Mapping[int, str]): def __init__(self, constants: Mapping[int, str]) -> None: @@ -266,6 +273,7 @@ class MouseButton(enum.IntEnum): """Forward mouse button.""" def __repr__(self) -> str: + """Return the enum name, excluding the value.""" return f"{self.__class__.__name__}.{self.name}" @@ -287,143 +295,116 @@ class MouseButtonMask(enum.IntFlag): """Forward mouse button is held.""" def __repr__(self) -> str: + """Return the bitwise OR flag combination of this value.""" if self.value == 0: return f"{self.__class__.__name__}(0)" return "|".join(f"{self.__class__.__name__}.{self.__class__(bit).name}" for bit in self.__class__ if bit & self) +@attrs.define(slots=True, kw_only=True) class Event: - """The base event class. + """The base event class.""" - Attributes: - type (str): This events type. - sdl_event: When available, this holds a python-cffi 'SDL_Event*' - pointer. All sub-classes have this attribute. - """ + sdl_event: _C_SDL_Event = attrs.field(default=None, eq=False, kw_only=True, repr=False) + """When available, this holds a python-cffi 'SDL_Event*' pointer. All sub-classes have this attribute.""" - def __init__(self, type: str | None = None) -> None: - if type is None: - type = self.__class__.__name__.upper() - self.type: Final = type - self.sdl_event = None + @property + @deprecated("The Event.type attribute is deprecated, use isinstance instead.") + def type(self) -> str: + """This events type. + + .. deprecated:: Unreleased + Using this attribute is now actively discouraged. Use :func:`isinstance` or :ref:`match`. + """ + type_override: str | None = getattr(self, "_type", None) + if type_override is not None: + return type_override + return self.__class__.__name__.upper() @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Event: - """Return a class instance from a python-cffi 'SDL_Event*' pointer.""" - raise NotImplementedError + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Event: + """Return a class instance from a python-cffi 'SDL_Event*' pointer. - def __str__(self) -> str: - return f"" + .. versionchanged:: Unreleased + This method was unsuitable for the public API and is now private. + """ + raise NotImplementedError +@attrs.define(slots=True, kw_only=True) class Quit(Event): """An application quit request event. For more info on when this event is triggered see: https://wiki.libsdl.org/SDL_EventType#SDL_QUIT - - Attributes: - type (str): Always "QUIT". """ @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Quit: - self = cls() - self.sdl_event = sdl_event - return self - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}()" + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls(sdl_event=sdl_event) +@attrs.define(slots=True, kw_only=True) class KeyboardEvent(Event): """Base keyboard event. - Attributes: - type (str): Will be "KEYDOWN" or "KEYUP", depending on the event. - scancode (Scancode): The keyboard scan-code, this is the physical location - of the key on the keyboard rather than the keys symbol. - sym (KeySym): The keyboard symbol. - mod (Modifier): A bitmask of the currently held modifier keys. - - For example, if shift is held then - ``event.mod & tcod.event.Modifier.SHIFT`` will evaluate to a true - value. - - repeat (bool): True if this event exists because of key repeat. - .. versionchanged:: 12.5 `scancode`, `sym`, and `mod` now use their respective enums. """ - def __init__(self, scancode: int, sym: int, mod: int, repeat: bool = False) -> None: - super().__init__() - self.scancode = Scancode(scancode) - self.sym = KeySym(sym) - self.mod = Modifier(mod) - self.repeat = repeat + scancode: Scancode + """The keyboard scan-code, this is the physical location + of the key on the keyboard rather than the keys symbol.""" + sym: KeySym + """The keyboard symbol.""" + mod: Modifier + """A bitmask of the currently held modifier keys. + + For example, if shift is held then + ``event.mod & tcod.event.Modifier.SHIFT`` will evaluate to a true + value. + """ + repeat: bool = False + """True if this event exists because of key repeat.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Any: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: keysym = sdl_event.key - self = cls(keysym.scancode, keysym.key, keysym.mod, bool(sdl_event.key.repeat)) - self.sdl_event = sdl_event - return self - - def __repr__(self) -> str: - return "tcod.event.{}(scancode={!r}, sym={!r}, mod={!r}{})".format( - self.__class__.__name__, - self.scancode, - self.sym, - self.mod, - ", repeat=True" if self.repeat else "", + return cls( + scancode=Scancode(keysym.scancode), + sym=KeySym(keysym.key), + mod=Modifier(keysym.mod), + repeat=bool(sdl_event.key.repeat), + sdl_event=sdl_event, ) - def __str__(self) -> str: - return self.__repr__().replace("tcod.event.", "") - +@attrs.define(slots=True, kw_only=True) class KeyDown(KeyboardEvent): pass +@attrs.define(slots=True, kw_only=True) class KeyUp(KeyboardEvent): pass +@attrs.define(slots=True, kw_only=True) class MouseState(Event): """Mouse state. - Attributes: - type (str): Always "MOUSESTATE". - position (Point): The position coordinates of the mouse. - tile (Point): The integer tile coordinates of the mouse on the screen. - state (int): A bitmask of which mouse buttons are currently held. - - Will be a combination of the following names: - - * tcod.event.BUTTON_LMASK - * tcod.event.BUTTON_MMASK - * tcod.event.BUTTON_RMASK - * tcod.event.BUTTON_X1MASK - * tcod.event.BUTTON_X2MASK - .. versionadded:: 9.3 .. versionchanged:: 15.0 Renamed `pixel` attribute to `position`. """ - def __init__( - self, - position: tuple[float, float] = (0, 0), - tile: tuple[float, float] | None = (0, 0), - state: int = 0, - ) -> None: - super().__init__() - self.position = Point(*position) - self._tile = Point(*tile) if tile is not None else None - self.state = state + position: Point = attrs.field(default=Point(0, 0)) + """The position coordinates of the mouse.""" + _tile: Point | None = attrs.field(default=Point(0, 0), alias="tile") + """The integer tile coordinates of the mouse on the screen.""" + state: MouseButtonMask = attrs.field(default=MouseButtonMask(0)) + """A bitmask of which mouse buttons are currently held.""" @property @deprecated("The mouse.pixel attribute is deprecated. Use mouse.position instead.") @@ -450,37 +431,11 @@ def tile(self) -> Point: def tile(self, xy: tuple[float, float]) -> None: self._tile = Point(*xy) - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self._tile or (0, 0))!r}, state={MouseButtonMask(self.state)})" - - def __str__(self) -> str: - return ("<%s, position=(x=%i, y=%i), tile=(x=%i, y=%i), state=%s>") % ( - super().__str__().strip("<>"), - *self.position, - *(self._tile or (0, 0)), - MouseButtonMask(self.state), - ) - +@attrs.define(slots=True, kw_only=True) class MouseMotion(MouseState): """Mouse motion event. - Attributes: - type (str): Always "MOUSEMOTION". - position (Point): The pixel coordinates of the mouse. - motion (Point): The pixel delta. - tile (Point): The integer tile coordinates of the mouse on the screen. - tile_motion (Point): The integer tile delta. - state (int): A bitmask of which mouse buttons are currently held. - - Will be a combination of the following names: - - * tcod.event.BUTTON_LMASK - * tcod.event.BUTTON_MMASK - * tcod.event.BUTTON_RMASK - * tcod.event.BUTTON_X1MASK - * tcod.event.BUTTON_X2MASK - .. versionchanged:: 15.0 Renamed `pixel` attribute to `position`. Renamed `pixel_motion` attribute to `motion`. @@ -489,17 +444,10 @@ class MouseMotion(MouseState): `position` and `motion` now use floating point coordinates. """ - def __init__( - self, - position: tuple[float, float] = (0, 0), - motion: tuple[float, float] = (0, 0), - tile: tuple[float, float] | None = (0, 0), - tile_motion: tuple[float, float] | None = (0, 0), - state: int = 0, - ) -> None: - super().__init__(position, tile, state) - self.motion = Point(*motion) - self._tile_motion = Point(*tile_motion) if tile_motion is not None else None + motion: Point = attrs.field(default=Point(0, 0)) + """The pixel delta.""" + _tile_motion: Point | None = attrs.field(default=Point(0, 0), alias="tile_motion") + """The tile delta.""" @property @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") @@ -528,149 +476,99 @@ def tile_motion(self, xy: tuple[float, float]) -> None: self._tile_motion = Point(*xy) @classmethod - def from_sdl_event(cls, sdl_event: Any) -> MouseMotion: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: motion = sdl_event.motion + state = MouseButtonMask(motion.state) - pixel = motion.x, motion.y - pixel_motion = motion.xrel, motion.yrel + pixel = Point(motion.x, motion.y) + pixel_motion = Point(motion.xrel, motion.yrel) subtile = _pixel_to_tile(*pixel) if subtile is None: - self = cls(pixel, pixel_motion, None, None, motion.state) + self = cls(position=pixel, motion=pixel_motion, tile=None, tile_motion=None, state=state) else: - tile = int(subtile[0]), int(subtile[1]) + tile = Point(int(subtile[0]), int(subtile[1])) prev_pixel = pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1] prev_subtile = _pixel_to_tile(*prev_pixel) or (0, 0) prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) - tile_motion = tile[0] - prev_tile[0], tile[1] - prev_tile[1] - self = cls(pixel, pixel_motion, tile, tile_motion, motion.state) + tile_motion = Point(tile[0] - prev_tile[0], tile[1] - prev_tile[1]) + self = cls(position=pixel, motion=pixel_motion, tile=tile, tile_motion=tile_motion, state=state) self.sdl_event = sdl_event return self - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, motion={tuple(self.motion)!r}, tile={tuple(self.tile)!r}, tile_motion={tuple(self.tile_motion)!r}, state={MouseButtonMask(self.state)!r})" - def __str__(self) -> str: - return ("<%s, motion=(x=%i, y=%i), tile_motion=(x=%i, y=%i)>") % ( - super().__str__().strip("<>"), - *self.motion, - *self.tile_motion, - ) - - -class MouseButtonEvent(MouseState): +@attrs.define(slots=True, kw_only=True) +class MouseButtonEvent(Event): """Mouse button event. - Attributes: - type (str): Will be "MOUSEBUTTONDOWN" or "MOUSEBUTTONUP", - depending on the event. - position (Point): The pixel coordinates of the mouse. - tile (Point): The tile coordinates of the mouse on the screen. - button (int): Which mouse button. - - This will be one of the following names: - - * tcod.event.BUTTON_LEFT - * tcod.event.BUTTON_MIDDLE - * tcod.event.BUTTON_RIGHT - * tcod.event.BUTTON_X1 - * tcod.event.BUTTON_X2 - .. versionchanged:: 19.0 `position` and `tile` now use floating point coordinates. - """ - def __init__( - self, - pixel: tuple[float, float] = (0, 0), - tile: tuple[float, float] | None = (0, 0), - button: int = 0, - ) -> None: - super().__init__(pixel, tile, button) + .. versionchanged:: Unreleased + No longer a subclass of :any:`MouseState`. + """ - @property - def button(self) -> int: - return self.state + position: Point = attrs.field(default=Point(0, 0)) + """The pixel coordinates of the mouse.""" + _tile: Point | None = attrs.field(default=Point(0, 0), alias="tile") + """The tile coordinates of the mouse on the screen.""" + button: MouseButton + """Which mouse button index was pressed or released in this event. - @button.setter - def button(self, value: int) -> None: - self.state = value + .. versionchanged:: Unreleased + Is now strictly a :any:`MouseButton` type. + """ @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Any: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: button = sdl_event.button - pixel = button.x, button.y + pixel = Point(button.x, button.y) subtile = _pixel_to_tile(*pixel) if subtile is None: - tile: tuple[float, float] | None = None + tile: Point | None = None else: - tile = float(subtile[0]), float(subtile[1]) - self = cls(pixel, tile, button.button) + tile = Point(float(subtile[0]), float(subtile[1])) + self = cls(position=pixel, tile=tile, button=MouseButton(button.button)) self.sdl_event = sdl_event return self - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(position={tuple(self.position)!r}, tile={tuple(self.tile)!r}, button={MouseButton(self.button)!r})" - - def __str__(self) -> str: - return "" % ( - self.type, - *self.position, - *self.tile, - MouseButton(self.button), - ) + @property + @deprecated( + "This attribute is for mouse state and mouse motion only. Use `event.button` instead.", category=FutureWarning + ) + def state(self) -> int: # noqa: D102 # Skip docstring for deprecated property + return int(self.button) +@attrs.define(slots=True, kw_only=True) class MouseButtonDown(MouseButtonEvent): """Same as MouseButtonEvent but with ``type="MouseButtonDown"``.""" +@attrs.define(slots=True, kw_only=True) class MouseButtonUp(MouseButtonEvent): """Same as MouseButtonEvent but with ``type="MouseButtonUp"``.""" +@attrs.define(slots=True, kw_only=True) class MouseWheel(Event): - """Mouse wheel event. - - Attributes: - type (str): Always "MOUSEWHEEL". - x (int): Horizontal scrolling. A positive value means scrolling right. - y (int): Vertical scrolling. A positive value means scrolling away from - the user. - flipped (bool): If True then the values of `x` and `y` are the opposite - of their usual values. This depends on the settings of - the Operating System. + """Mouse wheel event.""" + + x: int + """Horizontal scrolling. A positive value means scrolling right.""" + y: int + """Vertical scrolling. A positive value means scrolling away from the user.""" + flipped: bool + """If True then the values of `x` and `y` are the opposite of their usual values. + This depends on the settings of the Operating System. """ - def __init__(self, x: int, y: int, flipped: bool = False) -> None: - super().__init__() - self.x = x - self.y = y - self.flipped = flipped - @classmethod - def from_sdl_event(cls, sdl_event: Any) -> MouseWheel: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: wheel = sdl_event.wheel - self = cls(wheel.x, wheel.y, bool(wheel.direction)) - self.sdl_event = sdl_event - return self - - def __repr__(self) -> str: - return "tcod.event.%s(x=%i, y=%i%s)" % ( - self.__class__.__name__, - self.x, - self.y, - ", flipped=True" if self.flipped else "", - ) - - def __str__(self) -> str: - return "<%s, x=%i, y=%i, flipped=%r>" % ( - super().__str__().strip("<>"), - self.x, - self.y, - self.flipped, - ) + return cls(x=int(wheel.x), y=int(wheel.y), flipped=bool(wheel.direction), sdl_event=sdl_event) +@attrs.define(slots=True, kw_only=True) class TextInput(Event): """SDL text input event. @@ -678,33 +576,21 @@ class TextInput(Event): These events are not enabled by default since `19.0`. Use :any:`Window.start_text_input` to enable this event. - - Attributes: - type (str): Always "TEXTINPUT". - text (str): A Unicode string with the input. """ - def __init__(self, text: str) -> None: - super().__init__() - self.text = text + text: str + """A Unicode string with the input.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> TextInput: - self = cls(ffi.string(sdl_event.text.text, 32).decode("utf8")) - self.sdl_event = sdl_event - return self - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(text={self.text!r})" - - def __str__(self) -> str: - return "<{}, text={!r})".format(super().__str__().strip("<>"), self.text) + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls(text=str(ffi.string(sdl_event.text.text, 32), encoding="utf8"), sdl_event=sdl_event) +@attrs.define(slots=True, kw_only=True) class WindowEvent(Event): """A window event.""" - type: Final[ # type: ignore[misc] # Narrowing final type. + type: Final[ # Narrowing final type. Literal[ "WindowShown", "WindowHidden", @@ -726,44 +612,50 @@ class WindowEvent(Event): """The current window event. This can be one of various options.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> WindowEvent | Undefined: - if sdl_event.type not in cls._WINDOW_TYPES: - return Undefined.from_sdl_event(sdl_event) - event_type: Final = cls._WINDOW_TYPES[sdl_event.type] + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined: + if sdl_event.type not in _WINDOW_TYPES_FROM_ENUM: + return Undefined._from_sdl_event(sdl_event) + event_type: Final = _WINDOW_TYPES_FROM_ENUM[sdl_event.type] self: WindowEvent if sdl_event.type == lib.SDL_EVENT_WINDOW_MOVED: - self = WindowMoved(sdl_event.window.data1, sdl_event.window.data2) + self = WindowMoved(x=int(sdl_event.window.data1), y=int(sdl_event.window.data2), sdl_event=sdl_event) elif sdl_event.type in ( lib.SDL_EVENT_WINDOW_RESIZED, lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, ): - self = WindowResized(event_type, sdl_event.window.data1, sdl_event.window.data2) + self = WindowResized( + type=event_type, # type: ignore[arg-type] # Currently NOT validated + width=int(sdl_event.window.data1), + height=int(sdl_event.window.data2), + sdl_event=sdl_event, + ) else: - self = cls(event_type) - self.sdl_event = sdl_event + self = cls( + type=event_type, # type: ignore[arg-type] # Currently NOT validated + sdl_event=sdl_event, + ) return self - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r})" - - _WINDOW_TYPES: Final = { - lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown", - lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden", - lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed", - lib.SDL_EVENT_WINDOW_MOVED: "WindowMoved", - lib.SDL_EVENT_WINDOW_RESIZED: "WindowResized", - lib.SDL_EVENT_WINDOW_MINIMIZED: "WindowMinimized", - lib.SDL_EVENT_WINDOW_MAXIMIZED: "WindowMaximized", - lib.SDL_EVENT_WINDOW_RESTORED: "WindowRestored", - lib.SDL_EVENT_WINDOW_MOUSE_ENTER: "WindowEnter", - lib.SDL_EVENT_WINDOW_MOUSE_LEAVE: "WindowLeave", - lib.SDL_EVENT_WINDOW_FOCUS_GAINED: "WindowFocusGained", - lib.SDL_EVENT_WINDOW_FOCUS_LOST: "WindowFocusLost", - lib.SDL_EVENT_WINDOW_CLOSE_REQUESTED: "WindowClose", - lib.SDL_EVENT_WINDOW_HIT_TEST: "WindowHitTest", - } + +_WINDOW_TYPES_FROM_ENUM: Final = { + lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown", + lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden", + lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed", + lib.SDL_EVENT_WINDOW_MOVED: "WindowMoved", + lib.SDL_EVENT_WINDOW_RESIZED: "WindowResized", + lib.SDL_EVENT_WINDOW_MINIMIZED: "WindowMinimized", + lib.SDL_EVENT_WINDOW_MAXIMIZED: "WindowMaximized", + lib.SDL_EVENT_WINDOW_RESTORED: "WindowRestored", + lib.SDL_EVENT_WINDOW_MOUSE_ENTER: "WindowEnter", + lib.SDL_EVENT_WINDOW_MOUSE_LEAVE: "WindowLeave", + lib.SDL_EVENT_WINDOW_FOCUS_GAINED: "WindowFocusGained", + lib.SDL_EVENT_WINDOW_FOCUS_LOST: "WindowFocusLost", + lib.SDL_EVENT_WINDOW_CLOSE_REQUESTED: "WindowClose", + lib.SDL_EVENT_WINDOW_HIT_TEST: "WindowHitTest", +} +@attrs.define(slots=True, kw_only=True) class WindowMoved(WindowEvent): """Window moved event. @@ -772,25 +664,14 @@ class WindowMoved(WindowEvent): y (int): Movement on the y-axis. """ - type: Final[Literal["WINDOWMOVED"]] # type: ignore[assignment,misc] + type: Final[Literal["WINDOWMOVED"]] = "WINDOWMOVED" # type: ignore[assignment,misc] """Always "WINDOWMOVED".""" - def __init__(self, x: int, y: int) -> None: - super().__init__(None) - self.x = x - self.y = y - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, x={self.x!r}, y={self.y!r})" - - def __str__(self) -> str: - return "<{}, x={!r}, y={!r})".format( - super().__str__().strip("<>"), - self.x, - self.y, - ) + x: int + y: int +@attrs.define(slots=True, kw_only=True) class WindowResized(WindowEvent): """Window resized event. @@ -802,50 +683,31 @@ class WindowResized(WindowEvent): Removed "WindowSizeChanged" type. """ - type: Final[Literal["WindowResized"]] # type: ignore[misc] + type: Final[Literal["WindowResized"]] = "WindowResized" # type: ignore[misc] """Always "WindowResized".""" - def __init__(self, type: str, width: int, height: int) -> None: - super().__init__(type) - self.width = width - self.height = height - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, width={self.width!r}, height={self.height!r})" - - def __str__(self) -> str: - return "<{}, width={!r}, height={!r})".format( - super().__str__().strip("<>"), - self.width, - self.height, - ) + width: int + height: int +@attrs.define(slots=True, kw_only=True) class JoystickEvent(Event): """A base class for joystick events. .. versionadded:: 13.8 """ - def __init__(self, type: str, which: int) -> None: - super().__init__(type) - self.which = which - """The ID of the joystick this event is for.""" + which: int + """The ID of the joystick this event is for.""" @property def joystick(self) -> tcod.sdl.joystick.Joystick: - if self.type == "JOYDEVICEADDED": + if isinstance(self, JoystickDevice) and self.type == "JOYDEVICEADDED": return tcod.sdl.joystick.Joystick._open(self.which) return tcod.sdl.joystick.Joystick._from_instance_id(self.which) - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which})" - - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, which={self.which}>" - +@attrs.define(slots=True, kw_only=True) class JoystickAxis(JoystickEvent): """When a joystick axis changes in value. @@ -855,31 +717,19 @@ class JoystickAxis(JoystickEvent): :any:`tcod.sdl.joystick` """ - which: int - """The ID of the joystick this event is for.""" + _type: Final[Literal["JOYAXISMOTION"]] = "JOYAXISMOTION" - def __init__(self, type: str, which: int, axis: int, value: int) -> None: - super().__init__(type, which) - self.axis = axis - """The index of the changed axis.""" - self.value = value - """The raw value of the axis in the range -32768 to 32767.""" + axis: int + """The index of the changed axis.""" + value: int + """The raw value of the axis in the range -32768 to 32767.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> JoystickAxis: - return cls("JOYAXISMOTION", sdl_event.jaxis.which, sdl_event.jaxis.axis, sdl_event.jaxis.value) - - def __repr__(self) -> str: - return ( - f"tcod.event.{self.__class__.__name__}" - f"(type={self.type!r}, which={self.which}, axis={self.axis}, value={self.value})" - ) - - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, axis={self.axis}, value={self.value}>" + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls(which=int(sdl_event.jaxis.which), axis=int(sdl_event.jaxis.axis), value=int(sdl_event.jaxis.value)) +@attrs.define(slots=True, kw_only=True) class JoystickBall(JoystickEvent): """When a joystick ball is moved. @@ -889,35 +739,26 @@ class JoystickBall(JoystickEvent): :any:`tcod.sdl.joystick` """ - which: int - """The ID of the joystick this event is for.""" + _type: Final[Literal["JOYBALLMOTION"]] = "JOYBALLMOTION" - def __init__(self, type: str, which: int, ball: int, dx: int, dy: int) -> None: - super().__init__(type, which) - self.ball = ball - """The index of the moved ball.""" - self.dx = dx - """The X motion of the ball.""" - self.dy = dy - """The Y motion of the ball.""" + ball: int + """The index of the moved ball.""" + dx: int + """The X motion of the ball.""" + dy: int + """The Y motion of the ball.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> JoystickBall: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: return cls( - "JOYBALLMOTION", sdl_event.jball.which, sdl_event.jball.ball, sdl_event.jball.xrel, sdl_event.jball.yrel - ) - - def __repr__(self) -> str: - return ( - f"tcod.event.{self.__class__.__name__}" - f"(type={self.type!r}, which={self.which}, ball={self.ball}, dx={self.dx}, dy={self.dy})" + which=int(sdl_event.jball.which), + ball=int(sdl_event.jball.ball), + dx=int(sdl_event.jball.xrel), + dy=int(sdl_event.jball.yrel), ) - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, ball={self.ball}, dx={self.dx}, dy={self.dy}>" - +@attrs.define(slots=True, kw_only=True) class JoystickHat(JoystickEvent): """When a joystick hat changes direction. @@ -927,28 +768,20 @@ class JoystickHat(JoystickEvent): :any:`tcod.sdl.joystick` """ - which: int - """The ID of the joystick this event is for.""" + _type: Final[Literal["JOYHATMOTION"]] = "JOYHATMOTION" - def __init__(self, type: str, which: int, x: Literal[-1, 0, 1], y: Literal[-1, 0, 1]) -> None: - super().__init__(type, which) - self.x = x - """The new X direction of the hat.""" - self.y = y - """The new Y direction of the hat.""" + x: Literal[-1, 0, 1] + """The new X direction of the hat.""" + y: Literal[-1, 0, 1] + """The new Y direction of the hat.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> JoystickHat: - return cls("JOYHATMOTION", sdl_event.jhat.which, *_HAT_DIRECTIONS[sdl_event.jhat.hat]) - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which}, x={self.x}, y={self.y})" - - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, x={self.x}, y={self.y}>" + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + x, y = _HAT_DIRECTIONS[sdl_event.jhat.hat] + return cls(which=int(sdl_event.jhat.which), x=x, y=y) +@attrs.define(slots=True, kw_only=True) class JoystickButton(JoystickEvent): """When a joystick button is pressed or released. @@ -964,35 +797,31 @@ class JoystickButton(JoystickEvent): print(f"Released {button=} on controller {which}.") """ - which: int - """The ID of the joystick this event is for.""" - - def __init__(self, type: str, which: int, button: int) -> None: - super().__init__(type, which) - self.button = button - """The index of the button this event is for.""" + button: int + """The index of the button this event is for.""" + pressed: bool + """True if the button was pressed, False if the button was released.""" @property - def pressed(self) -> bool: - """True if the joystick button has been pressed, False when the button was released.""" - return self.type == "JOYBUTTONDOWN" + @deprecated("Check 'JoystickButton.pressed' instead of '.type'.") + def type(self) -> Literal["JOYBUTTONUP", "JOYBUTTONDOWN"]: + """Button state as a string. - @classmethod - def from_sdl_event(cls, sdl_event: Any) -> JoystickButton: - type = { - lib.SDL_EVENT_JOYSTICK_BUTTON_DOWN: "JOYBUTTONDOWN", - lib.SDL_EVENT_JOYSTICK_BUTTON_UP: "JOYBUTTONUP", - }[sdl_event.type] - return cls(type, sdl_event.jbutton.which, sdl_event.jbutton.button) - - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which}, button={self.button})" + .. deprecated:: Unreleased + Use :any:`pressed` instead. + """ + return ("JOYBUTTONUP", "JOYBUTTONDOWN")[self.pressed] - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, button={self.button}>" + @classmethod + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls( + which=int(sdl_event.jbutton.which), + button=int(sdl_event.jbutton.button), + pressed=bool(sdl_event.jbutton.down), + ) +@attrs.define(slots=True, kw_only=True) class JoystickDevice(JoystickEvent): """An event for when a joystick is added or removed. @@ -1009,7 +838,7 @@ class JoystickDevice(JoystickEvent): joysticks.remove(joystick) """ - type: Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] # type: ignore[misc] + type: Final[Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]] which: int """When type="JOYDEVICEADDED" this is the device ID. @@ -1017,132 +846,105 @@ class JoystickDevice(JoystickEvent): """ @classmethod - def from_sdl_event(cls, sdl_event: Any) -> JoystickDevice: - type = { + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + types: Final[dict[int, Literal["JOYDEVICEADDED", "JOYDEVICEREMOVED"]]] = { lib.SDL_EVENT_JOYSTICK_ADDED: "JOYDEVICEADDED", lib.SDL_EVENT_JOYSTICK_REMOVED: "JOYDEVICEREMOVED", - }[sdl_event.type] - return cls(type, sdl_event.jdevice.which) + } + return cls(type=types[sdl_event.type], which=int(sdl_event.jdevice.which)) +@attrs.define(slots=True, kw_only=True) class ControllerEvent(Event): """Base class for controller events. .. versionadded:: 13.8 """ - def __init__(self, type: str, which: int) -> None: - super().__init__(type) - self.which = which - """The ID of the joystick this event is for.""" + which: int + """The ID of the controller this event is for.""" @property def controller(self) -> tcod.sdl.joystick.GameController: """The :any:`GameController` for this event.""" - if self.type == "CONTROLLERDEVICEADDED": + if isinstance(self, ControllerDevice) and self.type == "CONTROLLERDEVICEADDED": return tcod.sdl.joystick.GameController._open(self.which) return tcod.sdl.joystick.GameController._from_instance_id(self.which) - def __repr__(self) -> str: - return f"tcod.event.{self.__class__.__name__}(type={self.type!r}, which={self.which})" - - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, which={self.which}>" - +@attrs.define(slots=True, kw_only=True) class ControllerAxis(ControllerEvent): """When a controller axis is moved. .. versionadded:: 13.8 """ - type: Final[Literal["CONTROLLERAXISMOTION"]] # type: ignore[misc] + _type: Final[Literal["CONTROLLERAXISMOTION"]] = "CONTROLLERAXISMOTION" - def __init__(self, type: str, which: int, axis: tcod.sdl.joystick.ControllerAxis, value: int) -> None: - super().__init__(type, which) - self.axis = axis - """Which axis is being moved. One of :any:`ControllerAxis`.""" - self.value = value - """The new value of this events axis. + axis: int + """Which axis is being moved. One of :any:`ControllerAxis`.""" + value: int + """The new value of this events axis. - This will be -32768 to 32767 for all axes except for triggers which are 0 to 32767 instead.""" + This will be -32768 to 32767 for all axes except for triggers which are 0 to 32767 instead.""" @classmethod - def from_sdl_event(cls, sdl_event: Any) -> ControllerAxis: + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: return cls( - "CONTROLLERAXISMOTION", - sdl_event.gaxis.which, - tcod.sdl.joystick.ControllerAxis(sdl_event.gaxis.axis), - sdl_event.gaxis.value, - ) - - def __repr__(self) -> str: - return ( - f"tcod.event.{self.__class__.__name__}" - f"(type={self.type!r}, which={self.which}, axis={self.axis}, value={self.value})" + which=int(sdl_event.gaxis.which), + axis=tcod.sdl.joystick.ControllerAxis(sdl_event.gaxis.axis), + value=int(sdl_event.gaxis.value), ) - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, axis={self.axis}, value={self.value}>" - +@attrs.define(slots=True, kw_only=True) class ControllerButton(ControllerEvent): """When a controller button is pressed or released. .. versionadded:: 13.8 """ - type: Final[Literal["CONTROLLERBUTTONDOWN", "CONTROLLERBUTTONUP"]] # type: ignore[misc] + button: tcod.sdl.joystick.ControllerButton + """The button for this event. One of :any:`ControllerButton`.""" + pressed: bool + """True if the button was pressed, False if it was released.""" - def __init__(self, type: str, which: int, button: tcod.sdl.joystick.ControllerButton, pressed: bool) -> None: - super().__init__(type, which) - self.button = button - """The button for this event. One of :any:`ControllerButton`.""" - self.pressed = pressed - """True if the button was pressed, False if it was released.""" + @property + @deprecated("Check 'ControllerButton.pressed' instead of '.type'.") + def type(self) -> Literal["CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN"]: + """Button state as a string. + + .. deprecated:: Unreleased + Use :any:`pressed` instead. + """ + return ("CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN")[self.pressed] @classmethod - def from_sdl_event(cls, sdl_event: Any) -> ControllerButton: - type = { - lib.SDL_EVENT_GAMEPAD_BUTTON_DOWN: "CONTROLLERBUTTONDOWN", - lib.SDL_EVENT_GAMEPAD_BUTTON_UP: "CONTROLLERBUTTONUP", - }[sdl_event.type] + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: return cls( - type, - sdl_event.gbutton.which, - tcod.sdl.joystick.ControllerButton(sdl_event.gbutton.button), - bool(sdl_event.gbutton.down), - ) - - def __repr__(self) -> str: - return ( - f"tcod.event.{self.__class__.__name__}" - f"(type={self.type!r}, which={self.which}, button={self.button}, pressed={self.pressed})" + which=int(sdl_event.gbutton.which), + button=tcod.sdl.joystick.ControllerButton(sdl_event.gbutton.button), + pressed=bool(sdl_event.gbutton.down), ) - def __str__(self) -> str: - prefix = super().__str__().strip("<>") - return f"<{prefix}, button={self.button}, pressed={self.pressed}>" - +@attrs.define(slots=True, kw_only=True) class ControllerDevice(ControllerEvent): """When a controller is added, removed, or remapped. .. versionadded:: 13.8 """ - type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] # type: ignore[misc] + type: Final[Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] @classmethod - def from_sdl_event(cls, sdl_event: Any) -> ControllerDevice: - type = { + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + types: dict[int, Literal["CONTROLLERDEVICEADDED", "CONTROLLERDEVICEREMOVED", "CONTROLLERDEVICEREMAPPED"]] = { lib.SDL_EVENT_GAMEPAD_ADDED: "CONTROLLERDEVICEADDED", lib.SDL_EVENT_GAMEPAD_REMOVED: "CONTROLLERDEVICEREMOVED", lib.SDL_EVENT_GAMEPAD_REMAPPED: "CONTROLLERDEVICEREMAPPED", - }[sdl_event.type] - return cls(type, sdl_event.gdevice.which) + } + return cls(type=types[sdl_event.type], which=int(sdl_event.gdevice.which)) @functools.cache @@ -1154,25 +956,17 @@ def _find_event_name(index: int, /) -> str: return "???" +@attrs.define(slots=True, kw_only=True) class Undefined(Event): """This class is a place holder for SDL events without their own tcod.event class.""" - def __init__(self) -> None: - super().__init__("") - @classmethod - def from_sdl_event(cls, sdl_event: Any) -> Undefined: - self = cls() - self.sdl_event = sdl_event - return self - - def __str__(self) -> str: - if self.sdl_event: - return f"" - return "" + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls(sdl_event=sdl_event) def __repr__(self) -> str: - return self.__str__() + """Return debug info for this undefined event, including the SDL event name.""" + return f"" _SDL_TO_CLASS_TABLE: dict[int, type[Event]] = { @@ -1200,13 +994,13 @@ def __repr__(self) -> str: } -def _parse_event(sdl_event: Any) -> Event: +def _parse_event(sdl_event: _C_SDL_Event) -> Event: """Convert a C SDL_Event* type into a tcod Event sub-class.""" if sdl_event.type in _SDL_TO_CLASS_TABLE: - return _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) - if sdl_event.type in WindowEvent._WINDOW_TYPES: - return WindowEvent.from_sdl_event(sdl_event) - return Undefined.from_sdl_event(sdl_event) + return _SDL_TO_CLASS_TABLE[sdl_event.type]._from_sdl_event(sdl_event) + if sdl_event.type in _WINDOW_TYPES_FROM_ENUM: + return WindowEvent._from_sdl_event(sdl_event) + return Undefined._from_sdl_event(sdl_event) def get() -> Iterator[Event]: @@ -1261,7 +1055,8 @@ def wait(timeout: float | None = None) -> Iterator[Event]: @deprecated( - "Event dispatch should be handled via a single custom method in a Protocol instead of this class.", + """EventDispatch is no longer maintained. +Event dispatching should be handled via a single custom method in a Protocol instead of this class.""", category=DeprecationWarning, ) class EventDispatch(Generic[T]): @@ -1275,8 +1070,8 @@ class EventDispatch(Generic[T]): The type hints at the return value of :any:`dispatch` and the `ev_*` methods. .. deprecated:: 18.0 - Event dispatch should be handled via a single custom method in a Protocol instead of this class. - Note that events can and should be handled using Python's `match` statement. + Event dispatch should be handled via a single custom method in a :class:`~typing.Protocol` instead of this class. + Note that events can and should be handled using :ref:`match`. Example:: @@ -1374,7 +1169,7 @@ def cmd_quit(self) -> None: __slots__ = () - def dispatch(self, event: Any) -> T | None: + def dispatch(self, event: Any) -> T | None: # noqa: ANN401 """Send an event to an `ev_*` method. `*` will be the `event.type` attribute converted to lower-case. @@ -1400,11 +1195,11 @@ def dispatch(self, event: Any) -> T | None: return None return func(event) - def event_get(self) -> None: + def event_get(self) -> None: # noqa: D102 for event in get(): self.dispatch(event) - def event_wait(self, timeout: float | None) -> None: + def event_wait(self, timeout: float | None) -> None: # noqa: D102 wait(timeout) self.event_get() @@ -1474,10 +1269,10 @@ def ev_windowfocuslost(self, event: tcod.event.WindowEvent, /) -> T | None: def ev_windowclose(self, event: tcod.event.WindowEvent, /) -> T | None: """Called when the window manager requests the window to be closed.""" - def ev_windowtakefocus(self, event: tcod.event.WindowEvent, /) -> T | None: + def ev_windowtakefocus(self, event: tcod.event.WindowEvent, /) -> T | None: # noqa: D102 pass - def ev_windowhittest(self, event: tcod.event.WindowEvent, /) -> T | None: + def ev_windowhittest(self, event: tcod.event.WindowEvent, /) -> T | None: # noqa: D102 pass def ev_joyaxismotion(self, event: tcod.event.JoystickAxis, /) -> T | None: @@ -1558,7 +1353,7 @@ def ev_controllerdeviceremapped(self, event: tcod.event.ControllerDevice, /) -> .. versionadded:: 13.8 """ - def ev_(self, event: Any, /) -> T | None: + def ev_(self, event: Any, /) -> T | None: # noqa: ANN401, D102 pass @@ -1571,8 +1366,8 @@ def get_mouse_state() -> MouseState: buttons = lib.SDL_GetMouseState(xy, xy + 1) tile = _pixel_to_tile(*xy) if tile is None: - return MouseState((xy[0], xy[1]), None, buttons) - return MouseState((xy[0], xy[1]), (int(tile[0]), int(tile[1])), buttons) + return MouseState(position=Point(xy[0], xy[1]), tile=None, state=buttons) + return MouseState(position=Point(xy[0], xy[1]), tile=Point(int(tile[0]), int(tile[1])), state=buttons) @overload @@ -1648,7 +1443,7 @@ def convert_coordinates_from_window( @ffi.def_extern() # type: ignore[untyped-decorator] -def _sdl_event_watcher(userdata: Any, sdl_event: Any) -> int: +def _sdl_event_watcher(userdata: Any, sdl_event: _C_SDL_Event) -> int: # noqa: ANN401 callback: Callable[[Event], None] = ffi.from_handle(userdata) callback(_parse_event(sdl_event)) return 0 @@ -2994,24 +2789,17 @@ def __getattr__(name: str) -> int: return value -__all__ = [ # noqa: F405 RUF022 - "Modifier", +__all__ = ( # noqa: F405 RUF022 "Point", - "BUTTON_LEFT", - "BUTTON_MIDDLE", - "BUTTON_RIGHT", - "BUTTON_X1", - "BUTTON_X2", - "BUTTON_LMASK", - "BUTTON_MMASK", - "BUTTON_RMASK", - "BUTTON_X1MASK", - "BUTTON_X2MASK", + "Modifier", + "MouseButton", + "MouseButtonMask", "Event", "Quit", "KeyboardEvent", "KeyDown", "KeyUp", + "MouseState", "MouseMotion", "MouseButtonEvent", "MouseButtonDown", @@ -3045,5 +2833,4 @@ def __getattr__(name: str) -> int: # --- From event_constants.py --- "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", - "MOUSEWHEEL", -] +) diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 1e0c1438..4b1f10ef 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -210,7 +210,7 @@ def get_global_state() -> tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetGlobalMouseState(xy, xy + 1) - return tcod.event.MouseState((xy[0], xy[1]), state=state) + return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state) def get_relative_state() -> tcod.event.MouseState: @@ -221,7 +221,7 @@ def get_relative_state() -> tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetRelativeMouseState(xy, xy + 1) - return tcod.event.MouseState((xy[0], xy[1]), state=state) + return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state) def get_state() -> tcod.event.MouseState: @@ -232,7 +232,7 @@ def get_state() -> tcod.event.MouseState: """ xy = ffi.new("int[2]") state = lib.SDL_GetMouseState(xy, xy + 1) - return tcod.event.MouseState((xy[0], xy[1]), state=state) + return tcod.event.MouseState(position=tcod.event.Point(xy[0], xy[1]), state=state) def get_focus() -> tcod.sdl.video.Window | None: From 6df9334ca245370c1b5e73570ea4409648d05b1f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 11 Mar 2026 19:14:14 -0700 Subject: [PATCH 1078/1101] Fix mouse tile regressions and improve integer coordinate handling Refactor Point to be generic to better track what uses int and float types Fix wrong C type in get_mouse_state, added function to samples Added separate integer attributes to mouse events --- CHANGELOG.md | 11 +++ examples/samples_tcod.py | 32 +++------ tcod/context.py | 13 +++- tcod/event.py | 150 ++++++++++++++++++++++++--------------- 4 files changed, 125 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c612d322..237c4017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Added - `tcod.sdl.video.Window` now accepts an SDL WindowID. +- `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred. ### Changed @@ -16,12 +17,22 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Event class initializers are keyword-only and no longer take a type parameter, with exceptions. Generally event class initialization is an internal process. - `MouseButtonEvent` no longer a subclass of `MouseState`. +- `tcod.event.Point` is now a generic type containing `int` or `float` values depending on the context. +- When converting mouse events to tiles: + `MouseState.position` and `MouseMotion.motion` refers to sub-tile coordinates. + `MouseState.integer_position` and `MouseMotion.integer_motion` refers to integer tile coordinates. ### Deprecated - `Event.type` is deprecated except for special cases such as `ControllerDevice`, `WindowEvent`, etc. - `MouseButtonEvent.state` is deprecated, replaced by the existing `.button` attribute. +### Fixed + +- Fixed incorrect C FFI types inside `tcod.event.get_mouse_state`. +- Fixed regression in mouse event tile coordinates being `float` instead of `int`. + `convert_coordinates_from_window` can be used if sub-tile coordinates were desired. + ## [20.1.0] - 2026-02-25 ### Added diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 32107d0d..19d4fd92 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -1011,7 +1011,6 @@ class MouseSample(Sample): def __init__(self) -> None: self.motion = tcod.event.MouseMotion() - self.mouse_left = self.mouse_middle = self.mouse_right = 0 self.log: list[str] = [] def on_enter(self) -> None: @@ -1022,15 +1021,18 @@ def on_enter(self) -> None: def on_draw(self) -> None: sample_console.clear(bg=GREY) + mouse_state = tcod.event.get_mouse_state() sample_console.print( 1, 1, - f"Pixel position : {self.motion.position.x:4.0f}x{self.motion.position.y:4.0f}\n" - f"Tile position : {self.motion.tile.x:4.0f}x{self.motion.tile.y:4.0f}\n" - f"Tile movement : {self.motion.tile_motion.x:4.0f}x{self.motion.tile_motion.y:4.0f}\n" - f"Left button : {'ON' if self.mouse_left else 'OFF'}\n" - f"Right button : {'ON' if self.mouse_right else 'OFF'}\n" - f"Middle button : {'ON' if self.mouse_middle else 'OFF'}\n", + f"Pixel position : {mouse_state.position.x:4.0f}x{mouse_state.position.y:4.0f}\n" + f"Tile position : {self.motion.tile.x:4d}x{self.motion.tile.y:4d}\n" + f"Tile movement : {self.motion.tile_motion.x:4d}x{self.motion.tile_motion.y:4d}\n" + f"Left button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.LEFT else 'OFF'}\n" + f"Middle button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.MIDDLE else 'OFF'}\n" + f"Right button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.RIGHT else 'OFF'}\n" + f"X1 button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.X1 else 'OFF'}\n" + f"X2 button : {'ON' if mouse_state.state & tcod.event.MouseButtonMask.X2 else 'OFF'}\n", fg=LIGHT_YELLOW, bg=None, ) @@ -1046,18 +1048,6 @@ def on_event(self, event: tcod.event.Event) -> None: match event: case tcod.event.MouseMotion(): self.motion = event - case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.LEFT): - self.mouse_left = True - case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.MIDDLE): - self.mouse_middle = True - case tcod.event.MouseButtonDown(button=tcod.event.MouseButton.RIGHT): - self.mouse_right = True - case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT): - self.mouse_left = False - case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.MIDDLE): - self.mouse_middle = False - case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT): - self.mouse_right = False case tcod.event.KeyDown(sym=KeySym.N1): tcod.sdl.mouse.show(visible=False) case tcod.event.KeyDown(sym=KeySym.N2): @@ -1422,8 +1412,8 @@ def handle_events() -> None: tile_event = tcod.event.convert_coordinates_from_window(event, context, root_console) SAMPLES[cur_sample].on_event(tile_event) match tile_event: - case tcod.event.MouseMotion(position=(x, y)): - mouse_tile_xy = int(x), int(y) + case tcod.event.MouseMotion(integer_position=(x, y)): + mouse_tile_xy = x, y case tcod.event.WindowEvent(type="WindowLeave"): mouse_tile_xy = -1, -1 diff --git a/tcod/context.py b/tcod/context.py index 5b1bb214..994290c5 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -29,6 +29,7 @@ import pickle import sys import warnings +from math import floor from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, NoReturn, TypeVar @@ -256,7 +257,8 @@ def convert_event(self, event: _Event) -> _Event: event_copy = copy.copy(event) if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion)) - event_copy.position = event._tile = tcod.event.Point(*self.pixel_to_tile(*event.position)) + event_copy.position = tcod.event.Point(*self.pixel_to_tile(event.position[0], event.position[1])) + event._tile = tcod.event.Point(floor(event_copy.position[0]), floor(event_copy.position[1])) if isinstance(event, tcod.event.MouseMotion): assert isinstance(event_copy, tcod.event.MouseMotion) assert event._tile is not None @@ -264,8 +266,13 @@ def convert_event(self, event: _Event) -> _Event: event.position[0] - event.motion[0], event.position[1] - event.motion[1], ) - event_copy.motion = event._tile_motion = tcod.event.Point( - int(event._tile[0]) - int(prev_tile[0]), int(event._tile[1]) - int(prev_tile[1]) + event_copy.motion = tcod.event.Point( + event_copy.position[0] - prev_tile[0], + event_copy.position[1] - prev_tile[1], + ) + event._tile_motion = tcod.event.Point( + event._tile[0] - floor(prev_tile[0]), + event._tile[1] - floor(prev_tile[1]), ) return event_copy diff --git a/tcod/event.py b/tcod/event.py index d6cf19e2..b4be0a29 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -87,6 +87,7 @@ import sys import warnings from collections.abc import Callable, Iterator, Mapping +from math import floor from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypeVar, overload import attrs @@ -149,32 +150,39 @@ def _describe_bitmask(bits: int, table: Mapping[int, str], default: str = "0") - return "|".join(result) -def _pixel_to_tile(x: float, y: float) -> tuple[float, float] | None: +def _pixel_to_tile(xy: tuple[float, float], /) -> Point[float] | None: """Convert pixel coordinates to tile coordinates.""" if not lib.TCOD_ctx.engine: return None - xy = ffi.new("double[2]", (x, y)) - lib.TCOD_sys_pixel_to_tile(xy, xy + 1) - return xy[0], xy[1] + xy_out = ffi.new("double[2]", xy) + lib.TCOD_sys_pixel_to_tile(xy_out, xy_out + 1) + return Point(float(xy_out[0]), float(xy_out[1])) -class Point(NamedTuple): - """A 2D position used for events with mouse coordinates. +if sys.version_info >= (3, 11) or TYPE_CHECKING: - .. seealso:: - :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp` + class Point(NamedTuple, Generic[T]): + """A 2D position used for events with mouse coordinates. - .. versionchanged:: 19.0 - Now uses floating point coordinates due to the port to SDL3. - """ + .. seealso:: + :any:`MouseMotion` :any:`MouseButtonDown` :any:`MouseButtonUp` + + .. versionchanged:: 19.0 + Now uses floating point coordinates due to the port to SDL3. + """ - x: float - """A pixel or tile coordinate starting with zero as the left-most position.""" - y: float - """A pixel or tile coordinate starting with zero as the top-most position.""" + x: T + """A pixel or tile coordinate starting with zero as the left-most position.""" + y: T + """A pixel or tile coordinate starting with zero as the top-most position.""" +else: + class Point(NamedTuple): # noqa: D101 + x: Any + y: Any -def _verify_tile_coordinates(xy: Point | None) -> Point: + +def _verify_tile_coordinates(xy: Point[int] | None) -> Point[int]: """Check if an events tile coordinate is initialized and warn if not. Always returns a valid Point object for backwards compatibility. @@ -399,36 +407,50 @@ class MouseState(Event): Renamed `pixel` attribute to `position`. """ - position: Point = attrs.field(default=Point(0, 0)) + position: Point[float] = attrs.field(default=Point(0.0, 0.0)) """The position coordinates of the mouse.""" - _tile: Point | None = attrs.field(default=Point(0, 0), alias="tile") - """The integer tile coordinates of the mouse on the screen.""" + _tile: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile") + state: MouseButtonMask = attrs.field(default=MouseButtonMask(0)) """A bitmask of which mouse buttons are currently held.""" + @property + def integer_position(self) -> Point[int]: + """Integer coordinates of this event. + + .. versionadded:: Unreleased + """ + x, y = self.position + return Point(floor(x), floor(y)) + @property @deprecated("The mouse.pixel attribute is deprecated. Use mouse.position instead.") - def pixel(self) -> Point: + def pixel(self) -> Point[float]: return self.position @pixel.setter - def pixel(self, value: Point) -> None: + def pixel(self, value: Point[float]) -> None: self.position = value @property @deprecated( "The mouse.tile attribute is deprecated." - " Use mouse.position of the event returned by context.convert_event instead." + " Use mouse.integer_position of the event returned by context.convert_event instead." ) - def tile(self) -> Point: + def tile(self) -> Point[int]: + """The integer tile coordinates of the mouse on the screen. + + .. deprecated:: Unreleased + Use :any:`integer_position` of the event returned by :any:`Context.convert_event` instead. + """ return _verify_tile_coordinates(self._tile) @tile.setter @deprecated( "The mouse.tile attribute is deprecated." - " Use mouse.position of the event returned by context.convert_event instead." + " Use mouse.integer_position of the event returned by context.convert_event instead." ) - def tile(self, xy: tuple[float, float]) -> None: + def tile(self, xy: tuple[int, int]) -> None: self._tile = Point(*xy) @@ -444,35 +466,50 @@ class MouseMotion(MouseState): `position` and `motion` now use floating point coordinates. """ - motion: Point = attrs.field(default=Point(0, 0)) + motion: Point[float] = attrs.field(default=Point(0.0, 0.0)) """The pixel delta.""" - _tile_motion: Point | None = attrs.field(default=Point(0, 0), alias="tile_motion") - """The tile delta.""" + _tile_motion: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile_motion") + + @property + def integer_motion(self) -> Point[int]: + """Integer motion of this event. + + .. versionadded:: Unreleased + """ + x, y = self.position + dx, dy = self.motion + prev_x, prev_y = x - dx, y - dy + return Point(floor(x) - floor(prev_x), floor(y) - floor(prev_y)) @property @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") - def pixel_motion(self) -> Point: + def pixel_motion(self) -> Point[float]: return self.motion @pixel_motion.setter @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") - def pixel_motion(self, value: Point) -> None: + def pixel_motion(self, value: Point[float]) -> None: self.motion = value @property @deprecated( "The mouse.tile_motion attribute is deprecated." - " Use mouse.motion of the event returned by context.convert_event instead." + " Use mouse.integer_motion of the event returned by context.convert_event instead." ) - def tile_motion(self) -> Point: + def tile_motion(self) -> Point[int]: + """The tile delta. + + .. deprecated:: Unreleased + Use :any:`integer_motion` of the event returned by :any:`Context.convert_event` instead. + """ return _verify_tile_coordinates(self._tile_motion) @tile_motion.setter @deprecated( "The mouse.tile_motion attribute is deprecated." - " Use mouse.motion of the event returned by context.convert_event instead." + " Use mouse.integer_motion of the event returned by context.convert_event instead." ) - def tile_motion(self, xy: tuple[float, float]) -> None: + def tile_motion(self, xy: tuple[int, int]) -> None: self._tile_motion = Point(*xy) @classmethod @@ -480,16 +517,16 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: motion = sdl_event.motion state = MouseButtonMask(motion.state) - pixel = Point(motion.x, motion.y) - pixel_motion = Point(motion.xrel, motion.yrel) - subtile = _pixel_to_tile(*pixel) + pixel = Point(float(motion.x), float(motion.y)) + pixel_motion = Point(float(motion.xrel), float(motion.yrel)) + subtile = _pixel_to_tile(pixel) if subtile is None: self = cls(position=pixel, motion=pixel_motion, tile=None, tile_motion=None, state=state) else: - tile = Point(int(subtile[0]), int(subtile[1])) - prev_pixel = pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1] - prev_subtile = _pixel_to_tile(*prev_pixel) or (0, 0) - prev_tile = int(prev_subtile[0]), int(prev_subtile[1]) + tile = Point(floor(subtile[0]), floor(subtile[1])) + prev_pixel = (pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1]) + prev_subtile = _pixel_to_tile(prev_pixel) or (0, 0) + prev_tile = floor(prev_subtile[0]), floor(prev_subtile[1]) tile_motion = Point(tile[0] - prev_tile[0], tile[1] - prev_tile[1]) self = cls(position=pixel, motion=pixel_motion, tile=tile, tile_motion=tile_motion, state=state) self.sdl_event = sdl_event @@ -507,10 +544,10 @@ class MouseButtonEvent(Event): No longer a subclass of :any:`MouseState`. """ - position: Point = attrs.field(default=Point(0, 0)) + position: Point[float] = attrs.field(default=Point(0.0, 0.0)) """The pixel coordinates of the mouse.""" - _tile: Point | None = attrs.field(default=Point(0, 0), alias="tile") - """The tile coordinates of the mouse on the screen.""" + _tile: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile") + """The tile integer coordinates of the mouse on the screen. Deprecated.""" button: MouseButton """Which mouse button index was pressed or released in this event. @@ -521,12 +558,12 @@ class MouseButtonEvent(Event): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: button = sdl_event.button - pixel = Point(button.x, button.y) - subtile = _pixel_to_tile(*pixel) + pixel = Point(float(button.x), float(button.y)) + subtile = _pixel_to_tile(pixel) if subtile is None: - tile: Point | None = None + tile: Point[int] | None = None else: - tile = Point(float(subtile[0]), float(subtile[1])) + tile = Point(floor(subtile[0]), floor(subtile[1])) self = cls(position=pixel, tile=tile, button=MouseButton(button.button)) self.sdl_event = sdl_event return self @@ -1362,12 +1399,12 @@ def get_mouse_state() -> MouseState: .. versionadded:: 9.3 """ - xy = ffi.new("int[2]") + xy = ffi.new("float[2]") buttons = lib.SDL_GetMouseState(xy, xy + 1) - tile = _pixel_to_tile(*xy) + tile = _pixel_to_tile(tuple(xy)) if tile is None: return MouseState(position=Point(xy[0], xy[1]), tile=None, state=buttons) - return MouseState(position=Point(xy[0], xy[1]), tile=Point(int(tile[0]), int(tile[1])), state=buttons) + return MouseState(position=Point(xy[0], xy[1]), tile=Point(floor(tile[0]), floor(tile[1])), state=buttons) @overload @@ -1431,14 +1468,13 @@ def convert_coordinates_from_window( ((event.position[0] - event.motion[0]), (event.position[1] - event.motion[1])), context, console, dest_rect ) position = convert_coordinates_from_window(event.position, context, console, dest_rect) - event.motion = tcod.event.Point(position[0] - previous_position[0], position[1] - previous_position[1]) - event._tile_motion = tcod.event.Point( - int(position[0]) - int(previous_position[0]), int(position[1]) - int(previous_position[1]) + event.motion = Point(position[0] - previous_position[0], position[1] - previous_position[1]) + event._tile_motion = Point( + floor(position[0]) - floor(previous_position[0]), floor(position[1]) - floor(previous_position[1]) ) if isinstance(event, (MouseState, MouseMotion)): - event.position = event._tile = tcod.event.Point( - *convert_coordinates_from_window(event.position, context, console, dest_rect) - ) + event.position = Point(*convert_coordinates_from_window(event.position, context, console, dest_rect)) + event._tile = Point(floor(event.position[0]), floor(event.position[1])) return event From ec32d8c2ecac0fcd6ca0a316022ec7c64b704bc4 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 12 Mar 2026 06:40:31 -0700 Subject: [PATCH 1079/1101] Update missing docs in tcod.event --- tcod/event.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index b4be0a29..a7b4bddf 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -425,7 +425,7 @@ def integer_position(self) -> Point[int]: @property @deprecated("The mouse.pixel attribute is deprecated. Use mouse.position instead.") - def pixel(self) -> Point[float]: + def pixel(self) -> Point[float]: # noqa: D102 # Skip docstring for deprecated attribute return self.position @pixel.setter @@ -483,7 +483,7 @@ def integer_motion(self) -> Point[int]: @property @deprecated("The mouse.pixel_motion attribute is deprecated. Use mouse.motion instead.") - def pixel_motion(self) -> Point[float]: + def pixel_motion(self) -> Point[float]: # noqa: D102 # Skip docstring for deprecated attribute return self.motion @pixel_motion.setter @@ -739,6 +739,7 @@ class JoystickEvent(Event): @property def joystick(self) -> tcod.sdl.joystick.Joystick: + """The :any:`Joystick` for this event.""" if isinstance(self, JoystickDevice) and self.type == "JOYDEVICEADDED": return tcod.sdl.joystick.Joystick._open(self.which) return tcod.sdl.joystick.Joystick._from_instance_id(self.which) @@ -2128,14 +2129,18 @@ def _missing_(cls, value: object) -> Scancode | None: return result def __eq__(self, other: object) -> bool: + """Compare with another Scancode value. + + Comparison between :any:`KeySym` and :any:`Scancode` is not allowed and will raise :any:`TypeError`. + """ if isinstance(other, KeySym): msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." raise TypeError(msg) return super().__eq__(other) def __hash__(self) -> int: - # __eq__ was defined, so __hash__ must be defined. - return super().__hash__() + """Return the hash for this value.""" + return super().__hash__() # __eq__ was defined, so __hash__ must be defined def __repr__(self) -> str: """Return the fully qualified name of this enum.""" @@ -2708,14 +2713,18 @@ def _missing_(cls, value: object) -> KeySym | None: return result def __eq__(self, other: object) -> bool: + """Compare with another KeySym value. + + Comparison between :any:`KeySym` and :any:`Scancode` is not allowed and will raise :any:`TypeError`. + """ if isinstance(other, Scancode): msg = "Scancode and KeySym enums can not be compared directly. Convert one or the other to the same type." raise TypeError(msg) return super().__eq__(other) def __hash__(self) -> int: - # __eq__ was defined, so __hash__ must be defined. - return super().__hash__() + """Return the hash for this value.""" + return super().__hash__() # __eq__ was defined, so __hash__ must be defined def __repr__(self) -> str: """Return the fully qualified name of this enum.""" From f873f4432b2d74966a332eea277b90bbe81c4cae Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 12 Mar 2026 07:13:26 -0700 Subject: [PATCH 1080/1101] Centralize unpacking of SDL_Event union into Event attribute Add sdl_event to several events which were missing it Haven't decided on timestamp handling yet, this is the most I can do before committing to anything --- tcod/event.py | 72 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index a7b4bddf..004ff59b 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -88,7 +88,7 @@ import warnings from collections.abc import Callable, Iterator, Mapping from math import floor -from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypeVar, overload +from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypedDict, TypeVar, overload import attrs import numpy as np @@ -309,11 +309,24 @@ def __repr__(self) -> str: return "|".join(f"{self.__class__.__name__}.{self.__class__(bit).name}" for bit in self.__class__ if bit & self) +class _CommonSDLEventAttributes(TypedDict): + """Common keywords for Event subclasses.""" + + sdl_event: _C_SDL_Event + + +def _unpack_sdl_event(sdl_event: _C_SDL_Event) -> _CommonSDLEventAttributes: + """Unpack an SDL_Event union into common attributes, such as timestamp.""" + return { + "sdl_event": sdl_event, + } + + @attrs.define(slots=True, kw_only=True) class Event: """The base event class.""" - sdl_event: _C_SDL_Event = attrs.field(default=None, eq=False, kw_only=True, repr=False) + sdl_event: _C_SDL_Event = attrs.field(default=None, eq=False, repr=False) """When available, this holds a python-cffi 'SDL_Event*' pointer. All sub-classes have this attribute.""" @property @@ -349,7 +362,7 @@ class Quit(Event): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: - return cls(sdl_event=sdl_event) + return cls(**_unpack_sdl_event(sdl_event)) @attrs.define(slots=True, kw_only=True) @@ -383,7 +396,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: sym=KeySym(keysym.key), mod=Modifier(keysym.mod), repeat=bool(sdl_event.key.repeat), - sdl_event=sdl_event, + **_unpack_sdl_event(sdl_event), ) @@ -521,14 +534,28 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: pixel_motion = Point(float(motion.xrel), float(motion.yrel)) subtile = _pixel_to_tile(pixel) if subtile is None: - self = cls(position=pixel, motion=pixel_motion, tile=None, tile_motion=None, state=state) + self = cls( + position=pixel, + motion=pixel_motion, + tile=None, + tile_motion=None, + state=state, + **_unpack_sdl_event(sdl_event), + ) else: tile = Point(floor(subtile[0]), floor(subtile[1])) prev_pixel = (pixel[0] - pixel_motion[0], pixel[1] - pixel_motion[1]) prev_subtile = _pixel_to_tile(prev_pixel) or (0, 0) prev_tile = floor(prev_subtile[0]), floor(prev_subtile[1]) tile_motion = Point(tile[0] - prev_tile[0], tile[1] - prev_tile[1]) - self = cls(position=pixel, motion=pixel_motion, tile=tile, tile_motion=tile_motion, state=state) + self = cls( + position=pixel, + motion=pixel_motion, + tile=tile, + tile_motion=tile_motion, + state=state, + **_unpack_sdl_event(sdl_event), + ) self.sdl_event = sdl_event return self @@ -564,7 +591,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: tile: Point[int] | None = None else: tile = Point(floor(subtile[0]), floor(subtile[1])) - self = cls(position=pixel, tile=tile, button=MouseButton(button.button)) + self = cls(position=pixel, tile=tile, button=MouseButton(button.button), **_unpack_sdl_event(sdl_event)) self.sdl_event = sdl_event return self @@ -602,7 +629,7 @@ class MouseWheel(Event): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: wheel = sdl_event.wheel - return cls(x=int(wheel.x), y=int(wheel.y), flipped=bool(wheel.direction), sdl_event=sdl_event) + return cls(x=int(wheel.x), y=int(wheel.y), flipped=bool(wheel.direction), **_unpack_sdl_event(sdl_event)) @attrs.define(slots=True, kw_only=True) @@ -620,7 +647,7 @@ class TextInput(Event): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: - return cls(text=str(ffi.string(sdl_event.text.text, 32), encoding="utf8"), sdl_event=sdl_event) + return cls(text=str(ffi.string(sdl_event.text.text, 32), encoding="utf8"), **_unpack_sdl_event(sdl_event)) @attrs.define(slots=True, kw_only=True) @@ -655,7 +682,9 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined: event_type: Final = _WINDOW_TYPES_FROM_ENUM[sdl_event.type] self: WindowEvent if sdl_event.type == lib.SDL_EVENT_WINDOW_MOVED: - self = WindowMoved(x=int(sdl_event.window.data1), y=int(sdl_event.window.data2), sdl_event=sdl_event) + self = WindowMoved( + x=int(sdl_event.window.data1), y=int(sdl_event.window.data2), **_unpack_sdl_event(sdl_event) + ) elif sdl_event.type in ( lib.SDL_EVENT_WINDOW_RESIZED, lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, @@ -664,12 +693,12 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined: type=event_type, # type: ignore[arg-type] # Currently NOT validated width=int(sdl_event.window.data1), height=int(sdl_event.window.data2), - sdl_event=sdl_event, + **_unpack_sdl_event(sdl_event), ) else: self = cls( type=event_type, # type: ignore[arg-type] # Currently NOT validated - sdl_event=sdl_event, + **_unpack_sdl_event(sdl_event), ) return self @@ -764,7 +793,12 @@ class JoystickAxis(JoystickEvent): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: - return cls(which=int(sdl_event.jaxis.which), axis=int(sdl_event.jaxis.axis), value=int(sdl_event.jaxis.value)) + return cls( + which=int(sdl_event.jaxis.which), + axis=int(sdl_event.jaxis.axis), + value=int(sdl_event.jaxis.value), + **_unpack_sdl_event(sdl_event), + ) @attrs.define(slots=True, kw_only=True) @@ -793,6 +827,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: ball=int(sdl_event.jball.ball), dx=int(sdl_event.jball.xrel), dy=int(sdl_event.jball.yrel), + **_unpack_sdl_event(sdl_event), ) @@ -816,7 +851,7 @@ class JoystickHat(JoystickEvent): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: x, y = _HAT_DIRECTIONS[sdl_event.jhat.hat] - return cls(which=int(sdl_event.jhat.which), x=x, y=y) + return cls(which=int(sdl_event.jhat.which), x=x, y=y, **_unpack_sdl_event(sdl_event)) @attrs.define(slots=True, kw_only=True) @@ -856,6 +891,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: which=int(sdl_event.jbutton.which), button=int(sdl_event.jbutton.button), pressed=bool(sdl_event.jbutton.down), + **_unpack_sdl_event(sdl_event), ) @@ -889,7 +925,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: lib.SDL_EVENT_JOYSTICK_ADDED: "JOYDEVICEADDED", lib.SDL_EVENT_JOYSTICK_REMOVED: "JOYDEVICEREMOVED", } - return cls(type=types[sdl_event.type], which=int(sdl_event.jdevice.which)) + return cls(type=types[sdl_event.type], which=int(sdl_event.jdevice.which), **_unpack_sdl_event(sdl_event)) @attrs.define(slots=True, kw_only=True) @@ -932,6 +968,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: which=int(sdl_event.gaxis.which), axis=tcod.sdl.joystick.ControllerAxis(sdl_event.gaxis.axis), value=int(sdl_event.gaxis.value), + **_unpack_sdl_event(sdl_event), ) @@ -963,6 +1000,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: which=int(sdl_event.gbutton.which), button=tcod.sdl.joystick.ControllerButton(sdl_event.gbutton.button), pressed=bool(sdl_event.gbutton.down), + **_unpack_sdl_event(sdl_event), ) @@ -982,7 +1020,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: lib.SDL_EVENT_GAMEPAD_REMOVED: "CONTROLLERDEVICEREMOVED", lib.SDL_EVENT_GAMEPAD_REMAPPED: "CONTROLLERDEVICEREMAPPED", } - return cls(type=types[sdl_event.type], which=int(sdl_event.gdevice.which)) + return cls(type=types[sdl_event.type], which=int(sdl_event.gdevice.which), **_unpack_sdl_event(sdl_event)) @functools.cache @@ -1000,7 +1038,7 @@ class Undefined(Event): @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: - return cls(sdl_event=sdl_event) + return cls(**_unpack_sdl_event(sdl_event)) def __repr__(self) -> str: """Return debug info for this undefined event, including the SDL event name.""" From 4bce98475d5a5fadf9ce8cd05384bf85356f7c96 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 12 Mar 2026 12:03:00 -0700 Subject: [PATCH 1081/1101] Add KeyboardEvent attributes from SDL3 --- CHANGELOG.md | 1 + tcod/event.py | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 237c4017..9ce0fe0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `tcod.sdl.video.Window` now accepts an SDL WindowID. - `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred. +- `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` ### Changed diff --git a/tcod/event.py b/tcod/event.py index 004ff59b..21963e18 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -387,6 +387,21 @@ class KeyboardEvent(Event): """ repeat: bool = False """True if this event exists because of key repeat.""" + which: int = 0 + """The SDL keyboard instance ID. Zero if unknown or virtual. + + .. versionadded:: Unreleased + """ + window_id: int = 0 + """The SDL window ID with keyboard focus. + + .. versionadded:: Unreleased + """ + pressed: bool = False + """True if the key was pressed, False if the key was released. + + .. versionadded:: Unreleased + """ @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: @@ -395,19 +410,22 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: scancode=Scancode(keysym.scancode), sym=KeySym(keysym.key), mod=Modifier(keysym.mod), - repeat=bool(sdl_event.key.repeat), + repeat=bool(keysym.repeat), + pressed=bool(keysym.down), + which=int(keysym.which), + window_id=int(keysym.windowID), **_unpack_sdl_event(sdl_event), ) @attrs.define(slots=True, kw_only=True) class KeyDown(KeyboardEvent): - pass + """A :any:`KeyboardEvent` where the key was pressed.""" @attrs.define(slots=True, kw_only=True) class KeyUp(KeyboardEvent): - pass + """A :any:`KeyboardEvent` where the key was released.""" @attrs.define(slots=True, kw_only=True) From 6f1dc93e68e1baf20c7785adb634ede1b2be2104 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 12 Mar 2026 12:34:17 -0700 Subject: [PATCH 1082/1101] Add ClipboardUpdate event Still missing other related functions to handle the clipboard --- CHANGELOG.md | 6 ++++-- tcod/event.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce0fe0c..06c4ed47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ### Added - `tcod.sdl.video.Window` now accepts an SDL WindowID. -- `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred. -- `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` +- `tcod.event`: + - `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred. + - `ClipboardUpdate` event. + - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes. ### Changed diff --git a/tcod/event.py b/tcod/event.py index 21963e18..e4aa3774 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -1041,6 +1041,27 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: return cls(type=types[sdl_event.type], which=int(sdl_event.gdevice.which), **_unpack_sdl_event(sdl_event)) +@attrs.define(slots=True, kw_only=True) +class ClipboardUpdate(Event): + """Announces changed contents of the clipboard. + + .. versionadded:: Unreleased + """ + + mime_types: tuple[str, ...] + """The MIME types of the clipboard.""" + + @classmethod + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + return cls( + mime_types=tuple( + str(ffi.string(sdl_event.clipboard.mime_types[i]), encoding="utf8") + for i in range(sdl_event.clipboard.num_mime_types) + ), + **_unpack_sdl_event(sdl_event), + ) + + @functools.cache def _find_event_name(index: int, /) -> str: """Return the SDL event name for this index.""" @@ -1085,6 +1106,7 @@ def __repr__(self) -> str: lib.SDL_EVENT_GAMEPAD_ADDED: ControllerDevice, lib.SDL_EVENT_GAMEPAD_REMOVED: ControllerDevice, lib.SDL_EVENT_GAMEPAD_REMAPPED: ControllerDevice, + lib.SDL_EVENT_CLIPBOARD_UPDATE: ClipboardUpdate, } From 6dee18f54031d3a9805cb67c81fd678efcf12e50 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 12 Mar 2026 13:43:54 -0700 Subject: [PATCH 1083/1101] Add Drop events --- CHANGELOG.md | 1 + tcod/event.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c4ed47..e85a0ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `tcod.event`: - `MouseState.integer_position` and `MouseMotion.integer_motion` to handle cases where integer values are preferred. - `ClipboardUpdate` event. + - `Drop` event. - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes. ### Changed diff --git a/tcod/event.py b/tcod/event.py index e4aa3774..6c474c86 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -88,6 +88,7 @@ import warnings from collections.abc import Callable, Iterator, Mapping from math import floor +from pathlib import Path from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypedDict, TypeVar, overload import attrs @@ -1062,6 +1063,68 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: ) +@attrs.define(slots=True, kw_only=True) +class Drop(Event): + """Handle dropping text or files on the window. + + Example:: + + match event: + case tcod.event.Drop(type="BEGIN"): + print("Object dragged over the window") + case tcod.event.Drop(type="POSITION", position=position): + pass + case tcod.event.Drop(type="TEXT", position=position, text=text): + print(f"Dropped {text=} at {position=}") + case tcod.event.Drop(type="FILE", position=position, path=path): + print(f"Dropped {path=} at {position=}") + case tcod.event.Drop(type="COMPLETE"): + print("Drop handling finished") + + .. versionadded:: Unreleased + """ + + type: Literal["BEGIN", "FILE", "TEXT", "COMPLETE", "POSITION"] + """The subtype of this event.""" + window_id: int + """The active window ID for this event.""" + position: Point[float] + """Mouse position relative to the window. Available in all subtypes except for ``type="BEGIN"``.""" + source: str + """The source app for this event, or an empty string if unavailable.""" + text: str + """The dropped data of a ``Drop(type="TEXT")`` or ``Drop(type="FILE")`` event. + + - If ``Drop(type="TEXT")`` then `text` is the dropped string. + - If ``Drop(type="FILE")`` then `text` is the str path of the dropped file. + Alternatively :any:`path` can be used. + - Otherwise `text` is an empty string. + """ + + @property + def path(self) -> Path: + """Return the current `text` as a :any:`Path`.""" + return Path(self.text) + + @classmethod + def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: + types: dict[int, Literal["BEGIN", "FILE", "TEXT", "COMPLETE", "POSITION"]] = { + lib.SDL_EVENT_DROP_BEGIN: "BEGIN", + lib.SDL_EVENT_DROP_FILE: "FILE", + lib.SDL_EVENT_DROP_TEXT: "TEXT", + lib.SDL_EVENT_DROP_COMPLETE: "COMPLETE", + lib.SDL_EVENT_DROP_POSITION: "POSITION", + } + return cls( + type=types[sdl_event.drop.type], + window_id=int(sdl_event.drop.windowID), + position=Point(float(sdl_event.drop.x), float(sdl_event.drop.y)), + source=str(ffi.string(sdl_event.drop.source), encoding="utf8") if sdl_event.drop.source else "", + text=str(ffi.string(sdl_event.drop.data), encoding="utf8") if sdl_event.drop.data else "", + **_unpack_sdl_event(sdl_event), + ) + + @functools.cache def _find_event_name(index: int, /) -> str: """Return the SDL event name for this index.""" @@ -1107,6 +1170,11 @@ def __repr__(self) -> str: lib.SDL_EVENT_GAMEPAD_REMOVED: ControllerDevice, lib.SDL_EVENT_GAMEPAD_REMAPPED: ControllerDevice, lib.SDL_EVENT_CLIPBOARD_UPDATE: ClipboardUpdate, + lib.SDL_EVENT_DROP_BEGIN: Drop, + lib.SDL_EVENT_DROP_FILE: Drop, + lib.SDL_EVENT_DROP_TEXT: Drop, + lib.SDL_EVENT_DROP_COMPLETE: Drop, + lib.SDL_EVENT_DROP_POSITION: Drop, } From 31d5d85a9058d46608eceff741d9c5ee58306549 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 09:09:16 -0700 Subject: [PATCH 1084/1101] Add basic event queue tests Break tcod.event, tcod.context cycle --- .vscode/settings.json | 1 + tcod/context.py | 2 +- tests/test_event.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tests/test_event.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 41cffd1b..3f3eaa0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "aarch", "ADDA", "ADDALPHA", + "ADDEVENT", "addoption", "addopts", "addressof", diff --git a/tcod/context.py b/tcod/context.py index 994290c5..f7b92314 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -68,7 +68,7 @@ "new_window", ) -_Event = TypeVar("_Event", bound=tcod.event.Event) +_Event = TypeVar("_Event", bound="tcod.event.Event") SDL_WINDOW_FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN """Fullscreen mode.""" diff --git a/tests/test_event.py b/tests/test_event.py new file mode 100644 index 00000000..9cc28243 --- /dev/null +++ b/tests/test_event.py @@ -0,0 +1,73 @@ +"""Tests for event parsing and handling.""" + +from typing import Any, Final + +import pytest + +import tcod.event +import tcod.sdl.sys +from tcod._internal import _check +from tcod.cffi import ffi, lib +from tcod.event import KeySym, Modifier, Scancode + +EXPECTED_EVENTS: Final = ( + tcod.event.Quit(), + tcod.event.KeyDown(scancode=Scancode.A, sym=KeySym.A, mod=Modifier(0), pressed=True), + tcod.event.KeyUp(scancode=Scancode.A, sym=KeySym.A, mod=Modifier(0), pressed=False), +) +"""Events to compare with after passing though the SDL event queue.""" + + +def as_sdl_event(event: tcod.event.Event) -> dict[str, dict[str, Any]]: + """Convert events into SDL_Event unions using cffi's union format.""" + match event: + case tcod.event.Quit(): + return {"quit": {"type": lib.SDL_EVENT_QUIT}} + case tcod.event.KeyboardEvent(): + return { + "key": { + "type": (lib.SDL_EVENT_KEY_UP, lib.SDL_EVENT_KEY_DOWN)[event.pressed], + "scancode": event.scancode, + "key": event.sym, + "mod": event.mod, + "down": event.pressed, + "repeat": event.repeat, + } + } + raise AssertionError + + +EVENT_PACK: Final = ffi.new("SDL_Event[]", [as_sdl_event(_e) for _e in EXPECTED_EVENTS]) +"""A custom C array of SDL_Event unions based on EXPECTED_EVENTS.""" + + +def push_events() -> None: + """Reset the SDL event queue to an expected list of events.""" + tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.EVENTS) # Ensure SDL event queue is enabled + + lib.SDL_PumpEvents() # Clear everything from the queue + lib.SDL_FlushEvents(lib.SDL_EVENT_FIRST, lib.SDL_EVENT_LAST) + + assert _check( # Fill the queue with EVENT_PACK + lib.SDL_PeepEvents(EVENT_PACK, len(EVENT_PACK), lib.SDL_ADDEVENT, lib.SDL_EVENT_FIRST, lib.SDL_EVENT_LAST) + ) == len(EVENT_PACK) + + +def test_get_events() -> None: + push_events() + assert tuple(tcod.event.get()) == EXPECTED_EVENTS + + assert tuple(tcod.event.get()) == () + assert tuple(tcod.event.wait(timeout=0)) == () + + push_events() + assert tuple(tcod.event.wait()) == EXPECTED_EVENTS + + +def test_event_dispatch() -> None: + push_events() + with pytest.deprecated_call(): + tcod.event.EventDispatch().event_wait(timeout=0) + push_events() + with pytest.deprecated_call(): + tcod.event.EventDispatch().event_get() From 3e07cbf02a523c126d57d98ce7758f6f7197449b Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 11:52:12 -0700 Subject: [PATCH 1085/1101] Fix missing log items in eventget example --- examples/eventget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/eventget.py b/examples/eventget.py index dd3e4058..9dc82ca3 100755 --- a/examples/eventget.py +++ b/examples/eventget.py @@ -54,8 +54,8 @@ def main() -> None: # noqa: C901, PLR0912 joysticks.remove(joystick) case tcod.event.MouseMotion(): motion_desc = str(event) - case _: # Log all events other than MouseMotion. - event_log.append(repr(event)) + if not isinstance(event, tcod.event.MouseMotion): # Log all events other than MouseMotion + event_log.append(repr(event)) if __name__ == "__main__": From 3b3330205537477da209bb3777f130d72a717edb Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 11:53:06 -0700 Subject: [PATCH 1086/1101] Update WindowEvent to handle all window event types Try to reduce code duplication in general at the cost of several minor API breaks --- .vscode/settings.json | 1 + CHANGELOG.md | 1 + tcod/event.py | 176 ++++++++++++++++++++++++++++-------------- 3 files changed, 118 insertions(+), 60 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f3eaa0f..10b92720 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -206,6 +206,7 @@ "htmlzip", "IBEAM", "ibus", + "ICCPROF", "ifdef", "ifndef", "iinfo", diff --git a/CHANGELOG.md b/CHANGELOG.md index e85a0ca6..a970f240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `ClipboardUpdate` event. - `Drop` event. - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes. + - `WindowEvent.data` and `WindowEvent.window_id` attributes and added missing SDL3 window events. ### Changed diff --git a/tcod/event.py b/tcod/event.py index 6c474c86..9b916d22 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -669,65 +669,113 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: return cls(text=str(ffi.string(sdl_event.text.text, 32), encoding="utf8"), **_unpack_sdl_event(sdl_event)) +_WindowTypes = Literal[ + "WindowShown", + "WindowHidden", + "WindowExposed", + "WindowMoved", + "WindowResized", + "PixelSizeChanged", + "MetalViewResized", + "WindowMinimized", + "WindowMaximized", + "WindowRestored", + "WindowEnter", + "WindowLeave", + "WindowFocusGained", + "WindowFocusLost", + "WindowClose", + "WindowTakeFocus", + "WindowHitTest", + "ICCProfileChanged", + "DisplayChanged", + "DisplayScaleChanged", + "SafeAreaChanged", + "Occluded", + "EnterFullscreen", + "LeaveFullscreen", + "Destroyed", + "HDRStateChanged", +] + + @attrs.define(slots=True, kw_only=True) class WindowEvent(Event): - """A window event.""" - - type: Final[ # Narrowing final type. - Literal[ - "WindowShown", - "WindowHidden", - "WindowExposed", - "WindowMoved", - "WindowResized", - "WindowMinimized", - "WindowMaximized", - "WindowRestored", - "WindowEnter", - "WindowLeave", - "WindowFocusGained", - "WindowFocusLost", - "WindowClose", - "WindowTakeFocus", - "WindowHitTest", - ] - ] + """A window event. + + Example:: + + match event: + case tcod.event.WindowEvent(type="WindowShown", window_id=window_id): + print(f"Window {window_id} was shown") + case tcod.event.WindowEvent(type="WindowHidden", window_id=window_id): + print(f"Window {window_id} was hidden") + case tcod.event.WindowEvent(type="WindowExposed", window_id=window_id): + print(f"Window {window_id} was exposed and needs to be redrawn") + case tcod.event.WindowEvent(type="WindowMoved", data=(x, y), window_id=window_id): + print(f"Window {window_id} was moved to {x=},{y=}") + case tcod.event.WindowEvent(type="WindowResized", data=(width, height), window_id=window_id): + print(f"Window {window_id} was resized to {width=},{height=}") + case tcod.event.WindowEvent(type="WindowMinimized", window_id=window_id): + print(f"Window {window_id} was minimized") + case tcod.event.WindowEvent(type="WindowMaximized", window_id=window_id): + print(f"Window {window_id} was maximized") + case tcod.event.WindowEvent(type="WindowRestored", window_id=window_id): + print(f"Window {window_id} was restored") + case tcod.event.WindowEvent(type="WindowEnter", window_id=window_id): + print(f"Mouse cursor has entered window {window_id}") + case tcod.event.WindowEvent(type="WindowLeave", window_id=window_id): + print(f"Mouse cursor has left window {window_id}") + case tcod.event.WindowEvent(type="WindowFocusGained", window_id=window_id): + print(f"Window {window_id} has gained keyboard focus") + case tcod.event.WindowEvent(type="WindowFocusLost", window_id=window_id): + print(f"Window {window_id} has lost keyboard focus") + case tcod.event.WindowEvent(type="WindowClose", window_id=window_id): + print(f"Window {window_id} has been closed") + case tcod.event.WindowEvent(type="DisplayChanged", data=(display_id, _), window_id=window_id): + print(f"Window {window_id} has been moved to display {display_id}") + case tcod.event.WindowEvent(type=subtype, data=data, window_id=window_id): + print(f"Other window event {subtype} on window {window_id} with {data=}") + + .. versionchanged:: Unreleased + Added `data` and `window_id` attributes and added missing SDL3 window events. + """ + + type: Final[_WindowTypes] """The current window event. This can be one of various options.""" + window_id: int + """The SDL window ID associated with this event.""" + + data: tuple[int, int] + """The SDL data associated with this event. What these values are for depends on the event sub-type.""" + @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined: if sdl_event.type not in _WINDOW_TYPES_FROM_ENUM: return Undefined._from_sdl_event(sdl_event) event_type: Final = _WINDOW_TYPES_FROM_ENUM[sdl_event.type] - self: WindowEvent + new_cls = cls if sdl_event.type == lib.SDL_EVENT_WINDOW_MOVED: - self = WindowMoved( - x=int(sdl_event.window.data1), y=int(sdl_event.window.data2), **_unpack_sdl_event(sdl_event) - ) - elif sdl_event.type in ( - lib.SDL_EVENT_WINDOW_RESIZED, - lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, - ): - self = WindowResized( - type=event_type, # type: ignore[arg-type] # Currently NOT validated - width=int(sdl_event.window.data1), - height=int(sdl_event.window.data2), - **_unpack_sdl_event(sdl_event), - ) - else: - self = cls( - type=event_type, # type: ignore[arg-type] # Currently NOT validated - **_unpack_sdl_event(sdl_event), - ) - return self + new_cls = WindowMoved + elif sdl_event.type == lib.SDL_EVENT_WINDOW_RESIZED: + new_cls = WindowResized + return new_cls( + type=event_type, + window_id=int(sdl_event.window.windowID), + data=(int(sdl_event.window.data1), int(sdl_event.window.data2)), + **_unpack_sdl_event(sdl_event), + ) -_WINDOW_TYPES_FROM_ENUM: Final = { +_WINDOW_TYPES_FROM_ENUM: Final[dict[int, _WindowTypes]] = { lib.SDL_EVENT_WINDOW_SHOWN: "WindowShown", lib.SDL_EVENT_WINDOW_HIDDEN: "WindowHidden", lib.SDL_EVENT_WINDOW_EXPOSED: "WindowExposed", lib.SDL_EVENT_WINDOW_MOVED: "WindowMoved", lib.SDL_EVENT_WINDOW_RESIZED: "WindowResized", + lib.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: "PixelSizeChanged", + lib.SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: "MetalViewResized", lib.SDL_EVENT_WINDOW_MINIMIZED: "WindowMinimized", lib.SDL_EVENT_WINDOW_MAXIMIZED: "WindowMaximized", lib.SDL_EVENT_WINDOW_RESTORED: "WindowRestored", @@ -737,42 +785,50 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> WindowEvent | Undefined: lib.SDL_EVENT_WINDOW_FOCUS_LOST: "WindowFocusLost", lib.SDL_EVENT_WINDOW_CLOSE_REQUESTED: "WindowClose", lib.SDL_EVENT_WINDOW_HIT_TEST: "WindowHitTest", + lib.SDL_EVENT_WINDOW_ICCPROF_CHANGED: "ICCProfileChanged", + lib.SDL_EVENT_WINDOW_DISPLAY_CHANGED: "DisplayChanged", + lib.SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: "DisplayScaleChanged", + lib.SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: "SafeAreaChanged", + lib.SDL_EVENT_WINDOW_OCCLUDED: "Occluded", + lib.SDL_EVENT_WINDOW_ENTER_FULLSCREEN: "EnterFullscreen", + lib.SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: "LeaveFullscreen", + lib.SDL_EVENT_WINDOW_DESTROYED: "Destroyed", + lib.SDL_EVENT_WINDOW_HDR_STATE_CHANGED: "HDRStateChanged", } @attrs.define(slots=True, kw_only=True) class WindowMoved(WindowEvent): - """Window moved event. + """Window moved event.""" - Attributes: - x (int): Movement on the x-axis. - y (int): Movement on the y-axis. - """ - - type: Final[Literal["WINDOWMOVED"]] = "WINDOWMOVED" # type: ignore[assignment,misc] - """Always "WINDOWMOVED".""" + @property + def x(self) -> int: + """Movement on the x-axis.""" + return self.data[0] - x: int - y: int + @property + def y(self) -> int: + """Movement on the y-axis.""" + return self.data[1] @attrs.define(slots=True, kw_only=True) class WindowResized(WindowEvent): """Window resized event. - Attributes: - width (int): The current width of the window. - height (int): The current height of the window. - .. versionchanged:: 19.4 Removed "WindowSizeChanged" type. """ - type: Final[Literal["WindowResized"]] = "WindowResized" # type: ignore[misc] - """Always "WindowResized".""" + @property + def width(self) -> int: + """The current width of the window.""" + return self.data[0] - width: int - height: int + @property + def height(self) -> int: + """The current height of the window.""" + return self.data[1] @attrs.define(slots=True, kw_only=True) From 25aa1e0bc8c6fd575fe73969fac0fc8287787066 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 12:10:24 -0700 Subject: [PATCH 1087/1101] Add `which` and `window_id` attribute for mouse events --- CHANGELOG.md | 1 + tcod/event.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a970f240..c9f385fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `Drop` event. - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes. - `WindowEvent.data` and `WindowEvent.window_id` attributes and added missing SDL3 window events. + - `which` and `window_id` attributes for mouse events. ### Changed diff --git a/tcod/event.py b/tcod/event.py index 9b916d22..3fae7952 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -446,6 +446,18 @@ class MouseState(Event): state: MouseButtonMask = attrs.field(default=MouseButtonMask(0)) """A bitmask of which mouse buttons are currently held.""" + which: int = 0 + """The mouse device ID for this event. + + .. versionadded:: Unreleased + """ + + window_id: int = 0 + """The window ID with mouse focus. + + .. versionadded:: Unreleased + """ + @property def integer_position(self) -> Point[int]: """Integer coordinates of this event. @@ -547,6 +559,7 @@ def tile_motion(self, xy: tuple[int, int]) -> None: @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: motion = sdl_event.motion + common = {"which": int(motion.which), "window_id": int(motion.windowID)} state = MouseButtonMask(motion.state) pixel = Point(float(motion.x), float(motion.y)) @@ -559,6 +572,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: tile=None, tile_motion=None, state=state, + **common, **_unpack_sdl_event(sdl_event), ) else: @@ -573,6 +587,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: tile=tile, tile_motion=tile_motion, state=state, + **common, **_unpack_sdl_event(sdl_event), ) self.sdl_event = sdl_event @@ -601,6 +616,18 @@ class MouseButtonEvent(Event): Is now strictly a :any:`MouseButton` type. """ + which: int = 0 + """The mouse device ID for this event. + + .. versionadded:: Unreleased + """ + + window_id: int = 0 + """The window ID with mouse focus. + + .. versionadded:: Unreleased + """ + @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: button = sdl_event.button @@ -610,7 +637,14 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: tile: Point[int] | None = None else: tile = Point(floor(subtile[0]), floor(subtile[1])) - self = cls(position=pixel, tile=tile, button=MouseButton(button.button), **_unpack_sdl_event(sdl_event)) + self = cls( + position=pixel, + tile=tile, + button=MouseButton(button.button), + which=int(button.which), + window_id=int(button.windowID), + **_unpack_sdl_event(sdl_event), + ) self.sdl_event = sdl_event return self From bc3a6b8b201bf87494cc93b01b4696f9365e45ce Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 12:47:04 -0700 Subject: [PATCH 1088/1101] Resolve missing docstrings and minor lint issues Fix documented word accidentally interpreted as a return type --- tcod/map.py | 20 ++++++++++---------- tcod/noise.py | 17 ++++++++++++++--- tcod/path.py | 4 ++++ tcod/sdl/joystick.py | 6 ++++++ tcod/sdl/mouse.py | 7 ++++++- tcod/sdl/render.py | 2 +- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/tcod/map.py b/tcod/map.py index 5054226b..e1f4403b 100644 --- a/tcod/map.py +++ b/tcod/map.py @@ -3,7 +3,7 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Final, Literal import numpy as np from typing_extensions import deprecated @@ -31,13 +31,6 @@ class Map: height (int): Height of the new Map. order (str): Which numpy memory order to use. - Attributes: - width (int): Read only width of this Map. - height (int): Read only height of this Map. - transparent: A boolean array of transparent cells. - walkable: A boolean array of walkable cells. - fov: A boolean array of the cells lit by :any:'compute_fov'. - Example:: >>> import tcod @@ -80,8 +73,10 @@ def __init__( order: Literal["C", "F"] = "C", ) -> None: """Initialize the map.""" - self.width = width - self.height = height + self.width: Final = width + """Read only width of this Map.""" + self.height: Final = height + """Read only height of this Map.""" self._order: Literal["C", "F"] = tcod._internal.verify_order(order) self._buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_) @@ -100,16 +95,19 @@ def __as_cdata(self) -> Any: # noqa: ANN401 @property def transparent(self) -> NDArray[np.bool_]: + """A boolean array of transparent cells.""" buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 0] return buffer.T if self._order == "F" else buffer @property def walkable(self) -> NDArray[np.bool_]: + """A boolean array of walkable cells.""" buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 1] return buffer.T if self._order == "F" else buffer @property def fov(self) -> NDArray[np.bool_]: + """A boolean array of the cells lit by :any:'compute_fov'.""" buffer: np.ndarray[Any, np.dtype[np.bool_]] = self._buffer[:, :, 2] return buffer.T if self._order == "F" else buffer @@ -146,6 +144,7 @@ def compute_fov( lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm) def __setstate__(self, state: dict[str, Any]) -> None: + """Unpickle this instance.""" if "_Map__buffer" in state: # Deprecated since 19.6 state["_buffer"] = state.pop("_Map__buffer") if "buffer" in state: # Deprecated @@ -159,6 +158,7 @@ def __setstate__(self, state: dict[str, Any]) -> None: self.map_c = self.__as_cdata() def __getstate__(self) -> dict[str, Any]: + """Pickle this instance.""" state = self.__dict__.copy() del state["map_c"] return state diff --git a/tcod/noise.py b/tcod/noise.py index e1295037..161dfc26 100644 --- a/tcod/noise.py +++ b/tcod/noise.py @@ -66,6 +66,7 @@ class Algorithm(enum.IntEnum): """Wavelet noise.""" def __repr__(self) -> str: + """Return the string representation for this algorithm.""" return f"tcod.noise.Algorithm.{self.name}" @@ -88,6 +89,7 @@ class Implementation(enum.IntEnum): """Turbulence noise implementation.""" def __repr__(self) -> str: + """Return the string representation for this implementation.""" return f"tcod.noise.Implementation.{self.name}" @@ -161,16 +163,17 @@ def __rng_from_seed(seed: None | int | tcod.random.Random) -> tcod.random.Random return seed def __repr__(self) -> str: + """Return the string representation of this noise instance.""" parameters = [ f"dimensions={self.dimensions}", f"algorithm={self.algorithm!r}", f"implementation={Implementation(self.implementation)!r}", ] - if self.hurst != 0.5: + if self.hurst != 0.5: # noqa: PLR2004 # Default value parameters.append(f"hurst={self.hurst}") - if self.lacunarity != 2: + if self.lacunarity != 2: # noqa: PLR2004 # Default value parameters.append(f"lacunarity={self.lacunarity}") - if self.octaves != 4: + if self.octaves != 4: # noqa: PLR2004 # Default value parameters.append(f"octaves={self.octaves}") if self._seed is not None: parameters.append(f"seed={self._seed}") @@ -178,10 +181,12 @@ def __repr__(self) -> str: @property def dimensions(self) -> int: + """Number of dimensions supported by this noise generator.""" return int(self._tdl_noise_c.dimensions) @property def algorithm(self) -> int: + """Current selected algorithm. Can be changed.""" noise_type = self.noise_c.noise_type return Algorithm(noise_type) if noise_type else Algorithm.SIMPLEX @@ -191,6 +196,7 @@ def algorithm(self, value: int) -> None: @property def implementation(self) -> int: + """Current selected implementation. Can be changed.""" return Implementation(self._tdl_noise_c.implementation) @implementation.setter @@ -202,14 +208,17 @@ def implementation(self, value: int) -> None: @property def hurst(self) -> float: + """Noise hurst exponent. Can be changed.""" return float(self.noise_c.H) @property def lacunarity(self) -> float: + """Noise lacunarity. Can be changed.""" return float(self.noise_c.lacunarity) @property def octaves(self) -> float: + """Level of detail on fBm and turbulence implementations. Can be changed.""" return float(self._tdl_noise_c.octaves) @octaves.setter @@ -343,6 +352,7 @@ def sample_ogrid(self, ogrid: Sequence[ArrayLike]) -> NDArray[np.float32]: return out def __getstate__(self) -> dict[str, Any]: + """Support picking this instance.""" state = self.__dict__.copy() if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL: # noqa: PLR2004 # Trigger a side effect of wavelet, so that copies will be synced. @@ -374,6 +384,7 @@ def __getstate__(self) -> dict[str, Any]: return state def __setstate__(self, state: dict[str, Any]) -> None: + """Unpickle this instance.""" if isinstance(state, tuple): # deprecated format return self._setstate_old(state) # unpack wavelet tile data if it exists diff --git a/tcod/path.py b/tcod/path.py index d68fa8e1..ecdb6b46 100644 --- a/tcod/path.py +++ b/tcod/path.py @@ -112,6 +112,7 @@ def __init__( callback: Callable[[int, int, int, int], float], shape: tuple[int, int], ) -> None: + """Initialize this callback.""" self.callback = callback super().__init__(callback, shape) @@ -139,6 +140,7 @@ def __new__(cls, array: ArrayLike) -> Self: return np.asarray(array).view(cls) def __repr__(self) -> str: + """Return the string representation of this object.""" return f"{self.__class__.__name__}({repr(self.view(np.ndarray))!r})" def get_tcod_path_ffi(self) -> tuple[Any, Any, tuple[int, int]]: @@ -1068,10 +1070,12 @@ def __init__(self, *, cost: ArrayLike, cardinal: int, diagonal: int, greed: int @property def ndim(self) -> int: + """Number of dimensions.""" return 2 @property def shape(self) -> tuple[int, int]: + """Shape of this graph.""" return self._shape @property diff --git a/tcod/sdl/joystick.py b/tcod/sdl/joystick.py index e7e1bec0..e6f2de84 100644 --- a/tcod/sdl/joystick.py +++ b/tcod/sdl/joystick.py @@ -103,6 +103,7 @@ class Joystick: """Currently opened joysticks.""" def __init__(self, sdl_joystick_p: Any) -> None: # noqa: ANN401 + """Wrap an SDL joystick C pointer.""" self.sdl_joystick_p: Final = sdl_joystick_p """The CFFI pointer to an SDL_Joystick struct.""" self.axes: Final[int] = _check_int(lib.SDL_GetNumJoystickAxes(self.sdl_joystick_p), failure=-1) @@ -135,11 +136,13 @@ def _from_instance_id(cls, instance_id: int) -> Joystick: return cls._by_instance_id[instance_id] def __eq__(self, other: object) -> bool: + """Return True if `self` and `other` refer to the same joystick.""" if isinstance(other, Joystick): return self.id == other.id return NotImplemented def __hash__(self) -> int: + """Return the joystick id as a hash.""" return hash(self.id) def _get_guid(self) -> str: @@ -173,6 +176,7 @@ class GameController: """Currently opened controllers.""" def __init__(self, sdl_controller_p: Any) -> None: # noqa: ANN401 + """Wrap an SDL controller C pointer.""" self.sdl_controller_p: Final = sdl_controller_p self.joystick: Final = Joystick(lib.SDL_GetGamepadJoystick(self.sdl_controller_p)) """The :any:`Joystick` associated with this controller.""" @@ -200,11 +204,13 @@ def get_axis(self, axis: ControllerAxis) -> int: return int(lib.SDL_GetGamepadAxis(self.sdl_controller_p, axis)) def __eq__(self, other: object) -> bool: + """Return True if `self` and `other` are both controllers referring to the same joystick.""" if isinstance(other, GameController): return self.joystick.id == other.joystick.id return NotImplemented def __hash__(self) -> int: + """Return the joystick id as a hash.""" return hash(self.joystick.id) # These could exist as convenience functions, but the get_X functions are probably better. diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index 4b1f10ef..c1dbff7f 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -28,6 +28,7 @@ class Cursor: """A cursor icon for use with :any:`set_cursor`.""" def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 + """Wrap an SDL cursor C pointer.""" if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"): msg = f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)})." raise TypeError(msg) @@ -37,9 +38,13 @@ def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 self.p = sdl_cursor_p def __eq__(self, other: object) -> bool: - return bool(self.p == getattr(other, "p", None)) + """Return True if `self` is the same cursor as `other`.""" + if isinstance(other, Cursor): + return bool(self.p == getattr(other, "p", None)) + return NotImplemented def __hash__(self) -> int: + """Returns the hash of this objects C pointer.""" return hash(self.p) @classmethod diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 7e4a5df6..05147825 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -571,7 +571,7 @@ def read_pixels( See https://wiki.libsdl.org/SDL3/SDL_RenderReadPixels Returns: - The output uint8 array of shape: ``(height, width, channels)`` with the fetched pixels. + The output uint8 array of shape ``(height, width, channels)`` with the fetched pixels. .. versionadded:: 15.0 From a63d3a16d7cb974229a8f4cbc757f3fdf9d48f98 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 13:34:43 -0700 Subject: [PATCH 1089/1101] Add timestamp to events, SDL time functions Adjust inherited members from Event, hide the deprecated `type` attribute. --- CHANGELOG.md | 1 + docs/tcod/event.rst | 2 +- tcod/event.py | 44 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f385fa..ea5dc596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - `KeyboardEvent.pressed`, `KeyboardEvent.which`, `KeyboardEvent.window_id` attributes. - `WindowEvent.data` and `WindowEvent.window_id` attributes and added missing SDL3 window events. - `which` and `window_id` attributes for mouse events. + - Events now have `Event.timestamp` and `Event.timestamp_ns` which use SDL's timer at `tcod.event.time` and `tcod.event.time_ns`. ### Changed diff --git a/docs/tcod/event.rst b/docs/tcod/event.rst index 3f2d3eeb..1fd5c7bb 100644 --- a/docs/tcod/event.rst +++ b/docs/tcod/event.rst @@ -3,7 +3,7 @@ SDL Event Handling ``tcod.event`` .. automodule:: tcod.event :members: - :inherited-members: object, int, str, tuple, Event + :inherited-members: object, int, str, tuple :member-order: bysource :exclude-members: KeySym, Scancode, Modifier, get, wait diff --git a/tcod/event.py b/tcod/event.py index 3fae7952..28ed6c15 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -314,12 +314,14 @@ class _CommonSDLEventAttributes(TypedDict): """Common keywords for Event subclasses.""" sdl_event: _C_SDL_Event + timestamp_ns: int def _unpack_sdl_event(sdl_event: _C_SDL_Event) -> _CommonSDLEventAttributes: """Unpack an SDL_Event union into common attributes, such as timestamp.""" return { "sdl_event": sdl_event, + "timestamp_ns": sdl_event.common.timestamp, } @@ -328,7 +330,27 @@ class Event: """The base event class.""" sdl_event: _C_SDL_Event = attrs.field(default=None, eq=False, repr=False) - """When available, this holds a python-cffi 'SDL_Event*' pointer. All sub-classes have this attribute.""" + """Holds a python-cffi ``SDL_Event*`` pointer for this event when available.""" + + timestamp_ns: int = attrs.field(default=0, eq=False) + """The time of this event in nanoseconds since SDL has been initialized. + + .. seealso:: + :any:`tcod.event.time_ns` + + .. versionadded:: Unreleased + """ + + @property + def timestamp(self) -> float: + """The time of this event in seconds since SDL has been initialized. + + .. seealso:: + :any:`tcod.event.time` + + .. versionadded:: Unreleased + """ + return self.timestamp_ns / 1_000_000_000 @property @deprecated("The Event.type attribute is deprecated, use isinstance instead.") @@ -337,6 +359,8 @@ def type(self) -> str: .. deprecated:: Unreleased Using this attribute is now actively discouraged. Use :func:`isinstance` or :ref:`match`. + + :meta private: """ type_override: str | None = getattr(self, "_type", None) if type_override is not None: @@ -3070,6 +3094,22 @@ def __getattr__(name: str) -> int: return value +def time_ns() -> int: + """Return the nanoseconds elapsed since SDL was initialized. + + .. versionadded:: Unreleased + """ + return int(lib.SDL_GetTicksNS()) + + +def time() -> float: + """Return the seconds elapsed since SDL was initialized. + + .. versionadded:: Unreleased + """ + return time_ns() / 1_000_000_000 + + __all__ = ( # noqa: F405 RUF022 "Point", "Modifier", @@ -3111,6 +3151,8 @@ def __getattr__(name: str) -> int: "get_modifier_state", "Scancode", "KeySym", + "time_ns", + "time", # --- From event_constants.py --- "MOUSEWHEEL_NORMAL", "MOUSEWHEEL_FLIPPED", From 8e0324e1e7252e21163e8a714251c707d4647c9f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 13:38:44 -0700 Subject: [PATCH 1090/1101] Remove reference to deprecated type attribute --- tcod/event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index 28ed6c15..ab3d36f2 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -682,12 +682,12 @@ def state(self) -> int: # noqa: D102 # Skip docstring for deprecated property @attrs.define(slots=True, kw_only=True) class MouseButtonDown(MouseButtonEvent): - """Same as MouseButtonEvent but with ``type="MouseButtonDown"``.""" + """Mouse button has been pressed.""" @attrs.define(slots=True, kw_only=True) class MouseButtonUp(MouseButtonEvent): - """Same as MouseButtonEvent but with ``type="MouseButtonUp"``.""" + """Mouse button has been released.""" @attrs.define(slots=True, kw_only=True) From d37feeae0731dac28ce462acf0925bc2ca6dc067 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 15:01:12 -0700 Subject: [PATCH 1091/1101] Remove unmaintained termbox Upstream repo has gone stale This might have worked better as an external project --- examples/termbox/README.md | 57 ------ examples/termbox/termbox.py | 296 -------------------------------- examples/termbox/termboxtest.py | 137 --------------- 3 files changed, 490 deletions(-) delete mode 100644 examples/termbox/README.md delete mode 100755 examples/termbox/termbox.py delete mode 100755 examples/termbox/termboxtest.py diff --git a/examples/termbox/README.md b/examples/termbox/README.md deleted file mode 100644 index 36f13c20..00000000 --- a/examples/termbox/README.md +++ /dev/null @@ -1,57 +0,0 @@ -API of `termbox` Python module implemented in `tld`. - -The code here are modified files from -[termbox repository](https://github.com/nsf/termbox/), so please consult -it for the license and other info. - -The code consists of two part - `termbox.py` module with API, translation -of official binding form the description below into `tld`: - -https://github.com/nsf/termbox/blob/b20c0a11/src/python/termboxmodule.pyx - -And the example `termboxtest.py` which is copied verbatim from: - -https://github.com/nsf/termbox/blob/b20c0a11/test_termboxmodule.py - -### API Mapping Notes - -Notes taken while mapping the Termbox class: - - tb_init() // initialization console = tdl.init(132, 60) - tb_shutdown() // shutdown - - tb_width() // width of the terminal screen console.width - tb_height() // height of the terminal screen console.height - - tb_clear() // clear buffer console.clear() - tb_present() // sync internal buffer with terminal tdl.flush() - - tb_put_cell() - tb_change_cell() console.draw_char(x, y, ch, fg, bg) - tb_blit() // drawing functions - - tb_select_input_mode() // change input mode - tb_peek_event() // peek a keyboard event - tb_poll_event() // wait for a keyboard event * tdl.event.get() - - - * - means the translation is not direct - - - - init... - tdl doesn't allow to resize window (or rather libtcod) - tb works in existing terminal window and queries it rather than making own - - colors... - tdl uses RGB values - tb uses it own constants - - event... - tb returns event one by one - tdl return an event iterator - - - tb Event tdl Event - .type .type - EVENT_KEY KEYDOWN diff --git a/examples/termbox/termbox.py b/examples/termbox/termbox.py deleted file mode 100755 index 4634c990..00000000 --- a/examples/termbox/termbox.py +++ /dev/null @@ -1,296 +0,0 @@ -"""Implementation of Termbox Python API in tdl. - -See README.md for details. -""" - -import tdl - -""" -Implementation status: - [ ] tdl.init() needs a window, made 132x60 - [ ] Termbox.close() is not implemented, does nothing - [ ] poll_event needs review, because it does not - completely follows the original logic - [ ] peek is stubbed, but not implemented - [ ] not all keys/events are mapped -""" - -# ruff: noqa - - -class TermboxException(Exception): - def __init__(self, msg) -> None: - self.msg = msg - - def __str__(self) -> str: - return self.msg - - -_instance = None - -# keys ---------------------------------- -KEY_F1 = 0xFFFF - 0 -KEY_F2 = 0xFFFF - 1 -KEY_F3 = 0xFFFF - 2 -KEY_F4 = 0xFFFF - 3 -KEY_F5 = 0xFFFF - 4 -KEY_F6 = 0xFFFF - 5 -KEY_F7 = 0xFFFF - 6 -KEY_F8 = 0xFFFF - 7 -KEY_F9 = 0xFFFF - 8 -KEY_F10 = 0xFFFF - 9 -KEY_F11 = 0xFFFF - 10 -KEY_F12 = 0xFFFF - 11 -KEY_INSERT = 0xFFFF - 12 -KEY_DELETE = 0xFFFF - 13 - -KEY_PGUP = 0xFFFF - 16 -KEY_PGDN = 0xFFFF - 17 - -KEY_MOUSE_LEFT = 0xFFFF - 22 -KEY_MOUSE_RIGHT = 0xFFFF - 23 -KEY_MOUSE_MIDDLE = 0xFFFF - 24 -KEY_MOUSE_RELEASE = 0xFFFF - 25 -KEY_MOUSE_WHEEL_UP = 0xFFFF - 26 -KEY_MOUSE_WHEEL_DOWN = 0xFFFF - 27 - -KEY_CTRL_TILDE = 0x00 -KEY_CTRL_2 = 0x00 -KEY_CTRL_A = 0x01 -KEY_CTRL_B = 0x02 -KEY_CTRL_C = 0x03 -KEY_CTRL_D = 0x04 -KEY_CTRL_E = 0x05 -KEY_CTRL_F = 0x06 -KEY_CTRL_G = 0x07 -KEY_BACKSPACE = 0x08 -KEY_CTRL_H = 0x08 -KEY_TAB = 0x09 -KEY_CTRL_I = 0x09 -KEY_CTRL_J = 0x0A -KEY_CTRL_K = 0x0B -KEY_CTRL_L = 0x0C -KEY_ENTER = 0x0D -KEY_CTRL_M = 0x0D -KEY_CTRL_N = 0x0E -KEY_CTRL_O = 0x0F -KEY_CTRL_P = 0x10 -KEY_CTRL_Q = 0x11 -KEY_CTRL_R = 0x12 -KEY_CTRL_S = 0x13 -KEY_CTRL_T = 0x14 -KEY_CTRL_U = 0x15 -KEY_CTRL_V = 0x16 -KEY_CTRL_W = 0x17 -KEY_CTRL_X = 0x18 -KEY_CTRL_Y = 0x19 -KEY_CTRL_Z = 0x1A - - -# -- mapped to tdl -KEY_HOME = "HOME" -KEY_END = "END" -KEY_ARROW_UP = "UP" -KEY_ARROW_DOWN = "DOWN" -KEY_ARROW_LEFT = "LEFT" -KEY_ARROW_RIGHT = "RIGHT" -KEY_ESC = "ESCAPE" -# /-- - - -KEY_CTRL_LSQ_BRACKET = 0x1B -KEY_CTRL_3 = 0x1B -KEY_CTRL_4 = 0x1C -KEY_CTRL_BACKSLASH = 0x1C -KEY_CTRL_5 = 0x1D -KEY_CTRL_RSQ_BRACKET = 0x1D -KEY_CTRL_6 = 0x1E -KEY_CTRL_7 = 0x1F -KEY_CTRL_SLASH = 0x1F -KEY_CTRL_UNDERSCORE = 0x1F -KEY_SPACE = 0x20 -KEY_BACKSPACE2 = 0x7F -KEY_CTRL_8 = 0x7F - -MOD_ALT = 0x01 - -# attributes ---------------------- - -# -- mapped to tdl -DEFAULT = Ellipsis - -BLACK = 0x000000 -RED = 0xFF0000 -GREEN = 0x00FF00 -YELLOW = 0xFFFF00 -BLUE = 0x0000FF -MAGENTA = 0xFF00FF -CYAN = 0x00FFFF -WHITE = 0xFFFFFF -# /-- - -BOLD = 0x10 -UNDERLINE = 0x20 -REVERSE = 0x40 - -# misc ---------------------------- - -HIDE_CURSOR = -1 -INPUT_CURRENT = 0 -INPUT_ESC = 1 -INPUT_ALT = 2 -OUTPUT_CURRENT = 0 -OUTPUT_NORMAL = 1 -OUTPUT_256 = 2 -OUTPUT_216 = 3 -OUTPUT_GRAYSCALE = 4 - - -# -- mapped to tdl -EVENT_KEY = "KEYDOWN" -# /-- -EVENT_RESIZE = 2 -EVENT_MOUSE = 3 - - -class Event: - """Aggregate for Termbox Event structure.""" - - type = None - ch = None - key = None - mod = None - width = None - height = None - mousex = None - mousey = None - - def gettuple(self): - return (self.type, self.ch, self.key, self.mod, self.width, self.height, self.mousex, self.mousey) - - -class Termbox: - def __init__(self, width=132, height=60) -> None: - global _instance - if _instance: - msg = "It is possible to create only one instance of Termbox" - raise TermboxException(msg) - - try: - self.console = tdl.init(width, height) - except tdl.TDLException as e: - raise TermboxException(e) - - self.e = Event() # cache for event data - - _instance = self - - def __del__(self) -> None: - self.close() - - def __exit__(self, *args): # t, value, traceback): - self.close() - - def __enter__(self): - return self - - def close(self): - global _instance - # tb_shutdown() - _instance = None - # TBD, does nothing - - def present(self): - """Sync state of the internal cell buffer with the terminal.""" - tdl.flush() - - def change_cell(self, x, y, ch, fg, bg): - """Change cell in position (x;y).""" - self.console.draw_char(x, y, ch, fg, bg) - - def width(self): - """Returns width of the terminal screen.""" - return self.console.width - - def height(self): - """Return height of the terminal screen.""" - return self.console.height - - def clear(self): - """Clear the internal cell buffer.""" - self.console.clear() - - def set_cursor(self, x, y): - """Set cursor position to (x;y). - - Set both arguments to HIDE_CURSOR or use 'hide_cursor' function to - hide it. - """ - tb_set_cursor(x, y) - - def hide_cursor(self): - """Hide cursor.""" - tb_set_cursor(-1, -1) - - def select_input_mode(self, mode): - """Select preferred input mode: INPUT_ESC or INPUT_ALT. - - INPUT_CURRENT returns the selected mode without changing anything. - """ - return int(tb_select_input_mode(mode)) - - def select_output_mode(self, mode): - """Select preferred output mode: one of OUTPUT_* constants. - - OUTPUT_CURRENT returns the selected mode without changing anything. - """ - return int(tb_select_output_mode(mode)) - - def peek_event(self, timeout=0): - """Wait for an event up to 'timeout' milliseconds and return it. - - Returns None if there was no event and timeout is expired. - Returns a tuple otherwise: (type, unicode character, key, mod, - width, height, mousex, mousey). - """ - """ - cdef tb_event e - with self._poll_lock: - with nogil: - result = tb_peek_event(&e, timeout) - assert(result >= 0) - if result == 0: - return None - if e.ch: - uch = unichr(e.ch) - else: - uch = None - """ - # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) - - def poll_event(self): - """Wait for an event and return it. - - Returns a tuple: (type, unicode character, key, mod, width, height, - mousex, mousey). - """ - """ - cdef tb_event e - with self._poll_lock: - with nogil: - result = tb_poll_event(&e) - assert(result >= 0) - if e.ch: - uch = unichr(e.ch) - else: - uch = None - """ - for e in tdl.event.get(): - # [ ] not all events are passed thru - self.e.type = e.type - if e.type == "KEYDOWN": - self.e.key = e.key - return self.e.gettuple() - return None - - # return (e.type, uch, e.key, e.mod, e.w, e.h, e.x, e.y) diff --git a/examples/termbox/termboxtest.py b/examples/termbox/termboxtest.py deleted file mode 100755 index 696be1ce..00000000 --- a/examples/termbox/termboxtest.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python - -import termbox - -# ruff: noqa - -spaceord = ord(" ") - - -def print_line(t, msg, y, fg, bg): - w = t.width() - l = len(msg) - x = 0 - for i in range(w): - c = spaceord - if i < l: - c = ord(msg[i]) - t.change_cell(x + i, y, c, fg, bg) - - -class SelectBox: - def __init__(self, tb, choices, active=-1) -> None: - self.tb = tb - self.active = active - self.choices = choices - self.color_active = (termbox.BLACK, termbox.CYAN) - self.color_normal = (termbox.WHITE, termbox.BLACK) - - def draw(self): - for i, c in enumerate(self.choices): - color = self.color_normal - if i == self.active: - color = self.color_active - print_line(self.tb, c, i, *color) - - def validate_active(self): - if self.active < 0: - self.active = 0 - if self.active >= len(self.choices): - self.active = len(self.choices) - 1 - - def set_active(self, i): - self.active = i - self.validate_active() - - def move_up(self): - self.active -= 1 - self.validate_active() - - def move_down(self): - self.active += 1 - self.validate_active() - - -choices = [ - "This instructs Psyco", - "to compile and run as", - "much of your application", - "code as possible. This is the", - "simplest interface to Psyco.", - "In good cases you can just add", - "these two lines and enjoy the speed-up.", - "If your application does a lot", - "of initialization stuff before", - "the real work begins, you can put", - "the above two lines after this", - "initialization - e.g. after importing", - "modules, creating constant global objects, etc.", - "This instructs Psyco", - "to compile and run as", - "much of your application", - "code as possible. This is the", - "simplest interface to Psyco.", - "In good cases you can just add", - "these two lines and enjoy the speed-up.", - "If your application does a lot", - "of initialization stuff before", - "the real work begins, you can put", - "the above two lines after this", - "initialization - e.g. after importing", - "modules, creating constant global objects, etc.", -] - - -def draw_bottom_line(t, i): - i = i % 8 - w = t.width() - h = t.height() - c = i - palette = [ - termbox.DEFAULT, - termbox.BLACK, - termbox.RED, - termbox.GREEN, - termbox.YELLOW, - termbox.BLUE, - termbox.MAGENTA, - termbox.CYAN, - termbox.WHITE, - ] - for x in range(w): - t.change_cell(x, h - 1, ord(" "), termbox.BLACK, palette[c]) - t.change_cell(x, h - 2, ord(" "), termbox.BLACK, palette[c]) - c += 1 - if c > 7: - c = 0 - - -with termbox.Termbox() as t: - sb = SelectBox(t, choices, 0) - t.clear() - sb.draw() - t.present() - i = 0 - run_app = True - while run_app: - event_here = t.poll_event() - while event_here: - (type, ch, key, mod, w, h, x, y) = event_here - if type == termbox.EVENT_KEY and key == termbox.KEY_ESC: - run_app = False - if type == termbox.EVENT_KEY: - if key == termbox.KEY_ARROW_DOWN: - sb.move_down() - elif key == termbox.KEY_ARROW_UP: - sb.move_up() - elif key == termbox.KEY_HOME: - sb.set_active(-1) - elif key == termbox.KEY_END: - sb.set_active(999) - event_here = t.peek_event() - - t.clear() - sb.draw() - draw_bottom_line(t, i) - t.present() - i += 1 From 72a93eecc7d4642366f2b6423b8cc360911d5f59 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 15:17:36 -0700 Subject: [PATCH 1092/1101] Test samples_libtcodpy and fix regressions --- examples/samples_libtcodpy.py | 9 +-------- tcod/libtcodpy.py | 4 +++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/samples_libtcodpy.py b/examples/samples_libtcodpy.py index 9e0cb41d..71780d97 100755 --- a/examples/samples_libtcodpy.py +++ b/examples/samples_libtcodpy.py @@ -12,14 +12,7 @@ import sys import warnings -import tcod as libtcod - -try: # Import Psyco if available - import psyco - - psyco.full() -except ImportError: - pass +from tcod import tcod as libtcod if not sys.warnoptions: warnings.simplefilter("ignore") # Prevent flood of deprecation warnings. diff --git a/tcod/libtcodpy.py b/tcod/libtcodpy.py index 7da9ff30..bae62a1f 100644 --- a/tcod/libtcodpy.py +++ b/tcod/libtcodpy.py @@ -546,7 +546,7 @@ def bsp_split_once(node: tcod.bsp.BSP, horizontal: bool, position: int) -> None: @deprecate("Call node.split_recursive instead.", category=FutureWarning) def bsp_split_recursive( node: tcod.bsp.BSP, - randomizer: tcod.random.Random | None, + randomizer: Literal[0] | tcod.random.Random | None, nb: int, minHSize: int, # noqa: N803 minVSize: int, # noqa: N803 @@ -558,6 +558,8 @@ def bsp_split_recursive( .. deprecated:: 2.0 Use :any:`BSP.split_recursive` instead. """ + if randomizer == 0: + randomizer = None node.split_recursive(nb, minHSize, minVSize, maxHRatio, maxVRatio, randomizer) From bc4ebdd07572234b09345a5b7073634b9aa3097d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 15:47:34 -0700 Subject: [PATCH 1093/1101] Clean up sample lint issues --- examples/samples_tcod.py | 84 ++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 19d4fd92..bae9acd6 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -22,6 +22,7 @@ import tcod.bsp import tcod.cffi import tcod.console +import tcod.constants import tcod.context import tcod.event import tcod.image @@ -76,15 +77,18 @@ def _get_elapsed_time() -> float: class Sample: + """Samples base class.""" + name: str = "???" def on_enter(self) -> None: - pass + """Called when entering a sample.""" def on_draw(self) -> None: - pass + """Called every frame.""" def on_event(self, event: tcod.event.Event) -> None: + """Called for each event.""" global cur_sample match event: case tcod.event.Quit() | tcod.event.KeyDown(sym=KeySym.ESCAPE): @@ -114,9 +118,12 @@ def on_event(self, event: tcod.event.Event) -> None: class TrueColorSample(Sample): + """Simple performance benchmark.""" + name = "True colors" def __init__(self) -> None: + """Initialize random generators.""" self.noise = tcod.noise.Noise(2, tcod.noise.Algorithm.SIMPLEX) """Noise for generating color.""" @@ -124,6 +131,7 @@ def __init__(self) -> None: """Numpy generator for random text.""" def on_draw(self) -> None: + """Draw this sample.""" self.interpolate_corner_colors() self.darken_background_characters() self.randomize_sample_console() @@ -178,9 +186,15 @@ def randomize_sample_console(self) -> None: class OffscreenConsoleSample(Sample): + """Console blit example.""" + name = "Offscreen console" + CONSOLE_MOVE_RATE = 1 / 2 + CONSOLE_MOVE_MARGIN = 5 + def __init__(self) -> None: + """Initialize the offscreen console.""" self.secondary = tcod.console.Console(sample_console.width // 2, sample_console.height // 2) self.screenshot = tcod.console.Console(sample_console.width, sample_console.height) self.counter = 0.0 @@ -189,57 +203,51 @@ def __init__(self) -> None: self.x_dir = 1 self.y_dir = 1 - self.secondary.draw_frame( + self.secondary.draw_frame(0, 0, self.secondary.width, self.secondary.height, clear=False, fg=WHITE, bg=BLACK) + self.secondary.print( 0, 0, - sample_console.width // 2, - sample_console.height // 2, - "Offscreen console", - clear=False, - fg=WHITE, - bg=BLACK, + width=self.secondary.width, + height=self.secondary.height, + text=" Offscreen console ", + fg=BLACK, + bg=WHITE, + alignment=tcod.constants.CENTER, ) - self.secondary.print_box( - 1, - 2, - sample_console.width // 2 - 2, - sample_console.height // 2, - "You can render to an offscreen console and blit in on another one, simulating alpha transparency.", + self.secondary.print( + x=1, + y=2, + width=sample_console.width // 2 - 2, + height=sample_console.height // 2, + text="You can render to an offscreen console and blit in on another one, simulating alpha transparency.", fg=WHITE, bg=None, alignment=libtcodpy.CENTER, ) def on_enter(self) -> None: + """Capture the previous sample screen as this samples background.""" self.counter = _get_elapsed_time() - # get a "screenshot" of the current sample screen - sample_console.blit(dest=self.screenshot) + sample_console.blit(dest=self.screenshot) # get a "screenshot" of the current sample screen def on_draw(self) -> None: - if _get_elapsed_time() - self.counter >= 1: + """Draw and animate the offscreen console.""" + if _get_elapsed_time() - self.counter >= self.CONSOLE_MOVE_RATE: self.counter = _get_elapsed_time() self.x += self.x_dir self.y += self.y_dir - if self.x == sample_console.width / 2 + 5: + if self.x == sample_console.width / 2 + self.CONSOLE_MOVE_MARGIN: self.x_dir = -1 - elif self.x == -5: + elif self.x == -self.CONSOLE_MOVE_MARGIN: self.x_dir = 1 - if self.y == sample_console.height / 2 + 5: + if self.y == sample_console.height / 2 + self.CONSOLE_MOVE_MARGIN: self.y_dir = -1 - elif self.y == -5: + elif self.y == -self.CONSOLE_MOVE_MARGIN: self.y_dir = 1 self.screenshot.blit(sample_console) self.secondary.blit( - sample_console, - self.x, - self.y, - 0, - 0, - sample_console.width // 2, - sample_console.height // 2, - 1.0, - 0.75, + sample_console, self.x, self.y, 0, 0, sample_console.width // 2, sample_console.height // 2, 1.0, 0.75 ) @@ -298,9 +306,7 @@ def on_draw(self) -> None: for x in range(sample_console.width): value = x * 255 // sample_console.width col = (value, value, value) - libtcodpy.console_set_char_background(sample_console, x, rect_y, col, self.bk_flag) - libtcodpy.console_set_char_background(sample_console, x, rect_y + 1, col, self.bk_flag) - libtcodpy.console_set_char_background(sample_console, x, rect_y + 2, col, self.bk_flag) + sample_console.draw_rect(x=x, y=rect_y, width=1, height=3, ch=0, fg=None, bg=col, bg_blend=self.bk_flag) angle = time.time() * 2.0 cos_angle = math.cos(angle) sin_angle = math.sin(angle) @@ -312,14 +318,8 @@ def on_draw(self) -> None: # in python the easiest way is to use the line iterator for x, y in tcod.los.bresenham((xo, yo), (xd, yd)).tolist(): if 0 <= x < sample_console.width and 0 <= y < sample_console.height: - libtcodpy.console_set_char_background(sample_console, x, y, LIGHT_BLUE, self.bk_flag) - sample_console.print( - 2, - 2, - f"{self.FLAG_NAMES[self.bk_flag & 0xFF]} (ENTER to change)", - fg=WHITE, - bg=None, - ) + sample_console.draw_rect(x, y, width=1, height=1, ch=0, fg=None, bg=LIGHT_BLUE, bg_blend=self.bk_flag) + sample_console.print(2, 2, f"{self.FLAG_NAMES[self.bk_flag & 0xFF]} (ENTER to change)", fg=WHITE, bg=None) class NoiseSample(Sample): From a954fa3a7a4812282961baba271324c590aa4601 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Mar 2026 15:56:33 -0700 Subject: [PATCH 1094/1101] Prepare 21.0.0 release. --- CHANGELOG.md | 3 +++ tcod/event.py | 48 +++++++++++++++++++++++------------------------ tcod/sdl/video.py | 2 +- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5dc596..c2b81f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [21.0.0] - 2026-03-13 + ### Added - `tcod.sdl.video.Window` now accepts an SDL WindowID. @@ -39,6 +41,7 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version - Fixed incorrect C FFI types inside `tcod.event.get_mouse_state`. - Fixed regression in mouse event tile coordinates being `float` instead of `int`. `convert_coordinates_from_window` can be used if sub-tile coordinates were desired. +- Fixed regression in `libtcodpy.bsp_split_recursive` not accepting `0`. ## [20.1.0] - 2026-02-25 diff --git a/tcod/event.py b/tcod/event.py index ab3d36f2..591f5e88 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -338,7 +338,7 @@ class Event: .. seealso:: :any:`tcod.event.time_ns` - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ @property @@ -348,7 +348,7 @@ def timestamp(self) -> float: .. seealso:: :any:`tcod.event.time` - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ return self.timestamp_ns / 1_000_000_000 @@ -357,7 +357,7 @@ def timestamp(self) -> float: def type(self) -> str: """This events type. - .. deprecated:: Unreleased + .. deprecated:: 21.0 Using this attribute is now actively discouraged. Use :func:`isinstance` or :ref:`match`. :meta private: @@ -371,7 +371,7 @@ def type(self) -> str: def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Event: """Return a class instance from a python-cffi 'SDL_Event*' pointer. - .. versionchanged:: Unreleased + .. versionchanged:: 21.0 This method was unsuitable for the public API and is now private. """ raise NotImplementedError @@ -415,17 +415,17 @@ class KeyboardEvent(Event): which: int = 0 """The SDL keyboard instance ID. Zero if unknown or virtual. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ window_id: int = 0 """The SDL window ID with keyboard focus. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ pressed: bool = False """True if the key was pressed, False if the key was released. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ @classmethod @@ -473,20 +473,20 @@ class MouseState(Event): which: int = 0 """The mouse device ID for this event. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ window_id: int = 0 """The window ID with mouse focus. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ @property def integer_position(self) -> Point[int]: """Integer coordinates of this event. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ x, y = self.position return Point(floor(x), floor(y)) @@ -508,7 +508,7 @@ def pixel(self, value: Point[float]) -> None: def tile(self) -> Point[int]: """The integer tile coordinates of the mouse on the screen. - .. deprecated:: Unreleased + .. deprecated:: 21.0 Use :any:`integer_position` of the event returned by :any:`Context.convert_event` instead. """ return _verify_tile_coordinates(self._tile) @@ -542,7 +542,7 @@ class MouseMotion(MouseState): def integer_motion(self) -> Point[int]: """Integer motion of this event. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ x, y = self.position dx, dy = self.motion @@ -567,7 +567,7 @@ def pixel_motion(self, value: Point[float]) -> None: def tile_motion(self) -> Point[int]: """The tile delta. - .. deprecated:: Unreleased + .. deprecated:: 21.0 Use :any:`integer_motion` of the event returned by :any:`Context.convert_event` instead. """ return _verify_tile_coordinates(self._tile_motion) @@ -625,7 +625,7 @@ class MouseButtonEvent(Event): .. versionchanged:: 19.0 `position` and `tile` now use floating point coordinates. - .. versionchanged:: Unreleased + .. versionchanged:: 21.0 No longer a subclass of :any:`MouseState`. """ @@ -636,20 +636,20 @@ class MouseButtonEvent(Event): button: MouseButton """Which mouse button index was pressed or released in this event. - .. versionchanged:: Unreleased + .. versionchanged:: 21.0 Is now strictly a :any:`MouseButton` type. """ which: int = 0 """The mouse device ID for this event. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ window_id: int = 0 """The window ID with mouse focus. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ @classmethod @@ -795,7 +795,7 @@ class WindowEvent(Event): case tcod.event.WindowEvent(type=subtype, data=data, window_id=window_id): print(f"Other window event {subtype} on window {window_id} with {data=}") - .. versionchanged:: Unreleased + .. versionchanged:: 21.0 Added `data` and `window_id` attributes and added missing SDL3 window events. """ @@ -1013,7 +1013,7 @@ class JoystickButton(JoystickEvent): def type(self) -> Literal["JOYBUTTONUP", "JOYBUTTONDOWN"]: """Button state as a string. - .. deprecated:: Unreleased + .. deprecated:: 21.0 Use :any:`pressed` instead. """ return ("JOYBUTTONUP", "JOYBUTTONDOWN")[self.pressed] @@ -1122,7 +1122,7 @@ class ControllerButton(ControllerEvent): def type(self) -> Literal["CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN"]: """Button state as a string. - .. deprecated:: Unreleased + .. deprecated:: 21.0 Use :any:`pressed` instead. """ return ("CONTROLLERBUTTONUP", "CONTROLLERBUTTONDOWN")[self.pressed] @@ -1160,7 +1160,7 @@ def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: class ClipboardUpdate(Event): """Announces changed contents of the clipboard. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ mime_types: tuple[str, ...] @@ -1195,7 +1195,7 @@ class Drop(Event): case tcod.event.Drop(type="COMPLETE"): print("Drop handling finished") - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ type: Literal["BEGIN", "FILE", "TEXT", "COMPLETE", "POSITION"] @@ -3097,7 +3097,7 @@ def __getattr__(name: str) -> int: def time_ns() -> int: """Return the nanoseconds elapsed since SDL was initialized. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ return int(lib.SDL_GetTicksNS()) @@ -3105,7 +3105,7 @@ def time_ns() -> int: def time() -> float: """Return the seconds elapsed since SDL was initialized. - .. versionadded:: Unreleased + .. versionadded:: 21.0 """ return time_ns() / 1_000_000_000 diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index b15d8d0b..e9565f88 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -177,7 +177,7 @@ class Window: def __init__(self, sdl_window_p: Any | int) -> None: # noqa: ANN401 """Wrap a SDL_Window pointer or SDL WindowID. - .. versionchanged:: Unreleased + .. versionchanged:: 21.0 Now accepts `int` types as an SDL WindowID. """ if isinstance(sdl_window_p, int): From 84d37e224d0cf3ed05c29277b79f6773597bbada Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 4 Apr 2026 10:58:57 -0700 Subject: [PATCH 1095/1101] Add missing MouseButtonEvent.integer_position property --- CHANGELOG.md | 8 ++++++++ tcod/context.py | 4 ++-- tcod/event.py | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b81f68..8276ae28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `MouseButtonEvent.integer_position` property. + +## Fixed + +- `integer_position` was missing from mouse button events. + ## [21.0.0] - 2026-03-13 ### Added diff --git a/tcod/context.py b/tcod/context.py index f7b92314..afffe96f 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -255,8 +255,8 @@ def convert_event(self, event: _Event) -> _Event: Now returns a new event with the coordinates converted into tiles. """ event_copy = copy.copy(event) - if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)): - assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion)) + if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion, tcod.event.MouseButtonEvent)): + assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion, tcod.event.MouseButtonEvent)) event_copy.position = tcod.event.Point(*self.pixel_to_tile(event.position[0], event.position[1])) event._tile = tcod.event.Point(floor(event_copy.position[0]), floor(event_copy.position[1])) if isinstance(event, tcod.event.MouseMotion): diff --git a/tcod/event.py b/tcod/event.py index 591f5e88..ccdbc2af 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -652,6 +652,15 @@ class MouseButtonEvent(Event): .. versionadded:: 21.0 """ + @property + def integer_position(self) -> Point[int]: + """Integer coordinates of this event. + + .. versionadded:: Unreleased + """ + x, y = self.position + return Point(floor(x), floor(y)) + @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: button = sdl_event.button From 01883503319ec1031f1f3b452b522f273a18bb74 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 4 Apr 2026 11:06:59 -0700 Subject: [PATCH 1096/1101] Update primary event handing example code --- tcod/event.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tcod/event.py b/tcod/event.py index ccdbc2af..97eeb2f7 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -27,21 +27,19 @@ while True: console = context.new_console() context.present(console, integer_scaling=True) - for event in tcod.event.wait(): - context.convert_event(event) # Adds tile coordinates to mouse events. + for pixel_event in tcod.event.wait(): + event = context.convert_event(pixel_event) # Convert mouse pixel coordinates to tile coordinates + print(event) # Print all events, for learning and debugging if isinstance(event, tcod.event.Quit): - print(event) raise SystemExit() elif isinstance(event, tcod.event.KeyDown): - print(event) # Prints the Scancode and KeySym enums for this event. + print(f"{event.sym=}, {event.scancode=}") # Show Scancode and KeySym enum names if event.sym in KEY_COMMANDS: print(f"Command: {KEY_COMMANDS[event.sym]}") elif isinstance(event, tcod.event.MouseButtonDown): - print(event) # Prints the mouse button constant names for this event. + print(f"{event.button=}, {event.integer_position=}") # Show mouse button and tile elif isinstance(event, tcod.event.MouseMotion): - print(event) # Prints the mouse button mask bits in a readable format. - else: - print(event) # Print any unhandled events. + print(f"{event.integer_position=}, {event.integer_motion=}") # Current mouse tile and tile motion Python 3.10 introduced `match statements `_ which can be used to dispatch events more gracefully: @@ -61,8 +59,8 @@ while True: console = context.new_console() context.present(console, integer_scaling=True) - for event in tcod.event.wait(): - context.convert_event(event) # Adds tile coordinates to mouse events. + for pixel_event in tcod.event.wait(): + event = context.convert_event(pixel_event) # Converts mouse pixel coordinates to tile coordinates. match event: case tcod.event.Quit(): raise SystemExit() @@ -70,12 +68,14 @@ print(f"Command: {KEY_COMMANDS[sym]}") case tcod.event.KeyDown(sym=sym, scancode=scancode, mod=mod, repeat=repeat): print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}") - case tcod.event.MouseButtonDown(button=button, pixel=pixel, tile=tile): - print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}") - case tcod.event.MouseMotion(pixel=pixel, pixel_motion=pixel_motion, tile=tile, tile_motion=tile_motion): - print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}") + case tcod.event.MouseButtonDown(button=button, integer_position=tile): + print(f"MouseButtonDown: {button=}, {tile=}") + case tcod.event.MouseMotion(integer_position=tile, integer_motion=tile_motion): + assert isinstance(pixel_event, tcod.event.MouseMotion) + pixel_motion = pixel_event.motion + print(f"MouseMotion: {pixel_motion=}, {tile=}, {tile_motion=}") case tcod.event.Event() as event: - print(event) # Show any unhandled events. + print(event) # Print unhandled events .. versionadded:: 8.4 """ From 34e7c3a1f649a9f9a1953e56617c7fb8aa1ea0bc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 4 Apr 2026 11:17:26 -0700 Subject: [PATCH 1097/1101] Prepare 21.1.0 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8276ae28..52ea993b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [21.1.0] - 2026-04-04 + ### Added - `MouseButtonEvent.integer_position` property. diff --git a/tcod/event.py b/tcod/event.py index 97eeb2f7..0ae16781 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -656,7 +656,7 @@ class MouseButtonEvent(Event): def integer_position(self) -> Point[int]: """Integer coordinates of this event. - .. versionadded:: Unreleased + .. versionadded:: 21.1 """ x, y = self.position return Point(floor(x), floor(y)) From fde6ae4566d2c3d37b6d07c0bbee9884562461ec Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 4 Apr 2026 12:47:25 -0700 Subject: [PATCH 1098/1101] Modernize MouseWheel and make conversion instance checks generic Add protocols for mouse-like events so that mouse conversions automatically handle new code --- CHANGELOG.md | 8 ++++++ tcod/context.py | 7 +++-- tcod/event.py | 76 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ea993b..8137520f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +### Added + +- `tcod.event.MouseWheel` now has `which`, `window_id`, `position` and `integer_position` attributes. + +## Fixed + +- `tcod.event.convert_coordinates_from_window` was not converting all types of mouse events. + ## [21.1.0] - 2026-04-04 ### Added diff --git a/tcod/context.py b/tcod/context.py index afffe96f..01ed61f7 100644 --- a/tcod/context.py +++ b/tcod/context.py @@ -255,10 +255,11 @@ def convert_event(self, event: _Event) -> _Event: Now returns a new event with the coordinates converted into tiles. """ event_copy = copy.copy(event) - if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion, tcod.event.MouseButtonEvent)): - assert isinstance(event_copy, (tcod.event.MouseState, tcod.event.MouseMotion, tcod.event.MouseButtonEvent)) + if isinstance(event, tcod.event._MouseEventWithPosition): + assert isinstance(event_copy, tcod.event._MouseEventWithPosition) event_copy.position = tcod.event.Point(*self.pixel_to_tile(event.position[0], event.position[1])) - event._tile = tcod.event.Point(floor(event_copy.position[0]), floor(event_copy.position[1])) + if isinstance(event, tcod.event._MouseEventWithTile): + event._tile = tcod.event.Point(floor(event_copy.position[0]), floor(event_copy.position[1])) if isinstance(event, tcod.event.MouseMotion): assert isinstance(event_copy, tcod.event.MouseMotion) assert event._tile is not None diff --git a/tcod/event.py b/tcod/event.py index 0ae16781..1fff269d 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -89,7 +89,20 @@ from collections.abc import Callable, Iterator, Mapping from math import floor from pathlib import Path -from typing import TYPE_CHECKING, Any, Final, Generic, Literal, NamedTuple, TypeAlias, TypedDict, TypeVar, overload +from typing import ( + TYPE_CHECKING, + Any, + Final, + Generic, + Literal, + NamedTuple, + Protocol, + TypeAlias, + TypedDict, + TypeVar, + overload, + runtime_checkable, +) import attrs import numpy as np @@ -630,7 +643,7 @@ class MouseButtonEvent(Event): """ position: Point[float] = attrs.field(default=Point(0.0, 0.0)) - """The pixel coordinates of the mouse.""" + """The coordinates of the mouse.""" _tile: Point[int] | None = attrs.field(default=Point(0, 0), alias="tile") """The tile integer coordinates of the mouse on the screen. Deprecated.""" button: MouseButton @@ -709,13 +722,63 @@ class MouseWheel(Event): """Vertical scrolling. A positive value means scrolling away from the user.""" flipped: bool """If True then the values of `x` and `y` are the opposite of their usual values. - This depends on the settings of the Operating System. + This depends on the operating system settings. + """ + + position: Point[float] = attrs.field(default=Point(0.0, 0.0)) + """Coordinates of the mouse for this event. + + .. versionadded:: Unreleased + """ + + which: int = 0 + """Mouse device ID for this event. + + .. versionadded:: Unreleased + """ + + window_id: int = 0 + """Window ID with mouse focus. + + .. versionadded:: Unreleased """ + @property + def integer_position(self) -> Point[int]: + """Integer coordinates of this event. + + .. versionadded:: Unreleased + """ + x, y = self.position + return Point(floor(x), floor(y)) + @classmethod def _from_sdl_event(cls, sdl_event: _C_SDL_Event) -> Self: wheel = sdl_event.wheel - return cls(x=int(wheel.x), y=int(wheel.y), flipped=bool(wheel.direction), **_unpack_sdl_event(sdl_event)) + return cls( + x=int(wheel.integer_x), + y=int(wheel.integer_y), + flipped=bool(wheel.direction), + position=Point(float(wheel.mouse_x), float(wheel.mouse_y)), + which=int(wheel.which), + window_id=int(wheel.windowID), + **_unpack_sdl_event(sdl_event), + ) + + +@runtime_checkable +class _MouseEventWithPosition(Protocol): + """Mouse event with position. Used internally to handle conversions.""" + + position: Point[float] + + +@runtime_checkable +class _MouseEventWithTile(Protocol): + """Mouse event with position and deprecated tile attribute. Used internally to handle conversions.""" + + position: Point[float] + _tile: Point[int] | None @attrs.define(slots=True, kw_only=True) @@ -1742,9 +1805,10 @@ def convert_coordinates_from_window( event._tile_motion = Point( floor(position[0]) - floor(previous_position[0]), floor(position[1]) - floor(previous_position[1]) ) - if isinstance(event, (MouseState, MouseMotion)): + elif isinstance(event, _MouseEventWithPosition): event.position = Point(*convert_coordinates_from_window(event.position, context, console, dest_rect)) - event._tile = Point(floor(event.position[0]), floor(event.position[1])) + if isinstance(event, _MouseEventWithTile): + event._tile = Point(floor(event.position[0]), floor(event.position[1])) return event From c3fde2460b910e70bbb34afd32a9d73fb7e211ec Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Sat, 4 Apr 2026 13:11:57 -0700 Subject: [PATCH 1099/1101] Prepare 21.2.0 release. --- CHANGELOG.md | 2 ++ tcod/event.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8137520f..bc5f79f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [21.2.0] - 2026-04-04 + ### Added - `tcod.event.MouseWheel` now has `which`, `window_id`, `position` and `integer_position` attributes. diff --git a/tcod/event.py b/tcod/event.py index 1fff269d..6b9453cf 100644 --- a/tcod/event.py +++ b/tcod/event.py @@ -728,26 +728,26 @@ class MouseWheel(Event): position: Point[float] = attrs.field(default=Point(0.0, 0.0)) """Coordinates of the mouse for this event. - .. versionadded:: Unreleased + .. versionadded:: 21.2 """ which: int = 0 """Mouse device ID for this event. - .. versionadded:: Unreleased + .. versionadded:: 21.2 """ window_id: int = 0 """Window ID with mouse focus. - .. versionadded:: Unreleased + .. versionadded:: 21.2 """ @property def integer_position(self) -> Point[int]: """Integer coordinates of this event. - .. versionadded:: Unreleased + .. versionadded:: 21.2 """ x, y = self.position return Point(floor(x), floor(y)) From aea3d3903eb22a27bfdbe1b15ae6cd0ca96d56a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 08:56:50 +0000 Subject: [PATCH 1100/1101] Bump codecov/codecov-action from 5 to 6 in the github-actions group Bumps the github-actions group with 1 update: [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `codecov/codecov-action` from 5 to 6 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0ae29156..7896754a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -170,7 +170,7 @@ jobs: - name: Xvfb logs if: runner.os != 'Windows' run: cat /tmp/xvfb.log - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 - uses: actions/upload-artifact@v7 if: runner.os == 'Windows' with: From aaafc20a4773709c721fa1b3f02970863477ff1d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Mon, 6 Apr 2026 14:11:01 -0700 Subject: [PATCH 1101/1101] Update pre-commit Replace superfluous release action --- .github/workflows/release-on-tag.yml | 6 ++---- .pre-commit-config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 0b9f07b0..d2e52c2f 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -24,7 +24,5 @@ jobs: run: scripts/get_release_description.py | tee release_body.md - name: Create Release id: create_release - uses: ncipollo/release-action@v1 - with: - name: "" - bodyFile: release_body.md + # https://cli.github.com/manual/gh_release_create + run: gh release create "${GITHUB_REF_NAME}" --verify-tag --notes-file release_body.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aff2ef24..6baa9d9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,12 +17,12 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.13 + rev: v0.15.9 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/zizmorcore/zizmor-pre-commit - rev: v1.22.0 + rev: v1.23.1 hooks: - id: zizmor

F4AUuIr>SlugWfa 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 O0s|?dB3*$n3Hl#-2bk~x diff --git a/fonts/libtcod/consolas10x10_gs_tc.png b/fonts/libtcod/consolas10x10_gs_tc.png deleted file mode 100755 index 4b9b913f28dbce3fa4154eb91898de8fffe9cb75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8651 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 HT7>)$K!AdZ diff --git a/fonts/libtcod/consolas12x12_gs_tc.png b/fonts/libtcod/consolas12x12_gs_tc.png deleted file mode 100755 index 4789d02f613babb4bcf0cda5c9c0aecf88e71253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8204 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 diff --git a/fonts/libtcod/consolas8x8_gs_tc.png b/fonts/libtcod/consolas8x8_gs_tc.png deleted file mode 100755 index fab3c4c3f4a4275590492121891190f0ae4ae3fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5936 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 ZjC@6X#%rvnyZwRyT55W#RS?_o{{cn@EtCKN diff --git a/fonts/libtcod/consolas_unicode_10x10.png b/fonts/libtcod/consolas_unicode_10x10.png deleted file mode 100755 index f2e824cb64312803a9314acc0adaa48b3e55fbf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24558 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 diff --git a/fonts/libtcod/consolas_unicode_12x12.png b/fonts/libtcod/consolas_unicode_12x12.png deleted file mode 100755 index 608b28c00d296bfa496d313a2fa7032e4c5eb7b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30673 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