Trouble with global dictionary - python

import tkinter as tk
#messagebox is not imported automatically w/ tkinter
from tkinter import messagebox as tkMessageBox
from tkinter import ttk
from random import random as rand
class Square(object):
""" class to use for each square """
def __init__(self):
self.mine_yn = False
self.flag_yn = False
self.prox_num = 0 # number of nearby mines, parse_mines() will fill this in.
self.button = None # ttk.Button instance.
def parse_mines():
"""Look at how many mines are next to a given square,
store in each Square instance that is inside of sqr_dict. """
global sqr_dict
global mine_frame
print('in parse_mines, sqr_dict='+str(sqr_dict))
def try_a_square(sq): #sq = coordinate string(key)
try:
if sqr_dict[sq].mine_yn == True: return 1
if sqr_dict[sq].mine_yn == False: return 0
except KeyError:
print('KeyError for '+sq)
return 0
n = 0
for x in range(5):
for y in range(4):
#check the 8 adjacent squares.
n = n + try_a_square('x'+str(x+1)+'y'+str(y+1))
n = n + try_a_square('x'+str(x+1)+'y'+str(y ))
n = n + try_a_square('x'+str(x+1)+'y'+str(y-1))
n = n + try_a_square('x'+str(x )+'y'+str(y+1))
n = n + try_a_square('x'+str(x )+'y'+str(y-1))
n = n + try_a_square('x'+str(x-1)+'y'+str(y+1))
n = n + try_a_square('x'+str(x-1)+'y'+str(y ))
n = n + try_a_square('x'+str(x-1)+'y'+str(y-1))
if sqr_dict[('x'+str(x)+'y'+str(y))].mine_yn == False:
(sqr_dict[('x'+str(x)+'y'+str(y))]).prox_num = n
print('x'+str(x)+'y'+str(y)+': '+str(n)) #(debug) print n for each sq
#sqr_dict[('x'+str(x)+'y'+str(y))].button.text=(str(n)) #(debug) show n on each button.
n = 0
def create_mine_field():
global mine_frame
global sqr_dict
sqr_dict = {}
mine_frame = tk.Toplevel(root)
mine_frame.grid()
#what to do if user hit 'X' to close window.
mine_frame.protocol("WM_DELETE_WINDOW", mine_frame_close)
# create grid of squares (buttons)
for x in range(5):
for y in range(4):
coord = 'x'+str(x) + 'y'+str(y)
sqr_dict[coord] = Square()
#print('coord='+coord) #debug
#populate with mines
if ( rand()*100 < mines_pct ):
sqr_dict[coord].mine_yn = True
print(str(sqr_dict[coord].mine_yn))
else:
sqr_dict[coord].mine_yn = False
if sqr_dict[coord].mine_yn:
t = '*'
else: t = ' '
# draw boxes
sqr_dict[coord].button = ttk.Button(mine_frame, text=t, width=3 )
sqr_dict[coord].button.grid(column=x, row=y)
# done, next: parse!
print('in create_mines, sqr_dict='+str(sqr_dict))
#parse_mines()
def root_close():
root.destroy()
def mine_frame_close():
root.destroy()
root = tk.Tk()
root.title("MineSweeper")
mines_pct = 20
start_button = ttk.Button(root,text="Start",command=create_mine_field)
start_button.pack()
root.mainloop()
I'm trying to make a simple minesweeper game with tkinter. If I run the above code a simple mine field appears. However if I uncomment the call to parser() then nothing shows up and it seems like it never finds any mines in the sqr_dict dictionary. (parser() will fill in the numbers of adjacent mines for each square)
I don't understand why this function would cause trouble before it is even called. No mine field appears when it's called. Please kindly give me your suggestions. Thanks!

The reason nothing shows up is because you are using both pack and grid on widgets that are children of the root window. Within any given window you must only use one or the other.

Related

Tkinter: Win check in scalable tic tac toe not working

