Partage

[pygame][mini-projet] Tetris

3 novembre 2010 à 23:08:44

A la demande générale, voilà le tetris ...
Je ne suis vraiment pas doué pour expliquer les choses, je vais faire de mon mieux, je suis ouvert à toutes suggestions.
Je pars du principe que tout le monde connais le jeu.

Il y a 36 façons de coder un tetris. Je vais essayer de vous expliquer ma façon de faire; je ne sais pas si c'est la meilleure ou la pire, mais elle fonctionne.
Comme ça fait assez longtemps que j'en ai pas coder un, je vais tout reprendre depuis la page blanche.

c'est partie:
  • 1- observation du jeu:
    soit un objet qui chute dans un tableau, il est mobile et répond au clavier, il interagit avec le décor car il ne peut occuper une place déjà prise.
    sa vitesse de chute est constante, et répond donc à une temporisation.
    il y a donc 3 facteurs qui agissent de l'objet:
    - l'évènement clavier (gauche, droite, bas et rotation)
    - l'évènement temps (fait chuter à intervals réguliers)
    - le décor (empêche le déplacement)

    l'objet n'occupe que des espaces vides.
    si on considère l'objet comme un élément coloré, il ne peut occuper qu'un espace 'noir'.
    si on considère l'objet comme une densité égale à 1, il ne peut occuper qu'un espace de 'densité 0'.
    il y a donc une notion binaire ...

    on peut donc définir un tableau et un objet comme ceci:
    les '1' ne pouvant occuper le place d'autres '1', il suffit de limiter le tableau avec des '1' pour empêcher l'objet d'en sortir.

    tableau                   objet
       100000000001                000000000000
       100000000001                000001000000
       100000000001                000011100000
       100000000001                000000000000
       100000000001
       100000000001
       100000000001           autre objet
       100000000001                000000000000
       100000000001                000010000000
       100000000001                000011100000
       100000000001                000000000000
       100000000001
       100000000001
       100000000001
       100000000001
       111111111111


    et là ça semble évident, chaque ligne du tableau et des objets peut être codée sur un entier.

  • 2- opérateurs logiques et numériques:
    -comment savoir si un objet n'empiète pas sur un espace déjà occupé?
    on sait que:
    un '0' peut occuper la place d'un '0'
    un '0' peut occuper la place d'un '1'
    un '1' peut occuper la place d'un '0'
    mais
    un '1' ne peut occuper la place d'un '1'

    ce schéma correspond à un 'ET NON' logique.

    -comment l'objet se déplace à gauche ou à droite?
    c'est simple, il suffit de déplacer les '1'.
    si la ligne 2 d'un objet est codée '0000011000' soit 24 en décimal
    24 multiplié par 2 donne 48, soit '0000110000' en binaire
    et 24 divisé par 2 donne 12, soit '0000001100' en binaire

    les '1' semblent s'être déplacés ...
    en appliquant ceci aux 4 lignes de l'objet, tout l'objet se déplace.

    c'est pour cela que l'objet à la même largeur que le tableau, car 'supperposé' à celui-ci, opérateurs logiques et numériques s'appliqueront plus facilement.

    -ok, et la rotation alors?
    de même que l'on code l'objet en forme de 'L' sur 4 int (ou des bin ou hex, c'est vous qui voyez),
    on code ses 4 phases de rotation ainsi:
    (les chiffres sont bidons)
    objet_en_forme_de_L = (45,26,96,12) , (57,84,32,79) , (84,23,15,47) , (63,35,29,21)
    un index pointe l'objet en cours; il suffit de le décrémenter pour créer la rotation.
  • 3 -problèmes et solutions ...
    pb:
    admettons que le jeu sur déroule avec l'objet_en_forme_de_L[2]
    si je déplace celui-ci complètement à droite, au prochaine cycle où il sera utilisé, il apparaitra à droite et non plus au milieu du tableau...
    sl:
    on utilise une copie de l'objet_en_forme_de_L[2] que l'on pourra manipuler.

    pb:
    l'objet_en_forme_de_L[2] est complètement à droite du tableau
    si j'applique une rotation, l'objet_en_forme_de_L[1] sera, lui, au milieu du tableau ...
    sl:
    on utilise une copie de l'objet_en_forme_de_L pour palier au 1er problème, et, au lieu de déplacer que l'objet indexé, on déplace tout le groupe.

  • 4- les évènements:
    • 4.1- clavier

      les 4 touches 'arrow' sont utilisées.
      l'objet se déplace à gauche, droite et bas tant que les touches 'left','right' et 'down' sont maintenues enfoncées,
      mais ne 'tourne' qu'à l'enfoncement de la touche 'up'.
      pygame met à disposition plusieurs outils pour gérer le clavier ...
      Exercice:
      écrivez un 'mainloop' qui réagit de la façon expliquée ci-dessus; vérifiez le fonctionnement en 'printant' la touche enfoncée ('left','right','up' et 'down')
      from pygame import *
      
      screen = display.set_mode((200,200))
      
      key.set_repeat(50,50)
      
      k_up_is_up = False
      
      while True:
          ev = event.wait()
          if ev.type == QUIT: exit()
          elif ev.type == KEYDOWN:
              if ev.key == K_DOWN: print 'down'
              elif ev.key == K_LEFT: print 'left'
              elif ev.key == K_RIGHT: print 'right'
              elif ev.key == K_UP and not k_up_is_up:
                  print 'up'
                  k_up_is_up = True
          elif ev.type == KEYUP and ev.key == K_UP: k_up_is_up = False
      

    • 4.2- temps

      l'objet chute de lui-même à vitesse constante(par cycle),ce qui signifie qu'il répond à un timing; non bloquant évidement...
      la encore pygame dispose de plusieurs solutions pour gérer le temps.
      Exercice:
      modifiez le 'mainloop' de façon à ce que soit 'printé' 'down' toutes les 500ms.
      from pygame import *
      
      screen = display.set_mode((200,200))
      
      key.set_repeat(50,50)
      time.set_timer(KEYDOWN,500)
      #ici l'astuce est de simuler l'appuie d'une touche
      #time.set_timer() poste un evenement a interval regulier
      #mais il n'est pas possible de specifier des attributs
      #ainsi l'evenement KEYDOWN aura un attribut key egal a 0
      #0 ne correspondant a aucune touche du clavier il n'y a pas de conflict
      
      k_up_is_up = False
      
      while True:
          ev = event.wait()
          if ev.type == QUIT: exit()
          elif ev.type == KEYDOWN:
              if not ev.key or ev.key== K_DOWN: print 'down'
              elif ev.key == K_LEFT: print 'left'
              elif ev.key == K_RIGHT: print 'right'
              elif ev.key == K_UP and not k_up_is_up:
                  print 'up'
                  k_up_is_up = True
          elif ev.type == KEYUP and ev.key == K_UP: k_up_is_up = False
      
  • 5- interaction objet/décor:

    algo du jeu (un parmis d'autre :p):
    il est simple ...
    soit un tableau vide.
    tant que le tableau ne déborde pas d'objets:
    un nouvel objet est généré et fait sa vie d'objet. Il vit jusqu'à ce qu'il ne puisse plus chuter.
    si l'objet fraîchement généré ne peut chuter au moins 1 fois, c'est que le tableau déborde.

    cycle de jeu:
    un cycle est le temps de vie d'un objet.
    au début d'un cycle, l'objet apparait hors champs, au dessus du tableau visible. Comme il ne peut pas survivre dans le vide sidérale du out of range,
    il est nécessaire que le tableau s'étende de façon à contenir l'objet.
    un tableau visible fait 10 x 20 cases:

    en rouge la parie visible

    100000000001 <---ligne 0
    100000000001 <---ligne 1
    100001000001
    100011100001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    100000000001
    111111111111

    puis il chute, se pose et meurt.
    en mourrant il se pétrifie et fait partie du décor.
    la encore la magie des opérateurs logiques opère...
    si 100000000001 est un élément du décor
    et 000111000000 est un élément de l'objet
    100000000001 OU(logigue) 000111000000 donne 100111000001 comme nouvelle valeur à l'élément du décor.
    (étant donné que deux '1' ne peuvent se chevaucher, ça marhe aussi avec le 'OU EXCLUSIF')

    un nouveau cycle est généré ...

    interactions:

    Exercice:
    1-écrivez une fonction qui teste si un objet peut descendre d'une ligne où pas.
    il faut donc au préalable définir un tableau et au moins un objet dans une phase de rotation.
    démarrez de la ligne 0 du tableau et laisser, via le 'mainloop', chuter l'objet qui ne devra pas dépasse le bas du tableau.
    pour vérifiez le fonctionnement, ecriver une fonction qui affiche en console un truc de ce genre ...
    Image utilisateur

    2-pour empêcher l'objet de sortir du tableau on y a collé des bordures; ça vous l'aviez compris.
    modifiez la fonction test() et 'mainloop' pour tenir compte du déplacement lattéral de l'oblet.

    3-modifiez la fonction test() et 'mainloop' pour tenir compte de la rotation de l'objet.

    4-modifiez 'mainloop' pour générer des objets tant que le tableau ne déborde pas ...

    5-et pour finir, vous l'avez devinez ... (j'abrège car j'en ai marre :p )
    modifiez 'mainloop' pour supprimer les 'lignes pleines' et insérer des lignes vides.

    from pygame import *
    from random import choice
    
    empty_ligne = 2049
    all_set = 4095
    tableau = [empty_ligne]*24+[all_set]
    
    objet = (
    ((0, 0, 8, 14), (0, 12, 8, 8), (0, 0, 14, 2), (0, 4, 4, 12)),
    ((0, 0, 4, 14), (0, 4, 6, 4), (0, 0, 14, 4), (0, 2, 6, 2)),
    ((0, 0, 6, 12), (0, 4, 6, 2), (0, 0, 6, 12), (0, 4, 6, 2)),
    ((0, 0, 12, 6), (0, 2, 6, 4), (0, 0, 12, 6), (0, 2, 6, 4)),
    ((0, 0, 2, 14), (0, 4, 4, 6), (0, 0, 14, 2), (0, 6, 4, 4)),
    ((0, 0, 6, 6), (0, 0, 6, 6), (0, 0, 6, 6), (0, 0, 6, 6)),
    ((0, 0, 0, 15), (4, 4, 4, 4), (0, 0, 0, 15), (2, 2, 2, 2))
    )
    
    screen = display.set_mode((200,400))
    key.set_repeat(50,50)
    time.set_timer(KEYDOWN,500)
    
    k_up_is_up = False
    
    test = lambda shift,down,rotation: not any([int(i*shift)&tableau[e] for e,i in enumerate(obj[(one+rotation)%4],ligne+down)])
    
    def affiche():
        t = tableau[:]
        for i in range(4): t[ligne+i] = t[ligne+i]|obj[one][i]    
        for i in t[4:]:
            for j in bin(i)[2:]: print 'X' if int(j) else ' ',
            print
    
    ligne = 1
    while ligne:
        ligne = 0
        obj = choice(objet)[:]
        one = choice((0,1,2,3))
        while True:
            ev = event.wait()
            if ev.type == KEYDOWN:
                if not ev.key or ev.key== K_DOWN:
                    if test(1,1,0): ligne+=1
                    else:
                        for e,i in enumerate(obj[one],ligne):
                            tableau[e] = tableau[e]|i
                            if tableau[e] == all_set:
                                del(tableau[e])
                                tableau.insert(0,empty_ligne)
                        break
                elif ev.key == K_LEFT:
                    if test(2,0,0): obj = [[j<<1 for j in i]for i in obj]
                elif ev.key == K_RIGHT:
                    if test(0.5,0,0): obj = [[j>>1 for j in i]for i in obj]
                elif ev.key == K_UP and not k_up_is_up:
                    k_up_is_up = True
                    if test(1,0,1): one = (one+1)%4
            elif ev.type == KEYUP and ev.key == K_UP: k_up_is_up = False
            elif ev.type == QUIT: exit()
            affiche()
    


Prochaine étape ... le rendu.
4 novembre 2010 à 17:55:35

Salut,

Je trouve ton explication fort intéressante et certains points assez astucieux.

J'ai commencé ce projet (peut-être pas par là où il aurait fallu), j'y travaille.
A l'issue de ce développement, je penserai avoir tout ce qu'il me faut pour lancer mon projet.

Bon courage à tous,
4 novembre 2010 à 18:01:05

J'avais écrit un Tetris, il y a un bout de temps, pour m'entrainer avec Pygame, avec quelques features sympa (hard-fall, ombre de la pièce, passage au niveau supérieur passé un certain score, tout ça) mais le code n'est vraiment pas très joli à voir.

S'il faut, j'essayerai de le reprendre et le programmer de façon plus élégante pour le publier ici. :)
Zeste de Savoir, le site qui en a dans le citron !
5 novembre 2010 à 21:50:07

voilà, le noyau est fait.
on va donc attaquer le rendu et les autres options du jeu, comme le comptage des points, le design, etc ...

la 1ere chose que je vous propose c'est modifier le code pour pouvoir initialiser des tableaux de tailles variables.
pas que ce soit essentiel, mais on ne sais pas encore à quoi ça ressemblera à la fin, et s'il faut modifier des valeurs, autant modifier quelques variables et ne pas se taper toutes les constantes du code.


Citation : NoHaR

J'avais écrit un Tetris, il y a un bout de temps, pour m'entrainer avec Pygame, avec quelques features sympa (hard-fall, ombre de la pièce, passage au niveau supérieur passé un certain score, tout ça)



oui, faites des propositions comme ça, et postez aussi de zoulis dessins parce que niveau design je suis super nul :p
6 novembre 2010 à 23:12:04

Salut!

Citation : Josmiley


Je ne suis vraiment pas doué pour expliquer les choses


Bah, c'est plutôt bien! :)

Perso il y a un truc qui me gène...
J'ai déjà codé un tétris, et ta méthode me séduit(j'ai jamais pensé à ça...).
Mais, comment gères tu les différentes couleurs, justes avec les bits?o_O

