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 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()
How to make the square change direction and move in the right direction when you press the "w, a, s, d" buttons
(So that he moves himself, after a single press of the button that sets the direction)
Without using classes
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
rectangle = field.create_rectangle(10, 20, 30, 40)
field.grid(row=0, column=0)
def w(y):
field.move(rectangle, 0, y)
y += 1
root.after(5)
def s(y):
field.move(rectangle, 0, y)
y -= 1
root.after(5)
def a(x):
field.move(rectangle, x, 0)
x -= 1
root.after(5)
def d(x):
field.move(rectangle, x, 0)
x += 10
root.after(5)
x=0
y=0
def snake(event):
if event.char == 'w':
w(y)
elif event.char == 'a':
a(x)
elif event.char == 's':
s(y)
elif event.char == 'd':
d(x)
field.move(rectangle, x, y)
root.bind("<Key>", snake)
root.mainloop()
You need to declare two global variables for the x and y directions, for example dx and dy.
Then if you want the rectangle keep moving, you need to use .after() loop.
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
rectangle = field.create_rectangle(10, 20, 30, 40)
field.grid(row=0, column=0)
def move_snake():
field.move(rectangle, dx, dy)
# change the delay value 30 (ms) to other value to suit your case
root.after(30, move_snake)
# initial directions for x and y
dx = 0
dy = 0
def change_direction(event):
global dx, dy
if event.char in 'wasd':
dx = dy = 0
if event.char == 'w':
dy = -1 # move up
elif event.char == 'a':
dx = -1 # move left
elif event.char == 's':
dy = 1 # move down
elif event.char == 'd':
dx = 1 # move right
root.bind("<Key>", change_direction)
move_snake() # start the moving loop
root.mainloop()
Instead of incrementing and decrementing the x,y, have a default value, which you change as per the direction -
from tkinter import *
root = Tk()
root.title('Snake')
root["width"] = 400
root["height"] = 400
field = Canvas(root)
rectangle = field.create_rectangle(10, 20, 30, 40)
field.grid(row=0, column=0)
x = 0 # Default
y = 0
speed = 10 # Set your speed. The lesser the value, the more fast
def pos(event):
global x, y
x = 0
y = 0
if event.char == 'w':
y = -1 # Changes the default values
elif event.char == 'a':
x = -1
elif event.char == 's':
y = 1
elif event.char == 'd':
x = 1
def snake():
field.move(rectangle, x, y)
root.after(speed, snake)
root.bind("<Key>", pos)
snake()
root.mainloop()
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()
I recently tried making a submarine game and made about 132 lines of code. I run the code and pressed the specified keys to move the submarine. Errors came up in the Shell that event doesn't have attribute keysm. Could anyone tell what I am supposed to replace event with or correct keysm?
Full Code:
from tkinter import *
from random import randint
from time import sleep, time
from math import sqrt
HEIGHT = 500
WIDTH = 800
window = Tk()
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 10
GAP = 100
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
SHIP_SPD = 10
BUB_CHANCE = 10
time_text = c.create_text(50, 50, fill='white')
score_text = c.create_text(150, 50, fill='white')
TIME_LIMIT = 30
BONUS_SCORE = 1000
score = 0
bonus = 0
end = time() + TIME_LIMIT
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
def move_ship(event):
if event.keysm == 'Up':
c.move(ship_id, 0, -SHIP_SPD)
c.move(ship_id2, 0, -SHIP_SPD)
elif event.keysm == 'Down':
c.move(ship_id, 0, SHIP_SPD)
c.move(ship_id2, 0, SHIP_SPD)
elif event.keysm == 'Left':
c.move(ship_id2, -SHIP_SPD, 0)
c.move(ship_id2, -SHIP_SPD, 0)
elif event.keysm == 'Right':
c.move(ship_id, SHIP_SPD, 0)
c.move(ship_id2, SHIP_SPD, 0)
c.bind_all('<Key>', move_ship)
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)
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 distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2 - x1)**2 + (y2 - y1)**2)
def del_bubble(i):
del bub_r[i]
del bub_speed[i]
c.delete(bub_id[i])
del bub_id[i]
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
def show_score(score):
c.itemconfig(score_text, text=str(score))
def show_time(time_left):
c.itemconfig(time_text, text=str(time_left))
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)
c.create_text(50, 30, text='TIME', fill='white')
c.create_text(150, 30, text='SCORE', fill='white')
#MAIN GAME LOOP
while time() < end:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end - time()))
window.update()
sleep(0.01)