I'm making a scalable tic tac toe game in Tkinter (meaning the board size can be 2x2 up to whatever fits the screen). I'm using cget("image") to find what mark a button has. For some reason, the win check displays very random things. I've tried a lot of semi-random things to fix it, but had no success in fixing it. Here's the code:
from tkinter import *
class XOGame:
def main_game(self):
self.__game_window = Tk()
self.__grid_size = 3 # User inputted in a different part of the code
self.__game_window.title("Tic Tac Toe (" + str(self.__grid_size) + "x"
+ str(self.__grid_size) + ")")
# this is user inputted in a different part of the program.
self.__players = ["p1", "p2"]
self.__player1 = self.__players[0]
self.__player2 = self.__players[1]
self.build_board(self.__game_window)
self.__game_window.mainloop()
def build_board(self, window):
self.__size = self.__grid_size ** 2
self.__turn_nr = 1
self.__win = False
self.__empty_square = PhotoImage(master=window,
file="rsz_empty.gif")
self.__x = PhotoImage(master=window,
file="rsz_cross.gif")
self.__o = PhotoImage(master=window,
file="rsz_nought.gif")
self.__squares = [None] * self.__size
self.create_win_check_lists()
# Building the buttons and gridding them
for i in range(self.__size):
self.__squares[i] = (Button(window, image=self.__empty_square))
row = 0
column = 0
number = 1
for j in self.__squares:
j.grid(row=row, column=column)
j.config(command=lambda index=self.__squares.index(j):
self.change_mark(index))
column += 1
if number % 3 == 0:
row += 1
column = 0
number += 1
# This is the part where the picture changing happens.
def change_mark(self, i):
"""
Function changes mark of empty button to either X or O depending on the
player in turn. It also checks if the change in mark results in a win.
:param i: The button number, whose mark is being changed
:return: None
"""
if self.__turn_nr % 2 == 1:
self.__player_in_turn = self.__player1
else:
self.__player_in_turn = self.__player2
if self.__player_in_turn == self.__player1:
self.__mark = self.__x
else:
self.__mark = self.__o
self.__squares[i].configure(image=self.__mark, state=DISABLED)
self.__turn_nr += 1
self.check_win(i)
if self.__win is True:
print("this is thought to be a win")
else:
print("the game thinks this is not a win")
# Checking if the game tied.
if self.__turn_nr == self.__size + 1 and not self.__win:
print("the game thinks it tied.")
def check_win(self, i):
"""
Checks if mark placement leads to a win.
:param i: i is the button location.
:return: None
"""
# checks row
if self.__win == False:
for row in self.__rows:
if i + 1 in row:
self.__win = self.checksameimage(row)
if self.__win == True:
break
# checks column
if self.__win == False:
for column in self.__columns:
if i + 1 in column:
self.__win = self.checksameimage(column)
if self.__win == True:
break
# if i is in a diagonal, checks one/both diagonals
if self.__win == False:
for diag in self.__diagonals:
if i + 1 in diag:
self.__win = self.checksameimage(diag)
if self.__win == True:
break
return self.__win
# checking if all the images are same
# This is likely where the issue is. Either this part or checkEqual.
def checksameimage(self, lst):
images = []
for nr in lst:
try:
images.append(self.__squares[nr].cget("image"))
except IndexError:
pass
return self.checkEqual(images)
def checkEqual(self, lst):
"""
Function checks if all elements in a list are equal. Used for checking
if the dice throws are the same.
:param lst: The list the check is performed on
:return: True/False, True if all elements are equal.
"""
if all(x == lst[0] for x in lst):
return True
else:
return False
def create_win_check_lists(self):
"""
Creates lists whose elements are lists of the locations of each spot
of the game board that belongs to a row/column/diagonal
:return:
"""
self.__rows = [[] for _ in range(self.__grid_size)]
for i in range(self.__grid_size):
self.__rows[i].append(i + 1)
for k in range(1, self.__grid_size):
self.__rows[i].append(i + 1 + self.__grid_size * k)
self.__columns = [[] for _ in range(self.__grid_size)]
for i in range(self.__grid_size):
for j in range(1, self.__grid_size + 1):
self.__columns[i].append(i * self.__grid_size + j)
self.getDiagonals(self.__columns)
def getDiagonals(self, lst):
self.__diagonals = [[], []]
self.__diagonals[0] = [lst[i][i] for i in range(len(lst))]
self.__diagonals[1] = [lst[i][len(lst) - i - 1] for i in
range(len(lst))]
def start(self):
# Function starts the first window of the game.
self.main_game()
def main():
ui = XOGame()
ui.start()
main()
The images used in the code are 125x125. Here is a link that works for 24h: https://picresize.com/b5df006025f0d8
your problem is unquestionably with indices. We can see what index each square corresponds to by changing the image to be text corresponding to the index:
Try changing this loop in build_board:
for i in range(self.__size):
self.__squares[i] = (Button(window, image=self.__empty_square))
To instead show the index it corresponds to:
for i in range(self.__size):
self.__squares[i] = (Button(window, text=str(i)))
Then print out lst passed to checksameimage and you will see that the indices you are checking are all one higher than you intended.

