Un clone de “Snake” avec curses et art

Description

Il y a quelque semaine on a entamé la création d’un clone de « Snake », en modalité texte.

Nous sommes partis d’un vidéo sur YouTube, où le but étais de construire un « Snake » en python dans 5 minutes.

On a utilisé la librairie curses de Python, puis nous avons ajouté la gestion des couleurs.

Nous avons compliqué les choses en partageant l’écran en deux fenêtres, une pour les communications « pendant » le jeu, et l’autre pour la fenêtre de jeu lui-même.

Nous avons appris à gérer les boucles de jeux, l’affichage des points (sur une fenêtre dédiée à la communication), et aussi la fin du jeu.

Enfin, nous avons appris à gérer la notion de « high score », dans la façon la plus simple : En écrivant et lisant un fichier de texte. L’input ici est fait a “l’ancienne”, on gère lettre par lettre, et le nom du joueur est au maximum de 3 lettres.

Le diagramme d’états

Voilà à grandes lignes le diagramme des états du jeu :

Le code

Et voilà le code (une des versions, nous avons implémenté des moutures légèrement différents) :

import random
import  curses
import art 
from time import sleep 
from random import randint 

# On regarde si il y a un record 
try: 
    record = open("snake.record") 
    recordMan = record.readline()
    recordScore = int(record.readline()) 
    record.close() 
except : 
    recordMan = "ABC"
    recordScore = 3       

s = curses.initscr()
curses.curs_set(0)

# Ordre est important! 
curses.start_color()
curses.noecho()


sh, sw = s.getmaxyx()

w_comm = curses.newwin(sh, 20, 0, 0)
win = curses.newwin(sh, sw-20, 0, 20)

win.keypad(1)
win.timeout(100)

def afficheText(win, y, x, text, color, sleepTime=0): 
    textList=art.text2art(text).splitlines()
    i = 0
    for line in textList:  
        win.addstr(y+i, x, line, color)
        i+=1 
    win.refresh()   
    sleep(sleepTime)

# Ordre est important! 
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_GREEN)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_CYAN)
curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_BLACK)


colors = {'green': curses.color_pair(1), 
          'red': curses.color_pair(2), 
          'blue': curses.color_pair(3), 
          'yellow': curses.color_pair(4),
          'black': curses.color_pair(5)}

color_list = colors.values 

# il est possible d'utiliser box() aussi 

afficheText(win, int(sh/3), int(sw/4)-10, "SnAkE", colors['yellow'], 2)
afficheText(win, int(sh/3), int(sw/4)-10, "SnAkE", colors['black'], 0)

