Python: Making a projectile bounce on the GraphWin window along all walls? - python

Extend the projectile and tracker to implement the following graphics game:
a. There is square box of a given dimension (choose one reasonable size box) in which
there is a projectile which randomly bounces of the four walls.
b. When a projectile hits a wall, it is launched again by randomly selecting an angle in
the allowed range and a random velocity in a given range. For example, if the
projectile hits the bottom boundary, it selects a random angle in the range 0 to 180
degrees; if it hits the right vertical wall then, it selects random angle between 90 and
270 degrees; and so on.
c. Instead of drawing the circle, move the circle and also fill it with some color.
d. Extend to two projectiles.
Don't ask user the parameters. Use a set of parameters that you think the best.
So I am having trouble trying to find how to make two cballs (the projectiles) bounce from wall to wall. I figured how to bounce them off the bottom wall of the window but it doesn't stop at the right wall and bounce from there. Not only that I have no idea on how to make the balls follow instead of continuously printing circles to Track the movement of the balls. I use ".undraw()" but its very jumpy and not smooth at all.
I would appreciate the help very much! The assignment is due tomorrow and this is what i have:
# projectilebounce.py
# Projectile hits a wall and again is launced
from math import sin, cos, radians
from graphics import *
class Projectile:
def __init__(self, angle, velocity, height):
self.xpos = 0.0
self.ypos = height
theta = radians(angle)
self.xvel = velocity * cos(theta)
self.yvel = velocity * sin(theta)
def update(self, time):
self.xpos = self.xpos + time * self.xvel
yvel1 = self.yvel - 9.8 * time
self.ypos = self.ypos + time * (self.yvel + yvel1) / 2.0
self.yvel = yvel1
def getY(self):
return self.ypos
def getX(self):
return self.xpos
def reset(self, angle, velocity, height):
self.xpos = self.getX()
self.ypos = height
theta = radians(angle)
self.xvel = velocity * cos(theta)
self.yvel = velocity * sin(theta)
class Tracker:
def __init__(self, window, objToTrack):
self.win = window
self.obj = objToTrack
def update(self):
a = Circle( Point(self.obj.getX(), self.obj.getY()), 5)
a.draw(self.win)
a.setFill("Black")
a.undraw()
def getInputs():
a1 = 60
a2 = 30
v1 = 60
v2 = 60
h1 = 10
h2 = 10
t = 0.1
return a1, a2, v1, v2, h1, h2, t
def main():
print("This program graphically depicts the flight of a cannonball.\n")
win = GraphWin("TRACKER", 600, 500)
win.setCoords(0.0, 0.0, 600, 500)
angle1, angle2, vel1, vel2, h01, h02, time = getInputs()
cball1 = Projectile(angle1, vel1, h01)
cball2 = Projectile(angle2, vel2, h02)
tracker_cball1 = Tracker(win, cball1)
tracker_cball2 = Tracker(win, cball2)
while True:
if cball1.getY() <= 0: #<---ball 1
cball1.reset(60, 60, 10)
else:
cball1.update(time)
tracker_cball1.update()
if cball2.getY() <= 0: #<---ball 2
cball2.reset(30, 60, 10)
else:
cball2.update(time)
tracker_cball2.update()
if __name__ == '__main__' : main()
Thank you!, I am a big noob at coding at the moment. Much appreciated!

Related

How to stop flickering when using the clear() function in turtle graphics

