import random import math import time def basic_print(*zones, **kwargs): """Simulates the PRINT command from BASIC to some degree. Supports `printing zones` if given multiple arguments.""" line = "" if len(zones) == 1: line = str(zones[0]) else: line = "".join(["{:<14}".format(str(zone)) for zone in zones]) identation = kwargs.get("indent", 0) end = kwargs.get("end", "\n") print(" " * identation + line, end=end) def basic_input(prompt, type_conversion=None): """BASIC INPUT command with optional type conversion""" while True: try: inp = input(f"{prompt}? ") if type_conversion is not None: inp = type_conversion(inp) break except ValueError: basic_print("INVALID INPUT!") return inp # horse names do not change over the program, therefore making it a global. # throught the game, the ordering of the horses is used to indentify them HORSE_NAMES = [ "JOE MAW", "L.B.J.", "MR.WASHBURN", "MISS KAREN", "JOLLY", "HORSE", "JELLY DO NOT", "MIDNIGHT" ] def introduction(): """Print the introduction, and optional the instructions""" basic_print("HORSERACE", indent=31) basic_print("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", indent=15) basic_print("\n\n") basic_print("WELCOME TO SOUTH PORTLAND HIGH RACETRACK") basic_print(" ...OWNED BY LAURIE CHEVALIER") y_n = basic_input("DO YOU WANT DIRECTIONS") # if no instructions needed, return if y_n.upper() == "NO": return basic_print("UP TO 10 MAY PLAY. A TABLE OF ODDS WILL BE PRINTED. YOU") basic_print("MAY BET ANY + AMOUNT UNDER 100000 ON ONE HORSE.") basic_print("DURING THE RACE, A HORSE WILL BE SHOWN BY ITS") basic_print("NUMBER. THE HORSES RACE DOWN THE PAPER!") basic_print("") def setup_players(): """Gather the number of players and their names""" # ensure we get an integer value from the user number_of_players = basic_input("HOW MANY WANT TO BET", int) # for each user query their name and return the list of names player_names = [] basic_print("WHEN ? APPEARS,TYPE NAME") for _ in range(number_of_players): player_names.append(basic_input("")) return player_names def setup_horses(): """Generates random odds for each horse. Returns a list of odds, indexed by the order of the global HORSE_NAMES.""" odds = [random.randrange(1, 10) for _ in HORSE_NAMES] total = sum(odds) # rounding odds to two decimals for nicer output, # this is not in the origin implementation return [round(total/odd, 2) for odd in odds] def print_horse_odds(odds): """Print the odds for each horse""" basic_print("") for i in range(len(HORSE_NAMES)): basic_print(HORSE_NAMES[i], i, f"{odds[i]}:1") basic_print("") def get_bets(player_names): """For each player, get the number of the horse to bet on, as well as the amount of money to bet""" basic_print("--------------------------------------------------") basic_print("PLACE YOUR BETS...HORSE # THEN AMOUNT") bets = [] for name in player_names: horse = basic_input(name, int) amount = None while amount is None: amount = basic_input("", float) if amount < 1 or amount >= 100000: basic_print(" YOU CAN'T DO THAT!") amount = None bets.append((horse, amount)) basic_print("") return bets def get_distance(odd): """Advances a horse during one step of the racing simulation. The amount travelled is random, but scaled by the odds of the horse""" d = random.randrange(1,100) s = math.ceil(odd) if d < 10: return 1 elif d < s + 17: return 2 elif d < s + 37: return 3 elif d < s + 57: return 4 elif d < s + 77: return 5 elif d < s + 77: return 5 elif d < s + 92: return 6 else: return 7 def print_race_state(total_distance, race_pos): """Outputs the current state/stop of the race. Each horse is placed according to the distance they have travelled. In case some horses travelled the same distance, their numbers are printed on the same name""" # we dont want to modify the `race_pos` list, since we need # it later. Therefore we generating an interator from the list race_pos_iter = iter(race_pos) # race_pos is stored by last to first horse in the race. # we get the next horse we need to print out next_pos = next(race_pos_iter) # start line basic_print("XXXXSTARTXXXX") # print all 28 lines/unit of the race course for l in range(28): # ensure we still have a horse to print and if so, check if the # next horse to print is not the current line # needs iteration, since multiple horses can share the same line while next_pos is not None and l == total_distance[next_pos]: basic_print(f"{next_pos} ", end="") next_pos = next(race_pos_iter, None) else: # if no horses are left to print for this line, print a new line basic_print("") # finish line basic_print("XXXXFINISHXXXX") def simulate_race(odds): num_horses = len(HORSE_NAMES) # in spirit of the original implementation, using two arrays to # track the total distance travelled, and create an index from # race position -> horse index total_distance = [0] * num_horses # race_pos maps from the position in the race, to the index of the horse # it will later be sorted from last to first horse, based on the # distance travelled by each horse. # e.g. race_pos[0] => last horse # race_pos[-1] => winning horse race_pos = list(range(num_horses)) basic_print("\n1 2 3 4 5 6 7 8") while True: # advance each horse by a random amount for i in range(num_horses): total_distance[i] += get_distance(odds[i]) # bubble sort race_pos based on total distance travelled # in the original implementation, race_pos is reset for each # simulation step, so we keep this behaviour here race_pos = list(range(num_horses)) for l in range(num_horses): for i in range(num_horses-1-l): if total_distance[race_pos[i]] < total_distance[race_pos[i+1]]: continue race_pos[i], race_pos[i+1] = race_pos[i+1], race_pos[i] # print current state of the race print_race_state(total_distance, race_pos) # goal line is defined as 28 units from start # check if the winning horse is already over the finish line if total_distance[race_pos[-1]] >= 28: return race_pos # this was not in the original BASIC implementation, but it makes the # race visualization a nice animation (if the terminal size is set to 31 rows) time.sleep(1) def print_race_results(race_positions, odds, bets, player_names): """Print the race results, as well as the winnings of each player""" # print the race positions first basic_print("THE RACE RESULTS ARE:") position = 1 for horse_idx in reversed(race_positions): line = f"{position} PLACE HORSE NO. {horse_idx} AT {odds[horse_idx]}:1" basic_print("") basic_print(line) position += 1 # followed by the amount the players won winning_horse_idx = race_positions[-1] for idx, name in enumerate(player_names): (horse, amount) = bets[idx] if horse == winning_horse_idx: basic_print("") basic_print(f"{name} WINS ${amount * odds[winning_horse_idx]}") def main_loop(player_names, horse_odds): """Main game loop""" while True: print_horse_odds(horse_odds) bets = get_bets(player_names) final_race_positions = simulate_race(horse_odds) print_race_results(final_race_positions, horse_odds, bets, player_names) basic_print("DO YOU WANT TO BET ON THE NEXT RACE ?") one_more = basic_input("YES OR NO") if one_more.upper() != "YES": break def main(): # introduction, player names and horse odds are only generated once introduction() player_names = setup_players() horse_odds = setup_horses() # main loop of the game, the player can play multiple races, with the # same odds main_loop(player_names, horse_odds) if __name__ == "__main__": main()