pygame做一个成语填空游戏

709 阅读5分钟

最近看到很多人玩成语填字游戏,那么先用pygame来做一个吧,花了大半天终于完成了,附下效果图。 在这里插入图片描述 主要两个py文件

封装的一些操作类idiom_lib.py

# -*- coding=utf-8 -*-
import sys
import random

if sys.version_info < (3,0):
    reload(sys)
    sys.setdefaultencoding('utf-8')
elif sys.version_info <= (3,3):
    import imp
    imp.reload(sys)
else:
    import importlib
    importlib.reload(sys)

# 用于存放一条诗句的类
class IdiomInfo(object):
    def __init__(self,idiom):
        self.idiom = idiom # 诗句文本
        self.dire = 0 # 诗句方向 0 表示横向 1 表示纵向
        self.word_arr = [] # 诗句中每个汉字的WordInfo对象列表

    def to_str(self):
        arr = []
        for word_info in self.word_arr:
            arr.append('%s %s %s'%(word_info.i,word_info.j,word_info.word))
        return '%s,%s,%s'%(self.idiom, self.dire, '|'.join(arr))

# 用于存放单个文字的类
class WordInfo(object):
    def __init__(self, word, i, j):
        self.i = i # 文字所在的x坐标
        self.j = j # 文字所在的y坐标
        self.word = word # 文字内容
        self.is_lock = True # 文字是否需要填词
        self.state = -1
        self.hide_index = -1
        self.op_hide_index = -1

# 对应中间区域的格子数量构建的矩阵类,便于中间的文字管理
class Matrix(object):
    rows = 0 # 行数
    cols = 0 # 列数
    data = [] # 所有文字

    def __init__(self, rows, cols, data=None):
        self.rows = rows
        self.cols = cols
        if data is None: data = [None for i in range(rows * cols)]
        self.data = data

    # 设置文字
    def set_val(self, x, y, val):
        self.data[y * self.cols + x] = val

    # 获取文字
    def get_val(self, x, y):
        return self.data[y * self.cols + x]

    def exist_val_four_around(self, x, y, ignore_set):
        '''
        判断文字的四周是否有其他文字
        :param x: 列号
        :param y: 行号
        :param ignore_set: 需要忽略的格子
        :return:
        '''
        move_arr = [(-1,0),(1,0),(0,-1),(0,1)] # 左、右、上、下,四个方向

        for dx,dy in move_arr:
            tx = x + dx
            ty = y + dy
            if (tx,ty) in ignore_set: continue
            if tx < 0 or tx >= self.cols or ty <0 or ty >= self.rows: continue
            if self.data[ty * self.cols + tx]: return True
        return False

