hey guys

i have a very basic program but unfortunatly it does not recognise how many bombs are near it it just displays ok if there is no bomb. and if there is a bomb a message pops up saying bomb would you like to retry, If i press no it quits.. which is fine. how ever i want it to start a new game if i pressed yes. If this makes sense:)

i would appreciate any help :)

import Tkinter as tk
import tkMessageBox
from Tkinter import *
import sys

class BoardGame:

    under_vals = [
    '0',  '1',  '1', 
    '0',  '1',  '0', 
    '1',  '0',  '0', 
    '0',  '1',  '1', ]

    disp_vals = [
    '',  '',  '', 
    '',  '',  '', 
    '',  '',  '', 
    '',  '',  '', ]
    
    buttons={} #empty dictionary
    def __init__(self):
        self.win = tk.Tk()
        self.win.title("Minesweeper");

        #initialisation happens here
        self.choices = tk.Menu(self.win)
        self.choices.add_command(label="Start new game", command=self.newGame)
        self.win.config(menu=self.choices)

        return
    def displayBoard(self):
        # create all buttons with a loop
        r = 0
        c = 0
        count = 0
        for b in self.under_vals:
            rel = 'ridge'
            cmd = lambda x=(r,c): self.click(x)
      
            button=tk.Button(self.win,text='',width=5,relief=rel,command=cmd)
            button.grid(row=r,column=c)
            self.buttons[(r,c)]=button
            count += 1
            c += 1
            if c > 2:
                c = 0
                r += 1
    def newGame():
        self.win.newGame
    
    def enterMainLoop(self):
        self.win.mainloop()
    def click(self, i):
        r,c=i
        bomb=self.under_vals[r*3+c]=='1'  #Evaluates whether there is a bomb or not
        if not bomb:
            self.buttons[i].config(bg="#00FF00", text="OK")#config is used to edit
        else:
            if tkMessageBox.askyesno("error", "BOMB- Try Again?"):
                return newGame()
            else:
                quit()
            
tc = BoardGame()
tc.displayBoard()
tc.enterMainLoop()

How about this??
This will cause the game board to be reset/redrawn when the user selects 'yes'.

import Tkinter as tk
import tkMessageBox
from Tkinter import *
import sys

class BoardGame:

    under_vals = [
    '0',  '1',  '1', 
    '0',  '1',  '0', 
    '1',  '0',  '0', 
    '0',  '1',  '1', ]

    disp_vals = [
    '',  '',  '', 
    '',  '',  '', 
    '',  '',  '', 
    '',  '',  '', ]
    
    buttons={} #empty dictionary
    def __init__(self):
        self.win = tk.Tk()
        self.win.title("Minesweeper");

        #initialisation happens here
        self.choices = tk.Menu(self.win)
        self.choices.add_command(label="Start new game", command=self.newGame)
        self.win.config(menu=self.choices)

        return
    def displayBoard(self):
        # create all buttons with a loop
        r = 0
        c = 0
        count = 0
        for b in self.under_vals:
            rel = 'ridge'
            cmd = lambda x=(r,c): self.click(x)
      
            button=tk.Button(self.win,text='',width=5,relief=rel,command=cmd)
            button.grid(row=r,column=c)
            self.buttons[(r,c)]=button
            count += 1
            c += 1
            if c > 2:
                c = 0
                r += 1
    def newGame(self):
        self.displayBoard();
    def enterMainLoop(self):
        self.win.mainloop()
    def click(self, i):
        r,c=i
        bomb=self.under_vals[r*3+c]=='1'  #Evaluates whether there is a bomb or not
        if not bomb:
            self.buttons[i].config(bg="#00FF00", text="OK")#config is used to edit
        else:
            if tkMessageBox.askyesno("error", "BOMB- Try Again?"):
                self.newGame()
            else:
                quit()
            
tc = BoardGame()
tc.displayBoard()
tc.enterMainLoop()

Cheers for now,
Jas.

Edited 6 Years Ago by JasonHippy: n/a

thats perfect thankyou so much!

Would you be able to help me work out how to instead of displaying "ok" when there is no bomb, but to display how many bombs are nearby? and how to right click to signify you think there is a bomb?

This code has been doing my head in for ages!!!

