Un Puissance 4 sur ta NumWorks !

Projets

Le Puissance 4 est probablement un jeu de votre enfance, c’est pourquoi je vous propose de jouer à ma version du Puissance 4 directement sur votre NumWorks avec un ami.

L’idée

L’idée du Puissance 4 a été longuement réfléchi car le projet de ces vacances était libre et j’avais eu l’idée de faire un jeu sur la NumWorks mais je ne savais pas lequel faire. Après avoir parcouru le site à la recherche de jeu original qui n’avait pas été déjà fait, l’idée du Puissance 4 m’est venu à l’esprit.

La réalisation

Pour réaliser le jeu j’ai avant tout fait en sorte qu’il soit jouable sur la console avant de faire la partie graphique de la calculatrice (même si certains ajouts ont été fait pendant le développement de la partie graphique).

Le Script

Je vais maintenant vous présenter le script et vous faire une brève explication de chacune des fonctions :

from kandinsky import fill_rect as rect, draw_string as txt
from time import sleep
from ion import keydown

Au tout début du script nous avons évidemment les appels des différents modules :

  • Kandinsky : c’est un des modules propriétaire de NumWorks qui est utilisé afin de dessiner des rectangles ou même écrire du texte sur l’écran de la calculatrice
  • Time : permet avec la fonction sleep que j’appel de mettre des pause dans le script
  • Ion : c’est le second module propriétaire de NumWorks qui permet de prendre en compte l’appui des touches pendant le script.

Nous avons ensuite la définition des variables globales :

# 1 = rouge // 2 = jaune
# Variables globales
player = 1 #Définit le joueur qui doit jouer
grille_preview = [0, 0, 0, 0, 0, 0, 0] #Est utilisé pour déterminer les positions possible du preview
grille = [[0 for i in range(7)] for i in range(6)] #La matrice qui représente la grille de jeu

#Les couleurs utilisées
rouge = (182, 2, 5)
jaune = (255, 181, 49)
gris = (191, 189, 193)

pos = 3 #Donne la position du preview

#Les points des joueurs
points_rouge = 0
points_jaune = 0

Ensuite une des fonctions majeures du jeu : la fonction de vérification. Cette fonction va après chaque coup vérifier toutes les positions afin de voir si il y a un gagnant ou pas.

def verifie(): #Gagnant ?
  for i in range(6): #lignes
    for j in range(4):
      if grille[i][j] == player and grille[i][j+1] == player and grille[i][j+2] == player and grille[i][j+3] == player:
        gagnant(player)
  for i in range(3): #colonnes
    for j in range(7):
      if grille[i][j] == player and grille[i+1][j] == player and grille[i+2][j] == player and grille[i+3][j] == player:
        gagnant(player)
  for i in range(3): #diagonales
    for j in range(4):
      if grille[i][j] == player and grille[i+1][j+1] == player and grille[i+2][j+2] == player and grille[i+3][j+3] == player:
        gagnant(player)
  for i in range(3, 6):
    for j in range(4):
      if grille[i][j] == player and grille[i-1][j+1] == player and grille[i-2][j+2] == player and grille[i-3][j+3] == player:
        gagnant(player)

Cependant je n’ai pas tout dit à propos de cette fonction car celle ci n’est pas tout à fait de moi : en effet avant d’avoir cette fonction là j’avais codé une fonction vérification mais celle ci faisait environ 70 lignes et n’était pas du tout optimisé ce qui amenait à des ralentissement, ChatGPT m’a donc aidé à réduire la fonction afin d’avoir la version ci-dessus. Et donc voici en exclusivité ma fonction de vérification originelle :

