From 4a16a167a72663d87942af149bcb3630e3dcd94e Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Sep 2024 01:55:40 -0500 Subject: [PATCH 1/4] Compile project with mypyc --- pyproject.toml | 24 ++++++++++----- src/checkers/__init__.py | 8 ++++- src/checkers/base2d.py | 20 ++----------- src/checkers/client.py | 15 ++++++---- src/checkers/component.py | 4 ++- src/checkers/game.py | 53 +++++++++++++++++++++------------- src/checkers/network.py | 10 +------ src/checkers/network_shared.py | 9 +++--- src/checkers/sound.py | 2 +- src/checkers/state.py | 22 ++++++++++---- 10 files changed, 95 insertions(+), 72 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f84af82..c49eee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools >= 64"] -build-backend = "setuptools.build_meta" +requires = ["hatchling >= 1.20.0"] +build-backend = "hatchling.build" [project] name = "checkers" @@ -39,13 +39,14 @@ keywords = [ dependencies = [ "pygame~=2.6.0", "typing_extensions>=4.12.2", + "mypy_extensions>=1.0.0", "trio~=0.26.2", "cryptography>=43.0.0", "exceptiongroup; python_version < '3.11'", ] -[tool.setuptools.dynamic] -version = {attr = "checkers.game.__version__"} +[tool.hatch.version] +path = "src/checkers/game.py" [project.urls] "Homepage" = "https://github.com/CoolCat467/Checkers" @@ -53,10 +54,19 @@ version = {attr = "checkers.game.__version__"} "Bug Tracker" = "https://github.com/CoolCat467/Checkers/issues" [project.scripts] -checkers_game = "checkers.game:cli_run" +checkers_game = "checkers:cli_run" -[tool.setuptools.package-data] -checkers = ["py.typed", "data/*"] +[tool.hatch.build.targets.wheel.hooks.mypyc] +dependencies = [ + "hatch-mypyc>=0.16.0", + "mypy==1.11.2", +] +require-runtime-dependencies = true +exclude = [ + "src/checkers/vector.py", + "src/checkers/base_io.py", + "src/checkers/buffer.py", +] [tool.mypy] mypy_path = "src" diff --git a/src/checkers/__init__.py b/src/checkers/__init__.py index 5007862..54a86b3 100644 --- a/src/checkers/__init__.py +++ b/src/checkers/__init__.py @@ -2,7 +2,7 @@ # Programmed by CoolCat467 -# Copyright (C) 2023 CoolCat467 +# Copyright (C) 2023-2024 CoolCat467 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,3 +16,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + + +from checkers.game import cli_run as cli_run + +if __name__ == "__main__": + cli_run() diff --git a/src/checkers/base2d.py b/src/checkers/base2d.py index 10dbbff..aeeb543 100644 --- a/src/checkers/base2d.py +++ b/src/checkers/base2d.py @@ -33,7 +33,7 @@ from checkers.vector import Vector2 if TYPE_CHECKING: - from collections.abc import Callable, Generator, Iterable, Sequence + from collections.abc import Callable, Iterable, Sequence def amol( @@ -148,20 +148,6 @@ def get_colors( return colors -def average_color( - surface: pygame.surface.Surface, -) -> Generator[int, None, None]: - """Return the average RGB value of a surface.""" - s_r, s_g, s_b = 0, 0, 0 - colors = get_colors(surface) - for color in colors: - r, g, b = color - s_r += r - s_g += g - s_b += b - return (int(x / len(colors)) for x in (s_r, s_g, s_b)) - - def replace_with_color( surface: pygame.surface.Surface, color: tuple[int, int, int], @@ -357,8 +343,8 @@ def __init__( self.value = 0 self.max_value = int(states) self.anim = anim - self.press_time: float = 1 - self.last_press: float = 0 + self.press_time: float = 1.0 + self.last_press: float = 0.0 self.scan = int(max(get_surf_lens(self.anim)) / 2) + 2 keys = list(kwargs.keys()) diff --git a/src/checkers/client.py b/src/checkers/client.py index 5f2cf1e..6795201 100644 --- a/src/checkers/client.py +++ b/src/checkers/client.py @@ -46,6 +46,9 @@ write_position, ) +if TYPE_CHECKING: + from mypy_extensions import u8 + async def read_advertisements( timeout: int = 3, # noqa: ASYNC109 @@ -304,7 +307,7 @@ async def read_create_piece(self, event: Event[bytearray]) -> None: buffer = Buffer(event.data) piece_pos = read_position(buffer) - piece_type = buffer.read_value(StructFormat.UBYTE) + piece_type: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event( Event("gameboard_create_piece", (piece_pos, piece_type)), @@ -381,7 +384,7 @@ async def read_update_piece_animation( buffer = Buffer(event.data) piece_pos = read_position(buffer) - piece_type = buffer.read_value(StructFormat.UBYTE) + piece_type: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event( Event("gameboard_update_piece_animation", (piece_pos, piece_type)), @@ -415,7 +418,7 @@ async def read_game_over(self, event: Event[bytearray]) -> None: """Read update_piece event from server.""" buffer = Buffer(event.data) - winner = buffer.read_value(StructFormat.UBYTE) + winner: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event(Event("game_winner", winner)) self.running = False @@ -430,7 +433,7 @@ async def read_action_complete(self, event: Event[bytearray]) -> None: from_pos = read_position(buffer) to_pos = read_position(buffer) - current_turn = buffer.read_value(StructFormat.UBYTE) + current_turn: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event( Event("game_action_complete", (from_pos, to_pos, current_turn)), @@ -441,7 +444,7 @@ async def read_initial_config(self, event: Event[bytearray]) -> None: buffer = Buffer(event.data) board_size = read_position(buffer) - current_turn = buffer.read_value(StructFormat.UBYTE) + current_turn: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event( Event("game_initial_config", (board_size, current_turn)), @@ -451,7 +454,7 @@ async def read_playing_as(self, event: Event[bytearray]) -> None: """Read playing_as event from server.""" buffer = Buffer(event.data) - playing_as = buffer.read_value(StructFormat.UBYTE) + playing_as: u8 = buffer.read_value(StructFormat.UBYTE) await self.raise_event( Event("game_playing_as", playing_as), diff --git a/src/checkers/component.py b/src/checkers/component.py index 9d8997f..463fc20 100644 --- a/src/checkers/component.py +++ b/src/checkers/component.py @@ -33,6 +33,8 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable, Generator, Iterable + from mypy_extensions import u8 + T = TypeVar("T") @@ -45,7 +47,7 @@ def __init__( self, name: str, data: T, - levels: int = 0, + levels: u8 = 0, ) -> None: """Initialize event.""" self.name = name diff --git a/src/checkers/game.py b/src/checkers/game.py index a728feb..e71ab88 100644 --- a/src/checkers/game.py +++ b/src/checkers/game.py @@ -22,7 +22,7 @@ __title__ = "Checkers" __author__ = "CoolCat467" __license__ = "GNU General Public License Version 3" -__version__ = "2.0.1" +__version__ = "2.0.2" # Note: Tile Ids are chess board tile titles, A1 to H8 # A8 ... H8 @@ -34,9 +34,8 @@ import contextlib import platform from collections import deque -from os import path from pathlib import Path -from typing import TYPE_CHECKING, Any, Final, TypeAlias, TypeVar +from typing import TYPE_CHECKING, Any, Final, TypeVar import pygame import trio @@ -53,7 +52,7 @@ Event, ExternalRaiseManager, ) -from checkers.network_shared import DEFAULT_PORT, find_ip +from checkers.network_shared import DEFAULT_PORT, Pos, find_ip from checkers.objects import Button, OutlinedText from checkers.server import GameServer from checkers.sound import SoundData, play_sound as base_play_sound @@ -68,6 +67,7 @@ Iterable, Sequence, ) + from types import ModuleType from pygame.surface import Surface @@ -105,11 +105,21 @@ T = TypeVar("T") -DATA_FOLDER: Final = Path(__file__).parent / "data" +if globals().get("__file__") is None: + if TYPE_CHECKING: -IS_WINDOWS: Final = platform.system() == "Windows" + def resolve(name: str) -> ModuleType: ... # noqa: D103 + + else: + from importlib.resources._common import resolve + + __file__ = str( + Path(resolve("checkers.data").__path__[0]).parent / "game.py", + ) -Pos: TypeAlias = tuple[int, int] +DATA_FOLDER: Final = Path(__file__).absolute().parent / "data" + +IS_WINDOWS: Final = platform.system() == "Windows" def render_text( @@ -704,7 +714,7 @@ def generate_tile_images(self) -> None: outline_ident = outline.precalculate_outline(name, outline_color) image.add_image(f"{name}_outlined", outline_ident) - def get_tile_location(self, position: Pos) -> Vector2: + def get_tile_location(self, position: tuple[int, int]) -> Vector2: """Return the center point of a given tile position.""" location = Vector2.from_iter(position) * self.tile_size center = self.tile_size // 2 @@ -773,7 +783,7 @@ def generate_board_image(self) -> Surface: ### Blit the id of the tile at the tile's location ##surf.blit( ## render_text( - ## trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + ## DATA_FOLDER / "VeraSerif.ttf", ## 20, ## "".join(map(str, (x, y))), ## GREEN @@ -826,7 +836,7 @@ async def update_selected(self) -> None: if not self.selected: movement: sprite.MovementComponent = self.get_component("movement") - movement.speed = 0 + movement.speed = 0.0 async def click(self, event: Event[dict[str, int]]) -> None: """Toggle selected.""" @@ -841,15 +851,18 @@ async def drag(self, event: Event[Any]) -> None: self.selected = True await self.update_selected() movement: sprite.MovementComponent = self.get_component("movement") - movement.speed = 0 + movement.speed = 0.0 - async def mouse_down(self, event: Event[dict[str, int | Pos]]) -> None: + async def mouse_down( + self, + event: Event[dict[str, int | tuple[int, int]]], + ) -> None: """Target click pos if selected.""" if not self.selected: return if event.data["button"] == 1: movement: sprite.MovementComponent = self.get_component("movement") - movement.speed = 200 + movement.speed = 200.0 target: sprite.TargetingComponent = self.get_component("targeting") assert isinstance(event.data["pos"], tuple) target.destination = Vector2.from_iter(event.data["pos"]) @@ -944,7 +957,7 @@ class FPSCounter(objects.Text): def __init__(self) -> None: """Initialize FPS counter.""" font = pygame.font.Font( - trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + DATA_FOLDER / "VeraSerif.ttf", 28, ) super().__init__("fps", font) @@ -1112,11 +1125,11 @@ async def entry_actions(self) -> None: self.id = self.machine.new_group("title") button_font = pygame.font.Font( - trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + DATA_FOLDER / "VeraSerif.ttf", 28, ) title_font = pygame.font.Font( - trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + DATA_FOLDER / "VeraSerif.ttf", 56, ) @@ -1266,7 +1279,7 @@ def __init__(self) -> None: self.buttons: dict[tuple[str, int], int] = {} self.font = pygame.font.Font( - trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + DATA_FOLDER / "VeraSerif.ttf", 28, ) @@ -1412,7 +1425,7 @@ async def do_actions(self) -> None: self.exit_data = (exit_status, message, True) font = pygame.font.Font( - trio.Path(path.dirname(__file__), "data", "VeraSerif.ttf"), + DATA_FOLDER / "VeraSerif.ttf", 28, ) @@ -1455,7 +1468,7 @@ async def do_actions(self) -> None: class CheckersClient(sprite.GroupProcessor): """Checkers Game Client.""" - __slots__ = ("manager",) + __slots__ = ("manager", "__weakref__") def __init__(self, manager: ComponentManager) -> None: """Initialize Checkers Client.""" @@ -1503,7 +1516,7 @@ async def async_run() -> None: client = CheckersClient(event_manager) background = pygame.image.load( - path.join(path.dirname(__file__), "data", "background.png"), + DATA_FOLDER / "background.png", ).convert() client.clear(screen, background) diff --git a/src/checkers/network.py b/src/checkers/network.py index c199eff..2a0cb5a 100644 --- a/src/checkers/network.py +++ b/src/checkers/network.py @@ -30,11 +30,8 @@ TYPE_CHECKING, Any, AnyStr, - Generic, Literal, NoReturn, - SupportsIndex, - TypeAlias, ) import trio @@ -51,15 +48,10 @@ ) if TYPE_CHECKING: - from collections.abc import Iterable from types import TracebackType from typing_extensions import Self - BytesConvertable: TypeAlias = SupportsIndex | Iterable[SupportsIndex] -else: - BytesConvertable = Generic - class NetworkTimeoutError(Exception): """Network Timeout Error.""" @@ -67,7 +59,7 @@ class NetworkTimeoutError(Exception): __slots__ = () -class NetworkStreamNotConnectedError(RuntimeError): +class NetworkStreamNotConnectedError(Exception): """Network Stream Not Connected Error.""" __slots__ = () diff --git a/src/checkers/network_shared.py b/src/checkers/network_shared.py index 69264b0..1eab3b7 100644 --- a/src/checkers/network_shared.py +++ b/src/checkers/network_shared.py @@ -2,7 +2,7 @@ # Programmed by CoolCat467 -# Copyright (C) 2023 CoolCat467 +# Copyright (C) 2023-2024 CoolCat467 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ from typing import TYPE_CHECKING, Final, NamedTuple, TypeAlias import trio +from mypy_extensions import u8 from .base_io import StructFormat @@ -37,7 +38,7 @@ DEFAULT_PORT: Final = 31613 -Pos: TypeAlias = tuple[int, int] +Pos: TypeAlias = tuple[u8, u8] class TickEventData(NamedTuple): @@ -49,8 +50,8 @@ class TickEventData(NamedTuple): def read_position(buffer: Buffer) -> Pos: """Read a position tuple from buffer.""" - pos_x = buffer.read_value(StructFormat.UBYTE) - pos_y = buffer.read_value(StructFormat.UBYTE) + pos_x: u8 = buffer.read_value(StructFormat.UBYTE) + pos_y: u8 = buffer.read_value(StructFormat.UBYTE) return pos_x, pos_y diff --git a/src/checkers/sound.py b/src/checkers/sound.py index b8ac825..9e120a3 100644 --- a/src/checkers/sound.py +++ b/src/checkers/sound.py @@ -52,7 +52,7 @@ def play_sound( """Play sound with pygame.""" sound_object = mixer.Sound(filename) sound_object.set_volume(sound_data.volume) - seconds = sound_object.get_length() + seconds: int | float = sound_object.get_length() if sound_data.maxtime > 0: seconds = sound_data.maxtime _channel = sound_object.play( diff --git a/src/checkers/state.py b/src/checkers/state.py index f0e6ab8..6af98cb 100644 --- a/src/checkers/state.py +++ b/src/checkers/state.py @@ -26,7 +26,17 @@ import copy import math -from typing import TYPE_CHECKING, Any, NamedTuple, Self, TypeVar, cast +from typing import ( + TYPE_CHECKING, + Any, + NamedTuple, + Self, + TypeAlias, + TypeVar, + cast, +) + +from mypy_extensions import u8 if TYPE_CHECKING: from collections.abc import Callable, Generator, Iterable @@ -45,7 +55,7 @@ T = TypeVar("T") -Pos = tuple[int, int] +Pos: TypeAlias = tuple[u8, u8] class Action(NamedTuple): @@ -79,7 +89,7 @@ def get_sides(xy: Pos) -> tuple[Pos, Pos, Pos, Pos]: return cast(tuple[Pos, Pos, Pos, Pos], tuple_sides) -def pawn_modify(moves: tuple[T, ...], piece_type: int) -> tuple[T, ...]: +def pawn_modify(moves: tuple[T, ...], piece_type: u8) -> tuple[T, ...]: """Return moves but remove invalid moves for pawns.""" assert ( len(moves) == 4 @@ -104,7 +114,7 @@ def __init__( self, size: tuple[int, int], pieces: dict[Pos, int], - turn: int = 1, # Black moves first + turn: bool = True, # Black moves first /, pre_calculated_actions: dict[Pos, ActionSet] | None = None, ) -> None: @@ -285,7 +295,7 @@ def action_from_points(start: Pos, end: Pos) -> Action: def get_turn(self) -> int: """Return whose turn it is. 0 = red, 1 = black.""" - return self.turn + return int(self.turn) def valid_location(self, position: Pos) -> bool: """Return if position is valid.""" @@ -483,7 +493,7 @@ def check_for_win(self) -> int | None: has_move = True # Player has at least one move, no need to continue break - if not has_move and self.turn == player: + if not has_move and self.turn == bool(player): # Continued without break, so player either has no moves # or no possible moves, so their opponent wins return (player + 1) % 2 From b97df45955be2cf9930f6e8d862239d29c4d3f2e Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Sep 2024 02:26:57 -0500 Subject: [PATCH 2/4] Fix type issues and remove duplicate weakref slot --- src/checkers/game.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/checkers/game.py b/src/checkers/game.py index e71ab88..b64a62f 100644 --- a/src/checkers/game.py +++ b/src/checkers/game.py @@ -67,7 +67,6 @@ Iterable, Sequence, ) - from types import ModuleType from pygame.surface import Surface @@ -106,15 +105,11 @@ T = TypeVar("T") if globals().get("__file__") is None: - if TYPE_CHECKING: - - def resolve(name: str) -> ModuleType: ... # noqa: D103 - - else: - from importlib.resources._common import resolve + import importlib __file__ = str( - Path(resolve("checkers.data").__path__[0]).parent / "game.py", + Path(importlib.import_module("checkers.data").__path__[0]).parent + / "game.py", ) DATA_FOLDER: Final = Path(__file__).absolute().parent / "data" @@ -1468,7 +1463,7 @@ async def do_actions(self) -> None: class CheckersClient(sprite.GroupProcessor): """Checkers Game Client.""" - __slots__ = ("manager", "__weakref__") + __slots__ = ("manager",) def __init__(self, manager: ComponentManager) -> None: """Initialize Checkers Client.""" From 6e77236cd6211a3512e58bf33665ce1e961a276e Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Fri, 20 Sep 2024 02:29:42 -0500 Subject: [PATCH 3/4] Fix `Self` not existing in older versions --- src/checkers/state.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/checkers/state.py b/src/checkers/state.py index 6af98cb..e00ce49 100644 --- a/src/checkers/state.py +++ b/src/checkers/state.py @@ -30,7 +30,6 @@ TYPE_CHECKING, Any, NamedTuple, - Self, TypeAlias, TypeVar, cast, @@ -41,6 +40,8 @@ if TYPE_CHECKING: from collections.abc import Callable, Generator, Iterable + from typing_extensions import Self + MANDATORY_CAPTURE = True # If a jump is available, do you have to or not? PAWN_JUMP_FORWARD_ONLY = True # Pawns not allowed to go backwards in jumps? From e76e66f3a79cd88aafff483b9a79fcdce2f3a6e8 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 24 Sep 2024 23:32:46 -0500 Subject: [PATCH 4/4] Add mypy_extensions dependancy --- pyproject.toml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c49eee7..b735671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["hatchling >= 1.20.0"] -build-backend = "hatchling.build" +requires = ["setuptools >= 64"] +build-backend = "setuptools.build_meta" [project] name = "checkers" @@ -45,8 +45,8 @@ dependencies = [ "exceptiongroup; python_version < '3.11'", ] -[tool.hatch.version] -path = "src/checkers/game.py" +[tool.setuptools.dynamic] +version = {attr = "checkers.game.__version__"} [project.urls] "Homepage" = "https://github.com/CoolCat467/Checkers" @@ -56,17 +56,8 @@ path = "src/checkers/game.py" [project.scripts] checkers_game = "checkers:cli_run" -[tool.hatch.build.targets.wheel.hooks.mypyc] -dependencies = [ - "hatch-mypyc>=0.16.0", - "mypy==1.11.2", -] -require-runtime-dependencies = true -exclude = [ - "src/checkers/vector.py", - "src/checkers/base_io.py", - "src/checkers/buffer.py", -] +[tool.setuptools.package-data] +checkers = ["py.typed", "data/*"] [tool.mypy] mypy_path = "src"