From c4f14c2fda36557a80603c034c729c6a15f78cf3 Mon Sep 17 00:00:00 2001 From: Jaskrendix Date: Wed, 17 Jan 2024 13:06:05 +0100 Subject: [PATCH] py39 --- .github/workflows/python-app.yml | 2 +- README.md | 2 +- apps/demo/demo-stitched.py | 5 +- apps/demo/demo.py | 31 +++--- apps/demo/translate.py | 1 - apps/tutorial/quest.py | 7 +- docs/_build/html/_sources/index.txt | 2 +- docs/_build/html/index.html | 2 +- docs/conf.py | 148 ++++++++++++++-------------- docs/index.rst | 2 +- pyproject.toml | 9 +- pyscroll/animation.py | 7 +- pyscroll/common.py | 12 +-- pyscroll/data.py | 38 +++---- pyscroll/group.py | 15 +-- pyscroll/isometric.py | 24 ++--- pyscroll/orthographic.py | 147 ++++++++++++++------------- pyscroll/quadtree.py | 23 +++-- tests/pyscroll/test_pyscroll.py | 17 ++-- 19 files changed, 259 insertions(+), 235 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e6c1c97..761f5aa 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9'] + python-version: ['3.9', '3.10', '3.11', '3.12'] name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 4a47e58..5d0ddbf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ pyscroll ======== -For Python 3.7+ and pygame 2.0+ +For Python 3.9+ and pygame 2.0+ __pygame-ce is supported__ diff --git a/apps/demo/demo-stitched.py b/apps/demo/demo-stitched.py index 054eabe..789c56f 100644 --- a/apps/demo/demo-stitched.py +++ b/apps/demo/demo-stitched.py @@ -9,7 +9,6 @@ from __future__ import annotations from pathlib import Path -from typing import List import pygame from pygame.locals import ( @@ -57,11 +56,11 @@ def __init__(self) -> None: self.feet = pygame.Rect(0, 0, self.rect.width * 0.5, 8) @property - def position(self) -> List[float]: + def position(self) -> list[float]: return list(self._position) @position.setter - def position(self, value: List[float]) -> None: + def position(self, value: list[float]) -> None: self._position = list(value) def update(self, dt: float) -> None: diff --git a/apps/demo/demo.py b/apps/demo/demo.py index bc7c6d0..a4f3d91 100644 --- a/apps/demo/demo.py +++ b/apps/demo/demo.py @@ -36,11 +36,12 @@ def init_screen(width, height): class ScrollTest: - """ Test and demo of pyscroll + """Test and demo of pyscroll For normal use, please see the quest demo, not this. """ + def __init__(self, filename): # load data from pytmx @@ -50,19 +51,22 @@ def __init__(self, filename): map_data = pyscroll.data.TiledMapData(tmx_data) # create new renderer - self.map_layer = pyscroll.orthographic.BufferedRenderer(map_data, screen.get_size()) + self.map_layer = pyscroll.orthographic.BufferedRenderer( + map_data, screen.get_size() + ) # create a font and pre-render some text to be displayed over the map f = pygame.font.Font(pygame.font.get_default_font(), 20) - t = ["scroll demo. press escape to quit", - "arrow keys move"] + t = ["scroll demo. press escape to quit", "arrow keys move"] # save the rendered text self.text_overlay = [f.render(i, 1, (180, 180, 0)) for i in t] # set our initial viewpoint in the center of the map - self.center = [self.map_layer.map_rect.width / 2, - self.map_layer.map_rect.height / 2] + self.center = [ + self.map_layer.map_rect.width / 2, + self.map_layer.map_rect.height / 2, + ] # the camera vector is used to handle camera movement self.camera_acc = [0, 0, 0] @@ -88,8 +92,7 @@ def draw_text(self, surface): y += text.get_height() def handle_input(self): - """ Simply handle pygame input events - """ + """Simply handle pygame input events""" for event in pygame.event.get(): if event.type == QUIT: self.running = False @@ -129,7 +132,7 @@ def handle_input(self): def update(self, td): self.last_update_time = td - friction = pow(.0001, self.last_update_time) + friction = pow(0.0001, self.last_update_time) # update the camera vector self.camera_vel[0] += self.camera_acc[0] * td @@ -167,18 +170,18 @@ def update(self, td): def run(self): clock = pygame.time.Clock() self.running = True - fps = 60. + fps = 60.0 fps_log = collections.deque(maxlen=20) try: while self.running: # somewhat smoother way to get fps and limit the framerate - clock.tick(fps*2) + clock.tick(fps * 2) try: fps_log.append(clock.get_fps()) - fps = sum(fps_log)/len(fps_log) - dt = 1/fps + fps = sum(fps_log) / len(fps_log) + dt = 1 / fps except ZeroDivisionError: continue @@ -197,7 +200,7 @@ def run(self): pygame.init() pygame.font.init() screen = init_screen(800, 600) - pygame.display.set_caption('pyscroll Test') + pygame.display.set_caption("pyscroll Test") try: filename = sys.argv[1] diff --git a/apps/demo/translate.py b/apps/demo/translate.py index 070bba1..f2b4b65 100644 --- a/apps/demo/translate.py +++ b/apps/demo/translate.py @@ -7,7 +7,6 @@ class Dummy: - def run(self): surface = None diff --git a/apps/tutorial/quest.py b/apps/tutorial/quest.py index 7b0301e..fd0227b 100644 --- a/apps/tutorial/quest.py +++ b/apps/tutorial/quest.py @@ -11,7 +11,6 @@ from __future__ import annotations from pathlib import Path -from typing import List import pygame from pygame.locals import ( @@ -70,6 +69,7 @@ class Hero(pygame.sprite.Sprite): it collides with level walls. """ + def __init__(self) -> None: super().__init__() self.image = load_image("hero.png").convert_alpha() @@ -80,11 +80,11 @@ def __init__(self) -> None: self.feet = pygame.Rect(0, 0, self.rect.width * 0.5, 8) @property - def position(self) -> List[float]: + def position(self) -> list[float]: return list(self._position) @position.setter - def position(self, value: List[float]) -> None: + def position(self, value: list[float]) -> None: self._position = list(value) def update(self, dt: float) -> None: @@ -113,6 +113,7 @@ class QuestGame: Finally, it uses a pyscroll group to render the map and Hero. """ + map_path = RESOURCES_DIR / "grasslands.tmx" def __init__(self, screen: pygame.Surface) -> None: diff --git a/docs/_build/html/_sources/index.txt b/docs/_build/html/_sources/index.txt index 48ebde7..9790aaa 100644 --- a/docs/_build/html/_sources/index.txt +++ b/docs/_build/html/_sources/index.txt @@ -6,7 +6,7 @@ pyscroll ======== -for Python 2.7 & 3.3 and Pygame 1.9 +for Python 3.9 and Pygame 1.9 A simple, fast module for adding scrolling maps to your new or existing game. diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index 9d78562..329377f 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -46,7 +46,7 @@

