前面的文章《python小欢喜(八)俄罗斯方块 (6) 源码文件的初步组织》将源代码分成了3个独立的文件
config.py,block.py,main.py
为了显示一个小方块,在block.py的 Block类的初始化方法中有如下语句
self.image = pygame.image.load("block.png")
这要求在源码所在的文件目录中存放有 block.png这个图片文件。 接下来想办法去掉对这个图片文件的依赖,通过代码生成图片
为此在block.py中添加
from PIL import Image,ImageDraw
为Block类添加一个生成方块图片的方法
#生成方块图片
def createBlockImg(self):
#生成一个指定大小与颜色的矩形图像
im = Image.new('RGB',(Config.blockWidth,Config.blockWidth),Config.blockColor)
draw = ImageDraw.Draw(im)
#画一个黑色的边框
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() #转换成字节
size = im.size #图形分辨率
mode = im.mode
return pygame.image.fromstring(data,size,mode)
对config.py中的配置类 class Config 也做相应的修改
#配置参数类
class Config():
def __init__(self):
pass
screenWidth = 600
screenHeight= 800
blockWidth = 40
blockColor = 'blue'
speed = 40
在其中添加了一行:blockColor = ‘blue’ ,指明方块的颜色为蓝色
修改后的完整代码
config.py
# config.py
# 配置数据,全局变量的定义
#颜色常量的定义
BLACK = (0,0,0) # 用RGB值定义黑色
WHITE = (255,255,255) # 用RGB值定义白色
#配置参数类
class Config():
def __init__(self):
pass
screenWidth = 600
screenHeight= 800
blockWidth = 40
blockColor = 'blue'
speed = 40
#方块移动的速度
speed = Config.speed
#方块组合形状的二维矩阵图示,1表示该处有方块,0表示没有
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],
),
]
#将形状图转换为形状坐标列表
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
#存放形状初始坐标列表的全局变量 shapeList
shapeList = shpaeGraph2List(shapeGraph)
block.py
# block.py
# 方块及方块组合类的定义
import pygame
from PIL import Image,ImageDraw
from config import *
# 方块类
class Block(pygame.sprite.Sprite):
def __init__(self,x,y):
self.inix = x
self.iniy = y
pygame.sprite.Sprite.__init__(self)
#self.image = pygame.image.load("block.png")
self.image = self.createBlockImg()
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#生成方块图片
def createBlockImg(self):
im = Image.new('RGB',(Config.blockWidth,Config.blockWidth),Config.blockColor)
draw = ImageDraw.Draw(im)
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() #转换成字节
size = im.size #图形分辨率
mode = im.mode
return pygame.image.fromstring(data,size,mode)
#重置初始位置
def reset(self):
self.rect.x = self.inix
self.rect.y = self.iniy
#方块向下移动
def down(self, speed):
# 向下移动
self.rect.centery += speed
#方块左右移动
def move(self, speed):
# 左右移动
self.rect.centerx += speed
# 表示下落中的多个方块的组合,typeIdx指明组合形状在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))
#得到组合对象的包络矩形
self.rect = self.boundingRect()
#记录初始位置,此处要使用copy方法
self.iniRect = self.rect.copy()
#重置初始位置
def reset(self):
#恢复初始位置,此处要使用copy方法
self.rect = self.iniRect.copy()
for block in self.sprites():
block.reset()
#方块组合向下移动
def down(self, speed):
if not self.bottomGroup.collided(self):
self.rect.y += speed
for block in self.sprites():
block.down(speed)
else:
#self.reset()
self.bottomGroup.eat(self)
#方块组合左右移动
def move(self, speed):
#print([self.rect.x,self.rect.y,self.rect.width,self.rect.height])
if (speed > 0 and self.rect.x < Config.screenWidth-self.rect.width) or (speed < 0 and self.rect.x > 0):
self.rect.x += speed
for block in self.sprites():
block.move(speed)
#求出包围组合对象的矩形
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)
#旋转
def rotate(self):
#取组合对象的中心点作为旋转中心,旋转中心应位于网格点上
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():
#求出当前方块的中心与旋转中心的距离差
dx = block.rect.centerx -cx
dy = block.rect.centery -cy
#距离差组成的复数 乘上 复数 i ,得到的复数是 原复数逆时针旋转90度的结果
r = complex(dx,dy)*complex(0,1)
#得到旋转之后的结果
block.rect.centerx = cx + r.real + Config.blockWidth
block.rect.centery = cy + r.imag
#取得包络矩形的原始水平位置
lastRectX = self.rect.x
#更新组合对象的包络矩形
self.rect = self.boundingRect()
dx = lastRectX - self.rect.x
#使得旋转后的组合对象的水平位置保持不变
self.rect.x += dx
for block in self.sprites():
block.rect.x+=dx
# 表示底部方块的组合
class BottomGroup(pygame.sprite.Group):
def __init__(self):
pygame.sprite.Group.__init__(self)
#预设一行方块,放置在窗口下边界之下,不会显示,但可用于让下落的方块停下来
n = int(Config.screenWidth/Config.blockWidth)
for i in range(n):
#测试时故意让预设的一行方块向上移动一行,这样就可显示出来,可以看到碰撞检测的效果
y= Config.screenHeight-Config.blockWidth
#y= Config.screenHeight
x= i*Config.blockWidth
self.add(Block(x,y))
#检查下落的方块是否与底部方块发生了碰撞
def collided(self,fallingGroup):
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
#吃掉下落的方块
def eat(self,fallingGroup):
for d in fallingGroup.sprites():
self.add(d)
fallingGroup.empty()
main.py
# main.py
# 俄罗斯方块,主程序所在文件
import pygame
from block import *
import random
#随机生成下落方块组合
def randomFallingGroup():
return FallingGroup(typeIdx=random.randint(0,8),bottomGroup=bottomGroup)
# 重绘显示区域,形成动画效果
def animate():
global fallingGroup,bottomGroup
#设置屏幕为黑色
screen.fill(BLACK)
#下落方块组合执行下落方法
fallingGroup.down(speed)
#如果下落的方块组合已经被底部方块组合“吃”掉了,则生成新的下落方块组合
if len(fallingGroup.sprites())<=0:
fallingGroup = randomFallingGroup()
#下落方块组合执行绘制方法
fallingGroup.draw(screen)
#底部方块组合执行绘制方法
bottomGroup.draw(screen)
#刷新屏幕
pygame.display.flip()
# ------------------------main---------------------------------------------------------------------
# 初始化各种对象
pygame.init()
#游戏窗口的屏幕
screen = pygame.display.set_mode([Config.screenWidth,Config.screenHeight])
#用黑色填充背景
screen.fill(BLACK)
#设置图形窗口标题
pygame.display.set_caption("俄罗斯方块")
#游戏时钟
clock = pygame.time.Clock()
#生成底部方块组合对象
bottomGroup =BottomGroup()
#随机生成一个下落方块组合对象
fallingGroup = randomFallingGroup()
# 事件处理循环
running = True
while running:
#设定每秒帧数,为了实现俄罗斯方块一格一格的下落效果,将帧率设得很低,相应的下降速度(每秒位移量)等于方块的边长
clock.tick(2)
for event in pygame.event.get():
if event.type == pygame.QUIT: running = False
if event.type == pygame.KEYDOWN: # 如果按下了键盘上的键
if event.key == pygame.K_LEFT: # 如果按下了向左的方向键
fallingGroup.move(-1*speed)
elif event.key == pygame.K_RIGHT: #如果按下了向右的方向键
fallingGroup.move(speed)
elif event.key == pygame.K_UP: #如果按下了向上的方向键
fallingGroup.rotate()
animate()
pygame.quit() #退出pygame