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;
}







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()








share









$endgroup$



















    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()








    share









    $endgroup$















      0












      0








      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()








      share









      $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





      share












      share










      share



      share










      asked 6 mins ago









      alec_aalec_a

      1976




      1976






















          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
          });


          }
          });














          draft saved

          draft discarded


















          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
















          draft saved

          draft discarded




















































          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          is 'sed' thread safeWhat should someone know about using Python scripts in the shell?Nexenta bash script uses...

          How do i solve the “ No module named 'mlxtend' ” issue on Jupyter?

          Pilgersdorf Inhaltsverzeichnis Geografie | Geschichte | Bevölkerungsentwicklung | Politik | Kultur...