# 诗句操作库
class IdiomLib():
    def __init__(self, block_num=12):
        self.word_dic={} # 一个字有哪些诗句,格式为: {字:[诗句,诗句],……}
        self.word_arr=[] # 所有文字列表
        self.block_num=block_num # 横向和纵向上的格子数量
        self.matrix = Matrix(self.block_num, self.block_num) # 中间区域的文字矩阵
        self.idiom_dic={} # 当前关卡的诗句列表,格式为:{诗句:诗句对象IdiomInfo}
        self.all_word_num=0 # 当前关卡的文字数量
        self.hide_arr = [] # 当前关卡用于保存中间区域的缺字格子,列表类型:[[x,y,文字,None],……]

    # 从文件中加载诗句
    def load_idiom_from_file(self, filename='words.txt'):
        if sys.version_info < (3,0): 
            f = open(filename)
        else:
            f = open(filename,encoding='UTF-8')
        all_idiom = f.readlines()
        f.close()

        for idiom in all_idiom:
            if sys.version_info < (3,0):
                idiom = idiom.strip().decode('utf-8')
            else:
                idiom = idiom.strip()
            for word in idiom:
                if word not in self.word_dic: 
                    self.word_dic[word] = [idiom]
                else:
                    self.word_dic[word].append(idiom)

        self.word_arr = list(self.word_dic.keys())

    def check_new_idiom(self, new_idiom, new_dire, word_info):
        '''
        检查新新诗句是否有效,无效的场景 1.文字排开后发现超出边界 2.文字排开后发现和已选诗句的文字重叠
        :param new_idiom: 诗句
        :param new_dire: 诗句展开方向
        :param word_info: 当前的文字对象
        :return:
        '''
        windex = new_idiom.index(word_info.word)
        cx,cy = word_info.i, word_info.j
        ignore_set = set([(cx,cy)])

        new_idiom_word_arr=[]
        for i in range(-windex,-windex+len(new_idiom)): 
            if i==0: 
                new_idiom_word_arr.append(word_info)
            else:
                tx = cx+i  if new_dire == 0 else  cx
                # 横向超出边界
                if tx < 0 or tx >= self.block_num: return None,None

                ty = cy if new_dire == 0 else cy+i
                # 纵向超出边界
                if ty < 0 or ty >= self.block_num: return None,None

                # 抛掉第一个字外其他每一个字所在位置四周是否有文字
                if self.matrix.exist_val_four_around(tx, ty, ignore_set): return None,None

                old_word_info = self.matrix.get_val(tx, ty)
                if old_word_info:
                    return None,None

                new_word_info = WordInfo(new_idiom[i+windex], tx, ty)
                new_idiom_word_arr.append(new_word_info)

        return new_idiom_word_arr,windex

    def add_idiom_to_matrix(self, idiom_num):
        if idiom_num == 0: return 0
        for idiom,idiom_info in self.idiom_dic.items(): # 遍历已选定的诗句
            dire = idiom_info.dire
            new_dire = 1 - dire # 诗句横向纵向交替,因为横向=0,纵向=1,1-横向=纵向,1-纵向=横向
            for word_info in idiom_info.word_arr: # 遍历已选定的诗句中每个字
                word = word_info.word
                idiom_list = self.word_dic[word]
                for new_idiom in idiom_list: # 遍历这个字组成的诗句
                    if new_idiom in self.idiom_dic: continue # 如果诗句已经选定,跳过
                    new_idiom_word_arr,windex = self.check_new_idiom(new_idiom, new_dire, word_info) # 检查诗句有效性
                    if new_idiom_word_arr:
                        new_idiom_info = IdiomInfo(new_idiom)
                        new_idiom_info.dire = new_dire
                        # 对诗句的每个字在文字矩阵里放置
                        for new_index in range(len(new_idiom_word_arr)):
                            new_word_info = new_idiom_word_arr[new_index]
                            if new_index == windex:
                                new_idiom_info.word_arr.append(word_info)
                            else:
                                self.matrix.set_val(new_word_info.i, new_word_info.j , new_word_info)
                                new_idiom_info.word_arr.append(new_word_info)
                        self.idiom_dic[new_idiom] = new_idiom_info

                        # 继续增加下一个诗句
                        return len(new_idiom) -1 + self.add_idiom_to_matrix(idiom_num - 1)

        return 0

    def get_idiom_matrix(self, idiom_num):
        self.idiom_dic={}
        cx = int(self.block_num/2)-1
        cy = int(self.block_num/2)-1

        # 随机取一个字
        n = random.randint(0,len(self.word_arr)-1)
        word = self.word_arr[n]
        # 在这个字组成的诗句列表里取第一个诗句
        idiom = self.word_dic[word][0]
        wn = len(idiom)

        # 第一个诗句存到字典里
        self.idiom_dic[idiom] = IdiomInfo(idiom)

        # 对诗句的每个字在文字矩阵里放置
        for i in range(len(idiom)):
            word_info = WordInfo(idiom[i],cx-int(wn/2)+1+i,cy)
            self.matrix.set_val(cx-int(wn/2)+1+i,cy,word_info)
            self.idiom_dic[idiom].word_arr.append(word_info)

        # 添加下一个诗句
        wn += self.add_idiom_to_matrix(idiom_num-1)
        return wn

    def get_hide_arr(self, percent):
        self.hide_arr=[] # 用于保存中间区域的缺字格子,列表类型:[[x,y,文字,None],……]
        idiom_word_arr = [] # 列表类型:[[诗句,[文字对象,文字对象,……]],……]

        for k,v in self.idiom_dic.items():
            arr = []
            for word_info in v.word_arr:
                arr.append(word_info)
            idiom_word_arr.append([k, arr])
        # 按诗句的文字数量由多到少排序
        idiom_word_arr.sort(key=lambda x:-len(x[-1]))

        idiom_index = 0
        while len(self.hide_arr) < self.all_word_num*percent:
            tmp_arr = idiom_word_arr[idiom_index%len(idiom_word_arr)][1] # 取得一个诗句的一组文字
            n = random.randint(0,len(tmp_arr)-1) # 一组文字中随机取一个位置
            info = tmp_arr.pop(n) # 移除文字
            word=info.word
            info.word = '' # 格子上文字内容置空
            info.hide_index = len(self.hide_arr) # 记录下在文字隐藏列表中的位置索引
            info.is_lock = False # 格子上文字可点击
            self.hide_arr.append([info.i,info.j,word,None]) # 将文字加到隐藏列表
            idiom_index+=1 # 转到下一个诗句

        return self.hide_arr  

    def get_next_select(self, x, y):
        '''
        根据指定位置选取下一个默认选中格子
        :param x:
        :param y:
        :return:
        '''
        arr = []
        for i in range(self.block_num):
            for j in range(self.block_num):
                info = self.matrix.get_val(i, j)
                if info is not None and len(info.word) == 0:
                    dist = (i-x)*(i-x)+(j-y)*(j-y)
                    if i<x: dist+=0.2 # 格子在左,增加0.2距离,排序时会靠后
                    if j<y: dist+=0.4 # 格子在上,增加0.4距离,排序时会靠后
                    arr.append((i,j,dist))
        if len(arr) == 0:
            return None
        # 按所有可选格子的距离正序排序
        arr.sort(key=lambda x:x[-1])
        return (arr[0][0],arr[0][1])

    def check_idiom(self):
        for idiom, idiom_info in self.idiom_dic.items():
            tmp_idiom_str = ''
            word_arr = idiom_info.word_arr
            for word_info in word_arr:
                word = word_info.word
                if len(word) > 0:
                    tmp_idiom_str+=word
            if len(tmp_idiom_str) == len(idiom):
                state = 1 if tmp_idiom_str == idiom else 2
            else:
                state = 0

            for word_info in word_arr:
                if word_info.state != 1: word_info.state = state

        for idiom, idiom_info in self.idiom_dic.items():
            word_arr = idiom_info.word_arr
            for word_info in word_arr:
                if word_info.state != 1:
                    return False
        return True

    stage = 1

    def init(self, new_stage):
        idiom_num = int(new_stage/5)+3
        if new_stage>100:
            percent = 0.7 # 100关以后隐藏文字的比例不再改变
        else:
            percent = 0.2+(new_stage*1.0/100)*(0.7-0.2)
        self.matrix = Matrix(self.block_num, self.block_num)
        # 生成一组诗句
        self.all_word_num = self.get_idiom_matrix(idiom_num)
        # 诗句中按比例提取一些隐藏字
        self.get_hide_arr(percent)
        # 默认选择中间区域第一个缺字的格子
        self.select_rect = self.hide_arr[0][0],self.hide_arr[0][1]

