Python 实现俄罗斯方块游戏

from random import *
from tkinter import *
from time import sleep
from tkinter import messagebox


class RussiaBlock(object):
    def __init__(self):
        # 方块颜色列表
        self.color = ['red', 'orange', 'yellow', 'purple', 'blue', 'green', 'pink']

        # 字典 存储形状对应7种形状 元组存储坐标
        self.shapeDict = {1: [(0, 0), (0, -1), (0, -2), (0, 1)],  # shape I
                          2: [(0, 0), (0, -1), (1, -1), (1, 0)],  # shape O
                          3: [(0, 0), (-1, 0), (0, -1), (1, 0)],  # shape T T型
                          4: [(0, 0), (0, -1), (1, 0), (2, 0)],  # shape J 右长倒L盖子
                          5: [(0, 0), (0, -1), (-1, 0), (-2, 0)],  # shape L
                          6: [(0, 0), (0, -1), (-1, -1), (1, 0)],  # shape Z
                          7: [(0, 0), (-1, 0), (0, -1), (1, -1)]}  # shape S
        # 旋转坐标控制
        self.rotateDict = {(0, 0): (0, 0), (0, 1): (-1, 0), (0, 2): (-2, 0), (0, -1): (1, 0),
                           (0, -2): (2, 0), (1, 0): (0, 1), (2, 0): (0, 2), (-1, 0): (0, -1),
                           (-2, 0): (0, -2), (1, 1): (-1, 1), (-1, 1): (-1, -1),
                           (-1, -1): (1, -1), (1, -1): (1, 1)}
        # 初始高度,宽度 核心块位置
        self.coreLocation = [4, -2]
        self.height, self.width = 20, 10
        self.size = 32
        # map_s can record the location of every square.i宽  j高
        self.map_s = {}
        # 全部置0
        for i in range(self.width):
            for j in range(-4, self.height):
                self.map_s[(i, j)] = 0
        # 添加边界
        for i in range(-4, self.width + 4):
            self.map_s[(i, self.height)] = 1
        for j in range(-4, self.height + 4):
            for i in range(-4, 0):
                self.map_s[(i, j)] = 1
        for j in range(-4, self.height + 4):
            for i in range(self.width, self.width + 4):
                self.map_s[(i, j)] = 1

        # 初始化分数0  默认不加快  按下时加快
        self.score = 0
        self.isFaster = False
        # 创建GUI界面
        self.root = Tk()
        self.root.title("RussiaBlock")
        self.root.geometry("500x645")
        self.area = Canvas(self.root, width=320, height=640, bg='white')
        self.area.grid(row=2)
        self.pauseBut = Button(self.root, text="Pause", height=2, width=13, font=18, command=self.is_pause)
        self.pauseBut.place(x=340, y=100)
        self.startBut = Button(self.root, text="Start", height=2, width=13, font=18, command=self.play)
        self.startBut.place(x=340, y=20)
        self.restartBut = Button(self.root, text="Restart", height=2, width=13, font=18, command=self.is_restart)
        self.restartBut.place(x=340, y=180)
        self.quitBut = Button(self.root, text="Quit", height=2, width=13, font=18, command=self.is_quit)
        self.quitBut.place(x=340, y=260)
        self.scoreLabel1 = Label(self.root, text="Score:", font=24)
        self.scoreLabel1.place(x=340, y=600)
        self.scoreLabel2 = Label(self.root, text="0", fg='red', font=24)
        self.scoreLabel2.place(x=410, y=600)
        # 按键交互
        self.area.bind("<Up>", self.rotate)
        self.area.bind("<Left>", self.move_left)
        self.area.bind("<Right>", self.move_right)
        self.area.bind("<Down>", self.move_faster)
        self.area.bind("<Key-w>", self.rotate)
        self.area.bind("<Key-a>", self.move_left)
        self.area.bind("<Key-d>", self.move_right)
        self.area.bind("<Key-s>", self.move_faster)
        self.area.focus_set()
        # 菜单
        self.menu = Menu(self.root)
        self.root.config(menu=self.menu)
        self.startMenu = Menu(self.menu)
        self.menu.add_cascade(label='Start', menu=self.startMenu)
        self.startMenu.add_command(label='New Game', command=self.is_restart)
        self.startMenu.add_separator()
        self.startMenu.add_command(label='Continue', command=self.play)
        self.exitMenu = Menu(self.menu)
        self.menu.add_cascade(label='Exit', command=self.is_quit)
        self.helpMenu = Menu(self.root)
        self.menu.add_cascade(label='Help', menu=self.helpMenu)
        self.helpMenu.add_command(label='How to play', command=self.rule)
        self.helpMenu.add_separator()
        self.helpMenu.add_command(label='About...', command=self.about)

    # 先将核心块的所在位置在map_s中的元素设为1,通过self.shapeDict获取其余方块位置,将map_s中对应元素设为1。
    def get_location(self):
        map_s[(core[0], core[1])] = 1
        for i in range(4):
            map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 1

    # 判断方块下移一格后对应位置map_s中的元素是否为一,是,则不可移动,返回False;否,可以移动,返回True。
    def can_move(self):
        for i in range(4):
            if map_s[(core[0] + getNew[i][0]), (core[1] + 1 + getNew[i][1])] == 1:
                return False
        return True

    # 先用randRange获取1~7中的随机整数,随机到某一整数,那么访问self.shapeDict,获取这种形状方块的核心块及其他方块的相对位置。
    # 访问颜色字典,获取此方块的颜色。建立循环,当方块可移动时(while self. can_move():),且暂停键未被摁下(if is_pause:),
    # 核心块纵坐标加一,根据核心块及其他方块对于核心块的相对位置,画出四个方块。用self.get_location()函数获取方块的位置。
    def draw_new(self):
        global next_s
        global getNew
        global core
        next_s = randrange(1, 8)
        # 形状
        getNew = self.shapeDict[next_s]
        core = [4, -2]
        time = 0.2
        while self.can_move():
            if is_pause:
                core[1] += 1
                self.draw_square()
                if self.isFaster:
                    sleep(time - 0.15)
                else:
                    sleep(time + 0.22)
                self.isFaster = False
            else:
                self.draw_square()
                sleep(time)
        self.get_location()

    # 绘制当前方块
    def draw_square(self):
        self.area.delete("new")
        for i in range(4):
            self.area.create_rectangle((core[0] + getNew[i][0]) * self.size,
                                       (core[1] + getNew[i][1]) * self.size,
                                       (core[0] + getNew[i][0] + 1) * self.size,
                                       (core[1] + getNew[i][1] + 1) * self.size,
                                       fill=self.color[next_s - 1], tags="new")
        self.area.update()

    # 给底部每行中方块都加上标签:bottom + str(j), j代表该块所在行数,每次遍历map_s,建立对于range(self. height)的for循环,删去每一行,
    # 若map_s什么地方的元素为1,画出这一位置的方块,不断更新。这样可以画出底部方块。
    def draw_bottom(self):
        for j in range(self.height):
            self.area.delete('bottom' + str(j))
            for i in range(self.width):
                if map_s[(i, j)] == 1:
                    self.area.create_rectangle(self.size * i, self.size * j, self.size * (i + 1),
                                               self.size * (j + 1), fill='grey', tags='bottom' + str(j))
            self.area.update()

    # 判断填满遍历map_s每一行的各个元素,若所有元素为1,则标签中score值+10,将
    # 此行所有元素改为0,行数map_s(i,j)=map_s(i-1,j)(即所有之上的行下移)
    # ,那么后续画底部方块时,可实现消行。
    def is_fill(self):
        for j in range(self.height):
            t = 0
            for i in range(self.width):
                if map_s[(i, j)] == 1:
                    t = t + 1
            if t == self.width:
                self.get_score()
                self.delete_line(j)

    # 加分
    def get_score(self):
        score_value = eval(self.scoreLabel2['text'])
        score_value += 10
        self.scoreLabel2.config(text=str(score_value))

    # 消行
    def delete_line(self, j):
        for t in range(j, 2, -1):
            for i in range(self.width):
                map_s[(i, t)] = map_s[(i, t - 1)]
        for i in range(self.width):
            map_s[(i, 0)] = 0
        self.draw_bottom()

    # 遍历每一行,若从顶部到底部map_s每一行都有某一个元素或更多元素为1,
    # 那么说明方块以顶到最上端,游戏结束。此处不可以简单判定最上一行是否有元素为1就判定结束,
    # 若这样会产生顶部有新的方块产生,然后导致顶部有元素为1,误判为游戏结束。
    def is_over(self):
        t = 0
        for j in range(self.height):
            for i in range(self.width):
                if self.map_s[(i, j)] == 1:
                    t += 1
                    break
        if t >= self.height:
            return False
        else:
            return True

    # 先判断方块是否可以旋转(针对其靠近边界时)。先将其现在所在位置对应map_s中的元素改为0,判断其旋
    # 转后位置对应map_s中的元素是否有一,若有,说明其旋转后的位置已经被占,是不能旋转的,返回值为False
    # 。否则为可旋转,返回值True。若已判定可以旋转,那么访问self.rotateDict,得出旋转以后所有小块的位置
    # 变换,将变换以后的位置对应map_s的元素设为1,旋转便已完成。
    def can_rotate(self):
        for i in range(4):
            map_s[((core[0] + getNew[i][0]),
                   (core[1] + getNew[i][1]))] = 0
        for i in range(4):
            if map_s[((core[0] + self.rotateDict[getNew[i]][0]),
                      (core[1] + self.rotateDict[getNew[i]][1]))] == 1:
                return False
        return True

    # 旋转
    def rotate(self, event):
        if next != 2:
            if self.can_rotate():
                for i in range(4):
                    getNew[i] = self.rotateDict[getNew[i]]
                self.draw_square()
        if not self.can_move():
            for i in range(4):
                map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 1

    # 先判断是否左移/右移,同样,将方块现在所处位置的map_s中元素设为0,看其移动后的位置上map_s的元素是否有1,
    # 若有,说明这一位置已被占据或已到边界,不可移动,返回False。若可移动,返回True。按下左键,若可
    # 以移动,核心块的横坐标减1,由于我们只讨论其他小块对于核心块的相对位置,所以其他小块的位置自动随
    # 核心块的位置移动而移动。将移动过后的位置对应map_s中的元素设为1。
    def can_left(self):
        core_now = core
        for i in range(4):
            map_s[((core_now[0] + getNew[i][0]), (core_now[1] + getNew[i][1]))] = 0
        for i in range(4):
            if map_s[((core_now[0] + getNew[i][0] - 1), (core_now[1] + getNew[i][1]))] == 1:
                return False
        return True

    # 左移
    def move_left(self, event):
        if self.can_left():
            core[0] -= 1
            self.draw_square()
            self.draw_bottom()
        if not self.can_move():
            for i in range(4):
                map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 1

    # 判断右移
    def can_right(self):
        for i in range(4):
            map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 0
        for i in range(4):
            if map_s[((core[0] + getNew[i][0] + 1), (core[1] + getNew[i][1]))] == 1:
                return False
        return True

    # 右移
    def move_right(self, event):
        if self.can_right():
            core[0] += 1
            self.draw_square()
            self.draw_bottom()
        if not self.can_move():
            for i in range(4):
                map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 1

    # 初始化中有一self. isFaster 的变量被设为False,当其为False时,
    # 程序中的sleep(time)中time的值为0.35,而按下下键,self. isFaster变为True,
    # time变成0.05,通过调整sleep()中变量的大小可以调节方块运动的速度。
    # 此功能通过if语句实现。
    def move_faster(self, event):
        self.isFaster = True
        if not self.can_move():
            for i in range(4):
                map_s[((core[0] + getNew[i][0]), (core[1] + getNew[i][1]))] = 1

    # run the program
    def run(self):
        self.is_fill()
        self.draw_new()
        self.draw_bottom()

    # play the game
    def play(self):
        self.startBut.config(state=DISABLED)
        global is_pause
        is_pause = True
        global map_s
        map_s = self.map_s
        while True:
            if self.is_over():
                self.run()
            else:
                break
        self.over()

    # restart the game
    def restart(self):
        self.map_s = {}
        for i in range(self.width):
            for j in range(-4, self.height):
                self.map_s[(i, j)] = 0
        for i in range(-1, self.width):
            self.map_s[(i, self.height)] = 1
        for j in range(-4, self.height + 1):
            self.map_s[(-1, j)] = 1
            self.map_s[(self.width, j)] = 1
        self.score = 0
        for j in range(self.height):
            self.area.delete('bottom' + str(j))
        self.play()

    # 结束后告诉用户失败
    def over(self):
        feedback = messagebox.askquestion("You Lose!", "You Lose!\nDo you want to restart?")
        if feedback == 'yes':
            self.restart()
        else:
            self.root.destroy()

    # 退出
    def is_quit(self):
        ask_quit = messagebox.askquestion("Quit", "Are you sure to quit?")
        if ask_quit == 'yes':
            self.root.destroy()
            exit()

    # 判断是否按下restart
    def is_restart(self):
        ask_restart = messagebox.askquestion("Restart", "Are you sure to restart?")
        if ask_restart == 'yes':
            self.restart()
        else:
            return

    # 每次一按下暂停键,is_pause = not is_pause,当is_pause = True时,由于之前提到过的if is_pause:语句,
    # 方块可以移动,游戏运行。当按下暂停键以后,is_pause值为False,方块将不可移动。同时,is_pause值为False时
    # ,暂停键变为开始键,即标签由Pause 改为 Resume,当is_pause值为True时,Resume改为Pause。这一功能由if语句实现。
    def is_pause(self):
        global is_pause
        is_pause = not is_pause
        if not is_pause:
            self.pauseBut["text"] = "Resume"
        else:
            self.pauseBut["text"] = "Pause"

    # 帮助
    def rule(self):
        rule_top = Toplevel()
        rule_top.title('Help')
        rule_top.geometry('800x400')
        rule = "Start: Press the start button or choose the option 'Continue' to start the game."
        rule_label = Label(rule_top, text=rule, fg='blue', font=18)
        rule_label.place(x=50, y=50)

    # 显示有关信息
    def about(self):
        about_top = Toplevel()
        about_top.title('About')
        about_top.geometry('300x150')
        about = "RussiaBlock.py\n\By Programmer Lee\n\All Rights Reserved."
        about_label = Label(about_top, font=('Curier', 20), fg='darkblue', text=about)
        about_label.pack()

    def mainloop(self):
        self.root.mainloop()


def main():
    russia_block = RussiaBlock()
    russia_block.mainloop()


main()

运行效果:

 

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值