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将其转换为毫秒