if __name__ == '__main__':
	pass
    # lib = IdiomLib(block_num=10)
    # lib.load_idiom_from_file()

    # arr = []
    # for i in range(1,101):
    #     lib.init(i)
    #     idiom_arr = []
    #     for k,v in lib.idiom_dic.items():
    #         idiom_arr.append(v.to_str())
    #     hide_arr = []
    #     for x,y,word,op in lib.hide_arr:
    #         hide_arr.append('%s %s %s'%(x,y,word))
    #     arr.append({'hide_num':len(hide_arr),'block_num':lib.block_num, 'word_num':lib.all_word_num,'idiom_arr':';'.join(idiom_arr),'hide_arr':';'.join(hide_arr)})
    # #arr.sort(cmp=lambda x,y:cmp(x['hide_num']*2+x['word_num'], y['hide_num']*2+y['word_num']))
    # arr.sort(key=lambda x:x['hide_num']*2+x['word_num'])

    # import json
    # f = open('idiom.json','w+') 
    # f.write(json.dumps(arr))
    # f.close()

主程序main.py

# -*- coding=utf-8 -*-
import sys
import random
import pygame
from pygame.locals import *
from idiom_lib import IdiomLib

if sys.version_info < (3,0):
    reload(sys)
    sys.setdefaultencoding('utf-8')
