离谱的俄罗斯方块(小白自学py)

本文详细介绍了使用Python和pygame库开发的一个简单俄罗斯方块游戏,包括游戏主循环、不同类型的块组管理(固定和下落)、碰撞检测以及方块的移动、旋转和消除功能。
摘要由CSDN通过智能技术生成

main.py

import pygame, sys
from pygame.locals import *
from game import *
from const import *

pygame.init()
DISPLAYSURF = pygame.display.set_mode( (GAME_WIDTH_SIZE, GAME_HEIGHT_SIZE) )
game = Game(DISPLAYSURF)
pygame.display.set_caption("简陋的俄罗斯方块。")

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
    game.update()
    DISPLAYSURF.fill( (0, 0, 0) )
    rect = pygame.Rect(240, 0, 320, 600)  # 创建一个矩形,该矩形将位于屏幕的中央
    pygame.draw.rect(DISPLAYSURF, (18,18,18), rect)
    game.draw()
    pygame.display.update()

 game.py

import pygame
from pygame.locals import *
from blockGroup import *
from const import *

class Game(pygame.sprite.Sprite):
    def __init__(self, surface):
        self.surface = surface
        self.fixedBlockGroup = BlockGroup(BlockGroupType.FIXED, BLOCK_SIZE_W, BLOCK_SIZE_H, [], self.getRelPos())
        self.dropBlockGroup = None
        self.nextBlockGroup = None
        self.gameOverImage = pygame.image.load(OVER_IMAGE)
        self.scoreFont = pygame.font.Font(None, 48)
        self.score = 0
        self.isGameOver = False
        self.generateNextBlockGroup()

    def generateDropBlockGroup(self):
        conf = BlockGroup.GenerateBlockGroupConfig(0, GAME_COL/2 - 1)# 出生点位置
        self.dropBlockGroup = BlockGroup(BlockGroupType.DROP, BLOCK_SIZE_W, BLOCK_SIZE_H, conf, self.getRelPos())
        # conf 表示位于屏幕中间的一个随机方块组中每一个方块的配置信息(值为一个三维列表);self.dropBlockGroup 属性的值为包含了四个Block类实例的一维列表

    def generateDropBlockGroup(self):
        self.dropBlockGroup = self.nextBlockGroup
        self.dropBlockGroup.setBaseIndexes(0, GAME_COL/2-1)
        self.generateNextBlockGroup()

    def generateNextBlockGroup(self):
        conf = BlockGroup.GenerateBlockGroupConfig(0, GAME_COL + 3)# 提示下一个方块类型的位置
        self.nextBlockGroup = BlockGroup(BlockGroupType.DROP, BLOCK_SIZE_W, BLOCK_SIZE_H, conf, self.getRelPos())

    def draw(self):
        self.fixedBlockGroup.draw(self.surface)
        if self.dropBlockGroup:
            self.dropBlockGroup.draw(self.surface)
        self.nextBlockGroup.draw(self.surface)
        if self.isGameOver:
            rect1 = self.gameOverImage.get_rect()# 创建一个和图片大小一样的矩形
            rect1.centerx = GAME_WIDTH_SIZE/2# 设置矩形的中心坐标
            rect1.centery = GAME_HEIGHT_SIZE/2
            self.surface.blit(self.gameOverImage, rect1)# 把图片按照矩形的位置画出来
        scoreTextImage = self.scoreFont.render('Score:' + str(self.score), True, (255, 255, 255))
        self.surface.blit(scoreTextImage, (10,20))# 把分数渲染出来

    def getRelPos(self):# 偏移
        return(240, 50)

    def willCollido(self):# 用来检测动态块组是不是将要和静态块组发生碰撞的函数
        hash = {}
        allIndexes = self.fixedBlockGroup.getBlockIndexes()# [( ,), ( ,), ( ,), ...]
        for idx in allIndexes:
            hash[idx] = 1#{( , ):1, ( , ):1, ( , ):1, ...}
        dropIndexes = self.dropBlockGroup.getNextBlockIndexes()# [( ,), ( ,), ( ,), ...]
        for dropIdex in dropIndexes:
            if hash.get(dropIdex):# 判断是不是将要和静态组碰撞
                return True
            if dropIdex[0] >= GAME_ROW:# 判断是不是将要和地板碰撞
                return True
        return False

    def update(self):
        if self.isGameOver:
            return
        self.checkGameOver()
        self.fixedBlockGroup.update()
        if self.fixedBlockGroup.IsEliminating():# 如果静态组需要执行消除,则不执行下面的逻辑了
            return
        if self.dropBlockGroup:
            self.dropBlockGroup.update()
        else:
            self.generateDropBlockGroup()
        if self.willCollido():# 如果检测到已经发生碰撞
            blocks = self.dropBlockGroup.getBlocks()# 获取DROP块组的blocks属性,赋给变量blocks
            for blk in blocks:# 遍历变量blocks中的每一个实例,
                self.fixedBlockGroup.addBlocks(blk)# 把它们追加到FIXED类型的块组中
            self.dropBlockGroup.clearBlocks()# 追加完成后,清除下落类型的块组
            self.dropBlockGroup = None
            # 场上没有DROP类型的块组了,在下一次更新时,game类实例的update方法会重新生成一个新的DROP类型的块组
            score1 = self.fixedBlockGroup.processEliminate()
            if score1:# 对静态方块组调用消除函数
                self.score += score1# 成功消除则分数+1

    def checkGameOver(self):
        allIndexes = self.fixedBlockGroup.getBlockIndexes()
        for idx in allIndexes:
            if idx[0] < 2:
                self.isGameOver = True