Ton explication me plait, mais là, je bloque...
je connais 3, 4 méthodes pour représenter les figures dans un tétris.
Ce que tu proposes est original, mais je pense qu'il y a blocage, ou alors je suis à coté de la plaque(c'est fort possible! :-° )
Zeste de Savoir, le site qui en a dans le citron !
7 novembre 2010 à 0:36:48

Citation : GurneyH

Salut!
Perso il y a un truc qui me gène...
J'ai déjà codé un tétris, et ta méthode me séduit(j'ai jamais pensé à ça...).
Mais, comment gères tu les différentes couleurs, justes avec les bits?o_O



c'est vrai que cette méthode ne permet pas de gérer les combos de couleurs, du moins proprement; si c'est ce dont tu veux parler.
il est toujours possible de lire la/les couleur/s d'une ligne en lisant la couleur des pixels.
7 novembre 2010 à 10:24:29

En principe, dans Tetris, on ne fait pas de combos avec les couleurs, chaque pièce a une couleur donnée, point.

Ceci dit, c'est vrai que ça parait problématique. Mais ce genre de chose peut se régler une fois que l'on décide de gérer l'affichage avec des groupes de sprites et de dirtysprites.

Comprendre par là : une fois qu'une pièce est posée, l'objet qui la modélise n'a plus besoin des mêmes propriétés, et il devient plus simple d'enregistrer chaque bloc séparément, avec sa couleur dans la matrice qui représente le plateau.

De même, entre deux affichages, souvent, on n'a pas besoin de raffraichir tout l'écran, mais juste la zone couverte par la pièce (et éventuellement son ombre) à l'instant t et celle couverte par la pièce (et éventuellement son nombre) à l'instant t+1 (sauf, évidemment, quand on fait une ligne, et qu'il faut la faire disparaître et tout faire tomber au-dessus)…

C'est pour cette raison que je voudrais recoder mon Tetris avant de le soumettre ici. En l'état, il ne gère pas les couleurs des blocs, et le raffraichissement de l'écran est complet à chaque fois.
Zeste de Savoir, le site qui en a dans le citron !
7 novembre 2010 à 10:33:15

Citation : NoHaR


Ceci dit, c'est vrai que ça parait problématique. Mais ce genre de chose peut se régler une fois que l'on décide de gérer l'affichage avec des groupes de sprites et de dirtysprites.

Comprendre par là : une fois qu'une pièce est posée, l'objet qui la modélise n'a plus besoin des mêmes propriétés, et il devient plus simple d'enregistrer chaque bloc séparément, avec sa couleur dans la matrice qui représente le plateau.



ha, j'avais pas compris ça comme ça ...
nul besoin de connaitre la couleur d'une pièce posée, puisqu'elle n'existe plus; elle fait partie du décor. Voyez-vous ... ;)

c'est vrai que je vous ai donné un code où le plateau est entièrement 'retracé', mais c'est une contrainte dû au mode console.
7 novembre 2010 à 10:36:17

Attention quand même parce que le décor est destructible. ;)
Zeste de Savoir, le site qui en a dans le citron !
7 novembre 2010 à 10:37:38

Citation : NoHaR

Attention quand même parce que le décor est destructible. ;)


disons qu'il est gommable ^^
8 novembre 2010 à 18:00:31

finalement j'ai fixé une taille de 10x20.

le fichier masktetris.pngImage utilisateur ou le zip

from pygame import *
from random import randint

font.init()

empty_ligne = 2049
all_set = 4095
tableau = [empty_ligne]*(24)+[all_set]

mask = image.load('masktetris.png')
red = Surface((300,30),SRCALPHA)
red.fill((255,0,0,100))
font0 = font.Font('FreeMonoBold.ttf',20)

objet = [
        [[0, 0, 128, 224], [0, 192, 128, 128], [0, 0, 224, 32], [0, 64, 64, 192],(255,0,0)],
        [[0, 0, 64, 224], [0, 64, 96, 64], [0, 0, 224, 64], [0, 32, 96, 32],(100,100,0)],
        [[0, 0, 96, 192], [0, 64, 96, 32], [0, 0, 96, 192], [0, 64, 96, 32],(0,100,100)],
        [[0, 0, 192, 96], [0, 32, 96, 64], [0, 0, 192, 96], [0, 32, 96, 64],(0,0,255)],
        [[0, 0, 32, 224], [0, 64, 64, 96], [0, 0, 112, 64], [0, 96, 32, 32],(0,255,0)],
        [[0, 0, 96, 96], [0, 0, 96, 96], [0, 0, 96, 96], [0, 0, 96, 96],(100,255,100)],
        [[0, 0, 0, 240], [64, 64, 64, 64], [0, 0, 0, 240], [32, 32, 32, 32],(100,0,100)]
        ]

screen = display.set_mode((480,600))
bgcolor = 0xd0d0d0
bg = Surface((300,720)); bg.fill(bgcolor)
key.set_repeat(50,50)

score = 0
speed = 30
S = 0

time.set_timer(KEYDOWN,speed)
screen.blit(font0.render('score: '+str(score),1,(200,200,200)),(330,300))
screen.blit(font0.render('speed: '+str(0),1,(200,200,200)),(330,330))

k_up_is_up = False
k_space_is_up = False

test = lambda shift,down,rotation: not any([int(i*shift)&tableau[e] for e,i in enumerate(obj[(one+rotation)%4],ligne+down)])

def make_surf(index,one):
    img = Surface([120]*2,SRCALPHA)
    img.fill((0,0,0,0))
    shadow = img.copy()
    color = objet[index][-1]
    for y,i in enumerate(objet[index][one]):
        i /= 16
        for x in 90,60,30,0:
            if i&1:
                img.blit(mask,img.fill(color,(x,y*30,30,30)))
                shadow.fill(color+(50,)if shadow_set else bgcolor,(x,y*30,30,30))
            i /= 2
    return img,shadow

def YShadowUpdate():
    global ligne,Yshadow
    C = ligne
    while test(1,1,0): ligne+=1
    Yshadow = (ligne-4)*30
    ligne = C

Yshadow = ligne = 1
shadow_set = True
next_index = randint(0,6)
next_one = randint(0,3)
next_imgs = make_surf(next_index,next_one)
while ligne:
    event.clear()
    Y = -120
    X = 90
    ligne = 0
    index = next_index
    one = next_one
    img,shadow = next_imgs
    obj = objet[index]
    next_index = randint(0,6)
    next_one = randint(0,3)
    next_imgs = make_surf(next_index,next_one)
    screen.blit(transform.scale(next_imgs[0],(60,60)),screen.fill(0,(30*12,60,60,60)))
    YShadowUpdate()
    while True:
        ev = event.wait()
        if ev.type == KEYDOWN:
            if not ev.key or ev.key== K_DOWN:
                if not Y%30:
                    if test(1,1,0): ligne+=1
                    else:
                        bg.blit(img,(X,ligne*30))
                        s = 0
                        for e,i in enumerate(obj[one],ligne):
                            tableau[e] = tableau[e]|i
                            if tableau[e] == all_set:
                                S += 1
                                if not S%10: speed -= 1; time.set_timer(KEYDOWN,speed)
                                s += 1
                                score += s**2
                                del(tableau[e]); tableau.insert(0,empty_ligne)
                                display.update(screen.blit(red,(0,(e-4)*30)))
                                time.wait(300)
                                bg.blit(bg,(0,30),(0,0,300,(e)*30))
                                bg.fill(bgcolor,(0,0,30*10,30))
                                screen.fill(0,(330,300,120,60))
                                screen.blit(font0.render('score: '+str(score),1,(200,200,200)),(330,300))
                                screen.blit(font0.render('speed: '+str(30-speed),1,(200,200,200)),(330,330))
                                screen.blit(bg,(0,0),(0,120,300,(e)*30))
                                display.flip()
                        break
                if not ev.key: Y += 3
                else: Y = (ligne-4)*30
                screen.blit(bg,(0,-120))
                screen.blit(shadow,(X,Yshadow))
                screen.blit(img,(X,Y))
                display.flip(); continue
            elif ev.key == K_LEFT:
                if test(2,0,0): obj = [[j<<1 for j in i]for i in obj]; X -= 30; YShadowUpdate(); continue
            elif ev.key == K_RIGHT:
                if test(0.5,0,0): obj = [[j>>1 for j in i]for i in obj]; X += 30; YShadowUpdate(); continue
            elif ev.key == K_UP and not k_up_is_up:
                k_up_is_up = True
                if test(1,0,1): one = (one+1)%4; img,shadow = make_surf(index,one); YShadowUpdate(); continue
            elif ev.key == K_SPACE and not k_space_is_up:
                display.update((screen.blit(bg,(X,Y),(X,Y,120,120)),screen.blit(img,(X,Yshadow))))
                Y = Yshadow; ligne = Yshadow/30+4; k_space_is_up = True; continue
        elif ev.type == KEYUP:
            if ev.key == K_UP: k_up_is_up = False; continue
            elif ev.key == K_SPACE: k_space_is_up = False; continue
            elif ev.key == K_p:
                while event.wait().type != KEYUP: pass
                continue
            elif ev.key == K_s:
                shadow_set = not shadow_set
                img,shadow = make_surf(index,one)
                continue
        elif ev.type == QUIT: exit()