elif sys.version_info <= (3,3):
    import imp
    imp.reload(sys)
else:
    import importlib
    importlib.reload(sys)

# 横向和纵向上的文字数量
block_num=12
lib = IdiomLib(block_num=block_num)
lib.load_idiom_from_file()

# 顶部空出的像素(顶部空出的总高度是header_height+main_space)
header_height = 30
# 中间区域四周空出的像素
main_space = 20

# 每个文字的高度和宽度的像素值
block_size = 36
# 每个米字格往里缩多少像素
bspace = 2
space = 20
# 总宽度=文字像素*文字数量+四周边距*2
width = block_size * block_num + main_space * 2
# 总高度=顶部高度+文字像素*文字数量+四周边距*2+底部待选区高度(即:文字像素*3+待选区两边留白space*3)
height = header_height + block_size * block_num + main_space * 2 + block_size * 3 + space * 3

pygame.init()
screen = pygame.display.set_mode((width,height))
screencaption = pygame.display.set_caption(u'诗词填空')

font = pygame.font.Font(u'syht.otf', int(block_size*0.8))

dray_blue = 50,50,200
white = 255,255,255
#textImage = font.render(u'你好', True, white)

# 加载和拉伸背景图片
bg_image = pygame.image.load('bg.jpeg')
bg_image = pygame.transform.scale(bg_image,(width, height))

# 加载和拉伸中间区域的图片
bg2_image = pygame.image.load('bg2.jpeg')
bg2_image = pygame.transform.scale(bg2_image,(block_size*block_num,block_size*block_num))

# 加载和拉伸每个汉字的米字格背景图片
block_bg_image = pygame.image.load('tzg.jpg')
block_bg_image = pygame.transform.scale(block_bg_image,(block_size-bspace*2,block_size-bspace*2))


stage = 1
lib.init(stage) # 初始化第一关

# 获取文字宽度高度,以保证文字居中显示
stage_textImage = pygame.font.Font(u'syht.otf', 30).render(u'第%s关'%stage, True, dray_blue)
stage_font_width, stage_font_height = stage_textImage.get_size()
stage_x = int((width - stage_font_width)/2)
stage_y = int((header_height - stage_font_height)/2)+int(main_space/2)

