I am creating a simple pong game clone in tkinter and i want to create a game loop so that when the game is finished, it will restart.
import tkinter as tk
import random
import time
from time import sleep
# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')
# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h}+{center_x}+{center_y}')
def main():
# canvas
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
cv.delete('all')
cv.configure(bg="#000000")
global x, y, num_r, num_l, ball
# initializing variables
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
x = 0
y = 0
num_l = 0
num_r = 0
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
#print(dirx, diry)
# paddles
paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')
# middle line
midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2+5, 85, outline='#000000', fill='#ffffff')
midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2+5, 185, outline='#000000', fill='#ffffff')
midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2+5, 285, outline='#000000', fill='#ffffff')
midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2+5, 385, outline='#000000', fill='#ffffff')
midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2+5, 485, outline='#000000', fill='#ffffff')
midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2+5, 585, outline='#000000', fill='#ffffff')
midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2+5, 685, outline='#000000', fill='#ffffff')
# score trackers
score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_left.place(relx=0.43, rely=0.1)
score_right.place(relx=0.55, rely=0.1)
# ball
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, outline='#000000', fill='#696969')
# movement of the paddles
def detectMoveKeys():
# left paddle
root.bind('w', leftUp)
root.bind('s', leftDown)
# right paddle
root.bind('i', rightUp)
root.bind('k', rightDown)
root.after(5, detectMoveKeys)
def leftUp(self):
cv.move(paddle_l, 0, -10)
#print('registered')
def leftDown(self):
cv.move(paddle_l, 0, 10)
#print('registered')
def rightUp(self):
cv.move(paddle_r, 0, -10)
#print('registered')
def rightDown(self):
cv.move(paddle_r, 0, 10)
#print('registered')
def ballMovement():
global x, y
if cv.coords(ball)[1]+10 == canvas_h:
y = y * (0-1)
print('ball touched bottom', x, y)
if cv.coords(ball)[1] == 0:
y = y * (0-1)
print('ball touched top', x, y)
pos = cv.coords(ball)
if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched left paddle', x, y)
if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched right paddle', x, y)
cv.move(ball, x, y)
#print(cv.coords(ball)[0])
cv.pack()
root.after(35, ballMovement)
def endGame():
if score_right['text'] == '10':
score_right['text'] = '0'
score_left['text'] = '0'
r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
cv.delete('all')
score_right.config(text='')
score_left.config(text='')
cv.destroy()
root.after(3000, main)
root.after(10, endGame)
def outOfBounds():
global num_l, num_r, ball, dirx, diry, x, y
# detects if the ball is out of bounds on the left side of the screen
if cv.coords(ball)[0] == -30.0:
print('right scored')
num_r += 1
score_right['text'] = str(num_r)
cv.pack()
cv.coords(ball, 630, 350, 650, 370)
#ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, fill='#696969')
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# detects if the ball is out of bounds on the right side of the screen
if cv.coords(ball)[0] == 1310.0:
print('left scored')
num_l += 1
score_left['text'] = str(num_l)
cv.pack()
cv.delete(ball)
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, fill='#696969')
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
root.after(10, outOfBounds)
root.after(5, detectMoveKeys)
root.after(35, ballMovement)
root.after(10, outOfBounds)
root.after(10, endGame)
main()
root.mainloop()
When I first start the program, everything runs fine until the first game loop is started.
Then it starts lagging and eventually freezes.
Here are the error messages:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
func(*args)
File "/home/anton/Documents/pong/pong.py", line 140, in outOfBounds
if cv.coords(ball)[0] == -30.0:
File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/usr/lib64/python3.10/tkinter/__init__.py", line 839, in callit
func(*args)
File "/home/anton/Documents/pong/pong.py", line 103, in ballMovement
if cv.coords(ball)[1]+10 == canvas_h:
File "/usr/lib64/python3.10/tkinter/__init__.py", line 2795, in coords
self.tk.call((self._w, 'coords') + args))]
_tkinter.TclError: invalid command name ".!canvas"
What I have tried:
Making sure all variables are set to their default values after the game start
Putting everything in a tk.Frame widget and destroying that
If it helps I am running Fedora 36 Workstation.
Does anyone know what's the problem? Thanks in advance.
Edit:
I have implemented an 'end' flag so the loops stop, but the program starts lagging again after some restarts. I don't know how to fix this either.
...
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
def main():
#print('main called')
global x, y, num_r, num_l, ball, end
# canvas
cv.configure(bg="black")
...
end = False
...
def detectMoveKeys():
...
if end == False:
root.after(5, detectMoveKeys)
...
def ballMovement():
...
if end == False:
root.after(35, ballMovement)
def outOfBounds():
...
if end == False:
root.after(10, outOfBounds)
def endGame():
global end, ball
def r_won_destroy():
r_won.destroy()
cv.coords(ball, canvas_w/2-5, canvas_h/2-5, canvas_w/2+5, canvas_h/2+5)
cv.delete('all')
if score_right['text'] == '10':
score_right['text'] = ''
score_left['text'] = ''
r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, r_won_destroy)
if end == False:
root.after(10, endGame)
if end == False:
root.after(5, detectMoveKeys)
root.after(35, ballMovement)
root.after(10, outOfBounds)
root.after(10, endGame)
main()
root.mainloop()
I am new to tkinter so sorry if this is an obvious mistake.
Can someone solve this, please?
Part of the problem is in this block of code:
def endGame():
if score_right['text'] == '10':
...
root.after(3000, main)
root.after(10, endGame)
If the if condition is true, you call main after 3 seconds. You also continue to call endGame 100 times a second whether it's true or false So, even when the game ends, you're continuing to call endGame 100 times a second.
When you call main from inside that if block, main also calls endGame, causing endGame to start a new loop of running 100 times a second. So after one restart you are calling endGame 100 times a second twice. The third restart you're calling endGame 100 times a second three times. And so on. Eventually you'll end up calling endGame thousands of times a second, far more often than it can keep up with.
The same thing is happening for detectMoveKeys, ballMovement, and outOfBounds. You never stop the endless loop of those functions when the game ends, and start a new endless loop every time a game starts.
I think I did it. I may have just been calling the outOfBounds, ballMovement, detectMoveKeys and endGame too many times a second. (100 times) I have reduced that to 5 times a second and the lagging seems to be gone. I've tested 100 restarts and it was still responsive. But I feel like after an undefined amount of restarts, it's going to be lagging again, because the function calls may still be stacking on top of one another. Please tell me if this is the case. Thanks a lot for the help.
Here is the full code for you to test it out:
import tkinter as tk
import random
# setting base Tk widget
root = tk.Tk()
root.resizable(False, False)
root.title("Pong")
root.configure(bg='black')
# finding the center of the screen
screen_w = root.winfo_screenwidth()
screen_h = root.winfo_screenheight()
canvas_w = 1280
canvas_h = 720
center_x = int(screen_w/2 - canvas_w / 2)
center_y = int(screen_h/2 - canvas_h / 2)
root.geometry(f'{canvas_w}x{canvas_h}+{center_x}+{center_y}')
# canvas
cv = tk.Canvas(root, width=canvas_w, height=canvas_h)
cv.configure(bg="black")
# paddles
paddle_l = cv.create_rectangle(20, 320, 30, 390, outline='#000000', fill='#ffffff')
paddle_r = cv.create_rectangle(1250, 320, 1260, 390, outline='#000000', fill='#ffffff')
# middle line
midline1 = cv.create_rectangle(canvas_w/2-5, 25, canvas_w/2+5, 85, outline='#000000', fill='#ffffff')
midline2 = cv.create_rectangle(canvas_w/2-5, 125, canvas_w/2+5, 185, outline='#000000', fill='#ffffff')
midline3 = cv.create_rectangle(canvas_w/2-5, 225, canvas_w/2+5, 285, outline='#000000', fill='#ffffff')
midline4 = cv.create_rectangle(canvas_w/2-5, 325, canvas_w/2+5, 385, outline='#000000', fill='#ffffff')
midline5 = cv.create_rectangle(canvas_w/2-5, 425, canvas_w/2+5, 485, outline='#000000', fill='#ffffff')
midline6 = cv.create_rectangle(canvas_w/2-5, 525, canvas_w/2+5, 585, outline='#000000', fill='#ffffff')
midline7 = cv.create_rectangle(canvas_w/2-5, 625, canvas_w/2+5, 685, outline='#000000', fill='#ffffff')
# score trackers
score_left = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_right = tk.Label(text='0', bg='#000000', fg='#ffffff', font=('Helvetica', 30))
score_left.place(relx=0.43, rely=0.1)
score_right.place(relx=0.55, rely=0.1)
# ball
ball = cv.create_rectangle(canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10, outline='#000000', fill='#696969')
cv.pack()
# movement of the paddles
def detectMoveKeys():
# left paddle
root.bind('w', leftUp)
root.bind('s', leftDown)
# right paddle
root.bind('i', rightUp)
root.bind('k', rightDown)
if end == False:
root.after(200, detectMoveKeys)
def leftUp(self):
cv.move(paddle_l, 0, -10)
#print('registered')
def leftDown(self):
cv.move(paddle_l, 0, 10)
#print('registered')
def rightUp(self):
cv.move(paddle_r, 0, -10)
#print('registered')
def rightDown(self):
cv.move(paddle_r, 0, 10)
#print('registered')
def ballMovement():
global x, y
# checking if the ball touched the bottom side of the window
if cv.coords(ball)[1]+10 == canvas_h:
y = y * (0-1)
print('ball touched bottom', x, y)
# checking if the ball touched the top side of the window
if cv.coords(ball)[1] == 0:
y = y * (0-1)
print('ball touched top', x, y)
pos = cv.coords(ball)
if paddle_l in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched left paddle', x, y)
if paddle_r in cv.find_overlapping(pos[0], pos[1], pos[2], pos[3]):
x = x * (0-1)
print('ball touched right paddle', x, y)
cv.move(ball, x, y)
#print(cv.coords(ball)[0])
cv.pack()
if end == False:
root.after(35, ballMovement)
def outOfBounds():
global num_l, num_r, ball, dirx, diry, x, y
# checking if the ball is out of bounds on the left side
if cv.coords(ball)[0] <= -30.0:
print('right scored')
num_r += 1
score_right['text'] = str(num_r)
# setting the ball to the original position and setting the direction
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.pack()
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# checking if the ball is out of bounds on the right side
if cv.coords(ball)[0] >= 1310.0:
print('left scored')
num_l += 1
score_left['text'] = str(num_l)
# setting the ball to the original position and setting the direction
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.pack()
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
if end == False:
root.after(200, outOfBounds)
# checking if the game should end
def endGame():
global end, ball, paddle_l, paddle_r
# deleting the label and settings objects back to original positions
def r_won_destroy():
r_won.destroy()
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.coords(paddle_l, 20, 320, 30, 390)
cv.coords(paddle_r, 1250, 320, 1260, 390)
score_right['text'] = '0'
score_left['text'] = '0'
# checking if the right score is 10, if yes, the game ends
if score_right['text'] == '10':
r_won = tk.Label(text='Right won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
r_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, r_won_destroy)
# deleting the label and settings objects back to original positions
def l_won_destroy():
l_won.destroy()
cv.coords(ball, canvas_w/2-10, canvas_h/2-10, canvas_w/2+10, canvas_h/2+10)
cv.coords(paddle_l, 20, 320, 30, 390)
cv.coords(paddle_r, 1250, 320, 1260, 390)
score_right['text'] = '0'
score_left['text'] = '0'
# checking if the left score is 10, if yes, the game ends
if score_left['text'] == '10':
l_won = tk.Label(text='Left won,\nthe game will\nrestart in 3 sec.', bg='white', fg='black', font=('Helvetica', 50))
l_won.place(relx=0.3, rely=0.5)
end = True
#cv.itemconfig('all', fill='black')
root.after(3000, main)
root.after(2000, l_won_destroy)
if end == False:
root.after(200, endGame)
def main():
# initializing variables
global x, y, end, num_l, num_r
end = False
num_l = 0
num_r = 0
x = 0
y = 0
dirx = random.randint(0, 1)
diry = random.randint(0, 1)
# setting the starting diretion of the ball
if dirx == 0 and diry == 0:
x = -10
y = -10
if dirx == 0 and diry == 1:
x = -10
y = 10
if dirx == 1 and diry == 0:
x = 10
y = -10
if dirx == 1 and diry == 1:
x = 10
y = 10
# calling other functions
detectMoveKeys()
ballMovement()
outOfBounds()
endGame()
# calling the main function
main()
# starting mainloop for the main window object
root.mainloop()
Related
After snake dies "Game over" is display in the window and after player click game should start again. I don't know how to make this. I try to call function after click but I see this error: "TypeError: next_turn() missing 1 required positional argument: 'food'" , adding food as argument in functions not help.
type from tkinter import *
import random
GAME_WIDTH = 700
GAME_HEIGHT = 700
SPEED = 70
SPACE_SIZE = 20
BODY_PARTS = 3
SNAKE_COLOR = "#00FF00"
FOOD_COLOR = "#FF0000"
BACKGROUND_COLOR = "#000000"
class Snake:
def __init__(self):
self.body_size = BODY_PARTS
self.coordinates = []
self.squares = []
for i in range(0, BODY_PARTS):
self.coordinates.append([0, 0])
for x, y in self.coordinates:
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR, tag="snake")
self.squares.append(square)
class Food:
def __init__(self):
x = random.randint(0, (GAME_WIDTH / SPACE_SIZE) - 1) * SPACE_SIZE
y = random.randint(0, (GAME_HEIGHT / SPACE_SIZE) - 1) * SPACE_SIZE
self.coordinates = [x, y]
canvas.create_oval(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=FOOD_COLOR, tag="food")
def next_turn(snake, food):
x, y = snake.coordinates[0]
if direction == "up":
y -= SPACE_SIZE
elif direction == "down":
y += SPACE_SIZE
elif direction == "left":
x -= SPACE_SIZE
elif direction == "right":
x += SPACE_SIZE
snake.coordinates.insert(0, (x, y))
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR)
snake.squares.insert(0, square)
if x == food.coordinates[0] and y == food.coordinates[1]:
global score
score += 1
label.config(text="Score:{}".format(score))
canvas.delete('food')
food = Food()
global SPEED
SPEED -= 1
else:
del snake.coordinates[-1]
canvas.delete(snake.squares[-1])
del snake.squares[-1]
if check_collisions(snake):
game_over()
else:
window.after(SPEED, next_turn, snake, food)
def change_direction(new_direction):
global direction
if new_direction == 'left':
if direction != 'right':
direction = new_direction
elif new_direction == 'right':
if direction != 'left':
direction = new_direction
elif new_direction == 'up':
if direction != 'down':
direction = new_direction
elif new_direction == 'down':
if direction != 'up':
direction = new_direction
def check_collisions(snake):
x, y = snake.coordinates[0]
if x < 0 or x >= GAME_WIDTH:
return True
elif y < 0 or y >= GAME_HEIGHT:
return True
for body_part in snake.coordinates[1:]:
if x == body_part[0] and y == body_part[1]:
return True
return False
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2, font=("consolas", 70), text="GAME OVER", fill="red", tag="gameover")
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height() - 200, font=("consolas", 20), text="click to play", fill="red", tag="para")
window.bind('<Button-1>', next_turn)
# HERE-----------------------------------------------------------
window = Tk()
window.title("Snake game")
window.resizable(False, False)
score = 0
direction = "down"
label = Label(window, text="Score:{}".format(score), font=('consolas', 40))
label.pack()
canvas = Canvas(window, bg=BACKGROUND_COLOR, height=GAME_HEIGHT, width=GAME_WIDTH)
canvas.pack()
window.update()
window_width = window.winfo_width()
window_height = window.winfo_height()
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
x = int((screen_width/2) - (window_width/2))
y = int((screen_height/2) - (window_height/2))
window.geometry(f"{window_width}x{window_height}+{x}+{y}")
window.bind('<a>', lambda event: change_direction('left'))
window.bind('<d>', lambda event: change_direction('right'))
window.bind('<w>', lambda event: change_direction('up'))
window.bind('<s>', lambda event: change_direction('down'))
snake = Snake()
food = Food()
next_turn(snake, food)
window.mainloop()
Try this
...
def replay(dummy):
canvas.delete(ALL)
snake = Snake()
food = Food()
next_turn(snake, food)
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2, font=("consolas", 70), text="GAME OVER", fill="red", tag="gameover")
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height() - 200, font=("consolas", 20), text="click to play", fill="red", tag="para")
window.bind('<Button-1>', replay)
...
One problem I see is with:
window.bind('<Button-1>', next_turn)
bind method expects a function that accepts one argument event. next_turn expects two.
I'm tryin to make a snake game, but I am unable to record the score and high-score properly. Whenever the snake collects an item, all that happens is that the high score increases, but not the general score.
from tkinter import *
from settings import *
import random
import turtle
import pygame as pg
import keyboard
from os import path
global game_state
class Snake:
def __init__(self):
self.body_size = BODY_PARTS
self.coordinates = []
self.squares = []
for i in range(0, BODY_PARTS):
self.coordinates.append([0, 0])
for x, y in self.coordinates:
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR, tag="snake")
self.squares.append(square)
class Food:
def __init__(self):
x = random.randint(0, (GAME_WIDTH / SPACE_SIZE)-1) * SPACE_SIZE
y = random.randint(0, (GAME_HEIGHT / SPACE_SIZE) - 1) * SPACE_SIZE
self.coordinates = [x, y]
canvas.create_oval(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=FOOD_COLOR, tag="food")
class TryAgain:
def __init__(self):
x = (GAME_WIDTH / SPACE_SIZE)-1 * SPACE_SIZE
y = (GAME_HEIGHT / SPACE_SIZE) - 1 * SPACE_SIZE
self.coordinates = [x, y]
def show_title_screen():
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/4,
font=('times new roman',70), text="SNAKE!", fill="Green", tag="title")
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/2,
font=('times new roman',22), text="Use the arrow keys to move", fill="Green", tag="title")
def next_turn(snake, food):
x, y = snake.coordinates[0]
if direction == "up":
y -= SPACE_SIZE
elif direction == "down":
y += SPACE_SIZE
elif direction == "left":
x -= SPACE_SIZE
elif direction == "right":
x += SPACE_SIZE
snake.coordinates.insert(0, (x, y))
square = canvas.create_rectangle(x, y, x + SPACE_SIZE, y + SPACE_SIZE, fill=SNAKE_COLOR)
snake.squares.insert(0, square)
if x == food.coordinates[0] and y == food.coordinates[1]:
global score
global high_score
score += 1
label.config(text="Score:{}".format(score))
if score >= high_score:
high_score = score
label.config(text="High Score:{}".format(high_score))
canvas.delete("food")
food = Food()
else:
del snake.coordinates[-1]
canvas.delete(snake.squares[-1])
del snake.squares[-1]
if check_collisions(snake):
game_over()
else:
window.after(SPEED, next_turn, snake, food)
def change_direction(new_direction):
global direction
if new_direction == 'left':
if direction != 'right':
direction = new_direction
elif new_direction == 'right':
if direction != 'left':
direction = new_direction
elif new_direction == 'up':
if direction != 'down':
direction = new_direction
elif new_direction == 'down':
if direction != 'up':
direction = new_direction
def check_collisions(snake):
x, y = snake.coordinates[0]
if x < 0 or x >= GAME_WIDTH:
return True
elif y < 0 or y >= GAME_HEIGHT:
return True
for body_part in snake.coordinates[1:]:
if x == body_part[0] and y == body_part[1]:
return True
return False
def game_over():
canvas.delete(ALL)
canvas.create_text(canvas.winfo_width()/2, canvas.winfo_height()/4,
font=('times new roman',70), text="GAME OVER", fill="orange", tag="gameover")
button = TryAgain()
def load_data(self):
self.dir = path.dirname(__dirname__)
with open(path.join(self.dir, HS_FILE), 'w') as f:
try:
self.highscore = int(f.read())
except:
self.highscore = 0
window = Tk()
window.title("Snake game")
window.resizable(False, False)
score = 0
high_score = 0
direction = 'down'
label = Label(window, text="Score:{}".format(score), font=('times new roman', 40))
label.pack()
label = Label(window, text="High Score:{}".format(high_score), font=('times new roman', 40))
label.pack()
canvas = Canvas(window, bg=BACKGROUND_COLOR, height=GAME_HEIGHT, width=GAME_WIDTH)
canvas.pack()
window.update()
window_width = window.winfo_width()
window_height = window.winfo_height()
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
x = int((screen_width/2) - (window_width/2))
y = int((screen_height/2) - (window_height/2))
window.geometry(f"{window_width}x{window_height}+{x}+{y}")
show_title_screen()
window.bind('<Left>', lambda event: change_direction('left'))
window.bind('<Right>', lambda event: change_direction('right'))
window.bind('<Up>', lambda event: change_direction('up'))
window.bind('<Down>', lambda event: change_direction('down'))
snake = Snake()
food = Food()
next_turn(snake, food)
window.mainloop()
In one instance, I tried to rearrange the canvas codes that involved "score" and "high score", and tried to make it so that when the high score is greater than the score, it would start counting, but that still doesn't work.
I don't know anything about tkinter. But I think the problem is due to this:
label = Label(window, text="Score:{}".format(score), font=('times new roman', 40))
label.pack()
label = Label(window, text="High Score:{}".format(high_score), font=('times new roman', 40))
label.pack()
You're using the same variable name for both labels.
And then when you update the scores:
score += 1
label.config(text="Score:{}".format(score))
if score >= high_score:
high_score = score
label.config(text="High Score:{}".format(high_score))
The variable name label refers only to the high score label.
I think you can fix this by using different variable names for the two labels, i.e. something like score_label and high_score_label.
i am making a pong game
i managed to code the ball to bounce around the window
but it seems that the code, for using user input to get the rectangles to move isn't working.
when i run the code i have been getting a traceback to my move.ball command relating to making the ball bounce, and i believe that may be stopping the rest of the code from running. I have included the full code in case any errors can be spotted in it.
from tkinter import *
import tkinter as tkr
import time
tk = tkr.Tk()
Canvas = tkr.Canvas(tk, width=300, height=400)
Canvas.grid()
ball = Canvas.create_oval(3,3,40,40,fill="light blue")
player1 = Canvas.create_rectangle(20,5,90,30,fill="black")
Canvas.moveto(player1, 120, 380)
player2 = Canvas.create_rectangle(20,5,90,30,fill="black")
Canvas.moveto(player2, 120, -5)
x = 1
y = 3
while True:
Canvas.move(ball,x,y)
pos = Canvas.coords(ball)
if pos[3] >= 400 or pos[1] <= 0:
y = -y
if pos[2] >= 300 or pos[0] <= 0:
x = -x
tk.update()
time.sleep(0.025)
pass
tk.mainloop()
def left(event):
x == -10
y == 0
Canvas.move(player1, x, y)
def right(event):
x == 10
y == 0
Canvas.move(player1, x, y)
def up(event):
x == 0
y == -10
Canvas.move(player1, x, y)
def down(event):
x == -10
y == 0
Canvas.move(player1, x, y)
root.bind("<Left>", left)
root.bind("<Right>", right)
root.bind("<Up>", up)
root.bind("<Down>", down)
tk.mainloop()
i am getting this trace back-
Traceback (most recent call last):
File "C:/Users/amarb/Desktop/code/pong.py", line 15, in <module>
Canvas.move(ball,x,y)
File "C:\Users\amarb\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2949, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".!canvas"
.!canvas is the internal identifier of the canvas widget that you created. This error is happening because you try to run code after mainloop() returns, which is after the window being destroyed. You can't interact with widgets after they have been destroyed.
Edit: New. How to get the paddle moving and hit the paddle. I only used one player at a time. You will do the rest. I had to modify some and add some new functions. I am using new python 3.11.0b3.
from tkinter import *
import tkinter as tkr
import time
import random
tk = tkr.Tk()
width=300
height=400
Canvas = tkr.Canvas(tk, width=width, height=height)
Canvas.pack()
tk.update()
ball = Canvas.create_oval(3,3,25,25,fill="red")
Canvas.move(ball, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
ball_x = starts[0]
ball_y = 3
player1 = Canvas.create_rectangle(20,5,90,30,fill="black")
Canvas.moveto(player1, 180, 380)
player2 = Canvas.create_rectangle(20,5,90,30,fill="blue")
Canvas.move(player2, 120, -5)
paddle_x = 0
def hit_bat(pos):
bat_pos = Canvas.coords(player1) #retrieve the coordinates of the bat position - note the ball coordinates are being passed
if pos[2] >= bat_pos[0] and pos[0] <= bat_pos[2]: #if the right side of the ball (that is the x right hand coordinate) is greater than the left side of the bat, AND the left side of the ball is less than the right side of the bat ....move etc
if pos[3]>= bat_pos[1] and pos[3] <= bat_pos[3]: #if the bottom of the ball (pos[3]) is between the paddle's top [bat pos[1]) and bottom (pos[3])
return True
return False
def draw_ball():
global ball_x, ball_y
Canvas.move(ball, ball_x, ball_y)
pos = Canvas.coords(ball)
if pos[1] <= 0:
ball_y = 6
if pos[3] >= height:
ball_y = -6
#Call the hit_bat function
if hit_bat(pos) == True: #if the hit_bat function returns a value of True, the direction of the ball is changed
ball_y = -6 #if the ball hits the bat, then send the ball flying up at a rate of -6 (higher the number the faster the fly!)
if pos[0] <= 0:
ball_x = 6
if pos[2]>= width:
ball_x = -6
tk.update()
def hit_paddle(pos):
paddle_pos = Canvas.coords(ball)
if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
return True
return False
def draw_paddle():
global paddle_x
Canvas.move(player1, paddle_x, 0)
pos = Canvas.coords(player1)
if pos[0] <= 0:
paddle_x = 0
elif pos[2] >= width:
paddle_x = 0
time.sleep(0.02)
tk.update()
def move_left(event):
global paddle_x
paddle_x = -2
def move_right(event):
global paddle_x
paddle_x = 2
Canvas.bind_all("<KeyPress-Left>", move_left)
Canvas.bind_all("<KeyPress-Right>", move_right)
while True:
draw_ball()
draw_paddle()
New image:
My 9 year-old son is studying Python and he faced below problem in his code (bubble breaker game):
Traceback (most recent call last):
File "M:\Yandex disk\YandexDisk\ДЕТКИ\python modules\Bubble Blaster.py", line 55, in <module>
move_bubbles()
File "M:\Yandex disk\YandexDisk\ДЕТКИ\python modules\Bubble Blaster.py", line 48, in
move_bubbles
c.move(bub_id[i], -bub_speed[i], 0)
File "C:\Users\User\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line
2949, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid command name ".!canvas"*
I've rechecked the whole code twice but can't find any mistake.
Here is the whole code:
from tkinter import *
HEIGHT = 1080
WIDTH = 1920
window = Tk()
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
SHIP_R = 15
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
SHIP_SPD = 10
def move_ship(event):
if event.keysym == 'Up':
c.move(ship_id, 0, -SHIP_SPD)
c.move(ship_id2, 0, -SHIP_SPD)
elif event.keysym == 'Down':
c.move(ship_id, 0, SHIP_SPD)
c.move(ship_id2, 0, SHIP_SPD)
elif event.keysym == 'Left':
c.move(ship_id, -SHIP_SPD, 0)
c.move(ship_id2, -SHIP_SPD, 0)
elif event.keysym == 'Right':
c.move(ship_id, SHIP_SPD, 0)
c.move(ship_id2, SHIP_SPD, 0)
c.bind_all('<Key>', move_ship)
from random import randint
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 10
GAP = 100
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x - r, y - r, x + r, y + r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
from time import sleep, time
BUB_CHANCE = 10
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
window.update()
sleep(0.01)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def del_bubble(I):
del bub_r[I]
del bub_speed[I]
c.delete(bub_id[i])
del bub_id[I]
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[I])
if x < -GAP:
del_bubble(I)
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
window.update()
sleep(0.01)
from math import sqrt
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2 - x1)**2 + (y2 - y1)**2)
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R + bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
score = 0
#MAIN GAME LOOP
while True:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
score += collision()
print(score)
window.update()
sleep(0.01)
What is causing this error?
Major mistakes:
no main loop
from tkinter import *
root = Tk()
root.mainloop()
https://tkdocs.com/tutorial/eventloop.html
all loops inside the main loop are created with the .after method
https://tkdocs.com/shipman/universal.html
when the window is closed, while cycles continue to work, so an error pops up _tkinter.TclError: invalid command name ".!canvas"*
and little things
c.delete(bub_id[i]) The variable "i" is not defined in the function.
x, y = get_coords(bub_id[I])
if x < -GAP:
del_bubble(I)
in this function does not define a large "I".
How to make that when touching (when the coordinates of two objects coincide) the cube to the circle, the circle moves to a random place on the field? Without using classes.
import random
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
oval = field.create_oval(10, 20, 30, 40)
field.grid(row=0, column=0)
rectangle = field.create_rectangle(10, 20, 30, 40)
field.grid(row=0, column=0)
x = 0
y = 0
food_x = random.randrange(0, 300, 20)
food_y = random.randrange(0, 300, 20)
field.move(oval, food_x, food_y)
def food():
global food_x
global food_y
if x == food_x and y == food_y:
food_x = random.randrange(0, 300, 20)
food_y = random.randrange(0, 300, 20)
field.move(oval, food_x, food_y)
root.after(250, food)
food()
def position(event):
global x, y
if event.char in 'wasd':
x = 0
y = 0
if event.char == 'w':
y = -20
elif event.char == 'a':
x = -20
elif event.char == 's':
y = 20
elif event.char == 'd':
x = 20
def snake():
field.move(rectangle, x, y)
root.after(250, snake)
root.bind("<Key>", position)
snake()
food()
root.mainloop()
I think this shows how to do what you want. I've changed the name of your functions and variables to better reflect what they do or represent to make the code clearer. To detect whether the cube and circle are touching is accomplished by first retrieving the coordinates of their corresponding bounding-boxes via the coords() method common to all Canvas objects, before comparing them to determine whether any overlap exists.
I also changed how the food circle is move to a new positions because what you had was capable of moving it very far away from its current location, frequently completely off the field. The replacement code only moves it by the difference between where it currently is and a new position that's within the boundaries of the field.
import random
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
DELAY = 250
field = Canvas(root)
field.grid(row=0, column=0)
food_obj = field.create_oval(10, 20, 30, 40)
snake_obj = field.create_rectangle(10, 20, 30, 40)
x, y = 0, 0
def move_food():
"""Move food to random location."""
food_coords = field.coords(food_obj)
x1 = random.randrange(0, 300, 20)
y1 = random.randrange(0, 300, 20)
field.move(food_obj, x1-food_coords[0], y1-food_coords[1])
def check_collision():
"""If bounding box of snake and food overlap, move food to new position."""
a_x1, a_y1, a_x2, a_y2 = field.coords(food_obj)
b_x1, b_y1, b_x2, b_y2 = field.coords(snake_obj)
# Determine of bounding boxes overlap.
if a_x1 <= b_x2 and b_x1 <= a_x2 and a_y1 <= b_y2 and b_y1 <= a_y2:
move_food()
root.after(DELAY, check_collision)
def on_keypress(event):
global x, y
if event.char in 'wasd ':
x, y = 0, 0
if event.char == 'w':
y = -20
elif event.char == 'a':
x = -20
elif event.char == 's':
y = 20
elif event.char == 'd':
x = 20
def slither():
field.move(snake_obj, x, y)
root.after(DELAY, slither)
root.bind("<Key>", on_keypress)
move_food()
slither()
check_collision()
root.mainloop()