Skip to content

Fix multiple open issues #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:

- name: "Create archives"
run: |
zip -r seekers-stubs.zip seekers/grpc/stubs
zip -r seekers.zip *
zip -r seekers-linux-stubs.zip seekers/grpc/stubs
zip -r seekers-linux.zip *

- name: "Build binaries"
run: |
Expand Down Expand Up @@ -73,8 +73,8 @@ jobs:

- name: "Create archives"
run: |
powershell Compress-Archive ".\" "seekers.zip"
powershell Compress-Archive ".\seekers\grpc\stubs" "seekers-stubs.zip"
powershell Compress-Archive ".\" "seekers-win32.zip"
powershell Compress-Archive ".\seekers\grpc\stubs" "seekers-win32-stubs.zip"

- name: "Build binaries"
run: |
Expand Down
2 changes: 1 addition & 1 deletion freeze.bat
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ echo Building binaries ...
.\venv\Scripts\python setup.py build

echo Compress artifacts ...
for /d %%a in (build\*) do (powershell Compress-Archive ".\%%a\*" "seekers-bin.zip")
for /d %%a in (build\*) do (powershell Compress-Archive ".\%%a\*" "seekers-win32-bin.zip")
echo Finished!
2 changes: 1 addition & 1 deletion freeze.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ echo "Building binaries ..."
venv/bin/python setup.py build

echo "Create archive"
zip -r seekers-bin.zip build
zip -r seekers-linux-bin.zip build
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pygame>=2.1.0
pygame~=2.6.0
grpcio==1.64.1
protobuf==5.27.2
9 changes: 5 additions & 4 deletions run_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import sys
import logging
import seekers.grpc.client
import seekers.seekers_types

from seekers.game.player import LocalPlayerAi


def run_ai(args: argparse.Namespace):
Expand All @@ -14,7 +15,7 @@ def run_ai(args: argparse.Namespace):
stream=sys.stdout, force=True
)

ai = seekers.seekers_types.LocalPlayerAi.from_file(args.ai_file)
ai = LocalPlayerAi.from_file(args.ai_file)

service_wrapper = seekers.grpc.client.GrpcSeekersServiceWrapper(address=args.address)
client = seekers.grpc.client.GrpcSeekersClient(service_wrapper, ai, careful_mode=args.careful)
Expand All @@ -33,9 +34,9 @@ def run_ai(args: argparse.Namespace):

