Merge pull request 'Ajout de vérification de types et de documentation' (#3) from checks into main

Reviewed-on: #3
This commit is contained in:
Tipragot 2023-10-24 11:12:53 +00:00 committed by Gitea
commit 73ff505095
No known key found for this signature in database
10 changed files with 114 additions and 129 deletions

3
.gitignore vendored
View file

@ -1 +1,2 @@
__pycache__
__pycache__
.mypy_cache

3
.pylintrc Normal file
View file

@ -0,0 +1,3 @@
[MESSAGES CONTROL]
disable=all
enable= missing-docstring

8
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"recommendations": [
"matangover.mypy",
"ms-python.pylint",
"ms-python.black-formatter",
"ms-python.python"
]
}

3
.vscode/launch.json vendored
View file

@ -6,7 +6,8 @@
"type": "python",
"request": "launch",
"module": "main",
"justMyCode": true
"justMyCode": true,
"preLaunchTask": "Python: Check",
}
]
}

29
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Python: MyPy",
"type": "shell",
"command": "mypy",
"args": [
"."
]
},
{
"label": "Python: Pylint",
"type": "shell",
"command": "pylint",
"args": [
"**/*.py"
]
},
{
"label": "Python: Check",
"dependsOrder": "sequence",
"dependsOn": [
"Python: MyPy",
"Python: Pylint",
]
},
]
}

View file