blockGroup.py

import random
import pygame, sys
from pygame.locals import *
from const import *
from block import *
from utils import *

class BlockGroup(object):   
    def GenerateBlockGroupConfig(rowIdx, colIdx):# 传入出生点位置
        shapeIdx = random.randint(0, len(BLOCK_SHAPE) - 1)# 方块组形状
        bType = random.randint(0, BlockType.BLOCKMAX - 1)# 方块组颜色
        configList = []
        rotIdx = 0# 初始旋转状态
        for i in range( len( BLOCK_SHAPE[shapeIdx][rotIdx] )):# 形状和旋转状态
            config = {
                "blockType" : bType,# 颜色信息
                "blockShape" : shapeIdx,# 形状
                "blockRot" : rotIdx,# 旋转
                "blockGroupIdx" : i,# 第几个块
                "rowIdx" : rowIdx,# 出生点行数
                "colIdx" : colIdx# 出生点列数
            }
            configList.append(config)# 把包含了每一个小块的所有信息的字典追加到配置列表
        return configList# 一组方块组的颜色位置信息[{::::::}, {}, {}, {}]
    
    def __init__(self,blockGroupType, width, height, blockConfigList, relpos):
        super().__init__()
        self.blocks = []
        self.time = 0# 上一次执行掉落时留下的时间戳
        self.pressTime = {}# 每次按下左和右键都会留下的时间戳组成的字典
        self.blockGroupType = blockGroupType# 静止还是下落类型
        self.isEliminating = False# 初始均为不需要消除状态
        self.dropInterval = 300# 掉落时间间隔
        self.eliminateTime = 0
        self.eliminateRowlist = []
        self.a = 0
        for config in blockConfigList:# 遍历列表中的每一个字典,再根据这些信息生成四个Blcok类实例
            blk = Block(config["blockType"], config["rowIdx"], config["colIdx"], config["blockShape"], config["blockRot"], config["blockGroupIdx"], width, height, relpos)
            self.blocks.append(blk)# 值为包含了四个Block类实例的一维列表

    def setBaseIndexes(self, baseRow, baseCol):
        for blk in self.blocks:
            blk.setBaseIndex(baseRow, baseCol)
            
    def draw(self, surface):# BlockGroup类实例调用BlockGroup类的draw方法
        for b in self.blocks:
            b.draw(surface)# Block类实例则调用Block类的draw方法
    
    def update(self):
        oldTime = self.time# 上一次的时间戳为旧戳
        curTime = getCurrentTime()# 获取当前时间戳
        diffTime = curTime - oldTime# 得知和上一次时间戳的时间间隔
        if self.blockGroupType == BlockGroupType.DROP:# 如果是下落方块组就可以执行自然掉落的更新方法
            if diffTime >= self.dropInterval:# 且时间差大于掉落间隔
                self.time = curTime# 当前的新时间戳保存起来,作为判断什么时候进行下一次掉落的旧时间戳
                #print(f"下落时间戳:{self.time}")
                for b in self.blocks:# 对每一个块实例执行下落
                    b.drop()
            self.keyDownHandler()# 最后下落方块组执行加速掉落、平移和旋转的更新方法
        
        for blk in self.blocks:
            blk.update()
        
        if self.IsEliminating():
            if getCurrentTime() - self.eliminateTime > 500:
                tmpBlocks = []
                for blk in self.blocks:
                    if blk.getIndex()[0] not in self.eliminateRowlist:
                        tmpBlocks.append(blk)
                        for i in self.eliminateRowlist:
                            if blk.getIndex()[0] < i:
                                blk.dropc += 1
                for blk in tmpBlocks:
                    while blk.dropc > 0:
                        blk.drop()
                        blk.dropc -= 1
                self.blocks = tmpBlocks# 更新方块组blocks属性包含的方块,符合条件的那一行在此时被丢掉
                print(f"消除后的方块剩余{len(self.blocks)}个")
                self.setEliminate(False)# 结束消除状态
                print(f"结束第{self.eliminateRowlist}行的消除状态\n")
                self.eliminateRowlist = []
    
    def getBlockIndexes(self):
        return [bloc.getIndex() for bloc in self.blocks]# [( , ), ( , ), ( , ), ( , )]
    
    def getNextBlockIndexes(self):
        return [bloc.getNextIndex() for bloc in self.blocks]
    
    def getLeftBlockIndexes(self):
        return [bloc.getLeftIndex() for bloc in self.blocks]
    
    def getRightBlockIndexes(self):
        return [bloc.getRightIndex() for bloc in self.blocks]
    
    def getUpBlockIndexes(self):
        return [bloc.getBlockRoConfigIndex() for bloc in self.blocks]
    
    def getBlocks(self):
        return self.blocks
    
    def clearBlocks(self):
        self.blocks = []
    
    def addBlocks(self, bl):
        self.blocks.append(bl)

    def checkAndSetPressTime(self, key):#检查并设置按下时间
        ret = False
        if getCurrentTime() - self.pressTime.get(key, 0) > 30:
            ret = True
        self.pressTime[key] = getCurrentTime()
        return ret

    def keyDownHandler(self):
        pressed = pygame.key.get_pressed()# 按键按下状态的列表
        if pressed[K_LEFT] and self.checkAndSetPressTime(K_LEFT):# 左键被按下且距离上次按下超过指定间隔
            b = True
            for blk in self.blocks:
                if blk.isLeftBound():
                    b = False
                    break
            if b:
                for blk in self.blocks:
                    blk.doLeft()
            else:
                print("不能左移")
                
        if pressed[K_RIGHT] and self.checkAndSetPressTime(K_RIGHT):
            b = True
            for blk in self.blocks:
                if blk.isRightBound():
                    b = False
                    break
            if b:
                for blk in self.blocks: 
                    blk.doRight()
            else:
                print("不能右移")

        if pressed[K_DOWN]:
            self.dropInterval = 50
        else:
            self.dropInterval = 800

        if pressed[K_UP] and self.checkAndSetPressTime(K_UP):
            for blk in self.blocks:
                blk.doRotate()

    def isRoOutBound(self):
        for blo in self.blocks:
            if blo.blockRot >= GAME_COL:
                return False

    def doEliminate(self):# 消除函数
        eliminateBlk = {}
        self.eliminateTime = getCurrentTime()
        for row in self.eliminateRowlist:
            for col in range(0, GAME_COL):
                idx = (row, col)
                eliminateBlk[idx] = 1# 把所有位于第row行的位置信息映射到哈希表
        self.printone(self.eliminateRowlist,eliminateBlk)#
        i = 0
        k = 0
        for blk in self.blocks:
            k += 1
            if eliminateBlk.get(blk.getIndex()):# 对静态组的每个块的索引检查,如果有块出现在了需要消除的行
                i += 1
                print(blk.getIndex())
                self.setEliminate(True)# 静态方块组进入闪烁状态
                blk.startBlink()# 对该小块执行闪烁
        print(f"对{k}个方块中的{i}个方块进行消除")

    def processEliminate(self):# 判断是否需要执行消除
        hash = {}

        allIndexes = self.getBlockIndexes()
        for idx in allIndexes:
            hash[idx] = 1# 把方块组的所有小块放在哈希表
        print(f"\n目前已有{len(self.blocks)}个小方块")#hash:{hash}

        for row in range(GAME_ROW-1, -1, -1):
            full = True
            for col in range(0, GAME_COL):
                idx = (row, col)# 遍历每一行每一列的位置
                if not hash.get(idx):# 如果这个块的坐标没有出现在哈希表中,说明这一行还没满
                    full = False# 不需要消除,直接结束本行的遍历,开始下一行
                    break
            if full:# 如果需要消除,则对本行执行消除函数
                self.eliminateRowlist.append(row)
                print(f"第{row}行符合消除条件")
        if self.eliminateRowlist:
            self.doEliminate()
            return len(self.eliminateRowlist)
    
    def setEliminate(self, el):# 设置方块组的消除状态
        self.isEliminating = el

    def IsEliminating(self):# 是否是要消除的
        return self.isEliminating

    def printone(self,aa,bb):
        deffint = getCurrentTime() - self.a
        if deffint > 100:
            print(f"消除第{aa}行的预计共{len(bb)}个方块")
        self.a = getCurrentTime()
        