while True:
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
		   	pygame.quit()
		   	exit()

		if event.type == MOUSEBUTTONDOWN:
			pressed_array = pygame.mouse.get_pressed()
			if pressed_array[0]:
				x, y = pygame.mouse.get_pos()

				# 计算选中的格子索引
				xi = (x - main_space) // block_size
				yi = (y - header_height - main_space) // block_size
				if xi>=0 and xi<block_num and yi>=0 and yi<block_num:
					# 提取选中格子的对象
					info = lib.matrix.get_val(xi, yi)
					# info=None的话格子是空的,info.state==1是已完成的句子不能点回去,info.hide_index>=0表示是下方点上来的文字
					if info and info.state != 1 and info.hide_index >= 0:
						if info.op_hide_index >= 0:
							lib.hide_arr[info.op_hide_index][-1] = None
							info.word = ''
							info.op_hide_index = -1
							lib.check_idiom()
						lib.select_rect = xi, yi

				sx = main_space
				sy = header_height + main_space+ block_size*block_num +space
				n = 0 # 下方选字区的文字序号
				# 这个循环主要用于判断鼠标是否点在下方选字区域的文字上
				for hi in range(len(lib.hide_arr)): # 遍历隐藏字列表
					tmp_x = sx + (n%block_num)*block_size # 第n个字的x方向起始像素位置
					tmp_y = sy + int(n/block_num)*block_size # 第n个字的y方向起始像素位置
					if lib.hide_arr[hi][-1] is None and x >= tmp_x and x <= tmp_x+block_size-bspace*2 and y >= tmp_y and y<= tmp_y+block_size-bspace*2:
						# 去除选中的代填文字上的对象
						info = lib.matrix.get_val(lib.select_rect[0],lib.select_rect[1])
						# 给对象赋值
						info.word = lib.hide_arr[hi][2]
						info.op_hide_index = hi # 记录下来源待选区域的位置,便于退出文字
						info.state = 0 # 还未完成的状态
						lib.hide_arr[hi][-1] = lib.select_rect
						# 选下一个推荐填充位置
						lib.select_rect = lib.get_next_select(lib.select_rect[0],lib.select_rect[1])
						flag = lib.check_idiom()
						if flag:
							stage += 1
							lib.init(stage)
							stage_textImage = pygame.font.Font(u'syht.otf', 30).render(u'第%s关'%stage, True, dray_blue)
						break
					n += 1

	screen.blit(bg_image, (0,0))
	screen.blit(stage_textImage, (stage_x,stage_y))

	# panel对应中间区域的画板
	panel = screen.subsurface((main_space,header_height+main_space,block_size*block_num,block_size*block_num))
	panel.blit(bg2_image, (0,0))

	# 对中间位置的文字进行绘制
	for i in range(block_num):
		for j in range(block_num):
			info = lib.matrix.get_val(i,j)
			if info is not None:
				bx = block_size*i+bspace
				by = block_size*j+bspace
				panel.blit(block_bg_image, (bx,by))
				
				if info.state == 1:
					textImage = font.render(info.word, True, (30,144,30)) # 正确完成的诗句上的字绿色
				elif info.is_lock == 1:
					textImage = font.render(info.word, True, (100,100,100)) # 不可点击灰色
				elif info.state == 2:
					textImage = font.render(info.word, True, (255,0,0)) # 错误红色
				else:
					textImage = font.render(info.word, True, dray_blue) # 默认蓝色

				tw, th = textImage.get_size()
				dx=int((block_size-bspace*2-tw)/2)
				dy=int((block_size-bspace*2-th)/2)
				panel.blit(textImage, (bx+dx,by+dy))
				if (i,j) == lib.select_rect: # 推荐填充位置填红色边框
					pygame.draw.rect(panel,(255,0,0),(bx,by,block_size-bspace*2,block_size-bspace*2),2)

	# 对下方待选文字区域的的文字进行绘制
	sx = main_space
	sy = header_height + main_space+ block_size*block_num +space
	n = 0
	for i,j,word,op in lib.hide_arr:
		screen.blit(block_bg_image, (sx + (n%block_num)*block_size,sy + int(n/block_num)*block_size))
		if op is None:
			textImage = font.render(word, True, dray_blue)
			tw, th = textImage.get_size()
			dx=int((block_size-bspace*2-tw)/2)
			dy=int((block_size-bspace*2-th)/2)
			screen.blit(textImage, (dx+sx+ (n%block_num)*block_size,dy+sy+ int(n/block_num)*block_size))
		n+=1

	pygame.display.update()

代码就这么多了,不过这边用到几个额外的依赖: bg.jpeg 用于做整个界面的背景 bg2.jpeg 用于做上半部分的背景 tzg.jpg 每个文字的格子的背景 words.txt 一个成语的列表文件(每行一条成语),如果换成诗词或者歇后语什么的也是没有问题的 syht.otf 一个字体库,用于正常显示中文

如果嫌格子太多或者太小,可以调一下这两个参数

block_size = 32
block_num=12

block_size 表示格子的大小 block_num 上半部分的区域横竖最多显示多少个格子

block_size = 26,block_num=18的效果图: 在这里插入图片描述 block_size = 40,block_num=10的效果图: 在这里插入图片描述

完整的代码和资源已经上传: github:github.com/zhangenter/…