【问题标题】:Minesweeper in tkinter; why does this happen?tkinter 中的扫雷;为什么会这样?
【发布时间】:2018-10-25 20:21:03
【问题描述】:

我正在尝试使用 tkinter 中的按钮制作扫雷,这是我第一次使用 tkinter。我唯一的问题是我不知道如何创建对不同键做出不同反应的按钮(我希望'f'创建一个标志并左键单击以“打开”磁贴),同时仍然能够传递一个变量这与按钮被创建为函数时不同......代码后描述会变得更清晰......

    from tkinter import *
    from random import *
    master = Tk()
    bomb_positions = []

    for i in range (160):
        random = randint(0, 2)

        if random == 0 or 1:                              #These are 'safe' buttons
            btn = Button(master, width=2)
            btn.bind('<ButtonRelease-1>', lambda event, i=i: check(i))
                                      #Correct value of i when check(i) is called at event
            btn.bind('f', lambda event, i=i: place_flag(i))
                                      #Diffrent value if i when place_flag(i) is called at event
            btn.pack()
            btn.grid(row=row, column=col)

        if random == 2:                              #These are 'bombs'
            btn = Button(master, width=2)
            btn.bind('<ButtonRelease-1>', function3)
            btn.bind('f', lambda event, i=i: place_flag(i))    #Same problem as above
            btn.pack()
            bomb_positions.append(i)

当运行程序时,每个按钮的特定值 i 进入 function1。但是,当我在任何按钮上按“f”时,会调用“place_flag()”函数,但 i 的值不同。 (有趣的是,在调用 'place_flag()' 函数时使用的 i 的值是从不给出任何值开始的。对于 tkinter 窗口的非活动部分上的每一次 TAB 按下,该值从 1 开始并随着每按一次 Tab。)

我希望 i 的值与“check()”函数后面的值相同,但我不知道是什么导致了我的问题。有什么想法吗?

(对编程非常陌生,因此对不正确的术语和模糊的解释感到抱歉……很高兴我能得到所有帮助!)

【问题讨论】:

  • random == 0 or 1 并不像您认为的那样做。 random == 0 为真(可能)或1 为真(始终):此if 将始终执行。
  • 谢谢,没注意到!但是,问题仍然存在......当在任何按钮上按“f”时,“i”的值从零开始,从 1 开始,并且随着我在游戏窗口的非活动区域上每次按“tab”而增加
  • 为什么不使用右键来放置标志而不是 f 键。这将更容易实现
  • 您会在我给出的答案中注意到,我已经从代码中删除了.pack()。不能在同一个父元素中同时使用这两种布局方法。

标签: python python-3.x variables tkinter lambda


【解决方案1】:

您没有跟踪您的按钮,因此您以后将无法编辑它们。我已经在你的代码中添加了一个 btnList 来跟踪它们。 我还为&lt;Enter&gt; 创建了一个新绑定,它将焦点设置在当前鼠标位于其顶部的按钮上。

此代码应该允许您将鼠标悬停在按钮上,按“f”键,它会将按钮的文本从空白更改为“F”。

我现在还更新了您的代码,以便当用户点击一个正方形时,它会检查该正方形是否在bomb_positions 列表中;如果是,则打印“Boom!!”到控制台并在按钮中放置一个 *,如果它不是炸弹则放置一个 O。

希望我所做的更改能帮助您继续游戏。

from tkinter import *
from random import *
master = Tk()
bomb_positions = []

def function3(event):
    print("Function3")


def place_flag(square):
    print("PlaceFlag")
    btnList[square]['text'] = 'F'

def check(square,btn):
    print("Check ",square, btn)
    if square in bomb_positions:
        print("Booommmmm!!!")
        btnList[square]['text'] = '*'
    else:
        btnList[square]['text'] = 'O'

def setFocus(event):
    event.widget.focus_set()

btnList = []

for i in range (160):

    random = randint(0, 2)
    row, col = divmod(i,16)

    if random == 0 or 1:                              #These are 'safe' buttons
        btn = Button(master, width=2)
        btn.bind('<ButtonRelease-1>', lambda event, i=i: check(i,btn))
                                  #Correct value of i when check(i) is called at event
        btn.bind('f', lambda event, i=i: place_flag(i))
                                  #Diffrent value if i when place_flag(i) is called at event
        #btn.pack()
        btn.grid(row=row, column=col)

    if random == 2:                              #These are 'bombs'
        btn = Button(master, width=2)
        btn.bind('<ButtonRelease-1>', lambda event, i=i: check(i,btn))
        btn.bind('f', lambda event, i=i: place_flag(i))    #Same problem as above
        btn.grid(row=row, column=col)
        bomb_positions.append(i)
    btn.bind("<Enter>",setFocus)
    btnList.append(btn)



