0

I'm trying to create a memory game in Python Tkinter. I'm fairly new to tkinter. I created sixteen buttons displaying the cards using a nested for loop and adding each button to a list. My issue is with replacing the card image with a princess image (it's a disney princess memory game). It says the list index is out of range, although it's not supposed to be. Can someone please help me fix it? I've tried solving it in multiple ways with no success. This is my code so far. I haven't finished making the game so that's why some of the variables aren't really used.

Code:

from tkinter import *
from random import choice

screen = Tk()
screen.title("Disney Princesses Memory Game")
width = screen.winfo_screenwidth()
height = screen.winfo_screenheight()
screen.geometry("%dx%d" % (width, height))
screen.configure(bg="#e0bce5")

title = Label(screen, text="Memory Game", font=("David", 50, "underline", "bold"), bg="#e0bce5")
title.place(x=400, y=20)

images_list = [
    PhotoImage(file="images/aurora.png"),
    PhotoImage(file="images/belle.png"),
    PhotoImage(file="images/cinderella.png"),
    PhotoImage(file="images/jasmine.png"),
    PhotoImage(file="images/mulan.png"),
    PhotoImage(file="images/rapunzel.png"),
    PhotoImage(file="images/snow white.png"),
    PhotoImage(file="images/tiana.png")
]

buttons_list = []

num_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


def replace_card(c, d):
    chosen_image = choice(images_list)
    check = num_list[images_list.index(chosen_image)]
    check += 1

    buttons_list[c][d].configure(image=chosen_image)

    if check > 2:
        num_list.remove(num_list[check])


card = PhotoImage(file="images/card.png")
x = 100
y = 250
for i in range(2):
    for j in range(8):
        a = Button(screen, image=card, command=replace_card(i, j))
        x += 100
        a.place(x=x, y=y)
        buttons_list.append([a])
    y += 100
    x = 100

screen.mainloop()

Here's the Error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Meirom\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1892, in __call__
    return self.func(*args)
  File "C:\Users\Meirom\PycharmProjects\memorygame\main.py", line 46, in <lambda>
    a = Button(screen, image=card, command=lambda: replace_card(i, j))
  File "C:\Users\Meirom\PycharmProjects\memorygame\main.py", line 35, in replace_card
    buttons_list[c][d].configure(image=chosen_image)
IndexError: list index out of range

The output that I expect needs to show the generated image on the button that the card is shown on when the card is clicked

Thank you so much to Shoaib Ahmed and to everyone else for all the help! This is the completed program (I need to add more features so it's not exactly finished, but the original error was solved):

from tkinter import *
from random import choice

screen = Tk()
screen.title("Disney Princesses Memory Game")
width = screen.winfo_screenwidth()
height = screen.winfo_screenheight()
screen.geometry("%dx%d" % (width, height))
screen.configure(bg="#e0bce5")

title = Label(screen, text="Memory Game", font=("David", 50, "underline", "bold"), bg="#e0bce5")
title.place(x=400, y=20)

images_list = [
    PhotoImage(file="images/aurora.png"),
    PhotoImage(file="images/belle.png"),
    PhotoImage(file="images/cinderella.png"),
    PhotoImage(file="images/jasmine.png"),
    PhotoImage(file="images/mulan.png"),
    PhotoImage(file="images/rapunzel.png"),
    PhotoImage(file="images/snow white.png"),
    PhotoImage(file="images/tiana.png")
]

buttons_list = []

chosen_images = []

flipped = []

num_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

card = PhotoImage(file="images/card.png")


def choose_images():
    count = 0
    for num in range(16):
        chosen_image = choice(images_list)
        count += 1

        if count > 2:
            count = 0
            reset()
        else:
            chosen_images.append(chosen_image)


def replace_card(c, d):
    global flipped
    buttons_list[c][d].configure(image=chosen_images[d])
    flipped.append(buttons_list[c][d])


def reset():
    global card
    for element in flipped:
        element.configure(image=card)


choose_images()
x = 100
y = 250
for i in range(2):
    buttons_list.append([])
    for j in range(8):
        a = Button(screen, image=card, command=lambda i=i, j=j: replace_card(i, j))
        x += 100
        a.place(x=x, y=y)
        buttons_list[i].append(a)
    y += 100
    x = 100

screen.mainloop()
Roni
  • 597
  • 5
  • 21
  • in `a = Button(screen, image=card, command=replace_card(i, j))` you can not call the function with parameters. you need to give a function name only, like `a = Button(screen, image=card, command=replace_card)` Check out this link to understand command binding better: https://www.pythontutorial.net/tkinter/tkinter-command/ – shoaib30 Jul 15 '21 at 09:29
  • Hi @Roni welcome to stackoverflow. Could you please add the actual error message to your post please? It'll make it easier for people to help – Dan Jul 15 '21 at 09:30
  • What are the rules of the game? The player clicks on the card, and what should happen to the lists and on the screen? – 8349697 Jul 15 '21 at 09:38
  • The implementation details depend on what the program is supposed to do. But it seems to me that there is no need to add buttons to the list and get them from the list. You can bind some kind of event to them. You can reset all button images if these buttons are children of some widget. – 8349697 Jul 15 '21 at 11:55
  • @8349697, can you please show me an example of what you mean? I don't really understand – Roni Jul 15 '21 at 12:02
  • You can make the widget respond to different [events](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/events.html): `widget.bind(event, handler)`. Try `a.bind('', handler_func)` in loop. An make `def handler_func(event): print(event.widget)`. `event.widget` will be the button clicked. Also: [Event Handling](https://tkdocs.com/tutorial/concepts.html#events) and `w.winfo_children()` in [Universal widget methods](https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/universal.html). – 8349697 Jul 15 '21 at 12:58
  • Ok, thank you. I managed to solve the problem without it though – Roni Jul 15 '21 at 17:05

1 Answers1

0

I don't have the images to try the code, but this should work. You need a Lambda function to pass arguments to your replace_card function

Source: https://www.pythontutorial.net/tkinter/tkinter-command/

from tkinter import *
from random import choice

screen = Tk()
screen.title("Disney Princesses Memory Game")
width = screen.winfo_screenwidth()
height = screen.winfo_screenheight()
screen.geometry("%dx%d" % (width, height))
screen.configure(bg="#e0bce5")

title = Label(screen, text="Memory Game", font=("David", 50, "underline", "bold"), bg="#e0bce5")
title.place(x=400, y=20)

images_list = [
    PhotoImage(file="images/aurora.png"),
    PhotoImage(file="images/belle.png"),
    PhotoImage(file="images/cinderella.png"),
    PhotoImage(file="images/jasmine.png"),
    PhotoImage(file="images/mulan.png"),
    PhotoImage(file="images/rapunzel.png"),
    PhotoImage(file="images/snow white.png"),
    PhotoImage(file="images/tiana.png")
]

buttons_list = []

num_list = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


def replace_card(c, d):
    chosen_image = choice(images_list)
    check = num_list[images_list.index(chosen_image)]
    check += 1

    buttons_list[c][d].configure(image=chosen_image)

    if check > 2:
        num_list.remove(num_list[check])


card = PhotoImage(file="images/card.png")
x = 100
y = 250
for i in range(2):
    buttons_list.append([])
    for j in range(8):
        a = Button(screen, image=card, command=lambda i=i, j=j: replace_card(i, j))
        x += 100
        a.place(x=x, y=y)
        buttons_list[i].append(a)
    y += 100
    x = 100

screen.mainloop()
shoaib30
  • 877
  • 11
  • 24
  • 1
    It should be `command=lambda i=i, j=j: replace_card(i, j)`. Look [here](https://stackoverflow.com/questions/10865116/tkinter-creating-buttons-in-for-loop-passing-command-arguments) – TheLizzard Jul 15 '21 at 09:36
  • The code runs, but when I click one of the cards, the same error appears on the console – Roni Jul 15 '21 at 09:46
  • It is because your original code appends the buttons into `buttons_list` in a wrong way. – acw1668 Jul 15 '21 at 09:54
  • @Roni the button list was a single-dimensional list, I have updated it. should work now. Please add working code to your question so that we can run it without other dependencies https://stackoverflow.com/help/minimal-reproducible-example – shoaib30 Jul 15 '21 at 09:58
  • Traceback (most recent call last): File "C:\Users\Meirom\PycharmProjects\memorygame\main.py", line 45, in buttons_list[i] = [] IndexError: list assignment index out of range – Roni Jul 15 '21 at 10:05
  • I managed to fix it now, it runs with no errors, but it has multiple bugs. I updated my code. Take a look – Roni Jul 15 '21 at 10:11
  • @Roni "doesn't work" does not help us understand the problem. If you can expand on what the bugs are it would help. And I have updated the code with a bug I could find. Please explain what is going wrong so that we can help – shoaib30 Jul 15 '21 at 10:29
  • @Shoaib Ahmed I updated my code in my question. Take a look at that. Read the other comments I wrote to you. If you still need more details, please let me know – Roni Jul 15 '21 at 10:39
  • 1
    `buttons_list[i] = []` should be `buttons_list.append([])`. – acw1668 Jul 15 '21 at 10:48
  • @acw1668 thank you for pointing it out, @Roni check the line `buttons_list[i].append(a)` in your code, you are appending to `buttons_list` instead – shoaib30 Jul 15 '21 at 11:17
  • @Shoaib Ahmed, thank you so much for your help and your patience! It finally works! – Roni Jul 15 '21 at 12:00