WIP: Simulation dans une copie du monde #53
|
@ -6,6 +6,7 @@ Pour moddifier le monde, on n'agis que sur les composants.
|
|||
"""
|
||||
|
||||
|
||||
import copy
|
||||
from typing import Iterator, Optional, Sequence
|
||||
|
||||
|
||||
|
@ -123,6 +124,22 @@ class World(Entity):
|
|||
self.__entities: dict[type[object], set[int]] = {}
|
||||
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":
|
||||
"""
|
||||
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 = [
|
||||
AABB.from_entity(other) for other in world.query(Solid, Position, Scale, Origin)
|
||||
]
|
||||
distance = 0.0
|
||||
counter = 0
|
||||
while movement.length > 0.0001 and counter < 50:
|
||||
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
|
||||
else:
|
||||
step = movement * max(t - 0.000001, 0)
|
||||
distance += step.length
|
||||
aabb.move(step)
|
||||
aabb.entity_position(entity)
|
||||
movement -= step
|
||||
|
@ -163,18 +165,42 @@ def move_entity(entity: Entity, movement: Vec2, disable_callback: bool = False):
|
|||
if normal.y != 0:
|
||||
movement.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 not entity.get(
|
||||
CollisionHandler, CollisionHandler(lambda e, o: True)
|
||||
).callback(entity, obstacle):
|
||||
break
|
||||
stop = True
|
||||
if not obstacle.get(
|
||||
CollisionHandler, CollisionHandler(lambda e, o: True)
|
||||
).callback(obstacle, entity):
|
||||
break
|
||||
movement *= entity[Velocity]
|
||||
stop = True
|
||||
movement *= entity.get(Velocity, Velocity(0)).length
|
||||
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):
|
||||
|
|
|
@ -2,106 +2,146 @@
|
|||
Le jeux principale.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from engine import Plugin, Scene
|
||||
import random
|
||||
from typing import Callable
|
||||
from engine import CurrentScene, Plugin, Scene
|
||||
from engine.ecs import Entity, World
|
||||
from engine.math import Vec2
|
||||
from plugins import render
|
||||
from plugins import physics
|
||||
from plugins.inputs import Held
|
||||
from plugins.physics import CollisionHandler, Solid, Velocity
|
||||
from plugins.render import (
|
||||
Origin,
|
||||
Position,
|
||||
Scale,
|
||||
SpriteBundle,
|
||||
TextBundle,
|
||||
Text,
|
||||
TextBundle,
|
||||
TextSize,
|
||||
)
|
||||
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.
|
||||
"""
|
||||
|
||||
ONE = 0
|
||||
TWO = 1
|
||||
|
||||
|
||||
class RightWall:
|
||||
"""
|
||||
Composant qui marque une entité comme etant le mur de droite.
|
||||
Ressource correspondant au score du joueur dans la partie.
|
||||
"""
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Composant qui marque une entité qui peux faire rebondir
|
||||
"""
|
||||
# Collision avec un joueur
|
||||
if Player in other:
|
||||
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):
|
||||
"""
|
||||
Composant qui indique la touche pour faire monter le joueur
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class DownKey(str):
|
||||
@staticmethod
|
||||
def spawn_ball(world: World):
|
||||
"""
|
||||
Composant qui indique la touche pour faire descender le joueur
|
||||
"""
|
||||
|
||||
|
||||
class Speed(int):
|
||||
"""
|
||||
Composant qui represente la vitesse de l'entité.
|
||||
"""
|
||||
|
||||
|
||||
class Player1Score(int):
|
||||
"""
|
||||
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.
|
||||
Fais apparaitre une balle au milieu de l'écran.
|
||||
"""
|
||||
world.new_entity().set(
|
||||
SpriteBundle(
|
||||
"ball.png",
|
||||
0,
|
||||
position=Vec2(render.WIDTH / 2, render.HEIGHT / 2),
|
||||
origin=Vec2(0.5),
|
||||
),
|
||||
Velocity(random.choice([-1, 1]) * 300, random.randint(-1, 1) * 100),
|
||||
CollisionHandler(Ball.handler),
|
||||
Ball(),
|
||||
)
|
||||
|
||||
|
||||
class StartAnimation(float):
|
||||
|
@ -109,447 +149,8 @@ class StartAnimation(float):
|
|||
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
|
||||
def aleatoire():
|
||||
type = random.randint(0, 3)
|
||||
match type:
|
||||
case 0:
|
||||
return Bonus.MULTI
|
||||
case 1:
|
||||
return Bonus.BIG
|
||||
case 2:
|
||||
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),
|
||||
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)
|
||||
|
||||
|
||||
def __update_scores(world: World, ball: Entity):
|
||||
"""
|
||||
La fontion permet de mettre a jour les scores.
|
||||
"""
|
||||
|
||||
# met a jour le score du joueur 1 et 2
|
||||
for panel in world.query(Score):
|
||||
panel[Text] = f"{world[Player1Score]} - {world[Player2Score]}"
|
||||
|
||||
ball.destroy()
|
||||
|
||||
if world.query(Ball) == set():
|
||||
__animation(world, 3)
|
||||
|
||||
|
||||
def __animation(world: World, number: int):
|
||||
def start(world: World, number: int):
|
||||
world.new_entity().set(
|
||||
TextBundle(
|
||||
str(number),
|
||||
|
@ -561,8 +162,8 @@ def __animation(world: World, number: int):
|
|||
StartAnimation(world[Time]),
|
||||
)
|
||||
|
||||
|
||||
def __update_animation(world: World):
|
||||
@staticmethod
|
||||
def update(world: World):
|
||||
"""
|
||||
Fonction qui permet de mettre a jour l'animation du compte a rebours.
|
||||
"""
|
||||
|
@ -576,39 +177,170 @@ def __update_animation(world: World):
|
|||
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)
|
||||
StartAnimation.start(world, int(animation[Text]) - 1)
|
||||
else:
|
||||
# creation de la balle
|
||||
__spawn_ball(world)
|
||||
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():
|
||||
if world[TimeUntilBonus].is_ended(world):
|
||||
__spawn_bonus(world)
|
||||
@staticmethod
|
||||
def render(world: 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(
|
||||
[__spawn_ellements],
|
||||
[__update_move, __check_bonus_collision, __update_animation, __update_bonus_time],
|
||||
[],
|
||||
[__update_ai, __apply_target, StartAnimation.update, ScoreRenderer.render],
|
||||
[],
|
||||
)
|
||||
|
||||
ONE_PLAYER = (
|
||||
Plugin(
|
||||
[lambda world: world.set(GameMode.ONE)],
|
||||
[_update_bot],
|
||||
[__initialize(False)],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
+ __SCENE
|
||||
|
@ -616,9 +348,37 @@ ONE_PLAYER = (
|
|||
|
||||
TWO_PLAYER = (
|
||||
Plugin(
|
||||
[lambda world: world.set(GameMode.TWO)],
|
||||
[__initialize(True)],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
+ __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