python little happy Tetris (10) to clear the bottoms that have been filled.

Previous articles< python little joy (eight) preliminary organization of Tetris (6) source files > Divide the source code into three separate files
config.py,block.py,main.py

In the previous example, the function of free falling of blocks has been realized. Next comes a function: when the bottom box fills a row, it should be cleared, and the game points should be increased accordingly. At present, the problem of game points is not considered. First, it realizes how to clear the rows that have been filled at the bottom.

The actual effect of clearing rows is as follows


To achieve row cleanup, modify the BottomGroup class in block.py.
Add a method getLineSet that counts the number of squares per row

#Getting rowset information
    def getLineSet(self):
        lineSet ={}
        #Check each box in the bottom box combination one by one
        for b in self.sprites():
            if b.rect.y in lineSet.keys():
                lineSet[b.rect.y].append(b)
            else:
                lineSet[b.rect.y]=[b]        
        return lineSet

A dictionary object is used to record the information of each line. The y coordinate of each line is used as the key value of the dictionary object. All the block objects in the line form a list and are stored in the dictionary according to the key value.

An auxiliary function for printing row set information is added to facilitate viewing bottom box information on the terminal.

#Output line set content, output line height, number of blocks in line, x coordinates of each square mark in line
    def printLineSet(self,lineSet):
        print('-------LineSet------------')
        for y in lineSet:
            xPos=[]
            for b in lineSet[y]:
                xPos.append(b.rect.x)
            print(y,len(lineSet[y]),xPos)

The core algorithm for clearing rows that have been filled at the bottom is implemented in method removeFullLine

    #Check each line of the bottom box and remove it if the line is filled with the box.
    def removeFullLine(self):
        #A list of y coordinates of removed rows
        removeLines =[]
        #Get the current row set information
        lineSet = self.getLineSet()
        self.printLineSet(lineSet)
        
        for y in lineSet:
            #If the number of squares in a row has reached the maximum number of squares in a row
            if len(lineSet[y]) >= int(Config.screenWidth/Config.blockWidth):
                #Clear the boxes one by one, noting that the kill method of the sprite object is called
                for b in lineSet[y]:
                    b.kill()
                lineSet[y]=[]
                removeLines.append(y)

        self.printLineSet(lineSet)

        #If the remaining blocks are higher than the removed rows, move down        
        for b in self.sprites():
            b.downTimes = 0
            for height in removeLines:                
                if height > b.rect.y:
                    #Record the number of times the box should be moved down
                    b.downTimes+=1
        #Calling block's Mobile Method
        for b in self.sprites():
            b.down(b.downTimes*Config.blockWidth)
                    

Complete python code

config.py

# config.py
# Configuration data, definition of global variables

#Definition of color constants
BLACK = (0,0,0)       # Defining Black with RGB Value
WHITE = (255,255,255) # Defining White with RGB Value

#Configuration parameter class
class Config():
    def __init__(self):
        pass
    screenWidth = 320
    screenHeight= 800
    blockWidth = 40
    blockColor = 'blue'
    downSpeed = 2

