Golf Physics “Game” Announcing the arrival of Valued Associate #679: Cesar Manara ...
prime numbers and expressing non-prime numbers
If a contract sometimes uses the wrong name, is it still valid?
Is it ethical to give a final exam after the professor has quit before teaching the remaining chapters of the course?
How would the world control an invulnerable immortal mass murderer?
List *all* the tuples!
Is it true that "carbohydrates are of no use for the basal metabolic need"?
ListPlot join points by nearest neighbor rather than order
Sci-Fi book where patients in a coma ward all live in a subconscious world linked together
What's the meaning of 間時肆拾貳 at a car parking sign
Fundamental Solution of the Pell Equation
When do you get frequent flier miles - when you buy, or when you fly?
51k Euros annually for a family of 4 in Berlin: Is it enough?
What is Arya's weapon design?
Extract all GPU name, model and GPU ram
Should I discuss the type of campaign with my players?
Check which numbers satisfy the condition [A*B*C = A! + B! + C!]
What is the role of the transistor and diode in a soft start circuit?
Should I use a zero-interest credit card for a large one-time purchase?
What LEGO pieces have "real-world" functionality?
Why was the term "discrete" used in discrete logarithm?
English words in a non-english sci-fi novel
String `!23` is replaced with `docker` in command line
Do I really need recursive chmod to restrict access to a folder?
How can I make names more distinctive without making them longer?
Golf Physics “Game”
Announcing the arrival of Valued Associate #679: Cesar Manara
Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern)Golf game boilerplateA small Bejeweled-like game in PygameSimple top down shooter gameSnake game in PygamePython/Pygame Fighting GameSimon memory game in pygameMouse Click Controlled Meteor Avoidance GameSimple Python Pygame GameFirst Pong gamePython Pygame treasure hunt gameGolf game boilerplate
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
$begingroup$
Continuation of this post
I wrote a program in pygame that basically acts as a physics engine for a ball. You can hit the ball around and your strokes are counted, as well as an extra stroke for going out of bounds. Recently, I added a few things like air resistance, and rewrote my movement physics to make it easier to bounce. I'm wondering if the physics approach is good. Also, is there any way to convert everything into SI units? Right now, my air drag is an arbitrary value.
import math
import pygame as pg
class Colors:
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GOLD = (255, 215, 0)
GRAY = (100, 100, 100)
NIGHT = (20, 24, 82)
DAY = (135, 206, 235)
MOON = (245, 243, 206)
SMOKE = (96, 96, 96)
class Constants:
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
WINDOW_COLOR = Colors.NIGHT
TICKRATE = 60
GAME_SPEED = .35
LINE_COLOR = Colors.GOLD
ALINE_COLOR = Colors.GOLD
X_BOUNDS_BARRIER = 1
Y_BOUNDS_BARRIER = 1
BOUNCE_FUZZ = 0
START_X = int(.5 * SCREEN_WIDTH)
START_Y = int(.99 * SCREEN_HEIGHT)
AIR_DRAG = .3
GRAVITY = 9.80665
class Fonts:
pg.font.init()
strokeFont = pg.font.SysFont("monospace", 50)
STROKECOLOR = Colors.YELLOW
powerFont = pg.font.SysFont("arial", 15, bold=True)
POWERCOLOR = Colors.GREEN
angleFont = pg.font.SysFont("arial", 15, bold=True)
ANGLECOLOR = Colors.GREEN
penaltyFont = pg.font.SysFont("georgia", 40, bold=True)
PENALTYCOLOR = Colors.RED
toggleBoundsFont = pg.font.SysFont("geneva", 20)
TOGGLEBOUNDSCOLOR = Colors.RED
resistMultiplierFont = pg.font.SysFont("courier new", 13)
RESISTMULTIPLIERCOLOR = Colors.RED
powerMultiplierFont = pg.font.SysFont("courier new", 13)
POWERMULTIPLIERCOLOR = Colors.RED
class Ball(object):
def __init__(self, x, y, dx = 0, dy = 0, bounce = .8, radius = 10, color=Colors.SMOKE, outlinecolor=Colors.RED, density=1):
self.color = color
self.outlinecolor = outlinecolor
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.ax = 0
self.ay = Constants.GRAVITY
self.dt = Constants.GAME_SPEED
self.bounce = bounce
self.radius = radius
self.mass = 4/3 * math.pi * self.radius**3 * density
def show(self, window):
pg.draw.circle(window, self.outlinecolor, (int(self.x), int(self.y)), self.radius)
pg.draw.circle(window, self.color, (int(self.x), int(self.y)), self.radius - int(.4 * self.radius))
def update(self, update_frame):
update_frame += 1
self.vx += self.ax * self.dt
self.vy += self.ay * self.dt
if resist_multiplier:
drag = 6*math.pi * self.radius * resist_multiplier * Constants.AIR_DRAG
air_resist_x = -drag * self.vx / self.mass
air_resist_y = -drag * self.vy / self.mass
self.vx += air_resist_x/self.dt
self.vy += air_resist_y/self.dt
self.x += self.vx * self.dt
self.y += self.vy * self.dt
bounced, stop, shoot = False, False, True
# Top & Bottom
if self.y + self.radius > Constants.SCREEN_HEIGHT:
self.y = Constants.SCREEN_HEIGHT - self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
if self.y - self.radius < Constants.Y_BOUNDS_BARRIER:
self.y = Constants.Y_BOUNDS_BARRIER + self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
# Speed/Resistance Rectangles
if (self.x >= .875*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .88*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .87*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, -2 * abs(self.vx)
bounced = True
if (self.x <= .1175*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .118*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .119*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, 2 * abs(self.vx)
bounced = True
if x_bounded:
if (self.x - self.radius < Constants.X_BOUNDS_BARRIER):
self.x = Constants.X_BOUNDS_BARRIER + self.radius
self.vx = -self.vx
bounced = True
if (self.x + self.radius > Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER):
self.x = Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER - self.radius
self.vx = -self.vx
bounced = True
if self.vx > 1000:
self.vx = 1000
self.y = Constants.SCREEN_HEIGHT/4
if bounced:
self.vx *= self.bounce
self.vy *= self.bounce
print(f'n Update Frame: {update_frame}',
' x-pos: %spx' % round(self.x),
' y-pos: %spx' % round(self.y),
' x-vel: %spx/u' % round(self.vx),
' y-vel: %spx/u' % round(self.vy),
sep='n', end='nn')
return update_frame, shoot, stop
@staticmethod
def quadrant(x, y, xm, ym):
if ym < y and xm > x:
return 1
elif ym < y and xm < x:
return 2
elif ym > y and xm < x:
return 3
elif ym > y and xm > x:
return 4
else:
return False
def draw_window():
clock.tick(Constants.TICKRATE)
window.fill(Constants.WINDOW_COLOR)
resist_multiplier_text = 'Air Resistance: {:2.2f} m/s'.format(resist_multiplier)
resist_multiplier_label = Fonts.resistMultiplierFont.render(resist_multiplier_text, 1, Fonts.RESISTMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (.8875*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.8875*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(resist_multiplier_label, (.8925*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
power_multiplier_text = f'Swing Strength: {int(power_multiplier*100)}%'
power_multiplier_label = Fonts.powerMultiplierFont.render(power_multiplier_text, 1, Fonts.POWERMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (0, .98*Constants.SCREEN_HEIGHT, .1125*Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.1125*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (0, .975*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(power_multiplier_label, (.005*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
if not shoot:
pg.draw.arrow(window, Constants.ALINE_COLOR, Constants.ALINE_COLOR, aline[0], aline[1], 5)
pg.draw.arrow(window, Constants.LINE_COLOR, Constants.LINE_COLOR, line[0], line[1], 5)
stroke_text = 'Strokes: %s' % strokes
stroke_label = Fonts.strokeFont.render(stroke_text, 1, Fonts.STROKECOLOR)
if not strokes:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - .21 * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
else:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - (.21+.02*math.floor(math.log10(strokes))) * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
power_text = 'Shot Strength: %sN' % power_display
power_label = Fonts.powerFont.render(power_text, 1, Fonts.POWERCOLOR)
if not shoot: window.blit(power_label, (cursor_pos[0] + .008 * Constants.SCREEN_WIDTH, cursor_pos[1]))
angle_text = 'Angle: %s°' % angle_display
angle_label = Fonts.angleFont.render(angle_text, 1, Fonts.ANGLECOLOR)
if not shoot: window.blit(angle_label, (ball.x - .06 * Constants.SCREEN_WIDTH, ball.y - .01 * Constants.SCREEN_HEIGHT))
if penalty:
penalty_text = f'Out of Bounds! +1 Stroke'
penalty_label = Fonts.penaltyFont.render(penalty_text, 1, Fonts.PENALTYCOLOR)
penalty_rect = penalty_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .225*Constants.SCREEN_HEIGHT))
window.blit(penalty_label, penalty_rect)
toggle_bounds_text = "Use [b] to toggle bounds"
toggle_bounds_label = Fonts.toggleBoundsFont.render(toggle_bounds_text, 1, Fonts.TOGGLEBOUNDSCOLOR)
toggle_bounds_rect = toggle_bounds_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .275*Constants.SCREEN_HEIGHT))
window.blit(toggle_bounds_label, toggle_bounds_rect)
ball.show(window)
pg.display.flip()
def angle(cursor_pos):
x, y, xm, ym = ball.x, ball.y, cursor_pos[0], cursor_pos[1]
if x-xm:
angle = math.atan((y - ym) / (x - xm))
elif y > ym:
angle = math.pi/2
else:
angle = 3*math.pi/2
q = ball.quadrant(x,y,xm,ym)
if q: angle = math.pi*math.floor(q/2) - angle
if round(angle*deg) == 360:
angle = 0
if x > xm and not round(angle*deg):
angle = math.pi
return angle
def arrow(screen, lcolor, tricolor, start, end, trirad, thickness=2):
pg.draw.line(screen, lcolor, start, end, thickness)
rotation = (math.atan2(start[1] - end[1], end[0] - start[0])) + math.pi/2
pg.draw.polygon(screen, tricolor, ((end[0] + trirad * math.sin(rotation),
end[1] + trirad * math.cos(rotation)),
(end[0] + trirad * math.sin(rotation - 120*rad),
end[1] + trirad * math.cos(rotation - 120*rad)),
(end[0] + trirad * math.sin(rotation + 120*rad),
end[1] + trirad * math.cos(rotation + 120*rad))))
setattr(pg.draw, 'arrow', arrow)
def distance(x, y):
return math.sqrt(x**2 + y**2)
def update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded):
for event in pg.event.get():
if event.type == pg.QUIT:
quit = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
quit = True
if event.key == pg.K_RIGHT:
if rkey != max(resist_dict):
rkey += 1
if event.key == pg.K_LEFT:
if rkey != min(resist_dict):
rkey -= 1
if event.key == pg.K_UP:
if skey != max(strength_dict):
skey += 1
if event.key == pg.K_DOWN:
if skey != min(strength_dict):
skey -= 1
if event.key == pg.K_b:
x_bounded = not x_bounded
if event.key == pg.K_q:
rkey = min(resist_dict)
skey = max(strength_dict)
x_bounded = True
if event.key == pg.K_e:
rkey = max(resist_dict)
skey = max(strength_dict)
x_bounded = False
if event.type == pg.MOUSEBUTTONDOWN:
if not shoot:
shoot, stop = True, False
strokes, xb, yb = hit_ball(strokes)
return quit, rkey, skey, shoot, xb, yb, strokes, x_bounded
def hit_ball(strokes):
x, y = ball.x, ball.y
xb, yb = ball.x, ball.y
power = power_multiplier/4 * distance(line_ball_x, line_ball_y)
print('nnBall Hit!')
print('npower: %sN' % round(power, 2))
ang = angle(cursor_pos)
print('angle: %s°' % round(ang * deg, 2))
print('cos(a): %s' % round(math.cos(ang), 2)), print('sin(a): %s' % round(math.sin(ang), 2))
ball.vx, ball.vy = power * math.cos(ang), -power * math.sin(ang)
strokes += 1
return strokes, xb, yb
def initialize():
pg.init()
pg.display.set_caption('Golf')
window = pg.display.set_mode((Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.event.set_grab(True)
pg.mouse.set_cursor((8, 8), (0, 0), (0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0))
return window
rad, deg = math.pi/180, 180/math.pi
x, y, power, ang, strokes = [0]*5
xb, yb = None, None
shoot, penalty, stop, quit, x_bounded = [False]*5
p_ticks, update_frame = 0, 0
ball = Ball(Constants.START_X, Constants.START_Y)
clock = pg.time.Clock()
strength_dict = {0: .01, 1: .02, 2: .04, 3: .08, 4: .16, 5: .25, 6: .50, 7: .75, 8: 1}; skey = 6
resist_dict = {0: 0, 1: .01, 2: .02, 3: .03, 4: .04, 5: .05, 6: .1, 7: .2, 8: .3, 9: .4, 10: .5, 11: .6, 12: .7,
13: .8, 14: .9, 15: 1, 16: 1.25, 17: 1.5, 18: 1.75, 19: 2, 20: 2.5, 21: 3, 22: 3.5, 23: 4, 24: 4.5,
25: 5}; rkey = 7
if __name__ == '__main__':
window = initialize()
while not quit:
power_multiplier = strength_dict[skey]
resist_multiplier = resist_dict[rkey]
seconds = (pg.time.get_ticks()-p_ticks)/1000
if seconds > 1.2: penalty = False
cursor_pos = pg.mouse.get_pos()
line = [(ball.x, ball.y), cursor_pos]
line_ball_x, line_ball_y = cursor_pos[0] - ball.x, cursor_pos[1] - ball.y
aline = [(ball.x, ball.y), (ball.x + .015 * Constants.SCREEN_WIDTH, ball.y)]
if not shoot:
power_display = round(
distance(line_ball_x, line_ball_y) * power_multiplier/5)
angle_display = round(angle(cursor_pos) * deg)
else:
if stop or (abs(ball.vy) < 5 and abs(ball.vx) < 1 and abs(ball.y - (Constants.START_Y - 2)) <= Constants.BOUNCE_FUZZ):
shoot = False
#ball.y = Constants.START_Y
print('nThe ball has come to a rest!')
update_frame = 0
else:
update_frame, shoot, stop = ball.update(update_frame)
if not Constants.X_BOUNDS_BARRIER < ball.x < Constants.SCREEN_WIDTH:
shoot = False
print(f'nOut of Bounds! Pos: {round(ball.x), round(ball.y)}')
penalty = True
p_ticks = pg.time.get_ticks()
strokes += 1
if Constants.X_BOUNDS_BARRIER < xb < Constants.SCREEN_WIDTH:
ball.x = xb
else:
ball.x = Constants.START_X
ball.y = yb
quit, rkey, skey, shoot, xb, yb, strokes, x_bounded = update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded)
draw_window()
print("nShutting down...")
pg.quit()
python pygame
$endgroup$
add a comment |
$begingroup$
Continuation of this post
I wrote a program in pygame that basically acts as a physics engine for a ball. You can hit the ball around and your strokes are counted, as well as an extra stroke for going out of bounds. Recently, I added a few things like air resistance, and rewrote my movement physics to make it easier to bounce. I'm wondering if the physics approach is good. Also, is there any way to convert everything into SI units? Right now, my air drag is an arbitrary value.
import math
import pygame as pg
class Colors:
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GOLD = (255, 215, 0)
GRAY = (100, 100, 100)
NIGHT = (20, 24, 82)
DAY = (135, 206, 235)
MOON = (245, 243, 206)
SMOKE = (96, 96, 96)
class Constants:
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
WINDOW_COLOR = Colors.NIGHT
TICKRATE = 60
GAME_SPEED = .35
LINE_COLOR = Colors.GOLD
ALINE_COLOR = Colors.GOLD
X_BOUNDS_BARRIER = 1
Y_BOUNDS_BARRIER = 1
BOUNCE_FUZZ = 0
START_X = int(.5 * SCREEN_WIDTH)
START_Y = int(.99 * SCREEN_HEIGHT)
AIR_DRAG = .3
GRAVITY = 9.80665
class Fonts:
pg.font.init()
strokeFont = pg.font.SysFont("monospace", 50)
STROKECOLOR = Colors.YELLOW
powerFont = pg.font.SysFont("arial", 15, bold=True)
POWERCOLOR = Colors.GREEN
angleFont = pg.font.SysFont("arial", 15, bold=True)
ANGLECOLOR = Colors.GREEN
penaltyFont = pg.font.SysFont("georgia", 40, bold=True)
PENALTYCOLOR = Colors.RED
toggleBoundsFont = pg.font.SysFont("geneva", 20)
TOGGLEBOUNDSCOLOR = Colors.RED
resistMultiplierFont = pg.font.SysFont("courier new", 13)
RESISTMULTIPLIERCOLOR = Colors.RED
powerMultiplierFont = pg.font.SysFont("courier new", 13)
POWERMULTIPLIERCOLOR = Colors.RED
class Ball(object):
def __init__(self, x, y, dx = 0, dy = 0, bounce = .8, radius = 10, color=Colors.SMOKE, outlinecolor=Colors.RED, density=1):
self.color = color
self.outlinecolor = outlinecolor
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.ax = 0
self.ay = Constants.GRAVITY
self.dt = Constants.GAME_SPEED
self.bounce = bounce
self.radius = radius
self.mass = 4/3 * math.pi * self.radius**3 * density
def show(self, window):
pg.draw.circle(window, self.outlinecolor, (int(self.x), int(self.y)), self.radius)
pg.draw.circle(window, self.color, (int(self.x), int(self.y)), self.radius - int(.4 * self.radius))
def update(self, update_frame):
update_frame += 1
self.vx += self.ax * self.dt
self.vy += self.ay * self.dt
if resist_multiplier:
drag = 6*math.pi * self.radius * resist_multiplier * Constants.AIR_DRAG
air_resist_x = -drag * self.vx / self.mass
air_resist_y = -drag * self.vy / self.mass
self.vx += air_resist_x/self.dt
self.vy += air_resist_y/self.dt
self.x += self.vx * self.dt
self.y += self.vy * self.dt
bounced, stop, shoot = False, False, True
# Top & Bottom
if self.y + self.radius > Constants.SCREEN_HEIGHT:
self.y = Constants.SCREEN_HEIGHT - self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
if self.y - self.radius < Constants.Y_BOUNDS_BARRIER:
self.y = Constants.Y_BOUNDS_BARRIER + self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
# Speed/Resistance Rectangles
if (self.x >= .875*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .88*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .87*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, -2 * abs(self.vx)
bounced = True
if (self.x <= .1175*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .118*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .119*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, 2 * abs(self.vx)
bounced = True
if x_bounded:
if (self.x - self.radius < Constants.X_BOUNDS_BARRIER):
self.x = Constants.X_BOUNDS_BARRIER + self.radius
self.vx = -self.vx
bounced = True
if (self.x + self.radius > Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER):
self.x = Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER - self.radius
self.vx = -self.vx
bounced = True
if self.vx > 1000:
self.vx = 1000
self.y = Constants.SCREEN_HEIGHT/4
if bounced:
self.vx *= self.bounce
self.vy *= self.bounce
print(f'n Update Frame: {update_frame}',
' x-pos: %spx' % round(self.x),
' y-pos: %spx' % round(self.y),
' x-vel: %spx/u' % round(self.vx),
' y-vel: %spx/u' % round(self.vy),
sep='n', end='nn')
return update_frame, shoot, stop
@staticmethod
def quadrant(x, y, xm, ym):
if ym < y and xm > x:
return 1
elif ym < y and xm < x:
return 2
elif ym > y and xm < x:
return 3
elif ym > y and xm > x:
return 4
else:
return False
def draw_window():
clock.tick(Constants.TICKRATE)
window.fill(Constants.WINDOW_COLOR)
resist_multiplier_text = 'Air Resistance: {:2.2f} m/s'.format(resist_multiplier)
resist_multiplier_label = Fonts.resistMultiplierFont.render(resist_multiplier_text, 1, Fonts.RESISTMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (.8875*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.8875*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(resist_multiplier_label, (.8925*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
power_multiplier_text = f'Swing Strength: {int(power_multiplier*100)}%'
power_multiplier_label = Fonts.powerMultiplierFont.render(power_multiplier_text, 1, Fonts.POWERMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (0, .98*Constants.SCREEN_HEIGHT, .1125*Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.1125*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (0, .975*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(power_multiplier_label, (.005*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
if not shoot:
pg.draw.arrow(window, Constants.ALINE_COLOR, Constants.ALINE_COLOR, aline[0], aline[1], 5)
pg.draw.arrow(window, Constants.LINE_COLOR, Constants.LINE_COLOR, line[0], line[1], 5)
stroke_text = 'Strokes: %s' % strokes
stroke_label = Fonts.strokeFont.render(stroke_text, 1, Fonts.STROKECOLOR)
if not strokes:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - .21 * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
else:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - (.21+.02*math.floor(math.log10(strokes))) * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
power_text = 'Shot Strength: %sN' % power_display
power_label = Fonts.powerFont.render(power_text, 1, Fonts.POWERCOLOR)
if not shoot: window.blit(power_label, (cursor_pos[0] + .008 * Constants.SCREEN_WIDTH, cursor_pos[1]))
angle_text = 'Angle: %s°' % angle_display
angle_label = Fonts.angleFont.render(angle_text, 1, Fonts.ANGLECOLOR)
if not shoot: window.blit(angle_label, (ball.x - .06 * Constants.SCREEN_WIDTH, ball.y - .01 * Constants.SCREEN_HEIGHT))
if penalty:
penalty_text = f'Out of Bounds! +1 Stroke'
penalty_label = Fonts.penaltyFont.render(penalty_text, 1, Fonts.PENALTYCOLOR)
penalty_rect = penalty_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .225*Constants.SCREEN_HEIGHT))
window.blit(penalty_label, penalty_rect)
toggle_bounds_text = "Use [b] to toggle bounds"
toggle_bounds_label = Fonts.toggleBoundsFont.render(toggle_bounds_text, 1, Fonts.TOGGLEBOUNDSCOLOR)
toggle_bounds_rect = toggle_bounds_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .275*Constants.SCREEN_HEIGHT))
window.blit(toggle_bounds_label, toggle_bounds_rect)
ball.show(window)
pg.display.flip()
def angle(cursor_pos):
x, y, xm, ym = ball.x, ball.y, cursor_pos[0], cursor_pos[1]
if x-xm:
angle = math.atan((y - ym) / (x - xm))
elif y > ym:
angle = math.pi/2
else:
angle = 3*math.pi/2
q = ball.quadrant(x,y,xm,ym)
if q: angle = math.pi*math.floor(q/2) - angle
if round(angle*deg) == 360:
angle = 0
if x > xm and not round(angle*deg):
angle = math.pi
return angle
def arrow(screen, lcolor, tricolor, start, end, trirad, thickness=2):
pg.draw.line(screen, lcolor, start, end, thickness)
rotation = (math.atan2(start[1] - end[1], end[0] - start[0])) + math.pi/2
pg.draw.polygon(screen, tricolor, ((end[0] + trirad * math.sin(rotation),
end[1] + trirad * math.cos(rotation)),
(end[0] + trirad * math.sin(rotation - 120*rad),
end[1] + trirad * math.cos(rotation - 120*rad)),
(end[0] + trirad * math.sin(rotation + 120*rad),
end[1] + trirad * math.cos(rotation + 120*rad))))
setattr(pg.draw, 'arrow', arrow)
def distance(x, y):
return math.sqrt(x**2 + y**2)
def update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded):
for event in pg.event.get():
if event.type == pg.QUIT:
quit = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
quit = True
if event.key == pg.K_RIGHT:
if rkey != max(resist_dict):
rkey += 1
if event.key == pg.K_LEFT:
if rkey != min(resist_dict):
rkey -= 1
if event.key == pg.K_UP:
if skey != max(strength_dict):
skey += 1
if event.key == pg.K_DOWN:
if skey != min(strength_dict):
skey -= 1
if event.key == pg.K_b:
x_bounded = not x_bounded
if event.key == pg.K_q:
rkey = min(resist_dict)
skey = max(strength_dict)
x_bounded = True
if event.key == pg.K_e:
rkey = max(resist_dict)
skey = max(strength_dict)
x_bounded = False
if event.type == pg.MOUSEBUTTONDOWN:
if not shoot:
shoot, stop = True, False
strokes, xb, yb = hit_ball(strokes)
return quit, rkey, skey, shoot, xb, yb, strokes, x_bounded
def hit_ball(strokes):
x, y = ball.x, ball.y
xb, yb = ball.x, ball.y
power = power_multiplier/4 * distance(line_ball_x, line_ball_y)
print('nnBall Hit!')
print('npower: %sN' % round(power, 2))
ang = angle(cursor_pos)
print('angle: %s°' % round(ang * deg, 2))
print('cos(a): %s' % round(math.cos(ang), 2)), print('sin(a): %s' % round(math.sin(ang), 2))
ball.vx, ball.vy = power * math.cos(ang), -power * math.sin(ang)
strokes += 1
return strokes, xb, yb
def initialize():
pg.init()
pg.display.set_caption('Golf')
window = pg.display.set_mode((Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.event.set_grab(True)
pg.mouse.set_cursor((8, 8), (0, 0), (0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0))
return window
rad, deg = math.pi/180, 180/math.pi
x, y, power, ang, strokes = [0]*5
xb, yb = None, None
shoot, penalty, stop, quit, x_bounded = [False]*5
p_ticks, update_frame = 0, 0
ball = Ball(Constants.START_X, Constants.START_Y)
clock = pg.time.Clock()
strength_dict = {0: .01, 1: .02, 2: .04, 3: .08, 4: .16, 5: .25, 6: .50, 7: .75, 8: 1}; skey = 6
resist_dict = {0: 0, 1: .01, 2: .02, 3: .03, 4: .04, 5: .05, 6: .1, 7: .2, 8: .3, 9: .4, 10: .5, 11: .6, 12: .7,
13: .8, 14: .9, 15: 1, 16: 1.25, 17: 1.5, 18: 1.75, 19: 2, 20: 2.5, 21: 3, 22: 3.5, 23: 4, 24: 4.5,
25: 5}; rkey = 7
if __name__ == '__main__':
window = initialize()
while not quit:
power_multiplier = strength_dict[skey]
resist_multiplier = resist_dict[rkey]
seconds = (pg.time.get_ticks()-p_ticks)/1000
if seconds > 1.2: penalty = False
cursor_pos = pg.mouse.get_pos()
line = [(ball.x, ball.y), cursor_pos]
line_ball_x, line_ball_y = cursor_pos[0] - ball.x, cursor_pos[1] - ball.y
aline = [(ball.x, ball.y), (ball.x + .015 * Constants.SCREEN_WIDTH, ball.y)]
if not shoot:
power_display = round(
distance(line_ball_x, line_ball_y) * power_multiplier/5)
angle_display = round(angle(cursor_pos) * deg)
else:
if stop or (abs(ball.vy) < 5 and abs(ball.vx) < 1 and abs(ball.y - (Constants.START_Y - 2)) <= Constants.BOUNCE_FUZZ):
shoot = False
#ball.y = Constants.START_Y
print('nThe ball has come to a rest!')
update_frame = 0
else:
update_frame, shoot, stop = ball.update(update_frame)
if not Constants.X_BOUNDS_BARRIER < ball.x < Constants.SCREEN_WIDTH:
shoot = False
print(f'nOut of Bounds! Pos: {round(ball.x), round(ball.y)}')
penalty = True
p_ticks = pg.time.get_ticks()
strokes += 1
if Constants.X_BOUNDS_BARRIER < xb < Constants.SCREEN_WIDTH:
ball.x = xb
else:
ball.x = Constants.START_X
ball.y = yb
quit, rkey, skey, shoot, xb, yb, strokes, x_bounded = update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded)
draw_window()
print("nShutting down...")
pg.quit()
python pygame
$endgroup$
add a comment |
$begingroup$
Continuation of this post
I wrote a program in pygame that basically acts as a physics engine for a ball. You can hit the ball around and your strokes are counted, as well as an extra stroke for going out of bounds. Recently, I added a few things like air resistance, and rewrote my movement physics to make it easier to bounce. I'm wondering if the physics approach is good. Also, is there any way to convert everything into SI units? Right now, my air drag is an arbitrary value.
import math
import pygame as pg
class Colors:
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GOLD = (255, 215, 0)
GRAY = (100, 100, 100)
NIGHT = (20, 24, 82)
DAY = (135, 206, 235)
MOON = (245, 243, 206)
SMOKE = (96, 96, 96)
class Constants:
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
WINDOW_COLOR = Colors.NIGHT
TICKRATE = 60
GAME_SPEED = .35
LINE_COLOR = Colors.GOLD
ALINE_COLOR = Colors.GOLD
X_BOUNDS_BARRIER = 1
Y_BOUNDS_BARRIER = 1
BOUNCE_FUZZ = 0
START_X = int(.5 * SCREEN_WIDTH)
START_Y = int(.99 * SCREEN_HEIGHT)
AIR_DRAG = .3
GRAVITY = 9.80665
class Fonts:
pg.font.init()
strokeFont = pg.font.SysFont("monospace", 50)
STROKECOLOR = Colors.YELLOW
powerFont = pg.font.SysFont("arial", 15, bold=True)
POWERCOLOR = Colors.GREEN
angleFont = pg.font.SysFont("arial", 15, bold=True)
ANGLECOLOR = Colors.GREEN
penaltyFont = pg.font.SysFont("georgia", 40, bold=True)
PENALTYCOLOR = Colors.RED
toggleBoundsFont = pg.font.SysFont("geneva", 20)
TOGGLEBOUNDSCOLOR = Colors.RED
resistMultiplierFont = pg.font.SysFont("courier new", 13)
RESISTMULTIPLIERCOLOR = Colors.RED
powerMultiplierFont = pg.font.SysFont("courier new", 13)
POWERMULTIPLIERCOLOR = Colors.RED
class Ball(object):
def __init__(self, x, y, dx = 0, dy = 0, bounce = .8, radius = 10, color=Colors.SMOKE, outlinecolor=Colors.RED, density=1):
self.color = color
self.outlinecolor = outlinecolor
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.ax = 0
self.ay = Constants.GRAVITY
self.dt = Constants.GAME_SPEED
self.bounce = bounce
self.radius = radius
self.mass = 4/3 * math.pi * self.radius**3 * density
def show(self, window):
pg.draw.circle(window, self.outlinecolor, (int(self.x), int(self.y)), self.radius)
pg.draw.circle(window, self.color, (int(self.x), int(self.y)), self.radius - int(.4 * self.radius))
def update(self, update_frame):
update_frame += 1
self.vx += self.ax * self.dt
self.vy += self.ay * self.dt
if resist_multiplier:
drag = 6*math.pi * self.radius * resist_multiplier * Constants.AIR_DRAG
air_resist_x = -drag * self.vx / self.mass
air_resist_y = -drag * self.vy / self.mass
self.vx += air_resist_x/self.dt
self.vy += air_resist_y/self.dt
self.x += self.vx * self.dt
self.y += self.vy * self.dt
bounced, stop, shoot = False, False, True
# Top & Bottom
if self.y + self.radius > Constants.SCREEN_HEIGHT:
self.y = Constants.SCREEN_HEIGHT - self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
if self.y - self.radius < Constants.Y_BOUNDS_BARRIER:
self.y = Constants.Y_BOUNDS_BARRIER + self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
# Speed/Resistance Rectangles
if (self.x >= .875*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .88*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .87*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, -2 * abs(self.vx)
bounced = True
if (self.x <= .1175*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .118*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .119*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, 2 * abs(self.vx)
bounced = True
if x_bounded:
if (self.x - self.radius < Constants.X_BOUNDS_BARRIER):
self.x = Constants.X_BOUNDS_BARRIER + self.radius
self.vx = -self.vx
bounced = True
if (self.x + self.radius > Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER):
self.x = Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER - self.radius
self.vx = -self.vx
bounced = True
if self.vx > 1000:
self.vx = 1000
self.y = Constants.SCREEN_HEIGHT/4
if bounced:
self.vx *= self.bounce
self.vy *= self.bounce
print(f'n Update Frame: {update_frame}',
' x-pos: %spx' % round(self.x),
' y-pos: %spx' % round(self.y),
' x-vel: %spx/u' % round(self.vx),
' y-vel: %spx/u' % round(self.vy),
sep='n', end='nn')
return update_frame, shoot, stop
@staticmethod
def quadrant(x, y, xm, ym):
if ym < y and xm > x:
return 1
elif ym < y and xm < x:
return 2
elif ym > y and xm < x:
return 3
elif ym > y and xm > x:
return 4
else:
return False
def draw_window():
clock.tick(Constants.TICKRATE)
window.fill(Constants.WINDOW_COLOR)
resist_multiplier_text = 'Air Resistance: {:2.2f} m/s'.format(resist_multiplier)
resist_multiplier_label = Fonts.resistMultiplierFont.render(resist_multiplier_text, 1, Fonts.RESISTMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (.8875*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.8875*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(resist_multiplier_label, (.8925*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
power_multiplier_text = f'Swing Strength: {int(power_multiplier*100)}%'
power_multiplier_label = Fonts.powerMultiplierFont.render(power_multiplier_text, 1, Fonts.POWERMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (0, .98*Constants.SCREEN_HEIGHT, .1125*Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.1125*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (0, .975*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(power_multiplier_label, (.005*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
if not shoot:
pg.draw.arrow(window, Constants.ALINE_COLOR, Constants.ALINE_COLOR, aline[0], aline[1], 5)
pg.draw.arrow(window, Constants.LINE_COLOR, Constants.LINE_COLOR, line[0], line[1], 5)
stroke_text = 'Strokes: %s' % strokes
stroke_label = Fonts.strokeFont.render(stroke_text, 1, Fonts.STROKECOLOR)
if not strokes:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - .21 * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
else:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - (.21+.02*math.floor(math.log10(strokes))) * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
power_text = 'Shot Strength: %sN' % power_display
power_label = Fonts.powerFont.render(power_text, 1, Fonts.POWERCOLOR)
if not shoot: window.blit(power_label, (cursor_pos[0] + .008 * Constants.SCREEN_WIDTH, cursor_pos[1]))
angle_text = 'Angle: %s°' % angle_display
angle_label = Fonts.angleFont.render(angle_text, 1, Fonts.ANGLECOLOR)
if not shoot: window.blit(angle_label, (ball.x - .06 * Constants.SCREEN_WIDTH, ball.y - .01 * Constants.SCREEN_HEIGHT))
if penalty:
penalty_text = f'Out of Bounds! +1 Stroke'
penalty_label = Fonts.penaltyFont.render(penalty_text, 1, Fonts.PENALTYCOLOR)
penalty_rect = penalty_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .225*Constants.SCREEN_HEIGHT))
window.blit(penalty_label, penalty_rect)
toggle_bounds_text = "Use [b] to toggle bounds"
toggle_bounds_label = Fonts.toggleBoundsFont.render(toggle_bounds_text, 1, Fonts.TOGGLEBOUNDSCOLOR)
toggle_bounds_rect = toggle_bounds_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .275*Constants.SCREEN_HEIGHT))
window.blit(toggle_bounds_label, toggle_bounds_rect)
ball.show(window)
pg.display.flip()
def angle(cursor_pos):
x, y, xm, ym = ball.x, ball.y, cursor_pos[0], cursor_pos[1]
if x-xm:
angle = math.atan((y - ym) / (x - xm))
elif y > ym:
angle = math.pi/2
else:
angle = 3*math.pi/2
q = ball.quadrant(x,y,xm,ym)
if q: angle = math.pi*math.floor(q/2) - angle
if round(angle*deg) == 360:
angle = 0
if x > xm and not round(angle*deg):
angle = math.pi
return angle
def arrow(screen, lcolor, tricolor, start, end, trirad, thickness=2):
pg.draw.line(screen, lcolor, start, end, thickness)
rotation = (math.atan2(start[1] - end[1], end[0] - start[0])) + math.pi/2
pg.draw.polygon(screen, tricolor, ((end[0] + trirad * math.sin(rotation),
end[1] + trirad * math.cos(rotation)),
(end[0] + trirad * math.sin(rotation - 120*rad),
end[1] + trirad * math.cos(rotation - 120*rad)),
(end[0] + trirad * math.sin(rotation + 120*rad),
end[1] + trirad * math.cos(rotation + 120*rad))))
setattr(pg.draw, 'arrow', arrow)
def distance(x, y):
return math.sqrt(x**2 + y**2)
def update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded):
for event in pg.event.get():
if event.type == pg.QUIT:
quit = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
quit = True
if event.key == pg.K_RIGHT:
if rkey != max(resist_dict):
rkey += 1
if event.key == pg.K_LEFT:
if rkey != min(resist_dict):
rkey -= 1
if event.key == pg.K_UP:
if skey != max(strength_dict):
skey += 1
if event.key == pg.K_DOWN:
if skey != min(strength_dict):
skey -= 1
if event.key == pg.K_b:
x_bounded = not x_bounded
if event.key == pg.K_q:
rkey = min(resist_dict)
skey = max(strength_dict)
x_bounded = True
if event.key == pg.K_e:
rkey = max(resist_dict)
skey = max(strength_dict)
x_bounded = False
if event.type == pg.MOUSEBUTTONDOWN:
if not shoot:
shoot, stop = True, False
strokes, xb, yb = hit_ball(strokes)
return quit, rkey, skey, shoot, xb, yb, strokes, x_bounded
def hit_ball(strokes):
x, y = ball.x, ball.y
xb, yb = ball.x, ball.y
power = power_multiplier/4 * distance(line_ball_x, line_ball_y)
print('nnBall Hit!')
print('npower: %sN' % round(power, 2))
ang = angle(cursor_pos)
print('angle: %s°' % round(ang * deg, 2))
print('cos(a): %s' % round(math.cos(ang), 2)), print('sin(a): %s' % round(math.sin(ang), 2))
ball.vx, ball.vy = power * math.cos(ang), -power * math.sin(ang)
strokes += 1
return strokes, xb, yb
def initialize():
pg.init()
pg.display.set_caption('Golf')
window = pg.display.set_mode((Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.event.set_grab(True)
pg.mouse.set_cursor((8, 8), (0, 0), (0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0))
return window
rad, deg = math.pi/180, 180/math.pi
x, y, power, ang, strokes = [0]*5
xb, yb = None, None
shoot, penalty, stop, quit, x_bounded = [False]*5
p_ticks, update_frame = 0, 0
ball = Ball(Constants.START_X, Constants.START_Y)
clock = pg.time.Clock()
strength_dict = {0: .01, 1: .02, 2: .04, 3: .08, 4: .16, 5: .25, 6: .50, 7: .75, 8: 1}; skey = 6
resist_dict = {0: 0, 1: .01, 2: .02, 3: .03, 4: .04, 5: .05, 6: .1, 7: .2, 8: .3, 9: .4, 10: .5, 11: .6, 12: .7,
13: .8, 14: .9, 15: 1, 16: 1.25, 17: 1.5, 18: 1.75, 19: 2, 20: 2.5, 21: 3, 22: 3.5, 23: 4, 24: 4.5,
25: 5}; rkey = 7
if __name__ == '__main__':
window = initialize()
while not quit:
power_multiplier = strength_dict[skey]
resist_multiplier = resist_dict[rkey]
seconds = (pg.time.get_ticks()-p_ticks)/1000
if seconds > 1.2: penalty = False
cursor_pos = pg.mouse.get_pos()
line = [(ball.x, ball.y), cursor_pos]
line_ball_x, line_ball_y = cursor_pos[0] - ball.x, cursor_pos[1] - ball.y
aline = [(ball.x, ball.y), (ball.x + .015 * Constants.SCREEN_WIDTH, ball.y)]
if not shoot:
power_display = round(
distance(line_ball_x, line_ball_y) * power_multiplier/5)
angle_display = round(angle(cursor_pos) * deg)
else:
if stop or (abs(ball.vy) < 5 and abs(ball.vx) < 1 and abs(ball.y - (Constants.START_Y - 2)) <= Constants.BOUNCE_FUZZ):
shoot = False
#ball.y = Constants.START_Y
print('nThe ball has come to a rest!')
update_frame = 0
else:
update_frame, shoot, stop = ball.update(update_frame)
if not Constants.X_BOUNDS_BARRIER < ball.x < Constants.SCREEN_WIDTH:
shoot = False
print(f'nOut of Bounds! Pos: {round(ball.x), round(ball.y)}')
penalty = True
p_ticks = pg.time.get_ticks()
strokes += 1
if Constants.X_BOUNDS_BARRIER < xb < Constants.SCREEN_WIDTH:
ball.x = xb
else:
ball.x = Constants.START_X
ball.y = yb
quit, rkey, skey, shoot, xb, yb, strokes, x_bounded = update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded)
draw_window()
print("nShutting down...")
pg.quit()
python pygame
$endgroup$
Continuation of this post
I wrote a program in pygame that basically acts as a physics engine for a ball. You can hit the ball around and your strokes are counted, as well as an extra stroke for going out of bounds. Recently, I added a few things like air resistance, and rewrote my movement physics to make it easier to bounce. I'm wondering if the physics approach is good. Also, is there any way to convert everything into SI units? Right now, my air drag is an arbitrary value.
import math
import pygame as pg
class Colors:
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GOLD = (255, 215, 0)
GRAY = (100, 100, 100)
NIGHT = (20, 24, 82)
DAY = (135, 206, 235)
MOON = (245, 243, 206)
SMOKE = (96, 96, 96)
class Constants:
SCREEN_WIDTH = 1500
SCREEN_HEIGHT = 800
WINDOW_COLOR = Colors.NIGHT
TICKRATE = 60
GAME_SPEED = .35
LINE_COLOR = Colors.GOLD
ALINE_COLOR = Colors.GOLD
X_BOUNDS_BARRIER = 1
Y_BOUNDS_BARRIER = 1
BOUNCE_FUZZ = 0
START_X = int(.5 * SCREEN_WIDTH)
START_Y = int(.99 * SCREEN_HEIGHT)
AIR_DRAG = .3
GRAVITY = 9.80665
class Fonts:
pg.font.init()
strokeFont = pg.font.SysFont("monospace", 50)
STROKECOLOR = Colors.YELLOW
powerFont = pg.font.SysFont("arial", 15, bold=True)
POWERCOLOR = Colors.GREEN
angleFont = pg.font.SysFont("arial", 15, bold=True)
ANGLECOLOR = Colors.GREEN
penaltyFont = pg.font.SysFont("georgia", 40, bold=True)
PENALTYCOLOR = Colors.RED
toggleBoundsFont = pg.font.SysFont("geneva", 20)
TOGGLEBOUNDSCOLOR = Colors.RED
resistMultiplierFont = pg.font.SysFont("courier new", 13)
RESISTMULTIPLIERCOLOR = Colors.RED
powerMultiplierFont = pg.font.SysFont("courier new", 13)
POWERMULTIPLIERCOLOR = Colors.RED
class Ball(object):
def __init__(self, x, y, dx = 0, dy = 0, bounce = .8, radius = 10, color=Colors.SMOKE, outlinecolor=Colors.RED, density=1):
self.color = color
self.outlinecolor = outlinecolor
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.ax = 0
self.ay = Constants.GRAVITY
self.dt = Constants.GAME_SPEED
self.bounce = bounce
self.radius = radius
self.mass = 4/3 * math.pi * self.radius**3 * density
def show(self, window):
pg.draw.circle(window, self.outlinecolor, (int(self.x), int(self.y)), self.radius)
pg.draw.circle(window, self.color, (int(self.x), int(self.y)), self.radius - int(.4 * self.radius))
def update(self, update_frame):
update_frame += 1
self.vx += self.ax * self.dt
self.vy += self.ay * self.dt
if resist_multiplier:
drag = 6*math.pi * self.radius * resist_multiplier * Constants.AIR_DRAG
air_resist_x = -drag * self.vx / self.mass
air_resist_y = -drag * self.vy / self.mass
self.vx += air_resist_x/self.dt
self.vy += air_resist_y/self.dt
self.x += self.vx * self.dt
self.y += self.vy * self.dt
bounced, stop, shoot = False, False, True
# Top & Bottom
if self.y + self.radius > Constants.SCREEN_HEIGHT:
self.y = Constants.SCREEN_HEIGHT - self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
if self.y - self.radius < Constants.Y_BOUNDS_BARRIER:
self.y = Constants.Y_BOUNDS_BARRIER + self.radius
self.vy = -self.vy
bounced = True
print(' Bounce!')
# Speed/Resistance Rectangles
if (self.x >= .875*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .88*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .87*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, -2 * abs(self.vx)
bounced = True
if (self.x <= .1175*Constants.SCREEN_WIDTH + self.radius) and (self.y + self.radius >= .98*Constants.SCREEN_HEIGHT):
self.x = .118*Constants.SCREEN_WIDTH + self.radius
self.y = .98*Constants.SCREEN_HEIGHT - self.radius
self.x = .119*Constants.SCREEN_WIDTH + self.radius
self.vy, self.vx = -self.vy, 2 * abs(self.vx)
bounced = True
if x_bounded:
if (self.x - self.radius < Constants.X_BOUNDS_BARRIER):
self.x = Constants.X_BOUNDS_BARRIER + self.radius
self.vx = -self.vx
bounced = True
if (self.x + self.radius > Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER):
self.x = Constants.SCREEN_WIDTH - Constants.X_BOUNDS_BARRIER - self.radius
self.vx = -self.vx
bounced = True
if self.vx > 1000:
self.vx = 1000
self.y = Constants.SCREEN_HEIGHT/4
if bounced:
self.vx *= self.bounce
self.vy *= self.bounce
print(f'n Update Frame: {update_frame}',
' x-pos: %spx' % round(self.x),
' y-pos: %spx' % round(self.y),
' x-vel: %spx/u' % round(self.vx),
' y-vel: %spx/u' % round(self.vy),
sep='n', end='nn')
return update_frame, shoot, stop
@staticmethod
def quadrant(x, y, xm, ym):
if ym < y and xm > x:
return 1
elif ym < y and xm < x:
return 2
elif ym > y and xm < x:
return 3
elif ym > y and xm > x:
return 4
else:
return False
def draw_window():
clock.tick(Constants.TICKRATE)
window.fill(Constants.WINDOW_COLOR)
resist_multiplier_text = 'Air Resistance: {:2.2f} m/s'.format(resist_multiplier)
resist_multiplier_label = Fonts.resistMultiplierFont.render(resist_multiplier_text, 1, Fonts.RESISTMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (.8875*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT, Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.8875*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), (.88*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(resist_multiplier_label, (.8925*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
power_multiplier_text = f'Swing Strength: {int(power_multiplier*100)}%'
power_multiplier_label = Fonts.powerMultiplierFont.render(power_multiplier_text, 1, Fonts.POWERMULTIPLIERCOLOR)
pg.draw.rect(window, Colors.BLACK, (0, .98*Constants.SCREEN_HEIGHT, .1125*Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (.1125*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .99*Constants.SCREEN_HEIGHT), 3, 3)
pg.draw.arrow(window, Colors.MOON, Colors.GREEN, (0, .975*Constants.SCREEN_HEIGHT), (.12*Constants.SCREEN_WIDTH, .975*Constants.SCREEN_HEIGHT), 3)
window.blit(power_multiplier_label, (.005*Constants.SCREEN_WIDTH, .98*Constants.SCREEN_HEIGHT))
if not shoot:
pg.draw.arrow(window, Constants.ALINE_COLOR, Constants.ALINE_COLOR, aline[0], aline[1], 5)
pg.draw.arrow(window, Constants.LINE_COLOR, Constants.LINE_COLOR, line[0], line[1], 5)
stroke_text = 'Strokes: %s' % strokes
stroke_label = Fonts.strokeFont.render(stroke_text, 1, Fonts.STROKECOLOR)
if not strokes:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - .21 * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
else:
window.blit(stroke_label, (Constants.SCREEN_WIDTH - (.21+.02*math.floor(math.log10(strokes))) * Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT - .985 * Constants.SCREEN_HEIGHT))
power_text = 'Shot Strength: %sN' % power_display
power_label = Fonts.powerFont.render(power_text, 1, Fonts.POWERCOLOR)
if not shoot: window.blit(power_label, (cursor_pos[0] + .008 * Constants.SCREEN_WIDTH, cursor_pos[1]))
angle_text = 'Angle: %s°' % angle_display
angle_label = Fonts.angleFont.render(angle_text, 1, Fonts.ANGLECOLOR)
if not shoot: window.blit(angle_label, (ball.x - .06 * Constants.SCREEN_WIDTH, ball.y - .01 * Constants.SCREEN_HEIGHT))
if penalty:
penalty_text = f'Out of Bounds! +1 Stroke'
penalty_label = Fonts.penaltyFont.render(penalty_text, 1, Fonts.PENALTYCOLOR)
penalty_rect = penalty_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .225*Constants.SCREEN_HEIGHT))
window.blit(penalty_label, penalty_rect)
toggle_bounds_text = "Use [b] to toggle bounds"
toggle_bounds_label = Fonts.toggleBoundsFont.render(toggle_bounds_text, 1, Fonts.TOGGLEBOUNDSCOLOR)
toggle_bounds_rect = toggle_bounds_label.get_rect(center=(Constants.SCREEN_WIDTH/2, .275*Constants.SCREEN_HEIGHT))
window.blit(toggle_bounds_label, toggle_bounds_rect)
ball.show(window)
pg.display.flip()
def angle(cursor_pos):
x, y, xm, ym = ball.x, ball.y, cursor_pos[0], cursor_pos[1]
if x-xm:
angle = math.atan((y - ym) / (x - xm))
elif y > ym:
angle = math.pi/2
else:
angle = 3*math.pi/2
q = ball.quadrant(x,y,xm,ym)
if q: angle = math.pi*math.floor(q/2) - angle
if round(angle*deg) == 360:
angle = 0
if x > xm and not round(angle*deg):
angle = math.pi
return angle
def arrow(screen, lcolor, tricolor, start, end, trirad, thickness=2):
pg.draw.line(screen, lcolor, start, end, thickness)
rotation = (math.atan2(start[1] - end[1], end[0] - start[0])) + math.pi/2
pg.draw.polygon(screen, tricolor, ((end[0] + trirad * math.sin(rotation),
end[1] + trirad * math.cos(rotation)),
(end[0] + trirad * math.sin(rotation - 120*rad),
end[1] + trirad * math.cos(rotation - 120*rad)),
(end[0] + trirad * math.sin(rotation + 120*rad),
end[1] + trirad * math.cos(rotation + 120*rad))))
setattr(pg.draw, 'arrow', arrow)
def distance(x, y):
return math.sqrt(x**2 + y**2)
def update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded):
for event in pg.event.get():
if event.type == pg.QUIT:
quit = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
quit = True
if event.key == pg.K_RIGHT:
if rkey != max(resist_dict):
rkey += 1
if event.key == pg.K_LEFT:
if rkey != min(resist_dict):
rkey -= 1
if event.key == pg.K_UP:
if skey != max(strength_dict):
skey += 1
if event.key == pg.K_DOWN:
if skey != min(strength_dict):
skey -= 1
if event.key == pg.K_b:
x_bounded = not x_bounded
if event.key == pg.K_q:
rkey = min(resist_dict)
skey = max(strength_dict)
x_bounded = True
if event.key == pg.K_e:
rkey = max(resist_dict)
skey = max(strength_dict)
x_bounded = False
if event.type == pg.MOUSEBUTTONDOWN:
if not shoot:
shoot, stop = True, False
strokes, xb, yb = hit_ball(strokes)
return quit, rkey, skey, shoot, xb, yb, strokes, x_bounded
def hit_ball(strokes):
x, y = ball.x, ball.y
xb, yb = ball.x, ball.y
power = power_multiplier/4 * distance(line_ball_x, line_ball_y)
print('nnBall Hit!')
print('npower: %sN' % round(power, 2))
ang = angle(cursor_pos)
print('angle: %s°' % round(ang * deg, 2))
print('cos(a): %s' % round(math.cos(ang), 2)), print('sin(a): %s' % round(math.sin(ang), 2))
ball.vx, ball.vy = power * math.cos(ang), -power * math.sin(ang)
strokes += 1
return strokes, xb, yb
def initialize():
pg.init()
pg.display.set_caption('Golf')
window = pg.display.set_mode((Constants.SCREEN_WIDTH, Constants.SCREEN_HEIGHT))
pg.event.set_grab(True)
pg.mouse.set_cursor((8, 8), (0, 0), (0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0))
return window
rad, deg = math.pi/180, 180/math.pi
x, y, power, ang, strokes = [0]*5
xb, yb = None, None
shoot, penalty, stop, quit, x_bounded = [False]*5
p_ticks, update_frame = 0, 0
ball = Ball(Constants.START_X, Constants.START_Y)
clock = pg.time.Clock()
strength_dict = {0: .01, 1: .02, 2: .04, 3: .08, 4: .16, 5: .25, 6: .50, 7: .75, 8: 1}; skey = 6
resist_dict = {0: 0, 1: .01, 2: .02, 3: .03, 4: .04, 5: .05, 6: .1, 7: .2, 8: .3, 9: .4, 10: .5, 11: .6, 12: .7,
13: .8, 14: .9, 15: 1, 16: 1.25, 17: 1.5, 18: 1.75, 19: 2, 20: 2.5, 21: 3, 22: 3.5, 23: 4, 24: 4.5,
25: 5}; rkey = 7
if __name__ == '__main__':
window = initialize()
while not quit:
power_multiplier = strength_dict[skey]
resist_multiplier = resist_dict[rkey]
seconds = (pg.time.get_ticks()-p_ticks)/1000
if seconds > 1.2: penalty = False
cursor_pos = pg.mouse.get_pos()
line = [(ball.x, ball.y), cursor_pos]
line_ball_x, line_ball_y = cursor_pos[0] - ball.x, cursor_pos[1] - ball.y
aline = [(ball.x, ball.y), (ball.x + .015 * Constants.SCREEN_WIDTH, ball.y)]
if not shoot:
power_display = round(
distance(line_ball_x, line_ball_y) * power_multiplier/5)
angle_display = round(angle(cursor_pos) * deg)
else:
if stop or (abs(ball.vy) < 5 and abs(ball.vx) < 1 and abs(ball.y - (Constants.START_Y - 2)) <= Constants.BOUNCE_FUZZ):
shoot = False
#ball.y = Constants.START_Y
print('nThe ball has come to a rest!')
update_frame = 0
else:
update_frame, shoot, stop = ball.update(update_frame)
if not Constants.X_BOUNDS_BARRIER < ball.x < Constants.SCREEN_WIDTH:
shoot = False
print(f'nOut of Bounds! Pos: {round(ball.x), round(ball.y)}')
penalty = True
p_ticks = pg.time.get_ticks()
strokes += 1
if Constants.X_BOUNDS_BARRIER < xb < Constants.SCREEN_WIDTH:
ball.x = xb
else:
ball.x = Constants.START_X
ball.y = yb
quit, rkey, skey, shoot, xb, yb, strokes, x_bounded = update_values(quit, rkey, skey, shoot, xb, yb, strokes, x_bounded)
draw_window()
print("nShutting down...")
pg.quit()
python pygame
python pygame
asked 6 mins ago
alec_aalec_a
1976
1976
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f217588%2fgolf-physics-game%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f217588%2fgolf-physics-game%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown