Why does this collision not work properly? - python

I am coding a basic ping pong game in python.
I have this code:
from tkinter import *
from tkinter.ttk import *
from math import sqrt
import time, random
# creates tkinter window or root window
Window = Tk()
HEIGHT = 700
WIDTH = 700
c = Canvas(Window, width = WIDTH, height = HEIGHT)
c.pack()
c.configure(bg='black')
#Define mid x and y
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
#Create paddles and ball
paddle_1 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white')
paddle_2 = c.create_rectangle(0, 0, 100, 20, fill='grey', outline = 'white')
ball = c.create_oval(0, 0, 30, 30, fill='white', outline = 'grey')
c.move(paddle_1, 300, 0)
c.move(paddle_2, 300, 680)
c.move(ball, 330, 340)
id = [paddle_1, paddle_2, ball]
ball_move_x = 0
ball_move_y = 10
cooldown = 0
#get co-ordinates of object
def get_coords(i):
pos = c.coords(i)
x = int(pos[0] + pos[2]/2)
y = int(pos[1] + pos[3]/2)
return x, y
#Bounce (makes goofy physics)
def bounce(x, y):
x += random.randint(1,20)/10
x *= -1
y += 1
y *= -1
return x, y
#Collision code (uses co-ords and checks if within a certain range)
def collision(paddle_x, paddle_y, ball_x, ball_y, x = ball_move_x, y = ball_move_y,):
if(ball_x in range(paddle_x-50, paddle_x+50, 1) and ball_y in range(paddle_y-30, paddle_y+30, 1)):
x, y = bounce(ball_move_x, ball_move_y)
print("collision")
return x, y
else:
return x, y
# Optimised movement functions without cycling through with an if operator
def move_paddle_1_left(e):
c.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
c.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
c.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
c.move(paddle_2, +100, 0)
# bind functions to key
c.bind_all('<KeyPress-a>', move_paddle_1_left)
c.bind_all('<KeyPress-d>', move_paddle_1_right)
c.bind_all('<Left>', move_paddle_2_left)
c.bind_all('<Right>', move_paddle_2_right)
c.pack()
#MAIN GAME LOOP
while True:
Window.update()
paddle_1_x, paddle_1_y = get_coords(id[0])
paddle_2_x, paddle_2_y = get_coords(id[1])
ball_x, ball_y = get_coords(id[2])
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
c.move(ball, +ball_move_x, +ball_move_y)
time.sleep(0.0333333)
Window.update()
The problem is that the collision (so far, only bouncing off paddles is implemented) is buggy: it will essentially collide and collide infinitely.
My approach to collision detection and response is that I get the balls middle co-ords and the bat's middle co-ords; if the ball's co-ords are within a range of the bats co-ords I count it as a collision.
Since then I have been having an issue where essentially it will collide infinitely and just spam up and down. Why does this occur, and how can I fix it?

