Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
77c5bda
Compile project with mypyc
CoolCat467 Sep 20, 2024
d250b05
Fix type issues and remove duplicate weakref slot
CoolCat467 Sep 20, 2024
d4ae9e2
Fix `Self` not existing in older versions
CoolCat467 Sep 20, 2024
45e4077
Merge branch 'main' into switch-to-hatchling
CoolCat467 Sep 25, 2024
38052ab
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
CoolCat467 Sep 25, 2024
8501e05
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
CoolCat467 Sep 25, 2024
0e4781a
Merge branch 'main' into switch-to-hatchling
CoolCat467 Sep 28, 2024
68ad6c2
Merge branch 'main' into switch-to-hatchling
CoolCat467 Oct 13, 2024
cc0a1b6
Don't compile `namedtuple_mod`
CoolCat467 Oct 13, 2024
7bed24b
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
CoolCat467 Oct 20, 2024
9bf4662
Don't compile `component`, uses weak references
CoolCat467 Oct 20, 2024
0f2f9f1
Avoid mypyc errors
CoolCat467 Oct 20, 2024
de1d791
Statemachine also uses weak references
CoolCat467 Oct 22, 2024
3157877
Merge branch 'main' into switch-to-hatchling
CoolCat467 Nov 4, 2024
54083b9
Merge branch 'main' into switch-to-hatchling
CoolCat467 Dec 4, 2024
3d2dbcc
Merge branch 'main' into switch-to-hatchling
CoolCat467 Jan 19, 2025
6bded43
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 19, 2025
52a0aad
Fix spelling issue
CoolCat467 Jan 19, 2025
ccd1952
Update requirements
CoolCat467 Jan 19, 2025
66253aa
`component` -> `libcomponent`
CoolCat467 Jan 19, 2025
24a6111
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
CoolCat467 Jul 28, 2025
ae6c3da
Merge remote-tracking branch 'origin/main' into switch-to-hatchling
CoolCat467 Jul 29, 2025
04570dc
Move some code around to support mypyc
CoolCat467 Jul 29, 2025
1a2a1b8
Avoid mypyc compilation issue
CoolCat467 Aug 1, 2025
458e4f7
Fix windows not having a socket attribute hopefully
CoolCat467 Aug 2, 2025
ae5ba54
Merge branch 'main' into switch-to-hatchling
CoolCat467 Sep 19, 2025
dd4abcb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 19, 2025
4e90b6f
Fix merge issues
CoolCat467 Sep 19, 2025
c88bfa9
Remove duplciated code and update mypy require to 1.18.2
CoolCat467 Sep 19, 2025
eacd418
Update lockfile
CoolCat467 Sep 19, 2025
170787c
Fix position of return button
CoolCat467 Sep 19, 2025
e35d869
Fix mypyc issues
CoolCat467 Sep 19, 2025
c9ca795
Disable slots for some reason (I forget)
CoolCat467 Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 47 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -10,13 +10,11 @@ authors = [
]
description = "Graphical Checkers Game with AI support"
readme = {file = "README.md", content-type = "text/markdown"}
license = {file = "LICENSE"}
license = "GPL-3.0"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
Expand Down Expand Up @@ -46,8 +44,8 @@ dependencies = [
"exceptiongroup; python_version < '3.11'",
]

[tool.setuptools.dynamic]
version = {attr = "checkers.game.__version__"}
[tool.hatch.version]
path = "src/checkers/game.py"

[project.urls]
"Source" = "https://github.com/CoolCat467/Checkers"
Expand All @@ -67,15 +65,52 @@ tests = [
"uv>=0.5.21",
]
tools = [
"mypy>=1.17.0",
"mypy>=1.18.2",
"ruff>=0.9.2",
"codespell>=2.3.0",
"pre-commit>=4.2.0",
"attrs>=25.3.0",
]

[tool.setuptools.package-data]
checkers = ["py.typed", "data/*"]
[tool.hatch.build.targets.wheel]
packages = ["src/checkers", "src/checkers_computer_players"]

[tool.hatch.build.targets.wheel.hooks.mypyc]
dependencies = [
"hatch-mypyc>=0.16.0",
"mypy>=1.18.2",
]
require-runtime-dependencies = true
include = [
"src/checkers/base2d.py",
"src/checkers/network_shared.py",
"src/checkers/sound.py",
"src/checkers/state.py",
"src/checkers/server_state.py",
"src/checkers_computer_players/minimax.py",
"src/checkers_computer_players/checkers_minimax.py",
]
exclude = [
"tests/test_statemachine.py",
"src/checkers/client.py",
## "src/checkers/element_list.py",
## "src/checkers/game.py",
"src/checkers/multi_inherit.py",
## "src/checkers/namedtuple_mod.py",
## "src/checkers/objects.py",
"src/checkers/server.py",
## "src/checkers/sprite.py",
## "src/checkers/statemachine.py",
"src/checkers/vector.py",
## "src/checkers_computer_players/example_ai.py",
## "src/checkers_computer_players/max_y_jumper_ai.py",
"src/checkers_computer_players/machine_client.py",
## "src/checkers_computer_players/minimax_ai.py",
]

