Save unfinished work
This commit is contained in:
parent
cd81b0c34a
commit
d994449f1e
|
@ -6,6 +6,7 @@ Pour moddifier le monde, on n'agis que sur les composants.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import copy
|
||||||
from typing import Iterator, Optional, Sequence
|
from typing import Iterator, Optional, Sequence
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +124,22 @@ class World(Entity):
|
||||||
self.__entities: dict[type[object], set[int]] = {}
|
self.__entities: dict[type[object], set[int]] = {}
|
||||||
self.__next_id: int = 1
|
self.__next_id: int = 1
|
||||||
|
|
||||||
|
def partial_copy(
|
||||||
|
self, *needed: type[object], ressources: Sequence[type[object]] = ()
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Renvoie une copie du monde avec seulement les composants demandé.
|
||||||
|
"""
|
||||||
|
new_world = World()
|
||||||
|
for need in needed:
|
||||||
|
for entity in self.query(need):
|
||||||
|
new_entity = Entity(new_world, entity.identifier)
|
||||||
|
new_entity.set(copy.deepcopy(self.get_component(entity, need)))
|
||||||
|
for ressource in ressources:
|
||||||
|
if ressource in self:
|
||||||
|
new_world.set(copy.deepcopy(self[ressource]))
|
||||||
|
return new_world
|
||||||
|
|
||||||
def new_entity(self) -> "Entity":
|
def new_entity(self) -> "Entity":
|
||||||
"""
|
"""
|
||||||
Créer une nouvelle entité dans le monde et la renvoie.
|
Créer une nouvelle entité dans le monde et la renvoie.
|
||||||
|
|
|
@ -147,6 +147,7 @@ def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
|
||||||
others = [
|
others = [
|
||||||
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
|
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
|
||||||
]
|
]
|
||||||
|
distance = 0.0
|
||||||
counter = 0
|
counter = 0
|
||||||
while movement.length > 0.0001 and counter < 50:
|
while movement.length > 0.0001 and counter < 50:
|
||||||
t, normal, obstacle = aabb_to_aabbs(aabb, others, movement)
|
t, normal, obstacle = aabb_to_aabbs(aabb, others, movement)
|
||||||
|
@ -154,6 +155,7 @@ def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
|
||||||
step = movement
|
step = movement
|
||||||
else:
|
else:
|
||||||
step = movement * max(t - 0.000001, 0)
|
step = movement * max(t - 0.000001, 0)
|
||||||
|
distance += step.length
|
||||||
aabb.move(step)
|
aabb.move(step)
|
||||||
aabb.entity_position(entity)
|
aabb.entity_position(entity)
|
||||||
movement -= step
|
movement -= step
|
||||||
|
@ -163,18 +165,42 @@ def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
|
||||||
if normal.y != 0:
|
if normal.y != 0:
|
||||||
movement.y *= -1
|
movement.y *= -1
|
||||||
entity[Velocity].y *= -1
|
entity[Velocity].y *= -1
|
||||||
movement /= entity[Velocity]
|
movement /= entity[Velocity].length
|
||||||
|
stop = False
|
||||||
if obstacle is not None and not disable_callback:
|
if obstacle is not None and not disable_callback:
|
||||||
if not entity.get(
|
if not entity.get(
|
||||||
CollisionHandler, CollisionHandler(lambda e, o: True)
|
CollisionHandler, CollisionHandler(lambda e, o: True)
|
||||||
).callback(entity, obstacle):
|
).callback(entity, obstacle):
|
||||||
break
|
stop = True
|
||||||
if not obstacle.get(
|
if not obstacle.get(
|
||||||
CollisionHandler, CollisionHandler(lambda e, o: True)
|
CollisionHandler, CollisionHandler(lambda e, o: True)
|
||||||
).callback(obstacle, entity):
|
).callback(obstacle, entity):
|
||||||
break
|
stop = True
|
||||||
movement *= entity[Velocity]
|
movement *= entity.get(Velocity, Velocity(0)).length
|
||||||
counter += 1
|
counter += 1
|
||||||
|
if stop:
|
||||||
|
break
|
||||||
|
return distance
|
||||||
|
|
||||||
|
|
||||||
|
def simulate(entity: Entity, handler: Callable[[Entity], bool]) -> float:
|
||||||
|
"""
|
||||||
|
Simule une entitée jusqu'a ce que le handler retourne True.
|
||||||
|
"""
|
||||||
|
base_handler = entity.get(
|
||||||
|
CollisionHandler, CollisionHandler(lambda e, o: True)
|
||||||
|
).callback
|
||||||
|
|
||||||
|
def __handler(a: Entity, b: Entity):
|
||||||
|
if not base_handler(a, b):
|
||||||
|
return False
|
||||||
|
return not handler(b)
|
||||||
|
|
||||||
|
entity.set(CollisionHandler(__handler))
|
||||||
|
velocity = entity[Velocity].length
|
||||||
|
distance = move_entity(entity, entity[Velocity] * 1000) / velocity
|
||||||
|
entity.set(CollisionHandler(base_handler))
|
||||||
|
return distance
|
||||||
|
|
||||||
|
|
||||||
def __apply_velocity(world: World):
|
def __apply_velocity(world: World):
|
||||||
|
|
|
@ -2,106 +2,146 @@
|
||||||
Le jeux principale.
|
Le jeux principale.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from enum import Enum
|
import random
|
||||||
from engine import Plugin, Scene
|
from typing import Callable
|
||||||
|
from engine import CurrentScene, Plugin, Scene
|
||||||
from engine.ecs import Entity, World
|
from engine.ecs import Entity, World
|
||||||
from engine.math import Vec2
|
from engine.math import Vec2
|
||||||
from plugins import render
|
from plugins import render
|
||||||
from plugins import physics
|
from plugins import physics
|
||||||
from plugins.inputs import Held
|
from plugins.inputs import Held
|
||||||
|
from plugins.physics import CollisionHandler, Solid, Velocity
|
||||||
from plugins.render import (
|
from plugins.render import (
|
||||||
Origin,
|
Origin,
|
||||||
Position,
|
Position,
|
||||||
Scale,
|
Scale,
|
||||||
SpriteBundle,
|
SpriteBundle,
|
||||||
TextBundle,
|
|
||||||
Text,
|
Text,
|
||||||
|
TextBundle,
|
||||||
TextSize,
|
TextSize,
|
||||||
)
|
)
|
||||||
from plugins.timing import Delta, Time
|
from plugins.timing import Delta, Time
|
||||||
import random
|
|
||||||
from plugins.physics import CollisionHandler, Solid, Velocity
|
|
||||||
|
|
||||||
|
|
||||||
class GameMode(Enum):
|
PLAYER_WALL_DISTANCE = 150
|
||||||
|
|
||||||
|
|
||||||
|
class Score(int):
|
||||||
"""
|
"""
|
||||||
Ressource qui definit le game mode choisi par l'utilisateur.
|
Ressource correspondant au score du joueur dans la partie.
|
||||||
"""
|
|
||||||
|
|
||||||
ONE = 0
|
|
||||||
TWO = 1
|
|
||||||
|
|
||||||
|
|
||||||
class RightWall:
|
|
||||||
"""
|
|
||||||
Composant qui marque une entité comme etant le mur de droite.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class LeftWall:
|
class Wall:
|
||||||
"""
|
"""
|
||||||
Composant qui marque une entité comme etant le mur de gauche.
|
Composant marquant une entitée comme etant une mur.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Player1:
|
class WinWall:
|
||||||
"""
|
"""
|
||||||
Composant qui marque une entité comme etant le joeuur 1.
|
Composant marquant une entitée comme etant un mur permettant de gagner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_my_side(wall: Entity, player: Entity) -> bool:
|
||||||
|
return abs(player[Position].x - wall[Position].x) <= PLAYER_WALL_DISTANCE + 1
|
||||||
|
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
"""
|
||||||
|
Composant marquant une entité comme étant un joueur.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Player2:
|
class PlayerScore(int):
|
||||||
"""
|
"""
|
||||||
Composant qui marque une entité comme etant le joeuur 2.
|
Composant stockant le score d'un joueur.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerSpeed(float):
|
||||||
|
"""
|
||||||
|
Composant donnant la vitesse de deplacement d'un joueur.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerTarget(float):
|
||||||
|
"""
|
||||||
|
Composant donnant l'objectif de déplacement d'un joueur.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class AI:
|
||||||
|
"""
|
||||||
|
Composant définissant le comportement d'un joueur.
|
||||||
|
|
||||||
|
A chaques mises à jour, le callback logic sera appelé pour calculer
|
||||||
|
la position y cible du joueur dans un monde virtuel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, logic: Callable[[World, Entity], float]):
|
||||||
|
self.logic = logic
|
||||||
|
|
||||||
|
|
||||||
class Ball:
|
class Ball:
|
||||||
"""
|
"""
|
||||||
Composant qui marque une entité comme etant une balle.
|
Composant marquant une entité comme étant une balle.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handler(ball: Entity, other: Entity):
|
||||||
|
"""
|
||||||
|
Gère les collisions de la balle.
|
||||||
|
"""
|
||||||
|
# On récupère le monde
|
||||||
|
world = ball.world
|
||||||
|
|
||||||
class Bounce:
|
# Collision avec un joueur
|
||||||
"""
|
if Player in other:
|
||||||
Composant qui marque une entité qui peux faire rebondir
|
speed = ball[Velocity].length
|
||||||
"""
|
ball[Velocity] = ball[Velocity].normalized
|
||||||
|
ball[Velocity].y = (ball[Position].y - other[Position].y) * 0.005
|
||||||
|
ball[Velocity] = ball[Velocity].normalized * min((speed * 1.1), 1000.0)
|
||||||
|
world[Score] += 100
|
||||||
|
|
||||||
|
# Collision avec un mur
|
||||||
|
elif Wall in other:
|
||||||
|
if WinWall in other:
|
||||||
|
for entity in world.query(Player, PlayerScore):
|
||||||
|
if not WinWall.is_my_side(other, entity):
|
||||||
|
if entity[AI].logic == bot_ai:
|
||||||
|
print("GAME OVER, score:", world[Score])
|
||||||
|
world[CurrentScene] = ONE_PLAYER
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
world[Score] += 1000
|
||||||
|
entity[PlayerScore] += 1
|
||||||
|
ball.destroy()
|
||||||
|
if len(world.query(Ball)) == 0:
|
||||||
|
StartAnimation.start(world, 3)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
world[Score] += 20
|
||||||
|
|
||||||
class UpKey(str):
|
return True
|
||||||
"""
|
|
||||||
Composant qui indique la touche pour faire monter le joueur
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
class DownKey(str):
|
def spawn_ball(world: World):
|
||||||
"""
|
"""
|
||||||
Composant qui indique la touche pour faire descender le joueur
|
Fais apparaitre une balle au milieu de l'écran.
|
||||||
"""
|
"""
|
||||||
|
world.new_entity().set(
|
||||||
|
SpriteBundle(
|
||||||
class Speed(int):
|
"ball.png",
|
||||||
"""
|
0,
|
||||||
Composant qui represente la vitesse de l'entité.
|
position=Vec2(render.WIDTH / 2, render.HEIGHT / 2),
|
||||||
"""
|
origin=Vec2(0.5),
|
||||||
|
),
|
||||||
|
Velocity(random.choice([-1, 1]) * 300, random.randint(-1, 1) * 100),
|
||||||
class Player1Score(int):
|
CollisionHandler(Ball.handler),
|
||||||
"""
|
Ball(),
|
||||||
Ressource qui represente le score du joueur 1.
|
)
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Player2Score(int):
|
|
||||||
"""
|
|
||||||
Ressource qui represente le score du joueur 2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Score:
|
|
||||||
"""
|
|
||||||
Composant qui marque l'entité comme étant l'affichage du score.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class StartAnimation(float):
|
class StartAnimation(float):
|
||||||
|
@ -109,506 +149,198 @@ class StartAnimation(float):
|
||||||
Composant qui represente un le moment auxquel on a lancé l'animation du compte a rebours
|
Composant qui represente un le moment auxquel on a lancé l'animation du compte a rebours
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TimeUntilBonus:
|
|
||||||
"""
|
|
||||||
ressource qui represente le temps restant avant d'avoir le bonus
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, time: float, world: World):
|
|
||||||
self.time = time
|
|
||||||
self.started_time = int(world[Time])
|
|
||||||
|
|
||||||
def is_ended(self, world: World):
|
|
||||||
return world[Time] - self.started_time >= self.time
|
|
||||||
|
|
||||||
def start(self, world: World):
|
|
||||||
self.started_time = world[Time]
|
|
||||||
|
|
||||||
|
|
||||||
class LastPlayerTurn:
|
|
||||||
"""
|
|
||||||
un composant qui represente le dernier joueur qui a joué.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HasBonus:
|
|
||||||
"""
|
|
||||||
un composant qui represente si l'entité a un bonus
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, bonus: "Bonus", time: float, world: World):
|
|
||||||
self.bonus = bonus
|
|
||||||
self.time = time
|
|
||||||
|
|
||||||
self.start_time = world[Time]
|
|
||||||
|
|
||||||
def is_ended(self, world: World):
|
|
||||||
return world[Time] - self.start_time >= self.time
|
|
||||||
|
|
||||||
def suppr_bonus_from_entity(self, entity: Entity):
|
|
||||||
match self.bonus:
|
|
||||||
case Bonus.MULTI:
|
|
||||||
pass
|
|
||||||
case Bonus.BIG:
|
|
||||||
entity[Scale] /= 2
|
|
||||||
case Bonus.FAST:
|
|
||||||
entity[Speed] /= 1.5
|
|
||||||
case Bonus.REVERSE:
|
|
||||||
entity[UpKey], entity[DownKey] = entity[DownKey], entity[UpKey]
|
|
||||||
|
|
||||||
|
|
||||||
class Bonus(Enum):
|
|
||||||
MULTI = 0
|
|
||||||
BIG = 1
|
|
||||||
FAST = 2
|
|
||||||
REVERSE = 3
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def aleatoire():
|
def start(world: World, number: int):
|
||||||
type = random.randint(0, 3)
|
world.new_entity().set(
|
||||||
match type:
|
TextBundle(
|
||||||
case 0:
|
str(number),
|
||||||
return Bonus.MULTI
|
2,
|
||||||
case 1:
|
5000,
|
||||||
return Bonus.BIG
|
position=Vec2(render.WIDTH / 2, render.HEIGHT / 2),
|
||||||
case 2:
|
origin=Vec2(0.5),
|
||||||
return Bonus.FAST
|
|
||||||
case _:
|
|
||||||
return Bonus.REVERSE
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_texture(bonus: "Bonus"):
|
|
||||||
match bonus:
|
|
||||||
case Bonus.MULTI:
|
|
||||||
return "multi.png"
|
|
||||||
case Bonus.BIG:
|
|
||||||
return "big.png"
|
|
||||||
case Bonus.FAST:
|
|
||||||
return "fast.png"
|
|
||||||
case _:
|
|
||||||
return "reverse.png"
|
|
||||||
|
|
||||||
|
|
||||||
def __spawn_ellements(world: World):
|
|
||||||
"""
|
|
||||||
La fonction permet de initializer les ellements de la scene.
|
|
||||||
"""
|
|
||||||
|
|
||||||
world.new_entity().set(SpriteBundle(("background.jpg"), -5))
|
|
||||||
|
|
||||||
world.set(
|
|
||||||
TimeUntilBonus(5, world),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mon mur de gauche
|
|
||||||
world.new_entity().set(
|
|
||||||
Origin(Vec2(1, 0)),
|
|
||||||
Scale(Vec2(10, render.HEIGHT)),
|
|
||||||
Position(Vec2(70, 0)),
|
|
||||||
Solid(),
|
|
||||||
LeftWall(),
|
|
||||||
CollisionHandler(__bounce_on_left_wall),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mon mur du RN
|
|
||||||
world.new_entity().set(
|
|
||||||
Origin(Vec2(0, 0)),
|
|
||||||
Scale(Vec2(10, render.HEIGHT)),
|
|
||||||
Position(Vec2(render.WIDTH - 70, 0)),
|
|
||||||
Solid(),
|
|
||||||
RightWall(),
|
|
||||||
CollisionHandler(__bounce_on_right_wall),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mon mur du bas
|
|
||||||
world.new_entity().set(
|
|
||||||
Origin(Vec2(0, 0)),
|
|
||||||
Scale(Vec2(render.WIDTH, 10)),
|
|
||||||
Position(Vec2(0, render.HEIGHT)),
|
|
||||||
Solid(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mon mur du haut
|
|
||||||
world.new_entity().set(
|
|
||||||
Origin(Vec2(0, 1)),
|
|
||||||
Scale(Vec2(render.WIDTH, 10)),
|
|
||||||
Position(Vec2(0, 0)),
|
|
||||||
Solid(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Joueur 1
|
|
||||||
world.new_entity().set(
|
|
||||||
SpriteBundle(
|
|
||||||
"player_1.png",
|
|
||||||
0,
|
|
||||||
Vec2(100, render.HEIGHT / 2),
|
|
||||||
Vec2(44, 250),
|
|
||||||
Vec2(0.5),
|
|
||||||
),
|
|
||||||
Solid(),
|
|
||||||
Player1(),
|
|
||||||
UpKey("z"),
|
|
||||||
DownKey("s"),
|
|
||||||
Speed(1000),
|
|
||||||
LastPlayerTurn(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Joueur 2
|
|
||||||
world.new_entity().set(
|
|
||||||
SpriteBundle(
|
|
||||||
"player_2.png",
|
|
||||||
0,
|
|
||||||
Vec2(render.WIDTH - 100, render.HEIGHT / 2),
|
|
||||||
Vec2(44, 250),
|
|
||||||
Vec2(0.5),
|
|
||||||
),
|
|
||||||
Solid(),
|
|
||||||
Player2(),
|
|
||||||
(UpKey("up"), DownKey("down"), Speed(1000))
|
|
||||||
if world[GameMode] == GameMode.TWO
|
|
||||||
else Speed(300),
|
|
||||||
)
|
|
||||||
|
|
||||||
__spawn_ball(world)
|
|
||||||
|
|
||||||
# Initialisation des scores
|
|
||||||
world.set(Player1Score(0), Player2Score(0))
|
|
||||||
|
|
||||||
world.new_entity().set(
|
|
||||||
TextBundle(
|
|
||||||
"0 - 0",
|
|
||||||
10,
|
|
||||||
50,
|
|
||||||
position=Vec2(render.WIDTH / 2, 75),
|
|
||||||
origin=Vec2(0.5),
|
|
||||||
),
|
|
||||||
Score(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __spawn_bonus(world: World):
|
|
||||||
bonus = Bonus.aleatoire()
|
|
||||||
world.new_entity().set(
|
|
||||||
SpriteBundle(
|
|
||||||
Bonus.get_texture(bonus),
|
|
||||||
3,
|
|
||||||
Vec2(
|
|
||||||
random.randint(200, render.WIDTH - 200),
|
|
||||||
random.randint(100, render.HEIGHT - 100),
|
|
||||||
),
|
),
|
||||||
Vec2(70),
|
StartAnimation(world[Time]),
|
||||||
Vec2(0.5),
|
|
||||||
),
|
|
||||||
bonus,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __spawn_ball(world: World):
|
|
||||||
"""
|
|
||||||
Fonction qui fait apparaitre une balle avec une velocitée aleatoire
|
|
||||||
"""
|
|
||||||
# random velocité
|
|
||||||
velocity = Vec2(2 * random.randint(100, 200), random.randint(100, 200))
|
|
||||||
|
|
||||||
# mouvement a droite ou a gauche
|
|
||||||
if random.randint(0, 1) == 0:
|
|
||||||
velocity.x = -velocity.x
|
|
||||||
|
|
||||||
# Balle
|
|
||||||
world.new_entity().set(
|
|
||||||
SpriteBundle(
|
|
||||||
"ball.png",
|
|
||||||
0,
|
|
||||||
Vec2(render.WIDTH / 2, render.HEIGHT / 2),
|
|
||||||
Vec2(40, 40),
|
|
||||||
Vec2(0.5),
|
|
||||||
),
|
|
||||||
Ball(),
|
|
||||||
Velocity(velocity),
|
|
||||||
CollisionHandler(__collision_with_ball),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __collision_with_ball(a: Entity, b: Entity):
|
|
||||||
if Player1 in b or Player2 in b:
|
|
||||||
for player in a.world.query(LastPlayerTurn):
|
|
||||||
del player[LastPlayerTurn]
|
|
||||||
b.set(LastPlayerTurn())
|
|
||||||
return __bounce_on_player(a, b)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def __bonus_touched(ball: Entity, bonus: Entity):
|
|
||||||
player = ball.world.query(LastPlayerTurn).pop()
|
|
||||||
match bonus[Bonus]:
|
|
||||||
case Bonus.MULTI:
|
|
||||||
__spawn_ball(bonus.world)
|
|
||||||
__spawn_ball(bonus.world)
|
|
||||||
ball.world[TimeUntilBonus].start(ball.world)
|
|
||||||
case Bonus.BIG:
|
|
||||||
player[Scale] *= 2
|
|
||||||
player.set(HasBonus(Bonus.BIG, 10, bonus.world))
|
|
||||||
case Bonus.FAST:
|
|
||||||
player[Speed] *= 1.5
|
|
||||||
player.set(HasBonus(Bonus.FAST, 10, bonus.world))
|
|
||||||
case Bonus.REVERSE:
|
|
||||||
for entity in ball.world.query(UpKey):
|
|
||||||
if LastPlayerTurn in entity:
|
|
||||||
continue
|
|
||||||
entity[UpKey], entity[DownKey] = entity[DownKey], entity[UpKey]
|
|
||||||
|
|
||||||
entity.set(HasBonus(Bonus.REVERSE, 10, bonus.world))
|
|
||||||
|
|
||||||
bonus.destroy()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def __bounce_on_player(a: Entity, b: Entity):
|
|
||||||
"""
|
|
||||||
Fonction qui decrit se qui se passe lorque la ball entre en collision avec un joueur
|
|
||||||
"""
|
|
||||||
if Player1 in b or Player2 in b:
|
|
||||||
speed = a[Velocity].length
|
|
||||||
a[Velocity] = a[Velocity].normalized
|
|
||||||
a[Velocity].y = (a[Position].y - b[Position].y) * 0.005
|
|
||||||
a[Velocity] = a[Velocity].normalized * min((speed * 1.1), 1000.0)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def __bounce_on_left_wall(a: Entity, b: Entity):
|
|
||||||
"""
|
|
||||||
Fonction qui decrit se qui se passe lorque la ball entre en collision avec le mur de gauche
|
|
||||||
"""
|
|
||||||
if Ball in b:
|
|
||||||
world = a.world
|
|
||||||
world[Player2Score] += 1
|
|
||||||
__update_scores(world, b)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def __bounce_on_right_wall(a: Entity, b: Entity):
|
|
||||||
"""
|
|
||||||
Fonction qui decrit se qui se passe lorque la ball entre en collision avec le mur de droite
|
|
||||||
"""
|
|
||||||
if Ball in b:
|
|
||||||
world = a.world
|
|
||||||
world[Player1Score] += 1
|
|
||||||
__update_scores(world, b)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def __move_up(world: World):
|
|
||||||
"""
|
|
||||||
La fonction permet de faire bouger les entitees vers le haut.
|
|
||||||
"""
|
|
||||||
held = world[Held]
|
|
||||||
for entity in world.query(UpKey):
|
|
||||||
if entity[UpKey] in held:
|
|
||||||
entity[Position] = Vec2(
|
|
||||||
entity[Position].x,
|
|
||||||
(entity[Position].y - entity[Speed] * world[Delta]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __move_down(world: World):
|
|
||||||
"""
|
|
||||||
La fonction permet de faire bouger les entitees vers le bas.
|
|
||||||
"""
|
|
||||||
held = world[Held]
|
|
||||||
for entity in world.query(DownKey):
|
|
||||||
if entity[DownKey] in held:
|
|
||||||
entity[Position] = Vec2(
|
|
||||||
entity[Position].x,
|
|
||||||
(entity[Position].y + entity[Speed] * world[Delta]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __update_move(world: World):
|
|
||||||
"""
|
|
||||||
La fontion permet de faire bouger les entitees vers le haut ou vers le bas.
|
|
||||||
"""
|
|
||||||
__move_down(world)
|
|
||||||
__move_up(world)
|
|
||||||
|
|
||||||
|
|
||||||
def __simulate_wall_position(entity: Entity, component_type: type):
|
|
||||||
"""
|
|
||||||
Simule une entité afin de trouver lorsqu'elle entrera en collision avec une entité contenant un certain composant.
|
|
||||||
"""
|
|
||||||
simulation_entity = entity.world.new_entity()
|
|
||||||
|
|
||||||
def __collision_handler(a: Entity, b: Entity):
|
|
||||||
entity[CollisionHandler].callback(a, b)
|
|
||||||
return component_type not in b
|
|
||||||
|
|
||||||
simulation_entity.set(
|
|
||||||
Position(entity[Position]),
|
|
||||||
Scale(entity[Scale]),
|
|
||||||
Velocity(entity[Velocity]),
|
|
||||||
Origin(entity[Origin]),
|
|
||||||
CollisionHandler(__collision_handler),
|
|
||||||
)
|
|
||||||
physics.move_entity(simulation_entity, entity[Velocity] * 500)
|
|
||||||
return simulation_entity
|
|
||||||
|
|
||||||
|
|
||||||
def _update_bot(world: World):
|
|
||||||
"""
|
|
||||||
Fonction qui update les mouvement du bot
|
|
||||||
"""
|
|
||||||
# On récupère la balle la plus proche du bot
|
|
||||||
ball_query = world.query(Position, Velocity, CollisionHandler)
|
|
||||||
if ball_query == set():
|
|
||||||
return None
|
|
||||||
ball = max(ball_query, key=lambda entity: entity[Position].y)
|
|
||||||
|
|
||||||
# On récupère le bot et le joueur
|
|
||||||
bot = world.query(Player2).pop()
|
|
||||||
player = world.query(Player1).pop()
|
|
||||||
|
|
||||||
# On trouve l'endroit ou la balle va arriver sur le mur de droite
|
|
||||||
bot.remove(Solid)
|
|
||||||
right_wall_ball = __simulate_wall_position(ball, RightWall)
|
|
||||||
right_touch_height = right_wall_ball[Position].y
|
|
||||||
right_wall_ball.destroy()
|
|
||||||
bot.set(Solid())
|
|
||||||
|
|
||||||
# On teste différentes possitions pour voir laquelle la plus éloigné du joueur
|
|
||||||
# Mais seulement si la balle vas vers la droite car sinon elle touchera le mur
|
|
||||||
# de gauche sans intervention du bot
|
|
||||||
if ball[Velocity].x > 0:
|
|
||||||
bot_base_y = bot[Position].y
|
|
||||||
target: float = right_touch_height
|
|
||||||
better_distance = None
|
|
||||||
for offset in [-100, -50, 0, 50, 100]:
|
|
||||||
bot[Position].y = right_touch_height + offset
|
|
||||||
player.remove(Solid)
|
|
||||||
left_wall_ball = __simulate_wall_position(ball, LeftWall)
|
|
||||||
player.set(Solid())
|
|
||||||
left_touch_height = left_wall_ball[Position].y
|
|
||||||
left_wall_ball.destroy()
|
|
||||||
if (
|
|
||||||
better_distance is None
|
|
||||||
or abs(left_touch_height - player[Position].y) > better_distance
|
|
||||||
):
|
|
||||||
better_distance = abs(left_touch_height - player[Position].y)
|
|
||||||
target = right_touch_height + offset
|
|
||||||
bot[Position].y = bot_base_y
|
|
||||||
else:
|
|
||||||
target = right_touch_height
|
|
||||||
|
|
||||||
# On se déplace vers la meilleure option
|
|
||||||
diff = target - bot[Position].y
|
|
||||||
if abs(diff) > 10:
|
|
||||||
bot[Position].y += (diff / abs(diff)) * bot[Speed] * world[Delta]
|
|
||||||
|
|
||||||
|
|
||||||
def __check_bonus_collision(world: World):
|
|
||||||
"""
|
|
||||||
Fonction qui permet de voir si un bonus est entrée en collision avec une entité.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __collision_handler(a: Entity, b: Entity):
|
|
||||||
if Bonus in b:
|
|
||||||
__bonus_touched(a, b)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
for entity in world.query(Bonus):
|
|
||||||
entity.set(Solid())
|
|
||||||
for entity in world.query(Ball):
|
|
||||||
simulated_ball = world.new_entity()
|
|
||||||
simulated_ball.set(
|
|
||||||
Position(entity[Position]),
|
|
||||||
Scale(entity[Scale]),
|
|
||||||
Velocity(entity[Velocity]),
|
|
||||||
Origin(entity[Origin]),
|
|
||||||
CollisionHandler(__collision_handler),
|
|
||||||
)
|
)
|
||||||
physics.move_entity(simulated_ball, entity[Velocity] * world[Delta])
|
|
||||||
simulated_ball.destroy()
|
|
||||||
for entity in world.query(Bonus):
|
|
||||||
entity.remove(Solid)
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update(world: World):
|
||||||
|
"""
|
||||||
|
Fonction qui permet de mettre a jour l'animation du compte a rebours.
|
||||||
|
"""
|
||||||
|
|
||||||
def __update_scores(world: World, ball: Entity):
|
for animation in world.query(StartAnimation):
|
||||||
"""
|
time = world[Time] - animation[StartAnimation]
|
||||||
La fontion permet de mettre a jour les scores.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# met a jour le score du joueur 1 et 2
|
if animation[TextSize] > 700:
|
||||||
for panel in world.query(Score):
|
animation[TextSize] = 5000 / (25 * time + 1)
|
||||||
panel[Text] = f"{world[Player1Score]} - {world[Player2Score]}"
|
|
||||||
|
|
||||||
ball.destroy()
|
|
||||||
|
|
||||||
if world.query(Ball) == set():
|
|
||||||
__animation(world, 3)
|
|
||||||
|
|
||||||
|
|
||||||
def __animation(world: World, number: int):
|
|
||||||
world.new_entity().set(
|
|
||||||
TextBundle(
|
|
||||||
str(number),
|
|
||||||
2,
|
|
||||||
5000,
|
|
||||||
position=Vec2(render.WIDTH / 2, render.HEIGHT / 2),
|
|
||||||
origin=Vec2(0.5),
|
|
||||||
),
|
|
||||||
StartAnimation(world[Time]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def __update_animation(world: World):
|
|
||||||
"""
|
|
||||||
Fonction qui permet de mettre a jour l'animation du compte a rebours.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for animation in world.query(StartAnimation):
|
|
||||||
time = world[Time] - animation[StartAnimation]
|
|
||||||
|
|
||||||
if animation[TextSize] > 700:
|
|
||||||
animation[TextSize] = 5000 / (25 * time + 1)
|
|
||||||
else:
|
|
||||||
animation[TextSize] -= 1000 * world[Delta]
|
|
||||||
if animation[TextSize] < 0:
|
|
||||||
if int(animation[Text]) > 1 or "space" in world[Held]:
|
|
||||||
__animation(world, int(animation[Text]) - 1)
|
|
||||||
else:
|
else:
|
||||||
# creation de la balle
|
animation[TextSize] -= 1000 * world[Delta]
|
||||||
__spawn_ball(world)
|
if animation[TextSize] < 0:
|
||||||
animation.destroy()
|
if int(animation[Text]) > 1 or "space" in world[Held]:
|
||||||
|
StartAnimation.start(world, int(animation[Text]) - 1)
|
||||||
|
else:
|
||||||
|
Ball.spawn_ball(world)
|
||||||
|
animation.destroy()
|
||||||
|
|
||||||
|
|
||||||
def __update_bonus_time(world: World):
|
class ScoreRenderer:
|
||||||
"""
|
"""
|
||||||
Fonction qui permet de mettre à jour les bonus.
|
Composant marquant l'entité d'affichage du score.
|
||||||
"""
|
"""
|
||||||
for player in world.query(HasBonus):
|
|
||||||
if not player[HasBonus].is_ended(world):
|
|
||||||
return None
|
|
||||||
player[HasBonus].suppr_bonus_from_entity(player)
|
|
||||||
del player[HasBonus]
|
|
||||||
world[TimeUntilBonus].start(world)
|
|
||||||
|
|
||||||
if world.query(Bonus) == set() and world.query(HasBonus) == set():
|
@staticmethod
|
||||||
if world[TimeUntilBonus].is_ended(world):
|
def render(world: World):
|
||||||
__spawn_bonus(world)
|
"""
|
||||||
|
Met à jour le score.
|
||||||
|
"""
|
||||||
|
one_players = any(map(lambda e: e[AI].logic == bot_ai, world.query(AI)))
|
||||||
|
for entity in world.query(ScoreRenderer):
|
||||||
|
if one_players:
|
||||||
|
entity[Text] = str(world[Score])
|
||||||
|
else:
|
||||||
|
entity[Text] = " - ".join(
|
||||||
|
map(lambda e: str(e[PlayerScore]), world.query(PlayerScore))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __initialize(two: bool):
|
||||||
|
"""
|
||||||
|
Créer une initialisation du jeu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __initialize_inner(world: World):
|
||||||
|
"""
|
||||||
|
Fait apparaitre tous les elements de la partie.
|
||||||
|
"""
|
||||||
|
# Ajout du fond
|
||||||
|
world.new_entity().set(SpriteBundle("background.jpg", -5))
|
||||||
|
|
||||||
|
# Ajout des murs du monde
|
||||||
|
world.new_entity().set(
|
||||||
|
Position(0, 0),
|
||||||
|
Scale(render.WIDTH, 10),
|
||||||
|
Origin(0, 1),
|
||||||
|
Solid(),
|
||||||
|
Wall(),
|
||||||
|
)
|
||||||
|
world.new_entity().set(
|
||||||
|
Position(0, render.HEIGHT),
|
||||||
|
Scale(render.WIDTH, 10),
|
||||||
|
Origin(0, 0),
|
||||||
|
Solid(),
|
||||||
|
Wall(),
|
||||||
|
)
|
||||||
|
world.new_entity().set(
|
||||||
|
Position(PLAYER_WALL_DISTANCE - 1, 0),
|
||||||
|
Scale(10, render.HEIGHT),
|
||||||
|
Origin(1, 0),
|
||||||
|
Solid(),
|
||||||
|
Wall(),
|
||||||
|
WinWall(),
|
||||||
|
)
|
||||||
|
world.new_entity().set(
|
||||||
|
Position(render.WIDTH - PLAYER_WALL_DISTANCE + 1, 0),
|
||||||
|
Scale(10, render.HEIGHT),
|
||||||
|
Origin(0, 0),
|
||||||
|
Solid(),
|
||||||
|
Wall(),
|
||||||
|
WinWall(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout du joueur de gauche
|
||||||
|
world.new_entity().set(
|
||||||
|
SpriteBundle(
|
||||||
|
"player_1.png",
|
||||||
|
0,
|
||||||
|
position=Vec2(PLAYER_WALL_DISTANCE, render.HEIGHT / 2),
|
||||||
|
origin=Vec2(1, 0.5),
|
||||||
|
),
|
||||||
|
Solid(),
|
||||||
|
Player(),
|
||||||
|
PlayerScore(0),
|
||||||
|
PlayerSpeed(500),
|
||||||
|
AI(human_ai("z", "s")),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout du joueur de droite
|
||||||
|
world.new_entity().set(
|
||||||
|
SpriteBundle(
|
||||||
|
"player_2.png",
|
||||||
|
0,
|
||||||
|
position=Vec2(render.WIDTH - PLAYER_WALL_DISTANCE, render.HEIGHT / 2),
|
||||||
|
origin=Vec2(0, 0.5),
|
||||||
|
),
|
||||||
|
Solid(),
|
||||||
|
Player(),
|
||||||
|
PlayerScore(0),
|
||||||
|
PlayerSpeed(500),
|
||||||
|
AI(human_ai("up", "down") if two else bot_ai),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajout d'une balle
|
||||||
|
Ball.spawn_ball(world)
|
||||||
|
|
||||||
|
# On initialize le score
|
||||||
|
world[Score] = 0
|
||||||
|
world.new_entity().set(
|
||||||
|
TextBundle(
|
||||||
|
"dsqd", 1, 100, position=Vec2(render.WIDTH / 2, 200), origin=Vec2(0.5)
|
||||||
|
),
|
||||||
|
ScoreRenderer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return __initialize_inner
|
||||||
|
|
||||||
|
|
||||||
|
def __update_ai(world: World):
|
||||||
|
"""
|
||||||
|
Met à jour le comportement des joueurs.
|
||||||
|
"""
|
||||||
|
for entity in world.query(AI):
|
||||||
|
new_world = world.partial_copy(
|
||||||
|
Position,
|
||||||
|
Scale,
|
||||||
|
Origin,
|
||||||
|
Velocity,
|
||||||
|
Solid,
|
||||||
|
Player,
|
||||||
|
PlayerSpeed,
|
||||||
|
Ball,
|
||||||
|
Wall,
|
||||||
|
WinWall,
|
||||||
|
ressources=(Held,),
|
||||||
|
)
|
||||||
|
entity[PlayerTarget] = entity[AI].logic(
|
||||||
|
new_world, Entity(new_world, entity.identifier)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __apply_target(world: World):
|
||||||
|
"""
|
||||||
|
Applique les déplacements cibles des joueurs.
|
||||||
|
"""
|
||||||
|
for entity in world.query(Player, PlayerTarget):
|
||||||
|
diff = entity[PlayerTarget] - entity[Position].y
|
||||||
|
if abs(diff) > entity[PlayerSpeed] * world[Delta]:
|
||||||
|
entity[Position].y += diff / abs(diff) * entity[PlayerSpeed] * world[Delta]
|
||||||
|
else:
|
||||||
|
entity[Position].y = entity[PlayerTarget]
|
||||||
|
if entity[Position].y - entity[Scale].y / 2 < 0:
|
||||||
|
entity[Position].y = entity[Scale].y / 2
|
||||||
|
if entity[Position].y + entity[Scale].y / 2 > render.HEIGHT:
|
||||||
|
entity[Position].y = render.HEIGHT - entity[Scale].y / 2
|
||||||
|
|
||||||
|
|
||||||
__SCENE = Scene(
|
__SCENE = Scene(
|
||||||
[__spawn_ellements],
|
[],
|
||||||
[__update_move, __check_bonus_collision, __update_animation, __update_bonus_time],
|
[__update_ai, __apply_target, StartAnimation.update, ScoreRenderer.render],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
ONE_PLAYER = (
|
ONE_PLAYER = (
|
||||||
Plugin(
|
Plugin(
|
||||||
[lambda world: world.set(GameMode.ONE)],
|
[__initialize(False)],
|
||||||
[_update_bot],
|
[],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
+ __SCENE
|
+ __SCENE
|
||||||
|
@ -616,9 +348,37 @@ ONE_PLAYER = (
|
||||||
|
|
||||||
TWO_PLAYER = (
|
TWO_PLAYER = (
|
||||||
Plugin(
|
Plugin(
|
||||||
[lambda world: world.set(GameMode.TWO)],
|
[__initialize(True)],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
+ __SCENE
|
+ __SCENE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def human_ai(up: str, down: str):
|
||||||
|
def __human_ai(world: World, player: Entity) -> float:
|
||||||
|
pressed = up in world[Held], down in world[Held]
|
||||||
|
match pressed:
|
||||||
|
case (True, False):
|
||||||
|
return 0
|
||||||
|
case (False, True):
|
||||||
|
return render.HEIGHT
|
||||||
|
case _:
|
||||||
|
return player[Position].y
|
||||||
|
|
||||||
|
return __human_ai
|
||||||
|
|
||||||
|
|
||||||
|
def bot_ai(world: World, player: Entity) -> float:
|
||||||
|
player.remove(Solid)
|
||||||
|
best_distance = float("inf")
|
||||||
|
best_y = player[Position].y
|
||||||
|
for entity in world.query(Ball):
|
||||||
|
distance = physics.simulate(
|
||||||
|
entity, lambda e: WinWall in e and WinWall.is_my_side(e, player)
|
||||||
|
)
|
||||||
|
if distance < best_distance:
|
||||||
|
best_distance = distance
|
||||||
|
best_y = entity[Position].y
|
||||||
|
return best_y
|
||||||
|
|
Loading…
Reference in a new issue