block.py

import pygame
from pygame.locals import *
from const import *
from utils import *

class Block(pygame.sprite.Sprite):
    def __init__(self, blockType, baseRowIdx, baseColIdx, blockShape, blockRot,blockGroupIdx, width, height, relPos):
        super().__init__()
        self.blockType = blockType# 颜色
        self.blockShape = blockShape# 形状
        self.blockRot = blockRot# 旋转的下标
        self.blockGroupIdx = blockGroupIdx# 表示是本块组中的第几个块
        self.baseRowIdx = baseRowIdx# 行数,初始为出生点行数
        self.baseColIdx = baseColIdx# 列数,初始为出生点列数
        self.width = width
        self.height = height
        self.relPos = relPos
        self.blink = False
        self.blinkCount = 0
        self.dropc = 0
        self.loadImage()
        self.updateImagePos()

    def startBlink(self):
        self.blink = True
        self.blinkTime = getCurrentTime()

    def setBaseIndex(self, baseRowIdx, baseColIdx):
        self.baseRowIdx = baseRowIdx
        self.baseColIdx = baseColIdx

    def loadImage(self):
        self.image = pygame.image.load( BLOCK_RES[self.blockType] )
        self.image = pygame.transform.scale(self.image, (self.width, self.height))

    def updateImagePos(self):
        self.rect = self.image.get_rect()# 实例的矩形属性设置为刚刚调整好的图片大小的矩形属性
        self.rect.left = self.relPos[0] + self.width * self.colIdx
        self.rect.top = self.relPos[1] + self.height * self.rowIdx# 更改top = Y轴偏移 + 图片高*行数

    def draw(self, surface):
        self.updateImagePos()# 更新位置信息
        if self.blink and self.blinkCount % 2 == 0:
            return# 若为偶数则不执行绘制
        surface.blit(self.image, self.rect)

    def update(self):
        if self.blink:
            diffTime = getCurrentTime() - self.blinkTime
            self.blinkCount = int(diffTime / 100)# 每隔150ms决定一次绘制或不绘制

    def getIndex(self):
        return (int(self.rowIdx), int(self.colIdx))

    def getNextIndex(self):
        return (int(self.rowIdx + 1), int(self.colIdx))

    def getLeftIndex(self):
        return (int(self.rowIdx), int(self.colIdx - 1))

    def getRightIndex(self):
        return (int(self.rowIdx), int(self.colIdx + 1))

    def getRotateIndex(self):
        RoRow = self.baseRowIdx + self.getBlockRoConfigIndex()[0]
        RoCol = self.baseColIdx + self.getBlockRoConfigIndex()[1]
        return (int(RoRow), int(RoCol))

    def isLeftBound(self):
        return self.colIdx == 0

    def isRightBound(self):
        return self.colIdx == GAME_COL - 1

    def getBlockConfigIndex(self):# 获取方块的索引
        return BLOCK_SHAPE[self.blockShape][self.blockRot][self.blockGroupIdx]# 返回一个元组(, ) 表示其在父级中的相对行数、列数

    def getBlockRoConfigIndex(self):# 获取方块旋转后的索引
        blockRot = self.blockRot + 1
        if self.blockRot + 1 >= len(BLOCK_SHAPE[self.blockShape]):
            blockRot = 0
        return BLOCK_SHAPE[self.blockShape][blockRot][self.blockGroupIdx]

    def rowIdx(self):
        return self.baseRowIdx + self.getBlockConfigIndex()[0]# + 元组中的第一个值

    def colIdx(self):
        return self.baseColIdx + self.getBlockConfigIndex()[1]

    @property
    def rowIdx(self):
        return self.baseRowIdx + self.getBlockConfigIndex()[0]

    @property
    def colIdx(self):
        return self.baseColIdx + self.getBlockConfigIndex()[1]

    def doLeft(self):
        self.baseColIdx -= 1

    def doRight(self):
        self.baseColIdx += 1

    def drop(self):
        self.baseRowIdx += 1

    def doRotate(self):
        self.blockRot += 1
        if self.blockRot >= len(BLOCK_SHAPE[self.blockShape]):
            self.blockRot = 0