def verifie():
    #horizontalement
    for i in range(6):
        for j in range(4):
            rouge = 0
            jaune = 0
            for k in range(4):
                if grille[i][j+k] == 1 :
                    rouge += 1
                elif grille[i][j+k] == 2 :
                    jaune += 1
            if rouge == 4 :
                player_won(1)
            elif jaune == 4 :
                player_won(2)
    
    #verticalement
    for i in range(3):
        for j in range(7):
            rouge = 0
            jaune = 0
            for k in range(4):
                if grille[i+k][j] == 1 :
                    rouge += 1
                elif grille[i+k][j] == 2 :
                    jaune += 1
            if rouge == 4 :
                player_won(1)
            elif jaune == 4 :
                player_won(2)

    #diagonalement
    """(ij)
    /
    2 diag de 4 : 03 12 21 30 // 26 35 44 53
    2 diag de 5 : 04 13 22 31 40 // 16 25 34 43 52
    2 diag de 6 : 05 14 23 32 41 50 // 06 15 24 33 42 51
    
    \
    2 diag de 4 : 03 14 25 36 // 20 31 42 53
    2 diag de 5 : 02 13 24 35 46 // 10 21 32 43 54
    2 diag de 6 : 00 11 22 33 44 55 // 01 12 23 34 45 56
    """

    #création des listes pour vérifier
    diag4 = [[[0, 3], [1, 2], [2, 1], [3, 0]], [[2, 6], [3, 5], [4, 4], [5, 3]], [[0,3], [1,4], [2,5], [3,6]], [[2,0], [3,1], [4,2], [5,3]]]
    diag5 = [[[0, 4], [1, 3], [2, 2], [3, 1], [4, 0]], [[1, 6], [2, 5], [3, 4], [4, 3], [5, 2]], [[0, 2], [1, 3], [2, 4], [3, 5], [4, 6]], [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]]
    diag6 = [[[0, 5], [1, 4], [2, 3], [3, 2], [4, 1], [5,0]],[[0, 6], [1, 5], [2, 4], [3, 3], [4, 2], [5,1]],[[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5,5]],[[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5,6]]]

    #vérification des diagonales de 4
    for i in range(4):
        if grille[diag4[i][0][0]][diag4[i][0][1]] == grille[diag4[i][1][0]][diag4[i][1][1]] == grille[diag4[i][2][0]][diag4[i][2][1]] == grille[diag4[i][3][0]][diag4[i][3][1]] :
            if grille[diag4[i][0][0]][diag4[i][0][1]] == 1 :
                player_won(1)
            elif grille[diag4[i][0][0]][diag4[i][0][1]] == 2 :
                player_won(2)

    #vérification des diagonales de 5
    for i in range(4):
        for j in range(2):
            if grille[diag5[i][0+j][0]][diag5[i][0+j][1]] == grille[diag5[i][1+j][0]][diag5[i][1+j][1]] == grille[diag5[i][2+j][0]][diag5[i][2+j][1]] == grille[diag5[i][3+j][0]][diag5[i][3+j][1]] :
                if grille[diag5[i][0+j][0]][diag5[i][0+j][1]] == 1 :
                    player_won(1)
                elif grille[diag5[i][0+j][0]][diag5[i][0+j][1]] == 2 :
                    player_won(2)

    #vérification des diagonales de 6               
    for i in range(4):
        for j in range(3):
            if grille[diag6[i][0+j][0]][diag6[i][0+j][1]] == grille[diag6[i][1+j][0]][diag6[i][1+j][1]] == grille[diag6[i][2+j][0]][diag6[i][2+j][1]] == grille[diag6[i][3+j][0]][diag6[i][3+j][1]] :
                if grille[diag6[i][0+j][0]][diag6[i][0+j][1]] == 1 :
                    player_won(1)
                elif grille[diag6[i][0+j][0]][diag6[i][0+j][1]] == 2 :
                    player_won(2)

Ma fonction donc vérifiait chaque possibilité afin de gagner au jeu mais le plus long était surtout les diagonales qui n’étaient pas automatisées avec des boucles sans variables définies mais plutôt avec des listes interminables des positions possibles des diagonales et cela ralentissait considérablement le script. C’est pour cela que j’ai préféré utilisé la fonction de ChatGPT.

Par la suite nous avons une autre fonction majeure qui est celle qui est appelée dans le cas d’une fin de partie donc soit ci un joueur a gagné ou si il y a égalité et c’est cette même fonction qui redémarre une partie donc rénitialise la grille et actualise les scores :