I am writing a program for an exercise that simulates a projectile launch while tracking down every x and y position per second.
While the program technically works, and it does track down every x and y position, the text that tracks down the x and y position flickers for each step.
I understand the problem is that I am using the .clear() function (which, as its name implies, clears the text generated by the turtle). However, I believe that the program only functions properly with it.
Here is my code:
"""Program to track specific location of turtle (for every step)"""
from turtle import *
from math import *
def cball_graphics():
leonardo = Turtle()
leonardo.color("dark blue")
leonardo.shape("turtle")
leonardo.speed(1)
return leonardo
def show_position():
pos = Turtle()
pos.color("white")
pos.goto(30, -50)
pos.color("red")
return pos
class cannon_ball:
def __init__(self, angle, vel, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = vel * cos(theta)
self.y_vel = vel * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def get_x(self):
return self.x_pos
def get_y(self):
return self.y_pos
def variables():
angle = 55
vel = 10
height = 100
time = .01
return cannon_ball(angle, vel, height, time)
def main():
leonardo = cball_graphics()
"""pos is a variable that writes the position on the screen using x and y pos"""
pos = show_position()
pos.hideturtle()
projectile = variables()
while projectile.y_pos >= 0:
pos.write(f"{'%0.0f' % projectile.x_pos}, {'%0.0f' % projectile.y_pos}")
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
pos.clear()
main()
I believe that the tracer() and update() methods of the turtle screen will solve your problem. They allow you to write to the backing store and then copy it to the screen once it's the way you want. Particularly useful for eliminating flicker:
from turtle import Screen, Turtle
from math import pi, sin, cos
FONT = ('Arial', 18, 'normal')
def cball_graphics():
leonardo = Turtle()
leonardo.color('dark blue')
leonardo.shape('turtle')
return leonardo
def show_position():
pos = Turtle()
pos.hideturtle()
pos.goto(30, -50)
pos.color('red')
return pos
class cannon_ball:
def __init__(self, angle, velocity, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = velocity * cos(theta)
self.y_vel = velocity * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def freefall():
if projectile.y_pos >= 0:
pos.clear()
pos.write("({:0.0f}, {:0.0f})".format(projectile.x_pos, projectile.y_pos), font=FONT)
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
screen.update()
screen.ontimer(freefall)
variables = {
'angle': 55,
'velocity': 10,
'height': 100,
'time': 0.01,
}
screen = Screen()
screen.tracer(False)
leonardo = cball_graphics()
# pos is a turtle that writes the position on the screen using x and y pos
pos = show_position()
projectile = cannon_ball(**variables)
freefall()
screen.mainloop()
I also made several style changes to the code while I was at it...

Building Breakout Game (python) but I can't get bricks to delete_?

I am a new coder and I am building a "Breakout" game in python and i got most of it to except getting the bricks to delete. *What is odd, is after the ball touches the bottom wall (out of bounds) and regenerates, the bricks are then able to be deleted by playing the game! Also, I need to stop the game after 3 chances. I'm stuck, with those 2 problems. Help please. Here is my code:
import tkinter
import time
# How big is the playing area?
CANVAS_WIDTH = 600 # Width of drawing canvas in pixels
CANVAS_HEIGHT = 800 # Height of drawing canvas in pixels
# Constants for the bricks
N_ROWS = 8 # How many rows of bricks are there?
N_COLS = 10 # How many columns of bricks are there?
SPACING = 5 # How much space is there between each brick?
BRICK_START_Y = 50 # The y coordinate of the top-most brick
BRICK_HEIGHT = 20 # How many pixels high is each brick
BRICK_WIDTH = (CANVAS_WIDTH - (N_COLS + 1) * SPACING) // N_COLS
# Constants for the ball and paddle
BALL_SIZE = 70
PADDLE_Y = CANVAS_HEIGHT - 40
PADDLE_WIDTH = 200
def main():
canvas = make_canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Brick Breaker')
# Makes a ball
ball = canvas.create_oval(300, 300, 350, 350, fill="red", outline="red")
# Makes a paddle
paddle = canvas.create_rectangle(299, PADDLE_Y, PADDLE_WIDTH, CANVAS_HEIGHT - 20, fill="black")
# Change_X
dx = 6
# Change_Y
dy = 6
for row in range(N_ROWS):
# Draws columns of brick
for col in range(N_COLS):
draw_brick(canvas, row, col)
while True:
# Mouse location and respond to movement
mouse_x = canvas.winfo_pointerx()
# Move Paddle to X location
canvas.moveto(paddle, mouse_x, PADDLE_Y)
# Ball movement
canvas.move(ball, dx, dy)
# If ball hits left of right wall, change X location
if hit_left_wall(canvas, ball) or hit_right_wall(canvas, ball):
dx *= -1
# If ball hits top wall, then change Y location
elif hit_top_wall(canvas, ball):
dy *= -1
elif hit_brick(canvas, ball, paddle):
dy *= -1
if hit_bottom(canvas, ball):
canvas.delete(ball)
ball = make_ball(canvas)
# Recreates canvas
canvas.update()
# Pause time
time.sleep(1 / 50.)
canvas.mainloop()
# Finds coordinates of paddle
def hit_paddle(canvas, ball, paddle):
paddle_coords = canvas.coords(paddle)
x1 = paddle_coords[0]
y1 = paddle_coords[1]
x2 = paddle_coords[2]
y2 = paddle_coords[3]
# If any object begins to overlap with paddle, create a Hit
result = canvas.find_overlapping(x1, y1, x2, y2)
return len(result) > 1
def make_ball(canvas):
return canvas.create_oval(300, 300, 350, 350, fill="red", outline="red")
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
x_1 = ball_coord[0]
y_1 = ball_coord[1]
x_2 = ball_coord[2]
y_2 = ball_coord[3]
results = canvas.find_overlapping(x_1, y_1, x_2, y_2)
for object in results:
if object == paddle or object == ball:
return len(results) > 1
else:
canvas.delete(object)
def moveto(canvas, oval, x, y):
# Get current position
x0, y0, x1, y1 = canvas.coords(oval)
# Sets new position
canvas.move(oval, x - x0, y - y0)
def hit_bottom(canvas, ball):
return get_bottom_y(canvas, ball) >= CANVAS_HEIGHT
def hit_left_wall(canvas, ball):
return get_left_x(canvas, ball) <= 0
def hit_right_wall(canvas, ball):
return get_right_x(canvas, ball) >= CANVAS_WIDTH
def hit_top_wall(canvas, ball):
return get_top_y(canvas, ball) <= 0
def draw_brick(canvas, row, col):
x = col * (BRICK_WIDTH + SPACING)
y = row * (BRICK_HEIGHT + SPACING)
color = "blue"
canvas.create_rectangle(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, fill=color, outline=color)
def get_bottom_y(canvas, ball):
return canvas.coords(ball)[3]
def get_top_y(canvas, ball):
"""
This friendly method returns the y coordinate of the top of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 1 is the top-y
"""
return canvas.coords(ball)[1]
def get_left_x(canvas, ball):
"""
This friendly method returns the x coordinate of the left of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 0 is the left-x
"""
return canvas.coords(ball)[0]
def get_right_x(canvas, ball):
"""
This friendly method returns the x coordinate of the right of an object.
Recall that canvas.coords(object) returns a list of the object
bounding box: [x_1, y_1, x_2, y_2]. The element at index 2 is the right-x
"""
return canvas.coords(ball)[2]
def make_canvas(width, height, title):
"""
Creates and returns a drawing canvas
of the given int size with a blue border,
ready for drawing.
"""
top = tkinter.Tk()
top.minsize(width=width, height=height)
top.title(title)
canvas = tkinter.Canvas(top, width=width + 1, height=height + 1)
canvas.pack()
return canvas
if __name__ == '__main__':
main()
The first problem is due to the if statement in the for loop inside hit_brick():
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
x_1 = ball_coord[0]
y_1 = ball_coord[1]
x_2 = ball_coord[2]
y_2 = ball_coord[3]
results = canvas.find_overlapping(x_1, y_1, x_2, y_2)
for object in results:
if object == paddle or object == ball: # <-- problem here
return len(results) > 1
else:
canvas.delete(object)
As the values of ball and paddle are 1 and 2 (as they are the first two canvas items created) and so results is something like (1, N) when the ball hit one of the bricks.
So the if statement returns true for the first checking and then the function exits by the return statement.
Now let the ball hits the bottom and it will be recreated with id greater than existing canvas items. The results will be something like (N, ball) when the ball hits one of the bricks.
This time the if statement will return false and the brick is deleted.
So hit_brick() should be modified as below:
def hit_brick(canvas, ball, paddle):
ball_coord = canvas.coords(ball)
results = canvas.find_overlapping(*ball_coord)
for object in results:
if object not in (paddle, ball):
canvas.delete(object)
return len(results) > 1
For the second problem, you need to declare a variable, for example lives = 3, before the while loop and decrease it by one if the ball hits the bottom.
The while loop should be terminated if lives == 0:
def main():
...
lives = 3
while lives > 0:
...
if hit_bottom(canvas.ball):
...
lives -= 1

Python Turtle/Tkinter Timers Accelerating

I thought the canonical way to do animation with Python Turtle Graphics was to do something like
def animate():
# move stuff
ontimer(animate, delay)
Looking into the source code for turtle this implements tkinter after() in the background.
Can someone explain why in the program below the animation accelerates and decelerates dramatically when it is left running?
My theory is that since a new .after() id is created each time ontimer() is called, there are somehow multiple timers in existence which interfere with each other? Or maybe it's just a result of the randomness in the program? Or maybe the short interval between callbacks causes problems?
from random import *
from turtle import *
import math
class Vector(object):
def __init__(self, x = 0.0, y = 0.0):
self.x = x
self.y = y
def move(self, other):
""" Move vector by other (in-place)."""
self.__iadd__(other)
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
def rotate(self, angle):
"""Rotate vector counter-clockwise by angle (in-place)."""
radians = angle * math.pi / 180.0
cosine = math.cos(radians)
sine = math.sin(radians)
x = self.x
y = self.y
self.x = x * cosine - y * sine
self.y = y * cosine + x * sine
ant = Vector(0, 0)
aim = Vector(2, 0)
def wrap(value):
"Wrap value around -200 and 200."
if value > 200:
value = -200
elif value < -200:
value = 200
return value
def draw():
"Move ant and draw screen."
ant.move(aim)
ant.x = wrap(ant.x)
ant.y = wrap(ant.y)
aim.move(random() - 0.5)
aim.rotate(random() * 10 - 5)
clear()
goto(ant.x, ant.y)
dot(10)
if running:
ontimer(draw, 50)
setup(420, 420, 370, 0)
hideturtle()
tracer(False)
up()
running = True
draw()
done()
My belief is that your animation accelerates and decelerates because you use tracer() but fail to do an explicit update(). The tracer() function turns off animation but some turtle operations do an implicit update() as a side effect. Since you didn't do an explicit update() you're only getting random updates caused by those side effects.
Below I've added an explicit update() and simplified the code to make the turtle itself the moving object, rather than stamping and clearing. (BTW, if you save the result of stamp() you can ask it to clear itself.)
I've also switched from a circle to a turtle cursor image and added in logic to set the heading to the direction of motion:
from random import random
from turtle import Screen, Turtle, Vec2D
from math import pi, cos, sin
class Vector(object):
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
def move(self, other):
""" Move vector by other (in-place)."""
self.__iadd__(other)
self.wrap()
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
def rotate(self, degrees):
""" Rotate vector counter-clockwise by angle (in-place). """
radians = degrees * pi / 180.0
cosine = cos(radians)
sine = sin(radians)
x = self.x
y = self.y
self.x = x * cosine - y * sine
self.y = y * cosine + x * sine
def position(self):
return Vec2D(self.x, self.y)
def wrap(self):
""" Wrap value around -200 and 200. """
x = self.x
y = self.y
if x > 200:
self.x = -200
elif x < -200:
self.x = 200
if y > 200:
self.y = -200
elif y < -200:
self.y = 200
def draw():
""" Move ant and draw screen. """
ant.move(aim)
position = ant.position()
turtle.setheading(turtle.towards(position))
turtle.setposition(position)
screen.update()
aim.move(random() - 0.5)
aim.rotate(random() * 10 - 5)
screen.ontimer(draw, 75)
screen = Screen()
screen.setup(420, 420)
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.shape('turtle')
turtle.shapesize(0.5)
turtle.penup()
turtle.showturtle()
ant = Vector(0, 0)
aim = Vector(2, 0)
draw()
screen.mainloop()

Detecting if an arc has been clicked in pygame

I am currently trying to digitalize an boardgame I invented (repo: https://github.com/zutn/King_of_the_Hill). To make it work I need to check if one of the tiles (the arcs) on this board have been clicked. So far I have not been able to figure a way without giving up the pygame.arc function for drawing. If I use the x,y position of the position clicked, I can't figure a way out to determine the exact outline of the arc to compare to. I thought about using a color check, but this would only tell me if any of the tiles have been clicked. So is there a convenient way to test if an arc has been clicked in pygame or do I have to use sprites or something completely different? Additionally in a later step units will be included, that are located on the tiles. This would make the solution with the angle calculation postet below much more diffcult.
This is a simple arc class that will detect if a point is contained in the arc, but it will only work with circular arcs.
import pygame
from pygame.locals import *
import sys
from math import atan2, pi
class CircularArc:
def __init__(self, color, center, radius, start_angle, stop_angle, width=1):
self.color = color
self.x = center[0] # center x position
self.y = center[1] # center y position
self.rect = [self.x - radius, self.y - radius, radius*2, radius*2]
self.radius = radius
self.start_angle = start_angle
self.stop_angle = stop_angle
self.width = width
def draw(self, canvas):
pygame.draw.arc(canvas, self.color, self.rect, self.start_angle, self.stop_angle, self.width)
def contains(self, x, y):
dx = x - self.x # x distance
dy = y - self.y # y distance
greater_than_outside_radius = dx*dx + dy*dy >= self.radius*self.radius
less_than_inside_radius = dx*dx + dy*dy <= (self.radius- self.width)*(self.radius- self.width)
# Quickly check if the distance is within the right range
if greater_than_outside_radius or less_than_inside_radius:
return False
rads = atan2(-dy, dx) # Grab the angle
# convert the angle to match up with pygame format. Negative angles don't work with pygame.draw.arc
if rads < 0:
rads = 2 * pi + rads
# Check if the angle is within the arc start and stop angles
return self.start_angle <= rads <= self.stop_angle
Here's some example usage of the class. Using it requires a center point and radius instead of a rectangle for creating the arc.
pygame.init()
black = ( 0, 0, 0)
width = 800
height = 800
screen = pygame.display.set_mode((width, height))
distance = 100
tile_num = 4
ring_width = 20
arc = CircularArc((255, 255, 255), [width/2, height/2], 100, tile_num*(2*pi/7), (tile_num*(2*pi/7))+2*pi/7, int(ring_width*0.5))
while True:
fill_color = black
for event in pygame.event.get():
# quit if the quit button was pressed
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
x, y = pygame.mouse.get_pos()
# Change color when the mouse touches
if arc.contains(x, y):
fill_color = (200, 0, 0)
screen.fill(fill_color)
arc.draw(screen)
# screen.blit(debug, (0, 0))
pygame.display.update()

How to have an object run directly away from mouse at a constant speed

I have a ship that shoots lasers. It draws a line from itself to (mouseX, mouseY). How can I have the ship accelerate in the exact opposite direction as if being pushed backwards?
I have been working through some trig and ratios but am not making much progress. Acceleration is constant so how far the mouse is doesn't matter, just the direction.
I've been primarily working on trying to create a vector that only works with integers in the range (-4,4), as I cannot have the ship move across the screen at a Float speed, and the (-4,4) range would at least give me... 16 different directions for the ship to move, though I'd like that not to be a limitation.
There's no trigonometry involved, just vectors. If you take the subtract the position of the mouse from the position of the ship
(shipX-mouseX, shipY-mouseY)
that gives you the direction in which to move the ship. Multiply it by some factor, round it to integers, and add it to the ship's current position. You may want to do this a few times, to give some continuous motion. Also, you may want the factor to vary: increase the first few ticks, and then decrease to zero.
The vector of acceleration will be in the direction from the mouse cursor to the ship, away from the mouse position - so if the line from mouse to ship is at angle theta to horizontal (where anticlockwise theta is +ve, clockwise is -ve), so will the force F acting on the ship to accelerate it. If ship has mass M then using f=ma, acceleration A will be F/M. Acceleration in x axis will be A * cos(theta), in y A * sin(theta) - then use v=u+at after every time interval for your simulation. You will have to use floating point maths for this calculation.
Here's a full example using simple vector math. Note the comments.
import pygame
import math
# some simple vector helper functions, stolen from http://stackoverflow.com/a/4114962/142637
def magnitude(v):
return math.sqrt(sum(v[i]*v[i] for i in range(len(v))))
def add(u, v):
return [ u[i]+v[i] for i in range(len(u)) ]
def sub(u, v):
return [ u[i]-v[i] for i in range(len(u)) ]
def dot(u, v):
return sum(u[i]*v[i] for i in range(len(u)))
def normalize(v):
vmag = magnitude(v)
return [ v[i]/vmag for i in range(len(v)) ]
class Ship(object):
def __init__(self):
self.x, self.y = (0, 0)
self.set_target((0, 0))
self.org_speed = 0.9
self.speed = self.org_speed
self.laser = (None, 0)
#property
def pos(self):
return self.x, self.y
# for drawing, we need the position as tuple of ints
# so lets create a helper property
#property
def int_pos(self):
return map(int, self.pos)
#property
def target(self):
return self.t_x, self.t_y
#property
def int_target(self):
return map(int, self.target)
def set_target(self, pos):
self.t_x, self.t_y = pos
def update(self):
if self.speed < self.org_speed:
self.speed += min(self.org_speed, 0.3)
# if we won't move, don't calculate new vectors
if self.int_pos == self.int_target:
return
target_vector = sub(self.target, self.pos)
# a threshold to stop moving if the distance is to small.
# it prevents a 'flickering' between two points
if magnitude(target_vector) < 2:
return
# apply the ship's speed to the vector
move_vector = [c * self.speed for c in normalize(target_vector)]
# update position
self.x, self.y = add(self.pos, move_vector)
def draw(self, s):
pygame.draw.circle(s, (255, 0 ,0), self.int_pos, 5)
end, state = self.laser
if state > 0:
pygame.draw.line(s, (255, 255, 0 if state % 2 else 255), self.pos, end, 2)
self.laser = end, state - 1
def fire(self, pos):
self.speed = -min(5, magnitude(sub(pos, self.pos)) / 10.0)
self.laser = pos, 5
pygame.init()
quit = False
s = pygame.display.set_mode((300, 300))
c = pygame.time.Clock()
ship = Ship()
FIRE = pygame.USEREVENT
pygame.time.set_timer(FIRE, 2000)
while not quit:
quit = pygame.event.get(pygame.QUIT)
if pygame.event.get(FIRE):
ship.fire(pygame.mouse.get_pos())
ship.set_target(pygame.mouse.get_pos())
pygame.event.poll()
ship.update()
s.fill((0, 0, 0))
ship.draw(s)
pygame.display.flip()
c.tick(60)

Categories

Resources