layout_window is not defined even if i defined it

I did a game from a coding book in python and it says layout_window is not defined even if i wrote
def layout_window(window):
here is my code
import tkinter
import random
gameOver = False
score = 0
squaresToClear = 0
def play_bombdodger():
create_bombfield(bombfield)
window = tkinter.Tk()
layout_window(window)
window.mainloop
bombfield = []
def create_bombfield(bombfield):
global squaresToClear
for row in range(0,10):
rowList = []
for column in range(0,10):
if random.randint(1,100) < 20:
rowList.append(1)
else:
rowList.append(0)
squaresToClear = squaresToClear + 1
bombfield.append(rowList)
printfield(bombfield)
def printfield(bombfield):
for rowList in bombfield:
print(rowList)
play_bombdodger()
def layout_window(window):
for rowNumber, rowList in enumerate(bombfield):
for columnNumber, columnEntry in enumerate(bombfield):
if random.randint(1,100) < 25:
square = tkinter.Label(window, text = " ", bg = "darkgreen")
You call play_bombdodger which calls layout_window, but you call play_bombdodget before you define layout_window.

Python find closest turtle via mouse click

I'm in the process of creating a minesweeper style game using a turtle-based grid setup. I need to find the closest cell within the grid and reveal the icon located under it whether that be a bomb or a number icons. I'm not looking to make it exact, I just need the mouse click to find the nearest cell in the grid even if the click isn't directly on the board. Currently my code only reveals the icon of the last turtle created on the board and then does nothing else with further clicks.
What can I do to make it recognize the real closest click and do it multiple times until the last bomb is found?
import random
import turtle
import cell
class Game:
def __init__(self, size):
registershapes()
self.__boardsize = size
self.__boardlist = []
self.__bombnum = 0
self.__probe = 0
self.__probelist = []
offset = (size-1) * 17
for x in range(size):
for y in range(size):
t = cell.Cell(x,y)
t.up()
t.shape('question.gif')
t.goto(y*34-offset, offset-x*34)
self.__boardlist.append(t)
def hideMines(self, num):
if num > self.__boardsize ** 2:
return False
self.__bombnum = num
self.__rnums = []
i = 0
while i < self.__bombnum:
currentnum = random.randrange(0, (self.__boardsize**2) - 1)
if currentnum not in self.__rnums:
self.__rnums.append(currentnum)
i += 1
return True
def probe(self, x, y):
for t in self.__boardlist:
pos = t.position()
distx = abs(x - pos[0])
disty = abs(y - pos[1])
distfinal = (distx ** 2 + disty ** 2) ** 0.5
curdist = 0
if curdist < distfinal:
curdist = distfinal
closest = t
if closest in self.__probelist:
return (self.__probe, self.__bombnum)
elif closest in self.__rnums:
closest.shape("bomb.gif")
self.__bombnum -= 1
self.__probe += 1
self.__probelist.append(closest)
return (self.__probe, self.__bombnum)
else:
closest.shape("0.gif")
self.__probe += 1
self.__probelist.append(closest)
return (self.__probe, self.__bombnum)
def registershapes():
wn = turtle.Screen()
wn.register_shape('0.gif')
wn.register_shape('1.gif')
wn.register_shape('2.gif')
wn.register_shape('3.gif')
wn.register_shape('4.gif')
wn.register_shape('5.gif')
wn.register_shape('6.gif')
wn.register_shape('7.gif')
wn.register_shape('8.gif')
wn.register_shape('9.gif')
wn.register_shape('bomb.gif')
wn.register_shape('question.gif')
I believe you're approaching this problem the wrong way. You're activating screen.onclick() and trying to map it to a turtle. Instead, activate turtle.onclick() on the individual turtles, deactivating it when a turtle is selected. Then you don't have to search for the turtle in question, and not actively ignore turtles that have already been selected.
Below is my rework of your code from this and your previous question into an example you can run. I had to guess about the definition of the Cell class:
from turtle import Turtle, Screen
import random
class Cell(Turtle):
def __init__(self, number):
super().__init__("question.gif")
self.__number = number
self.penup()
def number(self):
return self.__number
class Game:
def __init__(self, size):
registershapes()
self.__boardsize = size
self.__boardlist = []
self.__bombnum = 0
self.__probe = 0
self.__rnums = None
offset = (size - 1) * 17
for y in range(size):
for x in range(size):
t = Cell(x + y * size)
t.goto(x * 34 - offset, offset - y * 34)
t.onclick(lambda x, y, self=t: closure(self))
self.__boardlist.append(t)
def hideMines(self, num):
if num > self.__boardsize ** 2:
return False
self.__bombnum = num
self.__rnums = []
i = 0
while i < self.__bombnum:
currentnum = random.randrange(0, self.__boardsize ** 2 - 1)
if currentnum not in self.__rnums:
self.__rnums.append(currentnum)
i += 1
return True
def probe(self, closest):
closest.onclick(None)
if closest.number() in self.__rnums:
closest.shape("bomb.gif")
self.__bombnum -= 1
else:
closest.shape("0.gif")
self.__probe += 1
return (self.__probe, self.__bombnum)
def registershapes():
screen.register_shape('0.gif')
# ...
screen.register_shape('bomb.gif')
screen.register_shape('question.gif')
def closure(closest):
_, rem = mine.probe(closest)
if rem == 0:
over = screen.textinput("Text Input", "Would you like to play again? (Y)es or (N)o")
if over.upper() == 'Y':
main()
else:
screen.bye()
def main():
global mine
board = screen.numinput("Numeric Input", "Enter desired board size: ")
mine = Game(int(board))
nummine = screen.numinput("Numeric Input", "Enter desired number of mines: ")
mine.hideMines(int(nummine))
screen = Screen()
mine = None
main()
screen.mainloop()