master.mainloop()

通过简单的更改,按钮也可以进行颜色编码,以显示其中是否有炸弹。

def check(square,btn):
    print("Check ",square, btn)
    if square in bomb_positions:
        print("Booommmmm!!!")
        btnList[square]['bg'] = 'red'
        btnList[square]['text'] = '*'
    else:
        btnList[square]['bg'] = 'green'

【讨论】:

    【解决方案2】:

    如果您尝试在 Python 中实现 Minesweeper,您可能会发现这是一个有用的代码起点:

    import tkinter
    import functools
    
    class MineSweep(tkinter.Frame):
    
        @classmethod
        def main(cls, width, height):
            root = tkinter.Tk()
            window = cls(root, width, height)
            root.mainloop()
    
        def __init__(self, master, width, height):
            super().__init__(master)
            self.__width = width
            self.__height = height
            self.__build_buttons()
            self.grid()
    
        def __build_buttons(self):
            self.__buttons = []
            for y in range(self.__height):
                row = []
                for x in range(self.__width):
                    button = tkinter.Button(self)
                    button.grid(column=x, row=y)
                    button['text'] = '?'
                    command = functools.partial(self.__push, x, y)
                    button['command'] = command
                    row.append(button)
                self.__buttons.append(row)
    
        def __push(self, x, y):
            print('Column = {}\nRow = {}'.format(x, y))
    
    if __name__ == '__main__':
        MineSweep.main(10, 10)
    

    如果您正在寻找功能更全面的程序进行修改,您可能希望以此为起点:

    import tkinter
    import functools
    import random
    from tkinter.simpledialog import askstring, Dialog
    from tkinter.messagebox import showinfo
    import os.path
    
    ################################################################################
    
    class MineSweep(tkinter.Frame):
    
        @classmethod
        def main(cls, width, height, mines, scores):
            root = tkinter.Tk()
            root.resizable(False, False)
            root.title('MineSweep')
            window = cls(root, width, height, mines, scores)
            root.protocol('WM_DELETE_WINDOW', window.close)
            root.mainloop()
    
    ################################################################################
    
        def __init__(self, master, width, height, mines, scores):
            super().__init__(master)
            self.__width = width
            self.__height = height
            self.__mines = mines
            self.__wondering = width * height
            self.__started = False
            self.__playing = True
            self.__scores = ScoreTable()
            self.__record_file = scores
            if os.path.isfile(scores):
                self.__scores.load(scores)
            self.__build_timer()
            self.__build_buttons()
            self.grid()
    
        def close(self):
            self.__scores.save(self.__record_file)
            self.quit()
    
        def __build_timer(self):
            self.__secs = tkinter.IntVar()
            self.__timer = tkinter.Label(textvariable=self.__secs)
            self.__timer.grid(columnspan=self.__width, sticky=tkinter.EW)
            self.__after_handle = None
    
        def __build_buttons(self):
            self.__reset_button = tkinter.Button(self)
            self.__reset_button['text'] = 'Reset'
            self.__reset_button['command'] = self.__reset
            self.__reset_button.grid(column=0, row=1,
                                     columnspan=self.__width, sticky=tkinter.EW)
            self.__reset_button.blink_handle = None
            self.__buttons = []
            for y in range(self.__height):
                row = []
                for x in range(self.__width):
                    button = tkinter.Button(self, width=2, height=1,
                                            text='?', fg='red')
                    button.grid(column=x, row=y+2)
                    command = functools.partial(self.__push, x, y)
                    button['command'] = command
                    row.append(button)
                self.__buttons.append(row)
    
        def __reset(self):
            for row in self.__buttons:
                for button in row:
                    button.config(text='?', fg='red')
            self.__started = False
            self.__playing = True
            self.__wondering = self.__width * self.__height
            if self.__after_handle is not None:
                self.after_cancel(self.__after_handle)
                self.__after_handle = None
            self.__secs.set(0)
    
        def __push(self, x, y, real=True):
            button = self.__buttons[y][x]
            if self.__playing:
                if not self.__started:
                    self.__build_mines()
                    while self.__buttons[y][x].mine:
                        self.__build_mines()
                    self.__started = True
                    self.__after_handle = self.after(1000, self.__tick)
                if not button.pushed:
                    self.__push_button(button, x, y)
                elif real:
                    self.__blink(button, button['bg'], 'red')
            elif real:
                self.__blink(button, button['bg'], 'red')
    
        def __blink(self, button, from_bg, to_bg, times=8):
            if button.blink_handle is not None and times == 8:
                return
            button['bg'] = (to_bg, from_bg)[times & 1]
            times -= 1
            if times:
                blinker = functools.partial(self.__blink, button,
                                            from_bg, to_bg, times)
                button.blink_handle = self.after(250, blinker)
            else:
                button.blink_handle = None
    
        def __tick(self):
            self.__after_handle = self.after(1000, self.__tick)
            self.__secs.set(self.__secs.get() + 1)
    
        def __push_button(self, button, x, y):
            button.pushed = True
            if button.mine:
                button['text'] = 'X'
                self.__playing = False
                self.after_cancel(self.__after_handle)
                self.__after_handle = None
                self.__blink(self.__reset_button, button['bg'], 'red')
            else:
                button['fg'] = 'SystemButtonText'
                count = self.__total(x, y)
                button['text'] = count and str(count) or ' '
                self.__wondering -= 1
                if self.__wondering == self.__mines:
                    self.after_cancel(self.__after_handle)
                    self.__after_handle = None
                    self.__finish_game()
    
        def __finish_game(self):
            self.__playing = False
            score = self.__secs.get()
            for row in self.__buttons:
                for button in row:
                    if button.mine:
                        button['text'] = 'X'
            if self.__scores.eligible(score):
                name = askstring('New Record', 'What is your name?')
                if name is None:
                    name = 'Anonymous'
                self.__scores.add(name, score)
            else:
                showinfo('You did not get on the high score table.')
            HighScoreView(self, 'High Scores', self.__scores.listing())
    
        def __total(self, x, y):
            count = 0
            for x_offset in range(-1, 2):
                x_index = x + x_offset
                for y_offset in range(-1, 2):
                    y_index = y + y_offset
                    if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
                        count += self.__buttons[y_index][x_index].mine
            if not count:
                self.__propagate(x, y)
            return count
    
        def __propagate(self, x, y):
            for x_offset in range(-1, 2):
                x_index = x + x_offset
                for y_offset in range(-1, 2):
                    y_index = y + y_offset
                    if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
                        self.__push(x_index, y_index, False)
    
        def __build_mines(self):
            mines = [True] * self.__mines
            empty = [False] * (self.__width * self.__height - self.__mines)
            total = mines + empty
            random.shuffle(total)
            iterator = iter(total)
            for row in self.__buttons:
                for button in row:
                    button.mine = next(iterator)
                    button.pushed = False
                    button.blink_handle = None
    
    ################################################################################
    
    class ScoreTable:
    
        def __init__(self, size=10):
            self.__data = {999: [''] * size}
    
        def add(self, name, score):
            assert self.eligible(score)
            if score in self.__data:
                self.__data[score].insert(0, name)
            else:
                self.__data[score] = [name]
            if len(self.__data[max(self.__data)]) == 1:
                del self.__data[max(self.__data)]
            else:
                del self.__data[max(self.__data)][-1]
    
        def eligible(self, score):
            return score <= max(self.__data)
    
        def listing(self):
            for key in sorted(self.__data.keys()):
                for name in self.__data[key]:
                    yield name, key
    
        def load(self, filename):
            self.__data = eval(open(filename, 'r').read())
    
        def save(self, filename):
            open(filename, 'w').write(repr(self.__data))
    
    ################################################################################
    
    class HighScoreView(Dialog):
    
        def __init__(self, parent, title, generator):
            self.__scores = generator
            super().__init__(parent, title)
    
        def body(self, master):
            self.__labels = []
            for row, (name, score) in enumerate(self.__scores):
                label = tkinter.Label(master, text=name)
                self.__labels.append(label)
                label.grid(row=row, column=0)
                label = tkinter.Label(master, text=str(score))
                self.__labels.append(label)
                label.grid(row=row, column=1)
            self.__okay = tkinter.Button(master, command=self.ok, text='Okay')
            self.__okay.grid(ipadx=100, columnspan=2, column=0, row=row+1)
            return self.__okay
    
        def buttonbox(self):
            pass
    
    ################################################################################
    
    if __name__ == '__main__':
        MineSweep.main(10, 10, 10, 'scores.txt')
    

    参考:ActiveState Code » Recipes » MineSweep

    【讨论】:

      猜你喜欢
      • 2021-06-10
      • 2020-10-24
      • 2018-10-15
      • 2011-06-03
      • 2018-06-08
      • 2014-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多