When you detect collision then you use x *= -1 to move in different direction (up) but in next moves you don't use -1 to keep the same direction and it move again down.
You should keep this value as global variable
direction_x = 1
direction_y = 1
and alwasy use
return x*direction_x, y*direction_y
And when you detect collision then change direction
direction_y = -direction_y
direction_x = 1
direction_y = 1
def collision(paddle_x, paddle_y, ball_x, ball_y, x = ball_move_x, y = ball_move_y,):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("collision")
return x*direction_x, y*direction_y
Full working code with other changes
EDIT:
You forgot () when you calculate center - you have to first add and later divide but without () it first divided and later added.
x = int((pos[0] + pos[2])/2)
y = int((pos[1] + pos[3])/2)
import tkinter as tk # PEP8: `import *` is not preferred
from math import sqrt
import time
import random
# --- constants ---
HEIGHT = 700
WIDTH = 700
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
BALL_MOVE_X = 0
BALL_MOVE_Y = 10
# --- functions ---
def get_coords(canvas, item):
pos = canvas.coords(item)
x = int( (pos[0] + pos[2]) / 2 )
y = int( (pos[1] + pos[3]) / 2 )
return x, y
def collision(paddle_x, paddle_y, ball_x, ball_y, x=BALL_MOVE_X, y=BALL_MOVE_Y):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("paddle collision")
return x*direction_x, y*direction_y
def move_paddle_1_left(e):
canvas.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
canvas.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
canvas.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
canvas.move(paddle_2, +100, 0)
def gameloop():
# MAIN GAME LOOP
paddle_1_x, paddle_1_y = get_coords(canvas, paddle_1)
paddle_2_x, paddle_2_y = get_coords(canvas, paddle_2)
ball_x, ball_y = get_coords(canvas, ball)
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
canvas.move(ball, ball_move_x, ball_move_y)
window.after(25, gameloop) # repeat after 25ms
# --- main ---
window = tk.Tk() # PEP8: `lower case name`
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='black') # PEP8: `=` without spaces inside `( )`
canvas.pack()
paddle_1 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
paddle_2 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
canvas.move(paddle_1, 300, 0)
canvas.move(paddle_2, 300, 680)
ball = canvas.create_oval(0, 0, 30, 30, fill='white', outline='grey')
canvas.move(ball, 330, 340)
ball_move_x = 0
ball_move_y = 10
direction_x = 1
direction_y = 1
cooldown = 0
canvas.bind_all('<KeyPress-a>', move_paddle_1_left)
canvas.bind_all('<KeyPress-d>', move_paddle_1_right)
canvas.bind_all('<Left>', move_paddle_2_left)
canvas.bind_all('<Right>', move_paddle_2_right)
window.after(25, gameloop) # 25ms = 0.025s = 40 Frames Per Second (FPS)
window.mainloop()
PEP 8 -- Style Guide for Python Code
EDIT:
Version which change direction when touch window's border
import tkinter as tk # PEP8: `import *` is not preferred
from math import sqrt
import time
import random
# --- constants ---
HEIGHT = 700
WIDTH = 700
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
BALL_MOVE_X = 5
BALL_MOVE_Y = 10
# --- functions ---
def get_coords(canvas, item):
pos = canvas.coords(item)
x = int((pos[0] + pos[2])/2)
y = int((pos[1] + pos[3])/2)
return x, y
def collision(paddle_x, paddle_y, ball_x, ball_y, x=BALL_MOVE_X, y=BALL_MOVE_Y):
global direction_x
global direction_y
if (paddle_x-50 <= ball_x <= paddle_x+50) and (paddle_y-30 <= ball_y <= paddle_y+30):
direction_y = - direction_y
print("paddle collision")
#return x, y
return x, y
def move_paddle_1_left(e):
canvas.move(paddle_1, -100, 0)
def move_paddle_1_right(e):
canvas.move(paddle_1, +100, 0)
def move_paddle_2_left(e):
canvas.move(paddle_2, -100, 0)
def move_paddle_2_right(e):
canvas.move(paddle_2, +100, 0)
def gameloop():
# MAIN GAME LOOP
global direction_x
global direction_y
paddle_1_x, paddle_1_y = get_coords(canvas, paddle_1)
paddle_2_x, paddle_2_y = get_coords(canvas, paddle_2)
ball_x, ball_y = get_coords(canvas, ball)
if ball_x <= 0 or ball_x >= WIDTH:
direction_x = - direction_x
if ball_y <= 0 or ball_y >= HEIGHT:
print("Get point")
direction_y = - direction_y
ball_move_x, ball_move_y = collision(paddle_1_x, paddle_1_y, ball_x, ball_y)
ball_move_x, ball_move_y = collision(paddle_2_x, paddle_2_y, ball_x, ball_y)
canvas.move(ball, ball_move_x*direction_x, ball_move_y*direction_y)
window.after(25, gameloop) # repeat after 25ms
# --- main ---
window = tk.Tk() # PEP8: `lower case name`
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='black') # PEP8: `=` without spaces inside `( )`
canvas.pack()
paddle_1 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
paddle_2 = canvas.create_rectangle(0, 0, 100, 20, fill='grey', outline='white')
canvas.move(paddle_1, 300, 0)
canvas.move(paddle_2, 300, 680)
ball = canvas.create_oval(0, 0, 30, 30, fill='white', outline='grey')
canvas.move(ball, 330, 340)
ball_move_x = 0
ball_move_y = 10
direction_x = 1
direction_y = 1
cooldown = 0
canvas.bind_all('<KeyPress-a>', move_paddle_1_left)
canvas.bind_all('<KeyPress-d>', move_paddle_1_right)
canvas.bind_all('<Left>', move_paddle_2_left)
canvas.bind_all('<Right>', move_paddle_2_right)
window.after(25, gameloop) # 25ms = 0.025s = 40 Frames Per Second (FPS)
window.mainloop()

Related

I'm making a snake game and keep getting this error _tkinter.TclError: bad geometry specifier "704x772+288.0+14.0" what should I do?

I'm coding a snake game in python and I'm at the point where the food should show up on the screen but I am getting this error:
return self.tk.call('wm', 'geometry', self._w, newGeometry)
_tkinter.TclError: bad geometry specifier "704x772+288.0+14.0"
import random
GAME_WIDTH = 700
GAME_HEIGHT = 700
SPEED = 50
SPACE_SIZE = 50
BODY_PARTS = 3
SNAKE_COLOUR = "#00FF00"
FOOD_COLOUR = "#FF0000"
BACKGROUND_COLOUR = "#000000"
class snake:
pass
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_COLOUR, tag="food")
def next_turn():
pass
def change_direction(new_direction):
pass
def check_collisions():
pass
def game_over():
pass
from tkinter import *
window = Tk()
window.title("Snake Game")
score = 0
direction = 'down'
label = Label(window, text="Score:{}".format(score), font=('consolas', 40))
label.pack()
canvas = Canvas(window, bg=BACKGROUND_COLOUR, height=GAME_HEIGHT, width=GAME_WIDTH)
canvas.pack()
window.update()
window_width: int = 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}")
snake = Snake()
food = Food()
window.mainloop()
Replace window_width/2 with something that produces an int, e.g. window_width//2. Do it likewise for window_height/2.
This will remove the offending .0 from the geometry.

