Amazon
Amazon

Reputation: 105

Memory Game in Python with Tkinter

I'm trying to make a memory game using python 3 and tkinter. I'm new to python so this is kind of hard. My problem is that I'm trying to call the method "show_word" in the class "Cell" from the class "Memory" but I don't know exactly how to do it.

the memory game:

    def create_widgets(self):
        """ Create widgets to display the Memory game """
        # buttons to show the words
        column = 0
        row = 1
        the_pairs = self.readShuffle()
        for index in range(36):
            Button(self,
                   text = "hidden",
                   width = "7",
                   height = "2",
                   relief = GROOVE,
                   command = WHAT SHOULD I WRITE HERE???
                   ).grid(row = row, column = column, padx = 1, pady = 1)

            column += 1
            if column == 6:
                column = 0
                row += 1

Upvotes: 1

Views: 4662

Answers (2)

senderle
senderle

Reputation: 151177

You've run into a subtle scoping problem. When you create a function that refers to variables from a containing scope, the value of those variables isn't fixed at the time of function definition, but at the time of function execution. In other words, when you do this:

command = lambda: print(index) 

You're telling Python to print whatever the value of index is when the function is called. By the time a user pushes one of those buttons, causing the function to be called, the value of index is 35.

To fix the value at the time of function definition, you have to use default values, like so:

command = lambda x=index: print(x)

I'm sure you can figure out the corresponding show_word fix, but just in case:

command = lambda x=index: Cell.show_word(the_pairs[x])

Upvotes: 3

Karl Knechtel
Karl Knechtel

Reputation: 61643

I need an answer for my comment before I can help you directly, but in the mean time, here are some hints for writing the code more elegantly:

from tkinter import *
# `tkinter` is meant to be used that way, but `random` isn't really - the names
# can be confusing without an explicit module reference. So instead:
import random

class Cell:
    def __init__(self, word, hidden):
        self.word = word
        self.hidden = hidden

    def show_word(self):
        """ Shows the word behind the cell """
        self.hidden = not self.hidden
        # no need to take this logic apart into cases
        self.button.set(str(self))

    def __str__(self):
        """ Displays or hides the word """
        # A simpler form of conditional; also, don't compare things
        # explicitly to True or False
        return "---" if self.hidden else self.word

class Memory(Frame):
    """ GUI application that creates a Memory game """
    def __init__(self, master):
        super(Memory, self).__init__(master)
        self.grid()
        self.create_widgets()
        self.tries = 0

    def readShuffle(self):
        """ Creates and organizes (shuffles) the pairs in a list """
        # The modern idiom for handling files and ensuring they are closed
        with open("memo.txt","r") as words_file:
            # The file can be iterated over directly, and is treated as
            # a list of lines. Since we just want all the rstrip()ped
            # lines, we can do the file processing all at once with a list 
            # comprehension.
            # Let's also grab 18 random words while we're at it. We don't
            # really want to *shuffle* the words, but instead *sample* them -
            # we don't care about the words that we didn't select.
            words = random.sample(
                [line.rstrip('\n') for line in words_file], 18
            )

        # Instead of making 18 pairs of cells, we can make 18 cells and then
        # pair them up. The set of 18 cells can also be made easily with a 
        # list comprehension. Notice how we get to iterate directly now, 
        # instead of messing around with indices into lists.
        the_pairs = [Cell(word, True) for word in words] * 2
        shuffle(the_pairs)
        return the_pairs

    def create_widgets(self):
        """ Creates widgets to display the Memory game """
        # instruction text
        Label(self,
              text = "- The Memory Game -",
              font = ("Helvetica", 12, "bold"),
              ).grid(row = 0, column = 0, columnspan = 7)

        # buttons to show the words
        the_pairs = self.readShuffle()
        self.buttons = []
        # Again, we can iterate in a more Pythonic way.
        for i, pair in enumerate(the_pairs):
            # Instead of having extra counters to work out the row and column,
            # we can simply do math on the index value.
            column, row = i % 6, i // 6
            temp = StringVar()
            temp.set(str(pair))
            # Instead of adding the button to a list and then reaching into the
            # list to configure it, get everything set up first.
            button = Button(self,
                   textvariable = temp,
                   width = "7",
                   height = "2",
                   relief = GROOVE,
                   command = lambda: print(index)
                   ))
            button.grid(row = row, column = column, padx = 1, pady = 1)
            buttons.append(button)
            pair.button = temp

        # total tries
        self.label = Label(self) # Don't abbreviate!
        Label(self,
              text = "Total tries: 0",
              font = ("Helvetica", 11, "italic")
              ).grid(row = 7, columnspan = 7, pady = 5)

        # ...

Upvotes: 0

Related Questions