ecs #58
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -9,13 +9,10 @@
|
||||||
"python.analysis.inlayHints.functionReturnTypes": true,
|
"python.analysis.inlayHints.functionReturnTypes": true,
|
||||||
"python.analysis.inlayHints.pytestParameters": true,
|
"python.analysis.inlayHints.pytestParameters": true,
|
||||||
"python.analysis.typeCheckingMode": "strict",
|
"python.analysis.typeCheckingMode": "strict",
|
||||||
"python.analysis.diagnosticSeverityOverrides": {
|
|
||||||
"reportPrivateUsage": "none"
|
|
||||||
},
|
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||||
},
|
},
|
||||||
"pylint.args": [
|
"pylint.args": [
|
||||||
"--disable=broad-exception-caught,protected-access,undefined-variable,import-error,unused-import,no-member",
|
"--disable=protected-access,undefined-variable,import-error,unused-import,no-member",
|
||||||
],
|
],
|
||||||
}
|
}
|
857
src/engine.py
857
src/engine.py
|
@ -1,614 +1,16 @@
|
||||||
"""
|
"""
|
||||||
Un moteur de jeu inspiré de bevy.
|
Permet de lancer un jeu et de gérer la boucle principale de celui-ci.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import json
|
from typing import Callable
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
from typing import Callable, Optional, Sequence, SupportsFloat, TypeVar, Union
|
|
||||||
from time import time
|
|
||||||
import pygame
|
|
||||||
|
|
||||||
|
from ecs import World
|
||||||
class World:
|
|
||||||
"""
|
|
||||||
Un monde contenant toutes les données du jeu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__T = TypeVar("__T")
|
|
||||||
"""
|
|
||||||
Permet d'indiquer le type d'un composant ou d'une ressource.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Constructeur d'un nouveau monde vide.
|
|
||||||
"""
|
|
||||||
self._entities: dict[type[object], set[Entity]] = {}
|
|
||||||
self.__resources: dict[type[object], object] = {}
|
|
||||||
|
|
||||||
def create_entity(self, *components: object) -> "Entity":
|
|
||||||
"""
|
|
||||||
Crée une nouvelle entité avec les composants donnés et l'ajoute au monde.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
*components: les composants de l'entité.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
L'entité.
|
|
||||||
"""
|
|
||||||
return Entity(self, *components)
|
|
||||||
|
|
||||||
def __setitem__(self, resource_type: type[__T], resource: __T):
|
|
||||||
if resource_type != type(resource):
|
|
||||||
raise TypeError()
|
|
||||||
self.__resources[resource_type] = resource
|
|
||||||
|
|
||||||
def __getitem__(self, resource_type: type[__T]) -> __T:
|
|
||||||
return self.__resources[resource_type] # type: ignore
|
|
||||||
|
|
||||||
def __contains__(self, resource_type: type[object]) -> bool:
|
|
||||||
return resource_type in self.__resources
|
|
||||||
|
|
||||||
def __delitem__(self, resource_type: type[object]):
|
|
||||||
del self.__resources[resource_type]
|
|
||||||
|
|
||||||
def query(
|
|
||||||
self, *component_types: type[object], without: Sequence[type[object]] = ()
|
|
||||||
) -> set["Entity"]:
|
|
||||||
"""
|
|
||||||
Renvoie les entités qui ont tous les composants de type *component_types
|
|
||||||
et qui n'ont aucun des composants de type *without.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
*component_types: les types de composants des entités a renvoyer.
|
|
||||||
without: les types de composants des entités a exclure.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
Les entités qui correspondent aux critères.
|
|
||||||
"""
|
|
||||||
if len(component_types) == 0:
|
|
||||||
return set()
|
|
||||||
entities = set(self._entities.get(component_types[0], set()))
|
|
||||||
for component_type in component_types[1:]:
|
|
||||||
entities.intersection_update(self._entities.get(component_type, set()))
|
|
||||||
for component_type in without:
|
|
||||||
entities.difference_update(self._entities.get(component_type, set()))
|
|
||||||
return entities
|
|
||||||
|
|
||||||
|
|
||||||
class Entity:
|
|
||||||
"""
|
|
||||||
Une entité du monde. Elle contient des composants.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__T = TypeVar("__T")
|
|
||||||
"""
|
|
||||||
Permet d'indiquer le type d'un composant.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, world: World, *components: object):
|
|
||||||
"""
|
|
||||||
Crée une nouvelle entité avec les composants donnés et l'ajoute au monde.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
world: le monde dans lequel ajouter l'entité
|
|
||||||
*components: les composants à ajouter à l'entité.
|
|
||||||
"""
|
|
||||||
self.__world = world
|
|
||||||
self.__components: dict[type[object], object] = {}
|
|
||||||
for component in components:
|
|
||||||
self[type(component)] = component
|
|
||||||
|
|
||||||
def __setitem__(self, component_type: type[__T], component: __T):
|
|
||||||
if component_type != type(component):
|
|
||||||
raise TypeError()
|
|
||||||
self.__components[component_type] = component
|
|
||||||
self.__world._entities.setdefault(component_type, set()).add(self)
|
|
||||||
|
|
||||||
def __getitem__(self, component_type: type[__T]) -> __T:
|
|
||||||
return self.__components[component_type] # type: ignore
|
|
||||||
|
|
||||||
def __contains__(self, component_type: type[object]) -> bool:
|
|
||||||
return component_type in self.__components
|
|
||||||
|
|
||||||
def __delitem__(self, component_type: type[object]):
|
|
||||||
if self.__components.pop(component_type, None) is not None:
|
|
||||||
self.__world._entities[component_type].remove(self)
|
|
||||||
if len(self.__world._entities[component_type]) == 0:
|
|
||||||
del self.__world._entities[component_type]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"Entity({id(self)})"
|
|
||||||
|
|
||||||
|
|
||||||
class Vec2:
|
|
||||||
"""
|
|
||||||
Un vecteur 2D
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args: Union[SupportsFloat, "Vec2"]):
|
|
||||||
if (
|
|
||||||
len(args) == 2
|
|
||||||
and isinstance(args[0], SupportsFloat)
|
|
||||||
and isinstance(args[1], SupportsFloat)
|
|
||||||
):
|
|
||||||
self.x = float(args[0])
|
|
||||||
self.y = float(args[1])
|
|
||||||
elif len(args) == 1:
|
|
||||||
if isinstance(args[0], Vec2):
|
|
||||||
self.x = args[0].x
|
|
||||||
self.y = args[0].y
|
|
||||||
else:
|
|
||||||
self.x = float(args[0])
|
|
||||||
self.y = float(args[0])
|
|
||||||
elif len(args) == 0:
|
|
||||||
self.x = 0.0
|
|
||||||
self.y = 0.0
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid number of arguments")
|
|
||||||
|
|
||||||
def __add__(self, other: object) -> "Vec2":
|
|
||||||
if isinstance(other, Vec2):
|
|
||||||
return Vec2(self.x + other.x, self.y + other.y)
|
|
||||||
elif isinstance(other, SupportsFloat):
|
|
||||||
return Vec2(self.x + float(other), self.y + float(other))
|
|
||||||
raise ValueError(
|
|
||||||
f"Unsupported operand type(s) for +: 'Vec2' and '{type(other)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __sub__(self, other: object) -> "Vec2":
|
|
||||||
if isinstance(other, Vec2):
|
|
||||||
return Vec2(self.x - other.x, self.y - other.y)
|
|
||||||
elif isinstance(other, SupportsFloat):
|
|
||||||
return Vec2(self.x - float(other), self.y - float(other))
|
|
||||||
raise ValueError(
|
|
||||||
f"Unsupported operand type(s) for -: 'Vec2' and '{type(other)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __mul__(self, other: object) -> "Vec2":
|
|
||||||
if isinstance(other, Vec2):
|
|
||||||
return Vec2(self.x * other.x, self.y * other.y)
|
|
||||||
elif isinstance(other, SupportsFloat):
|
|
||||||
return Vec2(self.x * float(other), self.y * float(other))
|
|
||||||
raise ValueError(
|
|
||||||
f"Unsupported operand type(s) for *: 'Vec2' and '{type(other)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __truediv__(self, other: object) -> "Vec2":
|
|
||||||
if isinstance(other, Vec2):
|
|
||||||
return Vec2(self.x / other.x, self.y / other.y)
|
|
||||||
elif isinstance(other, SupportsFloat):
|
|
||||||
return Vec2(self.x / float(other), self.y / float(other))
|
|
||||||
raise ValueError(
|
|
||||||
f"Unsupported operand type(s) for /: 'Vec2' and '{type(other)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
|
||||||
if isinstance(other, Vec2):
|
|
||||||
return self.x == other.x and self.y == other.y
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash((self.x, self.y))
|
|
||||||
|
|
||||||
def __neg__(self) -> "Vec2":
|
|
||||||
return Vec2(-self.x, -self.y)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self) -> float:
|
|
||||||
"""
|
|
||||||
Retourne la longueur du vecteur.
|
|
||||||
"""
|
|
||||||
return math.sqrt(self.x**2 + self.y**2)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def normalized(self) -> "Vec2":
|
|
||||||
"""
|
|
||||||
Retourne une version normalisé du vecteur.
|
|
||||||
"""
|
|
||||||
length = self.length
|
|
||||||
return Vec2(self.x / length, self.y / length)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"Vec2({self.x}, {self.y})"
|
|
||||||
|
|
||||||
|
|
||||||
class Display:
|
|
||||||
"""
|
|
||||||
Une classe utilitaire pour la gestion de la fenêtre du jeu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
WIDTH = 1440.0
|
|
||||||
HEIGHT = 1080.0
|
|
||||||
RATIO = WIDTH / HEIGHT
|
|
||||||
INVERT_RATIO = HEIGHT / WIDTH
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _calculate_surface_rect() -> tuple[float, float, float, float]:
|
|
||||||
"""
|
|
||||||
Calcule et renvoit le rectangle de la surface dans la fenêtre du jeu.
|
|
||||||
"""
|
|
||||||
width, height = pygame.display.get_surface().get_size()
|
|
||||||
if width / height < Display.RATIO:
|
|
||||||
target_height = width * (Display.INVERT_RATIO)
|
|
||||||
offset = (height - target_height) / 2
|
|
||||||
rect = (0.0, offset, float(width), target_height)
|
|
||||||
else:
|
|
||||||
target_width = height * (Display.RATIO)
|
|
||||||
offset = (width - target_width) / 2
|
|
||||||
rect = (offset, 0.0, target_width, float(height))
|
|
||||||
return rect
|
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
|
||||||
"""
|
|
||||||
Un ressource qui représente le jeu actuel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.stop_requested = False
|
|
||||||
self.next_scene: Optional[str] = None
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""
|
|
||||||
Demande l'arrêt du jeu.
|
|
||||||
"""
|
|
||||||
self.stop_requested = True
|
|
||||||
|
|
||||||
def change_scene(self, scene_name: str):
|
|
||||||
"""
|
|
||||||
Demande un changement de scène.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
scene_name: Le nom de la scène dans laquelle aller.
|
|
||||||
"""
|
|
||||||
self.next_scene = scene_name
|
|
||||||
|
|
||||||
|
|
||||||
class Assets:
|
|
||||||
"""
|
|
||||||
Resource qui permet la gestion des assets.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, surface: pygame.Surface):
|
|
||||||
# Création de la texture d'erreur
|
|
||||||
error_texture = pygame.Surface((256, 256))
|
|
||||||
error_texture.fill((0, 0, 0))
|
|
||||||
pygame.draw.rect(error_texture, (255, 0, 255), (0, 0, 128, 128))
|
|
||||||
pygame.draw.rect(error_texture, (255, 0, 255), (128, 128, 128, 128))
|
|
||||||
self.__error_texture = error_texture.convert(surface)
|
|
||||||
|
|
||||||
# Cache des ressources
|
|
||||||
self.__textures: dict[str, pygame.Surface] = {}
|
|
||||||
self.__fonts: dict[int, pygame.font.Font] = {}
|
|
||||||
self.__sounds: dict[str, pygame.mixer.Sound] = {}
|
|
||||||
|
|
||||||
def get_texture(self, name: str) -> pygame.Surface:
|
|
||||||
"""
|
|
||||||
Renvoie la texture qui correspond au nom *name*.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
name: Le nom de la texture.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
La texture qui correspond au nom *name*.
|
|
||||||
"""
|
|
||||||
texture = self.__textures.get(name)
|
|
||||||
if texture is None:
|
|
||||||
if os.path.exists(f"assets/textures/{name}"):
|
|
||||||
texture = pygame.image.load(f"assets/textures/{name}")
|
|
||||||
if not name.startswith("animations/"):
|
|
||||||
texture = texture.convert_alpha()
|
|
||||||
self.__textures[name] = texture
|
|
||||||
return texture
|
|
||||||
return self.__error_texture
|
|
||||||
return texture
|
|
||||||
|
|
||||||
def get_texture_size(self, name: str) -> Vec2:
|
|
||||||
"""
|
|
||||||
Renvoie la taille de la texture qui correspond au nom *name*.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
name: Le nom de la texture.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
La taille de la texture qui correspond au nom *name*.
|
|
||||||
"""
|
|
||||||
return Vec2(*self.get_texture(name).get_size())
|
|
||||||
|
|
||||||
def get_font(self, size: int) -> pygame.font.Font:
|
|
||||||
"""
|
|
||||||
Renvoie la police qui correspond à la taille *size*.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
size: La taille de la police.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
La police qui correspond à la taille *size*.
|
|
||||||
"""
|
|
||||||
font = self.__fonts.get(size)
|
|
||||||
if font is None:
|
|
||||||
font = pygame.font.Font("assets/font.ttf", size)
|
|
||||||
self.__fonts[size] = font
|
|
||||||
return font
|
|
||||||
|
|
||||||
def get_text_size(self, text: str, size: int) -> Vec2:
|
|
||||||
"""
|
|
||||||
Renvoie la taille d'un texte avec une certaine taille de police.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
text: Le texte.
|
|
||||||
size: La taille de la police.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
La taille d'un texte avec une certaine taille de police.
|
|
||||||
"""
|
|
||||||
return Vec2(*self.get_font(size).size(text))
|
|
||||||
|
|
||||||
def get_sound(self, name: str) -> pygame.mixer.Sound:
|
|
||||||
"""
|
|
||||||
Renvoie le son qui correspond au nom *name*.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
name: Le nom du son.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
Le son qui correspond au nom *name*.
|
|
||||||
"""
|
|
||||||
sound = self.__sounds.get(name)
|
|
||||||
if sound is None:
|
|
||||||
sound = pygame.mixer.Sound(f"assets/sounds/{name}")
|
|
||||||
self.__sounds[name] = sound
|
|
||||||
return sound
|
|
||||||
|
|
||||||
|
|
||||||
class Time(float):
|
|
||||||
"""
|
|
||||||
Ressource qui represente le temps depuis 1900.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Delta(float):
|
|
||||||
"""
|
|
||||||
Ressource qui represente le temps depuis la dernière frame.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Keyboard:
|
|
||||||
"""
|
|
||||||
Ressource qui représente les entrées utilisateurs sur le clavier à la frame actuelle.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.keys: set[str] = set()
|
|
||||||
self.pressed: set[str] = set()
|
|
||||||
self.released: set[str] = set()
|
|
||||||
|
|
||||||
def is_key_pressed(self, key_name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
key_name: Le nom de la touche à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si la touche *key_name* a commencé a être appuyée pendant la frame actuelle.
|
|
||||||
"""
|
|
||||||
return key_name in self.pressed
|
|
||||||
|
|
||||||
def is_key(self, key_name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si la touche *key_name* est actuellement appuyée.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
key_name: Le nom de la touche à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si la touche *key_name* est actuellement appuyée.
|
|
||||||
"""
|
|
||||||
return key_name in self.keys
|
|
||||||
|
|
||||||
def is_key_released(self, key_name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si la touche *key_name* a été relachée pendant la frame actuelle.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
key_name: Le nom de la touche à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si la touche *key_name* a été relachée pendant la frame actuelle.
|
|
||||||
"""
|
|
||||||
return key_name in self.released
|
|
||||||
|
|
||||||
|
|
||||||
class Mouse:
|
|
||||||
"""
|
|
||||||
Ressource qui représente l'état de la souris à la frame actuelle.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.buttons: set[int] = set()
|
|
||||||
self.pressed: set[int] = set()
|
|
||||||
self.released: set[int] = set()
|
|
||||||
self.position: Vec2 = Vec2(0.0, 0.0)
|
|
||||||
self.delta: Vec2 = Vec2(0.0, 0.0)
|
|
||||||
|
|
||||||
def is_button_pressed(self, button: int) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si le bouton *button* a commencé a être appuyée pendant la frame actuelle.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
button: Le numéro du bouton à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si le bouton *button* a commencé a être appuyée pendant la frame actuelle.
|
|
||||||
"""
|
|
||||||
return button in self.pressed
|
|
||||||
|
|
||||||
def is_button(self, button: int) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si le bouton *button* est actuellement appuyé.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
button: Le numéro du bouton à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si le bouton *button* est actuellement appuyé.
|
|
||||||
"""
|
|
||||||
return button in self.buttons
|
|
||||||
|
|
||||||
def is_button_released(self, button: int) -> bool:
|
|
||||||
"""
|
|
||||||
Renvoie True si le bouton *button* a été relaché pendant la frame actuelle.
|
|
||||||
|
|
||||||
Paramètres:
|
|
||||||
button: Le numéro du bouton à tester.
|
|
||||||
|
|
||||||
Retourne:
|
|
||||||
True si le bouton *button* aLongrightarrow relaché pendant la frame actuelle.
|
|
||||||
"""
|
|
||||||
return button in self.released
|
|
||||||
|
|
||||||
|
|
||||||
class Position(Vec2):
|
|
||||||
"""
|
|
||||||
Composant qui représente la position d'une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Centered:
|
|
||||||
"""
|
|
||||||
Composant permettant de dire que l'affichage de l'entité doit être centré.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Offset(Vec2):
|
|
||||||
"""
|
|
||||||
Composant qui represente un décalage de la position d'une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Order(int):
|
|
||||||
"""
|
|
||||||
Composant qui represente l'ordre d'affichage d'une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Texture(str):
|
|
||||||
"""
|
|
||||||
Composant qui rerpésente la texture d'une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HoveredTexture(Texture):
|
|
||||||
"""
|
|
||||||
Composant qui represente la texture lorsque l'entité est survolée.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Animation:
|
|
||||||
"""
|
|
||||||
Composant qui représente une animation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
|
|
||||||
):
|
|
||||||
self.name = name
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
with open(
|
|
||||||
f"assets/textures/animations/{name}/info.json",
|
|
||||||
"r",
|
|
||||||
encoding="utf-8",
|
|
||||||
) as f:
|
|
||||||
info = json.load(f)
|
|
||||||
self.end_image: str = info.get("end_image", "")
|
|
||||||
self.offset = Offset(info["offset"]["x"], info["offset"]["y"])
|
|
||||||
self.frame_count: int = info["frame_count"]
|
|
||||||
self.fps: int = info["fps"]
|
|
||||||
self.time = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
class Text(str):
|
|
||||||
"""
|
|
||||||
Composant qui represente un texte.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TextSize(int):
|
|
||||||
"""
|
|
||||||
Composant qui represente la taille d'un texte.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Color(pygame.Color):
|
|
||||||
"""
|
|
||||||
Composant qui represente la couleur d'une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HoverEnter:
|
|
||||||
"""
|
|
||||||
Composant qui marque un entité comme commencée à être survolée.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Hovered:
|
|
||||||
"""
|
|
||||||
Composant qui marque un entité comme survolée.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HoverExit:
|
|
||||||
"""
|
|
||||||
Composant qui marque un entité comme arreté d'être survolée.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Clickable:
|
|
||||||
"""
|
|
||||||
Composant qui marque un entité comme pouvant etre cliquer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, callback: Callable[[World, Entity], object]):
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
|
|
||||||
class Sound:
|
|
||||||
"""
|
|
||||||
Composant qui une entité emettrant un son.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
volume: float = 0.5,
|
|
||||||
loop: bool = False,
|
|
||||||
callback: Callable[[World, Entity], object] = lambda _w, _e: None,
|
|
||||||
stop_on_remove: bool = False,
|
|
||||||
) -> None:
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
if os.path.isdir(f"assets/sounds/{name}"):
|
|
||||||
list_files = os.listdir(f"assets/sounds/{name}")
|
|
||||||
random_file = random.choice(list_files)
|
|
||||||
self.name = f"{name}/{random_file}"
|
|
||||||
|
|
||||||
self.volume = volume
|
|
||||||
self.loop = loop
|
|
||||||
self.callback = callback
|
|
||||||
self.stop_on_remove = stop_on_remove
|
|
||||||
|
|
||||||
|
|
||||||
class Scene:
|
class Scene:
|
||||||
"""
|
"""
|
||||||
Une scène dans le jeu.
|
Une scène du jeu.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -629,241 +31,52 @@ class Scene:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_game(
|
class CurrentScene(str):
|
||||||
scenes: dict[str, Scene],
|
|
||||||
start_scene: str,
|
|
||||||
*,
|
|
||||||
title: str = "Game",
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Lance le moteur de jeu.
|
Resource qui permet de savoir et de changer la scène actuelle.
|
||||||
"""
|
"""
|
||||||
# Initialisation de pygame
|
|
||||||
pygame.init()
|
|
||||||
if os.path.exists("icon.png"):
|
|
||||||
pygame.display.set_icon(pygame.image.load("icon.png"))
|
|
||||||
pygame.display.set_caption(title)
|
|
||||||
pygame.display.set_mode((800, 600), pygame.RESIZABLE)
|
|
||||||
surface = pygame.Surface((Display.WIDTH, Display.HEIGHT))
|
|
||||||
keyboard = Keyboard()
|
|
||||||
mouse = Mouse()
|
|
||||||
|
|
||||||
# Chargements des assets
|
|
||||||
assets = Assets(surface)
|
|
||||||
|
|
||||||
# creation des channels pour les sons
|
class KeepAlive:
|
||||||
channels: dict[Entity, tuple[bool, pygame.mixer.Channel]] = {}
|
"""
|
||||||
|
Composant qui marque une entité comme n'étant pas détruit lors
|
||||||
|
d'un changement de scène.
|
||||||
|
"""
|
||||||
|
|
||||||
# On récupère la première scène
|
|
||||||
scene = scenes.get(start_scene)
|
|
||||||
|
|
||||||
# Tant qu'il y a des scènes a executer on les executent
|
def start_game(scenes: dict[str, Scene], scene_name: str):
|
||||||
while scene is not None:
|
"""
|
||||||
# Initialisation du monde
|
Lance un jeu.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
- `scenes`: un dictionnaire contenant les scènes du jeu.
|
||||||
|
- `scene_name`: le nom de la scène à charger en premier lors du lancement du jeu.
|
||||||
|
"""
|
||||||
world = World()
|
world = World()
|
||||||
world[Game] = Game()
|
world[CurrentScene] = CurrentScene(scene_name)
|
||||||
world[Assets] = assets
|
scene = scenes.get(scene_name)
|
||||||
world[Keyboard] = keyboard
|
while scene is not None:
|
||||||
world[Mouse] = mouse
|
# On retire les ressources de l'ancienne scène
|
||||||
world[Time] = Time(time())
|
for resource in world:
|
||||||
|
if not isinstance(resource, KeepAlive):
|
||||||
|
world.remove(type(resource))
|
||||||
|
|
||||||
# Initialisation de la scène
|
# On retire les entité de l'ancienne scène
|
||||||
|
for entity in world.query(without=(KeepAlive,)):
|
||||||
|
entity.destroy()
|
||||||
|
|
||||||
|
# On initialise la nouvelle scene
|
||||||
for system in scene.init_systems:
|
for system in scene.init_systems:
|
||||||
system(world)
|
system(world)
|
||||||
|
|
||||||
# Tant que la scène n'est pas terminé
|
# Tant que la scène n'est pas terminé on la met à jour
|
||||||
while world[Game].next_scene is None and not world[Game].stop_requested:
|
while world.get(CurrentScene, "") == scene_name:
|
||||||
# On gère les évenements pygame
|
|
||||||
keyboard.pressed.clear()
|
|
||||||
keyboard.released.clear()
|
|
||||||
mouse.pressed.clear()
|
|
||||||
mouse.released.clear()
|
|
||||||
last_position = Vec2(mouse.position)
|
|
||||||
for event in pygame.event.get():
|
|
||||||
if event.type == pygame.QUIT:
|
|
||||||
world[Game].stop()
|
|
||||||
elif event.type == pygame.KEYDOWN:
|
|
||||||
key_name = pygame.key.name(event.key)
|
|
||||||
if key_name == "f11":
|
|
||||||
pygame.display.toggle_fullscreen()
|
|
||||||
keyboard.keys.add(key_name)
|
|
||||||
keyboard.pressed.add(key_name)
|
|
||||||
elif event.type == pygame.KEYUP:
|
|
||||||
key_name = pygame.key.name(event.key)
|
|
||||||
if key_name == "f11":
|
|
||||||
continue
|
|
||||||
keyboard.keys.remove(key_name)
|
|
||||||
keyboard.released.add(key_name)
|
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
|
||||||
mouse.buttons.add(event.button)
|
|
||||||
mouse.pressed.add(event.button)
|
|
||||||
elif event.type == pygame.MOUSEBUTTONUP:
|
|
||||||
mouse.buttons.remove(event.button)
|
|
||||||
mouse.released.add(event.button)
|
|
||||||
elif event.type == pygame.MOUSEMOTION:
|
|
||||||
rect = Display._calculate_surface_rect()
|
|
||||||
mouse.position = Vec2(
|
|
||||||
((event.pos[0] - rect[0]) / rect[2]) * Display.WIDTH,
|
|
||||||
((event.pos[1] - rect[1]) / rect[3]) * Display.HEIGHT,
|
|
||||||
)
|
|
||||||
mouse.delta = mouse.position - last_position
|
|
||||||
|
|
||||||
# On vérifie le survol des textures et textes
|
|
||||||
for entity in world.query(Position, Texture):
|
|
||||||
# Récupération de la position et taille de l'entité
|
|
||||||
position: Vec2 = entity[Position]
|
|
||||||
if Offset in entity:
|
|
||||||
position = position + entity[Offset]
|
|
||||||
size = assets.get_texture_size(entity[Texture])
|
|
||||||
if Centered in entity:
|
|
||||||
position -= size / 2
|
|
||||||
|
|
||||||
# On détermine si la souris est sur l'entité
|
|
||||||
if (
|
|
||||||
mouse.position.x >= position.x
|
|
||||||
and mouse.position.x <= position.x + size.x
|
|
||||||
and mouse.position.y >= position.y
|
|
||||||
and mouse.position.y <= position.y + size.y
|
|
||||||
):
|
|
||||||
if Hovered not in entity:
|
|
||||||
entity[HoverEnter] = HoverEnter()
|
|
||||||
else:
|
|
||||||
del entity[HoverEnter]
|
|
||||||
entity[Hovered] = Hovered()
|
|
||||||
else:
|
|
||||||
if Hovered in entity:
|
|
||||||
entity[HoverExit] = HoverExit()
|
|
||||||
else:
|
|
||||||
del entity[HoverExit]
|
|
||||||
del entity[Hovered]
|
|
||||||
|
|
||||||
# On met à jour le temps et delta
|
|
||||||
now = time()
|
|
||||||
world[Delta] = Delta(now - world[Time])
|
|
||||||
world[Time] = Time(now)
|
|
||||||
|
|
||||||
# On execute les objets clickables
|
|
||||||
if mouse.is_button_pressed(1):
|
|
||||||
for entity in world.query(Clickable, Hovered):
|
|
||||||
entity[Clickable].callback(world, entity)
|
|
||||||
|
|
||||||
# On met à jour la scène
|
|
||||||
for system in scene.update_systems:
|
for system in scene.update_systems:
|
||||||
system(world)
|
system(world)
|
||||||
|
|
||||||
# Gestion des sons en cours
|
# On arrete la scene
|
||||||
sound_entities = world.query(Sound)
|
|
||||||
entities_to_delete: list[Entity] = []
|
|
||||||
for entity, (stop_on_remove, channel) in channels.items():
|
|
||||||
if Sound in entity:
|
|
||||||
entity_sound = entity[Sound]
|
|
||||||
channel.set_volume(entity_sound.volume)
|
|
||||||
if not channel.get_busy():
|
|
||||||
entities_to_delete.append(entity)
|
|
||||||
del entity[Sound]
|
|
||||||
entity_sound.callback(world, entity)
|
|
||||||
continue
|
|
||||||
if stop_on_remove and entity not in sound_entities:
|
|
||||||
entities_to_delete.append(entity)
|
|
||||||
channel.stop()
|
|
||||||
for entity in entities_to_delete:
|
|
||||||
del channels[entity]
|
|
||||||
|
|
||||||
# Ajout des sons non gérés
|
|
||||||
for entity in world.query(Sound):
|
|
||||||
if entity not in channels:
|
|
||||||
entity_sound = entity[Sound]
|
|
||||||
sound = assets.get_sound(entity_sound.name)
|
|
||||||
channel = sound.play(loops=-1 if entity_sound.loop else 0)
|
|
||||||
if channel is None: # type: ignore
|
|
||||||
continue
|
|
||||||
channel.set_volume(entity_sound.volume)
|
|
||||||
channels[entity] = entity_sound.stop_on_remove, channel
|
|
||||||
|
|
||||||
# Mise à jour des animations
|
|
||||||
for entity in world.query(Animation):
|
|
||||||
animation = entity[Animation]
|
|
||||||
if animation.time == 0:
|
|
||||||
if animation.end_image == "" and Texture in entity:
|
|
||||||
animation.end_image = entity[Texture]
|
|
||||||
animation.time += world[Delta]
|
|
||||||
frame_index = int(animation.time * animation.fps)
|
|
||||||
if frame_index >= animation.frame_count:
|
|
||||||
entity[Texture] = Texture(animation.end_image)
|
|
||||||
del entity[Animation]
|
|
||||||
del entity[Offset]
|
|
||||||
animation.callback(world, entity)
|
|
||||||
else:
|
|
||||||
entity[Offset] = Offset(animation.offset.x, animation.offset.y)
|
|
||||||
entity[Texture] = Texture(
|
|
||||||
f"animations/{animation.name}/{frame_index:04}.png"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rendu du monde
|
|
||||||
for entity in sorted(world.query(Order, Position), key=lambda e: e[Order]):
|
|
||||||
# Récupération de la position de l'entité
|
|
||||||
position: Vec2 = entity[Position]
|
|
||||||
if Offset in entity:
|
|
||||||
position = position + entity[Offset]
|
|
||||||
centered = Centered in entity
|
|
||||||
|
|
||||||
# Affichage de la texture
|
|
||||||
if Texture in entity:
|
|
||||||
if HoveredTexture in entity and Hovered in entity:
|
|
||||||
texture = assets.get_texture(entity[HoveredTexture])
|
|
||||||
else:
|
|
||||||
texture = assets.get_texture(entity[Texture])
|
|
||||||
surface.blit(
|
|
||||||
texture,
|
|
||||||
(
|
|
||||||
position.x - texture.get_width() / 2
|
|
||||||
if centered
|
|
||||||
else position.x,
|
|
||||||
position.y - texture.get_height() / 2
|
|
||||||
if centered
|
|
||||||
else position.y,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Affichage des textes
|
|
||||||
if Text in entity:
|
|
||||||
color = entity[Color] if Color in entity else Color(255, 255, 255)
|
|
||||||
size = entity[TextSize] if TextSize in entity else 50
|
|
||||||
font = assets.get_font(size)
|
|
||||||
font_surface = font.render(entity[Text], True, color)
|
|
||||||
surface.blit(
|
|
||||||
font_surface,
|
|
||||||
(
|
|
||||||
position.x - font_surface.get_width() / 2
|
|
||||||
if centered
|
|
||||||
else position.x,
|
|
||||||
position.y - font_surface.get_height() / 2
|
|
||||||
if centered
|
|
||||||
else position.y,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mise a jour de la fenêtre
|
|
||||||
rect = Display._calculate_surface_rect()
|
|
||||||
pygame.transform.set_smoothscale_backend("MMX")
|
|
||||||
pygame.transform.smoothscale(
|
|
||||||
surface,
|
|
||||||
(rect[2], rect[3]),
|
|
||||||
pygame.display.get_surface().subsurface(rect),
|
|
||||||
)
|
|
||||||
pygame.display.flip()
|
|
||||||
surface.fill((0, 0, 0))
|
|
||||||
|
|
||||||
# Arrêt de la scène
|
|
||||||
for system in scene.stop_systems:
|
for system in scene.stop_systems:
|
||||||
system(world)
|
system(world)
|
||||||
|
|
||||||
# Récupération de la scène suivante
|
# Récupération de la scène suivante
|
||||||
next_scene = world[Game].next_scene
|
scene = scenes.get(world.get(CurrentScene, ""))
|
||||||
if next_scene is not None:
|
|
||||||
scene = scenes.get(next_scene)
|
|
||||||
else:
|
|
||||||
scene = None
|
|
||||||
|
|
||||||
# Arrêt du jeu
|
|
||||||
pygame.quit()
|
|
||||||
|
|
18
src/main.py
18
src/main.py
|
@ -1,19 +1,3 @@
|
||||||
"""
|
"""
|
||||||
Example de l'utilisation du moteur de jeu.
|
Module d'exemple de l'utilisation du moteur de jeu.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from engine import start_game
|
|
||||||
from scenes import classique, menteur, menu, directory_search
|
|
||||||
|
|
||||||
|
|
||||||
start_game(
|
|
||||||
{
|
|
||||||
"menu": menu.SCENE,
|
|
||||||
"classique": classique.SCENE,
|
|
||||||
"menteur": menteur.SCENE,
|
|
||||||
"histoire": directory_search.SCENE,
|
|
||||||
},
|
|
||||||
"menu",
|
|
||||||
title="Guess The Number",
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
"""
|
"""
|
||||||
Contient des plugins pour certaines features du jeu.
|
Module contenant tous les plugins du jeu.
|
||||||
|
|
||||||
|
Un plugin est une scène pouvant être ajouté a d'autres scènes
|
||||||
|
afin d'ajouter des fonctionnalités au jeu.
|
||||||
|
|
||||||
|
Le but est de faire en sorte que les plugins soient génériques
|
||||||
|
afin de pouvoir les utilisers dans plusieurs scènes et donc
|
||||||
|
éviter de répéter plusieurs fois le même code.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
Un plugin permettant de faire des déplacements fluides des entités.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from engine import Delta, Position, Scene, Vec2, World
|
|
||||||
|
|
||||||
|
|
||||||
class Target(Vec2):
|
|
||||||
"""
|
|
||||||
Composant donnant la position voulue de l'entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Speed(float):
|
|
||||||
"""
|
|
||||||
Composant donnant la vittesse de déplacement de l'entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def __update_positions(world: World):
|
|
||||||
"""
|
|
||||||
Met à jour la position des entités pour se rapprocher de la position voulue.
|
|
||||||
"""
|
|
||||||
for entity in world.query(Position, Target):
|
|
||||||
position = entity[Position]
|
|
||||||
target = entity[Target]
|
|
||||||
speed = entity[Speed] if Speed in entity else Speed(10)
|
|
||||||
entity[Position] = Position(
|
|
||||||
position + (target - position) * world[Delta] * speed
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
PLUGIN = Scene(
|
|
||||||
[],
|
|
||||||
[__update_positions],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
Definit un plugin qui crée un texte avec les touches frappées
|
|
||||||
"""
|
|
||||||
|
|
||||||
from engine import Keyboard, Scene, Sound, Text, World
|
|
||||||
|
|
||||||
|
|
||||||
class Typing(str):
|
|
||||||
"""
|
|
||||||
Marque une entité comme un texte qui s'ecrit en fonction du clavier
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def __update(world: World):
|
|
||||||
"""
|
|
||||||
Met a jour les entitées contenant le composant Typing
|
|
||||||
"""
|
|
||||||
keyboard = world[Keyboard]
|
|
||||||
for entity in world.query(Typing, Text):
|
|
||||||
text = entity[Text]
|
|
||||||
for key in keyboard.pressed:
|
|
||||||
if key == "backspace":
|
|
||||||
world.create_entity(Sound("click"))
|
|
||||||
text = text[:-1]
|
|
||||||
if key.startswith("["): # pavé numerique
|
|
||||||
key = key[1]
|
|
||||||
if key in entity[Typing]:
|
|
||||||
world.create_entity(Sound("click"))
|
|
||||||
text += key
|
|
||||||
entity[Text] = Text(text)
|
|
||||||
|
|
||||||
|
|
||||||
PLUGIN = Scene(
|
|
||||||
[],
|
|
||||||
[__update],
|
|
||||||
[],
|
|
||||||
)
|
|
|
@ -1,3 +0,0 @@
|
||||||
"""
|
|
||||||
Contient toutes les scènes du jeu.
|
|
||||||
"""
|
|
|
@ -1,227 +0,0 @@
|
||||||
"""
|
|
||||||
Définis la scène du jeu classique, sans variante.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import random
|
|
||||||
from plugins import typing
|
|
||||||
from engine import (
|
|
||||||
Centered,
|
|
||||||
Clickable,
|
|
||||||
Color,
|
|
||||||
Display,
|
|
||||||
Entity,
|
|
||||||
Game,
|
|
||||||
HoveredTexture,
|
|
||||||
Keyboard,
|
|
||||||
Order,
|
|
||||||
Position,
|
|
||||||
Scene,
|
|
||||||
Sound,
|
|
||||||
Text,
|
|
||||||
TextSize,
|
|
||||||
Texture,
|
|
||||||
World,
|
|
||||||
)
|
|
||||||
|
|
||||||
COLOR_TEXT = Color(66, 39, 148)
|
|
||||||
|
|
||||||
|
|
||||||
class RandomNumber(int):
|
|
||||||
"""
|
|
||||||
La ressource qui est le nombre a deviner.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TextDialogue:
|
|
||||||
"""
|
|
||||||
Le component qui declare l'entitee Text qui affiche le plus petit ou le plus grand
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NombreEssai(int):
|
|
||||||
"""
|
|
||||||
Le component qui declare le nombre d'essai
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NombreEssaiText:
|
|
||||||
"""
|
|
||||||
Le component qui affiche le nombre d'essai
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IsRunning:
|
|
||||||
"""
|
|
||||||
Le component qui indique si le jeu est en cours
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def __initialize_world(world: World):
|
|
||||||
"""
|
|
||||||
Initialise le monde du menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Fond d'ecran
|
|
||||||
world.create_entity(
|
|
||||||
Position(),
|
|
||||||
Order(0),
|
|
||||||
Texture("classique/background.png"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton valider/rejouer
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 875),
|
|
||||||
Order(1),
|
|
||||||
Centered(),
|
|
||||||
Texture("classique/valider.png"),
|
|
||||||
HoveredTexture("classique/valider_hover.png"),
|
|
||||||
Clickable(lambda world, _: _update(world)),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Zone de saisie
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 750),
|
|
||||||
Order(2),
|
|
||||||
Centered(),
|
|
||||||
typing.Typing("1234567890"),
|
|
||||||
Text(""),
|
|
||||||
COLOR_TEXT,
|
|
||||||
TextSize(150),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text qui dit si ton nombre et trop grand ou trop petit
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 500),
|
|
||||||
Order(3),
|
|
||||||
Centered(),
|
|
||||||
TextDialogue(),
|
|
||||||
TextSize(150),
|
|
||||||
COLOR_TEXT,
|
|
||||||
Text("Devine le nombre..."),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text qui affiche le nombre d'essai
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2 - 100, 150),
|
|
||||||
Order(4),
|
|
||||||
TextSize(100),
|
|
||||||
NombreEssaiText(),
|
|
||||||
COLOR_TEXT,
|
|
||||||
Text("il reste : 7 essais"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton pour revenir au menu
|
|
||||||
world.create_entity(
|
|
||||||
Order(11),
|
|
||||||
Position(150, 150),
|
|
||||||
Texture("classique/arrow.png"),
|
|
||||||
Clickable(on_menu_button),
|
|
||||||
HoveredTexture("classique/arrow_hover.png"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Les ressources.
|
|
||||||
world[NombreEssai] = NombreEssai(7)
|
|
||||||
world[RandomNumber] = RandomNumber(random.randint(0, 99))
|
|
||||||
world[IsRunning] = IsRunning()
|
|
||||||
|
|
||||||
|
|
||||||
def on_menu_button(world: World, entity: Entity):
|
|
||||||
"""
|
|
||||||
Fonction qui s'execute quand on clique sur un bouton.
|
|
||||||
"""
|
|
||||||
world[Game].change_scene("menu")
|
|
||||||
entity[Sound] = Sound("click")
|
|
||||||
|
|
||||||
|
|
||||||
def _update(world: World):
|
|
||||||
"""
|
|
||||||
Verifie si le nombre donné est le meme que celui que l'on a choisi.
|
|
||||||
Boucle du jeu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
world.create_entity(Sound("menu_click.wav"))
|
|
||||||
|
|
||||||
# si le jeu s'est arrete.
|
|
||||||
if IsRunning not in world:
|
|
||||||
# on relance le jeu.
|
|
||||||
world[Game].change_scene("classique")
|
|
||||||
|
|
||||||
for entity in world.query(typing.Typing, Text):
|
|
||||||
# One efface le nombre.
|
|
||||||
number: str = entity[Text]
|
|
||||||
entity[Text] = Text("")
|
|
||||||
|
|
||||||
# On gere le l'input de l'utilisateur.
|
|
||||||
for entity_text in world.query(TextDialogue):
|
|
||||||
if number == "": # si il a rien evoyé.
|
|
||||||
entity_text[Text] = Text("tu doit entrer un nombre !")
|
|
||||||
return
|
|
||||||
if world[RandomNumber] == int(number): # si il a trouve le nombre.
|
|
||||||
end_game(world, "Gagné")
|
|
||||||
return
|
|
||||||
elif world[NombreEssai] <= 1: # si il n'a plus d'essai.
|
|
||||||
end_game(world, "Perdu")
|
|
||||||
return
|
|
||||||
elif world[RandomNumber] > int(number): # si le nombre est trop petit.
|
|
||||||
entity_text[Text] = Text("Plus grand...")
|
|
||||||
else: # si le nombre est trop grand.
|
|
||||||
entity_text[Text] = Text("Plus petit...")
|
|
||||||
|
|
||||||
# on update l'affichage du nombre d'essai.
|
|
||||||
world[NombreEssai] = NombreEssai(world[NombreEssai] - 1)
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text(
|
|
||||||
f"il reste : {world[NombreEssai]} essai{'s' if world[NombreEssai] != 1 else ''}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def end_game(world: World, state: str):
|
|
||||||
"""
|
|
||||||
fonction applé quand le jeu est fini.
|
|
||||||
"""
|
|
||||||
del world[IsRunning] # le jeu est fini.
|
|
||||||
|
|
||||||
# On joue le son
|
|
||||||
if state == "Gagné":
|
|
||||||
world.create_entity(Sound("win_sound.wav"))
|
|
||||||
else:
|
|
||||||
world.create_entity(Sound("lose_sound.wav"))
|
|
||||||
|
|
||||||
# On affiche le message de fin.
|
|
||||||
for entity_text in world.query(TextDialogue):
|
|
||||||
entity_text[Text] = Text(f"{state} !")
|
|
||||||
|
|
||||||
# On empeche de pourvoir continuer le jeu.
|
|
||||||
for entity in world.query(typing.Typing, Text):
|
|
||||||
del entity[typing.Typing]
|
|
||||||
|
|
||||||
if state == "Gagné":
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text("")
|
|
||||||
else:
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text(" plus d'essais")
|
|
||||||
|
|
||||||
# on change la texture du button submit.
|
|
||||||
for entity in world.query(Clickable, Centered):
|
|
||||||
entity[Texture] = Texture("classique/play_again.png")
|
|
||||||
entity[HoveredTexture] = HoveredTexture("classique/play_again_hover.png")
|
|
||||||
|
|
||||||
|
|
||||||
def _check_return(world: World):
|
|
||||||
"""
|
|
||||||
Verifie si la touche entrée est appuyée.
|
|
||||||
"""
|
|
||||||
keyboard = world[Keyboard]
|
|
||||||
if keyboard.is_key_pressed("return") or keyboard.is_key_pressed("enter"):
|
|
||||||
_update(world)
|
|
||||||
|
|
||||||
|
|
||||||
SCENE = (
|
|
||||||
Scene(
|
|
||||||
[__initialize_world],
|
|
||||||
[_check_return],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
+ typing.PLUGIN
|
|
||||||
)
|
|
|
@ -1,378 +0,0 @@
|
||||||
"""
|
|
||||||
Scène du jeu dans lequel on se cache de Edmond dans les dossiers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
import random
|
|
||||||
from engine import (
|
|
||||||
Animation,
|
|
||||||
Centered,
|
|
||||||
Delta,
|
|
||||||
Display,
|
|
||||||
Entity,
|
|
||||||
Hovered,
|
|
||||||
Mouse,
|
|
||||||
Order,
|
|
||||||
Position,
|
|
||||||
Scene,
|
|
||||||
Sound,
|
|
||||||
Text,
|
|
||||||
TextSize,
|
|
||||||
Texture,
|
|
||||||
Vec2,
|
|
||||||
World,
|
|
||||||
)
|
|
||||||
from plugins import smooth
|
|
||||||
|
|
||||||
|
|
||||||
LINES = 3
|
|
||||||
COLUMNS = 5
|
|
||||||
SPACING = 200
|
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
|
||||||
"""
|
|
||||||
Etat de la scène.
|
|
||||||
"""
|
|
||||||
|
|
||||||
MOVING = 0
|
|
||||||
SEARCHING = 1
|
|
||||||
GAME_OVER = 2
|
|
||||||
|
|
||||||
|
|
||||||
class SelectedDirectory:
|
|
||||||
"""
|
|
||||||
Une ressource qui stoque le dossier selectionné pour le déplacement.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, entity: Entity, start_position: Vec2):
|
|
||||||
self.entity = entity
|
|
||||||
self.position = start_position
|
|
||||||
|
|
||||||
|
|
||||||
class AttackTimer(float):
|
|
||||||
"""
|
|
||||||
Ressource qui stoque un timer pour l'attaque.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class AttackSpeed(float):
|
|
||||||
"""
|
|
||||||
Ressource qui dit le temps de l'attaque.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoryPosition:
|
|
||||||
"""
|
|
||||||
La position d'un dossier dans la grille.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, x: int, y: int):
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
|
|
||||||
def __eq__(self, value: object) -> bool:
|
|
||||||
if isinstance(value, DirectoryPosition):
|
|
||||||
return self.x == value.x and self.y == value.y
|
|
||||||
return False
|
|
||||||
|
|
||||||
def screen_position(self) -> Vec2:
|
|
||||||
"""
|
|
||||||
Calcule la position de l'entité sur l'ecran.
|
|
||||||
"""
|
|
||||||
size = Vec2(SPACING)
|
|
||||||
offset = -(size * Vec2(COLUMNS - 1, LINES - 1) / 2)
|
|
||||||
first_position = Vec2(Display.WIDTH / 2, Display.HEIGHT / 2) + offset
|
|
||||||
return first_position + Vec2(self.x, self.y) * size
|
|
||||||
|
|
||||||
|
|
||||||
class AttackPoint(DirectoryPosition):
|
|
||||||
"""
|
|
||||||
Composant qui marque un point d'attaque.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoryName:
|
|
||||||
"""
|
|
||||||
Composant qui marque une entité comme étant le nom d'un dossier.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, entity: Entity):
|
|
||||||
self.entity = entity
|
|
||||||
|
|
||||||
|
|
||||||
class UserDirectory:
|
|
||||||
"""
|
|
||||||
Composant qui marque le dossier que l'utilisateur doit protéger.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class GameStarted:
|
|
||||||
"""
|
|
||||||
Une ressource qui permet de savoir que le jeu commence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def __change_folders_speeds(world: World, _e: Entity):
|
|
||||||
"""
|
|
||||||
Change les vitesses des dossiers.
|
|
||||||
"""
|
|
||||||
for entity in world.query(DirectoryPosition, smooth.Speed):
|
|
||||||
entity[smooth.Speed] = smooth.Speed(random.uniform(2.0, 2.5))
|
|
||||||
for entity in world.query(TextSize):
|
|
||||||
entity[TextSize] = TextSize(40)
|
|
||||||
|
|
||||||
|
|
||||||
def __remove_folders_speeds(world: World):
|
|
||||||
"""
|
|
||||||
Supprime les vitesses des dossiers.
|
|
||||||
"""
|
|
||||||
if GameStarted not in world and world[AttackTimer] >= 3.0:
|
|
||||||
for entity in world.query(DirectoryPosition, smooth.Speed):
|
|
||||||
del entity[smooth.Speed]
|
|
||||||
world[GameStarted] = GameStarted()
|
|
||||||
|
|
||||||
|
|
||||||
def __initialize_world(world: World):
|
|
||||||
"""
|
|
||||||
Initialise le monde de la scène.
|
|
||||||
"""
|
|
||||||
world[State] = State.MOVING
|
|
||||||
world[AttackTimer] = AttackTimer(0.0)
|
|
||||||
world[AttackSpeed] = AttackSpeed(5.0)
|
|
||||||
|
|
||||||
world.create_entity(
|
|
||||||
Position(),
|
|
||||||
Order(0),
|
|
||||||
Animation("fade_desktop", __change_folders_speeds),
|
|
||||||
)
|
|
||||||
|
|
||||||
names = [
|
|
||||||
"Classique",
|
|
||||||
"Menteur",
|
|
||||||
"Tricheur",
|
|
||||||
"Histoire",
|
|
||||||
"Je t'aime",
|
|
||||||
"Hello",
|
|
||||||
"Cheval",
|
|
||||||
"Defender",
|
|
||||||
"Dansons",
|
|
||||||
"Secrets",
|
|
||||||
"Edmond",
|
|
||||||
"Mon Amour",
|
|
||||||
"Melatonin",
|
|
||||||
"Films",
|
|
||||||
"Cinéma",
|
|
||||||
]
|
|
||||||
|
|
||||||
positions = [
|
|
||||||
Position(40 + (7 * 180) + 48, 35 + (5 * 166) + 38),
|
|
||||||
Position(40 + (5 * 180) + 48, 35 + (5 * 166) + 38),
|
|
||||||
Position(40 + (4 * 180) + 48, 35 + (4 * 166) + 38),
|
|
||||||
Position(40 + (3 * 180) + 48, 35 + (5 * 166) + 38),
|
|
||||||
Position(40 + (1 * 180) + 48, 35 + (5 * 166) + 38),
|
|
||||||
Position(40 + (6 * 180) + 48, 35 + (2 * 166) + 38),
|
|
||||||
Position(40 + (5 * 180) + 48, 35 + (3 * 166) + 38),
|
|
||||||
Position(40 + (4 * 180) + 48, 35 + (2 * 166) + 38),
|
|
||||||
Position(40 + (2 * 180) + 48, 35 + (4 * 166) + 38),
|
|
||||||
Position(40 + (1 * 180) + 48, 35 + (2 * 166) + 38),
|
|
||||||
Position(40 + (7 * 180) + 48, 35 + (1 * 166) + 38),
|
|
||||||
Position(40 + (5 * 180) + 48, 35 + (1 * 166) + 38),
|
|
||||||
Position(40 + (3 * 180) + 48, 35 + (0 * 166) + 38),
|
|
||||||
Position(40 + (2 * 180) + 48, 35 + (1 * 166) + 38),
|
|
||||||
Position(40 + (0 * 180) + 48, 35 + (1 * 166) + 38),
|
|
||||||
]
|
|
||||||
|
|
||||||
for y in range(LINES):
|
|
||||||
for x in range(COLUMNS):
|
|
||||||
position = DirectoryPosition(x, y)
|
|
||||||
entity = world.create_entity(
|
|
||||||
positions.pop(),
|
|
||||||
smooth.Speed(0),
|
|
||||||
Order(1),
|
|
||||||
Centered(),
|
|
||||||
Texture("directory.png"),
|
|
||||||
position,
|
|
||||||
)
|
|
||||||
|
|
||||||
if x == 2 and y == 1:
|
|
||||||
entity[UserDirectory] = UserDirectory()
|
|
||||||
entity[Texture] = Texture("user_directory.png")
|
|
||||||
|
|
||||||
world.create_entity(
|
|
||||||
Position(0, 0),
|
|
||||||
Order(1),
|
|
||||||
Centered(),
|
|
||||||
Text(names.pop()),
|
|
||||||
TextSize(0),
|
|
||||||
DirectoryName(entity),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __attacks(world: World):
|
|
||||||
"""
|
|
||||||
Déclenche les attaques de Edmond.
|
|
||||||
"""
|
|
||||||
if world[State] == State.GAME_OVER:
|
|
||||||
return
|
|
||||||
world[AttackTimer] = AttackTimer(world[AttackTimer] + world[Delta])
|
|
||||||
timer = world[AttackTimer]
|
|
||||||
if timer >= world[AttackSpeed] and world[State] == State.MOVING:
|
|
||||||
world[State] = State.SEARCHING
|
|
||||||
for entity in world.query(AttackPoint):
|
|
||||||
position = entity[AttackPoint]
|
|
||||||
for directory_entity in world.query(DirectoryPosition):
|
|
||||||
if directory_entity[DirectoryPosition] == position:
|
|
||||||
if UserDirectory in directory_entity:
|
|
||||||
directory_entity[Animation] = Animation(
|
|
||||||
"search_directory_failed"
|
|
||||||
)
|
|
||||||
world[State] = State.GAME_OVER
|
|
||||||
else:
|
|
||||||
directory_entity[Animation] = Animation("search_directory")
|
|
||||||
del entity[AttackPoint]
|
|
||||||
del entity[Position]
|
|
||||||
del entity[Order]
|
|
||||||
del entity[Centered]
|
|
||||||
del entity[Texture]
|
|
||||||
elif timer >= world[AttackSpeed] + 4.5 and world[State] == State.SEARCHING:
|
|
||||||
world[State] = State.MOVING
|
|
||||||
for _ in range(10):
|
|
||||||
position = AttackPoint(
|
|
||||||
random.randint(0, COLUMNS - 1),
|
|
||||||
random.randint(0, LINES - 1),
|
|
||||||
)
|
|
||||||
world.create_entity(
|
|
||||||
position,
|
|
||||||
Position(position.screen_position()),
|
|
||||||
Order(50),
|
|
||||||
Centered(),
|
|
||||||
Texture("attack_point.png"),
|
|
||||||
)
|
|
||||||
world[AttackTimer] = AttackTimer(0.0)
|
|
||||||
world[AttackSpeed] = AttackSpeed(world[AttackSpeed] * 0.9)
|
|
||||||
|
|
||||||
|
|
||||||
def __move_directories(world: World):
|
|
||||||
"""
|
|
||||||
Permet de déplacer les dossiers avec la souris.
|
|
||||||
"""
|
|
||||||
# Si on n'est pas dans le bon state on annule
|
|
||||||
if GameStarted not in world or world[State] == State.GAME_OVER:
|
|
||||||
return
|
|
||||||
|
|
||||||
# On met à jour la séléction
|
|
||||||
mouse = world[Mouse]
|
|
||||||
for entity in world.query(Hovered, DirectoryPosition):
|
|
||||||
if Animation in entity:
|
|
||||||
continue
|
|
||||||
if mouse.is_button_pressed(1):
|
|
||||||
world[SelectedDirectory] = SelectedDirectory(entity, Vec2(mouse.position))
|
|
||||||
break
|
|
||||||
|
|
||||||
# Si un dossier est séléctionné
|
|
||||||
if SelectedDirectory in world:
|
|
||||||
selected_directory = world[SelectedDirectory]
|
|
||||||
selected_entity = selected_directory.entity
|
|
||||||
directory_position = selected_entity[DirectoryPosition]
|
|
||||||
|
|
||||||
# Vérification du relachement de la souris
|
|
||||||
if not mouse.is_button(1):
|
|
||||||
del world[SelectedDirectory]
|
|
||||||
return
|
|
||||||
|
|
||||||
# On calcule le déplacement de la souris
|
|
||||||
mouse_delta = mouse.position - selected_directory.position
|
|
||||||
|
|
||||||
# On annule si il y a pas eu de déplacement significatif de la souris
|
|
||||||
if mouse_delta.length < 40:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Récupération du mouvement voulu
|
|
||||||
if abs(mouse_delta.x) >= abs(mouse_delta.y):
|
|
||||||
movement = (int(mouse_delta.x / abs(mouse_delta.x)), 0)
|
|
||||||
else:
|
|
||||||
movement = (0, int(mouse_delta.y / abs(mouse_delta.y)))
|
|
||||||
|
|
||||||
# Récupération des mouvements possible du dossier
|
|
||||||
movements: list[tuple[int, int]] = []
|
|
||||||
if directory_position.x != 0:
|
|
||||||
movements.append((-1, 0))
|
|
||||||
if directory_position.x != COLUMNS - 1:
|
|
||||||
movements.append((1, 0))
|
|
||||||
if directory_position.y != 0:
|
|
||||||
movements.append((0, -1))
|
|
||||||
if directory_position.y != LINES - 1:
|
|
||||||
movements.append((0, 1))
|
|
||||||
if len(movements) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Si le mouvement n'est pas possible, on annule
|
|
||||||
if movement not in movements:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Si l'entité est animé on annule
|
|
||||||
if Animation in selected_entity:
|
|
||||||
return
|
|
||||||
|
|
||||||
# On trouve l'autre dossier
|
|
||||||
for entity in world.query(DirectoryPosition, without=(Animation,)):
|
|
||||||
if entity != selected_entity and entity[
|
|
||||||
DirectoryPosition
|
|
||||||
] == DirectoryPosition(
|
|
||||||
directory_position.x + movement[0],
|
|
||||||
directory_position.y + movement[1],
|
|
||||||
):
|
|
||||||
other_directory = entity
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
# On actualise la position de l'autre dossier
|
|
||||||
other_directory[DirectoryPosition].x -= movement[0]
|
|
||||||
other_directory[DirectoryPosition].y -= movement[1]
|
|
||||||
|
|
||||||
# On actualise la position du dossier
|
|
||||||
selected_entity[DirectoryPosition].x += movement[0]
|
|
||||||
selected_entity[DirectoryPosition].y += movement[1]
|
|
||||||
|
|
||||||
# On joue un son
|
|
||||||
world.create_entity(Sound("slide.wav"))
|
|
||||||
|
|
||||||
# On retire le dossier selectionné
|
|
||||||
del world[SelectedDirectory]
|
|
||||||
|
|
||||||
|
|
||||||
def __update_positions(world: World):
|
|
||||||
"""
|
|
||||||
Met à jour la position cible des dossiers.
|
|
||||||
"""
|
|
||||||
for entity in world.query(DirectoryPosition):
|
|
||||||
position = entity[DirectoryPosition]
|
|
||||||
entity[smooth.Target] = smooth.Target(position.screen_position())
|
|
||||||
entity[Order] = Order(position.y + 1)
|
|
||||||
|
|
||||||
|
|
||||||
def __update_directory_names(world: World):
|
|
||||||
"""
|
|
||||||
Met à jour la position des noms des dossiers.
|
|
||||||
"""
|
|
||||||
for entity in world.query(DirectoryName):
|
|
||||||
directory_entity = entity[DirectoryName].entity
|
|
||||||
entity[Position] = Position(directory_entity[Position] + Vec2(0, 75))
|
|
||||||
entity[Order] = directory_entity[Order]
|
|
||||||
|
|
||||||
|
|
||||||
SCENE = (
|
|
||||||
Scene(
|
|
||||||
[__initialize_world],
|
|
||||||
[__attacks, __move_directories, __update_positions, __remove_folders_speeds],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
+ smooth.PLUGIN
|
|
||||||
+ Scene(
|
|
||||||
[],
|
|
||||||
[__update_directory_names],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
)
|
|
|
@ -1,236 +0,0 @@
|
||||||
"""
|
|
||||||
Définis la scène du jeu menteur, sans variante.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from random import randint
|
|
||||||
from plugins import typing
|
|
||||||
from engine import (
|
|
||||||
Centered,
|
|
||||||
Clickable,
|
|
||||||
Color,
|
|
||||||
Display,
|
|
||||||
Entity,
|
|
||||||
Game,
|
|
||||||
HoveredTexture,
|
|
||||||
Keyboard,
|
|
||||||
Order,
|
|
||||||
Position,
|
|
||||||
Scene,
|
|
||||||
Sound,
|
|
||||||
Text,
|
|
||||||
TextSize,
|
|
||||||
Texture,
|
|
||||||
World,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RandomNumber(int):
|
|
||||||
"""
|
|
||||||
La ressource qui est le nombre a deviner.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TextDialogue:
|
|
||||||
"""
|
|
||||||
Le component qui declare l'entitee Text qui affiche le plus petit ou le plus grand
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NombreEssai(int):
|
|
||||||
"""
|
|
||||||
Le component qui declare le nombre d'essai
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NombreEssaiText:
|
|
||||||
"""
|
|
||||||
Le component qui affiche le nombre d'essai
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IsRunning:
|
|
||||||
"""
|
|
||||||
Le component qui indique si le jeu est en cours
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
COLOR_TEXT = Color(59, 162, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def __initialize_world(world: World):
|
|
||||||
"""
|
|
||||||
Initialise le monde du menu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Fond d'ecran
|
|
||||||
world.create_entity(
|
|
||||||
Position(),
|
|
||||||
Order(0),
|
|
||||||
Texture("menteur/background.png"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton valider/rejouer
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 880),
|
|
||||||
Order(1),
|
|
||||||
Centered(),
|
|
||||||
Texture("menteur/valider.png"),
|
|
||||||
HoveredTexture("menteur/valider_hover.png"),
|
|
||||||
Clickable(lambda world, _: _update(world)),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Zone de saisie
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 750),
|
|
||||||
Order(2),
|
|
||||||
Centered(),
|
|
||||||
typing.Typing("1234567890"),
|
|
||||||
Text(""),
|
|
||||||
COLOR_TEXT,
|
|
||||||
TextSize(150),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text qui dit si ton nombre et trop grand ou trop petit
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 500),
|
|
||||||
Order(3),
|
|
||||||
Centered(),
|
|
||||||
TextDialogue(),
|
|
||||||
TextSize(150),
|
|
||||||
COLOR_TEXT,
|
|
||||||
Text("Devine le nombre..."),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text qui affiche le nombre d'essai
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH - 750, 120),
|
|
||||||
Order(4),
|
|
||||||
TextSize(100),
|
|
||||||
NombreEssaiText(),
|
|
||||||
COLOR_TEXT,
|
|
||||||
Text("il reste : 15 essais"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Bouton pour revenir au menu
|
|
||||||
world.create_entity(
|
|
||||||
Order(11),
|
|
||||||
Position(100, 100),
|
|
||||||
Texture("menteur/arrow.png"),
|
|
||||||
Clickable(on_menu_button),
|
|
||||||
HoveredTexture("menteur/arrow_hover.png"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Les ressources.
|
|
||||||
world[NombreEssai] = NombreEssai(15)
|
|
||||||
world[RandomNumber] = RandomNumber(randint(0, 99))
|
|
||||||
world[IsRunning] = IsRunning()
|
|
||||||
|
|
||||||
|
|
||||||
def on_menu_button(world: World, entity: Entity):
|
|
||||||
"""
|
|
||||||
Fonction qui s'execute quand on clique sur un bouton.
|
|
||||||
"""
|
|
||||||
world[Game].change_scene("menu")
|
|
||||||
entity[Sound] = Sound("click")
|
|
||||||
|
|
||||||
|
|
||||||
def _update(world: World):
|
|
||||||
"""
|
|
||||||
Verifie si le nombre donné est le meme que celui que l'on a choisi.
|
|
||||||
Boucle du jeu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
world.create_entity(Sound("menu_click.wav"))
|
|
||||||
|
|
||||||
# si le jeu s'est arrete.
|
|
||||||
if IsRunning not in world:
|
|
||||||
# on relance le jeu.
|
|
||||||
world[Game].change_scene("menteur")
|
|
||||||
|
|
||||||
for entity in world.query(typing.Typing, Text):
|
|
||||||
# One efface le nombre.
|
|
||||||
number: str = entity[Text]
|
|
||||||
entity[Text] = Text("")
|
|
||||||
|
|
||||||
# On gere le l'input de l'utilisateur.
|
|
||||||
for entity_text in world.query(TextDialogue):
|
|
||||||
if number == "": # si il a rien evoyé.
|
|
||||||
entity_text[Text] = Text("tu doit entrer un nombre !")
|
|
||||||
return
|
|
||||||
if world[RandomNumber] == int(number): # si il a trouve le nombre.
|
|
||||||
end_game(world, "Gagné")
|
|
||||||
return
|
|
||||||
elif world[NombreEssai] <= 1: # si il n'a plus d'essai.
|
|
||||||
end_game(world, "Perdu")
|
|
||||||
return
|
|
||||||
elif world[RandomNumber] > int(number): # si le nombre est trop petit.
|
|
||||||
lie = randint(1, 4)
|
|
||||||
if lie == 4:
|
|
||||||
entity_text[Text] = Text("Plus petit...")
|
|
||||||
else:
|
|
||||||
entity_text[Text] = Text("Plus grand...")
|
|
||||||
else: # si le nombre est trop grand.
|
|
||||||
lie = randint(1, 4)
|
|
||||||
if lie == 4:
|
|
||||||
entity_text[Text] = Text("Plus grand...")
|
|
||||||
else:
|
|
||||||
entity_text[Text] = Text("Plus petit...")
|
|
||||||
|
|
||||||
# on update l'affichage du nombre d'essai.
|
|
||||||
world[NombreEssai] = NombreEssai(world[NombreEssai] - 1)
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text(
|
|
||||||
f"il reste : {world[NombreEssai]} essai{'s' if world[NombreEssai] != 1 else ''}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def end_game(world: World, state: str):
|
|
||||||
"""
|
|
||||||
fonction applé quand le jeu est fini.
|
|
||||||
"""
|
|
||||||
del world[IsRunning] # le jeu est fini.
|
|
||||||
|
|
||||||
# On joue le son
|
|
||||||
if state == "Gagné":
|
|
||||||
world.create_entity(Sound("win_sound.wav"))
|
|
||||||
else:
|
|
||||||
world.create_entity(Sound("lose_sound.wav"))
|
|
||||||
|
|
||||||
# On affiche le message de fin.
|
|
||||||
for entity_text in world.query(TextDialogue):
|
|
||||||
entity_text[Text] = Text(f"{state} !")
|
|
||||||
|
|
||||||
# On empeche de pourvoir continuer le jeu.
|
|
||||||
for entity in world.query(typing.Typing, Text):
|
|
||||||
del entity[typing.Typing]
|
|
||||||
|
|
||||||
if state == "Gagné":
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text("")
|
|
||||||
else:
|
|
||||||
for entity in world.query(NombreEssaiText):
|
|
||||||
entity[Text] = Text(" plus d'essais")
|
|
||||||
|
|
||||||
# on change la texture du button submit.
|
|
||||||
for entity in world.query(Clickable, Centered):
|
|
||||||
entity[Texture] = Texture("menteur/play_again.png")
|
|
||||||
entity[HoveredTexture] = HoveredTexture("menteur/play_again_hover.png")
|
|
||||||
|
|
||||||
|
|
||||||
def _check_return(world: World):
|
|
||||||
"""
|
|
||||||
Verifie si la touche entrée est appuyée.
|
|
||||||
"""
|
|
||||||
keyboard = world[Keyboard]
|
|
||||||
if keyboard.is_key_pressed("return") or keyboard.is_key_pressed("enter"):
|
|
||||||
_update(world)
|
|
||||||
|
|
||||||
|
|
||||||
SCENE = (
|
|
||||||
Scene(
|
|
||||||
[__initialize_world],
|
|
||||||
[_check_return],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
+ typing.PLUGIN
|
|
||||||
)
|
|
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
Définis la scène du menu du jeu.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from engine import (
|
|
||||||
Centered,
|
|
||||||
Clickable,
|
|
||||||
Display,
|
|
||||||
Entity,
|
|
||||||
Game,
|
|
||||||
HoveredTexture,
|
|
||||||
Order,
|
|
||||||
Position,
|
|
||||||
Scene,
|
|
||||||
Sound,
|
|
||||||
Texture,
|
|
||||||
World,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __create_button(world: World, i: int, name: str):
|
|
||||||
"""
|
|
||||||
Ajoute un bouton au monde.
|
|
||||||
"""
|
|
||||||
world.create_entity(
|
|
||||||
Position(Display.WIDTH / 2, 450 + 150 * i),
|
|
||||||
Order(1),
|
|
||||||
Centered(),
|
|
||||||
Texture(f"menu/button_{name}.png"),
|
|
||||||
HoveredTexture(f"menu/button_{name}_hover.png"),
|
|
||||||
Clickable(lambda world, entity: on_click_butons(world, entity, name)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def on_click_butons(world: World, entity: Entity, name: str):
|
|
||||||
"""
|
|
||||||
Fonction qui s'execute quand on clique sur un bouton.
|
|
||||||
"""
|
|
||||||
entity[Sound] = Sound("click")
|
|
||||||
world[Game].change_scene(name)
|
|
||||||
|
|
||||||
|
|
||||||
def __initialize_world(world: World):
|
|
||||||
"""
|
|
||||||
Initialise le monde du menu.
|
|
||||||
"""
|
|
||||||
world.create_entity(Position(), Order(0), Texture("menu/background.png"))
|
|
||||||
scenes_name = ["classique", "menteur", "tricheur", "histoire"]
|
|
||||||
for i, name in enumerate(scenes_name):
|
|
||||||
__create_button(world, i, name)
|
|
||||||
|
|
||||||
|
|
||||||
SCENE = Scene(
|
|
||||||
[__initialize_world],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
)
|
|
Loading…
Reference in a new issue