diff --git a/.github/data/contributors-log.json b/.github/data/contributors-log.json index 43ae688..3bba434 100644 --- a/.github/data/contributors-log.json +++ b/.github/data/contributors-log.json @@ -364,5 +364,59 @@ "82" ], "demo-path": "Spell-Sense" + }, + "Birthday-Paradox": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "86" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox" + }, + "BitMap-Message": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "87" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message" + }, + "Bouncing-DVD": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "89" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD" + }, + "Dog-Age-Calculator": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "90" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator" + }, + "Story-Generator": { + "contributor-name": [ + "AdyaTech" + ], + "pull-request-number": [ + "91" + ], + "demo-path": "https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator" + }, + "slot-machine": { + "contributor-name": [ + "appiokstella" + ], + "pull-request-number": [ + "93" + ], + "demo-path": "slot-machine" } } \ No newline at end of file diff --git a/Birthday-Paradox/birthdayparadox.py b/Birthday-Paradox/birthdayparadox.py new file mode 100644 index 0000000..2ecf9e2 --- /dev/null +++ b/Birthday-Paradox/birthdayparadox.py @@ -0,0 +1,102 @@ +import datetime, random + + +def getBirthdays(numberOfBirthdays): + """Returns a list of number random date objects for birthdays.""" + birthdays = [] + for i in range(numberOfBirthdays): + # The year is unimportant for our simulation, as long as all + # birthdays have the same year. + startOfYear = datetime.date(2001, 1, 1) + + # Get a random day into the year: + randomNumberOfDays = datetime.timedelta(random.randint(0, 364)) + birthday = startOfYear + randomNumberOfDays + birthdays.append(birthday) + return birthdays + + +def getMatch(birthdays): + """Returns the date object of a birthday that occurs more than once + in the birthdays list.""" + if len(birthdays) == len(set(birthdays)): + return None # All birthdays are unique, so return None. + + # Compare each birthday to every other birthday: + for a, birthdayA in enumerate(birthdays): + for b, birthdayB in enumerate(birthdays[a + 1 :]): + if birthdayA == birthdayB: + return birthdayA # Return the matching birthday. + + +# Display the intro: +print('''Birthday Paradox, by Al Sweigart al@inventwithpython.com + +The birthday paradox shows us that in a group of N people, the odds +that two of them have matching birthdays is surprisingly large. +This program does a Monte Carlo simulation (that is, repeated random +simulations) to explore this concept. + +(It's not actually a paradox, it's just a surprising result.) +''') + +# Set up a tuple of month names in order: +MONTHS = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') + +while True: # Keep asking until the user enters a valid amount. + print('How many birthdays shall I generate? (Max 100)') + response = input('> ') + if response.isdecimal() and (0 < int(response) <= 100): + numBDays = int(response) + break # User has entered a valid amount. +print() + +# Generate and display the birthdays: +print('Here are', numBDays, 'birthdays:') +birthdays = getBirthdays(numBDays) +for i, birthday in enumerate(birthdays): + if i != 0: + # Display a comma for each birthday after the first birthday. + print(', ', end='') + monthName = MONTHS[birthday.month - 1] + dateText = '{} {}'.format(monthName, birthday.day) + print(dateText, end='') +print() +print() + +# Determine if there are two birthdays that match. +match = getMatch(birthdays) + +# Display the results: +print('In this simulation, ', end='') +if match != None: + monthName = MONTHS[match.month - 1] + dateText = '{} {}'.format(monthName, match.day) + print('multiple people have a birthday on', dateText) +else: + print('there are no matching birthdays.') +print() + +# Run through 100,000 simulations: +print('Generating', numBDays, 'random birthdays 100,000 times...') +input('Press Enter to begin...') + +print('Let\'s run another 100,000 simulations.') +simMatch = 0 # How many simulations had matching birthdays in them. +for i in range(100000): + # Report on the progress every 10,000 simulations: + if i % 10000 == 0: + print(i, 'simulations run...') + birthdays = getBirthdays(numBDays) + if getMatch(birthdays) != None: + simMatch = simMatch + 1 +print('100,000 simulations run.') + +# Display simulation results: +probability = round(simMatch / 100000 * 100, 2) +print('Out of 100,000 simulations of', numBDays, 'people, there was a') +print('matching birthday in that group', simMatch, 'times. This means') +print('that', numBDays, 'people have a', probability, '% chance of') +print('having a matching birthday in their group.') +print('That\'s probably more than you would think!') diff --git a/BitMap-Message/bitmapmessage.py b/BitMap-Message/bitmapmessage.py new file mode 100644 index 0000000..59961fa --- /dev/null +++ b/BitMap-Message/bitmapmessage.py @@ -0,0 +1,47 @@ +import sys + +# (!) Try changing this multiline string to any image you like: + +# There are 68 periods along the top and bottom of this string: +# (You can also copy and paste this string from +# https://inventwithpython.com/bitmapworld.txt) +bitmap = """ +.................................................................... + ************** * *** ** * ****************************** + ********************* ** ** * * ****************************** * + ** ***************** ****************************** + ************* ** * **** ** ************** * + ********* ******* **************** * * + ******** *************************** * + * * **** *** *************** ****** ** * + **** * *************** *** *** * + ****** ************* ** ** * + ******** ************* * ** *** + ******** ******** * *** **** + ********* ****** * **** ** * ** + ********* ****** * * *** * * + ****** ***** ** ***** * + ***** **** * ******** + ***** **** ********* + **** ** ******* * + *** * * + ** * * +....................................................................""" + +print('Bitmap Message') +print('Enter the message to display with the bitmap.') +message = input('> ') +if message == '': + sys.exit() + +# Loop over each line in the bitmap: +for line in bitmap.splitlines(): + # Loop over each character in the line: + for i, bit in enumerate(line): + if bit == ' ': + # Print an empty space since there's a space in the bitmap: + print(' ', end='') + else: + # Print a character from the message: + print(message[i % len(message)], end='') + print() # Print a newline. diff --git a/Bouncing-DVD/bouncingdvd.py b/Bouncing-DVD/bouncingdvd.py new file mode 100644 index 0000000..4e82ce8 --- /dev/null +++ b/Bouncing-DVD/bouncingdvd.py @@ -0,0 +1,140 @@ +import sys, random, time + +try: + import bext +except ImportError: + print('This program requires the bext module, which you') + print('can install by following the instructions at') + print('https://pypi.org/project/Bext/') + sys.exit() + +# Set up the constants: +WIDTH, HEIGHT = bext.size() +# We can't print to the last column on Windows without it adding a +# newline automatically, so reduce the width by one: +WIDTH -= 1 + +NUMBER_OF_LOGOS = 5 # (!) Try changing this to 1 or 100. +PAUSE_AMOUNT = 0.2 # (!) Try changing this to 1.0 or 0.0. +# (!) Try changing this list to fewer colors: +COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'] + +UP_RIGHT = 'ur' +UP_LEFT = 'ul' +DOWN_RIGHT = 'dr' +DOWN_LEFT = 'dl' +DIRECTIONS = (UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT) + +# Key names for logo dictionaries: +COLOR = 'color' +X = 'x' +Y = 'y' +DIR = 'direction' + + +def main(): + bext.clear() + + # Generate some logos. + logos = [] + for i in range(NUMBER_OF_LOGOS): + logos.append({COLOR: random.choice(COLORS), + X: random.randint(1, WIDTH - 4), + Y: random.randint(1, HEIGHT - 4), + DIR: random.choice(DIRECTIONS)}) + if logos[-1][X] % 2 == 1: + # Make sure X is even so it can hit the corner. + logos[-1][X] -= 1 + + cornerBounces = 0 # Count how many times a logo hits a corner. + while True: # Main program loop. + for logo in logos: # Handle each logo in the logos list. + # Erase the logo's current location: + bext.goto(logo[X], logo[Y]) + print(' ', end='') # (!) Try commenting this line out. + + originalDirection = logo[DIR] + + # See if the logo bounces off the corners: + if logo[X] == 0 and logo[Y] == 0: + logo[DIR] = DOWN_RIGHT + cornerBounces += 1 + elif logo[X] == 0 and logo[Y] == HEIGHT - 1: + logo[DIR] = UP_RIGHT + cornerBounces += 1 + elif logo[X] == WIDTH - 3 and logo[Y] == 0: + logo[DIR] = DOWN_LEFT + cornerBounces += 1 + elif logo[X] == WIDTH - 3 and logo[Y] == HEIGHT - 1: + logo[DIR] = UP_LEFT + cornerBounces += 1 + + # See if the logo bounces off the left edge: + elif logo[X] == 0 and logo[DIR] == UP_LEFT: + logo[DIR] = UP_RIGHT + elif logo[X] == 0 and logo[DIR] == DOWN_LEFT: + logo[DIR] = DOWN_RIGHT + + # See if the logo bounces off the right edge: + # (WIDTH - 3 because 'DVD' has 3 letters.) + elif logo[X] == WIDTH - 3 and logo[DIR] == UP_RIGHT: + logo[DIR] = UP_LEFT + elif logo[X] == WIDTH - 3 and logo[DIR] == DOWN_RIGHT: + logo[DIR] = DOWN_LEFT + + # See if the logo bounces off the top edge: + elif logo[Y] == 0 and logo[DIR] == UP_LEFT: + logo[DIR] = DOWN_LEFT + elif logo[Y] == 0 and logo[DIR] == UP_RIGHT: + logo[DIR] = DOWN_RIGHT + + # See if the logo bounces off the bottom edge: + elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_LEFT: + logo[DIR] = UP_LEFT + elif logo[Y] == HEIGHT - 1 and logo[DIR] == DOWN_RIGHT: + logo[DIR] = UP_RIGHT + + if logo[DIR] != originalDirection: + # Change color when the logo bounces: + logo[COLOR] = random.choice(COLORS) + + # Move the logo. (X moves by 2 because the terminal + # characters are twice as tall as they are wide.) + if logo[DIR] == UP_RIGHT: + logo[X] += 2 + logo[Y] -= 1 + elif logo[DIR] == UP_LEFT: + logo[X] -= 2 + logo[Y] -= 1 + elif logo[DIR] == DOWN_RIGHT: + logo[X] += 2 + logo[Y] += 1 + elif logo[DIR] == DOWN_LEFT: + logo[X] -= 2 + logo[Y] += 1 + + # Display number of corner bounces: + bext.goto(5, 0) + bext.fg('white') + print('Corner bounces:', cornerBounces, end='') + + for logo in logos: + # Draw the logos at their new location: + bext.goto(logo[X], logo[Y]) + bext.fg(logo[COLOR]) + print('DVD', end='') + + bext.goto(0, 0) + + sys.stdout.flush() # (Required for bext-using programs.) + time.sleep(PAUSE_AMOUNT) + + +# If this program was run (instead of imported), run the game: +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print() + print('Bouncing DVD Logo, by Al Sweigart') + sys.exit() # When Ctrl-C is pressed, end the program. diff --git a/Dog-Age-Calculator/dogage.py b/Dog-Age-Calculator/dogage.py new file mode 100644 index 0000000..19026bf --- /dev/null +++ b/Dog-Age-Calculator/dogage.py @@ -0,0 +1,15 @@ +def calculate_dog_age(human_age): + if human_age < 0: + return None + if human_age <= 2: + return human_age * 10.5 + return 21 + (human_age - 2) * 4 + + +human_age = int(input("Enter a dog's age in human years: ")) +dog_age = calculate_dog_age(human_age) + +if dog_age is None: + print("Age must be a positive number.") +else: + print(f"The dog's age in dog years is {dog_age}.") \ No newline at end of file diff --git a/Story-Generator/story.py b/Story-Generator/story.py new file mode 100644 index 0000000..627ff7c --- /dev/null +++ b/Story-Generator/story.py @@ -0,0 +1,51 @@ +import random +when = [ + "A few years ago", + "Yesterday", + "Last night", + "A long time ago", + "On 20th January" +] +who = [ + "a rabbit", + "an elephant", + "a mouse", + "a turtle", + "a cat" +] +names = [ + "Ali", + "Miriam", + "Daniel", + "Houuk", + "Starwalker" +] +places = [ + "Barcelona", + "India", + "Germany", + "Venice", + "England" +] +went_to = [ + "cinema", + "university", + "seminar", + "school", + "laundry" +] +happened = [ + "made a lot of friends", + "ate a burger", + "found a secret key", + "solved a mystery", + "wrote a book" +] +story = ( + f"{random.choice(when)}, " + f"{random.choice(names)} the {random.choice(who)} " + f"from {random.choice(places)} went to the " + f"{random.choice(went_to)} and " + f"{random.choice(happened)}." +) +print(story) \ No newline at end of file diff --git a/index.md b/index.md index d5226db..4b27d11 100644 --- a/index.md +++ b/index.md @@ -46,6 +46,12 @@ Welcome to **Python-Projects**, your friendly initiation into the world of open- | securepass | [Lampard7crypt](https://github.com/Lampard7crypt "goto Lampard7crypt profile") | [#72](https://github.com/Grow-with-Open-Source/Python-Projects/pull/72 "visit pr #72") | [/Grow-with-Open-Source/Python-Projects/securepass/](securepass "view the result of securepass") | | Biosimilars_Finder | [cmodevcodes](https://github.com/cmodevcodes "goto cmodevcodes profile") | [#83](https://github.com/Grow-with-Open-Source/Python-Projects/pull/83 "visit pr #83") | [/Grow-with-Open-Source/Python-Projects/Biosimilars_Finder/](Biosimilars_Finder "view the result of Biosimilars_Finder") | | Spell-Sense | [princechaudhary007](https://github.com/princechaudhary007 "goto princechaudhary007 profile") | [#82](https://github.com/Grow-with-Open-Source/Python-Projects/pull/82 "visit pr #82") | [/Grow-with-Open-Source/Python-Projects/Spell-Sense/](Spell-Sense "view the result of Spell-Sense") | +| Birthday-Paradox | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#86](https://github.com/Grow-with-Open-Source/Python-Projects/pull/86 "visit pr #86") | [/Grow-with-Open-Source/Python-Projects/Birthday-Paradox/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Birthday-Paradox "view the result of Birthday-Paradox") | +| BitMap-Message | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#87](https://github.com/Grow-with-Open-Source/Python-Projects/pull/87 "visit pr #87") | [/Grow-with-Open-Source/Python-Projects/BitMap-Message/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/BitMap-Message "view the result of BitMap-Message") | +| Bouncing-DVD | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#89](https://github.com/Grow-with-Open-Source/Python-Projects/pull/89 "visit pr #89") | [/Grow-with-Open-Source/Python-Projects/Bouncing-DVD/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Bouncing-DVD "view the result of Bouncing-DVD") | +| Dog-Age-Calculator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#90](https://github.com/Grow-with-Open-Source/Python-Projects/pull/90 "visit pr #90") | [/Grow-with-Open-Source/Python-Projects/Dog-Age-Calculator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Dog-Age-Calculator "view the result of Dog-Age-Calculator") | +| Story-Generator | [AdyaTech](https://github.com/AdyaTech "goto AdyaTech profile") | [#91](https://github.com/Grow-with-Open-Source/Python-Projects/pull/91 "visit pr #91") | [/Grow-with-Open-Source/Python-Projects/Story-Generator/](https://github.com/Grow-with-Open-Source/Python-Projects/tree/main/Story-Generator "view the result of Story-Generator") | +| slot-machine | [appiokstella](https://github.com/appiokstella "goto appiokstella profile") | [#93](https://github.com/Grow-with-Open-Source/Python-Projects/pull/93 "visit pr #93") | [/Grow-with-Open-Source/Python-Projects/slot-machine/](slot-machine "view the result of slot-machine") | diff --git a/slot-machine/README.md b/slot-machine/README.md new file mode 100644 index 0000000..5ec4360 --- /dev/null +++ b/slot-machine/README.md @@ -0,0 +1,11 @@ +## What it does + +A fully functional terminal-based slot machine game. The player deposits +money, chooses how many lines to bet on (up to 3), sets a bet amount per +line, and spins. The machine generates a random 3x3 grid of symbols and +pays out based on matching symbols across each line. The game tracks +balance across multiple spins until the player quits. + +## Requirements + +None — uses only built-in random module diff --git a/slot-machine/slot_machine.py b/slot-machine/slot_machine.py new file mode 100644 index 0000000..69bd93b --- /dev/null +++ b/slot-machine/slot_machine.py @@ -0,0 +1,162 @@ +import random + +MAX_LINES = 3 +MAX_BET = 10 +MIN_BET = 1 + +ROWS = 3 +COLS = 3 + +symbol_count = { + "A": 2, + "B": 4, + "C": 6, + "D": 8 +} + +symbol_value = { + "A": 5, + "B": 4, + "C": 3, + "D": 2 +} + + +def deposit(): + while True: + amount = input("What would you like to deposit? $") + + if amount.isdigit(): + amount = int(amount) + + if amount > 0: + return amount + + print("Please enter a valid amount.") + + +def get_number_of_lines(): + while True: + lines = input(f"Enter the number of lines to bet on (1-{MAX_LINES}): ") + + if lines.isdigit(): + lines = int(lines) + + if 1 <= lines <= MAX_LINES: + return lines + + print("Enter a valid number.") + + +def get_bet(): + while True: + amount = input("What would you like to bet on each line? $") + + if amount.isdigit(): + amount = int(amount) + + if MIN_BET <= amount <= MAX_BET: + return amount + + print(f"Amount must be between ${MIN_BET} and ${MAX_BET}.") + + +def get_slot_machine_spin(rows, cols, symbols): + all_symbols = [] + + for symbol, symbol_count in symbols.items(): + for _ in range(symbol_count): + all_symbols.append(symbol) + + columns = [] + + for _ in range(cols): + current_symbols = all_symbols[:] + column = [] + + for _ in range(rows): + value = random.choice(current_symbols) + current_symbols.remove(value) + column.append(value) + + columns.append(column) + + return columns + + +def print_slot_machine(columns): + for row in range(len(columns[0])): + for i, column in enumerate(columns): + if i != len(columns) - 1: + print(column[row], end=" | ") + else: + print(column[row], end="") + + print() + + +def check_winnings(columns, lines, bet, values): + winnings = 0 + winning_lines = [] + + for line in range(lines): + symbol = columns[0][line] + + for column in columns: + symbol_to_check = column[line] + + if symbol != symbol_to_check: + break + else: + winnings += values[symbol] * bet + winning_lines.append(line + 1) + + return winnings, winning_lines + + +def spin(balance): + lines = get_number_of_lines() + + while True: + bet = get_bet() + total_bet = bet * lines + + if total_bet > balance: + print( + f"You do not have enough balance. Current balance: ${balance}") + else: + break + + print(f"\nYou are betting ${bet} on {lines} lines.") + print(f"Total bet: ${total_bet}\n") + + slots = get_slot_machine_spin(ROWS, COLS, symbol_count) + + print_slot_machine(slots) + + winnings, winning_lines = check_winnings( + slots, lines, bet, symbol_value) + + print(f"\nYou won ${winnings}.") + print(f"Winning lines: {winning_lines}") + + return winnings - total_bet + + +def main(): + balance = deposit() + + while True: + print(f"\nCurrent balance: ${balance}") + + answer = input("Press Enter to spin (q to quit): ") + + if answer.lower() == "q": + break + + balance += spin(balance) + + print(f"\nYou left with ${balance}") + + +main()