import pyxel
import random


class Jeu:
    def __init__(self):
        pyxel.init(256, 128, title="Nuit du code")
        self.nb_plat = random.randint(4, 6)  # nombre de plateformes
        self.plat = {}  # dico avec les caractéristiques de chaque plateforme
        self.plateformes()
        self.nb_ennemis = random.randint(2, self.nb_plat - 2)  # nombre d'ennemis
        if self.nb_plat == 6:
            self.nb_ennemis -= 1  # réduit la difficulté
        self.ennem = []  # liste des ennemis
        self.ennemis()
        self.bomb = []  # liste des bombes
        self.explosion = []  # liste des explosions
        self.joueur_x = self.plat["plat1"]["x"] + 2  # position x du joueur
        self.joueur_y = self.plat["plat1"]["y"] - 15  # position y du joueur
        self.joueur_sens = 1  # direction du joueur
        self.db_saut = 0  # nombre de sauts faits (double saut)
        self.etape_saut = 5  # décomposition du saut en plusieurs frames
        self.chute = False  # joueur chute si True
        self.sol = self.plat["plat1"]  # plateforme sur laquelle est le joueur
        self.score = 0  # score du joueur
        self.vie = 3  # vie du joueur
        self.nv = 0  # niveau à choisir
        pyxel.load("res.pyxres")  # retélechargez pyxel avec pip pour le mettre à jour si ça ne fonctionne pas
        pyxel.run(self.update, self.draw)

    def plateformes(self):
        """
        Créer les plateformes
        Chaque plateforme a une position x, y et une largeur
        """
        # première plateforme
        self.plat["plat1"] = {"x": random.randint(5, 15), "y": random.randint(90, 100),
                              "larg": random.randint(200 // self.nb_plat - 3, 200 // self.nb_plat + 3)}
        # création des autres plateformes
        for i in range(2, self.nb_plat + 1):
            self.plat["plat" + str(i)] = {
                "x": random.randint(self.plat["plat" + str(i - 1)]["x"] + self.plat["plat" + str(i - 1)]["larg"] + 1,
                                    self.plat["plat" + str(i - 1)]["x"] + self.plat["plat" + str(i - 1)]["larg"] + 10),
                "y": random.randint(self.plat["plat" + str(i - 1)]["y"] - 10, self.plat["plat" + str(i - 1)]["y"] - 5),
                "larg": random.randint(200 // self.nb_plat - 3, 200 // self.nb_plat + 3)}
        if self.plat["plat"+str(self.nb_plat)]["x"] + self.plat["plat"+str(self.nb_plat)]["larg"] >= 250:  # dernière plateforme sort de l'écran
            self.plat = {}
            self.plateformes()
        
    def ennemis(self):
        """
        Création des ennemis
        Chaque ennemi a une position x, y, la plateforme sur laquelle il est placé et leur sens de déplacement
        """
        liste_plat = list(range(2, self.nb_plat + 1))  # liste des plateformes où les ennemis peuvent être
        for i in range(1, self.nb_ennemis + 1):
            a = random.choice(liste_plat)
            self.ennem.append({
                "plat": "plat" + str(a),
                "x": self.plat["plat" + str(a)]["x"] + self.plat["plat" + str(a)]["larg"] - 9,
                "y": self.plat["plat" + str(a)]["y"] - 5, "sens": -1
            })
            liste_plat.remove(a)  # pas deux ennemis sur la même plateforme

    def bombes(self):
        """
        Création des bombes et des explosions
        Chaque bombe a une position x, y
        """
        if pyxel.frame_count % ((4 - self.nv) * 30) == 0:  # fréquence différente selon le niveau
            self.bomb.append({
                "x": random.randint(self.plat["plat1"]["x"] + 10,
                                    self.plat["plat" + str(self.nb_plat)]["x"] + self.plat["plat" + str(self.nb_plat)][
                                        "larg"] - 10),
                "y": 0
            })  # position de départ de la bombe
        for bombe in self.bomb:
            bombe["y"] += 1  # gravité
            if bombe["y"] >= 130:
                self.bomb.remove(bombe)  # bombe sortie de l'écran
            for plat in self.plat.values():
                if plat["x"] < bombe["x"] < plat["x"] + plat["larg"] and bombe["y"] >= plat["y"]:
                    self.bomb.remove(bombe)
                    self.explosion.append({
                        "x": bombe["x"],
                        "y": bombe["y"],
                        "rayon": 1
                    })  # création d'une explosion quand la bombe touche une plateforme
        for explosion in self.explosion:
            explosion["rayon"] += 1
            if explosion["rayon"] >= 10:
                self.explosion.remove(explosion)  # explosion limitée à un rayon de 6 pixels

    def reset(self):
        """
        Réinitialisation de la partie (donc des variables)
        """
        self.nb_plat = random.randint(4, 6)
        self.plat = {}
        self.plateformes()
        self.nb_ennemis = random.randint(2, self.nb_plat - 2)
        if self.nb_plat == 6:
            self.nb_ennemis -= 1
        self.ennem = []
        self.ennemis()
        self.bomb = []
        self.explosion = []
        self.joueur_x = self.plat["plat1"]["x"] + 2
        self.joueur_y = self.plat["plat1"]["y"] - 15
        self.joueur_sens = 1
        self.etape_saut = 5
        self.db_saut = 0
        self.chute = False
        self.sol = self.plat["plat1"]

    def menu(self):
        """
        Création des différents menus
        """
        # menu de départ (choix du niveau)
        if self.nv == 0:
            if pyxel.btn(pyxel.KEY_1) or pyxel.btn(pyxel.KEY_KP_1):
                self.nv = 1
            if pyxel.btn(pyxel.KEY_2) or pyxel.btn(pyxel.KEY_KP_2):
                self.nv = 2
            if pyxel.btn(pyxel.KEY_3) or pyxel.btn(pyxel.KEY_KP_3):
                self.nv = 3
        else:
            # menu après une défaite
            if self.vie == 0:
                if pyxel.btn(pyxel.KEY_R):
                    self.vie = 3
                    self.reset()
                if pyxel.btn(pyxel.KEY_M):
                    self.vie = 3
                    self.nv = 0
                if pyxel.btn(pyxel.KEY_Q):
                    pyxel.quit()

    def deplacement_ennemis(self):
        """
        Permet aux ennemis de se déplacer d'un bout à l'autre de leur plateforme
        """
        if pyxel.frame_count % (4 - self.nv) == 0:  # vitesse des ennemis varie en fonction des niveaux
            for ennemi in self.ennem:
                ennemi["x"] += ennemi["sens"]
                # changement de sens quand ils sont au bout de leur plateforme
                if ennemi["x"] + 9 > self.plat[ennemi["plat"]]["x"] + self.plat[ennemi["plat"]]["larg"]:
                    ennemi["sens"] = -1
                if ennemi["x"] - 2 < self.plat[ennemi["plat"]]["x"]:
                    ennemi["sens"] = 1

    def update_sol(self):
        """
        Changement de sol en fonction de la position du joueur
        """
        for i in range(1, self.nb_plat + 1):
            if self.plat["plat" + str(i)]["x"] - 6 < self.joueur_x < self.plat["plat" + str(i)]["x"] + \
                    self.plat["plat" + str(i)]["larg"] + 6 and self.joueur_y - 15 < self.plat["plat" + str(i)][
                "y"]:  # joueur sur ou dessus la plateforme
                self.sol = self.plat["plat" + str(i)]
                self.chute = False
            if i >= 2:
                if self.joueur_x < self.plat["plat" + str(i - 1)]["x"] + self.plat["plat" + str(i - 1)][
                    "larg"] and self.joueur_x + 8 > self.plat["plat" + str(i)][
                    "x"]:  # joueur situé entre deux plateformes au niveau des abscisses
                    if self.joueur_y + 15 > self.plat["plat" + str(i)]["y"]:  # prend la plus haute des deux si possible
                        self.sol = self.plat["plat" + str(i - 1)]
                    else:
                        self.sol = self.plat["plat" + str(i)]
        if self.sol["y"] < self.joueur_y + 15 or self.joueur_x + 7 < self.sol["x"] or self.joueur_x + 1 >= self.sol[
            "x"] + self.sol["larg"]:  # joueur situé sur aucune plateforme
            self.chute = True

    def deplacement(self):
        """
        Permet au joueur de se déplacer suivant les commandes de l'utilisateur
        """
        # déplacement latéral
        if pyxel.btn(pyxel.KEY_RIGHT) and self.joueur_x < 246:
            self.joueur_x += 1
            self.joueur_sens = 1
        if pyxel.btn(pyxel.KEY_LEFT) and self.joueur_x > 0:
            self.joueur_x -= 1
            self.joueur_sens = -1
        # gravité
        if self.joueur_y + 15 < self.sol["y"] or self.chute:
            self.joueur_y += 1
        # double saut
        print(self.etape_saut)
        if pyxel.btnp(pyxel.KEY_SPACE) and self.db_saut < 2:
            self.etape_saut = 1
            if self.joueur_y + 15 <= self.sol["y"]:
                self.db_saut += 1
            else:  # désactive double saut pendant une chute
                self.db_saut += 2
        if self.etape_saut <= 4:  # décompose le saut en 4 étapes
            self.joueur_y -= 6 - self.db_saut
            self.etape_saut += 1
        if self.joueur_y + 15 == self.sol["y"] and not self.chute:  # réinitialise le double saut quand le joueur est au sol
            self.db_saut = 0

    def fin_partie(self):
        """
        Met fin à un niveau et en regénère un nouveau
        """
        if self.joueur_x + 12 >= self.plat["plat" + str(self.nb_plat)]["x"] + self.plat["plat" + str(self.nb_plat)][
            "larg"] \
                and self.joueur_y < self.plat["plat" + str(self.nb_plat)][
            "y"]:  # Victoire (le joueur atteint le drapeau)
            self.score += 1
            self.reset()
        if self.joueur_y > 138:  # joueur tombé des plateformes
            self.vie -= 1
            self.reset()
        for ennemi in self.ennem:
            if (ennemi["x"] + 7 > self.joueur_x + 5 > ennemi["x"] or ennemi["x"] < self.joueur_x < ennemi["x"] + 7) and \
                    self.joueur_y + 15 > ennemi["y"]:  # joueur heurte un ennemi
                self.vie -= 1
                self.reset()
        for bombe in self.bomb:  # joueur heurte une bombe en vol
            if self.joueur_x + 10 >= bombe["x"] >= self.joueur_x - 2 and self.joueur_y + 17 >= bombe["y"] >= self.joueur_y - 2:
                self.vie -= 1
                self.reset()
        for explosion in self.explosion:  # joueur heurte une explosion
            if self.joueur_x + 5 + explosion["rayon"] >= explosion["x"] >= self.joueur_x - explosion["rayon"] + 2 and self.joueur_y + 15 >= explosion["y"] - explosion["rayon"]:
                self.vie -= 1
                self.reset()

    def update(self):
        if self.nv != 0 and self.vie > 0:
            self.deplacement_ennemis()
            self.bombes()
            self.deplacement()
        self.update_sol()
        self.fin_partie()
        self.menu()

    def draw(self):
        pyxel.cls(0)
        if self.nv == 0:
            # menu niveau
            pyxel.text(70, 15, "Choisis le niveau avec le clavier:", 7)
            pyxel.text(85, 40, "1", 7)
            pyxel.text(124, 40, "2", 7)
            pyxel.text(163, 40, "3", 7)
            pyxel.text(95, 90, "Q pour quitter.", 7)
        else:
            if self.vie > 0:
                # joueur
                if self.joueur_y + 15 == self.sol["y"] and not self.chute:
                    pyxel.blt(self.joueur_x, self.joueur_y, 0, 0, 11, 8 * self.joueur_sens, 15, 8)
                else:
                    pyxel.blt(self.joueur_x, self.joueur_y, 0, 8, 11, 9 * self.joueur_sens, 16, 8)
                # plateformes
                for plat in self.plat:
                    pyxel.blt(self.plat[plat]["x"], self.plat[plat]["y"], 0, 0, 0, 2, 6)
                    for i in range(2, self.plat[plat]["larg"] - 2):
                        pyxel.blt(self.plat[plat]["x"] + i, self.plat[plat]["y"], 0, 2, 0, 1, 6)
                    pyxel.blt(self.plat[plat]["x"] + self.plat[plat]["larg"] - 2, self.plat[plat]["y"], 0, 3, 0, 2, 6)
                # ennemis
                for ennemi in self.ennem:
                    pyxel.blt(ennemi["x"], ennemi["y"], 0, 8 * (pyxel.frame_count % 2), 43, 7 * ennemi["sens"], 5, 0)
                # bombes
                for bombe in self.bomb:
                    pyxel.circ(bombe["x"], bombe["y"], 3, 9)
                # explosions
                for explosion in self.explosion:
                    pyxel.circb(explosion["x"], explosion["y"], explosion["rayon"], 9)
                # drapeau
                pyxel.rect(
                    self.plat["plat" + str(self.nb_plat)]["x"] + self.plat["plat" + str(self.nb_plat)]["larg"] - 5,
                    self.plat["plat" + str(self.nb_plat)]["y"] - 15, 2, 15, 11)
                # score et vie
                pyxel.text(210, 7, "Score:" + str(self.score), 7)
                pyxel.text(10, 7, "Vie(s):" + str(self.vie), 7)
            else:
                # menu pour la défaite
                pyxel.text(110, 5, "PERDU!!!", 7)
                pyxel.text(90, 40, "R pour recommencer", 7)
                pyxel.text(85, 75, "M pour revenir au menu", 7)
                pyxel.text(95, 110, "Q pour quitter", 7)


Jeu()