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

Why didn't this character "real die" when they blew their stack out in Altered Carbon?

Error "illegal generic type for instanceof" when using local classes

Coloring maths inside a tcolorbox

Why is my conclusion inconsistent with the van't Hoff equation?

Storing hydrofluoric acid before the invention of plastics

String `!23` is replaced with `docker` in command line

Why are there no cargo aircraft with "flying wing" design?

Is there a program I can run on the C64 to speed up booting of a game?

51k Euros annually for a family of 4 in Berlin: Is it enough?

Short Story with Cinderella as a Voo-doo Witch

A coin, having probability p of landing heads and probability of q=(1-p) of landing on heads.

What causes the vertical darker bands in my photo?

How to find all the available tools in macOS terminal?

How come Sam didn't become Lord of Horn Hill?

How can I make names more distinctive without making them longer?

In predicate logic, does existential quantification (∃) include universal quantification (∀), i.e. can 'some' imply 'all'?

How widely used is the term Treppenwitz? Is it something that most Germans know?

What is Arya's weapon design?

Output the ŋarâþ crîþ alphabet song without using (m)any letters

Resolving to minmaj7

How to deal with a team lead who never gives me credit?

The logistics of corpse disposal

What does this icon in iOS Stardew Valley mean?

Why aren't air breathing engines used as small first stages



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 2 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

          名間水力發電廠 目录 沿革 設施 鄰近設施 註釋 外部連結 导航菜单23°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.7113923°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.71139計畫概要原始内容臺灣第一座BOT 模式開發的水力發電廠-名間水力電廠名間水力發電廠 水利署首件BOT案原始内容《小檔案》名間電廠 首座BOT水力發電廠原始内容名間電廠BOT - 經濟部水利署中區水資源局

          Prove that NP is closed under karp reduction?Space(n) not closed under Karp reductions - what about NTime(n)?Class P is closed under rotation?Prove or disprove that $NL$ is closed under polynomial many-one reductions$mathbfNC_2$ is closed under log-space reductionOn Karp reductionwhen can I know if a class (complexity) is closed under reduction (cook/karp)Check if class $PSPACE$ is closed under polyonomially space reductionIs NPSPACE also closed under polynomial-time reduction and under log-space reduction?Prove PSPACE is closed under complement?Prove PSPACE is closed under union?

          Is my guitar’s action too high? Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern)Strings too stiff on a recently purchased acoustic guitar | Cort AD880CEIs the action of my guitar really high?Μy little finger is too weak to play guitarWith guitar, how long should I give my fingers to strengthen / callous?When playing a fret the guitar sounds mutedPlaying (Barre) chords up the guitar neckI think my guitar strings are wound too tight and I can't play barre chordsF barre chord on an SG guitarHow to find to the right strings of a barre chord by feel?High action on higher fret on my steel acoustic guitar