How to limit the amount of times a button is pressed

I am trying to make a game where you can shoot bullets to kill emojis. However, i can't manage to figure out how to stop spamming the space key to shoot bullets. If you keep on spamming, the game would be too easy. I am not exactly sure if what command I should use. Please help! thanks!
Here is my code:
# import everything from turtle
from turtle import *
import random
import math
#create a link to the object (creates the environment)
screen = Screen()
speed1 = 1.3
ht()
amountOfEmojis = 11
#set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
#establish important data for screen environment
screen.setworldcoordinates(screenMinX,screenMinY,screenMaxX,screenMaxY)
screen.bgcolor("black")
#turtle setup
penup()
ht()
speed(0)
goto(0, screenMaxY - 50)
color('white')
write("Welcome to Emoji Run!", align="center", font=("Courier New",26))
goto(0, screenMaxY - 70)
write("Use the arrow keys to move and space to fire. The point of the game is to kill the emojis", align="center")
goto(0, 0)
color("red")
emojis = ["Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png", "1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png", "Emoji_Icon_-_Sunglasses_cool_emoji_large.png"]
class Bullet(Turtle):
#constructor, object for a class, pass in information
def __init__(self,screen,x,y,heading):
#create a bullet
Turtle.__init__(self)#clones bullet
self.speed(0)
self.penup()
self.goto(x,y)
self.seth(heading)#pointing to itself
self.screen = screen
self.color('yellow')
self.max_distance = 500
self.distance = 0
self.delta = 20
self.shape("bullet")
#logic to move bullet
def move(self):
self.distance = self.distance + self.delta#how fast it's going to move
self.forward(self.delta)
if self.done():
self.reset()
def getRadius(self):
return 4#collision detection helper function
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def done(self):
return self.distance >= self.max_distance # append to list
class Asteroid(Turtle):
def __init__(self,screen,dx,dy,x,y,size,emoji):#spawn asteroid randomly
Turtle.__init__(self)#clone itself
self.speed(0)
self.penup()
self.goto(x,y)
self.color('lightgrey')
self.size = size
self.screen = screen
self.dx = dx
self.dy = dy
r = random.randint(0, len(emoji) - 1)
screen.addshape(emojis[r])
self.shape(emojis[r])
#self.shape("rock" + str(size)) #sets size and shape for asteroid
def getSize(self):#part of collision detection
return self.size
#getter and setter functions
def getDX(self):
return self.dx
def getDY(self):
return self.dy
def setDX(self,dx):
self.dx = dx
def setDY(self,dy):
self.dy = dy
def move(self):
x = self.xcor()
y = self.ycor()
#if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
def __init__(self,screen,dx,dy,x,y):
Turtle.__init__(self)
self.speed(0)
self.penup()
self.color("white")
self.goto(x,y)
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
self.shape("turtle")
def move(self):
x = self.xcor()
y = self.ycor()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
#logic for collision
def powPow(self, asteroids):
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet):#counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
bullet.blowUp()
hit = True
if (not bullet.done() and not hit):
dasBullets.append(bullet)
self.bullets = dasBullets
def fireBullet(self):
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
def fireEngine(self):#how turtle moves
angle = self.heading()
x = math.cos(math.radians(angle))
y = math.sin(math.radians(angle))
self.dx = self.dx + x#how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
#extra function
def turnTowards(self,x,y):
if x < self.xcor():
self.left(7)
if x > self.xcor():
self.right(7)
def getRadius(self):
return 10
def getDX(self):
return self.dx
def getDY(self):
return self.dy
#collision detection
def intersect(object1,object2):
dist = math.sqrt((object1.xcor() - object2.xcor())**2 + (object1.ycor() - object2.ycor())**2)
radius1 = object1.getRadius()
radius2 = object2.getRadius()
# The following if statement could be written as
# return dist <= radius1+radius2
if dist <= radius1+radius2:
return True
else:
return False
#adds object to screen
screen.register_shape("rock3",((-20, -16),(-21, 0), (-20,18),(0,27),(17,15),(25,0),(16,-15),(0,-21)))
screen.register_shape("rock2",((-15, -10),(-16, 0), (-13,12),(0,19),(12,10),(20,0),(12,-10),(0,-13)))
screen.register_shape("rock1",((-10,-5),(-12,0),(-8,8),(0,13),(8,6),(14,0),(12,0),(8,-6),(0,-7)))
screen.register_shape("ship",((-10,-10),(0,-5),(10,-10),(0,10)))
screen.register_shape("bullet",((-2,-4),(-2,4),(2,4),(2,-4)))
#ship spawn exactly the middle everytime
ship = SpaceShip(screen,0,0,(screenMaxX-screenMinX)/2+screenMinX,(screenMaxY-screenMinY)/2 + screenMinY)
#randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx = random.random() * 6 - 3
dy = random.random() * 6 - 3
x = random.randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random.random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(screen,dx,dy,x,y,random.randint(1,3), emojis)
asteroids.append(asteroid)
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = random.randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship,asteroid):
write("You Got Killed :(",font=("Verdana",25),align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
color('green')
write("You Killed the Emojis!!",font=("Arial",30),align="center")
ht()
if not gameover:
screen.ontimer(play, 30)
bullets = []
#controls
def turnLeft():
ship.left(7)
def turnRight():
ship.right(7)
def go():
ship.fireEngine()
def fire():
ship.fireBullet()
ht()
screen.tracer(0);
screen.onkey(turnLeft, 'left')
screen.onkey(turnRight, 'right')
screen.onkey(go, 'up')
screen.onkey(fire, 'space')
screen.listen()
play()
You can use a threaded timer to prevent the method to be called everytime you click the button, and just two attributes in your SpaceShip class.
Everytime the method fireBullet is called, a check is made on the variable can_shoot. If it's true, the bullet is spawned like you did and then a timer runs (with a thread, for not blocking the main flow) that just put can_shoot to False, sleep for any amount of ms you want, and then put can_shoot to True and the method is callable again.
import time
import threading
def __init__(self):
# your stuff
self.wait_between_fire = 300 / 1000 # amount of ms / 1000 to convert in seconds
self.can_shoot = True
class TimerThread(threading.Thread):
def __init__(self, ref):
threading.Thread.__init__(self)
self.ref = ref
def run():
self.ref.can_shoot = False
time.sleep(ref.wait_between_fire)
self.ref.can_shoot = True
def set_timer(self):
TimerThread(self).start()
def fireBullet(self):
if self.can_shoot:
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
self.set_timer()
We don't need to introduce time nor threading to solve this. We can use turtle's own timer events to control the rate of fire:
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
Here I've limited the rate of fire to 4 rounds per second. (250 / 1000 milliseconds.) Adjust as you see fit. Below is a rework of your program with this modification as well as other fixes and style tweaks:
from turtle import Screen, Turtle
from random import random, randint, randrange, choice
from math import radians, sin as sine, cos as cosine
class Bullet(Turtle):
MAX_DISTANCE = 500
DELTA = 20
RADIUS = 4
def __init__(self, position, heading):
super().__init__(shape="bullet")
self.hideturtle()
self.penup()
self.goto(position)
self.setheading(heading)
self.color('yellow')
self.showturtle()
self.distance = 0
def move(self):
self.distance += self.DELTA
self.forward(self.DELTA)
if self.done():
self.reset()
def getRadius(self):
''' collision detection helper method '''
return self.RADIUS
def blowUp(self):
''' method that makes something go off the screen '''
self.hideturtle()
def done(self):
return self.distance >= self.MAX_DISTANCE
class Asteroid(Turtle):
def __init__(self, dx, dy, position, size, emoji):
super().__init__()
self.hideturtle()
self.penup()
self.goto(position)
self.color('lightgrey')
emoji = choice(emojis)
# screen.addshape(emoji) # for StackOverflow debugging
self.shape(emoji)
self.showturtle()
self.size = size
self.dx = dx
self.dy = dy
def move(self):
x, y = self.position()
# if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x, y)
def blowUp(self):
''' method that makes something go off the screen '''
self.hideturtle()
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
RADIUS = 10
def __init__(self, screen, dx, dy, x, y):
super().__init__(shape='turtle')
self.hideturtle()
self.penup()
self.color("white")
self.goto(x, y)
self.showturtle()
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
def move(self):
x, y = self.position()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x, y)
def powPow(self, asteroids):
''' logic for collision '''
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet): # counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
hit = True
if not bullet.done() and not hit:
dasBullets.append(bullet)
else:
bullet.blowUp()
self.bullets = dasBullets
def fireBullet(self):
bullet = Bullet(self.position(), self.heading())
self.bullets.append(bullet)
def fireEngine(self):
angle = self.heading() # how turtle moves
x = cosine(radians(angle))
y = sine(radians(angle))
self.dx = self.dx + x # how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
def getRadius(self):
return self.RADIUS
def turnLeft(self):
self.left(7)
def turnRight(self):
self.right(7)
def intersect(object1, object2):
''' collision detection '''
return object1.distance(object2) <= object1.getRadius() + object2.getRadius()
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship, asteroid):
turtle.write("You Got Killed :(", font=("Verdana", 25), align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
turtle.color('green')
turtle.write("You Killed the Emojis!!", font=("Arial", 30), align="center")
if not gameover:
screen.ontimer(play, 30)
# controls
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
# create a link to the object (creates the environment)
speed1 = 1.3
amountOfEmojis = 11
# establish important data for screen environment
screen = Screen()
screen.bgcolor("black")
# set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
screen.setworldcoordinates(screenMinX, screenMinY, screenMaxX, screenMaxY)
# adds object to screen
screen.register_shape("rock3", ((-20, -16), (-21, 0), (-20, 18), (0, 27), (17, 15), (25, 0), (16, -15), (0, -21)))
screen.register_shape("rock2", ((-15, -10), (-16, 0), (-13, 12), (0, 19), (12, 10), (20, 0), (12, -10), (0, -13)))
screen.register_shape("rock1", ((-10, -5), (-12, 0), (-8, 8), (0, 13), (8, 6), (14, 0), (12, 0), (8, -6), (0, -7)))
screen.register_shape("ship", ((-10, -10), (0, -5), (10, -10), (0, 10)))
screen.register_shape("bullet", ((-2, -4), (-2, 4), (2, 4), (2, -4)))
screen.tracer(0)
# turtle setup
turtle = Turtle()
turtle.hideturtle()
turtle.penup()
turtle.goto(0, screenMaxY - 50)
turtle.color('white')
turtle.write("Welcome to Emoji Run!", align="center", font=("Courier New", 26))
turtle.goto(0, screenMaxY - 70)
turtle.write("Use the arrow keys to move, and space to fire. The point of the game is to kill the emojis.", align="center", font=("Courier New", 13))
turtle.goto(0, 0)
turtle.color("red")
emojis = [
"Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png",
"1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png",
"Emoji_Icon_-_Sunglasses_cool_emoji_large.png"
]
emojis = ['rock1', 'rock2', 'rock3'] # for StackOverflow debugging purposes
# ship spawn exactly the middle everytime
ship = SpaceShip(screen, 0, 0, (screenMaxX - screenMinX)/2 + screenMinX, (screenMaxY - screenMinY)/2 + screenMinY)
# randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx, dy = random() * 6 - 3, random() * 6 - 3
x = randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(dx, dy, (x, y), randint(1, 3), emojis)
asteroids.append(asteroid)
screen.onkey(ship.turnLeft, 'Left')
screen.onkey(ship.turnRight, 'Right')
screen.onkey(ship.fireEngine, 'Up')
screen.onkey(fire, 'space')
screen.listen()
screen.update()
play()
screen.mainloop()
Something to consider is that turtles are global entities that don't get garbage collected. So, you might want to collect your spent bullets in a list to reuse, only creating new ones when you need them.

Tkinter Python OOP: Move seperate widgets at once with canvas.move()

I want to translate my procedural bouncing Ball programme to OOP in order to train OOP a bit.
I run into the problem that if I call a function on one instance of the object that contains an infinite loop, the next instance will never call its function. Resulting in only one of the balls moving.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
#Moving the Oval(ov1)
def move(self):
self.xd = random.randint(5,10)
self.yd = random.randint(5,10)
while True:
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
else:
self.yd = -(random.randint(5,10))
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
else:
self.xd = -(random.randint(5,10))
root.update()
time.sleep(.01)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
#Problem is that ov1.move() has a internal loop and ov2.move() will never be called
# ov1.move()
# ov2.move()
# ov3.move()
# ov4.move()
# ov5.move()
tk.mainloop()
Have solved it on my own.
I just took out the While True: loop from the class and called the funcion in a loop bellow.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
self.xd = random.randint(5,10)
# self.xd = 10
self.yd = random.randint(5,10)
# self.yd = 10
#Moving the Oval(ov1)
def move(self):
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
# self.yd = 10
else:
self.yd = -(random.randint(5,10))
# self.yd = -10
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
# self.xd = 10
else:
self.xd = -(random.randint(5,10))
# self.xd = -10
root.update()
# time.sleep(.000000001)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
while True:
ov1.move()
ov2.move()
ov3.move()
ov4.move()
ov5.move()
time.sleep(.01)
tk.mainloop()

Python Game: Control Square. If square reaches within 10 pixels of random spawned square it adds 1 point. Doesn't work. Why?

I made a game where you control a square and a random other square spawns randomly on the map. If the first square gets within 10 pixels of the other it adds a point but the program doesn't work. Can anyone tell me why?
Here is my code:
from tkinter import *
from random import uniform, randrange
tk = Tk()
canvas = Canvas(tk, width=400, height=400,bg='black')
canvas.pack()
pointcount = -1
LENGTH = 15
WIDTH = 15
LENGTH2 = randrange(1,390)
WIDTH2 = randrange(1,390)
LENGTH3 = LENGTH2 + 15
WIDTH3 = WIDTH2 + 15
X = randrange(1,400)
Y = randrange(1,400)
Snake = canvas.create_rectangle(0,0,WIDTH,LENGTH,fill="green")
Food = canvas.create_rectangle(WIDTH2,LENGTH2,WIDTH3,LENGTH3,fill="yellow")
pos = canvas.coords(Snake)
pos2 = canvas.coords(Food)
def movement_right(event):
canvas.move(Snake,15,0)
def movement_left(event):
canvas.move(Snake,-15,0)
def movement_down(event):
canvas.move(Snake,0,15)
def movement_up(event):
canvas.move(Snake,0,-15)
for i in range(1,20):
if pos[0] - pos2[2] == 10:
pointcount = pointcount + 1
print("Total points : ", pointcount)
tk.update()
tk.bind('<Left>', movement_left)
tk.bind('<Right>', movement_right)
tk.bind('<Down>', movement_down)
tk.bind('<Up>', movement_up)
tk.mainloop()
You have to check it inside functions because other code is executed only once.
You can create on function to test positions and execute in all movement_ functions.
I use "cells" to set positions
import tkinter as tk
import random
# --- constants --- (UPPER_CASE names)
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 400
CELL_WIDTH = 20
CELL_HEIGHT = 20
COLS = SCREEN_WIDTH/CELL_WIDTH
ROWS = SCREEN_HEIGHT/CELL_HEIGHT
# --- functions ---
def test():
global pointcount
snake_pos = canvas.coords(snake)
food_pos = canvas.coords(food)
# calculate distance
diff_x = abs(snake_pos[0] - food_pos[0])
diff_y = abs(snake_pos[1] - food_pos[1])
print(diff_x, diff_y)
# if snake eat food
if diff_x == 0 and diff_y == 0:
pointcount += 1
print("Total points : ", pointcount)
# move food to new place
food_x1 = CELL_WIDTH * random.randrange(0, COLS)
food_y1 = CELL_HEIGHT * random.randrange(0, ROWS)
food_x2 = food_x1 + CELL_WIDTH
food_y2 = food_y1 + CELL_HEIGHT
canvas.coords(food, (food_x1, food_y1, food_x2, food_y2))
def movement_right(event):
canvas.move(snake, CELL_WIDTH, 0)
test()
def movement_left(event):
canvas.move(snake, -CELL_WIDTH, 0)
test()
def movement_down(event):
canvas.move(snake, 0, CELL_HEIGHT)
test()
def movement_up(event):
canvas.move(snake, 0, -CELL_HEIGHT)
test()
# --- other --- (lower_case names)
pointcount = 0
# --- main ---
root = tk.Tk()
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='black')
canvas.pack()
root.bind('<Left>', movement_left)
root.bind('<Right>', movement_right)
root.bind('<Down>', movement_down)
root.bind('<Up>', movement_up)
snake = canvas.create_rectangle(0, 0, CELL_WIDTH, CELL_HEIGHT, fill="green")
# create food in first random place
food_x1 = CELL_WIDTH * random.randrange(0, COLS)
food_y1 = CELL_HEIGHT * random.randrange(0, ROWS)
food_x2 = food_x1 + CELL_WIDTH
food_y2 = food_y1 + CELL_HEIGHT
food = canvas.create_rectangle(food_x1, food_y1, food_x2, food_y2, fill="yellow")
root.mainloop()