const.py

class BlockType:
    BLUE = 0
    CYAN = 1
    GREEN = 2
    POWDER = 3
    PURPLE = 4
    RED = 5
    TEA = 6
    BLOCKMAX = 7
# 类可以具有被称为“常量”的属性,这些属性在类定义后就不能改变
class BlockGroupType:
    FIXED = 0# 固定
    DROP = 1# 下落

BLOCK_RES = {
    BlockType.BLUE : "pic/blue.jpg",
    BlockType.CYAN : "pic/cyan.jpg",
    BlockType.GREEN : "pic/green.jpg",
    BlockType.POWDER : "pic/powder.jpg",
    BlockType.PURPLE : "pic/purple.jpg",
    BlockType.RED : "pic/red.jpg",
    BlockType.TEA : "pic/tea.jpg",
}

GAME_ROW = 17
GAME_COL = 10

BLOCK_SIZE_W = 32
BLOCK_SIZE_H = 32

GAME_WIDTH_SIZE = 800
GAME_HEIGHT_SIZE = 600

BLOCK_SHAPE = [
    [ ((0,0), (0,1), (1,0), (1,1)) ],# 方形

    [ ((0,0), (0,1), (0,2), (0,3)), ((0,0), (1,0), (2,0), (3,0)) ],# 长条型

    [ ((0,0), (0,1), (1,1), (1,2)), ((0,1), (1,0), (1,1), (2,0)) ],# z字型

    [ ((0,1), (0,2), (1,0), (1,1)), ((0,0), (1,0), (1,1), (2,1)) ],# 反z字型

    [ ((0,1), (1,0), (1,1), (1,2)), ((0,1), (1,0), (1,1), (2,1)),
      ((1,0), (1,1), (1,2), (2,1)) , ((0,1), (1,1), (1,2), (2,1)), ],# 飞机型

    [ ((0,2), (1,0), (1,1), (1,2)), ((0,0), (0,1), (1,1), (2,1)), 
      ((0,0), (0,1), (0,2), (1,0)), ((0,0), (1,0), (2,0), (2,1)) ],# 正L型

    [ ((0,0), (1,0), (1,1), (1,2)), ((0,1), (1,1), (2,0), (2,1)),
      ((0,0), (0,1), (0,2), (1,2)), ((0,0), (0,1), (1,0), (2,0)) ],# 反L型
]

OVER_IMAGE = "pic/ZombiesWon.jpg"

utils.py

import time

def getCurrentTime():
    t = time.time()# 获取了当前时间的 Unix 时间戳
    return int(t * 1000)# 乘以1000将其转换为毫秒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值