kitty

OK, using your previous code as a base, I've modified it slightly and come up with something which does more or less exactly what you wanted.

As you'll see I've moved some of your code around and created an extra class (GameSquare) which is used by each square on the board.

When the game is initialised, it uses the arrays you set up (under_vals and disp_vals) to determine which of the squares are bombs and how many bombs are near each square.

As each square knows whether or not it's a bomb, the only things left to deal with are the left and right clicks.
On a left click, if the square has a bomb, it will explode and the game will be over. If it does not, it will go green and display the number of bombs surrounding it.
On a right click, the square goes orange and a '?' is displayed.

To achieve this, I created two member functions in the GameSquare class to act as overrides for the click event (one for the left mouse button and one for the right). The calls to self.button.bind() binds one of the new overridden functions to each mouse button, overriding their default functions. And that's really the main thing to take note of in the code below. Other than that, it's pretty much your old code.

Anyway, here's the code:

import Tkinter as tk
import tkMessageBox
from Tkinter import *
import  sys

# class used for each button/square in the game
class GameSquare:
    def __init__(self, parent, isbomb, num, r, c):
        self.parent = parent
        self.isBomb = isbomb
        self.numToDisplay=num
        self.button = tk.Button(self.parent.win, text='', width=5, relief='ridge')
        self.button.grid(row=r, column=c)

        # set overridden handlers for the left and right mouse buttons
        self.button.bind('<Button-1>', self.lClick) # left
        self.button.bind('<Button-3>', self.rClick) # right

    # overridden Left click handler
    def lClick(self, event):
        if not self.isBomb: # we're safe
            self.button.config(bg="#00FF00", text=str(self.numToDisplay))
        else: # explode
            self.button.config(bg="#FF0000", text="BOOM!")
            if tkMessageBox.askyesno("error", "BOMB- Try Again?"):
                self.parent.newGame()
            else:
                quit()
                
    # overridden Right click handler
    def rClick(self, event): # mark a potential bomb
        self.button.config(bg="#FFCC00", text="?")

# main game class        
class BoardGame:
    under_vals = [
    '0',  '1',  '1', 
    '0',  '1',  '0', 
    '1',  '0',  '0', 
    '0',  '1',  '1', ]

    disp_vals = [
    '2',  '0',  '0', 
    '3',  '0',  '3', 
    '0',  '4',  '3', 
    '2',  '0',  '0', ]
    
    buttons={} #empty dictionary


    def __init__(self):
        self.win = tk.Tk()
        self.win.title("Minesweeper");

        #menu
        self.choices = tk.Menu(self.win)
        self.choices.add_command(label="Start new game", command=self.newGame)
        self.win.config(menu=self.choices)

    def displayBoard(self):
        # create all buttons with a loop
        r = 0
        c = 0
        count = 0
        for b in self.under_vals:
            # determine if this is a bomb
            isBomb=False
            if self.under_vals[count] == '1' :
                isBomb=True
            # determine how many mines are near this instance
            num = self.disp_vals[count]

            # Now create an instance of a GameSquare
            button=GameSquare(self, isBomb, num, r, c)
            self.buttons[(r,c)]=button
            count += 1
            c += 1
            if c > 2:
                c = 0
                r += 1
                
    def newGame(self):
        self.displayBoard();

    def enterMainLoop(self):
        self.win.mainloop()


tc = BoardGame()
tc.displayBoard()
tc.enterMainLoop()

if you wanted to create an override for GameSquare for the middle button, you could do this:

class GameSquare:
    def __init__(self, parent, isbomb, num, r, c):
...
        # set overridden handlers for.....
...
        self.button.bind('Button-2', self.mClick) # middle btn
...
    # overridden middle-click handler
    def mClick:
        # your code here
        # perhaps remove a '?' marker if there is one?

Note: '...' indicates lines of code from my first code block that I omitted for the sake of brevity.

There are still many things you'll need to do to finish your game. At the moment it uses the two hard-coded arrays to determine where the bombs are and how many bombs are nearby, but you could improve this by creating a function to randomly populate the under_vals array and then programmatically work out how many bombs are near each non-bomb square.
You might also want to add some kind of counter to track how many bombs have been successfully marked.
The game should also be over if all of the non-bomb squares have been revealed, or if all of the bombs have been correctly marked.