while event.wait().type != QUIT: pass

Image utilisateur
10 novembre 2010 à 9:58:07

Alors une première ébauche en mode bourrin. :lol:

import pygame
import random

BLOCK_SIZE = 16
GRID_WIDTH, GRID_HEIGHT = 10, 22
RESOLUTION = (BLOCK_SIZE * (GRID_WIDTH + 20) , BLOCK_SIZE * GRID_HEIGHT)

COLORS =    (128, 128, 128),    \
            (255,   0,   0),    \
            (  0,   0, 255),    \
            (255, 255,   0),    \
            (139,  69,  19),    \
            (255,   0, 255),    \
            (255, 255, 255),    \
            (0  , 255,   0),    \
            (0  , 255, 255)
            
# -----------------------------------------------------------------------------
class Block:
    def __init__(self, x, y, id):
        self.x, self.y = x, y
        self.color = COLORS[id]
    
    def display(self, ox, oy):
        screen = pygame.display.get_surface()
        x, y = ox + self.x, oy + self.y
        screen.fill(self.color, (x * BLOCK_SIZE, y * BLOCK_SIZE, \
                                        BLOCK_SIZE, BLOCK_SIZE))
        
# -----------------------------------------------------------------------------
class Figure:
    tetriminos =    (( 0,  0), ( 0,  0), ( 0,  0), ( 0,  0)),           \
                    ((-2,  0), (-1,  0), ( 0,  0), ( 1,  0)),           \
                    ((-1, -1), ( 0, -1), (-1,  0), ( 0,  0)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), ( 0,  1)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), ( 1,  1)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), (-1,  1)),           \
                    ((-1,  0), ( 0,  0), ( 0,  1), ( 1,  1)),           \
                    (( 0,  0), ( 1,  0), (-1,  1), ( 0,  1))
    
    def __init__(self, id):
        self.x, self.y = GRID_WIDTH / 2, 0
        self.id = id
        self.blocks = [Block(b[0], b[1], id) for b in Figure.tetriminos[id]]
    
    def display(self):
        for b in self.blocks:
            b.display(self.x, self.y)
    
    def checkPos(self, x, y, grid):
        for b in self.blocks:
            nxtX, nxtY = x + b.x, y + b.y 
            if nxtX < 0 or nxtX >= GRID_WIDTH or nxtY >= GRID_HEIGHT or grid[nxtY][nxtX]:
                return False
        return True
    
    def setPos(self, x, y):
        self.x, self.y = x, y
           
    def checkRotate(self, grid):
        for b in self.blocks:
            nxtX, nxtY = self.x + b.y, self.y - b.x
            if nxtX < 0 or nxtX >= GRID_WIDTH or nxtY >= GRID_HEIGHT \
                                                or grid[nxtY][nxtX]:
                return False
        return True
    
    def rotate(self):
        for b in self.blocks:
            b.x, b.y = b.y, -b.x

# ----------------------------------------------------------------------------
class Playfield:
    def __init__(self):
        self.grid = [[0 for i in xrange(GRID_WIDTH)] \
                            for j in xrange(GRID_HEIGHT)]
        self.nxtFigure = Figure(random.randrange(1, 8))
        self.crtFigure = Figure(random.randrange(1, 8))
        self.nxtTime = pygame.time.get_ticks() + 700
        
    def update(self, dx, dy, rotate):
        if rotate:
            self.updateRotate()
        if dx or dy:
            self.updateMove(dx, dy)
        
        crtTime = pygame.time.get_ticks()
        if crtTime > self.nxtTime:
            x, y = self.crtFigure.x, self.crtFigure.y + 1
            if self.crtFigure.checkPos(x, y, self.grid):
                self.crtFigure.setPos(x, y)
            else :
                self.storeFigure()
                if self.checkGameOver():
                    return True
                self.crtFigure = self.nxtFigure
                self.nxtFigure = Figure(random.randrange(1, 8))
            self.nxtTime = crtTime + 700
             
            return False
    
    def updateRotate(self):
        if self.crtFigure.checkRotate(self.grid):
                self.crtFigure.rotate()
    
    def updateMove(self, dx, dy):
        x, y = self.crtFigure.x + dx, self.crtFigure.y + dy
        if self.crtFigure.checkPos(x, y, self.grid):
            self.crtFigure.setPos(x, y)
                
    def storeFigure(self):
        for b in self.crtFigure.blocks:
            x, y = self.crtFigure.x + b.x, self.crtFigure.y + b.y
            self.grid[y][x] = self.crtFigure.id
        self.checkLines()
        
    def checkLines(self):
        for i, row in enumerate(self.grid):
            full = True
            for j, case in enumerate(row):
                if not case:
                    full = False
                    break
            if full:
                self.deleteLine(i)
                
    def deleteLine(self, row):
        for i in range(row, 1, -1):
            self.grid[i] = self.grid[i - 1]
    
    def checkGameOver(self):
        for case in self.grid[0]:
            if case:
                return True
        return False
    
    def display(self):
        screen = pygame.display.get_surface()
        screen.fill(0)
       
        for i, row in enumerate(self.grid):
            for j, case in enumerate(row):
                screen.fill(COLORS[self.grid[i][j]], (                                     \
                                                        j * BLOCK_SIZE,       \
                                                        i * BLOCK_SIZE,       \
                                                        BLOCK_SIZE,           \
                                                        BLOCK_SIZE            \
                                                    ))
        self.crtFigure.display()
        self.nxtFigure.setPos(GRID_WIDTH + 3, 1)
        self.nxtFigure.display()
        self.nxtFigure.setPos(GRID_WIDTH / 2, 0)

# -----------------------------------------------------------------------------
class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_mode(RESOLUTION)
        self.playfield = Playfield()
        
    def run(self):
        done = False
        while not done:
            dx, dy = 0, 0
            rotate = False
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    done = True
            if not done:
                keys = pygame.key.get_pressed()
                if keys[pygame.K_LEFT]:
                    dx = -1
                elif keys[pygame.K_RIGHT]:
                     dx = 1
                elif keys[pygame.K_DOWN]:
                    dy = 1
                elif keys[pygame.K_UP]:
                    rotate = True
            
                done = self.playfield.update(dx, dy, rotate)
                self.playfield.display()
      
                pygame.time.delay(100)
                pygame.display.flip()

game = Game()
game.run()


