Aller au contenu

Présentation⚓︎

Citation

"Pyxel est un moteur de jeu vidéo rétro pour Python. seulement 16 couleurs peuvent être affichées et que seulement 4 sons peuvent être lus en même temps, vous pouvez vous sentir libre de créer des jeux vidéo dans le style pixel art. Les spécifications et les API de Pyxel sont inspirées de PICO-8 et TIC-80. Pyxel est un logiciel libre et open source."

Extrait de github.com/kitao/pyxel

En complément des exercices proposés dans ce tutoriel, deux références sur Pyxel :

Pour utiliser pyxel, merci à Laurent Abbal de proposer les outils suivants :

Premiers pas avec pyxel⚓︎

On veut écrire un programme qui affiche dans une fenêtre de dimensions 50 x 50 avec :

  • au lancement de l'application, un menu d'accueil composé de trois textes et quatre images insérés dans les coins ;
  • si on appuie sur la touche S un écran apparaît avec un damier dont les cases changent de couleur chaque seconde ;
  • si on appuie sur la touche Q un écran de fin apparaît composé de trois textes et quatre images insérés dans les coins.

decouverte

Exercice 1

Pour commencer, téléchargez l'archive materiel.zip et ouvrez le fichier pyxel_decouverte.py dans le dossier premiers_pas. Complétez le squelette de code donné ci-dessous dans un éditeur Python pour satisfaire le cahier des charges fixé précédemment. On s'appuiera sur la documentation de Pyxel et le tutoriel de Laurent Abbal, en particulier pour l'utilisation de l'éditeur d'images. Les images sont dans le fichier scripts/game_of_life.pyxres qui doit être placé dans le même répertoire que le script et qui peut être édité depuis une ligne de commandes avec pyxel edit game_of_life.pyxres.

🐍 Script Python
"""
Découverte du module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 50
HAUTEUR = 50
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="découverte de pyxel")


# =========================================================
# == CHARGEMENT DES IMAGES
# =========================================================
pyxel.load("game_of_life.pyxres")

# =========================================================
# == FONCTIONS 
# =========================================================

def initialiser_grille():
    """Initialise une grille de dimensions LARGEUR x HAUTEUR  avec :
        grille[lig][col] = MORT si lig et col de même parité
        sinon grille[lig][col] = VIE    
    """
    # à compléter
    
    
def inverser_etat(etat):
    """Inverse l'état d'une case dans une grille : MORT- > VIE et VIE -> MORT"""
    # à compléter
        
def evolution_grille(grille):
    """Mise à jour de la grille en inversant l'éat de chaque case"""
    return grille

# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), "Hello", COULEUR[TEXTE]
    )
    # à compléter pour afficher les 2 autres textes
    # positionnement d'images 3 x 3 dans les 4 coins
    pyxel.blt(0, 0, 0, 0, 0, 3, 3)
    # à compléter pour afficher les 3 autres images


def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.4), int(HAUTEUR * 0.5), "Fin", COULEUR[TEXTE])
    # positionnement d'images 8 x 8 dans les 4 coins
    # à compléter pour afficher les 4 images

# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    # à compléter pour traiter l'appui sur la touche Q
    if pyxel.frame_count % 30 == 0:
        # mise à jour  de la grille
        jeu["grille"] = evolution_grille(jeu["grille"])
    


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    # à compléter avec le dessin du damier


# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": initialiser_grille(),
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# lancement de l'application
pyxel.run(update, draw)
🐍 Script Python
"""
Découverte du module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 50
HAUTEUR = 50
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="découverte de pyxel")


# =========================================================
# == CHARGEMENT DES IMAGES
# =========================================================
pyxel.load("game_of_life.pyxres")

# =========================================================
# == FONCTIONS 
# =========================================================

def initialiser_grille():
    """Initialise une grille de dimensions LARGEUR x HAUTEUR  avec :
        grille[lig][col] = MORT si lig et col de même parité
        sinon grille[lig][col] = VIE    
    """
    # à compléter
    # BEGIN CUT
    grille = [[MORT for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            if (lig + col) % 2 == 0:
                grille[lig][col] = VIE
    return grille
    # END CUT
    
    
def inverser_etat(etat):
    """Inverse l'état d'une case dans une grille : MORT- > VIE et VIE -> MORT"""
    # à compléter
    # BEGIN CUT
    if etat == VIE:
        return MORT
    else:
        return VIE
    # END CUT
        
def evolution_grille(grille):
    """Mise à jour de la grille en inversant l'éat de chaque case"""
    # BEGIN CUT
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            grille[lig][col] = inverser_etat(grille[lig][col])
    # END CUT
    return grille

# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), "Hello", COULEUR[TEXTE]
    )
    # à compléter pour afficher les 2 autres textes
    # BEGIN CUT
    pyxel.text(
        int(LARGEUR * 0.05), int(HAUTEUR * 0.5), "S -> start", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.05), int(HAUTEUR * 0.75), "Q -> quit", COULEUR[TEXTE]
    )
    # END CUT
    # positionnement d'images 3 x 3 dans les 4 coins
    pyxel.blt(0, 0, 0, 0, 0, 3, 3)
    # à compléter pour afficher les 3 autres images
    # BEGIN CUT
    pyxel.blt(HAUTEUR - 3, 0, 0, 8, 0, 3, 3)
    pyxel.blt(0, LARGEUR - 3, 0, 0, 5, 3, 3)
    pyxel.blt(LARGEUR - 3, HAUTEUR - 3, 0, 8, 5, 3, 3)
    # END CUT


def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.4), int(HAUTEUR * 0.5), "Fin", COULEUR[TEXTE])
    # positionnement d'images 8 x 8 dans les 4 coins
    # à compléter pour afficher les 4 images
    # BEGIN CUT
    pyxel.blt(0, 0, 0, 0, 8, 8, 8)
    pyxel.blt(HAUTEUR - 8, 0, 0, 0, 8, 8, 8)
    pyxel.blt(0, LARGEUR - 8, 0, 0, 8, 8, 8)
    pyxel.blt(LARGEUR - 8, HAUTEUR - 8, 0, 0, 8, 8, 8)
    # END CUT

# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    # à compléter pour traiter l'appui sur la touche Q
    # BEGIN CUT
    if pyxel.btn(pyxel.KEY_Q):
        jeu["fin"] = True
    # END CUT
    if pyxel.frame_count % 30 == 0:
        # mise à jour  de la grille
        jeu["grille"] = evolution_grille(jeu["grille"])
    


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    # à compléter avec le dessin du damier
    # BEGIN CUT
    else:
        # dessin de la grille
        for lig in range(HAUTEUR):
            for col in range(LARGEUR):
                pyxel.rect(col, lig, 1, 1, COULEUR[jeu["grille"][lig][col]])
    # END CUT


# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": initialiser_grille(),
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# lancement de l'application
pyxel.run(update, draw)
Video

Jeu de la vie⚓︎

Présentation⚓︎

Sources :

Le jeu de la vie inventé par le mathématicien anglais J. H. Conway en 1970, est un automate cellulaire en deux dimensions.

Les automates cellulaires et le jeu de la vie en particulier sont des systèmes complexes étudiés en mathématiques et en informatique théorique. Ainsi, l'automate de Conway a été prouvé turing-complet c'est-à-dire qu'il peut exécuter les mêmes algorithmes qu'un ordinateur.

L'univers du jeu est une grille infinie de cases appelées cellules.

Au départ un nombre fini de cellules sont vivantes et toutes les autres sont mortes.

Chaque cellule possède un voisinage de 8 cellules voisines (sauf les cellules sur les bords dans notre simulation).

voisinage

Info

A chaque génération, l'univers évolue selon trois règles simples de changement d'état pour chaque cellule :

  1. Règle 1 : Une cellule vivante qui n'a pas au moins 2 voisines vivantes meurt par isolement.
  2. Règle 2 : Une cellule vivante qui possède 4 voisines vivantes ou plus meurt par étouffement.
  3. Règle 3 : Une cellule morte qui possède exactement trois cellules vivantes, devient vivante, sinon elle reste morte.

Programmation avec pyxel étape 1⚓︎

Vous allez programmer le jeu de la vie en quelques étapes.

Exercice 2

  1. Si vous avez sauté l'étape précédente, téléchargez l'archive materiel.zip.
  2. Oepuis le dossier etape1, éditez le script game_of_life_outils.py.

Les cellules en nombre fini seront enregistrées dans une grille de dimensions fixées par les variables globales LARGEUR et HAUTEUR.