Navigation

pyscrollΒΆ

-

for Python 2.7 & 3.3 and Pygame 1.9

+

for Python 3.9 and Pygame 1.9

A simple, fast module for adding scrolling maps to your new or existing game.

diff --git a/docs/conf.py b/docs/conf.py index 5eca40b..23fe35c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,224 +18,218 @@ # If extensions (or modules to document with autodoc) are in another directory, # 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. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'pyscroll' -copyright = u'2014, bitcraft' +project = "pyscroll" +copyright = "2014, bitcraft" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '2.14.2' +version = "2.14.2" # The full version, including alpha/beta/rc tags. -release = '2.14.2' +release = "2.14.2" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'tests'] +exclude_patterns = ["_build", "tests"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# 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 = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # 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 # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # 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 # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pyscrolldoc' +htmlhelp_basename = "pyscrolldoc" # -- 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': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # 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 = [ - ('index', 'pyscroll.tex', u'pyscroll Documentation', - u'bitcraft', 'manual'), + ("index", "pyscroll.tex", "pyscroll Documentation", "bitcraft", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pyscroll', u'pyscroll Documentation', - [u'bitcraft'], 1) -] +man_pages = [("index", "pyscroll", "pyscroll Documentation", ["bitcraft"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -244,19 +238,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pyscroll', u'pyscroll Documentation', - u'bitcraft', 'pyscroll', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "pyscroll", + "pyscroll Documentation", + "bitcraft", + "pyscroll", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst index 099b1b8..135c864 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ pyscroll ======== -for Python 2.7 & 3.3+ and Pygame 1.9 +for Python 3.9+ and Pygame 1.9 A simple, fast module for adding scrolling maps to your new or existing game. Includes support to load and render maps in TMX format from the Tiled map editor. diff --git a/pyproject.toml b/pyproject.toml index d8dd816..3d654b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,14 +15,15 @@ classifiers = [ "Intended Audience :: Developers", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "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 :: 3.12", "Topic :: Games/Entertainment", "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries :: pygame", ] -requires-python = ">=3.7" +requires-python = ">=3.9" [project.urls] source = "https://github.com/bitcraft/pyscroll" @@ -36,7 +37,7 @@ include = ["pyscroll*"] [tool.black] line-length = 88 -target-version = ["py37"] +target-version = ["py39"] [tool.isort] line_length = 88 diff --git a/pyscroll/animation.py b/pyscroll/animation.py index 6864fba..2fcb821 100644 --- a/pyscroll/animation.py +++ b/pyscroll/animation.py @@ -1,16 +1,17 @@ from __future__ import annotations from collections import namedtuple -from typing import Sequence, Union +from collections.abc import Sequence +from typing import Union AnimationFrame = namedtuple("AnimationFrame", "image duration") TimeLike = Union[float, int] -__all__ = ('AnimationFrame', 'AnimationToken') +__all__ = ("AnimationFrame", "AnimationToken") class AnimationToken: - __slots__ = ['next', 'positions', 'frames', 'index'] + __slots__ = ["next", "positions", "frames", "index"] def __init__(self, positions, frames: Sequence, initial_time: int = 0): """ diff --git a/pyscroll/common.py b/pyscroll/common.py index a2b8f07..a80bf38 100644 --- a/pyscroll/common.py +++ b/pyscroll/common.py @@ -1,13 +1,13 @@ from __future__ import annotations from contextlib import contextmanager -from typing import Any, List, Tuple, Union +from typing import Any, Union from pygame import Rect, Surface, Vector2 -RectLike = Union[Rect, Tuple[Any, Any, Any, Any]] -Vector2D = Union[Tuple[float, float], Tuple[int, int], Vector2] -Vector2DInt = Tuple[int, int] +RectLike = Union[Rect, tuple[Any, Any, Any, Any]] +Vector2D = Union[tuple[float, float], tuple[int, int], Vector2] +Vector2DInt = tuple[int, int] @contextmanager @@ -18,7 +18,7 @@ def surface_clipping_context(surface: Surface, clip: RectLike): surface.set_clip(original) -def rect_difference(a: RectLike, b: RectLike) -> List[Rect]: +def rect_difference(a: RectLike, b: RectLike) -> list[Rect]: """ Compute difference of two rects. Returns up to 4. @@ -26,6 +26,6 @@ def rect_difference(a: RectLike, b: RectLike) -> List[Rect]: raise NotImplementedError -def rect_to_bb(rect: RectLike) -> Tuple[int, int, int, int]: +def rect_to_bb(rect: RectLike) -> tuple[int, int, int, int]: x, y, w, h = rect return x, y, x + w - 1, y + h - 1 diff --git a/pyscroll/data.py b/pyscroll/data.py index 638344b..b172d12 100644 --- a/pyscroll/data.py +++ b/pyscroll/data.py @@ -9,7 +9,6 @@ import time from heapq import heappop, heappush from itertools import product -from typing import List, Tuple import pygame from pygame import Surface @@ -39,27 +38,28 @@ class PyscrollDataAdapter: source, it is only tested using Tiled maps, loaded with pytmx. """ + # the following can be class/instance attributes # or properties. they are listed here as class # instances, but use as properties is fine, too. - tile_size = None # (int, int): size of each tile in pixels - map_size = None # (int, int): size of map in tiles - visible_tile_layers = None # list of visible layer integers + tile_size = None # (int, int): size of each tile in pixels + map_size = None # (int, int): size of map in tiles + visible_tile_layers = None # list of visible layer integers def __init__(self): - self._last_time = None # last time map animations were updated - self._animation_queue = list() # list of animation tokens - self._animated_tile = dict() # mapping of tile substitutions when animated - self._tracked_tiles = set() # track the tiles on screen with animations + self._last_time = None # last time map animations were updated + self._animation_queue = list() # list of animation tokens + self._animated_tile = dict() # mapping of tile substitutions when animated + self._tracked_tiles = set() # track the tiles on screen with animations def reload_data(self): raise NotImplementedError def process_animation_queue( - self, - tile_view: RectLike, - ) -> List[Tuple[int, int, int, Surface]]: + self, + tile_view: RectLike, + ) -> list[tuple[int, int, int, Surface]]: """ Given the time and the tile view, process tile changes and return them @@ -277,8 +277,7 @@ def get_tile_images_by_rect(self, rect: RectLike): """ x1, y1, x2, y2 = rect_to_bb(rect) for layer in self.visible_tile_layers: - for y, x in product(range(y1, y2 + 1), - range(x1, x2 + 1)): + for y, x in product(range(y1, y2 + 1), range(x1, x2 + 1)): tile = self.get_tile_image(x, y, layer) if tile: yield x, y, layer, tile @@ -289,6 +288,7 @@ class TiledMapData(PyscrollDataAdapter): For data loaded from pytmx. """ + def __init__(self, tmx): super(TiledMapData, self).__init__() self.tmx = tmx @@ -300,7 +300,7 @@ def reload_data(self): def get_animations(self): for gid, d in self.tmx.tile_properties.items(): try: - frames = d['frames'] + frames = d["frames"] except KeyError: continue @@ -333,8 +333,11 @@ def visible_tile_layers(self): @property def visible_object_layers(self): - return (layer for layer in self.tmx.visible_layers - if isinstance(layer, pytmx.TiledObjectGroup)) + return ( + layer + for layer in self.tmx.visible_layers + if isinstance(layer, pytmx.TiledObjectGroup) + ) def _get_tile_image(self, x: int, y: int, l: int): try: @@ -349,7 +352,7 @@ def get_tile_images_by_rect(self, rect: RectLike): def rev(seq, start, stop): if start < 0: start = 0 - return enumerate(seq[start:stop + 1], start) + return enumerate(seq[start : stop + 1], start) x1, y1, x2, y2 = rect_to_bb(rect) images = self.tmx.images @@ -389,6 +392,7 @@ class MapAggregator(PyscrollDataAdapter): - Cannot remove maps once added """ + def __init__(self, tile_size): super().__init__() self.tile_size = tile_size diff --git a/pyscroll/group.py b/pyscroll/group.py index cfca0df..96b81ca 100644 --- a/pyscroll/group.py +++ b/pyscroll/group.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING import pygame @@ -16,12 +16,8 @@ class PyscrollGroup(pygame.sprite.LayeredUpdates): map_layer: Pyscroll Renderer """ - def __init__( - self, - map_layer: BufferedRenderer, - *args, - **kwargs - ): + + def __init__(self, map_layer: BufferedRenderer, *args, **kwargs): pygame.sprite.LayeredUpdates.__init__(self, *args, **kwargs) self._map_layer = map_layer @@ -46,10 +42,7 @@ def view(self) -> pygame.Rect: """ return self._map_layer.view_rect.copy() - def draw( - self, - surface: pygame.surface.Surface - ) -> List[pygame.rect.Rect]: + def draw(self, surface: pygame.surface.Surface) -> list[pygame.rect.Rect]: """ Draw map and all sprites onto the surface. diff --git a/pyscroll/isometric.py b/pyscroll/isometric.py index 24fb7a7..c4c0b9b 100644 --- a/pyscroll/isometric.py +++ b/pyscroll/isometric.py @@ -7,18 +7,22 @@ def vector3_to_iso(vector3): offset = 0, 0 - return ((vector3[0] - vector3[1]) + offset[0], - ((vector3[0] + vector3[1]) >> 1) - vector3[2] + offset[1]) + return ( + (vector3[0] - vector3[1]) + offset[0], + ((vector3[0] + vector3[1]) >> 1) - vector3[2] + offset[1], + ) def vector2_to_iso(vector2): offset = 0, 0 - return ((vector2[0] - vector2[1]) + offset[0], - ((vector2[0] + vector2[1]) >> 1) + offset[1]) + return ( + (vector2[0] - vector2[1]) + offset[0], + ((vector2[0] + vector2[1]) >> 1) + offset[1], + ) class IsometricBufferedRenderer(BufferedRenderer): - """ TEST ISOMETRIC + """TEST ISOMETRIC here be dragons. lots of odd, untested, and unoptimised stuff. @@ -31,7 +35,7 @@ def _draw_surfaces(self, surface, rect, surfaces): [(surface.blit(i[0], i[1]), i[2]) for i in surfaces] def _initialize_buffers(self, view_size): - """ Create the buffers to cache tile drawing + """Create the buffers to cache tile drawing :param view_size: (int, int): size of the draw area :return: None @@ -59,8 +63,7 @@ def _initialize_buffers(self, view_size): self.redraw_tiles() def _flush_tile_queue(self): - """ Blits (x, y, layer) tuples to buffer from iterator - """ + """Blits (x, y, layer) tuples to buffer from iterator""" iterator = self._tile_queue surface_blit = self._buffer.blit map_get = self._animation_map.get @@ -79,12 +82,11 @@ def _flush_tile_queue(self): # iso => cart iso_x = ((x - y) * twh) + bw - iso_y = ((x + y) * thh) + iso_y = (x + y) * thh surface_blit(tile, (iso_x, iso_y)) def center(self, coords): - """ center the map on a "map pixel" - """ + """center the map on a "map pixel" """ x, y = [round(i, 0) for i in coords] self.view_rect.center = x, y diff --git a/pyscroll/orthographic.py b/pyscroll/orthographic.py index 84ec2b5..97c18a3 100644 --- a/pyscroll/orthographic.py +++ b/pyscroll/orthographic.py @@ -3,8 +3,9 @@ import logging import math import time +from collections.abc import Callable from itertools import chain, product -from typing import TYPE_CHECKING, Callable, List +from typing import TYPE_CHECKING import pygame from pygame import Rect, Surface @@ -28,6 +29,7 @@ class BufferedRenderer: created with Tiled. """ + _rgba_clear_color = 0, 0, 0, 0 _rgb_clear_color = 0, 0, 0 @@ -35,14 +37,14 @@ def __init__( self, data: PyscrollDataAdapter, size: Vector2DInt, - clamp_camera: bool=True, + clamp_camera: bool = True, colorkey=None, - alpha: bool=False, - time_source: Callable=time.time, - scaling_function: Callable=pygame.transform.scale, - tall_sprites: int=0, - sprite_damage_height: int=0, - zoom: float=1.0, + alpha: bool = False, + time_source: Callable = time.time, + scaling_function: Callable = pygame.transform.scale, + tall_sprites: int = 0, + sprite_damage_height: int = 0, + zoom: float = 1.0, ): """ Constructor @@ -75,7 +77,7 @@ def __init__( # internal private defaults if colorkey and alpha: - log.error('cannot select both colorkey and alpha') + log.error("cannot select both colorkey and alpha") raise ValueError elif colorkey: self._clear_color = colorkey @@ -85,29 +87,39 @@ def __init__( self._clear_color = None # private attributes - self._anchored_view = True # if true, map is fixed to upper left corner - self._previous_blit = None # rect of the previous map blit when map edges are visible - self._size = None # actual pixel size of the view, as it occupies the screen - self._redraw_cutoff = None # size of dirty tile edge that will trigger full redraw - self._x_offset = None # offsets are used to scroll map in sub-tile increments + self._anchored_view = True # if true, map is fixed to upper left corner + self._previous_blit = ( + None # rect of the previous map blit when map edges are visible + ) + self._size = None # actual pixel size of the view, as it occupies the screen + self._redraw_cutoff = ( + None # size of dirty tile edge that will trigger full redraw + ) + self._x_offset = None # offsets are used to scroll map in sub-tile increments self._y_offset = None - self._buffer = None # complete rendering of tilemap - self._tile_view = None # this rect represents each tile on the buffer - self._half_width = None # 'half x' attributes are used to reduce division ops. + self._buffer = None # complete rendering of tilemap + self._tile_view = None # this rect represents each tile on the buffer + self._half_width = None # 'half x' attributes are used to reduce division ops. self._half_height = None - self._tile_queue = None # tiles queued to be draw onto buffer - self._animation_queue = None # heap queue of animation token; schedules tile changes - self._layer_quadtree = None # used to draw tiles that overlap optional surfaces - self._zoom_buffer = None # used to speed up zoom operations + self._tile_queue = None # tiles queued to be draw onto buffer + self._animation_queue = ( + None # heap queue of animation token; schedules tile changes + ) + self._layer_quadtree = None # used to draw tiles that overlap optional surfaces + self._zoom_buffer = None # used to speed up zoom operations self._zoom_level = zoom - self._real_ratio_x = 1.0 # zooming slightly changes aspect ratio; this compensates - self._real_ratio_y = 1.0 # zooming slightly changes aspect ratio; this compensates + self._real_ratio_x = ( + 1.0 # zooming slightly changes aspect ratio; this compensates + ) + self._real_ratio_y = ( + 1.0 # zooming slightly changes aspect ratio; this compensates + ) self.view_rect = Rect(0, 0, 0, 0) # this represents the viewable map pixels self.set_size(size) if self.tall_sprites != 0: - log.warning('using tall_sprites feature is not supported') + log.warning("using tall_sprites feature is not supported") def reload(self): """ @@ -126,8 +138,9 @@ def scroll(self, vector: Vector2DInt): vector: x, y """ - self.center((vector[0] + self.view_rect.centerx, - vector[1] + self.view_rect.centery)) + self.center( + (vector[0] + self.view_rect.centerx, vector[1] + self.view_rect.centery) + ) def center(self, coords: Vector2D): """ @@ -199,17 +212,11 @@ def center(self, coords: Vector2D): self._flush_tile_queue(self._buffer) elif view_change > self._redraw_cutoff: - log.debug('scrolling too quickly. redraw forced') + log.debug("scrolling too quickly. redraw forced") self._tile_view.move_ip(dx, dy) self.redraw_tiles(self._buffer) - def draw( - self, - surface: Surface, - rect: RectLike, - surfaces: - List[Surface]=None - ): + def draw(self, surface: Surface, rect: RectLike, surfaces: list[Surface] = None): """ Draw the map onto a surface. @@ -238,11 +245,7 @@ def draw( if self._zoom_level == 1.0: self._render_map(surface, rect, surfaces) else: - self._render_map( - self._zoom_buffer, - self._zoom_buffer.get_rect(), - surfaces - ) + self._render_map(self._zoom_buffer, self._zoom_buffer.get_rect(), surfaces) self.scaling_function(self._zoom_buffer, rect.size, surface) return self._previous_blit.copy() @@ -293,7 +296,7 @@ def redraw_tiles(self, surface: Surface): """ # TODO/BUG: Animated tiles are getting reset here - log.debug('pyscroll buffer redraw') + log.debug("pyscroll buffer redraw") self._clear_surface(self._buffer) self._tile_queue = self.data.get_tile_images_by_rect(self._tile_view) self._flush_tile_queue(surface) @@ -303,8 +306,10 @@ def get_center_offset(self) -> Vector2DInt: Return x, y pair that will change world coords to screen coords. """ - return (-self.view_rect.centerx + self._half_width, - -self.view_rect.centery + self._half_height) + return ( + -self.view_rect.centerx + self._half_width, + -self.view_rect.centery + self._half_height, + ) def translate_point(self, point: Vector2D) -> Vector2DInt: """ @@ -320,7 +325,7 @@ def translate_point(self, point: Vector2D) -> Vector2DInt: else: return ( int(round((point[0] + mx)) * self._real_ratio_x), - int(round((point[1] + my) * self._real_ratio_y)) + int(round((point[1] + my) * self._real_ratio_y)), ) def translate_rect(self, rect: RectLike) -> Rect: @@ -338,9 +343,11 @@ def translate_rect(self, rect: RectLike) -> Rect: if self._zoom_level == 1.0: return Rect(x + mx, y + my, w, h) else: - return Rect(round((x + mx) * rx), round((y + my) * ry), round(w * rx), round(h * ry)) + return Rect( + round((x + mx) * rx), round((y + my) * ry), round(w * rx), round(h * ry) + ) - def translate_points(self, points: List[Vector2D]) -> List[Vector2DInt]: + def translate_points(self, points: list[Vector2D]) -> list[Vector2DInt]: """ Translate coordinates and return screen coordinates. @@ -361,7 +368,7 @@ def translate_points(self, points: List[Vector2D]) -> List[Vector2DInt]: append((int(round((c[0] + sx) * rx)), int(round((c[1] + sy) * ry)))) return retval - def translate_rects(self, rects: List[Rect]) -> List[Rect]: + def translate_rects(self, rects: list[Rect]) -> list[Rect]: """ Translate rect position and size to screen coordinates. @@ -386,17 +393,12 @@ def translate_rects(self, rects: List[Rect]) -> List[Rect]: round((x + sx) * rx), round((y + sy) * ry), round(w * rx), - round(h * ry) + round(h * ry), ) ) return retval - def _render_map( - self, - surface: Surface, - rect: RectLike, - surfaces: List[Surface] - ): + def _render_map(self, surface: Surface, rect: RectLike, surfaces: list[Surface]): """ Render the map and optional surfaces to destination surface. @@ -430,7 +432,9 @@ def _clear_surface(self, surface: Surface, area: RectLike = None): area: area to clear """ - clear_color = self._rgb_clear_color if self._clear_color is None else self._clear_color + clear_color = ( + self._rgb_clear_color if self._clear_color is None else self._clear_color + ) surface.fill(clear_color, area) def _draw_surfaces(self, surface: Surface, offset: Vector2DInt, surfaces): @@ -467,7 +471,7 @@ def _draw_surfaces(self, surface: Surface, offset: Vector2DInt, surfaces): damage_rect.x, damage_rect.y + (damage_rect.height - self.tall_sprites), damage_rect.width, - self.tall_sprites + self.tall_sprites, ) for hit_rect in hit(damage_rect): sprite_damage.add((l, hit_rect)) @@ -536,10 +540,19 @@ def _queue_edge_tiles(self, dx: int, dy: int): self._tile_queue = iter([]) def append(rect): - self._tile_queue = chain(self._tile_queue, self.data.get_tile_images_by_rect(rect)) + self._tile_queue = chain( + self._tile_queue, self.data.get_tile_images_by_rect(rect) + ) # TODO: optimize so fill is only used when map is smaller than buffer - self._clear_surface(self._buffer, ((rect[0] - v.left) * tw, (rect[1] - v.top) * th, - rect[2] * tw, rect[3] * th)) + self._clear_surface( + self._buffer, + ( + (rect[0] - v.left) * tw, + (rect[1] - v.top) * th, + rect[2] * tw, + rect[3] * th, + ), + ) if dx > 0: # right side append((v.right - 1, v.top, dx, v.height)) @@ -556,16 +569,12 @@ def append(rect): @staticmethod def _calculate_zoom_buffer_size(size: Vector2DInt, value: float): if value <= 0: - log.error('zoom level cannot be zero or less') + log.error("zoom level cannot be zero or less") raise ValueError value = 1.0 / value return int(size[0] * value), int(size[1] * value) - def _create_buffers( - self, - view_size: Vector2DInt, - buffer_size: Vector2DInt - ): + def _create_buffers(self, view_size: Vector2DInt, buffer_size: Vector2DInt): """ Create the buffers, taking in account pixel alpha or colorkey. @@ -623,8 +632,10 @@ def make_rect(x, y): self._x_offset = 0 self._y_offset = 0 - rects = [make_rect(*i) for i in product(range(buffer_tile_width), - range(buffer_tile_height))] + rects = [ + make_rect(*i) + for i in product(range(buffer_tile_width), range(buffer_tile_height)) + ] # TODO: figure out what depth -actually- does # values <= 8 tend to reduce performance @@ -646,5 +657,7 @@ def _flush_tile_queue(self, surface: Surface): self.data.prepare_tiles(self._tile_view) - blit_list = [(image, (x * tw - ltw, y * th - tth)) for x, y, l, image in self._tile_queue] + blit_list = [ + (image, (x * tw - ltw, y * th - tth)) for x, y, l, image in self._tile_queue + ] surface.blits(blit_list, doreturn=False) diff --git a/pyscroll/quadtree.py b/pyscroll/quadtree.py index 32eb8aa..bba8b5c 100644 --- a/pyscroll/quadtree.py +++ b/pyscroll/quadtree.py @@ -6,7 +6,8 @@ from __future__ import annotations import itertools -from typing import TYPE_CHECKING, Sequence, Set, Tuple +from collections.abc import Sequence +from typing import TYPE_CHECKING from pygame import Rect @@ -32,7 +33,7 @@ class FastQuadTree: __slots__ = ["items", "cx", "cy", "nw", "sw", "ne", "se"] - def __init__(self, items: Sequence, depth: int=4, boundary=None): + def __init__(self, items: Sequence, depth: int = 4, boundary=None): """Creates a quad-tree. Args: @@ -88,18 +89,26 @@ def __init__(self, items: Sequence, depth: int=4, boundary=None): # Create the sub-quadrants, recursively. if nw_items: - self.nw = FastQuadTree(nw_items, depth, (boundary.left, boundary.top, cx, cy)) + self.nw = FastQuadTree( + nw_items, depth, (boundary.left, boundary.top, cx, cy) + ) if ne_items: - self.ne = FastQuadTree(ne_items, depth, (cx, boundary.top, boundary.right, cy)) + self.ne = FastQuadTree( + ne_items, depth, (cx, boundary.top, boundary.right, cy) + ) if se_items: - self.se = FastQuadTree(se_items, depth, (cx, cy, boundary.right, boundary.bottom)) + self.se = FastQuadTree( + se_items, depth, (cx, cy, boundary.right, boundary.bottom) + ) if sw_items: - self.sw = FastQuadTree(sw_items, depth, (boundary.left, cy, cx, boundary.bottom)) + self.sw = FastQuadTree( + sw_items, depth, (boundary.left, cy, cx, boundary.bottom) + ) def __iter__(self): return itertools.chain(self.items, self.nw, self.ne, self.se, self.sw) - def hit(self, rect: RectLike) -> Set[Tuple[int, int, int, int]]: + def hit(self, rect: RectLike) -> set[tuple[int, int, int, int]]: """ Returns the items that overlap a bounding rectangle. diff --git a/tests/pyscroll/test_pyscroll.py b/tests/pyscroll/test_pyscroll.py index e07c55a..72fa3ae 100644 --- a/tests/pyscroll/test_pyscroll.py +++ b/tests/pyscroll/test_pyscroll.py @@ -8,8 +8,8 @@ class DummyDataAdapter(PyscrollDataAdapter): - tile_size = 32, 32 - map_size = 32, 32 + tile_size = (32, 32) + map_size = (32, 32) visible_tile_layers = [1] def get_animations(self): @@ -28,27 +28,26 @@ class DummyBufferer: class TestTileQueue(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.mock = DummyBufferer() self.queue = BufferedRenderer._queue_edge_tiles - def verify_queue(self, expected): + def verify_queue(self, expected: set[tuple[int, int]]): queue = {i[:2] for i in self.mock._tile_queue} self.assertEqual(queue, set(expected)) - def test_queue_left(self): + def test_queue_left(self) -> None: self.queue(self.mock, -1, 0) self.verify_queue({(2, 3), (2, 2)}) - def test_queue_top(self): + def test_queue_top(self) -> None: self.queue(self.mock, 0, -1) self.verify_queue({(2, 2), (3, 2)}) - def test_queue_right(self): + def test_queue_right(self) -> None: self.queue(self.mock, 1, 0) self.verify_queue({(3, 3), (3, 2)}) - def test_queue_bottom(self): + def test_queue_bottom(self) -> None: self.queue(self.mock, 0, 1) self.verify_queue({(2, 3), (3, 3)}) -