def gagnant(winner): #Met fin à une partie gagnant ou pas
  global points_jaune, points_rouge, player, nb_partie, grille, partie
  affichage_grille()
  if winner == 1 : #Vérifie la variable winner pour savoir qui a gagné et agir en conséquence
    points_rouge += 1
    player = 2
    txt("Rouge a gagné !", 88, 20)
  elif winner == 2 :
    points_jaune += 1
    player = 1
    txt("Jaune a gagné !", 88,20)
  else :
    txt("C'est une égalité", 76, 20)
  wait() #La fonction attend qu'il y ai une touche de pressé (permet de voir la grille avant sa rénitialisation
  actu_src() #Actualisation des score
  grille = [[0 for i in range(7)] for i in range(6)] #Rénitialise la grille
  affichage_grille() #Affiche la grille

Ensuite nous avons la fonction selection qui est je dirai la dernière fonction majeure du script, elle permet au joueur de selectionner la colonne ou il souhaite ajouter son jeton et appel la fonction jouer que nous allons voir juste après :

def selection():
  global pos
  affichage_grille()
  preview(pos) #Affiche le preview du jeton (par défaut la 4ème colonne)
  add = 1
  while True : # Boucle qui permet de prendre en compte l'appui des touches et agit en conséquence.
    preview(pos)
    while colonne_pleine(pos):
      pos = (pos+add)%7
      preview(pos)
    key_pressed = wait() #Récupère la touche appuyé
    if key_pressed == 0: #Flèche de gauche // décale le preview vers la gauche
      add = -1
    if key_pressed == 3:#Flèche de droite // décale le preview vers la droite
      add = +1
    if key_pressed==0 or key_pressed==3: #Calcul la position du preview 
      pos = (pos+add)%7
      preview(pos)
    if key_pressed == 4 or key_pressed == 52 : #OK ou EXE (permet de jouer le coup)
      rect(75, 17, 170, 20, (255,255,255))
      jouer(pos)

Suite à cela il y a la fonction jouer qui est appelé dans la fonction selection et qui permet de jouer le coup dans la colonne sélectionné dans selection() :

def jouer(colonne): # Ajoute un jeton dans la colonne donnée
  global player
  if player == 1 : #Si c'est au tour de rouge
    animation(colonne) # Fais l'animation de chute du jeton
    grille[gravite(colonne)][colonne] = 1 #Modifie la matrice 
    verifie() #Vérifie si il y a un gagant
    player = 2 #Change de joueur
  elif player == 2 : #La même chose que pour le rouge mais pour le jaune
    animation(colonne)
    grille[gravite(colonne)][colonne] = 2
    verifie()
    player = 1
  grille_pleine() #Vérifie si la grille est pleine dans le cas d'un nul

Après il y a la fonction qui anime la chute du jeton dans la grille :

def animation(colonne): #Animation chute
  if player == 1 : #Définit la couleur du jeton
    color = rouge
  else :
    color = jaune
  for i in range(0, gravite(colonne)+1): #Dessine des jeton à la suite jursqu'à la dernière ligne
      ligne = (i-1)*(i!=0)
      rect(75+(25*colonne),42+(ligne*25),20,20,gris)
      sleep(0.05)
      draw_cercle(75 + (25*colonne) + 7, 42 + (i*25) + 2, color)
      sleep(0.05)

Ensuite la fonction qui dessine les cercles mais qui n’est pas de moi mais de M.Robert :

def draw_cercle(x,y,color): #Fait des cercles (Par VR)
  for d in range(6):
    rect(x-d+(d==0),y+d+(d==5),6+2*d-2*(d==0),16-2*d-2*(d==5), color)

Par la suite il y a ma fonction colonne_pleine qui dit si la colonne donnée en paramètre est pleine ou pas :

def colonne_pleine(colonne):
  if (grille[0][colonne] == 1) or (grille[0][colonne] == 2):
    return True
  return False

Une fonction similaire à la précedente : fonction grille_pleine utilise colonne_pleine afin de déterminer si la grille est pleine ou pas donc si il y a égalité :

def grille_pleine(): #Vérifie si il y a une égalité soit si la grille est pleine
  colonne_pleines = 0
  for i in range(7): #Passe en revue les premières lignes de chaque colonne
    if colonne_pleine(i):
      colonne_pleines += 1
  if colonne_pleines == 7: #Si toutes les colonnes sont pleine
    gagnant(0) #Appelle la fonction gagnant pour mettre fin à la partie