Dans l'exercice suivant, vous allez écrire des fonctions outils de manipulation de la grille. Copiez puis complétez le squelette fourni dans game_of_life1_outils.py ouvert avec votre IDE Python.

Exercice 3
🐍 Script Python
"""
Jeu de la vie avec le module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 80
HAUTEUR = 80
VIE = 1
MORT = 0

# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    return [[grille[lig][col] for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    
    
def test_copie_grille():
    import random
    t = [[random.randint(1, 100) for _ in range(LARGEUR)] for _ in range(HAUTEUR)]
    t2 = copie_grille(t)
    assert (t2 == t) and (id(t2) != id(t))
    print("Tests réussis pour copie_grille")
    
    
def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """

def test_initialiser_grille():
    grille = [[MORT for _ in range(LARGEUR)] for __ in range(HAUTEUR)]
    motif = [(random.randint(0, 79), random.randint(0, 79)) for _ in range(50)]
    initialiser_grille(grille, motif)
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            assert (((lig, col) in motif) and grille[lig][col] == VIE) or grille[lig][col] == MORT
    print("Tests réussis pour initialiser_grille")
    
def nombre_voisins_vivants(grille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    

def test_nombre_voisins_vivants():
    grille1 = [[VIE, MORT, VIE], [VIE, VIE, MORT], [MORT, MORT, VIE]]
    assert nombre_voisins_vivants(grille1, 1, 1) == 4
    grille2 = [[VIE, MORT, VIE], [VIE, MORT, MORT], [MORT, MORT, MORT]]
    assert nombre_voisins_vivants(grille2, 1, 1) == 3
    grille3 = [[MORT, MORT, MORT], [VIE, VIE, VIE], [MORT, MORT, MORT]]
    assert nombre_voisins_vivants(grille3, 1, 1) == 3
    print("Tests réussis pour nombre_voisins_vivants")

def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    
def test_evolution_cellule():
    grille1 = [[VIE, MORT, VIE], [VIE, VIE, MORT], [MORT, MORT, VIE]]
    assert nombre_voisins_vivants(grille1, 1, 1) == MORT
    grille2 = [[VIE, MORT, VIE], [VIE, MORT, MORT], [MORT, MORT, VIE]]
    assert nombre_voisins_vivants(grille2, 1, 1) == VIE
    grille3 = [[MORT, MORT, MORT], [VIE, VIE, VIE], [MORT, MORT, MORT]]
    assert nombre_voisins_vivants(grille3, 1, 1) == MORT
    grille4 = [[MORT, MORT, VIE], [VIE, VIE, VIE], [MORT, MORT, MORT]]
    assert nombre_voisins_vivants(grille3, 1, 1) == VIE
    grille5 = [[MORT, MORT, VIE], [VIE, VIE, MORT], [MORT, MORT, MORT]]
    assert nombre_voisins_vivants(grille3, 1, 1) == MORT
    print("Tests réussis pour nombre_evolution_cellule")

def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
🐍 Script Python
# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    # à compléter
    # BEGIN CUT
    return [[grille[lig][col] for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    # END CUT
    

def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """
    # à compléter
    # BEGIN CUT
    for (lig, col) in motif:
        grille[lig][col] = VIE
    # END CUT


def nombre_voisins_vivants(grille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    # à compléter
    # BEGIN CUT
    v = 0
    for dl in range(-1, 2):
        for dc in range(-1, 2):
            if (
                (0 <= lig + dl < HAUTEUR)
                and (0 <= col + dc < LARGEUR)
                and (dl, dc) != (0, 0)
                and grille[lig + dl][col + dc] == VIE
            ):
                v = v + 1
    return v
    # END CUT


def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    # à compléter
    # BEGIN CUT
    etat = grille[lig][col]
    voisins_vivants = nombre_voisins_vivants(grille, lig, col)
    if etat == MORT:
        if voisins_vivants == 3:
            return VIE
        else:
            return MORT
    else:
        if 2 <= voisins_vivants <= 3:
            return VIE
        else:
            return MORT
    # END CUT


def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
    # à compléter
    # BEGIN CUT
    grille_nouvelle = copie_grille(grille)
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            grille_nouvelle[lig][col] = evolution_cellule(grille, lig, col)
    return grille_nouvelle
    # END CUT

Programmation avec pyxel étape 2⚓︎

On considère que l'archive materiel.zip a été déballée.

Complétez les fonctions d'affichage dans game_of_life1_eleves.py (du dossier etape1) pour obtenir la simulation de l'automate nommé planeur (glider en anglais).

Copiez et testez le code ouvert avec votre IDE Python.

Info

La configuration initiale de l'automate est enregistrée dans un fichier texte au format Plain décrit dans https://conwaylife.com/wiki/Plaintext. Elle est chargée par le programme dans un tableau de tableaux à l'aide de la fonction charger_fichier.

Deux automates sont livrés dans materiel.zip :

  • glider.cells
  • period60glidergun.cells

Testez le code dans un environnement de programmation Python où le module pyxel est installé.

On donne ci-dessous l'affichage avant et après que le tout le code manquant soit complété.

Avant / Après

decouverte

decouverte

Exercice 4
🐍 Script Python
"""
Jeu de la vie avec le module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 80
HAUTEUR = 80
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="jeu de la vie")



# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    # à compléter
    

def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """
    # à compléter


def nombre_voisins_vivants(gille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    # à compléter


def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    # à compléter


def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
    # à compléter

# =========================================================
# == INITIALISER LE MOTIF
# =========================================================
def charger_fichier(chemin):
    """
    Charger un motif stocké dans un fichier au format Plain
    https://conwaylife.com/wiki/Plaintext
    """
    f = open(chemin)
    ligne1 = f.readline().rstrip()
    titre = " ".join(ligne1.split()[1:])
    motif = []
    for ligne in f:
        if ligne[0] != "!":
            motif.append([c for c in ligne.rstrip()])
    return titre, motif


def motif_dans_grille(lig0, col0, motif, grille):
    """
    Recopie un motif (sous-grille) dans grille à partir de 
    la position (lig0, col0) en coin supérieur gauche
    """
    decodage = {".": MORT, "O": VIE}
    largeur_motif = len(motif[0])
    hauteur_motif = len(motif)
    assert lig0 + hauteur_motif <= len(grille) and col0 + largeur_motif <= len(
        grille[0]
    ), "Motif trop grand"
    for lig in range(lig0, lig0 + hauteur_motif):
        for col in range(col0, col0 + largeur_motif):
            grille[lig][col] = decodage[motif[lig - lig0][col - col0]]


# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), f"{jeu['nom']}", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.5), "Press s to start", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.6), "Press q to quit", COULEUR[TEXTE]
    )
    # Positionnement d'une image dans chaque coin
    # à compléter



def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.25), int(HAUTEUR * 0.5), "Fin de la vie", COULEUR[TEXTE])

    # Positionnement d'une image dans chaque coin
    # à compléter



# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    if pyxel.btn(pyxel.KEY_Q):
        jeu["fin"] = True
    # mise à jour de la grille (3 fois par seconde)    
    if pyxel.frame_count % 10 == 0:
        # à compléter
        "ecrire votre code ici"


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    else:
        # dessin de la grille à compléter
        # dessinez chaque case avec pyxel.rect
        # un exemple avec 4 cases
        pyxel.rect(0, 0, 1, 1, COULEUR[MORT])
        pyxel.rect(LARGEUR // 2, HAUTEUR // 2, 1, 1, COULEUR[VIE])
        pyxel.rect(0, HAUTEUR - 1, 1, 1, COULEUR[VIE])
        pyxel.rect(LARGEUR - 1, HAUTEUR - 1, 1, 1, COULEUR[MORT])
        # à compléter pour tracer la grille complète


# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": [[MORT for __ in range(LARGEUR)] for _ in range(HAUTEUR)],
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# on charge l'automate enregistré dans le fichier "glider.cells" au format Plain
# voir https://conwaylife.com/wiki/Plaintext
nom, motif = charger_fichier("glider.cells")
jeu["nom"] = nom
motif_dans_grille(0, 0, motif, jeu["grille"])
# lancement de l'application
pyxel.run(update, draw)
🐍 Script Python
"""
Jeu de la vie avec le module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 80
HAUTEUR = 80
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="jeu de la vie")

# BEGIN CUT
# =========================================================
# == CHARGEMENT DES IMAGES
# =========================================================
pyxel.load("game_of_life.pyxres")
# END CUT


# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    # à compléter
    # BEGIN CUT
    return [[grille[lig][col] for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    # END CUT
    

def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """
    # à compléter
    # BEGIN CUT
    for (lig, col) in motif:
        grille[lig][col] = VIE
    # END CUT


def nombre_voisins_vivants(grille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    # à compléter
    # BEGIN CUT
    v = 0
    for dl in range(-1, 2):
        for dc in range(-1, 2):
            if (
                (0 <= lig + dl < HAUTEUR)
                and (0 <= col + dc < LARGEUR)
                and (dl, dc) != (0, 0)
                and grille[lig + dl][col + dc] == VIE
            ):
                v = v + 1
    return v
    # END CUT


def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    # à compléter
    # BEGIN CUT
    etat = grille[lig][col]
    voisins_vivants = nombre_voisins_vivants(grille, lig, col)
    if etat == MORT:
        if voisins_vivants == 3:
            return VIE
        else:
            return MORT
    else:
        if 2 <= voisins_vivants <= 3:
            return VIE
        else:
            return MORT
    # END CUT


def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
    # à compléter
    # BEGIN CUT
    grille_nouvelle = copie_grille(grille)
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            grille_nouvelle[lig][col] = evolution_cellule(grille, lig, col)
    return grille_nouvelle
    # END CUT

# =========================================================
# == INITIALISER LE MOTIF
# =========================================================
def charger_fichier(chemin):
    """
    Charger un motif stocké dans un fichier au format Plain
    https://conwaylife.com/wiki/Plaintext
    """
    f = open(chemin)
    ligne1 = f.readline().rstrip()
    titre = " ".join(ligne1.split()[1:])
    motif = []
    for ligne in f:
        if ligne[0] != "!":
            motif.append([c for c in ligne.rstrip()])
    return titre, motif


def motif_dans_grille(lig0, col0, motif, grille):
    """
    Recopie un motif (sous-grille) dans grille à partir de 
    la position (lig0, col0) en coin supérieur gauche
    """
    decodage = {".": MORT, "O": VIE}
    largeur_motif = len(motif[0])
    hauteur_motif = len(motif)
    assert lig0 + hauteur_motif <= len(grille) and col0 + largeur_motif <= len(
        grille[0]
    ), "Motif trop grand"
    for lig in range(lig0, lig0 + hauteur_motif):
        for col in range(col0, col0 + largeur_motif):
            grille[lig][col] = decodage[motif[lig - lig0][col - col0]]


# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), f"{jeu['nom']}", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.5), "Press s to start", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.6), "Press q to quit", COULEUR[TEXTE]
    )



def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.25), int(HAUTEUR * 0.5), "Fin de la vie", COULEUR[TEXTE])



# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    if pyxel.btn(pyxel.KEY_Q):
        jeu["fin"] = True
    # mise à jour de la grille (3 fois par seconde)    
    if pyxel.frame_count % 10 == 0:
        # à compléter
        "ecrire votre code ici"
        # BEGIN CUT
        jeu["grille"] = evolution_grille(jeu["grille"])
        # END CUT


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    else:
        # dessin de la grille à compléter
        # dessinez chaque case avec pyxel.rect
        # un exemple avec 4 cases
        pyxel.rect(0, 0, 1, 1, COULEUR[MORT])
        pyxel.rect(LARGEUR // 2, HAUTEUR // 2, 1, 1, COULEUR[VIE])
        pyxel.rect(0, HAUTEUR - 1, 1, 1, COULEUR[VIE])
        pyxel.rect(LARGEUR - 1, HAUTEUR - 1, 1, 1, COULEUR[MORT])
        # à compléter pour tracer la grille complète
        # BEGIN CUT
        for lig in range(HAUTEUR):
            for col in range(LARGEUR):
                pyxel.rect(col, lig, 1, 1, COULEUR[jeu["grille"][lig][col]])
        # END CUT


# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": [[MORT for __ in range(LARGEUR)] for _ in range(HAUTEUR)],
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# on charge l'automate enregistré dans le fichier "glider.cells" au format Plain
# voir https://conwaylife.com/wiki/Plaintext
nom, motif = charger_fichier("glider.cells")
jeu["nom"] = nom
motif_dans_grille(0, 0, motif, jeu["grille"])
# lancement de l'application
pyxel.run(update, draw)

Programmation avec pyxel étape 3⚓︎

Dans l'archive materiel.zip se trouve un fichier game_of_life.pyxres. Éditez ce fichier depuis une console avec l'éditeur d'image de pyxel et la commande pyxel edit game_of_life.pyxres.

editeur

Info

Voir https://nuitducode.github.io/DOCUMENTATION/PYTHON/08-tutoriel-06/ ou la documentation pour l'utilisation de l'éditeur assez intuitive.

On charge les images depuis le fichier game_of_life.pyxres avec pyxel.load("game_of_life.pyxres").

pyxel.blt(0, 0, 0, 0, 0, 3, 3) permet de placer en (0,0) la partie de l'image 0 contenue dans game_of_life.pyxres qui se trouve en (0,0) et de dimensions (3,3).

On veut insérer des images dans les quatre coins de la page de menu d'accueil et de fin.

On donne ci-dessous l'affichage avant et après que le tout le code manquant soit complété.

Avant / Après

decouverte

decouverte

On considère que l'archive materiel.zip a été déballée. Recopiez et complétez le squelette de code dans le fichier game_of_life2_eleves.py (du dossier etape2) ouvert avec votre IDE Python.

Exercice 5
🐍 Script Python
"""
Jeu de la vie avec le module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 80
HAUTEUR = 80
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="jeu de la vie")


# =========================================================
# == CHARGEMENT DES IMAGES
# =========================================================
pyxel.load("game_of_life.pyxres")


# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    return [[grille[lig][col] for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    

def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """
    for (lig, col) in motif:
        grille[lig][col] = VIE


def nombre_voisins_vivants(grille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    v = 0
    for dl in range(-1, 2):
        for dc in range(-1, 2):
            if (
                (0 <= lig + dl < HAUTEUR)
                and (0 <= col + dc < LARGEUR)
                and (dl, dc) != (0, 0)
                and grille[lig + dl][col + dc] == VIE
            ):
                v = v + 1
    return v


def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    etat = grille[lig][col]
    voisins_vivants = nombre_voisins_vivants(grille, lig, col)
    if etat == MORT:
        if voisins_vivants == 3:
            return VIE
        else:
            return MORT
    else:
        if 2 <= voisins_vivants <= 3:
            return VIE
        else:
            return MORT


def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
    grille_nouvelle = copie_grille(grille)
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            grille_nouvelle[lig][col] = evolution_cellule(grille, lig, col)
    return grille_nouvelle

# =========================================================
# == INITIALISER LE MOTIF
# =========================================================
def charger_fichier(chemin):
    """
    Charger un motif stocké dans un fichier au format Plain
    https://conwaylife.com/wiki/Plaintext
    """
    f = open(chemin)
    ligne1 = f.readline().rstrip()
    titre = " ".join(ligne1.split()[1:])
    motif = []
    for ligne in f:
        if ligne[0] != "!":
            motif.append([c for c in ligne.rstrip()])
    return titre, motif


def motif_dans_grille(lig0, col0, motif, grille):
    """
    Recopie un motif (sous-grille) dans grille à partir de 
    la position (lig0, col0) en coin supérieur gauche
    """
    decodage = {".": MORT, "O": VIE}
    largeur_motif = len(motif[0])
    hauteur_motif = len(motif)
    assert lig0 + hauteur_motif <= len(grille) and col0 + largeur_motif <= len(
        grille[0]
    ), "Motif trop grand"
    for lig in range(lig0, lig0 + hauteur_motif):
        for col in range(col0, col0 + largeur_motif):
            grille[lig][col] = decodage[motif[lig - lig0][col - col0]]


# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), f"{jeu['nom']}", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.5), "Press s to start", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.6), "Press q to quit", COULEUR[TEXTE]
    )
    # Positionnement d'une image dans chaque coin
    # image dans le coin supérieur gauche
    pyxel.blt(0, 0, 0, 0, 0, 3, 3)
    # à compléter


def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.25), int(HAUTEUR * 0.5), "Fin de la vie", COULEUR[TEXTE])
    # Positionnement d'une image dans chaque coin
    # à compléter


# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    if pyxel.btn(pyxel.KEY_Q):
        jeu["fin"] = True
    # mise à jour de la grille (3 fois par seconde)    
    if pyxel.frame_count % 10 == 0:
        jeu["grille"] = evolution_grille(jeu["grille"])


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    else:
        # dessin de la grille à compléter
        # dessinez chaque case avec pyxel.rect
        # un exemple avec 4 cases
        pyxel.rect(0, 0, 1, 1, COULEUR[MORT])
        pyxel.rect(LARGEUR // 2, HAUTEUR // 2, 1, 1, COULEUR[VIE])
        pyxel.rect(0, HAUTEUR - 1, 1, 1, COULEUR[VIE])
        pyxel.rect(LARGEUR - 1, HAUTEUR - 1, 1, 1, COULEUR[MORT])
        for lig in range(HAUTEUR):
            for col in range(LARGEUR):
                pyxel.rect(col, lig, 1, 1, COULEUR[jeu["grille"][lig][col]])



# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": [[MORT for __ in range(LARGEUR)] for _ in range(HAUTEUR)],
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# on charge l'automate enregistré dans le fichier "glider.cells" au format Plain
# voir https://conwaylife.com/wiki/Plaintext
nom, motif = charger_fichier("period60glidergun.cells")
jeu["nom"] = nom
motif_dans_grille(0, 0, motif, jeu["grille"])
# lancement de l'application
pyxel.run(update, draw)
🐍 Script Python
"""
Jeu de la vie avec le module pyxel

https://github.com/kitao/pyxel/blob/main/doc/README.fr.md
"""

import pyxel

# =========================================================
# == CONSTANTES
# =========================================================
LARGEUR = 80
HAUTEUR = 80
VIE = 1
MORT = 0
TEXTE = 2
FONDS = 3
COULEUR = {VIE: 8, MORT: 0, TEXTE: 5, FONDS: 10}

# =========================================================
# == INITIALISATION FENETRE
# =========================================================
pyxel.init(LARGEUR, HAUTEUR, title="jeu de la vie")


# =========================================================
# == CHARGEMENT DES IMAGES
# =========================================================
pyxel.load("game_of_life.pyxres")


# =========================================================
# == FONCTIONS DE GRILLE
# =========================================================
def copie_grille(grille):
    """Renvoie une copie profonde de la grille"""
    return [[grille[lig][col] for col in range(LARGEUR)] for lig in range(HAUTEUR)]
    

def initialiser_grille(grille, motif):
    """
    Affecte l'état VIE à toutes les cases de grille 
    listées dans motif comme couples (lig, col)
    """
    for (lig, col) in motif:
        grille[lig][col] = VIE


def nombre_voisins_vivants(grille, lig, col):
    """Renvoie le nombre de voisins  vivants de la cellule en (lig, col)"""
    v = 0
    for dl in range(-1, 2):
        for dc in range(-1, 2):
            if (
                (0 <= lig + dl < HAUTEUR)
                and (0 <= col + dc < LARGEUR)
                and (dl, dc) != (0, 0)
                and grille[lig + dl][col + dc] == VIE
            ):
                v = v + 1
    return v


def evolution_cellule(grille, lig, col):
    """
    Renvoie le nouvel état (VIE ou MORT)de la cellule en (lig, col) 
    dans grille en fonction de son nombre de voisins vivants
    """
    etat = grille[lig][col]
    voisins_vivants = nombre_voisins_vivants(grille, lig, col)
    if etat == MORT:
        if voisins_vivants == 3:
            return VIE
        else:
            return MORT
    else:
        if 2 <= voisins_vivants <= 3:
            return VIE
        else:
            return MORT


def evolution_grille(grille):
    """
    Crée une copie profonde de grille (génération n)
    Remplit cette copie avec l'évolution de chaque cellule de grille
    Renvoie cette nouvelle grille (génération n + 1)
    """
    grille_nouvelle = copie_grille(grille)
    for lig in range(HAUTEUR):
        for col in range(LARGEUR):
            grille_nouvelle[lig][col] = evolution_cellule(grille, lig, col)
    return grille_nouvelle

# =========================================================
# == INITIALISER LE MOTIF
# =========================================================
def charger_fichier(chemin):
    """
    Charger un motif stocké dans un fichier au format Plain
    https://conwaylife.com/wiki/Plaintext
    """
    f = open(chemin)
    ligne1 = f.readline().rstrip()
    titre = " ".join(ligne1.split()[1:])
    motif = []
    for ligne in f:
        if ligne[0] != "!":
            motif.append([c for c in ligne.rstrip()])
    return titre, motif


def motif_dans_grille(lig0, col0, motif, grille):
    """
    Recopie un motif (sous-grille) dans grille à partir de 
    la position (lig0, col0) en coin supérieur gauche
    """
    decodage = {".": MORT, "O": VIE}
    largeur_motif = len(motif[0])
    hauteur_motif = len(motif)
    assert lig0 + hauteur_motif <= len(grille) and col0 + largeur_motif <= len(
        grille[0]
    ), "Motif trop grand"
    for lig in range(lig0, lig0 + hauteur_motif):
        for col in range(col0, col0 + largeur_motif):
            grille[lig][col] = decodage[motif[lig - lig0][col - col0]]


# =========================================================
# == AFFICHAGES MENU / FIN
# =========================================================


def afficher_menu():
    """Affichage du menu d'accueil"""
    pyxel.text(
        int(LARGEUR * 0.25), int(HAUTEUR * 0.25), f"{jeu['nom']}", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.5), "Press s to start", COULEUR[TEXTE]
    )
    pyxel.text(
        int(LARGEUR * 0.1), int(HAUTEUR * 0.6), "Press q to quit", COULEUR[TEXTE]
    )
    # Positionnement d'une image dans chaque coin
    # à compléter
    # BEGIN CUT    
    pyxel.blt(0, 0, 0, 0, 0, 3, 3)
    pyxel.blt(HAUTEUR - 3, 0, 0, 8, 0, 3, 3)
    pyxel.blt(0, LARGEUR - 3, 0, 0, 5, 3, 3)
    pyxel.blt(LARGEUR - 3, HAUTEUR - 3, 0, 8, 5, 3, 3)
    # END CUT


def afficher_fin():
    """Affichage de fin"""
    pyxel.text(int(LARGEUR * 0.25), int(HAUTEUR * 0.5), "Fin de la vie", COULEUR[TEXTE])
    # Positionnement d'une image dans chaque coin
    # à compléter
    # BEGIN CUT    
    pyxel.blt(0, 0, 0, 0, 8, 8, 8)
    pyxel.blt(HAUTEUR - 8, 0, 0, 0, 8, 8, 8)
    pyxel.blt(0, LARGEUR - 8, 0, 0, 8, 8, 8)
    pyxel.blt(LARGEUR - 8, HAUTEUR - 8, 0, 0, 8, 8, 8)
    # END CUT


# =========================================================
# == UPDATE
# =========================================================
def update():
    """mise à jour des variables (30 fois par seconde)"""
    # une génération par seconde
    if pyxel.btn(pyxel.KEY_S):
        jeu["menu"] = False
    if pyxel.btn(pyxel.KEY_Q):
        jeu["fin"] = True
    # mise à jour de la grille (3 fois par seconde)    
    if pyxel.frame_count % 10 == 0:
        jeu["grille"] = evolution_grille(jeu["grille"])


# =========================================================
# == DRAW
# =========================================================
def draw():
    """création des objets (30 fois par seconde)"""
    # vide la fenetre
    pyxel.cls(COULEUR[FONDS])
    if jeu["fin"]:
        afficher_fin()
    elif jeu["menu"]:
        afficher_menu()
    else:
        # dessin de la grille à compléter
        # dessinez chaque case avec pyxel.rect
        # un exemple avec 4 cases
        pyxel.rect(0, 0, 1, 1, COULEUR[MORT])
        pyxel.rect(LARGEUR // 2, HAUTEUR // 2, 1, 1, COULEUR[VIE])
        pyxel.rect(0, HAUTEUR - 1, 1, 1, COULEUR[VIE])
        pyxel.rect(LARGEUR - 1, HAUTEUR - 1, 1, 1, COULEUR[MORT])
        for lig in range(HAUTEUR):
            for col in range(LARGEUR):
                pyxel.rect(col, lig, 1, 1, COULEUR[jeu["grille"][lig][col]])



# =========================================================
# == DICTIONNAIRE GLOBAL DU JEU
# =========================================================
jeu = {
    "grille": [[MORT for __ in range(LARGEUR)] for _ in range(HAUTEUR)],
    "menu": True,
    "fin": False,
}
# =========================================================
# == PROGRAMME PRINCIPAL
# =========================================================
# on charge l'automate enregistré dans le fichier "glider.cells" au format Plain
# voir https://conwaylife.com/wiki/Plaintext
nom, motif = charger_fichier("period60glidergun.cells")
jeu["nom"] = nom
motif_dans_grille(0, 0, motif, jeu["grille"])
# lancement de l'application
pyxel.run(update, draw)