#A two-dimensional matrix illustration of the shape of a block combination. 1 indicates that there is a square at that place, and 0 indicates that there is no square at that place.
shapeGraph=[
(
[1,1,1,1],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
),
(
[0,1,0,0],
[1,1,1,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,0,0,0],
[1,0,0,0],
[1,0,0,0],
),
(
[1,1,1,0],
[1,0,0,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,0,0,0],
[1,1,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[1,0,0,0],
[1,0,0,0],
[0,0,0,0],
),
(
[1,0,0,0],
[1,1,0,0],
[1,0,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[0,1,1,0],
[0,0,0,0],
[0,0,0,0],
),
(
[1,1,0,0],
[1,1,0,0],
[0,0,0,0],
[0,0,0,0],
),
]

#Converting Shape Diagram to Shape Coordinate List
def shpaeGraph2List(shapeGraph):
    shapeList =[]
    for g in shapeGraph:
        shape=[]
        for y in range(4):
            for x in range(4):
                if g[y][x] == 1 :
                    shape.append([x*Config.blockWidth,y*Config.blockWidth])
        shapeList.append(shape)
    return shapeList

#Global variable shapeList that stores the initial coordinate list of shapes
shapeList = shpaeGraph2List(shapeGraph)

block.py

# block.py
# Definition of Block and Block Combination Class

import pygame
from PIL import Image,ImageDraw
from config import *

# Cube class
class Block(pygame.sprite.Sprite):
    def __init__(self,x,y):
        self.inix = x
        self.iniy = y
        pygame.sprite.Sprite.__init__(self)     
        self.image = self.createBlockImg()
        self.rect = self.image.get_rect()        
        self.rect.x = x
        self.rect.y = y
        #Number of downshifts, used to clear rows filled at the bottom
        self.downTimes = 0

    #Generating Block Pictures
    def createBlockImg(self):
        #Generate a rectangular image of specified size and color
        im = Image.new('RGB',(Config.blockWidth,Config.blockWidth),Config.blockColor)
        draw = ImageDraw.Draw(im)
        #Draw a black border
        draw.line([(0,0),(0,Config.blockWidth)],'black',1)
        draw.line([(0,Config.blockWidth),(Config.blockWidth,Config.blockWidth)],'black',1)
        draw.line([(Config.blockWidth,Config.blockWidth),(Config.blockWidth,0)],'black',1)
        draw.line([(Config.blockWidth,0),(0,0)],'black',1)
        data = im.tobytes()      #Convert to bytes
        size = im.size           #Graphic Resolution
        mode = im.mode        
        return pygame.image.fromstring(data,size,mode)

    #Reset the initial position
    def reset(self):
        self.rect.x = self.inix
        self.rect.y = self.iniy
        
    #The box moves down
    def down(self,distance):
        # Downward movement
        self.rect.centery += distance       

    #Square moves left and right
    def move(self,distance):
        # Left and right movement
        self.rect.centerx += distance 
       

# Represents the combination of multiple blocks in the drop, and typeIdx indicates the index of the combined shape in the shapeList
class FallingGroup(pygame.sprite.Group):
    def __init__(self, typeIdx,bottomGroup):
        self.bottomGroup = bottomGroup
        pygame.sprite.Group.__init__(self)
        iniX = int((Config.screenWidth/2)/Config.blockWidth)*Config.blockWidth

        shape = shapeList[typeIdx]       
        for xyPair in shape:
            x = iniX+xyPair[0]
            y = xyPair[1]             
            self.add(Block(x,y)) 
        #Envelope Rectangle of Composite Objects    
        self.rect = self.boundingRect()
        #Record the initial location, using the copy method here    
        self.iniRect = self.rect.copy()
        #Falling velocity
        self.downSpeed = Config.downSpeed

    #Reset the initial position
    def reset(self):
        #To restore the initial location, use the copy method here  
        self.rect = self.iniRect.copy()       
        for block in self.sprites():
            block.reset()

    #Block combinations move downward
    def down(self, distance):
        if not self.bottomGroup.collided(self):
            self.rect.y += distance
            for block in self.sprites():
                block.down(distance)           
        else:
            #self.reset()
            self.bottomGroup.eat(self)              

    #Square Combination Moves Left and Right
    def move(self, distance):
        #print([self.rect.x,self.rect.y,self.rect.width,self.rect.height])
        if (distance > 0 and self.rect.x < Config.screenWidth-self.rect.width) or (distance < 0 and self.rect.x > 0):
            self.rect.x += distance
            for block in self.sprites():
                block.move(distance) 

    #Finding the Rectangle Surrounding the Composite Object
    def boundingRect(self):
        minX = Config.screenWidth+100
        minY = Config.screenHeight+100
        maxX = -100
        maxY = -100
        for block in self.sprites():
            if block.rect.x < minX:
                minX = block.rect.x
            if block.rect.y < minY:
                minY = block.rect.y
            if block.rect.x > maxX:
                maxX = block.rect.x
            if block.rect.y > maxY:
                maxY = block.rect.y
        return pygame.Rect(minX,minY,maxX-minX+Config.blockWidth,maxY-minY+Config.blockWidth)

    #rotate
    def rotate(self):

        #The center of the combined object is taken as the center of rotation, and the center of rotation should be located at the grid point.    
        cx=int((self.rect.x+self.rect.width/2)/Config.blockWidth)*Config.blockWidth
        cy=int((self.rect.y+self.rect.height/2)/Config.blockWidth)*Config.blockWidth
        for block in self.sprites():
            #Calculate the distance difference between the center of the current square and the center of rotation
            dx = block.rect.centerx -cx
            dy = block.rect.centery -cy
            #The complex number consisting of the distance difference multiplied by the complex number i is the result of 90 degrees counter-clockwise rotation of the original complex number
            r  = complex(dx,dy)*complex(0,1)
            #Get the result after rotation.
            block.rect.centerx = cx + r.real + Config.blockWidth
            block.rect.centery = cy + r.imag

        #Get the original horizontal position of the envelope rectangle
        lastRectX = self.rect.x            
        #Updating Envelope Rectangle of Composite Objects
        self.rect = self.boundingRect()
        dx =  lastRectX - self.rect.x 
        
        #Keep the horizontal position of the rotated composite object unchanged
        self.rect.x += dx
        for block in self.sprites():
            block.rect.x+=dx


# Represents a combination of bottom squares
class BottomGroup(pygame.sprite.Group):
    def __init__(self):
        pygame.sprite.Group.__init__(self) 

    #Check whether the falling square collides with the bottom square
    def collided(self,fallingGroup):
        #Check if window boundaries are encountered
        for d in fallingGroup.sprites():
            if Config.screenHeight - d.rect.y <=Config.blockWidth:
                return True
        #Check if you encounter a box parked at the bottom
        for d in fallingGroup.sprites():
            for b in self.sprites():
                if b.rect.y - d.rect.y <=Config.blockWidth and b.rect.x == d.rect.x:
                    return True
        return False

    #Eat the falling square
    def eat(self,fallingGroup):

        for d in fallingGroup.sprites():
            self.add(d)            
        #Clear all elements in the drop box combination
        fallingGroup.empty()
        #Clear up the filled rows
        self.removeFullLine()

    #Check each line of the bottom box and remove it if the line is filled with the box.
    def removeFullLine(self):
        #A list of y coordinates of removed rows
        removeLines =[]
        #Get the current row set information
        lineSet = self.getLineSet()
        self.printLineSet(lineSet)
        
        for y in lineSet:
            #If the number of squares in a row has reached the maximum number of squares in a row
            if len(lineSet[y]) >= int(Config.screenWidth/Config.blockWidth):
                #Clear the boxes one by one, noting that the kill method of the sprite object is called
                for b in lineSet[y]:
                    b.kill()
                lineSet[y]=[]
                removeLines.append(y)

        self.printLineSet(lineSet)

        #If the remaining blocks are higher than the removed rows, move down        
        for b in self.sprites():
            b.downTimes = 0
            for height in removeLines:                
                if height > b.rect.y:
                    #Record the number of times the box should be moved down
                    b.downTimes+=1
        #Calling block's Mobile Method
        for b in self.sprites():
            b.down(b.downTimes*Config.blockWidth)
                    
    #Getting rowset information
    def getLineSet(self):
        lineSet ={}
        #Check each box in the bottom box combination one by one
        for b in self.sprites():
            if b.rect.y in lineSet.keys():
                lineSet[b.rect.y].append(b)
            else:
                lineSet[b.rect.y]=[b]        
        return lineSet
    
    #Output line set content, output line height, number of blocks in line, x coordinates of each square mark in line
    def printLineSet(self,lineSet):
        print('-------LineSet------------')
        for y in lineSet:
            xPos=[]
            for b in lineSet[y]:
                xPos.append(b.rect.x)
            print(y,len(lineSet[y]),xPos)

main.py

# main.py
# Tetris, main program file

import pygame
from block import *
import random

#Random Generation of Falling Block Combination
def randomFallingGroup():
    #return FallingGroup(typeIdx=0,bottomGroup=bottomGroup)
    return FallingGroup(typeIdx=random.randint(0,8),bottomGroup=bottomGroup)

# Redraw the display area to form the animation effect
def animate():
    global fallingGroup,bottomGroup,lastCheckPoint
	
	#Set the screen to black
    screen.fill(BLACK)

    #According to the falling speed, the interval time between two checkpoints (unit milliseconds) is calculated. If the next checkpoint is reached, the falling method is executed.
    if pygame.time.get_ticks() - lastCheckPoint > 1000/fallingGroup.downSpeed:
        #Dropping Block Combination Executing Dropping Method
        fallingGroup.down(Config.blockWidth)
        lastCheckPoint = pygame.time.get_ticks()

    #If the falling block combination has been "eaten" by the bottom block combination, a new falling block combination is generated.
    if len(fallingGroup.sprites()) <= 0:
        fallingGroup = randomFallingGroup()
    #Dropping Block Combination Execution Drawing Method
    fallingGroup.draw(screen) 
    #Drawing method of bottom block combination
    bottomGroup.draw(screen)   
    #Refresh screen 
    pygame.display.flip() 


# ------------------------main---------------------------------------------------------------------

# Initialize various objects
pygame.init()
#Screen of Game Window
screen = pygame.display.set_mode([Config.screenWidth,Config.screenHeight])
#Fill the background with black
screen.fill(BLACK)
#Setting the title of the graphics window
pygame.display.set_caption("Tetris") 
#Game clock
clock = pygame.time.Clock()

#Generating Bottom Block Combination Objects
bottomGroup =BottomGroup()

#Random Generation of a Drop Block Combination Object
fallingGroup = randomFallingGroup()

#Checkpoint at Falling Time
lastCheckPoint = pygame.time.get_ticks()

# Event Processing Loop
running = True
while running:
    #Set the number of frames per second
    clock.tick(30) 
    for event in pygame.event.get():    
        if event.type == pygame.QUIT: running = False
        if event.type == pygame.KEYDOWN:          # If you press the key on the keyboard
            if event.key == pygame.K_LEFT:        # If you press the left direction key
            	#Shift one lattice to the left
                fallingGroup.move(-1*Config.blockWidth)
            elif event.key == pygame.K_RIGHT:     #If you press the right direction key
                #Shift a grid to the right
                fallingGroup.move(Config.blockWidth)
            elif event.key == pygame.K_UP:        #If the upward direction key is pressed
                #Call the rotation method
                fallingGroup.rotate()
            elif event.key == pygame.K_DOWN:      #If the downward direction key is pressed
                #Falling speed multiplied by 100
                fallingGroup.downSpeed*=100
    #Call the method of drawing animation
    animate()  
pygame.quit() #Quit pygame

Keywords: Python Mobile

Added by ihcra on Thu, 03 Oct 2019 14:08:49 +0300