Posts: 1
Threads: 1
Joined: Jun 2022
Jun-06-2022, 06:12 PM
(This post was last modified: Jun-06-2022, 07:14 PM by Gribouillis.)
I need to add a reset button/option. Can someone help me?
import random
import time
from tkinter import Tk, Button, DISABLED, messagebox
def close_window(self):
root.destroy()
def show_symbol(x, y):
global first
global previousX, previousY
global moves
global pairs
buttons[x, y]['text'] = button_symbols[x, y]
buttons[x, y].update_idletasks()
if first:
previousX = x
previousY = y
first = False
moves = moves + 1
elif previousX != x or previousY != y:
if buttons[previousX, previousY]['text'] != buttons[x, y]['text']:
time.sleep(0.5)
buttons[previousX, previousY]['text'] = ''
buttons[x, y]['text'] = ''
else:
buttons[previousX, previousY]['command'] = DISABLED
buttons[x, y]['command'] = DISABLED
pairs = pairs + 1
if pairs == len(buttons) / 2:
messagebox.showinfo('Matching', 'Number of moves: ' + str(moves))
first = True
root = Tk()
root.title('Igra Memorije')
root.resizable(width=False, height=False)
buttons = {}
first = True
previousX = 0
previousY = 0
moves = 0
pairs = 0
button_symbols = {}
symbols = [u'\u2702', u'\u2702', u'\u2705', u'\u2705', u'\u2708', u'\u2708',
u'\u2709', u'\u2709', u'\u270A', u'\u270A', u'\u270B', u'\u270B',
u'\u270C', u'\u270C', u'\u270F', u'\u270F', u'\u2712', u'\u2712',
u'\u2714', u'\u2714', u'\u2716', u'\u2716', u'\u2728', u'\u2728',
]
random.shuffle(symbols)
for x in range(6):
for y in range(4):
button = Button(command=lambda x=x, y=y: show_symbol(x, y), width=5, height=3, border=2)
button.grid(column=x, row=y,padx=15,pady=20)
buttons[x, y] = button
button_symbols[x, y] = symbols.pop()
root.mainloop()
Attached Files
non.py (Size: 1.81 KB / Downloads: 261)
Posts: 165
Threads: 7
Joined: Nov 2018
Jun-10-2022, 02:39 AM
(This post was last modified: Jun-10-2022, 02:39 AM by joe_momma.)
Migrate your code to a class, it eliminates the use of globals.
One way to clear a number of buttons is assign them to a frame. Them you can
pack_forget() and clear it all. Make more functions, I modified your script into 2 functions,
the other part could be all in your class init function.
here's one way to reset your game
import random
import time
from tkinter import Tk, Button, DISABLED, messagebox, Frame
from functools import partial
def close_window():
root.destroy()
def show_symbol(x, y):
global first
global previousX, previousY
global moves
global pairs
buttons[x, y]['text'] = button_symbols[x, y]
buttons[x, y].update_idletasks()
if first:
previousX = x
previousY = y
first = False
moves = moves + 1
elif previousX != x or previousY != y:
if buttons[previousX, previousY]['text'] != buttons[x, y]['text']:
time.sleep(0.5)
buttons[previousX, previousY]['text'] = ''
buttons[x, y]['text'] = ''
else:
buttons[previousX, previousY]['command'] = DISABLED
buttons[x, y]['command'] = DISABLED
pairs = pairs + 1
if pairs == len(buttons) / 2:
messagebox.showinfo('Matching', 'Number of moves: ' + str(moves))
frame.pack_forget()
frame.pack(expand='yes',fill='both')
make_board()
first = True
def make_board():
global button_symbols
my_font= ('arial',30,'bold')
button_symbols = {}
symbols = [u'\u2702', u'\u2702', u'\u2705', u'\u2705', u'\u2708', u'\u2708',
u'\u2709', u'\u2709', u'\u270A', u'\u270A', u'\u270B', u'\u270B',
u'\u270C', u'\u270C', u'\u270F', u'\u270F', u'\u2712', u'\u2712',
u'\u2714', u'\u2714', u'\u2716', u'\u2716', u'\u2728', u'\u2728',
]
random.shuffle(symbols)
for x in range(6):
for y in range(4):
button = Button(frame,command=partial(show_symbol, x, y),
width=5, height=3, border=2,font= my_font)
button.grid(column=x, row=y,padx=15,pady=20)
buttons[x, y] = button
button_symbols[x, y] = symbols.pop()
root = Tk()
root.title('Igra Memorije')
root.resizable(width=False, height=False)
frame= Frame(root)
frame.pack(expand='yes',fill='both')
buttons = {}
first = True
previousX = 0
previousY = 0
moves = 0
pairs = 0
make_board()
root.mainloop()
Posts: 6,809
Threads: 20
Joined: Feb 2020
Jun-12-2022, 04:41 PM
(This post was last modified: Jun-13-2022, 02:42 AM by deanhystad.)
You want to set the button "state" = DISABLED, not the button "command". The button command should always be "show_symbol".
There is nothing special about the x,y of the buttons. I would make buttons a list instead of a dictionary. This removes some processing because you can use the initial symbols list instead of copying it to a dictionary.
You do not want to unpack the buttons, you just want to set the text to blank and the state to NORMAL.
When making the symbols list you don't have to type each symbol twice. Let Python do that for you. Less typing and less chance for error.
This is the program written as a class.
import random
import time
import tkinter as tk
from tkinter.messagebox import showinfo
symbols = [
"\u2702",
"\u2705",
"\u2708",
"\u2709",
"\u270A",
"\u270B",
"\u270C",
"\u270F",
"\u2712",
"\u2714",
"\u2716",
"\u2728",
]
font = ("Times", 24)
class MatchingGame(tk.Tk):
"""A tiles matching game.
Click on a tile to display it's picture. Click on a second tile.
If the pictures match, try to find the next match. If the tiles
do not match the pictures are hidden. The game is over when you
match all the tiles.
"""
def __init__(self, symbols, columns):
super().__init__()
self.title("Matching Game")
self.symbols = symbols * 2
self.tiles = [tk.Button(self, width=3, border=2, font=font) for _ in self.symbols]
for index, tile in enumerate(self.tiles):
tile.configure(command=lambda arg=tile: self.show_symbol(arg))
tile.grid(row=index // columns, column=index % columns)
self.reset()
def reset(self):
"""Reset the board to start a new game"""
random.shuffle(self.symbols)
for tile, symbol in zip(self.tiles, self.symbols):
tile.configure(text=" ", state=tk.NORMAL)
tile.symbol = symbol
self.prev = None
self.moves = 0
self.remaining = len(self.tiles)
def show_symbol(self, tile):
"""Uncover a the tile. If two tiles exposed check for match."""
tile.configure(text=tile.symbol, state=tk.DISABLED)
self.update_idletasks()
if self.prev is None:
self.prev = tile
self.moves += 1
else:
# Second tile. Check for match
if self.prev.symbol == tile.symbol:
self.remaining -= 2
else:
# Tiles do not match. Wait a moment and hide
time.sleep(1)
tile.configure(text=" ", state=tk.NORMAL)
self.prev.configure(text=" ", state=tk.NORMAL)
self.prev = None
if self.remaining == 0:
showinfo("Game Over", f"Number of moves: {self.moves}")
self.reset()
MatchingGame(symbols, 6).mainloop() One class works fine for a simple game like this, but in a more complicated game you would make multiple classes so that no one class is responsible for doing too many things. Let's say I want to separate the code of how the tiles work into it's own class so we have a class that knows how to play the matching game, and another class that knows how to show/hide the tile symbols.
import random
import time
import tkinter as tk
from tkinter.messagebox import showinfo
symbols = [
"\u2702",
"\u2705",
"\u2708",
"\u2709",
"\u270A",
"\u270B",
"\u270C",
"\u270F",
"\u2712",
"\u2714",
"\u2716",
"\u2728",
]
class Tile(tk.Button):
"""A special button for the matching game"""
font = ("Times", 24)
def __init__(self, parent, symbol, callback):
super().__init__(parent, width=3, border=2, font=self.font, command=self.click)
self.symbol = symbol
self.callback = callback
def __eq__(self, other):
"""Tiles are equal if their symbols match"""
return self.symbol == other.symbol
def click(self):
"""Callback when button is clicked. Show symbol and disable clicking"""
self.configure(text=self.symbol, state=tk.DISABLED)
self.callback(self)
def clear(self):
"""Hide symbol ane enable clicking"""
self.configure(text=" ", state=tk.NORMAL)
class MatchingGame(tk.Tk):
"""A tiles matching game.
Click on a tile to display it's picture. When two tiles are uncovered,
check for a match. If tiles match do not match, cover the tiles and try
again. The game is over when all tiles are uncovered.
"""
def __init__(self, symbols, columns):
super().__init__()
self.title("Matching Game")
self.tiles = [Tile(self, symbol, self.check_match) for symbol in symbols * 2]
self.columns = columns
self.reset()
def reset(self):
"""Reset the board to start a new game"""
random.shuffle(self.tiles)
for index, tile in enumerate(self.tiles):
tile.clear()
tile.grid(row=index // self.columns, column=index % self.columns)
self.prev = None
self.moves = 0
self.remaining = len(self.tiles)
def check_match(self, tile):
"""Check if uncovered tiles match."""
if self.prev is None:
self.prev = tile
self.moves += 1
else:
# Two tiles are uncovered. Check for match
if self.prev == tile:
self.remaining -= 2
else:
# Tiles do not match. Wait a moment and hide
self.update_idletasks()
time.sleep(1)
tile.clear()
self.prev.clear()
self.prev = None
if self.remaining == 0:
showinfo("Game Over", f"Number of moves: {self.moves}")
self.reset()
MatchingGame(symbols, 6).mainloop() The number of lines of code increased, but I think the matching game code is a little easier to understand now that it is focused on game play and the Tile class takes on the responsibility of flipping tiles.
|