Python hangman problems

So I have my hangman almost working however, it is only placing the first correct guess on the first line, and not continuing if I guess the second letter correctly. Also, no matter what letter I guess, it always draws the head on the third letter guessed. I have it narrowed down to my getGuessLocationInPuzzle function, where my index is always printing out zero even if I have guessed my second letter. I've been staring at this code for 2 and 1/2 hours, and can't solve it. Please help!
''' Import Statements '''
from graphics import *
import copy
import random
import string
def drawGallows(window):
gallows = Rectangle(Point(300,280),Point(200,300))
gallows.draw(window)
gallows = Line(Point(250,300),Point(250,100))
gallows.draw(window)
gallows = Line(Point(250,100),Point(120,100))
gallows.draw(window)
gallows = Line(Point(120,100),Point(120,120))
gallows.draw(window)
def getPuzzle():
fh = open("puzzle.txt","r")
fileList = fh.read()
files = string.split(fileList)
puzzle = random.choice(files)
return puzzle
def drawPuzzle(length, window):
lines = Line(Point(15,275),Point(25,275))
for index in range(length):
newline = copy.copy(lines)
newline.move(20,0)
newline.draw(window)
def getGuess(entry, window):
letter = entry.getText()
return letter
def getValidGuess(Validguesses, entry, window):
guess = getGuess(entry, window)
for index in Validguesses:
if guess == index:
return guess
def createTextOverlay(guess):
textstring = ""
num = ord(guess)
for index in range(num - ord("a")):
textstring = textstring + " "
textstring= textstring + guess
for index in range(25-(ord(guess)-ord("a"))):
textstring = textstring+" "
overlay = Text(Point(150, 375), textstring)
overlay.setFace("courier")
overlay.setFill("red")
return overlay
def updateGallows(numWrongGuesses, window):
if numWrongGuesses==1:
head = Circle(Point(120,140),20)
head.draw(window)
if numWrongGuesses==2:
body = Line(Point(120,220),Point(120,160))
body.draw(window)
if numWrongGuesses==3:
arms1 = Line(Point(100,200),Point(120,160))
arms1.draw(window)
if numWrongGuesses==4:
arms2 = Line(Point(120,160),Point(140,200))
arms2.draw(window)
if numWrongGuesses==5:
leg1 = Line(Point(120,220),Point(100,250))
leg1.draw(window)
if numWrongGuesses==6:
leg2 = Line(Poin(120,220),Point(140,250))
leg2.draw(window)
def updatePuzzleDisplay(locations, letter, window):
# For each value in the locations list, print out the letter above the
# appropriate blank space.
# For each value in locations list, create a text object.
locationonspaces = 35
for index in locations:
locationonspaces = locationonspaces + 5
letterthatisguessed = Text(Point(locationonspaces, 265 ), letter)
letterthatisguessed.draw(window)
# Create text object
def getGuessLocationInPuzzle(puzzle, guess):
word = len(puzzle)
locations = []
for index in range(word):
if puzzle[index]==guess:
locations.append(index)
print guess
print index
return locations
def main():
window = GraphWin("Hangman Final", 400, 400)
drawGallows(window)
lettersToGuess = Text(Point(150, 375), "abcdefghijklmnopqrstuvwxyz")
lettersToGuess.setFace("courier")
lettersToGuess.draw(window)
guessInput = Entry(Point(250, 325), 10)
guessInput.draw(window)
puzzle = getPuzzle()
print puzzle
length = len(puzzle)
drawPuzzle(length,window)
guess = getGuess(guessInput,window)
window.getMouse()
Validguesses = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
numWrongGuesses=0
while True:
guess = getValidGuess (Validguesses,guessInput,window)
#print guess
window.getMouse
locations = getGuessLocationInPuzzle(puzzle,guess)
# print locations
if locations == []:
overlay = createTextOverlay(guess)
overlay.draw(window)
updateGallows(numWrongGuesses,window)
numWrongGuesses = numWrongGuesses + 1
else:
updatePuzzleDisplay(locations, guess, window)
overlay = createTextOverlay(guess)
overlay.draw(window)
if guess !=0:
Validguesses.remove(guess)
window.getMouse()
window.getMouse()
main()