while True:
    win.clear()
    
    win.border() 
    w_comm.box()
    w_comm.refresh()
    
    snk_x = int(sw/4)
    snk_y = int(sh/2)
    snake = [
        [snk_y, snk_x],
        [snk_y, snk_x-1],
        [snk_y, snk_x-2]
    ]    
    
    food = [int(sh/2), int(sw/2)]
    win.addch(food[0], food[1], curses.ACS_DIAMOND, colors['red'])
    
    key = curses.KEY_RIGHT
    
    pause = False 
    
    touches = [ord('p'), ord('q'), curses.KEY_DOWN, curses.KEY_UP, curses.KEY_LEFT, curses.KEY_RIGHT]
    
    manges = 0 

    afficheText(w_comm, 5, 5, str(manges), colors['red'])
    w_comm.refresh() 
    
    while True:
        next_key = win.getch()  
        
        if next_key not in touches: 
            next_key=-1
        
        if next_key == ord('p'):
            pause = not pause 
            if pause : 
                old_key = key 
            else : 
                next_key = old_key
    
        if next_key == ord('q'):
            curses.endwin()
            quit()
    
        if not pause:  
            
            key = key if next_key == -1 else next_key
                
            if snake[0][0] in [0, sh] or snake[0][1]  in [0, sw-1] or snake[0] in snake[1:]:
                afficheText(win, int(sh/3), int(sw/5), "PERDU", colors['red'], 2)
                break 
    
            new_head = [snake[0][0], snake[0][1]]
    
            if key == curses.KEY_DOWN:
                new_head[0] += 1
            if key == curses.KEY_UP:
                new_head[0] -= 1
            if key == curses.KEY_LEFT:
                new_head[1] -= 1
            if key == curses.KEY_RIGHT:
                new_head[1] += 1
                    
            snake.insert(0, new_head)
        
            if snake[0] == food:
                food = None
                while food is None:
                    nf = [
                        random.randint(2, sh-3),
                        random.randint(21, sw-3)
                    ] 
                     
                    food = nf if nf not in snake else None
     
                    try:
                        win.addch(food[0], food[1], curses.ACS_DIAMOND, colors['red'])
                    except:                
                        food = None 
                afficheText(w_comm, 5, 5, str(manges), colors['black'])    
                manges+=1 
                afficheText(w_comm, 5, 5, str(manges), colors['red'])    
                
                #w_comm.addstr(5, 5, str(manges))
                w_comm.refresh() 
                
            else:
                tail = snake.pop() 
                win.addch(tail[0], tail[1], ' ')
        try:
            win.addch(snake[0][0], snake[0][1], curses.ACS_CKBOARD,colors['green'])
            for i in range (1,len(snake)-1):
                win.addch(snake[i][0], snake[i][1], curses.ACS_CKBOARD,colors['blue'])
        except:   
            afficheText(win, int(sh/3), int(sw/5), "PERDU", colors['red'], 2)
            curses.endwin()
            break
    
    # Si le dernier score est plus du record? Il faut mettre à jour le record
    if manges > recordScore : 
        # Nettoyer la fenetre
        win.clear()  
        win.border()
        # Premier charactere... 
        # On permet les lettres, les chiffres et les fleches pour bouger... 
        # Position = 0 
        prenom = "_"
        afficheText(win, 5, 5, prenom, colors['red'])  
        touchesCTRL = [curses.KEY_DOWN, curses.KEY_UP, curses.KEY_LEFT, curses.KEY_RIGHT]
        alphabet="_ABCDEFGJKLMNOPQRSTUVWXYZ0123456789"
        index = 0 
        position = 0 
        
        while position < 3: 
            next_key = win.getch() 
            
            if next_key in touchesCTRL: 
                if next_key == curses.KEY_UP:
                    index+=1  
                    if index >=len(alphabet):
                        index = 0
                    prenom = prenom[:-1] + alphabet[index]
                
                if next_key == curses.KEY_DOWN:
                    index-=1  
                    if index < 0 :
                        index = len(alphabet)-1 
                    prenom = prenom[:-1] + alphabet[index]            
                
                if next_key == curses.KEY_RIGHT:
                    position+=1 
                    if position < 3: 
                        prenom = prenom + alphabet[index]
                    
                if next_key == curses.KEY_LEFT:
                    if position > 0: 
                        position-=1
                        prenom = prenom[:-1]                
            
            win.clear()  
            win.border()                    
            afficheText(win, 5, 5, prenom, colors['red'])  
            
    if manges > recordScore :
        win.clear()  
        win.border()                    
        afficheText(win, 5, 5, "MERCI!", colors['red'],1) 

        record = open("/home/antonio/Dev/Python/snake.record", 'w') 
        record.write(prenom +"\n")
        record.write(str(manges)) 
        record.close() 
        recordScore = manges
      
                
    # Demander si rejouer 
    # Nettoyer la fenetre 

    win.clear()  
    win.border() 
    
    afficheText(win, 5, 5, "Encore? O/N", colors['red'])    
     
    # Gerer les touches 
    while True : 
        next_key = win.getch()  
         
        if next_key == ord('o') or next_key == ord('O'):  
            break
      
        if next_key == ord('q') or next_key == ord('n'):
            curses.endwin()
            quit()