appending code to create multiple random moving sprites - python tkinter

Below is the code I am trying to get working, i currently have it creating the sprites although once the second sprite is created the movement for the first is stopped.
Say there are 2 sprites both of them should be moving individually, completely random from each other.
The code creates a new sprite every 3 seconds(for testing purposes once the code is working it will be set to 10 seconds)
class enemymove(object):
def create():
global enemy, radiusenemy, xenemy, yenemy
amount = 0
enemy = list()
xenemy = list()
yenemy = list()
enemypositionx = list()
enemypositiony = list()
lastop = len(enemy)
enemy.append(amount)
xenemy.append(amount)
yenemy.append(amount)
enemypositionx.append(amount)
enemypositiony.append(amount)
amount = amount + 1
radiusenemy = 12.5
enemypositionx[lastop] = random.uniform(12.5, resX-12.5)
enemypositiony[lastop] = random.uniform(12.5, resY-12.5)
print(lastop)
enemy[lastop] = canvas.create_oval((enemypositionx[lastop]) + radiusenemy ,(enemypositiony[lastop]) - radiusenemy ,(enemypositionx[lastop]) - radiusenemy ,(enemypositiony[lastop]) + radiusenemy, fill="black", outline="black")
xenemy[lastop] = (canvas.coords(enemy[lastop])[2]) - 12.5
yenemy[lastop] = (canvas.coords(enemy[lastop])[3]) - 12.5
Thread(target = spawntime.timer).start()
enemymove.movement(lastop);
def movement(lastop):
global timer
timer = random.randint(150,3000)
count = random.randint(1, 4)
print(count)
if count == 1:
enemymove.up(lastop);
if count == 2:
enemymove.downward(lastop);
if count == 3:
enemymove.rightran(lastop);
if count == 4:
enemymove.leftran(lastop);
def up(lastop):
global xenemy, yenemy
print ("forward")
yenemy[lastop] = (canvas.coords(enemy[lastop])[1])
canvas.coords(enemy[lastop], xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy, xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def downward(lastop):
global xenemy, yenemy
print("back")
yenemy[lastop] = (canvas.coords(enemy[lastop])[3])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] + radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] - radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def rightran(lastop):
global xenemy, yenemy
print("right")
xenemy[lastop] = (canvas.coords(enemy[lastop])[2])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
def leftran(lastop):
global xenemy, yenemy
print("left")
xenemy[lastop] = (canvas.coords(enemy[lastop])[0])
canvas.coords(enemy[lastop], xenemy[lastop] - radiusenemy, yenemy[lastop] - radiusenemy, xenemy[lastop] + radiusenemy, yenemy[lastop] + radiusenemy)
print(yenemy)
print(xenemy)
canvas.after(timer, enemymove.movement, lastop)
class spawntime():
def timer():
global timeset
timeset = 3
spawntime.calculation()
def calculation():
global timeset
print ('The count is: ', timeset)
if timeset <= 0:
enemymove.create()
else:
timeset -= 1
canvas.after(1000, spawntime.calculation)
#runs the main code
def main():
global root, canvas
root.title("")
canvas = Canvas(root, width= resX, height=resY, bg = "white")
canvas.pack()
Thread(target = spawntime.timer).start()
root.mainloop()
main()
I'm reasonably new to appending, so making multiple different appended sprites move all together is out of my range, and so I am unsure of how to get this to work.
I don't understand your code so I create own version. Now every enemy moves after random time. After random time I add new enemy
I keep enemies on list but I don't need this list.
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemies(object):
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
# started amount of enemies
self.amount = 5
# list for all enemies
self.enemies = list()
# create enemies
for _ in range(self.amount):
self.create_one_enemy()
def create_one_enemy(self):
radius = 12.5 # random
x = random.uniform(radius, RES_X-radius)
y = random.uniform(radius, RES_Y-radius)
oval = self.canvas.create_oval(x-radius, y-radius, x+radius, y+radius, fill="black", outline="black")
# one enemy
enemy = [x, y, radius, oval]
# apped to list - but I don't need this list
self.enemies.append(enemy)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move_one_enemy, enemy)
def move_one_enemy(self, enemy):
#print('moving:', enemy)
# get old values
x, y, radius, oval = enemy
direction = random.randint(1,4)
if direction == 1: # up
y -= radius
elif direction == 2: # down
y += radius
elif direction == 3: # left
x -= radius
elif direction == 4: # right
x += radius
self.canvas.coords(oval, x-radius, y-radius, x+radius, y+radius)
# remember new values
enemy[0] = x
enemy[1] = y
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move_one_enemy, enemy)
# --- functions --- # lower_case name
def add_new_enemy():
enemies.create_one_enemy()
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# create enemies and move it using `root.after`
enemies = Enemies(canvas)
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
root.mainloop()
EDIT: but rather I would create class Enemy for single enemy and then list enemies to keep all Enemy instances.
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemy(object):
'''single enemy'''
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
self.radius = 12.5 # random
self.color = random.choice( ('black', 'red', 'green', 'blue', 'yellow') )
self.x = random.uniform(self.radius, RES_X-self.radius)
self.y = random.uniform(self.radius, RES_Y-self.radius)
self.x1 = self.x-self.radius
self.y1 = self.y-self.radius
self.x2 = self.x+self.radius
self.y2 = self.y+self.radius
self.oval = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=self.color, outline=self.color)
self.moving = True
self.start()
def start(self):
'''start moving'''
self.moving = True
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
def stop(self):
'''stop moving'''
self.moving = False
def move(self):
if self.moving: # to stop root.after
direction = random.randint(1,4)
if direction == 1: # up
self.y -= self.radius
self.y1 -= self.radius
self.y2 -= self.radius
elif direction == 2: # down
self.y += self.radius
self.y1 += self.radius
self.y2 += self.radius
elif direction == 3: # left
self.x -= self.radius
self.x1 -= self.radius
self.x2 -= self.radius
elif direction == 4: # right
self.x += self.radius
self.x1 += self.radius
self.x2 += self.radius
self.canvas.coords(self.oval, self.x1, self.y1, self.x2, self.y2)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
# --- functions --- # lower_case name
def add_new_enemy():
enemies.append(Enemy(canvas))
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# 5 enemies at the beginning
enemies = list()
for _ in range(5):
enemies.append(Enemy(canvas))
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
root.mainloop()
And now you can use list to stop/start enemies
for one_enemy in enemies:
one_enemy.stop()
or check some information
for one_enemy in enemies:
print("x:", one_enemy.x)
print("y:", one_enemy.y)
btw: and then you can create EnemiesGroup class
EDIT: EnemiesGroup and buttons to control group
import random
import tkinter as tk
# --- constants --- # UPPERCASE name
RES_X = 800
RES_Y = 600
# --- classes --- # CamelCase name
class Enemy(object):
'''single enemy'''
def __init__(self, canvas):
# access to canvas
self.canvas = canvas
self.radius = 12.5 # random
self.color = random.choice( ('black', 'red', 'green', 'blue', 'yellow') )
self.x = random.uniform(self.radius, RES_X-self.radius)
self.y = random.uniform(self.radius, RES_Y-self.radius)
self.x1 = self.x-self.radius
self.y1 = self.y-self.radius
self.x2 = self.x+self.radius
self.y2 = self.y+self.radius
self.oval = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=self.color, outline=self.color)
self.moving = True
self.start()
def start(self):
'''start moving'''
self.moving = True
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
def stop(self):
'''stop moving'''
self.moving = False
def move(self):
if self.moving: # to stop root.after
direction = random.randint(1,4)
if direction == 1: # up
self.y -= self.radius
self.y1 -= self.radius
self.y2 -= self.radius
elif direction == 2: # down
self.y += self.radius
self.y1 += self.radius
self.y2 += self.radius
elif direction == 3: # left
self.x -= self.radius
self.x1 -= self.radius
self.x2 -= self.radius
elif direction == 4: # right
self.x += self.radius
self.x1 += self.radius
self.x2 += self.radius
self.canvas.coords(self.oval, self.x1, self.y1, self.x2, self.y2)
# move this enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, self.move)
class EnemiesGroup(object):
def __init__(self, canvas):
self.canvas = canvas
self.enemies = list()
self.moving = True
def add_new_enemy(self):
# can be only 5 enemies
if len(self.enemies) < 5:
e = Enemy(self.canvas)
# stop new enemy if all enemies are stoped
e.moving = self.moving
self.enemies.append(e)
else:
print("You have 5 enemies - I can't add more.")
def stop_all_enemies(self):
for e in self.enemies:
e.stop()
# all enemies are stoped
self.moving = False
def start_all_enemies(self):
for e in self.enemies:
e.start()
# all enemies are moving
self.moving = True
# --- functions --- # lower_case name
def add_new_enemy():
enemies_group.add_new_enemy()
# add next enemy after random time
timer = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# --- main ---
root = tk.Tk()
root.title("")
canvas = tk.Canvas(root, width=RES_X, height=RES_Y, bg="white")
canvas.pack()
# enemies
enemies_group = EnemiesGroup(canvas)
for _ in range(5):
enemies_group.add_new_enemy()
# add new enemy after random time
random_time = random.randint(150, 3000)
root.after(random_time, add_new_enemy)
# buttons to control all enemies
button_stop = tk.Button(root, text='STOP', command=enemies_group.stop_all_enemies)
button_stop.pack()
button_start = tk.Button(root, text='START', command=enemies_group.start_all_enemies)
button_start.pack()
button_add = tk.Button(root, text='ADD NEW ENEMY', command=enemies_group.add_new_enemy)
button_add.pack()
root.mainloop()
EDIT: removing enemy by clicking oval.
In add_new_enemy I bind to oval event <Button-1> and function clicked (with enemy object).
Function clicked removes oval from canvas and removes enemy from enemies group.
class EnemiesGroup(object):
# ... other functions ...
def clicked(self, event, enemy):
print('clicked:', enemy),
# remove oval from canvas
self.canvas.delete(enemy.oval)
# remove enemy from list
self.enemies.remove(enemy)
# create new enemy after 10s
root.after(10000, self.add_new_enemy)
def add_new_enemy(self):
# can be only 5 enemies
if len(self.enemies) < 5:
print('create new enemy')
e = Enemy(self.canvas)
# stop new enemy if all enemies are stoped
e.moving = self.moving
# bind mouse button to enemy
self.canvas.tag_bind(e.oval, '<Button-1>', lambda event:self.clicked(event, e))
self.enemies.append(e)
else:
print("You have 5 enemies - I can't add more.")
# ... other functions ...

Categories