8. pygame Makes a Simple Tetris Game (Foundation 3)

Keywords: Attribute

The previous chapter was written at https://blog.csdn.net/zhangenter/article/details/89304707.
Continue now

1. Set different colors for each square.

According to the code, it can be judged that it is most appropriate to add an attribute to the falling blocks in those Block subclasses, and the most appropriate place for the color management of the falling blocks should be to modify rect_arr in the Panel class.
The modification in the Block subclass is relatively simple. Take the TBlock class as an example, add a line to the _init__function.

self.color=(255,0,0)

In Panel's paint function, the code will be

# Draw falling squares
        if self.move_block:
            for rect in self.moving_block.get_rect_arr():
                x,y=rect
                pygame.draw.line(self._bg,[0,0,255],[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
                pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz,self._y+y*bz,bz+1,bz+1],1)

Medium

pygame.draw.line(self._bg,[0,0,255],[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
//Change to
pygame.draw.line(self._bg,self.moving_block.color,[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)

It's a bit more troublesome to modify the fallen square. The original rect_arr is x,y. Now it's possible to add a color and change it directly, but considering the future extensibility, it's decisive to define a RectInfo class.

class RectInfo(object):
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

Modify the code stored in rect_arr to

    def add_block(self,block):
        for x,y in block.get_rect_arr():
            self.rect_arr.append(RectInfo(x,y, block.color))

And the design rect_arr can be modified

Paste down the current complete code

# -*- coding=utf-8 -*-
import random
import pygame
from pygame.locals import KEYDOWN,K_LEFT,K_RIGHT,K_UP,K_DOWN,K_SPACE

class RectInfo(object):
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

class Panel(object): # Layout for drawing the entire game window
    rect_arr=[] # The bottomed square
    moving_block=None # Falling square
    def __init__(self,bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]
    
    def add_block(self,block):
        for x,y in block.get_rect_arr():
            self.rect_arr.append(RectInfo(x,y, block.color))

    def create_move_block(self):
        block = create_block()
        block.move(5-2,-2) # Move the box to the middle 
        self.moving_block=block

    def check_overlap(self, diffx, diffy, check_arr=None):
        if check_arr is None: check_arr = self.moving_block.get_rect_arr()
        for x,y in check_arr:
            for rect_info in self.rect_arr:
                if x+diffx==rect_info.x and y+diffy==rect_info.y:
                    return True
        return False

    def control_block(self, diffx, diffy):
        if self.moving_block.can_move(diffx,diffy) and not self.check_overlap(diffx, diffy):
            self.moving_block.move(diffx,diffy)

    def change_block(self):
        if self.moving_block:
            new_arr = self.moving_block.change()
            if new_arr and not self.check_overlap(0, 0, check_arr=new_arr): # Deformation does not cause overlap of squares
                self.moving_block.rect_arr=new_arr

    def move_block(self):
        if self.moving_block is None: create_move_block()
        if self.moving_block.can_move(0,1) and not self.check_overlap(0,1): 
            self.moving_block.move(0,1)
            return 1
        else:
            self.add_block(self.moving_block)
            self.check_clear()

            for rect_info in self.rect_arr:
                if rect_info.y<0: return 9 # Game failure
            self.create_move_block()
            return 2

    def check_clear(self):
        tmp_arr = [[] for i in range(20)]
        # Save the box in an array by line first
        for rect_info in self.rect_arr:
            if rect_info.y<0: return
            tmp_arr[rect_info.y].append(rect_info)

        clear_num=0
        clear_lines=set([])
        y_clear_diff_arr=[[] for i in range(20)]
        # Calculate rows that can be eliminated from bottom to top and record the number of downward offsets of other rows after elimination
        for y in range(19,-1,-1):
            if len(tmp_arr[y])==10:
                clear_lines.add(y)
                clear_num += 1
            y_clear_diff_arr[y] = clear_num

        if clear_num>0:
            new_arr=[]
            # Skip removed rows and offset other rows
            for y in range(19,-1,-1):
                if y in clear_lines: continue
                tmp_row = tmp_arr[y]
                y_clear_diff=y_clear_diff_arr[y]
                for rect_info in tmp_row:
                    #new_arr.append([x,y+y_clear_diff])
                    new_arr.append(RectInfo(rect_info.x, rect_info.y+y_clear_diff, rect_info.color))
            
            self.rect_arr = new_arr


    def paint(self):
        mid_x=self._x+self._width/2
        pygame.draw.line(self._bg,self._bgcolor,[mid_x,self._y],[mid_x,self._y+self._height],self._width) # Fill in the background with a thick line segment
        
        # Draw bottomed squares
        bz=self._block_size
        for rect_info in self.rect_arr:
            x=rect_info.x
            y=rect_info.y
            pygame.draw.line(self._bg,rect_info.color,[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
            pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz,self._y+y*bz,bz+1,bz+1],1)
       
        # Draw falling squares
        if self.move_block:
            for rect in self.moving_block.get_rect_arr():
                x,y=rect
                pygame.draw.line(self._bg,self.moving_block.color,[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
                pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz,self._y+y*bz,bz+1,bz+1],1)


class Block(object):
    sx=0
    sy=0
    def __init__(self):
        self.rect_arr=[]

    def get_rect_arr(self): # Four Rectangular Lists for Getting Block Species
        return self.rect_arr

    def move(self,xdiff,ydiff): # A Method for Moving Blocks
        self.sx+=xdiff
        self.sy+=ydiff
        self.new_rect_arr=[]
        for x,y in self.rect_arr:
            self.new_rect_arr.append((x+xdiff,y+ydiff))
        self.rect_arr=self.new_rect_arr

    def can_move(self,xdiff,ydiff):
        for x,y in self.rect_arr:
            if y+ydiff>=20: return False
            if x+xdiff<0 or x+xdiff>=10: return False
        return True

    def change(self):
        self.shape_id+=1 # Next form
        if self.shape_id >= self.shape_num: 
            self.shape_id=0

        arr = self.get_shape()
        new_arr = []
        for x,y in arr:
            if x+self.sx<0 or x+self.sx>=10: # Deformation cannot exceed left and right boundary
                self.shape_id -= 1
                if self.shape_id < 0: self.shape_id = self.shape_num - 1
                return None 

            new_arr.append([x+self.sx,y+self.sy])

        return new_arr

class LongBlock(Block):
    shape_id=0
    shape_num=2
    def __init__(self, n=None): # Two forms
        super(LongBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(50,180,50)

    def get_shape(self):
        return [(1,0),(1,1),(1,2),(1,3)] if self.shape_id==0 else [(0,2),(1,2),(2,2),(3,2)]

class SquareBlock(Block): # A form
    shape_id=0
    shape_num=1
    def __init__(self, n=None):
        super(SquareBlock, self).__init__()
        self.rect_arr=self.get_shape()
        self.color=(0,0,255)

    def get_shape(self):
        return [(1,1),(1,2),(2,1),(2,2)]

class ZBlock(Block): # Two forms
    shape_id=0
    shape_num=2
    def __init__(self, n=None):
        super(ZBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(30,200,200)

    def get_shape(self):
        return [(2,0),(2,1),(1,1),(1,2)] if self.shape_id==0 else [(0,1),(1,1),(1,2),(2,2)]

class SBlock(Block): # Two forms
    shape_id=0
    shape_num=2
    def __init__(self, n=None):
        super(SBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(255,30,255)

    def get_shape(self):
        return [(1,0),(1,1),(2,1),(2,2)] if self.shape_id==0 else [(0,2),(1,2),(1,1),(2,1)]

class LBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(LBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(200,200,30)

    def get_shape(self):
        if self.shape_id==0: return [(1,0),(1,1),(1,2),(2,2)]
        elif self.shape_id==1: return [(0,1),(1,1),(2,1),(0,2)]
        elif self.shape_id==2: return [(0,0),(1,0),(1,1),(1,2)]
        else: return [(0,1),(1,1),(2,1),(2,0)]

class JBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(JBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(200,100,0)

    def get_shape(self):
        if self.shape_id==0: return [(1,0),(1,1),(1,2),(0,2)]
        elif self.shape_id==1: return [(0,1),(1,1),(2,1),(0,0)]
        elif self.shape_id==2: return [(2,0),(1,0),(1,1),(1,2)]
        else: return [(0,1),(1,1),(2,1),(2,2)]

class TBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(TBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(255,0,0)

    def get_shape(self):
        if self.shape_id==0: return [(0,1),(1,1),(2,1),(1,2)]
        elif self.shape_id==1: return [(1,0),(1,1),(1,2),(0,1)]
        elif self.shape_id==2: return [(0,1),(1,1),(2,1),(1,0)]
        else: return [(1,0),(1,1),(1,2),(2,1)]
        
def create_block():
    n = random.randint(0,19)
    if n==0: return SquareBlock(n=0)
    elif n==1 or n==2: return LongBlock(n=n-1)
    elif n==3 or n==4: return ZBlock(n=n-3)
    elif n==5 or n==6: return SBlock(n=n-5)
    elif n>=7 and n<=10: return LBlock(n=n-7)
    elif n>=11 and n<=14: return JBlock(n=n-11)
    else: return TBlock(n=n-15)

def run():
    pygame.init()
    space=30
    main_block_size=30
    main_panel_width=main_block_size*10
    main_panel_height=main_block_size*20
    screencaption = pygame.display.set_caption('Tetris')
    screen = pygame.display.set_mode((main_panel_width+160+space*3,main_panel_height+space*2)) #Setting window length and width
    main_panel=Panel(screen,main_block_size,[space,space,main_panel_width,main_panel_height])

    pygame.key.set_repeat(200, 30)
    main_panel.create_move_block()

    diff_ticks = 300 # Moving a snakehead event in milliseconds
    ticks = pygame.time.get_ticks() + diff_ticks

    game_state = 1 # Game Status 1. Represents Normal 2. Represents Failure
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                 pygame.quit()
                 exit()
            if event.type == KEYDOWN:
             if event.key == K_LEFT: main_panel.control_block(-1,0)
             if event.key == K_RIGHT: main_panel.control_block(1,0)
             if event.key == K_UP: main_panel.change_block()
             if event.key == K_DOWN: main_panel.control_block(0,1)
             if event.key == K_SPACE:
                flag = main_panel.move_block()
                while flag==1: 
                    flag = main_panel.move_block()
                if flag == 9: game_state = 2
       
        screen.fill((100,100,100)) # Set the interface to grey
        main_panel.paint() # Main panel drawing

        if game_state == 2:
            myfont = pygame.font.Font(None,30)
            white = 255,255,255
            textImage = myfont.render("Game over", True, white)
            screen.blit(textImage, (160,190))

        pygame.display.update() # You must call update to see the drawing display

        if game_state == 1 and pygame.time.get_ticks() >= ticks:
            ticks+=diff_ticks
            if main_panel.move_block()==9: game_state = 2 # Game over

run()

2. Next box

To facilitate the drawing of the next box's prompt window, we define a HintBox class to manage the drawing of the next box and interface.

class HintBox(object):
    next_block=None
    def __init__(self, bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]

    def take_block(self):
        block = self.next_block
        if block is None: # If there are no squares, create one first
            block = create_block()
    
        self.next_block = create_block() # Generate the next box
        return block

    def paint(self):
        mid_x=self._x+self._width/2
        pygame.draw.line(self._bg,self._bgcolor,[mid_x,self._y],[mid_x,self._y+self._height],self._width) 
        bz=self._block_size
        # Draw falling squares
        if self.next_block:
            arr = self.next_block.get_rect_arr()
            minx,miny=arr[0]
            maxx,maxy=arr[0]
            for x,y in arr:
                if x<minx: minx=x
                if x>maxx: maxx=x
                if y<miny: miny=y
                if y>maxy: maxy=y
            w=(maxx-minx)*bz
            h=(maxy-miny)*bz
            # Calculate the offset pixels needed to render the block at the center of the prompt window
            cx=self._width/2-w/2-minx*bz-bz/2 
            cy=self._height/2-h/2-miny*bz-bz/2

            for rect in arr:
                x,y=rect
                pygame.draw.line(self._bg,self.next_block.color,[self._x+x*bz+cx+bz/2,self._y+cy+y*bz],[self._x+x*bz+cx+bz/2,self._y+cy+(y+1)*bz],bz)
                pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz+cx,self._y+y*bz+cy,bz+1,bz+1],1)

Add an attribute to the Panel class

hint_box=None

Inside the Panel class

    def create_move_block(self):
        block = create_block()
        block.move(5-2,-2) # Move the box to the middle 
        self.moving_block=block

The way the box is generated is changed to hint_box

    def create_move_block(self):
        block = self.hint_box.take_block()
        block.move(5-2,-2) # Move the box to the middle 
        self.moving_block=block

Increase the program of initializing hint_box and setting main_panel in run function

    hint_box=HintBox(screen,main_block_size,[main_panel_width+space+space,space,160,160])
    main_panel.hint_box=hint_box

Add the drawing of the next cube prompt window in the main loop of the game

hint_box.paint() # Draw the next box's prompt window

Now you can display the next box prompt properly.

3. The Calculation of Scores

The method of calculating the elimination score is as follows:
1 elements and 100 points
2 elements and 300 points
3 elements and 800 points
4 elements and 1600 points
Similar to the design of the next box prompt window, we can add a ScoreBox class

class ScoreBox(object):
    total_score = 0
    def __init__(self, bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]

    def paint(self):
        myfont = pygame.font.Font(None,36)
        white = 255,255,255
        textImage = myfont.render('Score:%06d'%(self.total_score), True, white)
        self._bg.blit(textImage, (self._x,self._y))

Then add the score_box attribute to Panel

score_box=None

Define a global SCORE_MAP

SCORE_MAP=(100,300,800,1600)

In the check_clear function, if there is a block elimination, it executes

            score = SCORE_MAP[clear_num-1]
            self.score_box.total_score += score

Initialize score_box in run main function

    score_box=ScoreBox(screen,main_block_size,[main_panel_width+space+space,160+space*2,160,160])
    main_panel.score_box=score_box

And draw score_box in the game loop

score_box.paint() # Draw total score

IV. HISTORICAL HIGH SCORE

Prepare to save in the current directory with a pickle file of tetris.db
So first

import pickle,os

Since the highest score can be drawn with ScoreBox when drawing the current score, add a attribute of the highest score and a definition of a file directly to the ScoreBox.

high_score = 0
db_file = 'tetris.db'

Adding pickle load to the initialization function of CoreBox

if os.path.exists(self.db_file): self.high_score = pickle.load(open(self.db_file,'rb'))

Drawing with the highest score added to paint

    def paint(self):
        myfont = pygame.font.Font(None,36)
        white = 255,255,255
        textImage = myfont.render('High: %06d'%(self.high_score), True, white)
        self._bg.blit(textImage, (self._x,self._y))
        textImage = myfont.render('Score:%06d'%(self.total_score), True, white)
        self._bg.blit(textImage, (self._x,self._y+40))

Change the previous direct modification to ScoreBox's score to encapsulate a function of add_score

    def add_score(self, score):
        self.total_score += score
        if self.total_score > self.high_score:
            self.high_score=self.total_score
            pickle.dump(self.high_score, open(self.db_file,'wb+'))

Modify the score in the add_score function and decide whether it exceeds the maximum score. If it exceeds, save the score (of course, you can also judge and save the maximum score at the end of the game or close the interface, reducing disk io)
Look at the picture.

Put down the complete procedure

# -*- coding=utf-8 -*-
import random
import pygame
from pygame.locals import KEYDOWN,K_LEFT,K_RIGHT,K_UP,K_DOWN,K_SPACE
import pickle,os

SCORE_MAP=(100,300,800,1600)

class RectInfo(object):
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

class HintBox(object):
    next_block=None
    def __init__(self, bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]

    def take_block(self):
        block = self.next_block
        if block is None: # If there are no squares, create one first
            block = create_block()
    
        self.next_block = create_block() # Generate the next box
        return block

    def paint(self):
        mid_x=self._x+self._width/2
        pygame.draw.line(self._bg,self._bgcolor,[mid_x,self._y],[mid_x,self._y+self._height],self._width) 
        bz=self._block_size
        # Draw falling squares
        if self.next_block:
            arr = self.next_block.get_rect_arr()
            minx,miny=arr[0]
            maxx,maxy=arr[0]
            for x,y in arr:
                if x<minx: minx=x
                if x>maxx: maxx=x
                if y<miny: miny=y
                if y>maxy: maxy=y
            w=(maxx-minx)*bz
            h=(maxy-miny)*bz
            # Calculate the offset pixels needed to render the block at the center of the prompt window
            cx=self._width/2-w/2-minx*bz-bz/2 
            cy=self._height/2-h/2-miny*bz-bz/2

            for rect in arr:
                x,y=rect
                pygame.draw.line(self._bg,self.next_block.color,[self._x+x*bz+cx+bz/2,self._y+cy+y*bz],[self._x+x*bz+cx+bz/2,self._y+cy+(y+1)*bz],bz)
                pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz+cx,self._y+y*bz+cy,bz+1,bz+1],1)

class ScoreBox(object):
    total_score = 0
    high_score = 0
    db_file = 'tetris.db'
    def __init__(self, bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]
        
        if os.path.exists(self.db_file): self.high_score = pickle.load(open(self.db_file,'rb'))

    def paint(self):
        myfont = pygame.font.Font(None,36)
        white = 255,255,255
        textImage = myfont.render('High: %06d'%(self.high_score), True, white)
        self._bg.blit(textImage, (self._x,self._y))
        textImage = myfont.render('Score:%06d'%(self.total_score), True, white)
        self._bg.blit(textImage, (self._x,self._y+40))

    def add_score(self, score):
        self.total_score += score
        if self.total_score > self.high_score:
            self.high_score=self.total_score
            pickle.dump(self.high_score, open(self.db_file,'wb+'))

class Panel(object): # Layout for drawing the entire game window
    rect_arr=[] # The bottomed square
    moving_block=None # Falling square
    hint_box=None
    score_box=None
    def __init__(self,bg, block_size, position):
        self._bg=bg;
        self._x,self._y,self._width,self._height=position
        self._block_size=block_size
        self._bgcolor=[0,0,0]
    
    def add_block(self,block):
        for x,y in block.get_rect_arr():
            self.rect_arr.append(RectInfo(x,y, block.color))

    def create_move_block(self):
        block = self.hint_box.take_block()
        #block = create_block()
        block.move(5-2,-2) # Move the square to the center 
        self.moving_block=block

    def check_overlap(self, diffx, diffy, check_arr=None):
        if check_arr is None: check_arr = self.moving_block.get_rect_arr()
        for x,y in check_arr:
            for rect_info in self.rect_arr:
                if x+diffx==rect_info.x and y+diffy==rect_info.y:
                    return True
        return False

    def control_block(self, diffx, diffy):
        if self.moving_block.can_move(diffx,diffy) and not self.check_overlap(diffx, diffy):
            self.moving_block.move(diffx,diffy)

    def change_block(self):
        if self.moving_block:
            new_arr = self.moving_block.change()
            if new_arr and not self.check_overlap(0, 0, check_arr=new_arr): # Deformation does not cause overlap of squares
                self.moving_block.rect_arr=new_arr

    def move_block(self):
        if self.moving_block is None: create_move_block()
        if self.moving_block.can_move(0,1) and not self.check_overlap(0,1): 
            self.moving_block.move(0,1)
            return 1
        else:
            self.add_block(self.moving_block)
            self.check_clear()

            for rect_info in self.rect_arr:
                if rect_info.y<0: return 9 # Game failure
            self.create_move_block()
            return 2

    def check_clear(self):
        tmp_arr = [[] for i in range(20)]
        # Save the box in an array by line first
        for rect_info in self.rect_arr:
            if rect_info.y<0: return
            tmp_arr[rect_info.y].append(rect_info)

        clear_num=0
        clear_lines=set([])
        y_clear_diff_arr=[[] for i in range(20)]
        # Calculate rows that can be eliminated from bottom to top and record the number of downward offsets of other rows after elimination
        for y in range(19,-1,-1):
            if len(tmp_arr[y])==10:
                clear_lines.add(y)
                clear_num += 1
            y_clear_diff_arr[y] = clear_num

        if clear_num>0:
            new_arr=[]
            # Skip removed rows and offset other rows
            for y in range(19,-1,-1):
                if y in clear_lines: continue
                tmp_row = tmp_arr[y]
                y_clear_diff=y_clear_diff_arr[y]
                for rect_info in tmp_row:
                    #new_arr.append([x,y+y_clear_diff])
                    new_arr.append(RectInfo(rect_info.x, rect_info.y+y_clear_diff, rect_info.color))
            
            self.rect_arr = new_arr
            score = SCORE_MAP[clear_num-1]
            self.score_box.add_score(score)


    def paint(self):
        mid_x=self._x+self._width/2
        pygame.draw.line(self._bg,self._bgcolor,[mid_x,self._y],[mid_x,self._y+self._height],self._width) # Fill in the background with a thick line segment
        
        # Draw bottomed squares
        bz=self._block_size
        for rect_info in self.rect_arr:
            x=rect_info.x
            y=rect_info.y
            pygame.draw.line(self._bg,rect_info.color,[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
            pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz,self._y+y*bz,bz+1,bz+1],1)
       
        # Draw falling squares
        if self.move_block:
            for rect in self.moving_block.get_rect_arr():
                x,y=rect
                pygame.draw.line(self._bg,self.moving_block.color,[self._x+x*bz+bz/2,self._y+y*bz],[self._x+x*bz+bz/2,self._y+(y+1)*bz],bz)
                pygame.draw.rect(self._bg,[255,255,255],[self._x+x*bz,self._y+y*bz,bz+1,bz+1],1)

class Block(object):
    sx=0
    sy=0
    def __init__(self):
        self.rect_arr=[]

    def get_rect_arr(self): # Four Rectangular Lists for Getting Block Species
        return self.rect_arr

    def move(self,xdiff,ydiff): # A Method for Moving Blocks
        self.sx+=xdiff
        self.sy+=ydiff
        self.new_rect_arr=[]
        for x,y in self.rect_arr:
            self.new_rect_arr.append((x+xdiff,y+ydiff))
        self.rect_arr=self.new_rect_arr

    def can_move(self,xdiff,ydiff):
        for x,y in self.rect_arr:
            if y+ydiff>=20: return False
            if x+xdiff<0 or x+xdiff>=10: return False
        return True

    def change(self):
        self.shape_id+=1 # Next form
        if self.shape_id >= self.shape_num: 
            self.shape_id=0

        arr = self.get_shape()
        new_arr = []
        for x,y in arr:
            if x+self.sx<0 or x+self.sx>=10: # Deformation cannot exceed left and right boundary
                self.shape_id -= 1
                if self.shape_id < 0: self.shape_id = self.shape_num - 1
                return None 

            new_arr.append([x+self.sx,y+self.sy])

        return new_arr

class LongBlock(Block):
    shape_id=0
    shape_num=2
    def __init__(self, n=None): # Two forms
        super(LongBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(50,180,50)

    def get_shape(self):
        return [(1,0),(1,1),(1,2),(1,3)] if self.shape_id==0 else [(0,2),(1,2),(2,2),(3,2)]

class SquareBlock(Block): # A form
    shape_id=0
    shape_num=1
    def __init__(self, n=None):
        super(SquareBlock, self).__init__()
        self.rect_arr=self.get_shape()
        self.color=(0,0,255)

    def get_shape(self):
        return [(1,1),(1,2),(2,1),(2,2)]

class ZBlock(Block): # Two forms
    shape_id=0
    shape_num=2
    def __init__(self, n=None):
        super(ZBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(30,200,200)

    def get_shape(self):
        return [(2,0),(2,1),(1,1),(1,2)] if self.shape_id==0 else [(0,1),(1,1),(1,2),(2,2)]

class SBlock(Block): # Two forms
    shape_id=0
    shape_num=2
    def __init__(self, n=None):
        super(SBlock, self).__init__()
        if n is None: n=random.randint(0,1)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(255,30,255)

    def get_shape(self):
        return [(1,0),(1,1),(2,1),(2,2)] if self.shape_id==0 else [(0,2),(1,2),(1,1),(2,1)]

class LBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(LBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(200,200,30)

    def get_shape(self):
        if self.shape_id==0: return [(1,0),(1,1),(1,2),(2,2)]
        elif self.shape_id==1: return [(0,1),(1,1),(2,1),(0,2)]
        elif self.shape_id==2: return [(0,0),(1,0),(1,1),(1,2)]
        else: return [(0,1),(1,1),(2,1),(2,0)]

class JBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(JBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(200,100,0)

    def get_shape(self):
        if self.shape_id==0: return [(1,0),(1,1),(1,2),(0,2)]
        elif self.shape_id==1: return [(0,1),(1,1),(2,1),(0,0)]
        elif self.shape_id==2: return [(2,0),(1,0),(1,1),(1,2)]
        else: return [(0,1),(1,1),(2,1),(2,2)]

class TBlock(Block): # Four forms
    shape_id=0
    shape_num=4
    def __init__(self, n=None):
        super(TBlock, self).__init__()
        if n is None: n=random.randint(0,3)
        self.shape_id=n
        self.rect_arr=self.get_shape()
        self.color=(255,0,0)

    def get_shape(self):
        if self.shape_id==0: return [(0,1),(1,1),(2,1),(1,2)]
        elif self.shape_id==1: return [(1,0),(1,1),(1,2),(0,1)]
        elif self.shape_id==2: return [(0,1),(1,1),(2,1),(1,0)]
        else: return [(1,0),(1,1),(1,2),(2,1)]
        
def create_block():
    n = random.randint(0,19)
    if n==0: return SquareBlock(n=0)
    elif n==1 or n==2: return LongBlock(n=n-1)
    elif n==3 or n==4: return ZBlock(n=n-3)
    elif n==5 or n==6: return SBlock(n=n-5)
    elif n>=7 and n<=10: return LBlock(n=n-7)
    elif n>=11 and n<=14: return JBlock(n=n-11)
    else: return TBlock(n=n-15)

def run():
    pygame.init()
    space=30
    main_block_size=30
    main_panel_width=main_block_size*10
    main_panel_height=main_block_size*20
    screencaption = pygame.display.set_caption('Tetris')
    screen = pygame.display.set_mode((main_panel_width+160+space*3,main_panel_height+space*2)) #Setting window length and width
    main_panel=Panel(screen,main_block_size,[space,space,main_panel_width,main_panel_height])
    hint_box=HintBox(screen,main_block_size,[main_panel_width+space+space,space,160,160])
    score_box=ScoreBox(screen,main_block_size,[main_panel_width+space+space,160+space*2,160,160])
    
    main_panel.hint_box=hint_box
    main_panel.score_box=score_box

    pygame.key.set_repeat(200, 30)
    main_panel.create_move_block()

    diff_ticks = 300 # Moving a snakehead event in milliseconds
    ticks = pygame.time.get_ticks() + diff_ticks

    game_state = 1 # Game Status 1. Represents Normal 2. Represents Failure
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                 pygame.quit()
                 exit()
            if event.type == KEYDOWN:
             if event.key == K_LEFT: main_panel.control_block(-1,0)
             if event.key == K_RIGHT: main_panel.control_block(1,0)
             if event.key == K_UP: main_panel.change_block()
             if event.key == K_DOWN: main_panel.control_block(0,1)
             if event.key == K_SPACE:
                flag = main_panel.move_block()
                while flag==1: 
                    flag = main_panel.move_block()
                if flag == 9: game_state = 2
       
        screen.fill((100,100,100)) # Set the interface to grey
        main_panel.paint() # Main panel drawing
        hint_box.paint() # Draw the next box's prompt window
        score_box.paint() # Draw total score

        if game_state == 2:
            myfont = pygame.font.Font(None,30)
            white = 255,255,255
            textImage = myfont.render("Game over", True, white)
            screen.blit(textImage, (160,190))

        pygame.display.update() # You must call update to see the drawing display

        if game_state == 1 and pygame.time.get_ticks() >= ticks:
            ticks+=diff_ticks
            if main_panel.move_block()==9: game_state = 2 # Game over

run()

Maybe someone would wonder what the big space in the lower right corner is for?
I'm going to do battle display in that area. The basic article here is almost the end. The next one is going to write AI.

Posted by cheikhbouchihda on Fri, 10 May 2019 12:47:21 -0700