As I said the game still uses a lot of your original code and there are plenty of improvements that could be made to make the game more complete, but this should serve as a good base to start from.

If there's anything you don't understand, don't hesitate to ask.
All the best,
Jas.

thats amazing thankyou would you explain to me what it means when you have said overridden? and the handler?

kitty

thats amazing thankyou would you explain to me what it means when you have said overridden? and the handler?

kitty

OK, I'm not sure whether I'm strictly using the correct terminology here, I'm also not 100% au fait with the tk library, but....
Basically components like the buttons in the tk library use callbacks to deal with events like mouse button clicks (or keypresses or any number of other events defined in the tk library).

A callback is a function which gets called whenever a specified event occurs. Another term commonly used for callback functions used with events is 'event handler'.

So when I use the term 'handler' or 'event handler' I'm talking about the callback function, the function that gets called when the button is clicked. (or when a timer expires, or when some other relevant event occurs to an object!)

From your original code, you set a callback/event handler for your buttons in these 2 lines:

cmd = lambda x=(r,c): self.click(x)      button=tk.Button(self.win,text='',width=5,relief=rel,command=cmd)

You created a button and you told it that the command to execute when the button was pressed was the self.click() function passing the value of x.

That was all well and good for left-clicks, but there was no way of telling the button how to handle right-clicks from the buttons constructor (at least none that I could see). So by default (unless I'm mistaken) you could only set a callback for left mouse clicks, but not right-mouse clicks.

Now we'll take a look at my code:

self.button = tk.Button(self.parent.win, text='', width=5, relief='ridge')
        self.button.grid(row=r, column=c)

        # set overridden handlers for the left and right mouse buttons
        self.button.bind('<Button-1>', self.lClick) # left
        self.button.bind('<Button-3>', self.rClick) # right

So here, we're creating a blank button, setting its grid position and then using two calls to the button's bind function to create callbacks/handlers for the button. (For when the left and right mouse buttons are pressed).
These callbacks/handlers will override any predefined callbacks for the button (hence my using the term overridden handlers).

So if I did this:

# Create a button and set a callback (self.someFunction)
self.button = tk.Button(self.parent.win, text='', width=5, relief='ridge' command=self.someFunction)
# OK, so far the button will respond to left mouse clicks 
# by calling self.someFunction()

# Now I'll create two overridden handlers
self.button.bind('<Button-1>', self.lClick) # left
self.button.bind('<Button-3>', self.rClick) # right

# Now the button calls self.lClick() on a left-click
# and self.rClick() on a right-click
# self.someFunction() is no longer used!

In the above code, the callback (self.someFunction) set in the buttons constructor has been overridden by the callbacks defined in the bind commands.

However, you could make it so that several functions are called when a button is clicked.....

self.button = tk.Button(self.parent.win, text='', width=5, relief='ridge' command=self.someFunction)

# What's going on here?? What's with the extra parameter?
self.button.bind('<Button-1>', self.lClick, '+') # left
self.button.bind('<Button-3>', self.rClick) # right
self.button.bind('<Button-3>', self.rClick2, '+') # right

In the above example, the third parameter '+' in the bind function makes it so that a callback does not override exisiting callbacks, but rather it is called in addition to them.
So in the above example, on a left-click, the button would call self.lClick() and self.someFunction().
On a right-click the button would call self.rClick2() and self.rClick()

Note: I could be wrong, but I think that in the case of multiple callbacks for an event, they are called in the reverse order that they were declared. So the first declared callback is the last to be called. Hence rClick2() gets called before rClick() and lClick() is called before someFunction().
From the very few times I've used multiple handlers for a single event, it certainly seems to work that way!

So to round up using the button.bind function:
Omitting the third parameter from the call to bind, or passing in anything other than '+' for the third parameter will cause the specified callback to override/replace any other defined callbacks for the object.

But if the third parameter is '+', then the specified callback will be used in addition to existing/defined callbacks for the object.

Anyway, I think I've waffled for long enough...Hopefully some of it made some sense. I certainly hope I haven't confused you any further. Apologies if I have!

Cheers for now,
Jas.

This article has been dead for over six months. Start a new discussion instead.