I've downloaded a sample snake game from this github and I am desperately trying to modify the code to have an additional username entry window which then displays the username as the game is being played. I'd also hoping to code a leaderboard once I have the current issue sorted out.
My problem is that while the game itself works great as soon as I added the additional code to open the username entry window at the start the window dimension are applied to the game window as well.
Can anyone point out the likely blindingly obvious thing I'm missing here? My code is everything after the comment line.
import tkinter as tk
from random import randint
from PIL import Image, ImageTk
from tkinter import messagebox
MOVE_INCREMENT = 20
MOVES_PER_SECOND = 15
GAME_SPEED = 1000 // MOVES_PER_SECOND
def StartGame():
class Snake(tk.Canvas):
def __init__(self):
super().__init__(
width=600, height=620, background="black", highlightthickness=0
)
self.snake_positions = [(100, 100), (80, 100), (60, 100)]
self.food_position = self.set_new_food_position()
self.direction = "Right"
self.score = 0
self.load_assets()
self.create_objects()
self.bind_all("<Key>", self.on_key_press)
self.pack()
self.after(GAME_SPEED, self.perform_actions)
def load_assets(self):
try:
self.snake_body_image = Image.open("./assets/snake.png")
self.snake_body = ImageTk.PhotoImage(self.snake_body_image)
self.food_image = Image.open("./assets/food.png")
self.food = ImageTk.PhotoImage(self.food_image)
except IOError as error:
print(error)
root.destroy()
def create_objects(self):
self.create_text(
45, 12, text=f"Score: {self.score}", tag="score", fill="#fff", font=(10)
)
self.create_text(
150, 12, text=f"Username: {username}", tag="username", fill="#fff", font=(10)
)
for x_position, y_position in self.snake_positions:
self.create_image(
x_position, y_position, image=self.snake_body, tag="snake"
)
self.create_image(*self.food_position, image=self.food, tag="food")
self.create_rectangle(7, 27, 593, 613, outline="#525d69")
def check_collisions(self):
head_x_position, head_y_position = self.snake_positions[0]
return (
head_x_position in (0, 600)
or head_y_position in (20, 620)
or (head_x_position, head_y_position) in self.snake_positions[1:]
)
def check_food_collision(self):
if self.snake_positions[0] == self.food_position:
self.score = 1
self.snake_positions.append(self.snake_positions[-1])
self.create_image(
*self.snake_positions[-1], image=self.snake_body, tag="snake"
)
self.food_position = self.set_new_food_position()
self.coords(self.find_withtag("food"), *self.food_position)
score = self.find_withtag("score")
self.itemconfigure(score, text=f"Score: {self.score}", tag="score")
def end_game(self):
self.delete(tk.ALL)
self.create_text(
self.winfo_width() / 2,
self.winfo_height() / 2,
text=f"Game over! You scored {self.score}!",
fill="#fff",
font=("", 10)
)
def move_snake(self):
head_x_position, head_y_position = self.snake_positions[0]
if self.direction == "Left":
new_head_position = (head_x_position - MOVE_INCREMENT, head_y_position)
elif self.direction == "Right":
new_head_position = (head_x_position MOVE_INCREMENT, head_y_position)
elif self.direction == "Down":
new_head_position = (head_x_position, head_y_position MOVE_INCREMENT)
elif self.direction == "Up":
new_head_position = (head_x_position, head_y_position - MOVE_INCREMENT)
self.snake_positions = [new_head_position] self.snake_positions[:-1]
for segment, position in zip(self.find_withtag("snake"), self.snake_positions):
self.coords(segment, position)
def on_key_press(self, e):
new_direction = e.keysym
all_directions = ("Up", "Down", "Left", "Right")
opposites = ({"Up", "Down"}, {"Left", "Right"})
if (
new_direction in all_directions
and {new_direction, self.direction} not in opposites
):
self.direction = new_direction
def perform_actions(self):
if self.check_collisions():
self.end_game()
self.check_food_collision()
self.move_snake()
self.after(GAME_SPEED, self.perform_actions)
def set_new_food_position(self):
while True:
x_position = randint(1, 29) * MOVE_INCREMENT
y_position = randint(3, 30) * MOVE_INCREMENT
food_position = (x_position, y_position)
if food_position not in self.snake_positions:
return food_position
root = tk.Tk()
root.title("Snake")
root.resizable(False, False)
root.tk.call("tk", "scaling", 4.0)
board = Snake()
root.mainloop()
# My code
window = tk.Tk()
window.title("Snake game")
window.geometry('250x120')
Usern_label= tk.Label(window, text="Username: ", font=("Arial Bold", 10))
Usern_label.place(x=10, y=10)
UsernInput = tk.Text(window, bg='White', bd=5, width=15, height=1)
UsernInput.pack
UsernInput.place(x=90, y=10)
username = UsernInput.get("1.0", "end-1c")
if len(username) > 8:
messagebox.showwarning("Invalid username", "Please enter a valid username")
usern_button = tk.Button(window, text='Start Game', bd='5', command=StartGame)
usern_button.place(x=20, y=75)
window.mainloop()
CodePudding user response:
The cause of your problems is just copying the snake code and putting it in a function. You should take the Snake class out of the function. You should only have one instance of Tk, the root window. Therefore this needs to be created first. I've used .withdraw() to hide it until we need it. Then I've used Toplevel to create another window. This is like Tk but doesn't cause problems. Your current user validation does not work as you get and check the value of UsernInput immediately after creating it. Therefore it will always pass as the length of an empty string is less than 8. Instead you need to do the validation in the StartGame function. I've replaced the Text with an Entry because whilst Text would work fine, Entry is much more well suited to what you need (a single line of text).
When the user presses the button it calls StartGame. This validates the input and if it's valid it destroys the username window and shows the root, then does the same setup stuff as before. Although it isn't necessary in this case it's always a good idea to pass a parent widget to every child. In this case the snake canvas will automatically go to the root window but in your original code this is what caused the window size to be wrong as it automatically went in the username window. I've done this by adding the parent parameter to Snake and passing root. Assuming the Snake code all works fine this code should all work. If you want to use the value of username in Snake then you'll want to pass it as a parameter by changing it to def __init__(self, parent, username):. Then after adding something like self.username = username you can use it anywhere in the class.
import tkinter as tk
from random import randint
from PIL import Image, ImageTk
from tkinter import messagebox
MOVE_INCREMENT = 20
MOVES_PER_SECOND = 15
GAME_SPEED = 1000 // MOVES_PER_SECOND
class Snake(tk.Canvas):
def __init__(self, parent):
super().__init__(
parent, width=600, height=620, background="black", highlightthickness=0
)
# Rest of the Snake class code goes here
root = tk.Tk()
root.title("Snake")
root.withdraw() # Hide root window for now
def StartGame():
# Do the validation after the user has pressed the button
# The input has no value if you call it immediately after
username = UsernInput.get() # The get is easier with an Entry
if len(username) > 8:
messagebox.showwarning("Invalid username", "Please enter a valid username")
window.focus() # Go back to the window after showing the warning
else:
window.destroy() # Get rid of the username input
root.deiconify() # Show the root window again
root.tk.call("tk", "scaling", 4.0)
root.resizable(False, False)
board = Snake(root)
window = tk.Toplevel() # Don't use multiple Tk instances
window.title("Snake game")
window.geometry('250x120')
Usern_label= tk.Label(window, text="Username: ", font=("Arial Bold", 10))
Usern_label.place(x=10, y=10)
# It makes more sense to use an entry for what you are doing
UsernInput = tk.Entry(window, bg='White', bd=5, width=15)
UsernInput.place(x=90, y=10)
usern_button = tk.Button(window, text='Start Game', bd='5', command=StartGame)
usern_button.place(x=20, y=75)
root.mainloop()