Hum, je fais nettement plus long que Josmiley et avec moins de fonctionnalités(pas de score et pas de niveau encore.
On va essayer de rendre ça plus joli. :)
Zeste de Savoir, le site qui en a dans le citron !
10 novembre 2010 à 11:38:25

@GurneyH:
pygame.key.get_pressed() n'est pas une bonne solution pour tester les touches car un appuie bref n'est pas forcément détecté s'il se produit entre 2 pygame.key.get_pressed()
10 novembre 2010 à 14:13:46

Citation : josmiley

@GurneyH:
pygame.key.get_pressed() n'est pas une bonne solution pour tester les touches car un appuie bref n'est pas forcément détecté s'il se produit entre 2 pygame.key.get_pressed()


Sauf erreur de ma part, on va récupèrer le dernier event KEY_DOWN ou KEY_UP de la file.

Alors en effet, on loupe certaines actions, mais en pratique pour des jeux de ce style, je demande à voir la perte réelle.
Sinon, si on utilise le polling il faut une variable supplémentaire, pour éviter au joueur de marteler la touche pour déplacer la figure.
En pratique je n'ai jamais constater de perte(notable pour le joueur), avec le mapping...
Après, pygame.key.get_pressed(), n'est peut-être pas ce que je crois, (l'équivalent de SDl_GetKeyState)?
Mais pour un jeu de ce style, ce ne sera pas ma première priorité.
;)

J'ai d'ailleurs un débordement de tableau plus grave que je vais devoir corriger. :lol:
Zeste de Savoir, le site qui en a dans le citron !
10 novembre 2010 à 22:09:47

Citation

Alors en effet, on loupe certaines actions, mais en pratique pour des jeux de ce style, je demande à voir la perte réelle.


Y a quand même un manque flagrant de réactivité et c'est un jeu de rapidité ... non ? o_O
11 novembre 2010 à 5:03:08

Citation : Josmiley


Y a quand même un manque flagrant de réactivité et c'est un jeu de rapidité ... non ? o_O


C'est vrai.
Mais je pense, que c'est à cause de la tempo de 100ms que j'ai collé avant le flip dans la boucle principale, justement pour que ne soit pas trop réactif.
C'est un peu du bricolage, mais ce n'est pas la faute du mapping.

Par contre j'ai un bug sévère qui se produit de manière alétoire, il me semble, et qui rempli la figure courante jusqu'en haut du plateau de jeu. Je n'arrive pas à trouver l'erreur. :colere2:
Zeste de Savoir, le site qui en a dans le citron !
11 novembre 2010 à 8:00:06

Citation : GurneyH


Mais je pense, que c'est à cause de la tempo de 100ms que j'ai collé avant le flip dans la boucle principale, justement pour que ne soit pas trop réactif.
C'est un peu du bricolage, mais ce n'est pas la faute du mapping.



ben oui, ça oblige à maintenir la touche au moins 100ms, mais bon, si c'est fait exprès ^^
11 novembre 2010 à 8:40:46

Citation : Josmiley


ben oui, ça oblige à maintenir la touche au moins 100ms, mais bon, si c'est fait exprès ^^


Si je ne met pas de tempo c'est injouable en fait. :p(les rotations vont bien trop vite).
Je vais rejouer à un "vrai tetris", il ne me semble pas qu'on était obligé d'appuyer plusieurs fois pour se déplacer de 2 cases par exemple.

edit: Je viens de vérifier, sur la snes, on se déplace en maintenant appuyer, pour les rotations, il faut appuyer plusieurs fois.
Je vais pouvoir virer ma tempo!
:)
Zeste de Savoir, le site qui en a dans le citron !
11 novembre 2010 à 14:46:31

Bravo à GurneyH et josmiley, très bons vos petits jeux ! Et des jeux comme ça c'est une super pub pour Python !
11 novembre 2010 à 15:29:33

Bon, désolé, je n'ai pas eu le temps de corriger mon design (dont je ne suis pas fier), mais mon tetris ("loltriz") se trouve ici. Il s'agit d'un de mes premiers projets en python.

pour le lancer, il suffit de lancer python 2.x sur le fichier "loltriz.py"

Voici un screenshot :
Image utilisateur
Zeste de Savoir, le site qui en a dans le citron !
11 novembre 2010 à 16:46:27

Citation : NoHaR

Bon, désolé, je n'ai pas eu le temps de corriger mon design (dont je ne suis pas fier), mais mon tetris ("loltriz") se trouve ici. Il s'agit d'un de mes premiers projets en python.




Joli projet !

En tant que joueur, voici mon avis : jeu fluide et qui se joue très bien. Je trouve très bonne l'idée de mettre une image de la cible (j'aurais juste mis une couleur plus claire peut-être). Je trouve par contre assez gênant le contour non bien rectiligne ainsi que la police peu lisible ainsi aussi que l'absence de couleurs (ça reste des détails car le jeu est très bon).


Du point de vue du code, je ne suis pas assez connaisseur pour pouvoir juger de façon compétente. J'ai l'impression que les concepts OO de ton code sont assez simples, tu crées des classes ok mais tu utilises à peine l'héritage (class OptionToggler). Sinon, il y a quand même quelque chose qui me gêne : c'est que par exemple ton moteur de jeu appelle Pygame. Ce que je trouve dommage -- mais ce n'était sans doute pas dans ton projet initial, -- c'est que ton code implémente un Tétris mais ne me semble pas du tout réutilisable si on veut coder un Tétris avec Tkinter ou PyQt. Or, un Tétris reste un Tétris, le principe du jeu ne change pas quelle que soit le GUI utilisé et je trouve qu'il est dommage d'avoir à coder plusieurs fois la même chose. Maintenant, peut-être que ce dont je parle là est difficile à implémenter, je serais curieux d'avoir ton avis.
12 novembre 2010 à 9:36:02

Citation : NoHaR

Bon, désolé, je n'ai pas eu le temps de corriger mon design (dont je ne suis pas fier), mais mon tetris ("loltriz") se trouve ici. Il s'agit d'un de mes premiers projets en python.

pour le lancer, il suffit de lancer python 2.x sur le fichier "loltriz.py"

Voici un screenshot :
Image utilisateur



Je trouve le design excellent.
j'vais essayer d'intégrer le 'shadow' à mon code, tiens ...

Citation : candide

<citation rid="5595737">
Du point de vue du code, je ne suis pas assez connaisseur pour pouvoir juger de façon compétente. J'ai l'impression que les concepts OO de ton code sont assez simples, tu crées des classes ok mais tu utilises à peine l'héritage (class OptionToggler). Sinon, il y a quand même quelque chose qui me gêne : c'est que par exemple ton moteur de jeu appelle Pygame. Ce que je trouve dommage -- mais ce n'était sans doute pas dans ton projet initial, -- c'est que ton code implémente un Tétris mais ne me semble pas du tout réutilisable si on veut coder un Tétris avec Tkinter ou PyQt. Or, un Tétris reste un Tétris, le principe du jeu ne change pas quelle que soit le GUI utilisé et je trouve qu'il est dommage d'avoir à coder plusieurs fois la même chose. Maintenant, peut-être que ce dont je parle là est difficile à implémenter, je serais curieux d'avoir ton avis.



je suis assez d'accord avec candide au sujet du OO; j'essaie souvent de créer des class dont les objets peuvent évoluer indépendament de l'environnement. Mais en graphique, ces objets devraient se tenir informé de l'environnement dans lequel ils évoluent, ne serait-ce que pour afficher un truc au bon endroit. Et là, la 'généralisation' de la class compliquerait plus le code qu'autre chose, amha.