def main():
parser = argparse.ArgumentParser(description='Run a Python Seekers AI as a gRPC client.')
parser.add_argument("-address", "-a", type=str, default="localhost:7777",
parser.add_argument("--address", "-a", type=str, default="localhost:7777",
help="Address of the Seekers game. (default: localhost:7777)")
parser.add_argument("-loglevel", "-log", "-l", type=str, default="INFO",
parser.add_argument("--loglevel", "--log", "-l", type=str, default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
parser.add_argument("--careful", action="store_true", help="Enable careful mode for the gRPC clients. This will "
"raise an exception and stop the client when errors "
Expand Down
19 changes: 9 additions & 10 deletions run_seekers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys

from seekers import *
from seekers.game import SeekersGame


def parse_config_overrides(overrides: list[str]) -> dict[str, str]:
Expand All @@ -25,22 +24,22 @@ def parse_config_overrides(overrides: list[str]) -> dict[str, str]:

def main():
parser = argparse.ArgumentParser(description="Run python seekers AIs.")
parser.add_argument("--nogrpc", action="store_true", help="Don't host a gRPC server.")
parser.add_argument("--nokill", action="store_true", help="Don't kill the process after the game is over.")
parser.add_argument("--no-grpc", action="store_true", help="Don't host a gRPC server.")
parser.add_argument("--no-kill", action="store_true", help="Don't kill the process after the game is over.")
parser.add_argument("--debug", action="store_true", help="Enable debug mode. This will enable debug drawing.")
parser.add_argument("-address", "-a", type=str, default="localhost:7777",
parser.add_argument("--address", "-a", type=str, default="localhost:7777",
help="Address of the server. (default: localhost:7777)")
parser.add_argument("-config", "-c", type=str, default="config.ini",
parser.add_argument("--config", "-c", type=str, default="config.ini",
help="Path to the config file. (default: config.ini)")
parser.add_argument("-config-override", "-co", action="append",
parser.add_argument("--config-override", "--override", "-o", action="append",
help="Override a config option. Use the form option=value, e.g. global.seed=43.")
parser.add_argument("-loglevel", "-log", "-l", type=str, default="INFO",
parser.add_argument("--loglevel", "--log", "-l", type=str, default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
parser.add_argument("ai_files", type=str, nargs="*", help="Paths to the AIs.")

args = parser.parse_args()

if args.nogrpc and not args.ai_files:
if args.no_grpc and not args.ai_files:
raise ValueError("At least one AI file must be provided if gRPC is disabled.")

config = Config.from_filepath(args.config)
Expand All @@ -54,14 +53,14 @@ def main():

logging.basicConfig(level=args.loglevel, style="{", format=f"[{{name}}] {{levelname}}: {{message}}",
stream=sys.stdout)
address = args.address if not args.nogrpc else False
address = args.address if not args.no_grpc else False

seekers_game = SeekersGame(
local_ai_locations=args.ai_files,
config=config,
grpc_address=address,
debug=args.debug,
dont_kill=args.nokill
dont_kill=args.no_kill
)
seekers_game.listen()
seekers_game.start()
Expand Down
24 changes: 11 additions & 13 deletions seekers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from __future__ import annotations

from .vector import *
from .config import *
from .colors import Color
from .seekers_types import (
Config,
Vector,
Physical,
Goal,
Magnet,
Seeker,
Player,
World,
Camp,
)

from .player import *
from .goal import *
from .seeker import *
from .physical import *
from .camp import *
from .world import *

from . import debug_drawing
32 changes: 32 additions & 0 deletions seekers/camp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

import dataclasses

from .vector import *
from . import player


__all__ = [
"Camp"
]


@dataclasses.dataclass
class Camp:
id: str
owner: player.Player
position: Vector
width: float
height: float

def contains(self, pos: Vector) -> bool:
delta = self.position - pos
return 2 * abs(delta.x) < self.width and 2 * abs(delta.y) < self.height

@property
def top_left(self) -> Vector:
return self.position - Vector(self.width, self.height) / 2

@property
def bottom_right(self) -> Vector:
return self.position + Vector(self.width, self.height) / 2
2 changes: 2 additions & 0 deletions seekers/colors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import random
import typing
import colorsys
Expand Down
130 changes: 130 additions & 0 deletions seekers/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from __future__ import annotations

import configparser
import dataclasses
import typing

__all__ = [
"Config",
]


@dataclasses.dataclass
class Config:
"""Configuration for the Seekers game."""
global_wait_for_players: bool
global_playtime: int
global_seed: int
global_fps: int
global_speed: int
global_players: int
global_seekers: int
global_goals: int
global_color_threshold: float

map_width: int
map_height: int

camp_width: int
camp_height: int

seeker_thrust: float
seeker_magnet_slowdown: float
seeker_disabled_time: int
seeker_radius: float
seeker_mass: float
seeker_friction: float

goal_scoring_time: int
goal_radius: float
goal_mass: float
goal_thrust: float
goal_friction: float

@property
def map_dimensions(self):
return self.map_width, self.map_height

@classmethod
def from_file(cls, file) -> "Config":
cp = configparser.ConfigParser()
cp.read_file(file)

return cls(
global_wait_for_players=cp.getboolean("global", "wait-for-players"),
global_playtime=cp.getint("global", "playtime"),
global_seed=cp.getint("global", "seed"),
global_fps=cp.getint("global", "fps"),
global_speed=cp.getint("global", "speed"),
global_players=cp.getint("global", "players"),
global_seekers=cp.getint("global", "seekers"),
global_goals=cp.getint("global", "goals"),
global_color_threshold=cp.getfloat("global", "color-threshold"),

map_width=cp.getint("map", "width"),
map_height=cp.getint("map", "height"),

camp_width=cp.getint("camp", "width"),
camp_height=cp.getint("camp", "height"),

seeker_thrust=cp.getfloat("seeker", "thrust"),
seeker_magnet_slowdown=cp.getfloat("seeker", "magnet-slowdown"),
seeker_disabled_time=cp.getint("seeker", "disabled-time"),
seeker_radius=cp.getfloat("seeker", "radius"),
seeker_mass=cp.getfloat("seeker", "mass"),
seeker_friction=cp.getfloat("seeker", "friction"),

goal_scoring_time=cp.getint("goal", "scoring-time"),
goal_radius=cp.getfloat("goal", "radius"),
goal_mass=cp.getfloat("goal", "mass"),
goal_thrust=cp.getfloat("goal", "thrust"),
goal_friction=cp.getfloat("goal", "friction"),
)

@classmethod
def from_filepath(cls, filepath: str) -> "Config":
with open(filepath) as f:
return cls.from_file(f)

@staticmethod
def value_to_str(value: bool | float | int | str) -> str:
if isinstance(value, bool):
return str(value).lower()
elif isinstance(value, float):
return f"{value:.2f}"
else:
return str(value)

@staticmethod
def value_from_str(value: str, type_: typing.Literal["bool", "float", "int", "str"]) -> bool | float | int | str:
if type_ == "bool":
return value.lower() == "true"
elif type_ == "float":
return float(value)
elif type_ == "int":
return int(float(value))
else:
return value

@staticmethod
def get_section_and_key(attribute_name: str) -> tuple[str, str]:
"""Split an attribute name into the config header name and the key name."""

section, key = attribute_name.split("_", 1)

return section, key.replace("_", "-")

@staticmethod
def get_attribute_name(section: str, key: str) -> str:
return f"{section}_{key.replace('-', '_')}"

@classmethod
def get_field_type(cls, field_name: str) -> typing.Literal["bool", "float", "int", "str"]:
field_types = {f.name: f.type for f in dataclasses.fields(cls)}
return field_types[field_name]

def import_option(self, section: str, key: str, value: str):
field_name = self.get_attribute_name(section, key)
field_type = self.get_field_type(field_name)

setattr(self, field_name, self.value_from_str(value, field_type))
25 changes: 17 additions & 8 deletions seekers/debug_drawing.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
from __future__ import annotations

import abc
import dataclasses
import typing
from contextvars import ContextVar

from .seekers_types import Vector
from .draw import GameRenderer
from .vector import *
from . import draw

__all__ = [
"draw_text",
"draw_line",
"draw_circle",
]


@dataclasses.dataclass
class DebugDrawing(abc.ABC):
@abc.abstractmethod
def draw(self, game_renderer: GameRenderer):
def draw(self, game_renderer: draw.GameRenderer):
...


Expand All @@ -21,7 +29,7 @@ class TextDebugDrawing(DebugDrawing):
color: tuple[int, int, int] = (255, 255, 255)
center: bool = True

def draw(self, game_renderer: GameRenderer):
def draw(self, game_renderer: draw.GameRenderer):
# draw the text centered at the position
game_renderer.draw_text(self.text, self.color, self.position, center=self.center)

Expand All @@ -33,7 +41,7 @@ class LineDebugDrawing(DebugDrawing):
color: tuple[int, int, int] = (255, 255, 255)
width: int = 2

def draw(self, game_renderer: GameRenderer):
def draw(self, game_renderer: draw.GameRenderer):
game_renderer.draw_line(self.color, self.start, self.end, self.width)


Expand All @@ -44,7 +52,7 @@ class CircleDebugDrawing(DebugDrawing):
color: tuple[int, int, int] = (255, 255, 255)
width: int = 2

def draw(self, game_renderer: GameRenderer):
def draw(self, game_renderer: draw.GameRenderer):
game_renderer.draw_circle(self.color, self.position, self.radius, self.width)


Expand All @@ -60,5 +68,6 @@ def draw_circle(position: Vector, radius: float, color: tuple[int, int, int] = (
add_debug_drawing_func_ctxtvar.get()(CircleDebugDrawing(position, radius, color, width))


add_debug_drawing_func_ctxtvar: \
ContextVar[typing.Callable[[DebugDrawing], None]] = ContextVar("add_debug_drawing_func", default=lambda _: None)
add_debug_drawing_func_ctxtvar: ContextVar[typing.Callable[[DebugDrawing], None]] = (
ContextVar("add_debug_drawing_func", default=lambda _: None)
)
Loading
Loading