Il y a aussi la fonction gravité qui va retourner la ligne la plus basse où le jeton peut aller :

def gravite(colonne): #Détermine la ligne où le jeton peut se placer
  ligne = 5
  while grille[ligne][colonne] != 0: #parcours la colonne de haut en bas jusqu'à trouver la ligne vide la plus basse 
    if ligne == 0 :
      return ligne
    ligne -= 1
  return ligne

Une autre des fonctions principales : affichage_grille permet comme son nom l’indique d’afficher la grille entière :

def affichage_grille():
  rect(75, 42, 175, 150, (255,255,255))
  pos_x, pos_y_base, marge = 50, 42, 25
  for i in range(7):
    pos_x += marge
    pos_y = pos_y_base
    for y in range(6):
      cote = 20
      if grille[y][i] == 1:
        color = rouge
      elif grille[y][i] == 2:
        color = jaune
      else :
        color = gris
      if grille[y][i] == 1 or grille[y][i] == 2:
        rect(pos_x, pos_y, cote, cote, gris)
        draw_cercle(pos_x + 7, pos_y + 2,color)
      else:
        rect(pos_x, pos_y, cote, cote, gris)
      pos_y += marge

La fonction efface une possible grille avec un rectange blanc de la même taille que la grille puis réaffiche la grille

Voici ce que produit la fonction :

Ensuite il y a affichage_preview qui va afficher le preview du jeton au bon endroit :

def affichage_preview(col_preview):
   rect(75, 17, 170, 20, (255,255,255)) #Efface l'ancien preview
   if player == 1 : #Choisi la bonne couleur
      color = rouge
   else :
      color = jaune
   draw_cercle(75 + (col_preview * 25) + 7, 17+2, color) #Fait le preview au bon endroit

Il y a la fonction wait qui retourne la touche sur laquelle vous appuyez :

def wait(buttons=(0,1,2,3,4,52)): #Retourne la touche appuyée
   while True:
      for i in buttons:
         if keydown(i):
            while keydown(i): True
            return i

Et enfin les deux dernières fonctions qui sont liées : les fonctions du score, il y a la fonction affichage_src qui est appelé au début du jeu afin d’afficher le score :

#Score par Thomas S. mais code par Robin C.
def affichage_src():
   txt("J-1", 22, 42)
   draw_cercle(35,70,rouge)
   txt("Score", 12, 117)
   if points_rouge < 10 :
      largeur = 32
   else :
      largeur = 27
   txt(str(points_rouge),32,142)
   txt("J-2", 267, 42)
   draw_cercle(280,70,jaune)
   txt("Score", 257, 117)
   if points_jaune < 10 :
      largeur = 277
   else :
      largeur = 272
   txt(str(points_jaune),277,142)

def actu_src(): #Actualise le score
   txt(str(points_rouge),37-5*len(str(points_rouge)),142)
   txt(str(points_jaune),282-5*len(str(points_jaune)),142)

Et il y a aussi la fonction actu_src qui va à chaque fin de partie, actualiser le score en écrasant l’ancien score.

Voici ce que produit la fonction affichage_src() seule :

Et pour finir il y a le lancement du jeu à la fin avec juste avant le code qui permet d’afficher le lien en bas de l’écran :

#Lien article
rect(0,200,320,22,(148,113,222))
txt("Code by nsi.xyz/puissance4",33,202,(242,)*3,(148,113,222))

#lancement du jeu
affichage_src()
selection()

Images du jeu

Conclusion

Pour conclure le tout, ce projet a été très plaisant à faire et très instructif. Il faut savoir que faire un jeu sur la NumWorks comme le mien demande du travail notamment sur la prise en main de la partie graphique qui n’a pas été sans problèmes pour moi mais avec les entrainements de M.Robert sur twitter la prise en main fut plus simple.

Lien du jeu

Il existe deux liens pour le jeu, sachez que seul le premier garanti de disposer de la dernière version et que le deuxième est un lien alternatif qui peut être dépassé.