@ -1,23 +1,33 @@
from typing import Iterator, Callable
"""
Système de gestion d'entités, composants et ressources.
"""
from typing import Iterator, Callable, Tuple, TypeVar, Type, Optional
from logging import error
_T = TypeVar("_T")
"""
Un type générique utilisé pour les composants et les ressources.
"""
class World:
"""
Un monde contenant des entités et des ressources.
"""
def __init__(self):
def __init__(self) -> None:
"""
Permet de créer un nouveau monde vide.
"""
self._to_apply: set[Entity] = set()
self._entities: set[Entity] = set()
self._mapping: dict[type, set[Entity]] = {}
self._to_apply_resources: list[tuple[type, object]] = []
self._resources: dict[type, object] = {}
self._mapping: dict[Type, set[Entity]] = {}
self._to_apply_resources: list[tuple[Type, Optional[object]]] = []
self._resources: dict[Type, object] = {}
def create_entity(self, *components):
def create_entity(self, *components: _T) -> "Entity":
"""
Crée une entité avec les composants donnés en paramètres.
@ -29,7 +39,7 @@ class World:
"""
return Entity(self, *components)
def remove_entity(self, entity: "Entity"):
def remove_entity(self, entity: "Entity") -> None:
"""
Supprime une entité du monde.
@ -39,7 +49,7 @@ class World:
entity._deleted = True
self._to_apply.add(entity)
def set(self, *resources):
def set(self, *resources: _T) -> None:
"""
Définit les ressources données en paramètres.
Si les ressources existent deja, elles seront remplacées.
@ -50,7 +60,7 @@ class World:
for resource in resources:
self._to_apply_resources.append((type(resource), resource))
def remove(self, *resource_types):
def remove(self, *resource_types: Type[_T]) -> None:
"""
Supprime les ressources données en paramètres.
@ -61,7 +71,7 @@ class World:
if resource_type in self._resources:
self._to_apply_resources.append((resource_type, None))
def apply(self):
def apply(self) -> None:
"""
Applique les changements réaliser dans le monde.
"""
@ -77,7 +87,7 @@ class World:
del entity._components[component_type]
self._mapping[component_type].remove(entity)
else:
entity._components[component_type] = component
entity._components[component_type] = component # type: ignore[assignment]
self._mapping.setdefault(component_type, set()).add(entity)
entity._to_apply.clear()
self._to_apply.clear()
@ -88,7 +98,9 @@ class World:
self._resources[resource_type] = resource
self._to_apply_resources.clear()
def query(self, *needed: type, without: tuple[type] = []) -> Iterator["Entity"]:
def query(
self, *needed: Type[_T], without: Tuple[Type[_T], ...] = ()
) -> Iterator["Entity"]:
"""
Renvoie les entités qui ont les composants de *needed et sans les composants de *without.
@ -112,7 +124,7 @@ class World:
) and all(without_type not in entity for without_type in without):
yield entity
def __getitem__(self, resource_type: type) -> object:
def __getitem__(self, resource_type: Type[_T]) -> _T:
"""
Renvoie la ressource de type *resource_type.
@ -122,19 +134,23 @@ class World:
Retourne:
La ressource de type *resource_type.
"""
return self._resources[resource_type]
resource: _T = self._resources[resource_type] # type: ignore[assignment]
return resource
def __contains__(self, resource_type: type) -> bool:
def __contains__(self, *resource_types: Type[_T]) -> bool:
"""
Renvoie si la ressource de type *resource_type existe.
Renvoie True si le monde contient toutes les ressources de *resource_types.
Paramètres:
resource_type: Le type de ressource à tester.
*resource_types: Les types de ressource à tester.
Retourne:
Si la ressource de type *resource_type existe.
Si toutes les ressources de *resource_types sont dans le monde.
"""
return resource_type in self._resources
for resource_type in resource_types:
if resource_type not in self._resources:
return False
return True
class Entity:
@ -142,7 +158,7 @@ class Entity:
Une entité du monde.
"""
def __init__(self, world: World, *components):
def __init__(self, world: World, *components: _T):
"""
Créer une entité avec les composants en paramètres et l'ajoute au monde.
@ -151,14 +167,14 @@ class Entity:
*components: Les composants de l'entité.
"""
self._world = world
self._to_apply: list[tuple[type, object]] = [
self._to_apply: list[tuple[Type, Optional[object]]] = [
(type(component), component) for component in components
]
self._components: dict[type, object] = {}
self._components: dict[Type[_T], _T] = {}
self._deleted = False
self._world._to_apply.add(self)
def set(self, *components):
def set(self, *components: _T) -> None:
"""
Définit les composants de l'entité donnés en paramètres.
@ -169,7 +185,7 @@ class Entity:
self._to_apply.append((type(component), component))
self._world._to_apply.add(self)
def remove(self, *component_types: type):
def remove(self, *component_types: Type[_T]) -> None:
"""
Supprime les composants de l'entité donnés en paramètres.
@ -181,7 +197,7 @@ class Entity:
self._to_apply.append((component_type, None))
self._world._to_apply.add(self)
def __getitem__(self, component_type: type) -> object:
def __getitem__(self, component_type: Type[_T]) -> _T:
"""
Renvoie le composant de type *component_type.
@ -193,17 +209,20 @@ class Entity:
"""
return self._components[component_type]
def __contains__(self, component_type: type) -> bool:
def __contains__(self, *component_types: Type[_T]) -> bool:
"""
Renvoie si le composant de type *component_type existe.
Renvoie True si l'entité contient tous les composants de *component_types.
Paramètres:
component_type: Le type du composant à tester.
component_type: Les types des composants à tester.
Retourne:
Si le composant de type *component_type existe.
Si tous les composants de *component_types sont dans l'entité.
"""
return component_type in self._components
for component_type in component_types:
if component_type not in self._components:
return False
return True
class Game:
@ -211,7 +230,7 @@ class Game:
Permet de faire une simple boucle de jeu.
"""
def __init__(self):
def __init__(self) -> None:
"""
Créer une un jeu.
"""
@ -220,7 +239,7 @@ class Game:
self._update_tasks: dict[int, list[Callable[[World], None]]] = {}
self._shutdown_tasks: dict[int, list[Callable[[World], None]]] = {}
def add_startup_tasks(self, priority: int, *tasks: Callable[[World], None]):
def add_startup_tasks(self, priority: int, *tasks: Callable[[World], None]) -> None:
"""
Ajoute des taches qui s'executeront au démarrage du jeu.
@ -232,7 +251,7 @@ class Game:
raise RuntimeError("Cannot add startup task while the loop is running")
self._startup_tasks.setdefault(priority, []).extend(tasks)
def add_tasks(self, priority: int, *tasks: Callable[[World], None]):
def add_tasks(self, priority: int, *tasks: Callable[[World], None]) -> None:
"""
Ajoute des taches qui s'executeront a chaque mise à jour du jeu.
@ -244,7 +263,9 @@ class Game:
raise RuntimeError("Cannot add task while the loop is running")
self._update_tasks.setdefault(priority, []).extend(tasks)
def add_shutdown_tasks(self, priority: int, *tasks: Callable[[World], None]):
def add_shutdown_tasks(
self, priority: int, *tasks: Callable[[World], None]
) -> None:
"""
Ajoute des taches qui s'executeront à la fin de la boucle de jeu.
@ -256,7 +277,9 @@ class Game:
raise RuntimeError("Cannot add shutdown task while the loop is running")
self._shutdown_tasks.setdefault(priority, []).extend(tasks)
def _run_tasks(self, tasks: dict[int, list[Callable[[World], None]]], world: World):
def _run_tasks(
self, tasks: dict[int, list[Callable[[World], None]]], world: World
) -> None:
"""
Execute toutes les taches donnes en paramètres en respectant la priorité.
"""
@ -277,7 +300,7 @@ class Game:
self._running = True
# On initialize le monde
world = World()
world: World = World()
world.set(self)
# On applique les moddifications pour l'ajout de la ressource
@ -305,7 +328,7 @@ class Game:
# On retourne le monde
return world
def stop(self):
def stop(self) -> None:
"""
Demande la fin de la boucle de jeu. La boucle s'arretera a la prochaine mise à jour.
"""

View file

@ -1,90 +0,0 @@
from engine import World # Doit être mis dans le dossier principale pour fonctionner
# Création de composants pouvant être ajouté a des entitées
class Name(str):
pass
class Age(int):
pass
# Création d'un monde
world = World()
# Création de plusieurs entités
david = world.create_entity(Name("David"), Age(25))
fred = world.create_entity(Name("Fred"), Age(30))
paul_sans_age = world.create_entity(Name("Paul"))
age_tout_cour = world.create_entity(Age(14))
# On applique les moddifications
world.apply()
print("Récupération de toutes les entitées qui ont un nom")
for entity in world.query(Name):
print(entity[Name])
print("Récupération de toutes les entitées qui ont un age")
for entity in world.query(Age):
print(entity[Age])
# On change l'age de Fred
fred.set(Age(45))
# On applique les moddifications
world.apply()
print("Récupération de toutes les entités qui ont un nom et un age")
for entity in world.query(Name, Age):
print(entity[Name], entity[Age])
print("Récupération de toutes les entitées qui ont un nom mais pas d'age")
for entity in world.query(Name, without=(Age,)):
print(entity[Name])
print("Récupération de toutes les entités qui ont un age mais pas de nom")
for entity in world.query(Age, without=(Name,)):
print(entity[Age])
print("Récupération de toutes les entités")
for entity in world.query():
if Name in entity:
print(entity[Name], end=" ")
if Age in entity:
print(entity[Age], end=" ")
print()
# Création d'une ressource pouvant être ajoutée a un monde
class Gravity(float):
pass
# On peut aussi ajouter des ressources globales
world.set(Gravity(9.81))
print("On vérifie que la ressource Gravity existe")
print(Gravity in world)
# On applique les moddifications
world.apply()
print("On vérifie que la ressource Gravity existe après l'application")
print(Gravity in world)
print("Récupération de la ressource Gravity")
print(world[Gravity])
# On supprime la ressource Gravity
world.remove(Gravity)
print("On vérifie que la ressource Gravity n'existe plus")
print(Gravity in world)
# On applique les moddifications
world.apply()
print("On vérifie que la ressource Gravity n'existe plus")
print(Gravity in world)

View file

@ -1,3 +1,8 @@
"""
Ceci est un exemple de comment l'on peut utiliser le moteur du jeu.
"""
from engine import Game

3
mypy.ini Normal file
View file

@ -0,0 +1,3 @@
[mypy]
check_untyped_defs = True
disallow_untyped_defs = True

View file

@ -0,0 +1,2 @@
mypy
pylint