Par contre, en séparant le 'calcul' du 'rendu' ...
12 novembre 2010 à 13:06:48

Merci Candide et josmiley.

Bon étant donné que c'est moi qui ai commis réalisé ce code, je vais me permettre d'être très critique dessus.

L'intention


Comme je l'ai dit, il s'agit de l'un des tous premiers projets que j'ai réalisés avec Python pour me faire la main dessus.

Le but était d'obtenir un jeu avec un design visuel rigolo style "dessiné au marqueur sur un tableau blanc", d'où les lignes non-rectilignes (mais un peu quand même) et la police difficile à lire. Je suis d'accord sur le manque de couleur, et c'est un problème très difficile à régler en l'état à cause de certains problèmes de conception. J'ai mis pas mal l'accent sur la jouabilité, cependant, parce que j'avais pour ce jeu un client direct et très exigeant : ma copine. :p

La conception et ses lourdeurs


Pour résumer, je qualifierais cette conception d'intermédiaire. L'accent n'a pas du tout été mis dessus, mais quelques éléments ont été faits avec un certain soucis de réutilisabilité (comme le menu, que je voulais utilisable "un peu comme dans n'importe quelle lib graphique", et pour lequel j'ai essayé de jouer avec le "tout-est-objet" de Python — le OptionToggler en est un bon exemple, quoi que très perfectible avec le recul —).

Pour ce qui est des concepts OO que je n'utilise pas beaucoup (ou qui ne sont pas très respectés), je dirais que le problème ne vient pas nécessairement du manque d'héritage, mais qu'il est plus "bas" encore conceptuellement : mes classes ne sont pas toutes à responsabilité unique (beaucoup sont des do-it-all classes, très difficiles à re-factoriser et maintenir en l'état), et je n'ai pas assez eu le soucis de définir une interface publique (ou classe "virtuelle") pour chaque composant, implémentée par la suite par des classes filles. En conséquence, on se retrouve avec un moteur peu hiérarchisé, qui dépend effectivement de Pygame : le couplage entre l'abstraction et l'implémentation est beaucoup trop fort (d'autant plus que les mécanismes que j'utilise pour l'affichage ne sont vraiment, vraiment pas performants, bien que cela ne se sente pas du tout à l'utilisation).

Si j'avais à recoder ce jeu avec l'intention que son code soit "visible", je pense que j'aurais une conception plus claire :
* Les abstractions (le "moteur de jeu" actuel) seraient bien séparées du reste (plus qu'actuellement).
* Le système d'affichage serait plus optimisé, notamment en dérivant les classes pièces et en utilisant des "dirty sprites", afin d'éviter de redessiner tout l'écran à chaque fois.

Pour ce qui est du moteur, par contre, dans un jeu comme celui-ci, il est nécessairement beaucoup plus simple de le faire dépendre de la bibliothèque : si le but était de créer un moteur générique pour un Tetris, il faudrait le spécifier à la base, et ça demande de créer beaucoup plus d'abstractions : dans un cas simple comme ici (le système du jeu n'étant vraiment pas complexe), on peut raisonnablement dépendre de Pygame, c'est un choix qui me semble acceptable pour trouver l'équilibre entre la modularité du design (qui demande de créer plein de petites classes hiérarchisées à responsabilité unique, typiquement) et la simplicité du code.
Zeste de Savoir, le site qui en a dans le citron !
12 novembre 2010 à 20:24:56

Je viens juste de regarder le screenshot du jeu de NoHar.

C'est sympa, il va falloir que j'essaye le jeu.

Sinon, coder un Tétris, ça intéresse 3 personnes? o_O
Zeste de Savoir, le site qui en a dans le citron !
12 novembre 2010 à 23:02:03

merci pour les feedback que vous allez poster :p
Pitêtre modifer les couleurs...
http://joel-murielle.perso.sfr.fr/tetrisdz.zip
from pygame import *
from random import randint

font.init()

empty_ligne = 2049
all_set = 4095
tableau = [empty_ligne]*(24)+[all_set]

mask = image.load('masktetris.png')
red = Surface((300,30),SRCALPHA)
red.fill((255,0,0,100))
font0 = font.Font('FreeMonoBold.ttf',20)

objet = [
        [[0, 0, 128, 224], [0, 192, 128, 128], [0, 0, 224, 32], [0, 64, 64, 192],(255,0,0)],
        [[0, 0, 64, 224], [0, 64, 96, 64], [0, 0, 224, 64], [0, 32, 96, 32],(100,100,0)],
        [[0, 0, 96, 192], [0, 64, 96, 32], [0, 0, 96, 192], [0, 64, 96, 32],(0,100,100)],
        [[0, 0, 192, 96], [0, 32, 96, 64], [0, 0, 192, 96], [0, 32, 96, 64],(0,0,255)],
        [[0, 0, 32, 224], [0, 64, 64, 96], [0, 0, 112, 64], [0, 96, 32, 32],(0,255,0)],
        [[0, 0, 96, 96], [0, 0, 96, 96], [0, 0, 96, 96], [0, 0, 96, 96],(100,255,100)],
        [[0, 0, 0, 240], [64, 64, 64, 64], [0, 0, 0, 240], [32, 32, 32, 32],(100,0,100)]
        ]

screen = display.set_mode((480,600))
bgcolor = 0xd0d0d0
bg = Surface((300,720)); bg.fill(bgcolor)
key.set_repeat(50,50)

score = 0
speed = 30
S = 0

time.set_timer(KEYDOWN,speed)
screen.blit(font0.render('score: '+str(score),1,(200,200,200)),(330,300))
screen.blit(font0.render('speed: '+str(0),1,(200,200,200)),(330,330))

k_up_is_up = False
k_space_is_up = False

test = lambda shift,down,rotation: not any([int(i*shift)&tableau[e] for e,i in enumerate(obj[(one+rotation)%4],ligne+down)])

def make_surf(index,one):
    img = Surface([120]*2,SRCALPHA)
    img.fill((0,0,0,0))
    shadow = img.copy()
    color = objet[index][-1]
    for y,i in enumerate(objet[index][one]):
        i /= 16
        for x in 90,60,30,0:
            if i&1:
                img.blit(mask,img.fill(color,(x,y*30,30,30)))
                shadow.fill(color+(50,)if shadow_set else bgcolor,(x,y*30,30,30))
            i /= 2
    return img,shadow

def YShadowUpdate():
    global ligne,Yshadow
    C = ligne
    while test(1,1,0): ligne+=1
    Yshadow = (ligne-4)*30
    ligne = C

