python小欢喜(八)俄罗斯方块 (8) 用PIL生成方块图像,不再从图片文件加载

前面的文章《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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值