珠宝游戏

主题: 珠宝游戏

文件建立日期: 2020/04/17

最后修订日期: 2020/05/08 (Revised for Tool.py PEP8)

相关软件信息:

Win 10 Python 3.7.6 numpy 1.18.2 PySimpleGUI 4.18.2 PIL/Pillow 7.1.1

个人庫 Tool

说明:本文请随意引用或更改,只须标示出处及作者,作者不保证内容絶对正确无误,如造成任何后果,请自行负责.

说明

抽了点时间写了珠宝游戏, 没啥好说的.

游戏定义

  • 目的: 水平线或垂直线上连续至少三个一样的珠宝.
  • 控制: 鼠标拖弋以交换珠宝
  • 限制: 只限上下左右一格, 交换后不能造成连续至少三个一水平线或垂直线, 则返回
  • 连线后删除, 上方珠宝下落补满
  • 不作提示

游乐画面

珠宝游戏

代码及说明

  • 库的导入
import numpy as np
import PySimpleGUI as sg
import random
import math
import winsound
from time import sleep
from io import BytesIO
from pathlib import Path
from PIL import Image
from Tool import read_URL, read_file, save_file
  • 建立类, 主要是方便参数的储存及调用
    所有常数或常用数据都定义在这里
    • self.url1/self.urls 珠宝图片及背景图片来源
    • self.file1/self.file2 珠宝图片及背景图片下载后的存档, 下次就不用再下载
    • self.x/self.y 每个珠宝格子的座标
    • self.width/self.height 视窗大小
    • self.columns 水平方向的珠宝数
    • self.rows 垂直方向的珠宝数
    • self.sound 声音的频率表
    • self.data 不同颜色珠宝的图片
    • self.kind/self.figure 每一格珠宝的类别及其图片元件的 ID