Tkinter label height to fit content

I have a label into which I am going to put content of different sizes. I would like to know how high I need to make the label so that I can size the window so it can stay the same for the different content sizes. I have a strategy, but it seems more complicated then it should be.
I want to set a label to a given width and wraplength:
l = Label(root)
l['width'] = 30
l['wraplength'] = 244
l['text'] = "testing this"
Now I want to query the label to find how many lines are used. l['height'] stays at 0, so the best I have been able to come up with is to use l.winfo_height() and convert the height given in pixels to the number of lines used. Nothing in dir(l) seems to give me the information directly, but this strategy is fragile to font changes and other changes.
Any suggestions?
Update: using Brian Oakley's suggestion (which is similar to what I got on usenet) I have the following approximation to a solution (needs polishing, e.g. doesn't take into account that Label breaks at whitespace):
import Tkinter as Tk
import tkFont
import random
import sys
def genstr (j):
rno = random.randint(4,50)
ret_val = str(j) + ":"
for i in range (0, rno):
ret_val += "hello" + str(i)
return ret_val
def gendata (lh):
ret_val = []
for i in range(0,lh):
ret_val.append (genstr (i))
return ret_val
data = gendata (100)
root = Tk.Tk()
font = tkFont.Font(family='times', size=13)
class lines:
def __init__ (self):
self.lastct = 1 # remember where the cutoff was last work from there
def count (self, text, cutoff = 400):
global font
no_lines = 1
start_idx = 0
idx = self.lastct
while True:
if idx > len (text):
idx = len (text)
# shrink from guessed value
while font.measure (text[start_idx:idx - 1]) > cutoff:
if idx <= start_idx:
print "error"
sys.exit ()
else:
idx -= 1
self.lastct = idx - start_idx # adjust since was too big
# increase from guessed value (note: if first shrunk then done)
while (idx < len (text)
and font.measure (text[start_idx:idx]) < cutoff):
idx += 1
self.lastct = idx - start_idx # adjust since was too small
# next line has been determined
print "*" + text[start_idx:idx-1] + "*"
if idx == len(text) and font.measure (text[start_idx:]) < cutoff:
return no_lines
elif idx == len(text):
return no_lines + 1
else:
no_lines += 1
start_idx = idx - 1
idx = start_idx + self.lastct
lin = lines()
for i in range(0,len(data)):
lin.count(data[i], 450)
for i in range(0,min(len(data),10)):
l = Tk.Label(root)
l.pack()
l['text'] = data[i]
print i
no = lin.count (data[i], 450)
print "computed lines", no
l['width'] = 50
l['justify'] = Tk.LEFT
l['anchor'] = 'w'
l['wraplength'] = 450
l['padx']=10
l['pady'] = 5
l['height'] = no
l['font'] = font
if i % 2 == 0:
l['background'] = 'grey80'
else:
l['background'] = 'grey70'
root.mainloop()
You are correct that the height attribute doesn't change. That attribute doesn't tell you the actual height, only the height it is configured to be. The actual height depends on factors such as how much text is in it, the wrap length, the font, and how the widget geometry is managed.
tkinter Font objects have a measure method which lets you determine how tall and wide a string is for a given font. You can get the font for the widget and use that method to determine how much space is required for your string.

Categories