Yshadow = ligne = 1
shadow_set = False
next_index = randint(0,6)
next_one = randint(0,3)
next_imgs = make_surf(next_index,next_one)
while ligne:
    event.clear()
    Y = -120
    X = 90
    ligne = 0
    index = next_index
    one = next_one
    img,shadow = next_imgs
    obj = objet[index]
    next_index = randint(0,6)
    next_one = randint(0,3)
    next_imgs = make_surf(next_index,next_one)
    screen.blit(transform.scale(next_imgs[0],(60,60)),screen.fill(0,(30*12,60,60,60)))
    YShadowUpdate()
    while True:
        ev = event.wait()
        if ev.type == KEYDOWN:
            if not ev.key or ev.key== K_DOWN:
                if not Y%30:
                    if test(1,1,0): ligne+=1
                    else:
                        bg.blit(img,(X,ligne*30))
                        s = 0
                        for e,i in enumerate(obj[one],ligne):
                            tableau[e] = tableau[e]|i
                            if tableau[e] == all_set:
                                S += 1
                                if not S%10: speed -= 1; time.set_timer(KEYDOWN,speed)
                                s += 1
                                score += s**2
                                del(tableau[e]); tableau.insert(0,empty_ligne)
                                display.update(screen.blit(red,(0,(e-4)*30)))
                                time.wait(300)
                                bg.blit(bg,(0,30),(0,0,300,(e)*30))
                                bg.fill(bgcolor,(0,0,30*10,30))
                                screen.fill(0,(330,300,120,60))
                                screen.blit(font0.render('score: '+str(score),1,(200,200,200)),(330,300))
                                screen.blit(font0.render('speed: '+str(30-speed),1,(200,200,200)),(330,330))
                                screen.blit(bg,(0,0),(0,120,300,(e)*30))
                                display.flip()
                        break
                if not ev.key: Y += 3
                else: Y = (ligne-4)*30
                screen.blit(bg,(0,-120))
                screen.blit(shadow,(X,Yshadow))
                screen.blit(img,(X,Y))
                display.flip(); continue
            elif ev.key == K_LEFT:
                if test(2,0,0): obj = [[j<<1 for j in i]for i in obj]; X -= 30; YShadowUpdate(); continue
            elif ev.key == K_RIGHT:
                if test(0.5,0,0): obj = [[j>>1 for j in i]for i in obj]; X += 30; YShadowUpdate(); continue
            elif ev.key == K_UP and not k_up_is_up:
                k_up_is_up = True
                if test(1,0,1): one = (one+1)%4; img,shadow = make_surf(index,one); YShadowUpdate(); continue
            elif ev.key == K_SPACE and not k_space_is_up:
                display.update((screen.blit(bg,(X,Y),(X,Y,120,120)),screen.blit(img,(X,Yshadow))))
                Y = Yshadow; ligne = Yshadow/30+4; k_space_is_up = True; continue
        elif ev.type == KEYUP:
            if ev.key == K_UP: k_up_is_up = False; continue
            elif ev.key == K_SPACE: k_space_is_up = False; continue
            elif ev.key == K_p:
                while event.wait().type != KEYUP: pass
                continue
            elif ev.key == K_s:
                shadow_set = not shadow_set
                img,shadow = make_surf(index,one)
                continue
        elif ev.type == QUIT: exit()
while event.wait().type != QUIT: pass

touches classiques
'p' pour pause
's' pour shadow on/off
Image utilisateur
13 novembre 2010 à 12:06:42

Pareil pour moi, une deuxième version du Tétris.

import pygame
import random
import os
 
BLOCK_SIZE = 16
GRID_WIDTH, GRID_HEIGHT = 10, 20
RESOLUTION = (352, 352)
TICK_INTERVAL  = 50
EMPTY = -1
ROTL, ROTR = 1, 2
# -----------------------------------------------------------------------------
class TTFont:
    def __init__(self, path, sz = 16):
        self.font = pygame.font.Font(path, sz)
       
    def displayBlended(self, str, x, y, color = (255, 255, 255)):
        screen = pygame.display.get_surface()
        srf = self.font.render(str, True, (0, 0, 0))
        screen.blit(srf, (x + 1, y + 1, 0, 0))
        srf = self.font.render(str, True, color)
        screen.blit(srf, (x, y, 0, 0))
       
# -----------------------------------------------------------------------------
class Input:
    def __init__(self):
        self.quit = False
        self.up = False
        self.down = False
        self.left = False
        self.right = False
        self.w = False
        self.x = False
        self.s = False
        
    def update(self):
         for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.quit = True
                
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    self.left = True
                if event.key == pygame.K_RIGHT:
                    self.right = True
                if event.key == pygame.K_UP:
                    self.up = True
                if event.key == pygame.K_DOWN:
                    self.down = True
                if event.key == pygame.K_w:
                    self.w = True
                if event.key == pygame.K_x:
                    self.x = True
                if event.key == pygame.K_s:
                    self.s = True
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_LEFT:
                    self.left = False
                if event.key == pygame.K_RIGHT:
                    self.right = False
                if event.key == pygame.K_UP:
                    self.up = False
                if event.key == pygame.K_DOWN:
                    self.down = False
                if event.key == pygame.K_w:
                    self.w = False
                if event.key == pygame.K_x:
                    self.x = False
                if event.key == pygame.K_s:
                    self.s = False
# -------------------------------------------------------------
class Figure:
    tetriminos =    ((-2,  0), (-1,  0), ( 0,  0), ( 1,  0)),           \
                    ((-1,  1), ( 0,  1), (-1,  0), ( 0,  0)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), ( 0,  1)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), ( 1,  1)),           \
                    ((-1,  0), ( 0,  0), ( 1,  0), (-1,  1)),           \
                    ((-1,  0), ( 0,  0), ( 0,  1), ( 1,  1)),           \
                    (( 0,  0), ( 1,  0), (-1,  1), ( 0,  1))
    
    def __init__(self, id):
        self.x, self.y = GRID_WIDTH / 2, 0
        self.id = id
        self.blocks = [[b[0], b[1]] for b in Figure.tetriminos[id]]
        
    def checkPos(self, x, y, grid):
        for b in self.blocks:
            nxtX, nxtY = x + b[0], y + b[1] 
            if nxtX < 0 or nxtX >= GRID_WIDTH or nxtY >= GRID_HEIGHT or grid[nxtY][nxtX] != EMPTY:
                return False
        return True
    
    def setPos(self, x, y):
        self.x, self.y = x, y
           
    def checkRotate(self, grid, rot):
        for b in self.blocks:
            if rot == ROTL:
                nxtX, nxtY = self.x + b[1], self.y - b[0]
            else:
                nxtX, nxtY = self.x - b[1], self.y + b[0]
            if nxtX < 0 or nxtX >= GRID_WIDTH or nxtY >= GRID_HEIGHT \
                                                or grid[nxtY][nxtX] != EMPTY:
                return False
        return True
    
    def rotate(self, rot):
        for b in self.blocks:
            if rot == ROTL:
                b[0], b[1] = b[1], -b[0]
            else:
                b[0], b[1] = -b[1], b[0]
                
    def display(self, srf, id):
        screen = pygame.display.get_surface()
        for b in self.blocks:
            x, y = self.x + b[0], self.y + b[1]
            if y >= 0:
                srcRect = (id * BLOCK_SIZE, 0, BLOCK_SIZE, BLOCK_SIZE)
                dstRect = (16 + x * BLOCK_SIZE, 16 + y * BLOCK_SIZE, 0, 0)
                screen.blit(srf, dstRect, srcRect)
                  