class Bejewel():

    def __init__(self):

        self.title = 'Bejeweled Game'
        self.url1 = 'http://pngimg.com/uploads/diamond/diamond_PNG6698.png'
        self.file1 = 'Jewelry.png'
        self.url2 = ('http://b.zol-img.com.cn/sjbizhi/images/4/320x510/'
                     '1366614187468.jpg')
        self.file2 = 'background.png'
        self.font = ('Courier New', 16, 'bold')
        self.w = self.h = 80
        self.columns = 16
        self.rows = 9
        self.kinds = 6  # maximum 7 now        
        self.gap = 10
        self.width = self.columns * (self.w + self.gap) + self.gap
        self.height = self.rows * (self.h + self.gap) + self.gap
        self.steps = 5
        self.d = [(self.h+self.gap)//self.steps for i in range(self.steps-1)]
        self.dx = self.d + [self.w+self.gap-sum(self.d)]
        self.dy = self.d + [self.h+self.gap-sum(self.d)]
        self.background = 'darkgreen'
        self.x = [i*(self.w+self.gap)+self.gap for i in range(self.columns)]
        self.y = [self.height*2-i*(self.w+self.gap)-2*self.gap
                    for i in range(self.rows*2)]
        self.sound = [int(220*(1+(2**(1/12))**i)) for i in range(12)]
        self.data = []
        self.score = 0
        self.figure = None
        self.kind = None
  • 建立新的游戏内容
    • 珠宝类别由乱生成
    • 其他相关设定
    def Add_All(self):
        if self.figure is not None and self.kind is not None:
            cells = [[x, y] for x in range(self.columns)
                for y in range(self.rows*2) if self.figure[y, x]!=0]
            self.Delete(cells)
        self.score = 0
        self.Score(0)
        self.figure = np.full((self.rows*2, self.columns), 0)
        self.kind = np.random.randint(0, self.kinds-1, size = (
            self.rows*2, self.columns), dtype=np.int8)
        self.exist = np.full((self.rows*2, self.columns), 0, dtype=np.int8)
        for y in range(self.rows*2):
            for x in range(self.columns):
                self.Add_New(self.kind[y, x], x, y)
  • 画出单一珠宝
    def Add_New(self, value, x, y):
        self.figure[y, x] = draw.DrawImage(
            data=self.data[value], location=(self.x[x], self.y[y]))
  • 阵列转换成可使用的图片数据
    def Array_To_Data(self, array):
        image = Image.fromarray(array, mode='RGBA')
        with BytesIO() as output:
            image.save(output, format="PNG")
            data = output.getvalue()
        return data
  • 检查一条线中连线的珠宝位置
    def Check_Line(self, line, var, direction):
        result, value, count, tmp = [], None, 0, []
        for index, kind in enumerate(line):
            if kind != value:
                if count >= 3:
                    result += tmp
                tmp, value, count = [index], kind, 1
            else:
                count += 1
                tmp.append(index)
        if count >= 3:
            result += tmp
        if direction == 'x':
            result = [[var, y+self.rows] for y in result]
        else:
            result = [[x, var+self.rows] for x in result]
        return result
  • 检查连线的珠宝, 闪烁珠宝后删除, 并计分
    def Check_Lines(self):
        result = []
        for x in range(self.columns):
            result += self.Check_Line(self.kind[self.rows:, x], x, 'x')
        for y in range(self.rows):
            result += self.Check_Line(self.kind[y+self.rows, :], y, 'y')
        if result == []:
            return False
        cells = []
        for item in result:
            if item not in cells:
                cells.append(item)
        self.Flash(cells)
        self.Delete(cells)
        self.Score(len(cells))
        return True
  • 删除列表中的珠宝, 按列表建立标签, 再一次删除标签所对应的珠宝
    def Delete(self, cells):
        if cells == []:
            return
        for item in cells:
            x, y = item
            draw.Widget.addtag_withtag('Delete', self.figure[y, x])
        draw.Widget.delete('Delete')
        draw.Widget.dtag('Delete', 'Delete')
        for x, y in cells:
            self.kind[y, x], self.figure[y, x] = -1, 0
        window.Refresh()
  • 闪烁珠宝, 以左右移动的方式来表达

    def Flash(self, target):
        dxes = [-2] + [4, -4]*3 + [2]
        for x, y in target:
            draw.Widget.addtag_withtag('Remove', self.figure[y, x])
        for dx in dxes:
            draw.Widget.move('Remove', dx, 0)
            window.Refresh()
            sleep(0.05)
  • 下载背景图, 存档并显示
    def Load_background(self):
        if not Path(self.file2).is_file():
            response, data = read_URL(self.url2, byte=True)
            if data:
                with open(self.file2, 'wb') as f:
                    f.write(data)
            else:
                signal(f'Cannot get {filename} from web !')
                quit()
        im = read_file(self.file2)
        im = im.convert(mode='RGBA').resize((self.width, self.height))
        a = np.array(im, dtype=np.uint8)
        data = self.Array_To_Data(a)
        self.picture = draw.DrawImage(data=data, location=(0, self.height))
  • 下载单一珠宝图片, 改变成不同颜色图片, 以供使用
    def Load_Icon(self):
        if not Path(self.file1).is_file():
            response, data = read_URL(self.url1, byte=True)
            if data:
                with open(self.file1, 'wb') as f:
                    f.write(data)
            else:
                signal(f'Cannot get {elf.file1} from web !')
                quit()
        im = read_file(self.file1).resize((self.w, self.h))
        a = np.array(im, dtype=np.uint8)
        arrays = []
        for i, j, k in [[0, 1, 2], [0, 0, 0], [2, 2, 2], [2, 0, 2], [0, 2, 0],
                        [2, 2, 1], [1, 1, 0]]:
            t = a.copy()
            t[:,:,0], t[:,:,1], t[:,:,2] = a[:,:,i], a[:,:,j], a[:,: ,k]
            arrays.append(t)
        self.data = [self.Array_To_Data(array) for array in arrays]
  • 珠宝下移后, 最上方的空位补上新的珠宝
    def Move_Add(self, line):
        for x in line:
            value = random.randint(0, self.kinds-1)
            self.kind[0, x] = value
            self.Add_New(value, x, 0)
  • 由下往上检查空位, 其上方所有的所有珠宝全部下落一格, 再重复检查, 直到没有空位
    def Move_All_Down(self):
        while True:
            target_down, line_down = [], []
            for x in range(self.columns):
                for y in range(2*self.rows-1, self.rows-1, -1):
                    if self.kind[y, x] == -1:
                        target_down += [[x, i] for i in range(y-1, -1, -1)]
                        line_down.append(x)
                        break
            if target_down == []:
                return
            self.Move_Down(target_down)
            self.Move_Add(line_down)
  • 按列表建立标签, 标签中的珠宝一次全部下移一格, 事实上, 下移一格, 还分五个动作, 每次只移五分之一.
    def Move_Down(self, target):
        for x, y in target:
            draw.Widget.addtag_withtag('Move Down', self.figure[y, x])
            self.kind[y+1, x] = self.kind[y, x]
            self.figure[y+1, x] = self.figure[y, x]
        for dy in self.dy:
            draw.Widget.move('Move Down', 0, dy)
            window.Refresh()
            # sleep(0.01)
        draw.Widget.dtag('Move Down', 'Move Down')
  • 将鼠标拖弋起点的座标, 转换成格子位置
    def Position_to_cell(self, x, y):
        x0, offset_x = divmod(x - self.gap, self.w + self.gap)
        y0, offset_y = divmod(self.height*2 - y - self.gap, self.h + self.gap)
        if ((x <= self.gap) or (self.height-y <= self.gap) or
            (offset_x > self.w) or (offset_y > self.h)):
                return None, None
        return x0, y0
  • 游戏分数的计算更新, 计算方式以可消去格子总数除以3的平方, 再乘以3
    def Score(self, score):
        self.score += int(score**2//3)
        self.score = min(self.score, 999999)
        window.FindElement('Score').Update(
            value='Score {:0>6d}'.format(self.score))
  • 交换两个珠宝的位置, 交换时分五个仆置变换来实现连续变化
    def Switch(self, x1, y1, x2, y2):
        for i in range(self.steps):
            dx = -self.dx[i] if x1 > x2 else self.dx[i] if x1 < x2 else 0
            dy = self.dy[i] if y1 > y2 else -self.dy[i] if y1 < y2 else 0
            draw.MoveFigure(self.figure[y1, x1], dx, dy)
            draw.MoveFigure(self.figure[y2, x2], -dx, -dy)
            window.Refresh()
            sleep(0.05)
        self.kind[y1, x1], self.kind[y2, x2] = (
            self.kind[y2, x2], self.kind[y1, x1])
        self.figure[y1, x1], self.figure[y2, x2] = (self.figure[y2, x2],
            self.figure[y1, x1])
  • 交换两个珠宝位置, 如果不能连线, 将退回原位置
    def Switch_Icon(self, here, there):
        x1, y1 = here
        x2, y2 = there
        x0, y0 = self.Position_to_cell(x1, y1)
        if x0 == None:
            return False
        if abs(x2-x1) > abs(y2-y1):
            x, y = (x0+1, y0) if x2>x1 else (x0-1, y0)
        else:
            x, y = (x0, y0-1) if y2>y1 else (x0, y0+1)
        if not (0<=x0<self.columns and 0<=x<self.columns and
                self.rows<=y0<self.rows*2 and self.rows<=y<self.rows*2):
            return False
        self.Switch(x0, y0, x, y)
        if not self.Update():
            self.Switch(x, y, x0, y0)
  • 更新连线的处理, 有连线则上方珠宝下落, 再重复处理, 直到没有连线, 声音会随着次数的增加, 频率上调
    def Update(self):
        flag = False
        sound = -1
        while self.Check_Lines():
            flag = True
            sound = sound + 1 if sound <11 else 11
            winsound.Beep(self.sound[sound], 100)
            self.Move_All_Down()
        return flag
  • GUI 的按钮建立
    def Button(self, key):
        return sg.Button(key, font=self.font, size = (10, 1),
            enable_events=True, key=key)
  • GUI 的画布建立
    def Graph(self):
        return sg.Graph((self.width+1, self.height+1), (-1, -1),
            (self.width, self.height), background_color=self.background,
            drag_submits=True, enable_events=True, key='Graph')
  • GUI 的文字建立
    def Text(self):
        return sg.Text('Score 000000', font=self.font, size = (29, 1),
                       text_color = 'yellow', justification='left',
                       auto_size_text=False, key='Score')
  • 讯息处理, 有讯息会跳出框, 随后关闭窗口, 结束游戏
def signal(string):
    sg.popup(string)
    window.close()
    exit()
  • 珠宝类的建立, 视窗实体化, 载入背景及所有珠宝的图片资料
B = Bejewel()
layout = [[B.Text(), B.Button('New Game'), B.Button('Quit')], [B.Graph()]]
window = sg.Window(B.title, layout=layout, finalize=True)
draw = window.find_element('Graph')
B.Load_background()
B.Load_Icon()
  • 事件的处理, 主要是四件事
    • 游戏结束
    • 游戏开始
    • 鼠标的拖弋处理
    • 珠宝交换的处理
mouse_down = False
drag = False
old_position = None
start = False
while True:

    event, values = window.read()

    if event in [None, 'Quit']:
        break

    elif event == 'New Game':
        B.Add_All()
        B.Update()

    elif event == 'Graph':
        new_position = values['Graph']
        if (not drag) and (not mouse_down):
            old_position = new_position
            mouse_down = True
        elif (not drag) and mouse_down:
            if new_position != old_position:
                drag = True
    elif event == 'Graph+UP':
        if drag:
            B.Switch_Icon(old_position, new_position)
        mouse_down = False
        drag = False
        old_position = None

window.close()
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang
讨论数量: 1
张雷

非常不错,支持!

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!