''' 8""""8 8"""88 8 8"""" 8 " 8 8 8 8 8e 8 8 8e 8eeee 88 ee 8 8 88 88 88 8 8 8 88 88 88eee8 8eeee8 88eee 88 GOLF Despite being a text based game, the code uses simple geometry to simulate a course. Fairways are 40 yard wide rectangles, surrounded by 5 yards of rough around the perimeter. The green is a circle of 10 yards radius around the cup. The cup is always at point (0,0). Using basic trigonometry we can plot the ball's location using the distance of the stroke and and the angle of deviation (hook/slice). The stroke distances are based on real world averages of different club types. Lots of randomization, "business rules", and luck influence the game play. Probabilities are commented in the code. note: 'courseInfo', 'clubs', & 'scoreCard' arrays each include an empty object so indexing can begin at 1. Like all good programmers we count from zero, but in this context, it's more natural when hole number one is at index one |-----------------------------| | rough | | ---------------------- | | | | | | r | = = | r | | o | = = | o | | u | = . = | u | | g | = green = | g | | h | = = | h | | | = = | | | | | | | | | | | | Fairway | | | | | | | | ------ | | | -- -- | | | -- hazard --| | | -- -- | | | ------ | | | | | | | | | out | | | | of | | | | bounds | | | | | | | | | tee | Typical green size: 20-30 yards Typical golf course fairways are 35 to 45 yards wide Our fairway extends 5 yards past green Our rough is a 5 yard perimeter around fairway We calculate the new position of the ball given the ball's point, the distance of the stroke, and degrees off line (hook or slice). Degrees off (for a right handed golfer): Slice: positive degrees = ball goes right Hook: negative degrees = left goes left The cup is always at point: 0,0. We use atan2 to compute the angle between the cup and the ball. Setting the cup's vector to 0,-1 on a 360 circle is equivalent to: 0 deg = 12 o'clock; 90 deg = 3 o'clock; 180 deg = 6 o'clock; 270 = 9 o'clock The reverse angle between the cup and the ball is a difference of PI (using radians). Given the angle and stroke distance (hypotenuse), we use cosine to compute the opposite and adjacent sides of the triangle, which, is the ball's new position. 0 | 270 - cup - 90 | 180 cup | | | opp |-----* new position | / | / adj | / | / hyp |/ tee <- hook slice -> Given the large number of combinations needed to describe a particular stroke / ball location, we use the technique of "bitwise masking" to describe stroke results. With bit masking, multiple flags (bits) are combined into a single binary number that can be tested by applying a mask. A mask is another binary number that isolates a particular bit that you are interested in. You can then apply your language's bitwise opeartors to test or set a flag. Game design by Jason Bonthron, 2021 www.bonthron.com for my father, Raymond Bonthron, an avid golfer Inspired by the 1978 "Golf" from "Basic Computer Games" by Steve North, who modified an existing golf game by an unknown author Ported in 2022 to Python by Martin Thoma ''' import enum import math import random import time from dataclasses import dataclass from functools import partial from typing import Any, Callable, List, NamedTuple, Tuple def clear_console() -> None: print("\033[H\033[J", end="") class Point(NamedTuple): x: int y: int class GameObjType(enum.Enum): BALL = enum.auto() CUP = enum.auto() GREEN = enum.auto() FAIRWAY = enum.auto() ROUGH = enum.auto() TREES = enum.auto() WATER = enum.auto() SAND = enum.auto() class CircleGameObj(NamedTuple): # center point X: int Y: int Radius: int Type: GameObjType class RectGameObj(NamedTuple): # Upper left corner X: int Y: int Width: int Length: int Type: GameObjType Ball = CircleGameObj Hazard = CircleGameObj class HoleInfo(NamedTuple): hole: int yards: int par: int hazards: List[Hazard] description: str class HoleGeometry(NamedTuple): cup: CircleGameObj green: CircleGameObj fairway: RectGameObj rough: RectGameObj hazards: List[Hazard] @dataclass class Plot: x: int y: int offline: int def get_distance(pt1: Point, pt2: Point) -> float: """distance between 2 points""" return math.sqrt(math.pow((pt2.x - pt1.x), 2) + math.pow((pt2.y - pt1.y), 2)) def is_in_rectangle(pt: CircleGameObj, rect: RectGameObj) -> bool: # only true if its completely inside return ( (pt.X > rect.X) and (pt.X < rect.X + rect.Width) and (pt.Y > rect.Y) and (pt.Y < rect.Y + rect.Length) ) def to_radians(angle: float) -> float: return angle * (math.pi / 180.0) def to_degrees_360(angle: float) -> float: """radians to 360 degrees""" deg = angle * (180.0 / math.pi) if deg < 0.0: deg += 360.0 return deg def odds(x: int) -> bool: # chance an integer is <= the given argument # between 1-100 return random.randint(1, 101) <= x # THE COURSE CourseInfo = [ HoleInfo(0, 0, 0, [], ""), # include a blank so index 1 == hole 1 # -------------------------------------------------------- front 9 HoleInfo( 1, 361, 4, [ Hazard(20, 100, 10, GameObjType.TREES), Hazard(-20, 80, 10, GameObjType.TREES), Hazard(-20, 100, 10, GameObjType.TREES), ], "There are a couple of trees on the left and right.", ), HoleInfo( 2, 389, 4, [Hazard(0, 160, 20, GameObjType.WATER)], "There is a large water hazard across the fairway about 150 yards.", ), HoleInfo( 3, 206, 3, [ Hazard(20, 20, 5, GameObjType.WATER), Hazard(-20, 160, 10, GameObjType.WATER), Hazard(10, 12, 5, GameObjType.SAND), ], "There is some sand and water near the green.", ), HoleInfo( 4, 500, 5, [Hazard(-14, 12, 12, GameObjType.SAND)], "There's a bunker to the left of the green.", ), HoleInfo( 5, 408, 4, [ Hazard(20, 120, 20, GameObjType.TREES), Hazard(20, 160, 20, GameObjType.TREES), Hazard(10, 20, 5, GameObjType.SAND), ], "There are some trees to your right.", ), HoleInfo( 6, 359, 4, [Hazard(14, 0, 4, GameObjType.SAND), Hazard(-14, 0, 4, GameObjType.SAND)], "", ), HoleInfo( 7, 424, 5, [ Hazard(20, 200, 10, GameObjType.SAND), Hazard(10, 180, 10, GameObjType.SAND), Hazard(20, 160, 10, GameObjType.SAND), ], "There are several sand traps along your right.", ), HoleInfo(8, 388, 4, [Hazard(-20, 340, 10, GameObjType.TREES)], ""), HoleInfo( 9, 196, 3, [Hazard(-30, 180, 20, GameObjType.TREES), Hazard(14, -8, 5, GameObjType.SAND)], "", ), # -------------------------------------------------------- back 9 HoleInfo( hole=10, yards=400, par=4, hazards=[ Hazard(-14, -8, 5, GameObjType.SAND), Hazard(14, -8, 5, GameObjType.SAND), ], description="", ), HoleInfo( 11, 560, 5, [ Hazard(-20, 400, 10, GameObjType.TREES), Hazard(-10, 380, 10, GameObjType.TREES), Hazard(-20, 260, 10, GameObjType.TREES), Hazard(-20, 200, 10, GameObjType.TREES), Hazard(-10, 180, 10, GameObjType.TREES), Hazard(-20, 160, 10, GameObjType.TREES), ], "Lots of trees along the left of the fairway.", ), HoleInfo( 12, 132, 3, [ Hazard(-10, 120, 10, GameObjType.WATER), Hazard(-5, 100, 10, GameObjType.SAND), ], "There is water and sand directly in front of you. A good drive should clear both.", ), HoleInfo( 13, 357, 4, [ Hazard(-20, 200, 10, GameObjType.TREES), Hazard(-10, 180, 10, GameObjType.TREES), Hazard(-20, 160, 10, GameObjType.TREES), Hazard(14, 12, 8, GameObjType.SAND), ], "", ), HoleInfo(14, 294, 4, [Hazard(0, 20, 10, GameObjType.SAND)], ""), HoleInfo( 15, 475, 5, [Hazard(-20, 20, 10, GameObjType.WATER), Hazard(10, 20, 10, GameObjType.SAND)], "Some sand and water near the green.", ), HoleInfo(16, 375, 4, [Hazard(-14, -8, 5, GameObjType.SAND)], ""), HoleInfo( 17, 180, 3, [ Hazard(20, 100, 10, GameObjType.TREES), Hazard(-20, 80, 10, GameObjType.TREES), ], "", ), HoleInfo( 18, 550, 5, [Hazard(20, 30, 15, GameObjType.WATER)], "There is a water hazard near the green.", ), ] # -------------------------------------------------------- bitwise Flags dub = 0b00000000000001 hook = 0b00000000000010 slice_ = 0b00000000000100 passed_cup = 0b00000000001000 in_cup = 0b00000000010000 on_fairway = 0b00000000100000 on_green = 0b00000001000000 in_rough = 0b00000010000000 in_sand = 0b00000100000000 in_trees = 0b00001000000000 in_water = 0b00010000000000 out_of_bounds = 0b00100000000000 luck = 0b01000000000000 ace = 0b10000000000000 class Golf: ball: Ball hole_num: int = 0 stroke_num: int = 0 handicap: int = 0 player_difficulty: int = 0 hole_geometry: HoleGeometry # all fairways are 40 yards wide, extend 5 yards beyond the cup, and # have 5 yards of rough around the perimeter fairway_width: int = 40 fairway_extension: int = 5 rough_amt: int = 5 # ScoreCard records the ball position after each stroke # a new list for each hole # include a blank list so index 1 == hole 1 score_card: List[List[Ball]] = [[]] # YOUR BAG clubs: List[Tuple[str, int]] = [ ("", 0), # name, average yardage ("Driver", 250), ("3 Wood", 225), ("5 Wood", 200), ("Hybrid", 190), ("4 Iron", 170), ("7 Iron", 150), ("9 Iron", 125), ("Pitching wedge", 110), ("Sand wedge", 75), ("Putter", 10), ] def __init__(self) -> None: print(" ") print(' 8""""8 8"""88 8 8"""" ') print(' 8 " 8 8 8 8 ') print(" 8e 8 8 8e 8eeee ") print(" 88 ee 8 8 88 88 ") print(" 88 8 8 8 88 88 ") print(" 88eee8 8eeee8 88eee 88 ") print(" ") print("Welcome to the Creative Computing Country Club,") print("an eighteen hole championship layout located a short") print("distance from scenic downtown Lambertville, New Jersey.") print("The game will be explained as you play.") print("Enjoy your game! See you at the 19th hole...") print(" ") print("Type QUIT at any time to leave the game.") print("Type BAG at any time to review the clubs in your bag.") print(" ") input("Press any key to continue.") clear_console() self.start_game() def start_game(self) -> None: print(" ") print(" YOUR BAG") self.review_bag() print("Type BAG at any time to review the clubs in your bag.") print(" ") input("Press any key to continue.") clear_console() self.ask_handicap() def ask_handicap(self) -> None: print(" ") self.ask( "PGA handicaps range from 0 to 30.\nWhat is your handicap?", 0, 30, self.set_handicap_ask_difficulty, ) def set_handicap_ask_difficulty(self, i: int) -> None: self.handicap = i print(" ") self.ask( ( "Common difficulties at golf include:\n" "1=Hook, 2=Slice, 3=Poor Distance, 4=Trap Shots, 5=Putting\n" "Which one is your worst?" ), 1, 5, self.set_difficulty_and_hole, ) def set_difficulty_and_hole(self, j: int) -> None: self.player_difficulty = j clear_console() self.new_hole() def new_hole(self) -> None: self.hole_num += 1 self.stroke_num = 0 info: HoleInfo = CourseInfo[self.hole_num] yards: int = info.yards # from tee to cup cup = CircleGameObj(0, 0, 0, GameObjType.CUP) green = CircleGameObj(0, 0, 10, GameObjType.GREEN) fairway = RectGameObj( 0 - int(self.fairway_width / 2), 0 - (green.Radius + self.fairway_extension), self.fairway_width, yards + (green.Radius + self.fairway_extension) + 1, GameObjType.FAIRWAY, ) rough = RectGameObj( fairway.X - self.rough_amt, fairway.Y - self.rough_amt, fairway.Width + (2 * self.rough_amt), fairway.Length + (2 * self.rough_amt), GameObjType.ROUGH, ) self.ball = Ball(0, yards, 0, GameObjType.BALL) self.score_card_start_new_hole() self.hole_geometry = HoleGeometry(cup, green, fairway, rough, info.hazards) print(f" |> {self.hole_num}") print(" | ") print(" | ") print(" ^^^^^^^^^^^^^^^") print( f"Hole #{self.hole_num}. You are at the tee. Distance {info.yards} yards, par {info.par}." ) print(info.description) self.tee_up() def set_putter_and_stroke(self, strength: float) -> None: putter = self.clubs[self.putt] self.stroke((putter[1] * (strength / 10.0)), self.putt) def ask_gauge(self, c: int) -> None: self.club = self.clubs[c] print(" ") print(f"[{self.club[0].upper()}: average {self.club[1]} yards]") foo = partial(self.make_stroke, c=c) self.ask( "Now gauge your distance by a percentage of a full swing. (1-10)", 1, 10, foo, ) def make_stroke(self, strength: float, c: int) -> None: self.stroke((self.club[1] * (strength / 10.0)), c) def tee_up(self) -> None: # on the green? automatically select putter # otherwise Ask club and swing strength if self.is_on_green(self.ball) and not self.is_in_hazard( self.ball, GameObjType.SAND ): self.putt = 10 print("[PUTTER: average 10 yards]") msg = "Keep your head down.\n" if odds(20) else "" self.ask( f"{msg}Choose your putt potency. (1-10)", 1, 10, self.set_putter_and_stroke, ) else: self.ask("What club do you choose? (1-10)", 1, 10, self.ask_gauge) def stroke(self, club_amt: float, club_index: int) -> None: self.stroke_num += 1 flags = 0b000000000000 # fore! only when driving if (self.stroke_num == 1) and (club_amt > 210) and odds(30): print('"...Fore !"') # dub if odds(5): # there's always a 5% chance of dubbing it flags |= dub # if you're in the rough, or sand, you really should be using a wedge if ( ( self.is_in_rough(self.ball) or self.is_in_hazard(self.ball, GameObjType.SAND) ) and club_index not in {8, 9} and odds(40) ): flags |= dub # trap difficulty if ( self.is_in_hazard(self.ball, GameObjType.SAND) and self.player_difficulty == 4 ) and odds(20): flags |= dub # hook/slice # There's 10% chance of a hook or slice # if it's a known player_difficulty then increase chance to 30% # if it's a putt & putting is a player_difficulty increase to 30% rand_hook_slice: bool if ( self.player_difficulty == 1 or self.player_difficulty == 2 or (self.player_difficulty == 5 and self.is_on_green(self.ball)) ): rand_hook_slice = odds(30) else: rand_hook_slice = odds(10) if rand_hook_slice: if self.player_difficulty == 1: if odds(80): flags |= hook else: flags |= slice_ elif self.player_difficulty == 2: if odds(80): flags |= slice_ else: flags |= hook elif odds(50): flags |= hook else: flags |= slice_ # beginner's luck ! # If handicap is greater than 15, there's a 10% chance of avoiding all errors if (self.handicap > 15) and (odds(10)): flags |= luck # ace # there's a 10% chance of an Ace on a par 3 if CourseInfo[self.hole_num].par == 3 and odds(10) and self.stroke_num == 1: flags |= ace # distance: # If handicap is < 15, there a 50% chance of reaching club average, # a 25% of exceeding it, and a 25% of falling short # If handicap is > 15, there's a 25% chance of reaching club average, # and 75% chance of falling short # The greater the handicap, the more the ball falls short # If poor distance is a known player_difficulty, then reduce distance by 10% distance: float rnd = random.randint(1, 101) if self.handicap < 15 and rnd <= 25 or self.handicap >= 15 and rnd <= 75: distance = club_amt - (club_amt * (self.handicap / 100.0)) elif self.handicap < 15 and rnd <= 75 or self.handicap >= 15: distance = club_amt else: distance = club_amt + (club_amt * 0.10) if self.player_difficulty == 3 and odds(80): # poor distance distance *= 0.80 if (flags & luck) == luck: distance = club_amt # angle # For all strokes, there's a possible "drift" of 4 degrees # a hooks or slice increases the angle between 5-10 degrees, # hook uses negative degrees angle = random.randint(0, 5) if (flags & slice_) == slice_: angle = random.randint(5, 11) if (flags & hook) == hook: angle = 0 - random.randint(5, 11) if (flags & luck) == luck: angle = 0 plot = self.plot_ball(self.ball, distance, angle) # calculate a new location if (flags & luck) == luck and plot.y > 0: plot.y = 2 flags = self.find_ball( Ball(plot.x, plot.y, plot.offline, GameObjType.BALL), flags ) self.interpret_results(plot, flags) def plot_ball(self, ball: Ball, stroke_distance: float, degrees_off: float) -> Plot: cup_vector = Point(0, -1) rad_from_cup = math.atan2(ball.Y, ball.X) - math.atan2( cup_vector.y, cup_vector.x ) rad_from_ball = rad_from_cup - math.pi hypotenuse = stroke_distance adjacent = math.cos(rad_from_ball + to_radians(degrees_off)) * hypotenuse opposite = math.sqrt(math.pow(hypotenuse, 2) - math.pow(adjacent, 2)) new_pos: Point if to_degrees_360(rad_from_ball + to_radians(degrees_off)) > 180: new_pos = Point(int(ball.X - opposite), int(ball.Y - adjacent)) else: new_pos = Point(int(ball.X + opposite), int(ball.Y - adjacent)) return Plot(new_pos.x, new_pos.y, int(opposite)) def interpret_results(self, plot: Plot, flags: int) -> None: cup_distance: int = int( get_distance( Point(plot.x, plot.y), Point(self.hole_geometry.cup.X, self.hole_geometry.cup.Y), ) ) travel_distance: int = int( get_distance(Point(plot.x, plot.y), Point(self.ball.X, self.ball.Y)) ) print(" ") if (flags & ace) == ace: print("Hole in One! You aced it.") self.score_card_record_stroke(Ball(0, 0, 0, GameObjType.BALL)) self.report_current_score() return if (flags & in_trees) == in_trees: print("Your ball is lost in the trees. Take a penalty stroke.") self.score_card_record_stroke(self.ball) self.tee_up() return if (flags & in_water) == in_water: if odds(50): msg = "Your ball has gone to a watery grave." else: msg = "Your ball is lost in the water." print(f"{msg} Take a penalty stroke.") self.score_card_record_stroke(self.ball) self.tee_up() return if (flags & out_of_bounds) == out_of_bounds: print("Out of bounds. Take a penalty stroke.") self.score_card_record_stroke(self.ball) self.tee_up() return if (flags & dub) == dub: print("You dubbed it.") self.score_card_record_stroke(self.ball) self.tee_up() return if (flags & in_cup) == in_cup: msg = "You holed it." if odds(50) else "It's in!" print(msg) self.score_card_record_stroke(Ball(plot.x, plot.y, 0, GameObjType.BALL)) self.report_current_score() return if (flags & slice_) == slice_ and flags & on_green != on_green: bad = "badly" if (flags & out_of_bounds) == out_of_bounds else "" print(f"You sliced{bad}: {plot.offline} yards offline.") if (flags & hook) == hook and flags & on_green != on_green: bad = "badly" if (flags & out_of_bounds) == out_of_bounds else "" print(f"You hooked{bad}: {plot.offline} yards offline.") if self.stroke_num > 1: prev_ball = self.score_card_get_previous_stroke() d1 = get_distance( Point(prev_ball.X, prev_ball.Y), Point(self.hole_geometry.cup.X, self.hole_geometry.cup.Y), ) d2 = cup_distance if d2 > d1: print("Too much club.") if (flags & in_rough) == in_rough: print("You're in the rough.") if (flags & in_sand) == in_sand: print("You're in a sand trap.") if (flags & on_green) == on_green: if cup_distance < 4: pd = f"{str(cup_distance * 3)} feet" else: pd = f"{cup_distance} yards" print(f"You're on the green. It's {pd} from the pin.") if ((flags & on_fairway) == on_fairway) or ((flags & in_rough) == in_rough): print( f"Shot went {travel_distance} yards. " f"It's {cup_distance} yards from the cup." ) self.score_card_record_stroke(Ball(plot.x, plot.y, 0, GameObjType.BALL)) self.ball = Ball(plot.x, plot.y, 0, GameObjType.BALL) self.tee_up() def report_current_score(self) -> None: par = CourseInfo[self.hole_num].par if len(self.score_card[self.hole_num]) == par + 1: print("A bogey. One above par.") if len(self.score_card[self.hole_num]) == par: print("Par. Nice.") if len(self.score_card[self.hole_num]) == (par - 1): print("A birdie! One below par.") if len(self.score_card[self.hole_num]) == (par - 2): print("An Eagle! Two below par.") if len(self.score_card[self.hole_num]) == (par - 3): print("Double Eagle! Unbelievable.") total_par: int = sum(CourseInfo[i].par for i in range(1, self.hole_num + 1)) print(" ") print("-----------------------------------------------------") hole_str = "holes" if self.hole_num > 1 else "hole" print( f" Total par for {self.hole_num} {hole_str} is: {total_par}. " f"Your total is: {self.score_card_get_total()}." ) print("-----------------------------------------------------") print(" ") if self.hole_num == 18: self.game_over() else: time.sleep(2) self.new_hole() def find_ball(self, ball: Ball, flags: int) -> int: if self.is_on_fairway(ball) and not self.is_on_green(ball): flags |= on_fairway if self.is_on_green(ball): flags |= on_green if self.is_in_rough(ball): flags |= in_rough if self.is_out_of_bounds(ball): flags |= out_of_bounds if self.is_in_hazard(ball, GameObjType.WATER): flags |= in_water if self.is_in_hazard(ball, GameObjType.TREES): flags |= in_trees if self.is_in_hazard(ball, GameObjType.SAND): flags |= in_sand if ball.Y < 0: flags |= passed_cup # less than 2, it's in the cup d = get_distance( Point(ball.X, ball.Y), Point(self.hole_geometry.cup.X, self.hole_geometry.cup.Y), ) if d < 2: flags |= in_cup return flags def is_on_fairway(self, ball: Ball) -> bool: return is_in_rectangle(ball, self.hole_geometry.fairway) def is_on_green(self, ball: Ball) -> bool: d = get_distance( Point(ball.X, ball.Y), Point(self.hole_geometry.cup.X, self.hole_geometry.cup.Y), ) return d < self.hole_geometry.green.Radius def hazard_hit(self, h: Hazard, ball: Ball, hazard: GameObjType) -> bool: d = get_distance(Point(ball.X, ball.Y), Point(h.X, h.Y)) return d < h.Radius and h.Type == hazard def is_in_hazard(self, ball: Ball, hazard: GameObjType) -> bool: result: bool = False for h in self.hole_geometry.hazards: result = result and self.hazard_hit(h, ball, hazard) return result def is_in_rough(self, ball: Ball) -> bool: return is_in_rectangle(ball, self.hole_geometry.rough) and ( not is_in_rectangle(ball, self.hole_geometry.fairway) ) def is_out_of_bounds(self, ball: Ball) -> bool: return (not self.is_on_fairway(ball)) and (not self.is_in_rough(ball)) def score_card_start_new_hole(self) -> None: self.score_card.append([]) def score_card_record_stroke(self, ball: Ball) -> None: clone = Ball(ball.X, ball.Y, 0, GameObjType.BALL) self.score_card[self.hole_num].append(clone) def score_card_get_previous_stroke(self) -> Ball: return self.score_card[self.hole_num][len(self.score_card[self.hole_num]) - 1] def score_card_get_total(self) -> int: total: int = sum(len(h) for h in self.score_card) return total def ask( self, question: str, min_: int, max_: int, callback: Callable[[int], Any] ) -> None: # input from console is always an integer passed to a callback # or "quit" to end game print(question) i = input().strip().lower() if i == "quit": self.quit_game() return if i == "bag": self.review_bag() try: n = int(i) success = True except Exception: success = False n = 0 if success and n >= min_ and n <= max_: callback(n) else: self.ask(question, min_, max_, callback) def review_bag(self) -> None: print(" ") print(" # Club Average Yardage") print("-----------------------------------") print(" 1 Driver 250") print(" 2 3 Wood 225") print(" 3 5 Wood 200") print(" 4 Hybrid 190") print(" 5 4 Iron 170") print(" 6 7 Iron 150") print(" 7 9 Iron 125") print(" 8 Pitching wedge 110") print(" 9 Sand wedge 75") print(" 10 Putter 10") print(" ") def quit_game(self) -> None: print("\nLooks like rain. Goodbye!\n") return def game_over(self) -> None: net = self.score_card_get_total() - self.handicap print("Good game!") print(f"Your net score is: {net}") print("Let's visit the pro shop...") print(" ") return if __name__ == "__main__": Golf()