#[tool.hatch.build.targets.wheel.hooks.mypyc.options]
#opt_level = "1"
#multi_file = true

[tool.uv]
package = true
Expand Down Expand Up @@ -165,6 +200,8 @@ extend-ignore = [
[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"D100", # undocumented-public-module
"D101", # undocumented-public-class
"D102", # undocumented-public-method
"D103", # undocumented-public-function
"D107", # undocumented-public-init
]
Expand Down
23 changes: 23 additions & 0 deletions src/checkers/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Checkers Game Module."""

# Programmed by CoolCat467

# Copyright (C) 2023-2025 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

if __name__ == "__main__":
from checkers.game import cli_run as cli_run

cli_run()
27 changes: 19 additions & 8 deletions src/checkers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ async def read_advertisements(
# SO_REUSEADDR: allows binding to port potentially already in use
# Allow multiple copies of this program on one machine
# (not strictly needed)
udp_socket.setsockopt(
trio.socket.SOL_SOCKET,
trio.socket.SO_REUSEADDR,
1,
)
if hasattr(trio.socket, "SO_REUSEADDR"):
udp_socket.setsockopt(
trio.socket.SOL_SOCKET,
trio.socket.SO_REUSEADDR,
1,
)

await udp_socket.bind(("", ADVERTISEMENT_PORT))

Expand Down Expand Up @@ -101,7 +102,15 @@ async def read_advertisements(
mreq,
)
else: # IPv6
mreq = group_bin + struct.pack("@I", 0)
# print(
# "\n".join(
# f"{iface_index}: {iface_name}"
# for iface_index, iface_name in trio.socket.if_nameindex()
# )
# )
# iface_index = socket.if_nametoindex(iface_name)
iface_index = 0
mreq = group_bin + struct.pack("@I", iface_index)
udp_socket.setsockopt(
trio.socket.IPPROTO_IPV6,
trio.socket.IPV6_JOIN_GROUP,
Expand Down Expand Up @@ -152,7 +161,7 @@ class GameClient(ClientNetworkEventComponent):
to the server, and reading and raising incoming events from the server.
"""

__slots__ = ("connect_event_lock", "running")
# __slots__ = ("connect_event_lock", "running")

def __init__(self, name: str) -> None:
"""Initialize GameClient."""
Expand Down Expand Up @@ -317,7 +326,9 @@ async def handle_client_connect(
traceback.print_exception(ex)
else:
self.running = True
while not self.not_connected and self.running:
while self.running:
if self.not_connected:
break
await self.handle_read_event()
self.running = False

Expand Down
4 changes: 2 additions & 2 deletions src/checkers/element_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
class Element(sprite.Sprite):
"""Element sprite."""

__slots__ = ()
# __slots__ = ()

def self_destruct(self) -> None:
"""Remove this element."""
Expand All @@ -55,7 +55,7 @@ def __del__(self) -> None:
class ElementList(sprite.Sprite):
"""Element List sprite."""

__slots__ = ("_order",)
# __slots__ = ("_order",)

def __init__(self, name: object) -> None:
"""Initialize connection list."""
Expand Down
105 changes: 27 additions & 78 deletions src/checkers/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

from checkers import base2d, element_list, objects, sprite
from checkers.client import GameClient, read_advertisements
from checkers.multi_inherit import ConnectionElement, ReturnElement
from checkers.network_shared import DEFAULT_PORT, Pos
from checkers.objects import Button, OutlinedText
from checkers.server import GameServer
Expand Down Expand Up @@ -132,13 +133,13 @@ def render_text(
class Piece(sprite.Sprite):
"""Piece Sprite."""

__slots__ = (
"board_position",
"destination_tiles",
"piece_type",
"position_name",
"selected",
)
## __slots__ = (
## "board_position",
## "destination_tiles",
## "piece_type",
## "position_name",
## "selected",
## )

def __init__(
self,
Expand Down Expand Up @@ -282,7 +283,7 @@ async def handle_update_event(self, event: Event[int]) -> None:
class Tile(sprite.Sprite):
"""Outlined tile sprite - Only exists for selecting destination."""

__slots__ = ("board_position", "color")
# __slots__ = ("board_position", "color")

def __init__(
self,
Expand Down Expand Up @@ -379,14 +380,14 @@ def play_sound(
class GameBoard(sprite.Sprite):
"""Entity that stores data about the game board and renders it."""

__slots__ = (
"animation_queue",
"board_size",
"pieces",
"processing_animations",
"tile_size",
"tile_surfs",
)
## __slots__ = (
## "animation_queue",
## "board_size",
## "pieces",
## "processing_animations",
## "tile_size",
## "tile_surfs",
## )

# Define Tile Color Map and Piece Map
# tile_color_map = (BLACK, RED)
Expand Down Expand Up @@ -691,7 +692,7 @@ def generate_tile_images(self) -> None:
else:
image.add_image_and_mask(name, surface, "tile_0")

if index % 2 != 0:
if index & 1 != 0:
continue

outline_color = GREEN
Expand All @@ -712,7 +713,7 @@ def generate_tile_images(self) -> None:
color,
)

if piece_type % 2 == 0:
if piece_type & 1 == 0:
image.add_image(name, surface)
else:
image.add_image_and_mask(
Expand Down Expand Up @@ -812,7 +813,7 @@ def generate_board_image(self) -> Surface:
class ClickDestinationComponent(Component):
"""Component that will use targeting to go to wherever you click on the screen."""

__slots__ = ("selected",)
# __slots__ = ("selected",)
outline = pygame.color.Color(255, 220, 0)

def __init__(self) -> None:
Expand Down Expand Up @@ -1014,7 +1015,7 @@ async def check_conditions(self) -> None:
class GameState(AsyncState["CheckersClient"]):
"""Checkers Game Asynchronous State base class."""

__slots__ = ("id", "manager")
# __slots__ = ("id", "manager")

def __init__(self, name: str) -> None:
"""Initialize Game State."""
Expand Down Expand Up @@ -1224,7 +1225,7 @@ async def entry_actions(self) -> None:
class PlayHostingState(AsyncState["CheckersClient"]):
"""Start running server."""

__slots__ = ("address",)
# __slots__ = ("address",)

internal_server = False

Expand Down Expand Up @@ -1271,64 +1272,10 @@ class PlayInternalHostingState(PlayHostingState):
internal_server = True


class ReturnElement(element_list.Element, objects.Button):
"""Connection list return to title element sprite."""

__slots__ = ()

def __init__(self, name: str, font: pygame.font.Font) -> None:
"""Initialize return element."""
super().__init__(name, font)

self.update_location_on_resize = False
self.border_width = 4
self.outline = RED
self.text = "Return to Title"
self.visible = True
self.location = (SCREEN_SIZE[0] // 2, self.location.y + 10)

async def handle_click(
self,
_: Event[sprite.PygameMouseButtonEventData],
) -> None:
"""Handle Click Event."""
await self.raise_event(
Event("return_to_title", None, 2),
)


class ConnectionElement(element_list.Element, objects.Button):
"""Connection list element sprite."""

__slots__ = ()

def __init__(
self,
name: tuple[str, int],
font: pygame.font.Font,
motd: str,
) -> None:
"""Initialize connection element."""
super().__init__(name, font)

self.text = f"[{name[0]}:{name[1]}]\n{motd}"
self.visible = True

async def handle_click(
self,
_: Event[sprite.PygameMouseButtonEventData],
) -> None:
"""Handle Click Event."""
details = self.name
await self.raise_event(
Event("join_server", details, 2),
)


class PlayJoiningState(GameState):
"""Start running client."""

__slots__ = ("font",)
# __slots__ = ("font",)

def __init__(self) -> None:
"""Initialize Joining State."""
Expand Down Expand Up @@ -1360,6 +1307,8 @@ async def entry_actions(self) -> None:
30,
)
return_button = ReturnElement("return_button", return_font)
return_button.outline = RED
return_button.location = (SCREEN_SIZE[0] // 2, 30)
connections.add_element(return_button)

self.manager.register_handlers(
Expand Down Expand Up @@ -1431,7 +1380,7 @@ async def handle_return_to_title(self, _: Event[None]) -> None:
class PlayState(GameState):
"""Game Play State."""

__slots__ = ("exit_data",)
# __slots__ = ("exit_data",)

def __init__(self) -> None:
"""Initialize Play State."""
Expand Down Expand Up @@ -1570,7 +1519,7 @@ async def do_actions(self) -> None:
class CheckersClient(sprite.GroupProcessor):
"""Checkers Game Client."""

__slots__ = ("manager",)
# __slots__ = ("manager",)

def __init__(self, manager: ExternalRaiseManager) -> None:
"""Initialize Checkers Client."""
Expand Down
Loading