# ----------------------------------------------------------------------------
class Playfield:
    scoreLines = (0, 40, 100, 300, 1200)
    def __init__(self):
        self.grid = [[EMPTY for i in xrange(GRID_WIDTH)] \
                            for j in xrange(GRID_HEIGHT)]
        self.nxtFigure = Figure(random.randrange(7))
        self.crtFigure = Figure(random.randrange(7))
        self.shadow = Figure(self.crtFigure.id)
        self.shadowActivate = True
        self.speed = 750
        self.nxtTime = pygame.time.get_ticks() + self.speed
        self.dropped = 0
        self.score = 0
        self.lines = 0
        self.totalLines = 0
        self.level = 1
        self.statistics = 7 * [0]
        self.srfBackground = pygame.image.load("background1.png")
        self.srfBlocks = pygame.image.load("blocks.png")
        self.font = TTFont("arial.ttf")
    
    def toogleShadow(self):
        self.shadowActivate = 1 - self.shadowActivate
        
    def update(self, dx, dy, rot, hardDrop):
        if rot:
            self.updateRotate(rot)
        if hardDrop:
            while self.updateMove(self.crtFigure, 0, 1):
                pass
        if dx or dy:
            self.updateMove(self.crtFigure, dx, dy)
        
        self.shadow.blocks = self.crtFigure.blocks
        self.shadow.setPos(self.crtFigure.x, self.crtFigure.y)
        while self.updateMove(self.shadow, 0, 1):
            pass
        
        crtTime = pygame.time.get_ticks()
        if crtTime > self.nxtTime:
            x, y = self.crtFigure.x, self.crtFigure.y + 1
            if self.crtFigure.checkPos(x, y, self.grid):
                self.crtFigure.setPos(x, y)
            else :
                self.storeFigure()
                if self.checkGameOver():
                    return true
                self.crtFigure = self.nxtFigure
                self.nxtFigure = Figure(random.randrange(7))
                self.shadow = Figure(self.crtFigure.id)
                self.dropped = 0
                self.lines = 0
            self.nxtTime = crtTime + self.speed
             
            return False
    
    def updateRotate(self, rot):
        if self.crtFigure.checkRotate(self.grid, rot):
                self.crtFigure.rotate(rot)
    
    def updateMove(self, fg, dx, dy):
        x, y = fg.x + dx, fg.y + dy
        if not fg.checkPos(x, y, self.grid):
            return False
        else :
            fg.setPos(x, y)
            if dy:
                self.dropped += 1
        return True
    
    def storeFigure(self):
        for b in self.crtFigure.blocks:
            x, y = self.crtFigure.x + b[0], self.crtFigure.y + b[1]
            self.grid[y][x] = self.crtFigure.id
        self.checkLines()
        self.score += self.dropped
        self.score += Playfield.scoreLines[self.lines] * (self.level + 1)
        self.totalLines += self.lines
        self.level = (self.totalLines / 10) + 1
        self.speed = 800 - self.level * 50   
        self.statistics[self.crtFigure.id] += 1
        
    def checkLines(self):
        for i, row in enumerate(self.grid):
            full = True
            for j, case in enumerate(row):
                if case < 0:
                    full = False
                    break
            if full:
                self.deleteLine(i)
                self.lines += 1
                
    def deleteLine(self, row):
        for i in range(row, 1, -1):
            self.grid[i] = self.grid[i - 1] 
            self.grid[i - 1] = GRID_WIDTH * [EMPTY]
        
    def checkGameOver(self):
        for case in self.grid[0]:
            if case != EMPTY:
                return True
        return False
    
    def display(self):
        screen = pygame.display.get_surface()
        screen.blit(self.srfBackground, (0, 0, 0, 0))
       
        for i, row in enumerate(self.grid):
            for j, case in enumerate(row):
                if self.grid[i][j] >= 0:
                    srcRect = (self.grid[i][j] * BLOCK_SIZE, 0, 16, 16)
                    dstRect = (16 + j * BLOCK_SIZE, 16 + i * BLOCK_SIZE, 0, 0)
                    screen.blit(self.srfBlocks, dstRect, srcRect)
        
        if self.shadowActivate:
            self.shadow.display(self.srfBlocks, 7)
        self.crtFigure.display(self.srfBlocks, self.crtFigure.id)
        self.nxtFigure.setPos(GRID_WIDTH + 5, 1)
        self.nxtFigure.display(self.srfBlocks, self.nxtFigure.id)
        self.nxtFigure.setPos(GRID_WIDTH / 2, 0)
    
        self.font.displayBlended("Score : " + str(self.score), 204, 108)
        self.font.displayBlended("Level : " + str(self.level), 204, 125)
        self.font.displayBlended("Lines : " + str(self.totalLines), 204, 142)
        
        for i in xrange(7):
            srcRect = (i * BLOCK_SIZE, 0, BLOCK_SIZE, BLOCK_SIZE)
            dstRect = (214, 165 + i * 25, 0, 0)
            screen.blit(self.srfBlocks, dstRect, srcRect)
            self.font.displayBlended(str(self.statistics[i]), 235, 160 + i * 25)
# -----------------------------------------------------------------------------
class Game:
    def __init__(self):
        pygame.init()
        pygame.font.init()
        pygame.display.set_mode(RESOLUTION)
        pygame.display.set_caption("ZTris !!!")
        self.input = Input()
        self.playfield = Playfield()
        
    def timeLeft(self):
        now = pygame.time.get_ticks()
        if self.nxtTime <= now:
            return 0
        else:
            return self.nxtTime - now;

    def run(self):
        gameOver = False
        self.nxtTime = pygame.time.get_ticks() 
        while not self.input.quit and not gameOver:
            dx, dy = 0, 0
            rotate = False
            hardDrop = False
            self.input.update()
            if self.input.left:
                dx = -1
            if self.input.right:
                dx = 1
            if self.input.down:
                dy = 1
            if self.input.up:
                self.input.up = False
                hardDrop = True
            if self.input.w:
                self.input.w = False
                rotate = ROTL
            if self.input.x:
                self.input.x = False
                rotate = ROTR
            if self.input.s:
                self.input.s = False
                self.playfield.toogleShadow()
                
            gameOver = self.playfield.update(dx, dy, rotate, hardDrop)
            self.playfield.display()
            
            pygame.time.delay(self.timeLeft())
            self.nxtTime += TICK_INTERVAL

            pygame.display.flip()

game = Game()
game.run()


J'ai rajouté Level, scores, statistiques, hard drop et shadow.
Et... Le code est de pire en pire :honte:

Un screen
Image utilisateur


les images nécessaires.

background1.png
Image utilisateur

blocks.png
Image utilisateur


Ces images sont à mettre dans le même répertoire que le code.
Il faut aussi placer arial.ttf dans ce répertoire.

Go snake, alors. :p

edit:
les touches :
w et x pour les rotations.
haut pour hard drop
bas pour soft drop
s pour activer/désactiver l'ombre.
Zeste de Savoir, le site qui en a dans le citron !
13 novembre 2010 à 12:16:20

Josmiley et GurneyH : joli !

Ah oui j'ai oublié de préciser les touches pour le mien :

gauche-droite pour bouger
haut pour une rotation
bas pour soft drop
espace pour hard drop

:D
Zeste de Savoir, le site qui en a dans le citron !
13 novembre 2010 à 13:40:10

Punaise, en voyant mon screen avec un score de 5602, au level 1 avec 0 lignes, j'ai un gros doute! :honte:

En tout cas merci, Josmiley! ;)
Zeste de Savoir, le site qui en a dans le citron !
13 novembre 2010 à 20:25:11

heu ... si j'avais mis un lien ça l'aurait mieux fait non ? :phttp://joel-murielle.perso.sfr.fr/tetrisdz.zip


Citation : GurneyH


En tout cas merci, Josmiley! ;)


de quoi ? o_O
13 novembre 2010 à 20:33:59

Au hasard, pour tes initiatives intéressantes et ton implication dans la communauté Pygamienne du SdZ ?
Zeste de Savoir, le site qui en a dans le citron !

[pygame][mini-projet] Tetris

× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
  • Editeur
  • Markdown