diff --git a/.github/workflows/python-lint.yml b/.github/workflows/disabled/python-lint.yml similarity index 100% rename from .github/workflows/python-lint.yml rename to .github/workflows/disabled/python-lint.yml diff --git a/.github/workflows/python-format.yml b/.github/workflows/python-format.yml index 71331888..0eac2193 100644 --- a/.github/workflows/python-format.yml +++ b/.github/workflows/python-format.yml @@ -7,7 +7,7 @@ on: pull_request jobs: format-lint-python: - name: Format Python with black + name: Format Python with black and lint with flake8 runs-on: ubuntu-latest steps: @@ -20,12 +20,14 @@ jobs: python-version: '3.x' - name: Install Python dependencies - run: pip install black + run: pip install black flake8 - - name: Run black - uses: samuelmeuli/lint-action@v1 + - name: Run black and flake8 + uses: wearerequired/lint-action@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} black: true - black_args: --line-length 79 # same max line length as flake8 - auto_fix: true # auto commit style fixes \ No newline at end of file + flake8: true + black_args: "--line-length 79 --exclude='1_beginner/chapter1/examples/error.py'" # same max line length as flake8 + flake8_args: "--max-line-length=88 --ignore=E203,W503 --exclude=1_beginner/chapter1/examples/error.py" # prevent conflicts with black + auto_fix: true # auto commit style fixes + \ No newline at end of file diff --git a/.gitignore b/.gitignore index d1e0b281..c233127c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ *.xml *.iml +venv/ \ No newline at end of file diff --git a/1_beginner/chapter1/examples/error.py b/1_beginner/chapter1/examples/error.py new file mode 100644 index 00000000..269c6720 --- /dev/null +++ b/1_beginner/chapter1/examples/error.py @@ -0,0 +1,8 @@ +# Syntax Errors +prnt("Hello") # this would be a syntax error +# someone misspelled 'print' as 'prnt' + + print("Hello") # this would also result in an error since there's an extra indent + +# Runtime Error +print(1 / 0) # this would result in a ZeroDivision error diff --git a/1_beginner/chapter1/examples/printing.py b/1_beginner/chapter1/examples/printing.py index d726553a..40f4ab82 100644 --- a/1_beginner/chapter1/examples/printing.py +++ b/1_beginner/chapter1/examples/printing.py @@ -1,5 +1,10 @@ # Printing +# You can print other types besides strings; we'll get to that later +print("Hello") +print(1) +print(99.99) + # Strings can be in single or double quotes print("Message") print("Message") diff --git a/1_beginner/chapter3/examples/math_operators.py b/1_beginner/chapter3/examples/math_operators.py index 00648a58..8c54a408 100644 --- a/1_beginner/chapter3/examples/math_operators.py +++ b/1_beginner/chapter3/examples/math_operators.py @@ -29,4 +29,4 @@ # Exponent x = 2 y = 3 -print(x ** y) # prints 8 +print(x**y) # prints 8 diff --git a/1_beginner/chapter3/practice/decimal.py b/1_beginner/chapter3/practice/decimal.py index bb8a4ee2..bc381d1e 100644 --- a/1_beginner/chapter3/practice/decimal.py +++ b/1_beginner/chapter3/practice/decimal.py @@ -1,7 +1,10 @@ # Decimal # Write a program that asks the user for a floating point number as input. -# It returns the decimal part (the part to the right of the decimal point). +# It prints out the decimal part (the part to the right of the decimal point). # Don't worry about floating point errors! -# The output should round to the correct answer, though. +# Note: To display a more exact output, you can use +# rounded_decimal = round(decimal_variable, 10) +# to set rounded_decimal to your decimal_variable rounded to a precision +# of 10 decimal places. # Write code here diff --git a/1_beginner/chapter3/solutions/change.py b/1_beginner/chapter3/solutions/change.py index bad5b99b..a8cd5ee9 100644 --- a/1_beginner/chapter3/solutions/change.py +++ b/1_beginner/chapter3/solutions/change.py @@ -30,19 +30,19 @@ # calculate change and display it dollars = num_cents // CENTS_PER_DOLLAR remaining = num_cents % CENTS_PER_DOLLAR -print(dollars, " dollars") +print(dollars, "dollars") quarters = remaining // CENTS_PER_QUARTER remiaining = remaining % CENTS_PER_QUARTER -print(quarters, " quarters") +print(quarters, "quarters") dimes = remaining // CENTS_PER_DIME remiaining = remaining % CENTS_PER_DIME -print(dimes, " dimes") +print(dimes, "dimes") nickels = remaining // CENTS_PER_NICKEL remiaining = remaining % CENTS_PER_NICKEL -print(nickels, " nickels") +print(nickels, "nickels") cents = remaining -print(cents, " cents") +print(cents, "cents") diff --git a/1_beginner/chapter3/solutions/circle.py b/1_beginner/chapter3/solutions/circle.py index 3afe613f..78463e52 100644 --- a/1_beginner/chapter3/solutions/circle.py +++ b/1_beginner/chapter3/solutions/circle.py @@ -10,7 +10,7 @@ radius = float(input("Enter a radius: ")) # Calculate the area and circumference -area = PI * radius ** 2 +area = PI * radius**2 circumference = 2 * PI * radius # Print the result diff --git a/1_beginner/chapter3/solutions/cylinder_volume.py b/1_beginner/chapter3/solutions/cylinder_volume.py index 94897dfd..07494915 100644 --- a/1_beginner/chapter3/solutions/cylinder_volume.py +++ b/1_beginner/chapter3/solutions/cylinder_volume.py @@ -6,5 +6,5 @@ PI = 3.14 height = float(input("Height of cylinder: ")) radius = float(input("Radius of cylinder: ")) -volume = PI * radius ** 2 * height +volume = PI * radius**2 * height print("The volume of the cylinder is", volume) diff --git a/1_beginner/chapter3/solutions/decimal.py b/1_beginner/chapter3/solutions/decimal.py index 21ca8fde..f1917397 100644 --- a/1_beginner/chapter3/solutions/decimal.py +++ b/1_beginner/chapter3/solutions/decimal.py @@ -1,8 +1,11 @@ # Decimal # Write a program that asks the user for a floating point number as input. -# It returns the decimal part (the part to the right of the decimal point). +# It prints out the decimal part (the part to the right of the decimal point). # Don't worry about floating point errors! -# The output should round to the correct answer, though. +# Note: To display a more exact output, you can use +# rounded_decimal = round(decimal_variable, 10) +# to set rounded_decimal to your decimal_variable rounded to a precision +# of 10 decimal places. # Get the floating point number input num = float(input("Enter a floating point number: ")) diff --git a/1_beginner/chapter4/solutions/square.py b/1_beginner/chapter4/solutions/square.py index 2dbde5cf..678cae8b 100644 --- a/1_beginner/chapter4/solutions/square.py +++ b/1_beginner/chapter4/solutions/square.py @@ -9,7 +9,7 @@ if number % 2 == 0: # if number is even, print its square - print(number ** 2) + print(number**2) else: # otherwise, print the number itself print(number) diff --git a/1_beginner/chapter5/practice/alternating.py b/1_beginner/chapter5/practice/alternating.py index a57c1dde..0fe44abc 100644 --- a/1_beginner/chapter5/practice/alternating.py +++ b/1_beginner/chapter5/practice/alternating.py @@ -1,17 +1,12 @@ """ Alternating - -Ask the user for an integer. The print the numbers from 1 to that number, -but alternating in sign. For example, if the input was 5, what would be printed -is 1, -1, 2, -2, 3, -3, 4, -4, 5. (Note, DO NOT include the last negative -number). - -Do this with a for loop +Ask the user for a positive integer. Then print the numbers from 1 +to that number, but alternating in sign. For example, if the input +was 5, what would be printed is 1, -1, 2, -2, 3, -3, 4, -4, 5. +(Note, DO NOT include the last negative number). +Do this with a for loop and then with a while loop. """ -# Write code here. - -number = int(input("Enter Number Here: ")) - +# Write code here # Now try it with a while loop diff --git a/1_beginner/chapter5/practice/fizzbuzz.py b/1_beginner/chapter5/practice/fizzbuzz.py new file mode 100644 index 00000000..894f212b --- /dev/null +++ b/1_beginner/chapter5/practice/fizzbuzz.py @@ -0,0 +1,22 @@ +# Fizz Buzz +""" +Credit to: https://www.youtube.com/watch?v=QPZ0pIK_wsc + +Fizz Buzz is a game played between 2 people +where they take turns counting up starting at 1. +However, if the number is divisible by 3, the person +should say "fizz" instead of the number. +If the number is divisible by 5, the person should +say "buzz" instead of the number. +If the number is divisible by both 3 and 5, +the person should say "fizzbuzz" instead of the number. +For example, this is how the first 5 results would look like: +1 +2 +fizz +4 +buzz +Write a program that outputs the first 100 results. +""" + +# Write your code here diff --git a/1_beginner/chapter5/solutions/alternating.py b/1_beginner/chapter5/solutions/alternating.py new file mode 100644 index 00000000..de8f197d --- /dev/null +++ b/1_beginner/chapter5/solutions/alternating.py @@ -0,0 +1,27 @@ +""" +Alternating +Ask the user for a positive integer. Then print the numbers from 1 +to that number, but alternating in sign. For example, if the input +was 5, what would be printed is 1, -1, 2, -2, 3, -3, 4, -4, 5. +(Note, DO NOT include the last negative number). +Do this with a for loop and then with a while loop. +""" + +# for loop solution +number = int(input("Enter number here: ")) +for num in range(1, number + 1): + if num == number: + print(num) + else: + print(num) + print(-num) + + +# while loop solution +number = int(input("Enter number here: ")) +current_num = 1 +while current_num < number: + print(current_num) + print(-current_num) + current_num += 1 +print(current_num) diff --git a/1_beginner/chapter5/solutions/fizzbuzz.py b/1_beginner/chapter5/solutions/fizzbuzz.py new file mode 100644 index 00000000..b3c9dda9 --- /dev/null +++ b/1_beginner/chapter5/solutions/fizzbuzz.py @@ -0,0 +1,30 @@ +# Fizz Buzz +""" +Credit to: https://www.youtube.com/watch?v=QPZ0pIK_wsc + +Fizz Buzz is a game played between 2 people +where they take turns counting up starting at 1. +However, if the number is divisible by 3, the person +should say "fizz" instead of the number. +If the number is divisible by 5, the person should +say "buzz" instead of the number. +If the number is divisible by both 3 and 5, +the person should say "fizzbuzz" instead of the number. +For example, this is how the first 5 results would look like: +1 +2 +fizz +4 +buzz +Write a program that outputs the first 100 results. +""" + +for i in range(1, 101): + if i % 3 == 0 and i % 5 == 0: + print("fizzbuzz") + elif i % 3 == 0: + print("fizz") + elif i % 5 == 0: + print("buzz") + else: + print(i) diff --git a/1_beginner/chapter7/examples/string_manipulation.py b/1_beginner/chapter7/examples/string_manipulation.py index efeab443..99c3a6e5 100644 --- a/1_beginner/chapter7/examples/string_manipulation.py +++ b/1_beginner/chapter7/examples/string_manipulation.py @@ -44,6 +44,9 @@ my_string = "hello" print(my_string[2]) # prints 'l' print(my_string[2:4]) # prints 'll' +# start: 2 (inclusive), stop: 4 (exclusive), default step of 1 +print(my_string[-1:-3:-1]) # prints 'ol' +# start: -1 (inclusive), stop: -3 (exclusive), step of -1 for char in my_string: print(char) # prints each character on its own line diff --git a/2_intermediate/chapter10/practice/img_avg.py b/2_intermediate/chapter10/practice/img_avg.py index dc9de796..2b71c3b3 100644 --- a/2_intermediate/chapter10/practice/img_avg.py +++ b/2_intermediate/chapter10/practice/img_avg.py @@ -1,50 +1,83 @@ """ Image Average -Here is the challenge problem for 2D loops: -Images are often represented as 3D arrays, -where the rows and columns are the pixels in the image, -and each pixel has an RGB (red, green, blue) value -which determines the color of the pixel. +Here is the challenge problem for nested loops: +Images are often represented as 3D lists. +The outer list is the entire image. +The 1st level inner list is a row of pixels. +The 2nd level inner list is the RGB values for that pixel. +RGB (red, green, blue) values determine the color of the pixel. The interesting thing is that we can iterate over images. -The challenge is, given an image, create a program that +The challenge is: given an image, create a program that will return a different image where each pixel is the average of the pixels surrounding it in the original image. -The neighbors of an image are all the pixels that surround it, -1 on each side, and 4 on the diagonals, for 8 in total. Each -pixel doesn't necessarily have 8 neighbors, though (think about why). - -The code to grab an image from the internet and make it -into an array is given to you. The code also displays the new image -you create in the end. - -NOTE: The image is 3 dimensional because each pixel has RGB values. To find the average value of all of a pixels neighbors, you must -change the average of the red value to the red value, blue to blue, etc. +calculate the average of the red values, blue values, and green values. For example, if the neighbors of a pixel with value [1, 2, 3] were [20, 30, 40] and [10, 120, 30], the new pixel that would replace the -original one would be [15, 75, 35] +original one would be [15, 75, 35] (since the average of 20 and 10 is 15, +the average of 30 and 120 is 75, and the average of 40 and 30 is 35). + +EXAMPLE: An image with 9 pixels may look like: +[ + [ + [31, 41, 42], [51, 1, 101], [24, 141, 33] + ], + + [ + [50, 21, 28], [31, 49, 201], [90, 54, 33] + ], + + [ + [12, 81, 3], [22, 8, 91], [101, 141, 132] + ] +] + +HINT: Don't forget that a pixel may have varying amount of neighboring +pixels. A pixel at the edge, for example, has 3 neighboring pixels while +a pixel at the center of the image has 8 neighboring pixels (one on each +of its 4 sides, and then one at each of its 4 corners). """ +# Import libraries needed to run the program +# Before importing the libraries, you must have them installed. +# This problem requires the following libraries: +# pillow, requests, numpy, and matplotlib +# If you don't already have them installed, open your command prompt or terminal +# and please do +# this: pip install -U (library) (any other libraries, each separated by a space) +# ex: pip install -U numpy matplotlib requests pillow +# Note: on some windows machines, you may need to +# do: py -m pip install -U (library) (any other libraries, each separated by a space) + from PIL import Image import requests import numpy import matplotlib.pyplot as plt -url = "https://images.dog.ceo/breeds/waterdog-spanish/20180723_185544.jpg" -img = numpy.array(Image.open(requests.get(url, stream=True).raw)).tolist() -newimg = img -transpose = numpy.transpose(img) +# Code that grabs the image from the internet and makes it into an array +IMAGE_URL = ( + "https://images.dog.ceo/breeds/waterdog-spanish/20180723_185544.jpg" +) +img = numpy.array( + Image.open(requests.get(IMAGE_URL, stream=True).raw) +).tolist() + +# create newimg as an empty list so that we'll know if something went wrong +# ie. if we try to display it and the function didn't run, we'd get an +# invalid shape error +newimg = [[[] for column in row] for row in img] +# Code that displays the original image +print("now displaying the original image") plt.imshow(img) plt.show() -# write code to create newimg here +# Write code to create newimg here +# Code that displays the new image at the end +print("now displaying the new image") plt.imshow(newimg) plt.show() - -plt.imshow(transpose) -plt.show() diff --git a/2_intermediate/chapter10/solutions/img_avg.py b/2_intermediate/chapter10/solutions/img_avg.py index 815ab8ea..66077ed6 100644 --- a/2_intermediate/chapter10/solutions/img_avg.py +++ b/2_intermediate/chapter10/solutions/img_avg.py @@ -1,95 +1,162 @@ """ Image Average -Here is the challenge problem for 2D loops: -Images are often represented as 3D arrays, -where the rows and columns are the pixels in the image, -and each pixel has an RGB (red, green, blue) value -which determines the color of the pixel. +Here is the challenge problem for nested loops: +Images are often represented as 3D lists. +The outer list is the entire image. +The 1st level inner list is a row of pixels. +The 2nd level inner list is the RGB values for that pixel. +RGB (red, green, blue) values determine the color of the pixel. The interesting thing is that we can iterate over images. -The challenge is, given an image, create a program that +The challenge is: given an image, create a program that will return a different image where each pixel is the average of the pixels surrounding it in the original image. -The neighbors of an image are all the pixels that surround it, -1 on each side, and 4 on the diagonals, for 8 in total. Each -pixel doesn't necessarily have 8 neighbors, though (think about why). - -The code to grab an image from the internet and make it -into an array is given to you. The code also displays the new image -you create in the end. - -NOTE: The image is 3 dimensional because each pixel has RGB values. To find the average value of all of a pixels neighbors, you must -change the average of the red value to the red value, blue to blue, etc. +calculate the average of the red values, blue values, and green values. For example, if the neighbors of a pixel with value [1, 2, 3] were [20, 30, 40] and [10, 120, 30], the new pixel that would replace the -original one would be [15, 75, 35] +original one would be [15, 75, 35] (since the average of 20 and 10 is 15, +the average of 30 and 120 is 75, and the average of 40 and 30 is 35). + +EXAMPLE: An image with 9 pixels may look like: +[ + [ + [31, 41, 42], [51, 1, 101], [24, 141, 33] + ], + + [ + [50, 21, 28], [31, 49, 201], [90, 54, 33] + ], + + [ + [12, 81, 3], [22, 8, 91], [101, 141, 132] + ] +] + +HINT: Don't forget that a pixel may have varying amount of neighboring +pixels. A pixel at the edge, for example, has 3 neighboring pixels while +a pixel at the center of the image has 8 neighboring pixels (one on each +of its 4 sides, and then one at each of its 4 corners). """ +# Import libraries needed to run the program +# Before importing the libraries, you must have them installed. +# This problem requires the following libraries: +# pillow, requests, numpy, and matplotlib +# If you don't already have them installed, open your command prompt or terminal +# and please do +# this: pip install -U (library) (any other libraries, each separated by a space) +# ex: pip install -U numpy matplotlib requests pillow +# Note: on some windows machines, you may need to +# do: py -m pip install -U (library) (any other libraries, each separated by a space) + + from PIL import Image import requests import numpy import matplotlib.pyplot as plt -url = "https://images.dog.ceo/breeds/waterdog-spanish/20180723_185544.jpg" -img = numpy.array(Image.open(requests.get(url, stream=True).raw)) -newimg = img -transpose = numpy.transpose(img) +# Code that grabs the image from the internet and makes it into an array +IMAGE_URL = ( + "https://images.dog.ceo/breeds/waterdog-spanish/20180723_185544.jpg" +) +img = numpy.array( + Image.open(requests.get(IMAGE_URL, stream=True).raw) +).tolist() + +# create newimg as an empty list so that we'll know if something went wrong +# ie. if we try to display it and the function didn't run, we'd get an +# invalid shape error +newimg = [[[] for column in row] for row in img] + +# Code that displays the original image +print("now displaying the original image") +plt.imshow(img) +plt.show() -# write code to create newimg here -def solution1(): +def distort(original_image, new_image): """ - Iterating over the image here. i is a variable from - 0 to the width of the image. - j is a variable that ranges from 0 to the height of the image. - i is associated with values + Modifies new_image so that each pixel in new_image + will be the average of the surrounding + DISTORTION_RADIUS pixels. + DISTORTION_RADIUS can be changed for more/less distortion. + Arguments: + original_image (list or tuple) - the reference image. + new_image (list) - the image to modify. """ - for i in range(len(img)): - for j in range(len(img[0])): - x_n = [0] - y_n = [0] - - if i == 0: - x_n.append(1) - elif i == len(img) - 1: - x_n.append(-1) - else: - x_n.append(1) - x_n.append(-1) - - if j == 0: - y_n.append(1) - elif j == len(img[0]) - 1: - y_n.append(-1) - else: - y_n.append(1) - y_n.append(-1) - - r_avg = -1 * img[i][j][0] - g_avg = -1 * img[i][j][1] - b_avg = -1 * img[i][j][2] - c = -1 - - for x in x_n: - for y in y_n: - r_avg += img[i + x][j + y][0] - g_avg += img[i + x][j + y][1] - b_avg += img[i + x][j + y][2] - c += 1 - r_avg = r_avg / c - g_avg = g_avg / c - b_avg = b_avg / c - - newimg[i][j] = [r_avg, g_avg, b_avg] - - -solution1() - + DISTORTION_RADIUS = 1 # this should be a positive integer + # Note that each increase of DISTORTION_RADIUS increases + # run time amazingly. Slower PC's should stick to values like + # 1 or 2 for DISTORTION_RADIUS + + for row in range(len(original_image)): + for column in range(len(original_image[0])): + # we set these to empty lists because the for loops + # will iterate through all valid relative indexes + # (including 0) and append them to these lists. + x_relative_indexes = [] + y_relative_indexes = [] + + # handle y relative indexes + # +1 to DISTORTION_RADIUS because stop is exclusive + for relative_y in range(-DISTORTION_RADIUS, DISTORTION_RADIUS + 1): + if ( + row + relative_y < 0 + or row + relative_y > len(original_image) - 1 + ): + # ignore relative indexes that are out of range of the + # original image + continue + # if it isn't out of range, it's valid and should be appended + y_relative_indexes.append(relative_y) + + # handle x relative indexes + # +1 to DISTORTION_RADIUS because stop is exclusive + for relative_x in range(-DISTORTION_RADIUS, DISTORTION_RADIUS + 1): + if ( + column + relative_x < 0 + or column + relative_x > len(original_image[0]) - 1 + ): + # ignore relative indexes that are out of range of the + # original image + continue + # if it isn't out of range, it's valid and should be appended + x_relative_indexes.append(relative_x) + + # at this point, x_relative_indexes and y_relative_indexes are + # complete, so now we use them. + r_total = g_total = b_total = counter = 0 # initialize variables + for x in x_relative_indexes: + for y in y_relative_indexes: + # since images are 'rgb': + # red is the first val + r_total += original_image[row + y][column + x][0] + + # green is the second val + g_total += original_image[row + y][column + x][1] + + # blue is third val + b_total += original_image[row + y][column + x][2] + + counter += 1 + + # round because images don't deal w/ floats, only integers + r_avg = round(r_total / counter) + g_avg = round(g_total / counter) + b_avg = round(b_total / counter) + + # update the pixel in newimg to match the average of its + # surrounding pixels + new_image[row][column] = [r_avg, g_avg, b_avg] + + +print("now modifying file. Depending on your pc, this may take a while.") +distort(img, newimg) + +# Code that displays the new image at the end +print("now displaying the new image") plt.imshow(newimg) plt.show() - -plt.imshow(transpose) -plt.show() diff --git a/2_intermediate/chapter11/examples/define_function.py b/2_intermediate/chapter11/examples/define_function.py new file mode 100644 index 00000000..552421cc --- /dev/null +++ b/2_intermediate/chapter11/examples/define_function.py @@ -0,0 +1,8 @@ +# This is how you define a new function +# "scoop_ice_cream" is the function name +# and "flavor" is a parameter + + +def scoop_ice_cream(flavor): + # write function code here + pass diff --git a/2_intermediate/chapter11/examples/parameters.py b/2_intermediate/chapter11/examples/parameters.py new file mode 100644 index 00000000..90539b69 --- /dev/null +++ b/2_intermediate/chapter11/examples/parameters.py @@ -0,0 +1,43 @@ +# when using regular parameters, remember that order matters +def scoop_ice_cream(param1, param2, param3): + pass + + +scoop_ice_cream("chocolate", "vanilla", "sprinkles") + + +# keyword arguments can be used to input parameter out of order +def func(p1, p2, p3): + print(p1) # prints 2 + print(p2) # prints 3 + print(p3) # prints 1 + + +func(p3=1, p1=2, p2=3) + + +# keyword arguments can also be used to make parameters optional +def car(speed=100): # if no speed is given, 100 is defaulted + print("Car speed:", speed) + + +car(speed=150) # prints "Car speed: 150" +car() # prints "Car speed: 100" + + +# *args can take in an unknown number of regular parameters +def function_name(param1, *args): + print(param1) # prints "p1" + print(args) # prints (1, 2, 3, 4) + + +function_name("p1", 1, 2, 3, 4) + + +# **kwargs can take in an unknown number of keyword parameters +def function_name(param1, **kwargs): + print(param1) # prints "p1" + print(kwargs) # prints {"a":1, "b":2, "c":3} + + +function_name("p1", a=1, b=2, c=3) diff --git a/2_intermediate/chapter11/examples/return.py b/2_intermediate/chapter11/examples/return.py new file mode 100644 index 00000000..2f50f2b1 --- /dev/null +++ b/2_intermediate/chapter11/examples/return.py @@ -0,0 +1,14 @@ +# The return statement "hands back" a value +# to where the function itself was called + + +def average(numbers): + # returns the average of a given list + return sum(numbers) / len(numbers) + + +numbers = [1, 2, 3] + +# assigns average of "numbers" to "avg" and prints it +avg = average(numbers) +print(avg) diff --git a/2_intermediate/chapter11/practice/nutrition_facts.py b/2_intermediate/chapter11/practice/nutrition_facts.py new file mode 100644 index 00000000..cfb4c4e8 --- /dev/null +++ b/2_intermediate/chapter11/practice/nutrition_facts.py @@ -0,0 +1,75 @@ +""" +Write a function that provides the nutrition facts of an item +within the provided dictionary 'nutrition_facts'. It should +provide the calories by default. It should accept the other +keys within the item's dictionary as keyword arguments. Use **. +If that keyword argument is True, then print out the value +stored by the key in addition to the default string that +says the number of calories. If the user entered in an +invalid specific, it should tell the user about this. If the +user entered in an invalid food, it should ignore the user +completely. + +---Example 1--- +parameters: "lays potato chips", allergens=True + +output: +"Lays potato chips have/has 220 calories" +"allergens" : ["processed on equipment that also processes peanuts", +"contains milk ingredients"] + +---Example 2--- +parameters: "lays potato chips" + +output: +"Lays potato chips have/has 220 calories" + +---Example 3--- +parameters: "lays potato chips", main_ingredients= True + +output: +"Lays potato chips have/has 220 calories" +"main_ingredients" : ["potato", "salt", "canola oil"] +""" + +nutrition_facts = { + "lays potato chips": { + "item": "Lays potato chips", + "calories": 220, + "all_ingredients": [ + "potato", + "salt", + "canola oil", + "msg", + "yeast extract", + "onion extract", + "milk protein concentrate", + "sour cream", + "xantham gum", + "maltodextrin", + "sunflower oil", + ], + "main_ingredients": ["potato", "salt", "canola oil"], + "description": "Sour Cream and Onion Flavor", + "allergens": [ + "processed on equipment that also processes peanuts", + "contains milk ingredients", + ], + }, + "nutella": { + "item": "Nutella", + "calories": 200, + "all_ingredients": [ + "sugar", + "palm oil", + "hazelnuts", + "skim milk", + "cocoa", + "lecithin", + "vanillin (artificial flavor)", + ], + "main_ingredients": ["sugar", "palm oil", "hazelnuts"], + "description": "Hazelnut spread with cocoa", + "allergens": ["Contains Tree Nuts", "Contains milk", "Contains soy"], + }, +} diff --git a/2_intermediate/chapter11/practice/shopping.py b/2_intermediate/chapter11/practice/shopping.py new file mode 100644 index 00000000..9f3635e4 --- /dev/null +++ b/2_intermediate/chapter11/practice/shopping.py @@ -0,0 +1,22 @@ +""" +Code a function named shopping that will print the number +of items that a customer would like to buy. It will take in +an unknown number of parameters, each representing +one item. After that, the function will ask the customer, +"Are you sure you would like to buy" and stating the number +of items the customer wants to purchase.Your function does +not need to respond to a yes/no answer from the user; +it just needs to print the output (the question). + + +===Example 1=== +# Parameters: "soap", "brush", "comb" +# Output: "Are you sure you would like to buy 3 items?" + +===Example 2=== +# Parameters: "lotion", "shoes", "pencil", "crayon" +# Output: "Are you sure you would like to buy 4 items?" +""" + + +# Insert your code here diff --git a/2_intermediate/chapter11/solutions/nutrition_facts.py b/2_intermediate/chapter11/solutions/nutrition_facts.py new file mode 100644 index 00000000..b8b52605 --- /dev/null +++ b/2_intermediate/chapter11/solutions/nutrition_facts.py @@ -0,0 +1,97 @@ +""" +Write a function that provides the nutrition facts of an item +within the provided dictionary 'nutrition_facts'. It should +provide the calories by default. It should accept the other +keys within the item's dictionary as keyword arguments. Use **. +If that keyword argument is True, then print out the value +stored by the key in addition to the default string that +says the number of calories. If the user entered in an +invalid specific, it should tell the user about this. If the +user entered in an invalid food, it should ignore the user +completely. + +---Example 1--- +parameters: "lays potato chips", allergens=True + +output: +"Lays potato chips have/has 220 calories" +"allergens": ["processed on equipment that also processes peanuts", +"contains milk ingredients"] + +---Example 2--- +parameters: "lays potato chips" + +output: +"Lays potato chips have/has 220 calories" + +---Example 3--- +parameters: "lays potato chips", main_ingredients= True + +output: +"Lays potato chips have/has 220 calories" +"main_ingredients" : ["potato", "salt", "canola oil"] +""" + +nutrition_facts = { + "lays potato chips": { + "item": "Lays potato chips", + "calories": 220, + "all_ingredients": [ + "potato", + "salt", + "canola oil", + "msg", + "yeast extract", + "onion extract", + "milk protein concentrate", + "sour cream", + "xantham gum", + "maltodextrin", + "sunflower oil", + ], + "main_ingredients": ["potato", "salt", "canola oil"], + "description": "Sour Cream and Onion Flavor", + "allergens": [ + "processed on equipment that also processes peanuts", + "contains milk ingredients", + ], + }, + "nutella": { + "item": "Nutella", + "calories": 200, + "all_ingredients": [ + "sugar", + "palm oil", + "hazelnuts", + "skim milk", + "cocoa", + "lecithin", + "vanillin (artificial flavor)", + ], + "main_ingredients": ["sugar", "palm oil", "hazelnuts"], + "description": "Hazelnut spread with cocoa", + "allergens": ["Contains Tree Nuts", "Contains milk", "Contains soy"], + }, +} + + +def food_info(item, **specifics): + item = item.lower() + if item in nutrition_facts: + print( + nutrition_facts[item]["item"], + "have/has", + nutrition_facts[item]["calories"], + "calories", + ) + for specific in specifics: + if specific in nutrition_facts[item] and specifics[specific] is True: + print(f"{specific} : {nutrition_facts[item][specific]}") + else: + print( + f"{specific} isn't a valid specific about the nutrition facts" + ) + + +food_info("lays potato chips", allergens=True) +food_info("nutella", allergens=True) diff --git a/2_intermediate/chapter11/solutions/shopping.py b/2_intermediate/chapter11/solutions/shopping.py new file mode 100644 index 00000000..ca6526c5 --- /dev/null +++ b/2_intermediate/chapter11/solutions/shopping.py @@ -0,0 +1,27 @@ +""" +Code a function named shopping that will print the number +of items that a customer would like to buy. It will take in +an unknown number of parameters, each representing +one item. After that, the function will ask the customer, +"Are you sure you would like to buy" and stating the number +of items the customer wants to purchase. Your function does not +need to respond to a yes/no answer from the user; it just +needs to print the output (the question). + + +===Example 1=== +# Parameters: "soap", "brush", "comb" +# Output: "Are you sure you would like to buy 3 items?" + +===Example 2=== +# Parameters: "lotion", "shoes", "pencil", "crayon" +# Output: "Are you sure you would like to buy 4 items?" +""" + + +def shopping(*args): + item_number = len(args) + print("Are you sure you would like to buy", item_number, "items?") + + +shopping("soap", "brush", "comb") diff --git a/2_intermediate/chapter12/examples/class.py b/2_intermediate/chapter12/examples/class.py new file mode 100644 index 00000000..06cbdfcf --- /dev/null +++ b/2_intermediate/chapter12/examples/class.py @@ -0,0 +1,18 @@ +class dot_example: # use 'class' keyword followed by your class' name + # classes can store functions and data; we call functions "methods" + # we call data "attributes" + + # below are dot_example's attributes + fun = True + difficult = False + + +our_example = dot_example() # instantiate the class; make sure to use () + +# would print True +print(our_example.fun) # our_example is the object, fun is the attribute + +# would print False +print( + our_example.difficult +) # our_example is the object, difficult is the attribute diff --git a/2_intermediate/chapter12/examples/inheritance.py b/2_intermediate/chapter12/examples/inheritance.py new file mode 100644 index 00000000..8e1de605 --- /dev/null +++ b/2_intermediate/chapter12/examples/inheritance.py @@ -0,0 +1,22 @@ +# Inheritance in coding is when one "child" class receives +# all of the methods and attributes of another "parent" class + + +class Test: + def __init__(self): + self.x = 0 + + +# class Derived_Test inherits from class Test +class Derived_Test(Test): + def __init__(self): + Test.__init__(self) # do Test's __init__ method + # Test's __init__ gives Derived_Test the attribute 'x' + self.y = 1 + + +b = Derived_Test() + +# Derived_Test now has an attribute "x", even though +# it originally didn't +print(b.x, b.y) diff --git a/2_intermediate/chapter12/examples/init_function.py b/2_intermediate/chapter12/examples/init_function.py new file mode 100644 index 00000000..d98dc6bc --- /dev/null +++ b/2_intermediate/chapter12/examples/init_function.py @@ -0,0 +1,22 @@ +# The __init__ function is automatically called when +# a new object is created. It is good to use this method +# when there are certain values that are required beforehand +# for the object to work properly. + + +class Tesla: + def __init__(self, maxSpeed=120, color="red"): + # the init function always needs the self keyword + # if no maxSpeed is entered, maxSpeed will default to 120 + # if no color is entered, color will default to "red" + + # set the class' attribute maxSpeed to the provided maxSpeed + self.maxSpeed = maxSpeed + + # set the class' attribute color to the provided color + self.color = color + + +p1 = Tesla(140, "blue") +print(p1.maxSpeed) # will print 140 +print(p1.color) # will print "blue" diff --git a/2_intermediate/chapter12/examples/methods.py b/2_intermediate/chapter12/examples/methods.py new file mode 100644 index 00000000..5c570652 --- /dev/null +++ b/2_intermediate/chapter12/examples/methods.py @@ -0,0 +1,12 @@ +class Tesla: + def __init__(self, maxSpeed=120, color="red"): + self.maxSpeed = maxSpeed + self.color = color + + # a method: acts just like a function, but needs the self keyword + def drive(self): + print("The car is now driving") + + +p1 = Tesla(140, "blue") +p1.drive() # will execute the drive method from class Tesla diff --git a/2_intermediate/chapter12/examples/self_word.py b/2_intermediate/chapter12/examples/self_word.py new file mode 100644 index 00000000..f09e7d70 --- /dev/null +++ b/2_intermediate/chapter12/examples/self_word.py @@ -0,0 +1,28 @@ +# The self keyword is used when you want a method or +# attribute to be for a specific object. This means that, +# down below, each Tesla object can have different maxSpeed +# and colors from each other. + + +class Tesla: + def __init__(self, maxSpeed=120, color="red"): + self.maxSpeed = maxSpeed + self.color = color + + def change(self, c): + self.color = c + + +p1 = Tesla(140, "blue") +p2 = Tesla(100, "blue") + + +# Notice how, when we use the self keyword, each object can +# have different attributes even though they are from the +# same class. + +p1.change("green") +print(p1.color) # prints "green" + +p2.change("yellow") +print(p2.color) # prints "yellow" diff --git a/2_intermediate/chapter12/practice/buildings.py b/2_intermediate/chapter12/practice/buildings.py new file mode 100644 index 00000000..92169829 --- /dev/null +++ b/2_intermediate/chapter12/practice/buildings.py @@ -0,0 +1,21 @@ +# Create a class called 'building' +# It should have a build method that prints: +# "under construction..." +# "built" +# It should also have an __init__ method that runs the build method +# (basically, the __init__ method should call the build method) +# The __init__ method should also set an attribute 'built' to True + +# Create a class 'library' +# It should be a child class from 'building'. +# Its init method should run building's init method. It should also +# create an empty list called 'books' +# 'library' should also have a 'restock' method +# that asks the user for a book to buy and prints "bought %s" where +# %s is the bookname. The 'restock' method should also append the +# book's name to the library's list 'books' +# Lastly, the library class should have a method 'catalog' that prints +# all the books in the library on separate lines + +# Finally, instantiate the library class +# (you should see "under construction..." and "built" if you did it right diff --git a/2_intermediate/chapter12/solutions/buildings.py b/2_intermediate/chapter12/solutions/buildings.py new file mode 100644 index 00000000..3b345cce --- /dev/null +++ b/2_intermediate/chapter12/solutions/buildings.py @@ -0,0 +1,50 @@ +# Create a class called 'building' +# It should have a build method that prints: +# "under construction..." +# "built" +# It should also have an __init__ method that runs the build method +# (basically, the __init__ method should call the build method) +# The __init__ method should also set an attribute 'built' to True + +# Create a class 'library' +# It should be a child class from 'building'. +# Its init method should run building's init method. It should also +# create an empty list called 'books' +# 'library' should also have a 'restock' method +# that asks the user for a book to buy and prints "bought %s" where +# %s is the bookname. The 'restock' method should also append the +# book's name to the library's list 'books' +# Lastly, the library class should have a method 'catalog' that prints +# all the books in the library on separate lines + +# Finally, instantiate the library class +# (you should see "under construction..." and "built" if you did it right + + +class building: + def __init__(self): + self.build() + self.built = True + + def build(self): + print("under construction...") + print("built") + + +class library(building): + def __init__(self): + super().__init__() + self.books = [] + + def restock(self): + book = input("What book should we buy? ") + print("Bought %s" % book) + self.books.append(book) + + def catalog(self): + print("Here are our books:") + for book in self.books: + print(book) + + +oak_library = library() diff --git a/2_intermediate/chapter13/examples/bankAccount.py b/2_intermediate/chapter13/examples/bankAccount.py new file mode 100644 index 00000000..5c6248ca --- /dev/null +++ b/2_intermediate/chapter13/examples/bankAccount.py @@ -0,0 +1,45 @@ +class bankAccount: + def __init__(self, owner: str, balance: float): + self.owner = owner + self.balance = balance + + def __getitem__(self, item: str): + if item == "owner": + return self.owner + elif item == "balance": + return self.balance + else: + # if the attribute isn't a valid attribute, you should + # raise an AttributeError + raise AttributeError + + def __setitem__(self, item: str, value): + if item == "owner": + self.owner = value + elif item == "balance": + self.balance = value + else: + # if the attribute isn't a valid attribute, you should + # raise an AttributeError + raise AttributeError + + def __bool__(self): + """ + If we wanted the bank account to return True if the person + is not bankrupt and False if they are bankrupt, we could do: + """ + return self.balance > 0 + + +account = bankAccount("John", 100) +print(account["owner"]) +print(account["balance"]) +account["balance"] = 200 +print(account["balance"]) +account["owner"] = "John Jr." +print(account["owner"]) + + +print(bool(account)) +if account: + print("not bankrupt") diff --git a/2_intermediate/chapter13/examples/coordinateGrid.py b/2_intermediate/chapter13/examples/coordinateGrid.py new file mode 100644 index 00000000..6f0d00b4 --- /dev/null +++ b/2_intermediate/chapter13/examples/coordinateGrid.py @@ -0,0 +1,52 @@ +class coordinateGrid: + def __init__( + self, + x_start: int = 0, + x_end: int = 10, + y_start: int = 0, + y_end: int = 10, + ): + """ + Creates a list of coordinates similar to a coordinate grid. + Each item in self.coordinates is a list representing one row in a + coordinate grid. + each item within that row is a point (tuple) of x, y + ex: coordinateGrid(0, 1, -1, 1)'s coordinates would be + [ + [(0, 1), (1, 1)], + [(0, 0), (1, 0)], + [(0, -1), (1, -1)] + ] + Arguments: + x_start, x_end, y_start, and y_end are all inclusive + """ + self.coordinates = [ + [(x, y) for x in range(x_start, x_end + 1)] + for y in range(y_end, y_start - 1, -1) + ] + + def __contains__(self, item: tuple) -> bool: + """ + Checks to see if the provided tuple (or list) + of length 2 (the tuple/list represents a point of x,y) + is in self.coordinates. + """ + return True in [item in row for row in self.coordinates] + + def __len__(self) -> bool: + """ + In this case, we're saying that the length of the coordinateGrid + is its area. Thus, we do height * width + height = len(self.coordinates) and + width = len(self.coordinates[0]) (or any row's length) + """ + return len(self.coordinates) * len(self.coordinates[0]) + + +grid1 = coordinateGrid(-1, 1, -1, 1) +grid2 = coordinateGrid(-10, 10, -10, 10) +point1 = (10, 10) +print(point1 in grid1) +print(point1 in grid2) +print(len(grid1)) +print(len(grid2)) diff --git a/3_advanced/chapter13/examples/vector.py b/2_intermediate/chapter13/examples/vector.py similarity index 95% rename from 3_advanced/chapter13/examples/vector.py rename to 2_intermediate/chapter13/examples/vector.py index 3768a6ec..6038aad3 100644 --- a/3_advanced/chapter13/examples/vector.py +++ b/2_intermediate/chapter13/examples/vector.py @@ -1,26 +1,26 @@ -class Vector: - """ - Constructor - - self: a reference to the object we are creating - vals: a list of integers which are the contents of our vector - """ - - def __init__(self, vals): - self.vals = ( - vals # We're using the keyword self to create a field/property - ) - print("Assigned values ", vals, " to vector.") - - """ - String Function - - Converts the object to a string in readable format for programmers - """ - - def __str__(self): - return str(self.vals) # Returns the contents of the vector - - -vec = Vector([2, 3, 2]) -print(str(vec)) # [2, 3, 2] +class Vector: + """ + Constructor + + self: a reference to the object we are creating + vals: a list of integers which are the contents of our vector + """ + + def __init__(self, vals): + self.vals = ( + vals # We're using the keyword self to create a field/property + ) + print("Assigned values ", vals, " to vector.") + + """ + String Function + + Converts the object to a string in readable format for programmers + """ + + def __str__(self): + return str(self.vals) # Returns the contents of the vector + + +vec = Vector([2, 3, 2]) +print(str(vec)) # [2, 3, 2] diff --git a/3_advanced/chapter13/examples/vector2.py b/2_intermediate/chapter13/examples/vector2.py similarity index 90% rename from 3_advanced/chapter13/examples/vector2.py rename to 2_intermediate/chapter13/examples/vector2.py index 1c2236e3..224ccd01 100644 --- a/3_advanced/chapter13/examples/vector2.py +++ b/2_intermediate/chapter13/examples/vector2.py @@ -1,59 +1,59 @@ -class Vector: - """ - Constructor - - self: a reference to the object we are creating - vals: a list of integers which are the contents of our vector - """ - - def __init__(self, vals): - self.vals = vals - # print("Assigned values ", vals, " to vector.") - - """ - String Function - - Converts the object to a string in readable format for programmers - """ - - def __str__(self): - return str(self.vals) - - """ - Elementwise power: raises each element in our vector to the given power - """ - - def __pow__(self, power): - return Vector([i ** power for i in self.vals]) - - """ - Addition: adds each element to corresponding element in other vector - """ - - def __add__(self, vec): - return Vector( - [self.vals[i] + vec.vals[i] for i in range(len(self.vals))] - ) - - """ - Multiplies each element in the vector by a specified constant - """ - - def __mul__(self, constant): - return Vector([self.vals[i] * constant for i in range(len(self.vals))]) - - """ - Elementwise subtraction: does same as addition, just subtraction instead - """ - - def __sub__(self, vec): - return self + (vec * (-1)) - - -vec = Vector([2, 3, 2]) -otherVec = Vector([3, 4, 5]) -print(str(vec)) # [2, 3, 2] -print(vec ** 2) # [4, 9, 4] -print(vec - otherVec) # [-1, -1, -3] -print(vec + otherVec) # [5, 7, 7] -print(vec * 5) # [10, 15, 10] +class Vector: + """ + Constructor + + self: a reference to the object we are creating + vals: a list of integers which are the contents of our vector + """ + + def __init__(self, vals): + self.vals = vals + # print("Assigned values ", vals, " to vector.") + + """ + String Function + + Converts the object to a string in readable format for programmers + """ + + def __str__(self): + return str(self.vals) + + """ + Elementwise power: raises each element in our vector to the given power + """ + + def __pow__(self, power): + return Vector([i**power for i in self.vals]) + + """ + Addition: adds each element to corresponding element in other vector + """ + + def __add__(self, vec): + return Vector( + [self.vals[i] + vec.vals[i] for i in range(len(self.vals))] + ) + + """ + Multiplies each element in the vector by a specified constant + """ + + def __mul__(self, constant): + return Vector([self.vals[i] * constant for i in range(len(self.vals))]) + + """ + Elementwise subtraction: does same as addition, just subtraction instead + """ + + def __sub__(self, vec): + return self + (vec * (-1)) + + +vec = Vector([2, 3, 2]) +otherVec = Vector([3, 4, 5]) +print(str(vec)) # [2, 3, 2] +print(vec**2) # [4, 9, 4] +print(vec - otherVec) # [-1, -1, -3] +print(vec + otherVec) # [5, 7, 7] +print(vec * 5) # [10, 15, 10] diff --git a/3_advanced/chapter13/examples/vector3.py b/2_intermediate/chapter13/examples/vector3.py similarity index 90% rename from 3_advanced/chapter13/examples/vector3.py rename to 2_intermediate/chapter13/examples/vector3.py index d23d6a35..25307216 100644 --- a/3_advanced/chapter13/examples/vector3.py +++ b/2_intermediate/chapter13/examples/vector3.py @@ -1,68 +1,68 @@ -class Vector: - """ - Constructor - - self: a reference to the object we are creating - vals: a list of integers which are the contents of our vector - """ - - def __init__(self, vals): - self.vals = vals - # print("Assigned values ", vals, " to vector.") - - """ - String Function - - Converts the object to a string in readable format for programmers - """ - - def __str__(self): - return str(self.vals) - - def __pow__(self, power): - return Vector([i ** power for i in self.vals]) - - # Calculates Euclidean norm - def norm(self): - return sum((self ** 2).vals) ** 0.5 - - # __lt__: implements the less than operator (<) - def __lt__(self, other): - return self.norm() < other.norm() - - # __gt__: implements the greater than operator (>) - def __gt__(self, other): - return self.norm() > other.norm() - - # __le__: implements the less than equal to operator (<=) - def __le__(self, other): - return self.norm() <= other.norm() - - # __ge__: implements the greater than equal to operator (>=) - def __ge__(self, other): - return self.norm() >= other.norm() - - # __eq__: implements the equals operator (==) - def __eq__(self, other): - return self.norm() == other.norm() - - # __ne__:implements the not equals operator (!=) - def __ne__(self, other): - return self.norm() != other.norm() - - -vec = Vector([2, 3, 2]) -vec2 = Vector([3, 4, 5]) -print(vec < vec2) # True -print(vec > vec2) # False - -print(vec <= vec2) # True -print(vec >= vec2) # False -print(vec <= vec) # True -print(vec >= vec) # True - -print(vec == vec2) # False -print(vec == vec) # True - -print(vec != vec2) # True -print(vec != vec) # False +class Vector: + """ + Constructor + + self: a reference to the object we are creating + vals: a list of integers which are the contents of our vector + """ + + def __init__(self, vals): + self.vals = vals + # print("Assigned values ", vals, " to vector.") + + """ + String Function + + Converts the object to a string in readable format for programmers + """ + + def __str__(self): + return str(self.vals) + + def __pow__(self, power): + return Vector([i**power for i in self.vals]) + + # Calculates Euclidean norm + def norm(self): + return sum((self**2).vals) ** 0.5 + + # __lt__: implements the less than operator (<) + def __lt__(self, other): + return self.norm() < other.norm() + + # __gt__: implements the greater than operator (>) + def __gt__(self, other): + return self.norm() > other.norm() + + # __le__: implements the less than equal to operator (<=) + def __le__(self, other): + return self.norm() <= other.norm() + + # __ge__: implements the greater than equal to operator (>=) + def __ge__(self, other): + return self.norm() >= other.norm() + + # __eq__: implements the equals operator (==) + def __eq__(self, other): + return self.norm() == other.norm() + + # __ne__:implements the not equals operator (!=) + def __ne__(self, other): + return self.norm() != other.norm() + + +vec = Vector([2, 3, 2]) +vec2 = Vector([3, 4, 5]) +print(vec < vec2) # True +print(vec > vec2) # False + +print(vec <= vec2) # True +print(vec >= vec2) # False +print(vec <= vec) # True +print(vec >= vec) # True + +print(vec == vec2) # False +print(vec == vec) # True + +print(vec != vec2) # True +print(vec != vec) # False diff --git a/3_advanced/chapter13/practice/car.py b/2_intermediate/chapter13/practice/car.py similarity index 97% rename from 3_advanced/chapter13/practice/car.py rename to 2_intermediate/chapter13/practice/car.py index 742b40a9..dd972e70 100644 --- a/3_advanced/chapter13/practice/car.py +++ b/2_intermediate/chapter13/practice/car.py @@ -1,31 +1,31 @@ -""" -A new car is said to devalue 20% in the first year. Assuming that -this trend continues and that mileage divided by 100 is all you -subtract from this adjusted price, make a class "car" that has at -least the attributes "year, original price (aka og price), and -mileage." Also, follow these guidelines. - ---When using str() on a car, it should return the year, original - price, mileage, and adjusted price. ---When adding, it should add the value to its mileage before - adjusting the adjusted price. ---When multiplying, it should multiply the mileage by the value - before adjusting the adjusted price ---(While subtracting or dividing mileage on a car to sell it is - totally unethical,) When subtracting or dividing, it should - subtract the value from its mileage or divide its mileage by - the value before adjusting the adjusted price. ---When checking gt(which means greater than), lt, ge, le, ne, - and eq, it should compare the price with the other value. ---You should be able to compare cars (prices) but not add cars - together - -If you need help with modeling the equation for the adjusted price, -this may help - -self.adjustedprice=self.ogprice * (0.8**(2020-self.year))) -self.adjustedprice=round((self.adjustedprice),2)-self.mileage/100 - -""" - -# write your code below +""" +A new car is said to devalue 20% in the first year. Assuming that +this trend continues and that mileage divided by 100 is all you +subtract from this adjusted price, make a class "car" that has at +least the attributes "year, original price (aka og price), and +mileage." Also, follow these guidelines. + +--When using str() on a car, it should return the year, original + price, mileage, and adjusted price. +--When adding, it should add the value to its mileage before + adjusting the adjusted price. +--When multiplying, it should multiply the mileage by the value + before adjusting the adjusted price +--(While subtracting or dividing mileage on a car to sell it is + totally unethical,) When subtracting or dividing, it should + subtract the value from its mileage or divide its mileage by + the value before adjusting the adjusted price. +--When checking gt(which means greater than), lt, ge, le, ne, + and eq, it should compare the price with the other value. +--You should be able to compare cars (prices) but not add cars + together + +If you need help with modeling the equation for the adjusted price, +this may help + +self.adjustedprice=self.ogprice * (0.8**(2020-self.year))) +self.adjustedprice=round((self.adjustedprice),2)-self.mileage/100 + +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/filler b/2_intermediate/chapter13/practice/filler similarity index 100% rename from 3_advanced/chapter13/practice/filler rename to 2_intermediate/chapter13/practice/filler diff --git a/3_advanced/chapter13/practice/lexicographical_vector.py b/2_intermediate/chapter13/practice/lexicographical_vector.py similarity index 97% rename from 3_advanced/chapter13/practice/lexicographical_vector.py rename to 2_intermediate/chapter13/practice/lexicographical_vector.py index b828d345..43a8f202 100644 --- a/3_advanced/chapter13/practice/lexicographical_vector.py +++ b/2_intermediate/chapter13/practice/lexicographical_vector.py @@ -1,12 +1,12 @@ -""" -Reimplement the __lt__ and __gt__ in the given Vector -class(the one in this section) so that we are comparing -the vector's contents based on lexicographical ordering. -Think of lexicographical ordering as how you arrange words -in a dictionary. For instance, by lexicographical ordering, -'a' < 'ab', 'ab' < 'ad', 'bcd' > 'a'. It works analogously -for numbers, but instead, each character has been substituted -by a number. -""" - -# write your code below +""" +Reimplement the __lt__ and __gt__ in the given Vector +class(the one in this section) so that we are comparing +the vector's contents based on lexicographical ordering. +Think of lexicographical ordering as how you arrange words +in a dictionary. For instance, by lexicographical ordering, +'a' < 'ab', 'ab' < 'ad', 'bcd' > 'a'. It works analogously +for numbers, but instead, each character has been substituted +by a number. +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/line.py b/2_intermediate/chapter13/practice/line.py similarity index 97% rename from 3_advanced/chapter13/practice/line.py rename to 2_intermediate/chapter13/practice/line.py index bce1b743..6bfca643 100644 --- a/3_advanced/chapter13/practice/line.py +++ b/2_intermediate/chapter13/practice/line.py @@ -1,9 +1,9 @@ -""" -Write a class called Line which will take the arguments slope -and intercept in its constructor. When we print the class, -the __str__ method should return a string with the line expressed -in the form "y=mx+b" where m and b are the slope and intercept -respectively. -""" - -# write your code below +""" +Write a class called Line which will take the arguments slope +and intercept in its constructor. When we print the class, +the __str__ method should return a string with the line expressed +in the form "y=mx+b" where m and b are the slope and intercept +respectively. +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/matrix.py b/2_intermediate/chapter13/practice/matrix.py similarity index 97% rename from 3_advanced/chapter13/practice/matrix.py rename to 2_intermediate/chapter13/practice/matrix.py index e249ff53..91ba119a 100644 --- a/3_advanced/chapter13/practice/matrix.py +++ b/2_intermediate/chapter13/practice/matrix.py @@ -1,10 +1,10 @@ -""" -Build a class called Matrix which will take a list of lists -(containing integers) and store it as a field. Add an assertion -using the keyword assert to ensure that the list of lists is -rectangular (i.e. assert len(list_0) = len(list_i) for i in range(n)) -You should also implement a __str__ method so that we can print -the contents of the matrix using print without having to access its field. -""" - -# write your code below +""" +Build a class called Matrix which will take a list of lists +(containing integers) and store it as a field. Add an assertion +using the keyword assert to ensure that the list of lists is +rectangular (i.e. assert len(list_0) = len(list_i) for i in range(n)) +You should also implement a __str__ method so that we can print +the contents of the matrix using print without having to access its field. +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/matrix_add_subtract.py b/2_intermediate/chapter13/practice/matrix_add_subtract.py similarity index 96% rename from 3_advanced/chapter13/practice/matrix_add_subtract.py rename to 2_intermediate/chapter13/practice/matrix_add_subtract.py index 240d0e55..8aa83fe2 100644 --- a/3_advanced/chapter13/practice/matrix_add_subtract.py +++ b/2_intermediate/chapter13/practice/matrix_add_subtract.py @@ -1,25 +1,25 @@ -""" -Write a modified version of the Matrix class(that was defined in -one of the example problems in this section) with an __add__ -operation as well as a __sub__ operation. It should add matrices, -assuming that they will be of the same length. Also, the unmodified -Matrix class code will be given. -""" - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items])==list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things])==int - - def __str__(self): - return str(self.thelist) -""" - -# write your code below +""" +Write a modified version of the Matrix class(that was defined in +one of the example problems in this section) with an __add__ +operation as well as a __sub__ operation. It should add matrices, +assuming that they will be of the same length. Also, the unmodified +Matrix class code will be given. +""" + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items])==list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things])==int + + def __str__(self): + return str(self.thelist) +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/matrix_frobenius_norm.py b/2_intermediate/chapter13/practice/matrix_frobenius_norm.py similarity index 97% rename from 3_advanced/chapter13/practice/matrix_frobenius_norm.py rename to 2_intermediate/chapter13/practice/matrix_frobenius_norm.py index 434e7ef0..f7689fd1 100644 --- a/3_advanced/chapter13/practice/matrix_frobenius_norm.py +++ b/2_intermediate/chapter13/practice/matrix_frobenius_norm.py @@ -1,26 +1,26 @@ -""" -Write a modified version of the Matrix class(that was defined in -one of the example problems in this section) so that the __str__ -method instead returns a string containing a single number: the -matrix's Frobenius norm. The formula for the Frobenius norm will -be the square root of the sum of all the elements squared in the -matrix. Also, the unmodified Matrix class code will be given. -""" - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items])==list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things])==int - - def __str__(self): - return str(self.thelist) -""" - -# write your code below +""" +Write a modified version of the Matrix class(that was defined in +one of the example problems in this section) so that the __str__ +method instead returns a string containing a single number: the +matrix's Frobenius norm. The formula for the Frobenius norm will +be the square root of the sum of all the elements squared in the +matrix. Also, the unmodified Matrix class code will be given. +""" + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items])==list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things])==int + + def __str__(self): + return str(self.thelist) +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/matrix_less_greater.py b/2_intermediate/chapter13/practice/matrix_less_greater.py similarity index 96% rename from 3_advanced/chapter13/practice/matrix_less_greater.py rename to 2_intermediate/chapter13/practice/matrix_less_greater.py index e76f87f3..1d928dae 100644 --- a/3_advanced/chapter13/practice/matrix_less_greater.py +++ b/2_intermediate/chapter13/practice/matrix_less_greater.py @@ -1,25 +1,25 @@ -""" -Implement the less than and greater than operators for -the Matrix class(from a previous example problem) so that -we compare them based on their Frobenius norms which we -have implemented in the earlier section as an exercise. -Also, the unmodified Matrix class code will be given. -""" - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items])==list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things])==int - - def __str__(self): - return str(self.thelist) -""" - -# write your code below +""" +Implement the less than and greater than operators for +the Matrix class(from a previous example problem) so that +we compare them based on their Frobenius norms which we +have implemented in the earlier section as an exercise. +Also, the unmodified Matrix class code will be given. +""" + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items])==list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things])==int + + def __str__(self): + return str(self.thelist) +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/polar_coordinates.py b/2_intermediate/chapter13/practice/polar_coordinates.py similarity index 97% rename from 3_advanced/chapter13/practice/polar_coordinates.py rename to 2_intermediate/chapter13/practice/polar_coordinates.py index 2ad13c4a..98e23c30 100644 --- a/3_advanced/chapter13/practice/polar_coordinates.py +++ b/2_intermediate/chapter13/practice/polar_coordinates.py @@ -1,10 +1,10 @@ -""" -Write a class called PolarCoordinates which will take a -value called radius and angle. When we print this class, -we want the coordinates in Cartesian coordinates, or we want -you to print two values: x and y. (If you don't know the -conversion formula, x = radius * cos(angle), y = radius * sin(angle). -Use Python's built-in math library for the cosine and sine operators) -""" - -# write your code below +""" +Write a class called PolarCoordinates which will take a +value called radius and angle. When we print this class, +we want the coordinates in Cartesian coordinates, or we want +you to print two values: x and y. (If you don't know the +conversion formula, x = radius * cos(angle), y = radius * sin(angle). +Use Python's built-in math library for the cosine and sine operators) +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/triangle.py b/2_intermediate/chapter13/practice/triangle.py similarity index 97% rename from 3_advanced/chapter13/practice/triangle.py rename to 2_intermediate/chapter13/practice/triangle.py index 94e58bdf..61ec4f28 100644 --- a/3_advanced/chapter13/practice/triangle.py +++ b/2_intermediate/chapter13/practice/triangle.py @@ -1,15 +1,15 @@ -""" -Write a class called Triangle which will take three tuples -(each tuple contains two integers: the x and y coordinates -of a vertex). Then, define an __add__ operation that acts as -a translation operation. Its input argument will be a tuple -of two integers that will indicate the x and y translations -that will be applied to each coordinate. (basically, add the -tuple to each coordinate of the triangle). Also, define a -vertical and horizontal transformation tool in the form -of __mul__ which will also take a tuple of two integers that -will be multiplied to the x and y coordinates of each vertex -respectively. -""" - -# write your code below +""" +Write a class called Triangle which will take three tuples +(each tuple contains two integers: the x and y coordinates +of a vertex). Then, define an __add__ operation that acts as +a translation operation. Its input argument will be a tuple +of two integers that will indicate the x and y translations +that will be applied to each coordinate. (basically, add the +tuple to each coordinate of the triangle). Also, define a +vertical and horizontal transformation tool in the form +of __mul__ which will also take a tuple of two integers that +will be multiplied to the x and y coordinates of each vertex +respectively. +""" + +# write your code below diff --git a/3_advanced/chapter13/practice/vector.py b/2_intermediate/chapter13/practice/vector.py similarity index 97% rename from 3_advanced/chapter13/practice/vector.py rename to 2_intermediate/chapter13/practice/vector.py index 9dfd66c5..c3107c1f 100644 --- a/3_advanced/chapter13/practice/vector.py +++ b/2_intermediate/chapter13/practice/vector.py @@ -1,10 +1,10 @@ -""" -Define a Vector class so that the multiply operation is with -another Vector instead. The multiply operation should be the -inner or dot product of the two vectors. That means that each -element in the vector should be multiplied with its -corresponding element in the other vector, and then summed. -A scalar (regular number) should be returned. -""" - -# write your code below +""" +Define a Vector class so that the multiply operation is with +another Vector instead. The multiply operation should be the +inner or dot product of the two vectors. That means that each +element in the vector should be multiplied with its +corresponding element in the other vector, and then summed. +A scalar (regular number) should be returned. +""" + +# write your code below diff --git a/3_advanced/chapter13/solutions/car.py b/2_intermediate/chapter13/solutions/car.py similarity index 97% rename from 3_advanced/chapter13/solutions/car.py rename to 2_intermediate/chapter13/solutions/car.py index 976626b8..5f5c6717 100644 --- a/3_advanced/chapter13/solutions/car.py +++ b/2_intermediate/chapter13/solutions/car.py @@ -1,124 +1,124 @@ -""" -A new car is said to devalue 20% in the first year. Assuming that -this trend continues and that mileage divided by 100 is all you -subtract from this adjusted price, make a class "car" that has at -least the attributes "year, original price (aka og price), and -mileage." Also, follow these guidelines. - ---When using str() on a car, it should return the year, original - price, mileage, and adjusted price. ---When adding, it should add the value to its mileage before - adjusting the adjusted price. ---When multiplying, it should multiply the mileage by the value - before adjusting the adjusted price ---(While subtracting or dividing mileage on a car to sell it is - totally unethical,) When subtracting or dividing, it should - subtract the value from its mileage or divide its mileage by - the value before adjusting the adjusted price. ---When checking gt(which means greater than), lt, ge, le, ne, - and eq, it should compare the price with the other value. ---You should be able to compare cars (prices) but not add cars - together - -If you need help with modeling the equation for the adjusted price, -this may help - -self.adjustedprice=self.ogprice * (0.8**(2020-self.year))) -self.adjustedprice=round((self.adjustedprice),2)-self.mileage/100 - -""" - -# write your code below - - -class car: - def __init__(self, year, brand, ogprice, mileage): - self.year = year - self.brand = brand - self.ogprice = ogprice - self.mileage = mileage - self.adjustedprice = self.adjustprice() - - def adjustprice(self): - self.adjustedprice = float(self.ogprice * (0.8 ** (2020 - self.year))) - self.adjustedprice = ( - round((self.adjustedprice), 2) - self.mileage / 100 - ) - return self.adjustedprice - - def __str__(self): - return "This car is a {} model from {}. It was originally worth ${} and \ - has driven {} miles. It is now worth {}".format( - self.year, - self.brand, - self.ogprice, - self.mileage, - self.adjustedprice, - ) - - def __lt__(self, value): - if type(value) == car: - return self.adjustedprice < value.adjustedprice - elif type(value) != object: - return self.adjustedprice < value - - def __gt__(self, value): - if type(value) == car: - return self.adjustedprice > value.adjustedprice - elif type(value) != object: - return self.adjustedprice > value - - def __eq__(self, value): - if type(value) == car: - return self.adjustedprice == value.adjustedprice - elif type(value) != object: - return self.adjustedprice == value - - def __ne__(self, value): - if type(value) == car: - return self.adjustedprice != value.adjustedprice - elif type(value) != object: - return self.adjustedprice != value - - def __le__(self, value): - if type(value) == car: - return self.adjustedprice <= value.adjustedprice - elif type(value) != object: - return self.adjustedprice <= value - - def __ge__(self, value): - if type(value) == car: - return self.adjustedprice >= value.adjustedprice - elif type(value) != object: - return self.adjustedprice >= value - - def __add__(self, value): - if type(value) == car: - return None - elif type(value) != object: - self.mileage += value - self.adjustedprice = self.adjustprice() - - def __sub__(self, value): - if type(value) == car: - return None - elif type(value) != object: - self.mileage -= value - self.adjustedprice = self.adjustprice() - - def __truediv__(self, value): - if type(value) == car: - return None - elif type(value) != object: - self.mileage = self.mileage / value - self.adjustedprice = self.adjustprice() - - def __mul__(self, value): - if type(value) == car: - return None - elif type(value) != object: - self.mileage = self.mileage * value - self.adjustedprice = self.adjustprice() - - -Maserati = car(2009, "porsche", 30000, 14000) +""" +A new car is said to devalue 20% in the first year. Assuming that +this trend continues and that mileage divided by 100 is all you +subtract from this adjusted price, make a class "car" that has at +least the attributes "year, original price (aka og price), and +mileage." Also, follow these guidelines. + +--When using str() on a car, it should return the year, original + price, mileage, and adjusted price. +--When adding, it should add the value to its mileage before + adjusting the adjusted price. +--When multiplying, it should multiply the mileage by the value + before adjusting the adjusted price +--(While subtracting or dividing mileage on a car to sell it is + totally unethical,) When subtracting or dividing, it should + subtract the value from its mileage or divide its mileage by + the value before adjusting the adjusted price. +--When checking gt(which means greater than), lt, ge, le, ne, + and eq, it should compare the price with the other value. +--You should be able to compare cars (prices) but not add cars + together + +If you need help with modeling the equation for the adjusted price, +this may help + +self.adjustedprice=self.ogprice * (0.8**(2020-self.year))) +self.adjustedprice=round((self.adjustedprice),2)-self.mileage/100 + +""" + +# write your code below + + +class car: + def __init__(self, year, brand, ogprice, mileage): + self.year = year + self.brand = brand + self.ogprice = ogprice + self.mileage = mileage + self.adjustedprice = self.adjustprice() + + def adjustprice(self): + self.adjustedprice = float(self.ogprice * (0.8 ** (2020 - self.year))) + self.adjustedprice = ( + round((self.adjustedprice), 2) - self.mileage / 100 + ) + return self.adjustedprice + + def __str__(self): + return "This car is a {} model from {}. It was originally worth ${} and \ + has driven {} miles. It is now worth {}".format( + self.year, + self.brand, + self.ogprice, + self.mileage, + self.adjustedprice, + ) + + def __lt__(self, value): + if type(value) == car: + return self.adjustedprice < value.adjustedprice + elif type(value) != object: + return self.adjustedprice < value + + def __gt__(self, value): + if type(value) == car: + return self.adjustedprice > value.adjustedprice + elif type(value) != object: + return self.adjustedprice > value + + def __eq__(self, value): + if type(value) == car: + return self.adjustedprice == value.adjustedprice + elif type(value) != object: + return self.adjustedprice == value + + def __ne__(self, value): + if type(value) == car: + return self.adjustedprice != value.adjustedprice + elif type(value) != object: + return self.adjustedprice != value + + def __le__(self, value): + if type(value) == car: + return self.adjustedprice <= value.adjustedprice + elif type(value) != object: + return self.adjustedprice <= value + + def __ge__(self, value): + if type(value) == car: + return self.adjustedprice >= value.adjustedprice + elif type(value) != object: + return self.adjustedprice >= value + + def __add__(self, value): + if type(value) == car: + return None + elif type(value) != object: + self.mileage += value + self.adjustedprice = self.adjustprice() + + def __sub__(self, value): + if type(value) == car: + return None + elif type(value) != object: + self.mileage -= value + self.adjustedprice = self.adjustprice() + + def __truediv__(self, value): + if type(value) == car: + return None + elif type(value) != object: + self.mileage = self.mileage / value + self.adjustedprice = self.adjustprice() + + def __mul__(self, value): + if type(value) == car: + return None + elif type(value) != object: + self.mileage = self.mileage * value + self.adjustedprice = self.adjustprice() + + +Maserati = car(2009, "porsche", 30000, 14000) diff --git a/3_advanced/chapter13/solutions/filler b/2_intermediate/chapter13/solutions/filler similarity index 100% rename from 3_advanced/chapter13/solutions/filler rename to 2_intermediate/chapter13/solutions/filler diff --git a/3_advanced/chapter13/solutions/lexicographical_vector.py b/2_intermediate/chapter13/solutions/lexicographical_vector.py similarity index 97% rename from 3_advanced/chapter13/solutions/lexicographical_vector.py rename to 2_intermediate/chapter13/solutions/lexicographical_vector.py index 6b7a05f4..de8faafd 100644 --- a/3_advanced/chapter13/solutions/lexicographical_vector.py +++ b/2_intermediate/chapter13/solutions/lexicographical_vector.py @@ -1,72 +1,72 @@ -""" -Reimplement the __lt__ and __gt__ in the given Vector -class(the one in this section) so that we are comparing -the vector's contents based on lexicographical ordering. -Think of lexicographical ordering as how you arrange words -in a dictionary. For instance, by lexicographical ordering, -'a' < 'ab', 'ab' < 'ad', 'bcd' > 'a'. It works analogously -for numbers, but instead, each character has been substituted -by a number. -""" - -# write your code below - - -class Vector: - def __init__(self, vals): - self.vals = vals - self.length = len(self.vals) - self.scalar = 0 - - def __mul__(self, vec): - ... # see above example - - def morecheck(self, vec, shorter): - for i in range(shorter.length): - if self.vals[i] > vec.vals[i]: - return True - if self.vals[i] < vec.vals[i]: - return False - - def __gt__(self, vec): - assert type(vec) == Vector - if self.length > vec.length: - a = self.morecheck(vec, vec) - if a is not None: - return a - return True # if all other values ==, self = longer/greater - if self.length < vec.length: - a = self.morecheck(vec, self) - if a is not None: - return a - return False # if all other values ==, self = shorter/smaller - if self.length == vec.length: - a = self.morecheck(vec, self) - if a is not None: - return a - return False # if all other values ==, self = equal/not greater - - def lesscheck(self, vec, shorter): - for i in range(shorter.length): - if self.vals[i] < vec.vals[i]: - return True - if self.vals[i] > vec.vals[i]: - return False - - def __lt__(self, vec): - assert type(vec) == Vector - if self.length > vec.length: - a = self.lesscheck(vec, vec) - if a is not None: - return a - return False # if all other values ==, self = longer/greater - if self.length < vec.length: - a = self.lesscheck(vec, self) - if a is not None: - return a - return True # if all other values ==, self = shorter/smaller - if self.length == vec.length: - a = self.lesscheck(vec, self) - if a is not None: - return a - return False # if all other values ==, self = equal/not less +""" +Reimplement the __lt__ and __gt__ in the given Vector +class(the one in this section) so that we are comparing +the vector's contents based on lexicographical ordering. +Think of lexicographical ordering as how you arrange words +in a dictionary. For instance, by lexicographical ordering, +'a' < 'ab', 'ab' < 'ad', 'bcd' > 'a'. It works analogously +for numbers, but instead, each character has been substituted +by a number. +""" + +# write your code below + + +class Vector: + def __init__(self, vals): + self.vals = vals + self.length = len(self.vals) + self.scalar = 0 + + def __mul__(self, vec): + ... # see above example + + def morecheck(self, vec, shorter): + for i in range(shorter.length): + if self.vals[i] > vec.vals[i]: + return True + if self.vals[i] < vec.vals[i]: + return False + + def __gt__(self, vec): + assert type(vec) == Vector + if self.length > vec.length: + a = self.morecheck(vec, vec) + if a is not None: + return a + return True # if all other values ==, self = longer/greater + if self.length < vec.length: + a = self.morecheck(vec, self) + if a is not None: + return a + return False # if all other values ==, self = shorter/smaller + if self.length == vec.length: + a = self.morecheck(vec, self) + if a is not None: + return a + return False # if all other values ==, self = equal/not greater + + def lesscheck(self, vec, shorter): + for i in range(shorter.length): + if self.vals[i] < vec.vals[i]: + return True + if self.vals[i] > vec.vals[i]: + return False + + def __lt__(self, vec): + assert type(vec) == Vector + if self.length > vec.length: + a = self.lesscheck(vec, vec) + if a is not None: + return a + return False # if all other values ==, self = longer/greater + if self.length < vec.length: + a = self.lesscheck(vec, self) + if a is not None: + return a + return True # if all other values ==, self = shorter/smaller + if self.length == vec.length: + a = self.lesscheck(vec, self) + if a is not None: + return a + return False # if all other values ==, self = equal/not less diff --git a/3_advanced/chapter13/solutions/line.py b/2_intermediate/chapter13/solutions/line.py similarity index 96% rename from 3_advanced/chapter13/solutions/line.py rename to 2_intermediate/chapter13/solutions/line.py index 335aa00f..a524ff83 100644 --- a/3_advanced/chapter13/solutions/line.py +++ b/2_intermediate/chapter13/solutions/line.py @@ -1,23 +1,23 @@ -""" -Write a class called Line which will take the arguments slope -and intercept in its constructor. When we print the class, -the __str__ method should return a string with the line expressed -in the form "y=mx+b" where m and b are the slope and intercept -respectively. -""" - -# write your code below - - -class Line: - def __init__(self, slope, intercept): - self.slope = slope - self.intercept = intercept - - def __str__(self): - self.equation = "y={}x+{}".format(self.slope, self.intercept) - return self.equation - - -myline = Line(3, 1) -print(str(myline)) +""" +Write a class called Line which will take the arguments slope +and intercept in its constructor. When we print the class, +the __str__ method should return a string with the line expressed +in the form "y=mx+b" where m and b are the slope and intercept +respectively. +""" + +# write your code below + + +class Line: + def __init__(self, slope, intercept): + self.slope = slope + self.intercept = intercept + + def __str__(self): + self.equation = "y={}x+{}".format(self.slope, self.intercept) + return self.equation + + +myline = Line(3, 1) +print(str(myline)) diff --git a/3_advanced/chapter13/solutions/matrix.py b/2_intermediate/chapter13/solutions/matrix.py similarity index 97% rename from 3_advanced/chapter13/solutions/matrix.py rename to 2_intermediate/chapter13/solutions/matrix.py index 180755a5..fd1ba70f 100644 --- a/3_advanced/chapter13/solutions/matrix.py +++ b/2_intermediate/chapter13/solutions/matrix.py @@ -1,27 +1,27 @@ -""" -Build a class called Matrix which will take a list of lists -(containing integers) and store it as a field. Add an assertion -using the keyword assert to ensure that the list of lists is -rectangular (i.e. assert len(list_0) = len(list_i) for i in range(n)) -You should also implement a __str__ method so that we can print -the contents of the matrix using print without having to access its field. -""" - -# write your code below - - -class Matrix: - def __init__(self, thelist: list): - self.thelist = thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - - def __str__(self): - return str(self.thelist) - - -mymatrix = Matrix([[3, 4], [7, 8], [4, 8]]) -print(str(mymatrix)) +""" +Build a class called Matrix which will take a list of lists +(containing integers) and store it as a field. Add an assertion +using the keyword assert to ensure that the list of lists is +rectangular (i.e. assert len(list_0) = len(list_i) for i in range(n)) +You should also implement a __str__ method so that we can print +the contents of the matrix using print without having to access its field. +""" + +# write your code below + + +class Matrix: + def __init__(self, thelist: list): + self.thelist = thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + + def __str__(self): + return str(self.thelist) + + +mymatrix = Matrix([[3, 4], [7, 8], [4, 8]]) +print(str(mymatrix)) diff --git a/3_advanced/chapter13/solutions/matrix_add_subtract.py b/2_intermediate/chapter13/solutions/matrix_add_subtract.py similarity index 97% rename from 3_advanced/chapter13/solutions/matrix_add_subtract.py rename to 2_intermediate/chapter13/solutions/matrix_add_subtract.py index 769a5984..d2e24973 100644 --- a/3_advanced/chapter13/solutions/matrix_add_subtract.py +++ b/2_intermediate/chapter13/solutions/matrix_add_subtract.py @@ -1,57 +1,57 @@ -""" -Write a modified version of the Matrix class(that was defined in -one of the example problems in this section) with an __add__ -operation as well as a __sub__ operation. It should add matrices, -assuming that they will be of the same length. Also, the unmodified -Matrix class code will be given. -""" - -# write your code below - - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - - def __str__(self): - return str(self.thelist) -""" - - -class Matrix: - def __init__(self, thelist: list): - self.thelist = thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - - def __str__(self): - return str(self.thelist) - - def __add__(self, other): - assert type(other) == Matrix - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.thelist[items][things] += other.thelist[items][things] - - def __sub__(self, other): - assert type(other) == Matrix - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.thelist[items][things] -= other.thelist[items][things] - - -mymatrix = Matrix([[3, 4], [7, 8]]) -othermatrix = Matrix([[5, 6], [7, 8]]) -mymatrix - othermatrix -print(mymatrix.thelist) +""" +Write a modified version of the Matrix class(that was defined in +one of the example problems in this section) with an __add__ +operation as well as a __sub__ operation. It should add matrices, +assuming that they will be of the same length. Also, the unmodified +Matrix class code will be given. +""" + +# write your code below + + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + + def __str__(self): + return str(self.thelist) +""" + + +class Matrix: + def __init__(self, thelist: list): + self.thelist = thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + + def __str__(self): + return str(self.thelist) + + def __add__(self, other): + assert type(other) == Matrix + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.thelist[items][things] += other.thelist[items][things] + + def __sub__(self, other): + assert type(other) == Matrix + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.thelist[items][things] -= other.thelist[items][things] + + +mymatrix = Matrix([[3, 4], [7, 8]]) +othermatrix = Matrix([[5, 6], [7, 8]]) +mymatrix - othermatrix +print(mymatrix.thelist) diff --git a/3_advanced/chapter13/solutions/matrix_frobenius_norm.py b/2_intermediate/chapter13/solutions/matrix_frobenius_norm.py similarity index 96% rename from 3_advanced/chapter13/solutions/matrix_frobenius_norm.py rename to 2_intermediate/chapter13/solutions/matrix_frobenius_norm.py index df99717b..09be9792 100644 --- a/3_advanced/chapter13/solutions/matrix_frobenius_norm.py +++ b/2_intermediate/chapter13/solutions/matrix_frobenius_norm.py @@ -1,53 +1,53 @@ -""" -Write a modified version of the Matrix class(that was defined in -one of the example problems in this section) so that the __str__ -method instead returns a string containing a single number: the -matrix's Frobenius norm. The formula for the Frobenius norm will -be the square root of the sum of all the elements squared in the -matrix. Also, the unmodified Matrix class code will be given. -""" - -# write your code below - -import math - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - - def __str__(self): - return str(self.thelist) -""" - - -class Matrix: - def __init__(self, thelist: list): - self.thelist = thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - self.froebiannorm() - - def froebiannorm(self): - self.squared = 0 - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.squared += self.thelist[items][things] ** 2 - self.norm = math.sqrt(self.squared) - - def __str__(self): - return str(self.norm) - - -mymatrix = Matrix([[3, 4], [7, 8]]) -print(str(mymatrix)) +""" +Write a modified version of the Matrix class(that was defined in +one of the example problems in this section) so that the __str__ +method instead returns a string containing a single number: the +matrix's Frobenius norm. The formula for the Frobenius norm will +be the square root of the sum of all the elements squared in the +matrix. Also, the unmodified Matrix class code will be given. +""" + +# write your code below + +import math + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + + def __str__(self): + return str(self.thelist) +""" + + +class Matrix: + def __init__(self, thelist: list): + self.thelist = thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + self.froebiannorm() + + def froebiannorm(self): + self.squared = 0 + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.squared += self.thelist[items][things] ** 2 + self.norm = math.sqrt(self.squared) + + def __str__(self): + return str(self.norm) + + +mymatrix = Matrix([[3, 4], [7, 8]]) +print(str(mymatrix)) diff --git a/3_advanced/chapter13/solutions/matrix_less_greater.py b/2_intermediate/chapter13/solutions/matrix_less_greater.py similarity index 96% rename from 3_advanced/chapter13/solutions/matrix_less_greater.py rename to 2_intermediate/chapter13/solutions/matrix_less_greater.py index bb798b76..5521a3c6 100644 --- a/3_advanced/chapter13/solutions/matrix_less_greater.py +++ b/2_intermediate/chapter13/solutions/matrix_less_greater.py @@ -1,74 +1,74 @@ -""" -Implement the less than and greater than operators for -the Matrix class(from a previous example problem) so that -we compare them based on their Frobenius norms which we -have implemented in the earlier section as an exercise. -Also, the unmodified Matrix class code will be given. -""" - -# write your code below - -import math - -""" -This is the unmodified Matrix class code. - -class Matrix: - def __init__(self,thelist: list): - self.thelist=thelist - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - - def __str__(self): - return str(self.thelist) -""" - - -class Matrix: - def __init__(self, thelist: list): - self.thelist = thelist - self.norm = 0 - for items in range(len(self.thelist)): - assert type(self.thelist[items]) == list - assert len(self.thelist[0]) == len(self.thelist[items]) - for things in range(len(self.thelist[items])): - assert type(self.thelist[items][things]) == int - self.froebiannorm() - - def froebiannorm(self): - self.squared = 0 - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.squared += self.thelist[items][things] ** 2 - self.norm = math.sqrt(self.squared) - - def __str__(self): - return str(self.norm) - - def __add__(self, other): - assert type(other) == Matrix - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.thelist[items][things] += other.thelist[items][things] - - def __sub__(self, other): - assert type(other) == Matrix - for items in range(len(self.thelist)): - for things in range(len(self.thelist[items])): - self.thelist[items][things] -= other.thelist[items][things] - - def __lt__(self, other): - assert type(other) == Matrix - return self.norm < other.norm - - def __gt__(self, other): - assert type(other) == Matrix - return self.norm > other.norm - - -mymatrix = Matrix([[3, 4], [7, 8]]) -othermatrix = Matrix([[5, 6], [7, 8]]) -print(mymatrix > othermatrix) +""" +Implement the less than and greater than operators for +the Matrix class(from a previous example problem) so that +we compare them based on their Frobenius norms which we +have implemented in the earlier section as an exercise. +Also, the unmodified Matrix class code will be given. +""" + +# write your code below + +import math + +""" +This is the unmodified Matrix class code. + +class Matrix: + def __init__(self,thelist: list): + self.thelist=thelist + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + + def __str__(self): + return str(self.thelist) +""" + + +class Matrix: + def __init__(self, thelist: list): + self.thelist = thelist + self.norm = 0 + for items in range(len(self.thelist)): + assert type(self.thelist[items]) == list + assert len(self.thelist[0]) == len(self.thelist[items]) + for things in range(len(self.thelist[items])): + assert type(self.thelist[items][things]) == int + self.froebiannorm() + + def froebiannorm(self): + self.squared = 0 + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.squared += self.thelist[items][things] ** 2 + self.norm = math.sqrt(self.squared) + + def __str__(self): + return str(self.norm) + + def __add__(self, other): + assert type(other) == Matrix + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.thelist[items][things] += other.thelist[items][things] + + def __sub__(self, other): + assert type(other) == Matrix + for items in range(len(self.thelist)): + for things in range(len(self.thelist[items])): + self.thelist[items][things] -= other.thelist[items][things] + + def __lt__(self, other): + assert type(other) == Matrix + return self.norm < other.norm + + def __gt__(self, other): + assert type(other) == Matrix + return self.norm > other.norm + + +mymatrix = Matrix([[3, 4], [7, 8]]) +othermatrix = Matrix([[5, 6], [7, 8]]) +print(mymatrix > othermatrix) diff --git a/3_advanced/chapter13/solutions/polar_coordinates.py b/2_intermediate/chapter13/solutions/polar_coordinates.py similarity index 96% rename from 3_advanced/chapter13/solutions/polar_coordinates.py rename to 2_intermediate/chapter13/solutions/polar_coordinates.py index 9f038383..ff743831 100644 --- a/3_advanced/chapter13/solutions/polar_coordinates.py +++ b/2_intermediate/chapter13/solutions/polar_coordinates.py @@ -1,27 +1,27 @@ -""" -Write a class called PolarCoordinates which will take a -value called radius and angle. When we print this class, -we want the coordinates in Cartesian coordinates, or we want -you to print two values: x and y. (If you don't know the -conversion formula, x = radius * cos(angle), y = radius * sin(angle). -Use Python's built-in math library for the cosine and sine operators) -""" - -# write your code below - -import math - - -class PolarCoordinates: - def __init__(self, radius, angle): - self.radius = radius - self.angle = angle - - def __str__(self): - self.x = self.radius * math.cos(self.angle) - self.y = self.radius * math.sin(self.angle) - return "{},{}".format(self.x, self.y) - - -group = PolarCoordinates(2, math.pi) -print(str(group)) +""" +Write a class called PolarCoordinates which will take a +value called radius and angle. When we print this class, +we want the coordinates in Cartesian coordinates, or we want +you to print two values: x and y. (If you don't know the +conversion formula, x = radius * cos(angle), y = radius * sin(angle). +Use Python's built-in math library for the cosine and sine operators) +""" + +# write your code below + +import math + + +class PolarCoordinates: + def __init__(self, radius, angle): + self.radius = radius + self.angle = angle + + def __str__(self): + self.x = self.radius * math.cos(self.angle) + self.y = self.radius * math.sin(self.angle) + return "{},{}".format(self.x, self.y) + + +group = PolarCoordinates(2, math.pi) +print(str(group)) diff --git a/3_advanced/chapter13/solutions/triangle.py b/2_intermediate/chapter13/solutions/triangle.py similarity index 97% rename from 3_advanced/chapter13/solutions/triangle.py rename to 2_intermediate/chapter13/solutions/triangle.py index b9168a33..31043ab8 100644 --- a/3_advanced/chapter13/solutions/triangle.py +++ b/2_intermediate/chapter13/solutions/triangle.py @@ -1,45 +1,45 @@ -""" -Write a class called Triangle which will take three tuples -(each tuple contains two integers: the x and y coordinates -of a vertex). Then, define an __add__ operation that acts as -a translation operation. Its input argument will be a tuple -of two integers that will indicate the x and y translations -that will be applied to each coordinate. (basically, add the -tuple to each coordinate of the triangle). Also, define a -vertical and horizontal transformation tool in the form -of __mul__ which will also take a tuple of two integers that -will be multiplied to the x and y coordinates of each vertex -respectively. -""" - -# write your code below - - -class Triangle: - def __init__(self, pair1, pair2, pair3): - self.coordinatelist = [pair1, pair2, pair3] - for i in range(len(self.coordinatelist)): - assert ( - type(self.coordinatelist[i]) == tuple - and len(self.coordinatelist[i]) == 2 - ) - self.coordinatelist[i] = list(self.coordinatelist[i]) - - def __add__(self, other): - assert type(other) == tuple and len(other) == 2 - for i in range(len(self.coordinatelist)): - self.coordinatelist[i][0] += other[0] - self.coordinatelist[i][1] += other[1] - return tuple(self.coordinatelist) - - def __mul__(self, other): - assert type(other) == tuple and len(other) == 2 - for i in range(len(self.coordinatelist)): - self.coordinatelist[i][0] *= other[0] - self.coordinatelist[i][1] *= other[1] - return tuple(self.coordinatelist) - - -mytriangle = Triangle((0, 0), (1, 0), (0, 1)) -print(mytriangle + (1, 1)) -print(mytriangle * (2, 2)) +""" +Write a class called Triangle which will take three tuples +(each tuple contains two integers: the x and y coordinates +of a vertex). Then, define an __add__ operation that acts as +a translation operation. Its input argument will be a tuple +of two integers that will indicate the x and y translations +that will be applied to each coordinate. (basically, add the +tuple to each coordinate of the triangle). Also, define a +vertical and horizontal transformation tool in the form +of __mul__ which will also take a tuple of two integers that +will be multiplied to the x and y coordinates of each vertex +respectively. +""" + +# write your code below + + +class Triangle: + def __init__(self, pair1, pair2, pair3): + self.coordinatelist = [pair1, pair2, pair3] + for i in range(len(self.coordinatelist)): + assert ( + type(self.coordinatelist[i]) == tuple + and len(self.coordinatelist[i]) == 2 + ) + self.coordinatelist[i] = list(self.coordinatelist[i]) + + def __add__(self, other): + assert type(other) == tuple and len(other) == 2 + for i in range(len(self.coordinatelist)): + self.coordinatelist[i][0] += other[0] + self.coordinatelist[i][1] += other[1] + return tuple(self.coordinatelist) + + def __mul__(self, other): + assert type(other) == tuple and len(other) == 2 + for i in range(len(self.coordinatelist)): + self.coordinatelist[i][0] *= other[0] + self.coordinatelist[i][1] *= other[1] + return tuple(self.coordinatelist) + + +mytriangle = Triangle((0, 0), (1, 0), (0, 1)) +print(mytriangle + (1, 1)) +print(mytriangle * (2, 2)) diff --git a/3_advanced/chapter13/solutions/vector.py b/2_intermediate/chapter13/solutions/vector.py similarity index 96% rename from 3_advanced/chapter13/solutions/vector.py rename to 2_intermediate/chapter13/solutions/vector.py index 7bbfc15d..95c90664 100644 --- a/3_advanced/chapter13/solutions/vector.py +++ b/2_intermediate/chapter13/solutions/vector.py @@ -1,39 +1,39 @@ -""" -Define a Vector class so that the multiply operation is with -another Vector instead. The multiply operation should be the -inner or dot product of the two vectors. That means that each -element in the vector should be multiplied with its -corresponding element in the other vector, and then summed. -A scalar (regular number) should be returned. -""" - -# write your code below - - -class Vector: - def __init__(self, vals): - self.vals = vals - self.length = len(self.vals) - self.scalar = 0 - - def __mul__(self, vec): - assert type(vec) == Vector - a = 0 - if self.length >= vec.length: - for i in range(vec.length): - self.scalar += self.vals[i] * vec.vals[i] - while a + vec.length < self.length: - self.scalar += self.vals[i] - a += 1 - if self.length < vec.length: - for i in range(self.length): - self.scalar += self.vals[i] * vec.vals[i] - while (a + self.length) < vec.length: - self.scalar += self.vals[i] - a += 1 - return self.scalar - - -vector1 = Vector([2, 3, 2]) -vector2 = Vector([3, 4, 5]) -print(vector1 * vector2) # should give 28 +""" +Define a Vector class so that the multiply operation is with +another Vector instead. The multiply operation should be the +inner or dot product of the two vectors. That means that each +element in the vector should be multiplied with its +corresponding element in the other vector, and then summed. +A scalar (regular number) should be returned. +""" + +# write your code below + + +class Vector: + def __init__(self, vals): + self.vals = vals + self.length = len(self.vals) + self.scalar = 0 + + def __mul__(self, vec): + assert type(vec) == Vector + a = 0 + if self.length >= vec.length: + for i in range(vec.length): + self.scalar += self.vals[i] * vec.vals[i] + while a + vec.length < self.length: + self.scalar += self.vals[i] + a += 1 + if self.length < vec.length: + for i in range(self.length): + self.scalar += self.vals[i] * vec.vals[i] + while (a + self.length) < vec.length: + self.scalar += self.vals[i] + a += 1 + return self.scalar + + +vector1 = Vector([2, 3, 2]) +vector2 = Vector([3, 4, 5]) +print(vector1 * vector2) # should give 28 diff --git a/2_intermediate/intermediateproject/gui_memory_cards.py b/2_intermediate/intermediateproject/gui_memory_cards.py deleted file mode 100644 index c3124055..00000000 --- a/2_intermediate/intermediateproject/gui_memory_cards.py +++ /dev/null @@ -1,140 +0,0 @@ -import random -import tkinter as tk -from functools import partial -import time - - -class memcards(tk.Frame): - def __init__(self, parent, items): - super().__init__(parent) - self.parent = parent - self.grid() - - self.flipped = set() - self.current = [] - self.memorder = [None for i in range(len(items) * 2)] - self.dictionary = items - self.keys = list(items.keys()) - self.values = list(items.values()) - self.shuffle() - self.definemap() - self.createlabel() - - def createlabel(self): - self.label = tk.Label( - self, - text="The Rows and Columns start at 0, not 1; #" - + " means unflipped; _ means correct", - ) - self.label.grid( - row=len(self.memorder) // len(self.currmap[0]), - column=0, - rowspan=2, - columnspan=len(self.currmap[0]), - sticky=tk.W + tk.S, - ) - self.label.config(bg="purple") - - def definemap(self): - # figure out potential heights and widths - divisors = [] - for i in range(len(self.memorder)): - if (len(self.memorder)) % (i + 1) == 0: - divisors.append(i + 1) - # gets the real width and height of map - width = divisors[len(divisors) // 2] - height = divisors[(len(divisors) // 2) - 1] - themap = [[None for i in range(width)] for x in range(height)] - for x in range(height): - for i in range(width): - themap[x][i] = tk.Button( - self, text="#", command=partial(self.flip, x, i) - ) - themap[x][i].grid(row=x, column=i, ipadx=10, ipady=5) - themap[x][i].config(bg="light blue") - self.currmap = themap - - def shuffle(self): - doneitems = {} - while len(self.memorder) > len(doneitems): - itemloc = random.randint(0, len(self.keys) - 1) - b = random.randint(0, 1) - memorderloc = random.randint(0, len(self.keys * 2) - 1) - if memorderloc not in doneitems.values(): - if b == 0 and self.memorder[memorderloc] not in self.keys: - self.memorder[memorderloc] = self.keys[itemloc] - doneitems[self.memorder[memorderloc]] = memorderloc - if b == 1 and self.memorder[memorderloc] not in self.values: - self.memorder[memorderloc] = self.values[itemloc] - doneitems[self.memorder[memorderloc]] = memorderloc - - def flip(self, row, column): - self.currmap[row][column]["text"] = self.memorder[ - (row * len(self.currmap[0])) + column - ] - self.currmap[row][column].grid(row=row, column=column) - self.current.append( - [ - self.memorder[(row * len(self.currmap[0])) + column], - [row, column], - ] - ) - - def unflip(self, correct: bool): - if correct: - self.flipped.add(self.current[0][0]) - self.flipped.add(self.current[1][0]) - self.currmap[self.current[0][1][0]][self.current[0][1][1]][ - "text" - ] = "__" - self.currmap[self.current[1][1][0]][self.current[1][1][1]][ - "text" - ] = "__" - self.label["text"] = "Correct" - else: - self.currmap[self.current[0][1][0]][self.current[0][1][1]][ - "text" - ] = "#" - self.currmap[self.current[1][1][0]][self.current[1][1][1]][ - "text" - ] = "#" - self.label["text"] = "Incorrect" - self.current = [] - - def mainloop(self): - try: - while 1: - self.update_idletasks() - self.update() - if len(self.current) == 2: - if ( - self.current[0][0] in self.dictionary - and self.dictionary[self.current[0][0]] - == self.current[1][0] - ): - time.sleep(0.5) - self.unflip(True) - elif ( - self.current[1][0] in self.dictionary - and self.dictionary[self.current[1][0]] - == self.current[0][0] - ): - time.sleep(0.5) - self.unflip(True) - else: - time.sleep(0.5) - self.unflip(False) - if len(self.flipped) == len(self.memorder): - self.label["text"] = "Congratulations, you win!" - time.sleep(0.01) - except tk.TclError: - print("Exited successfully, Game Over") - - -items = {"a": 1, "b": 2, "c": 3, "d": 4} - - -root = tk.Tk() -root.minsize(150, 100) -app = memcards(root, items) -app.mainloop() diff --git a/2_intermediate/intermediateproject/memory_cards.py b/2_intermediate/intermediateproject/memory_cards.py deleted file mode 100644 index be1e21f1..00000000 --- a/2_intermediate/intermediateproject/memory_cards.py +++ /dev/null @@ -1,93 +0,0 @@ -import random - - -class memcards: - def __init__(self, items: dict): - self.memorder = [None for i in range(len(items) * 2)] - self.dict = items - self.keys = list(items.keys()) - self.values = list(items.values()) - self.shuffle() - self.currmap = self.definemap() - self.play() - - def play(self): - instructions = ( - "The Rows and Columns start at 0, not 1; # means " - + "unflipped; _ means correct" - ) - # I used a set, but you could also have used a list or - # a dictionary so long as you checked if elements were already - # in the list before adding them - flipped = set() - print(instructions) - while len(self.memorder) > len(flipped): - self.display() - row1 = int(input("Which row would you like to select? ")) - column1 = int(input("Which column would you like to select? ")) - item1 = self.flip(row1, column1, "reveal") - row2 = int(input("Which row would you like to select now? ")) - column2 = int(input("Which column would you like to select? ")) - item2 = self.flip(row2, column2, "reveal") - if item1 in self.dict and self.dict[item1] == item2: - print("Correct!") - flipped.add(self.flip(row1, column1, "correct")) - flipped.add(self.flip(row2, column2, "correct")) - elif item2 in self.dict and self.dict[item2] == item1: - print("Correct!") - flipped.add(self.flip(row1, column1, "correct")) - flipped.add(self.flip(row2, column2, "correct")) - else: - self.flip(row1, column1, "hide") - self.flip(row2, column2, "hide") - print("Try again, incorrect :(") - print("Congratulations, you win! You found all of the pairs!") - - def definemap(self): - # figure out potential heights and widths - divisors = [] - for i in range(len(self.memorder)): - if (len(self.memorder)) % (i + 1) == 0: - divisors.append(i + 1) - # gets the real width and height of map - width = divisors[len(divisors) // 2] - height = divisors[(len(divisors) // 2) - 1] - themap = [["# " for i in range(width)] for i in range(height)] - return themap - - def shuffle(self): - doneitems = {} - while len(self.memorder) > len(doneitems): - itemloc = random.randint(0, len(self.keys) - 1) - b = random.randint(0, 1) - memorderloc = random.randint(0, len(self.keys * 2) - 1) - if memorderloc not in doneitems.values(): - if b == 0 and self.memorder[memorderloc] not in self.keys: - self.memorder[memorderloc] = self.keys[itemloc] - doneitems[self.memorder[memorderloc]] = memorderloc - if b == 1 and self.memorder[memorderloc] not in self.values: - self.memorder[memorderloc] = self.values[itemloc] - doneitems[self.memorder[memorderloc]] = memorderloc - - def flip(self, row, column, operation): - if operation == "reveal": - self.currmap[row][column] = self.memorder[ - (row * len(self.currmap[0])) + column - ] - self.display() - return self.memorder[(row * len(self.currmap[0])) + column] - if operation == "hide": - self.currmap[row][column] = "# " - return None - if operation == "correct": - self.currmap[row][column] = "_ " - return self.memorder[(row * len(self.currmap[0])) + column] - - def display(self): - for i in range(len(self.currmap)): - print(self.currmap[i]) - - -mydiction = {"a": 1, "b": 2, "c": 3} - -letsplay = memcards(mydiction) diff --git a/2_intermediate/intermediateproject/memory_cards_prompt b/2_intermediate/intermediateproject/memory_cards_prompt deleted file mode 100644 index 6a9d7a5e..00000000 --- a/2_intermediate/intermediateproject/memory_cards_prompt +++ /dev/null @@ -1,13 +0,0 @@ -Follow these steps to create this algorithm. -1) Store words and definitions in a dictionary(or a list, if you want, depending on how you write the code). -2) Generate a grid(this should probably be in a 2D list) and fill it randomly with the words and definitions. -Display the grid and indicate that all cards are flipped down. -3) For every turn: -3a) Display the grid(showing which cards have been flipped up so far). -3ai) Define a function called display_grid() to do this. -3b) Ask the user to pick 2 locations(they should input the row and column of each position they choose). -If they pick a location with a card that’s already flipped up, ask them to pick again. -3c) Check whether the cards at the chosen locations are a matching word/definition pair. -Give the user a message telling them whether they chose correctly or not. -3d) Update the grid display, so that if the user guessed correctly, they’ll see empty spots on the grid display during the next turn. -4) Display a win message when the user has all the cards flipped up(when they’ve found all the word/definition pairs). diff --git a/2_intermediate/intermediateproject/vowel_exercise.py b/2_intermediate/intermediateproject/vowel_exercise.py deleted file mode 100644 index 2be16e72..00000000 --- a/2_intermediate/intermediateproject/vowel_exercise.py +++ /dev/null @@ -1,18 +0,0 @@ -words = ["Apple", "Orange", "Candles", "Kara", "orange"] - -output = [] -VOWEL_LIST = ["A", "E", "I", "O", "U", "a", "e", "i", "o", "u"] -for elem in words: # Loops through every element in words - if len(elem) % 2 == 0 or elem[0] in VOWEL_LIST: - num_vowels = 0 - for char in elem: - if char in VOWEL_LIST: - num_vowels += 1 - output.append(num_vowels) - else: - num_consonants = 0 - for char in elem: - if char not in VOWEL_LIST: - num_consonants += 1 - output.append(num_consonants) -print(output) # Should print [2, 3, 5, 2, 3] in this case diff --git a/3_advanced/advancedproject/dice.py b/3_advanced/advancedproject/dice.py deleted file mode 100644 index 31da22a2..00000000 --- a/3_advanced/advancedproject/dice.py +++ /dev/null @@ -1,97 +0,0 @@ -# Daniel likes to get together with his friends every week on a -# random day to play dice. In his game of dice, the objective is -# to see who gets three of the same number first. - -# Algorithm Description: Use a class to represent a player. Create a -# turn log (using 2d list with each inner list containing the outcomes -# for all players representing a turn. Ex: [[1,2,4],[4,2,6]] ). -# Create a dictionary (that is an instance variable of the player -# class) to keep track of how many of each dice outcome each -# person playing the game got. For example, Daniel’s outcomes can -# look like {1:2, 2:3, 3:1, 4:0, 5:1, 6:2}. Once a person gets 3 -# of the same outcome, a unique statement will be created -# (the statement should be like "Player x won"). -# If multiple people won, it should be like "Player x, y won" - -# Follow these steps to create this algorithm. -# 1) Import the random module which we will be using later. - -# 2) Create a main class with -# --- instance variable in init: that asks the user for how many -# players and creates that many player classes -# --- instance variable in init: holding a list containing player -# (which is a class covered in the next section) instances based -# on how much the user inputted(so if user says 2 players playing, -# there should be 2 player instances in this list). -# --- instance variable in init: holding a turn log that should -# take each player's result each round (see in algorithm description) -# --- instance variable in init: holding the winners for this game -# --- instance variable in init: holding whether this game is over -# or not. -# --- a 'round' method that simulates one round of the game. so all -# the players should roll a random outcome(more in section 3). -# remember, every round, the turn log should be updated. -# also, check if the game has been won, and if it has, update the -# list containing the winners and the variable containing whether -# the game has been won. - -# 3) Create a player class with -# --- instance variable in init: holding the random dice outcome -# for the player for this round -# --- instance variable in init: holding a dictionary that -# stores how many times they got each outcome -# --- instance variable in init: that tracks whether the -# player has rolled 3 of the same thing(in other words won) or not. -# --- a 'roll' method that determines the random outcome for the player -# (dice: a random int between 0 and 6 inclusive) and also whether the -# player won this round or not. - -# At the end run the main class by doing main(). Running main() -# should print the turn log and print which player(s) won. - -import random - - -class main: - def __init__(self): - self.playercount = int(input("How many players are playing? ")) - self.turnlog = [] - self.players = [player() for i in range(self.playercount)] - self.winners = [] - self.over = False - - print( - "Note: player 0 is the first player, " - + "player 1 is the second player, etc" - ) - while not self.over: - self.round() - print("This is the record of the game") - print(self.turnlog) - print("Player(s)", str(self.winners).lstrip("[").rstrip("]"), "won") - - def round(self): - for i in range(self.playercount): - self.players[i].roll() - if self.players[i].win: - self.winners.append(i) - self.over = True - self.turnlog.append( - [self.players[x].thisround for x in range(self.playercount)] - ) - - -class player: - def __init__(self): - self.thisround = None - self.outcomes = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0} - self.win = False - - def roll(self): - self.thisround = random.randint(1, 6) - self.outcomes[self.thisround] += 1 - if 3 in self.outcomes.values(): - self.win = True - - -letsplay = main() diff --git a/3_advanced/chapter13/examples/filler b/3_advanced/chapter13/examples/filler deleted file mode 100644 index a27d933f..00000000 --- a/3_advanced/chapter13/examples/filler +++ /dev/null @@ -1 +0,0 @@ -#This is filler. Remove later. diff --git a/3_advanced/chapter14/examples/enumerate.py b/3_advanced/chapter14/examples/enumerate.py new file mode 100644 index 00000000..53023364 --- /dev/null +++ b/3_advanced/chapter14/examples/enumerate.py @@ -0,0 +1,19 @@ +# The enumerate function assigns numbers to every element +# in an iterable, starting with zero. +# it returns an enumerate object, so you have to do list or tuple +# to access the enumerated values. + + +countries = ["Japan", "America", "South Korea", "China"] +numerated_list = list(enumerate(countries)) +print(numerated_list) +# prints [(0, ' Japan'), (1, 'America'), (2, 'South Korea'), (3, ' China')] + + +# This code gets all the countries with even indexes greater than 1 +answer_list = [] +for index, country in enumerate(countries): + if index % 2 == 0 and index > 1: + answer_list.append(country) +print(answer_list) +# prints ['South Korea'] diff --git a/3_advanced/chapter14/examples/filler b/3_advanced/chapter14/examples/filler deleted file mode 100644 index 644012f7..00000000 --- a/3_advanced/chapter14/examples/filler +++ /dev/null @@ -1 +0,0 @@ -#This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter14/examples/list_comp.py b/3_advanced/chapter14/examples/list_comp.py new file mode 100644 index 00000000..1d066806 --- /dev/null +++ b/3_advanced/chapter14/examples/list_comp.py @@ -0,0 +1,32 @@ +# List comprehensions are a faster and more +# elegant way to create a new list +# based on an existing list + + +# squares each number from 0 to 9 and adds to 'listL' +listL = [] +for i in range(10): + listL.append(i * i) +print(listL) +# prints [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + + +# does the same thing as above, but in shorter, cleaner code +squares = [i * i for i in range(10)] +print(squares) +# also prints [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + + +# squares all numbers in list 'a' IF they are greater than 5 +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +squares = [i * i for i in a if i > 5] +print(squares) +# prints [36, 49, 64, 81, 100] + + +# converts all letters in 'word' to uppercase and adds to list 'ask' +word = " Hey how are you" +asks = [i.upper() for i in word] +print(asks) +# prints [' ', 'H', 'E', 'Y', ' ', 'H', 'O', 'W', +# ' ', 'A', 'R', 'E', ' ', 'Y', 'O', 'U'] diff --git a/3_advanced/chapter14/examples/swapping_vars.py b/3_advanced/chapter14/examples/swapping_vars.py new file mode 100644 index 00000000..3ed76f03 --- /dev/null +++ b/3_advanced/chapter14/examples/swapping_vars.py @@ -0,0 +1,33 @@ +# Normally, when switching the values of two variables, +# you need a third, temporary variable. With python, +# you can ignore that step using tuple unpacking. + + +""" Switching two variables with a temporary variable """ +# Switching 2 and 3 +list1 = [1, 2, 3, 4, 5] + +temp = list1[1] +list1[1] = list1[2] +list1[2] = temp + +print(list1) +# prints [1, 3, 2, 4, 5] + + +""" Switching two variables without a temporary variable """ +list1 = [1, 2, 3, 4, 5] + +list1[1], list1[2] = list1[2], list1[1] + +print(list1) +# also prints [1, 3, 2, 4, 5] + + +""" Switching many variables without a temporary variable """ +list1 = [1, 2, 3, 4, 5] + +list1[0], list1[1], list1[2] = list1[1], list1[2], list1[0] + +print(list1) +# prints [2, 3, 1, 4, 5] diff --git a/3_advanced/chapter14/examples/tuple_unpack.py b/3_advanced/chapter14/examples/tuple_unpack.py new file mode 100644 index 00000000..2404891e --- /dev/null +++ b/3_advanced/chapter14/examples/tuple_unpack.py @@ -0,0 +1,27 @@ +# Extracting values from "countries" + + +""" Typical way to extract values """ +countries = ("china", "mexico", "brazil", "USA") + +a = countries[0] +b = countries[1] +c = countries[2] +d = countries[3] + +print(a, b, c, d) # prints "china mexico brazil USA" + + +""" Extracting values with tuple unpacking """ +a, b, c, d = countries + +print(a, b, c, d) # prints "china mexico brazil USA" + + +""" Special feature for tuple unpacking """ +# Using the * says that you want all values +# in the middle of the tuple to be put together +# in a list. +a, *b, c = countries + +print(a, b, c) # prints "china ["mexico", "brazil"] USA" diff --git a/3_advanced/chapter14/examples/zip.py b/3_advanced/chapter14/examples/zip.py new file mode 100644 index 00000000..34f51221 --- /dev/null +++ b/3_advanced/chapter14/examples/zip.py @@ -0,0 +1,27 @@ +# The zip function groups elements from different +# iterables into tuples by their index + + +a = [1, 2, 3] +b = ["a", "b", "c"] +c = ["!", "@", "#"] + +G = zip(a, b, c) + +print(list(G)) +# prints [(1, 'a', '!'), (2, 'b', '@'), (3, 'c', '#')] + + +list_one = [1, 2] +list_two = [41] + +for pair in zip(list_one, list_two): + print(pair) +# this would only print (1, 41) because list_two has only one element + + +countries = [" Japan", "America", "South Korea", " China"] +numbers = [1, 2, 3, 4] +dict1 = dict(zip(countries, numbers)) +print(dict1) +# prints {' China': 4, ' Japan': 1, 'America': 2, 'South Korea': 3} diff --git a/3_advanced/chapter14/practice/filler b/3_advanced/chapter14/practice/filler deleted file mode 100644 index 8f31d373..00000000 --- a/3_advanced/chapter14/practice/filler +++ /dev/null @@ -1 +0,0 @@ -This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter14/solutions/filler b/3_advanced/chapter14/solutions/filler deleted file mode 100644 index 8f31d373..00000000 --- a/3_advanced/chapter14/solutions/filler +++ /dev/null @@ -1 +0,0 @@ -This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter14/solutions/odd_squares.py b/3_advanced/chapter14/solutions/odd_squares.py index 1e385094..4a4dfd74 100644 --- a/3_advanced/chapter14/solutions/odd_squares.py +++ b/3_advanced/chapter14/solutions/odd_squares.py @@ -10,4 +10,4 @@ ex_list[idx] = int(ex_list[idx]) # write your code below -odds_quares = [n ** 2 for n in list if n % 2 == 1] +odds_quares = [n**2 for n in list if n % 2 == 1] diff --git a/3_advanced/chapter15/examples/calculate_time.py b/3_advanced/chapter15/examples/calculate_time.py new file mode 100644 index 00000000..a2e01338 --- /dev/null +++ b/3_advanced/chapter15/examples/calculate_time.py @@ -0,0 +1,13 @@ +# This code will get all the odd birthdays and print it +birthdays = [12, 4, 21, 11, 24] # O(1) + +odd_birthdays = [] # O(1) +for birthday in birthdays: # O(n) + if birthday % 2 == 1: # O(1)*O(n) = O(n) + odd_birthdays.append(birthday) # O(1)*O(n) = O(n) + +print(odd_birthdays) # O(1) + +# Sum = O(1) + O(1) + O(n) + O(n) + O(n) + O(1) +# Sum = 3*O(1) + 3*O(n) +# Final Running Time = O(n) diff --git a/3_advanced/chapter15/examples/filler b/3_advanced/chapter15/examples/filler deleted file mode 100644 index 644012f7..00000000 --- a/3_advanced/chapter15/examples/filler +++ /dev/null @@ -1 +0,0 @@ -#This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter15/examples/running_time.py b/3_advanced/chapter15/examples/running_time.py new file mode 100644 index 00000000..2f06c596 --- /dev/null +++ b/3_advanced/chapter15/examples/running_time.py @@ -0,0 +1,32 @@ +""" O(1) """ +# Any assignments +x = 1 # O(1) +x += 1 # O(1) + +# If statement structure +# Condition and code inside not always O(1) +if 1 == 1: # O(1) + print(1) # O(1) +else: # O(1) + print(2) # O(1) + +# Some list operations +x = [1, 2, 4, 213] +x.append(14) # O(1) +x[0] = 11 # O(1) + + +""" O(n) """ +# "Most" for loops are O(n) +for number in [123, 4, 21, 312, 41]: # O(n) + print(number) # O(1) + + +""" O(n^2), O(n^3), etc. """ +# "Most" of the time, every extra for loop +# increases running time by a factor of n + +example_list = [12, 3, 214, 5, 12] +for num1 in example_list: # O(n) + for num2 in example_list: # O(n) + print(num1, num2) # O(1) diff --git a/3_advanced/chapter15/examples/tricky.py b/3_advanced/chapter15/examples/tricky.py new file mode 100644 index 00000000..1406a8a6 --- /dev/null +++ b/3_advanced/chapter15/examples/tricky.py @@ -0,0 +1,17 @@ +# Tricky if statements +ex_list = [1, 23, 421, 32] +if 1 == 2: + for num in ex_list: + print(num) +else: + print(ex_list) + + +# Loops ending prematurely +ex_list = [1, 23, 421, 32] +counter = 0 +for num in ex_list: + counter += 1 + print(num) + if counter == 1: + break diff --git a/3_advanced/chapter16/practice/ch16_practice1.py b/3_advanced/chapter15/practice/ch15_practice1.py similarity index 100% rename from 3_advanced/chapter16/practice/ch16_practice1.py rename to 3_advanced/chapter15/practice/ch15_practice1.py diff --git a/3_advanced/chapter16/practice/ch16_practice2.py b/3_advanced/chapter15/practice/ch15_practice2.py similarity index 96% rename from 3_advanced/chapter16/practice/ch16_practice2.py rename to 3_advanced/chapter15/practice/ch15_practice2.py index 3bf1ccfe..9e886b2e 100644 --- a/3_advanced/chapter16/practice/ch16_practice2.py +++ b/3_advanced/chapter15/practice/ch15_practice2.py @@ -1,19 +1,19 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -The input of the problem is ex_list, and assume it has -n elements. At the end, put the total running time of -code. - - -# ex_list = [?,?,?,...]#Input,O(1) -for i in range(2): # O(1) - ex_list.insert(0, 1) - ex_list.append(1) -for number in ex_list: - for number in ex_list: - break - break -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +The input of the problem is ex_list, and assume it has +n elements. At the end, put the total running time of +code. + + +# ex_list = [?,?,?,...]#Input,O(1) +for i in range(2): # O(1) + ex_list.insert(0, 1) + ex_list.append(1) +for number in ex_list: + for number in ex_list: + break + break +""" diff --git a/3_advanced/chapter16/practice/ch16_practice3.py b/3_advanced/chapter15/practice/ch15_practice3.py similarity index 96% rename from 3_advanced/chapter16/practice/ch16_practice3.py rename to 3_advanced/chapter15/practice/ch15_practice3.py index d8f26f99..582bf665 100644 --- a/3_advanced/chapter16/practice/ch16_practice3.py +++ b/3_advanced/chapter15/practice/ch15_practice3.py @@ -1,20 +1,20 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -The input of the problem is ex_list, and assume it -has n elements. At the end, put the total running -time of code. - - -ex_list = [?,?,?,...]#Input,O(1) -for i in range(len(ex_list)):#O(n) - if i%2 == 0: - print(1) - else: - print(2) - for j in range(len(ex_list)): - for k in range(len(ex_list)): - print(j,k) -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +The input of the problem is ex_list, and assume it +has n elements. At the end, put the total running +time of code. + + +ex_list = [?,?,?,...]#Input,O(1) +for i in range(len(ex_list)):#O(n) + if i%2 == 0: + print(1) + else: + print(2) + for j in range(len(ex_list)): + for k in range(len(ex_list)): + print(j,k) +""" diff --git a/3_advanced/chapter16/practice/ch16_practice4.py b/3_advanced/chapter15/practice/ch15_practice4.py similarity index 53% rename from 3_advanced/chapter16/practice/ch16_practice4.py rename to 3_advanced/chapter15/practice/ch15_practice4.py index 7ecd7d08..ebf66abd 100644 --- a/3_advanced/chapter16/practice/ch16_practice4.py +++ b/3_advanced/chapter15/practice/ch15_practice4.py @@ -1,28 +1,18 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -The input of the problem is ex_list, and assume it -has n elements. At the end, put the total running -time of code. Note: You will be surprised! - - -ex_list = [?,?,?,...]#Input,O(1) -for i in range(len(ex_list)):#O(n) - print(i) - ex_list.append(i) -for i in ex_list: - print(i) - ex_list.append(i) -#Total running time = There is no upper bound, so -#no Big-O. -# -#Explanation: The second for loop will keep looping -#since ex_list will keep increasing in size each time -#you loop. You may ask why doesn't the first for loop do -#the same? That is because the number of times the first -#for loop loops is set at the very start of the loop, -#whereas for the second for loop will keep looping until -#every element is checked. -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +The input of the problem is ex_list, and assume it +has n elements. At the end, put the total running +time of code. Note: You will be surprised! + + +ex_list = [?,?,?,...]#Input,O(1) +for i in range(len(ex_list)):#O(n) + print(i) + ex_list.append(i) +for i in ex_list: + print(i) + ex_list.append(i) +""" diff --git a/3_advanced/chapter16/practice/ch16_practice5.py b/3_advanced/chapter15/practice/ch15_practice5.py similarity index 96% rename from 3_advanced/chapter16/practice/ch16_practice5.py rename to 3_advanced/chapter15/practice/ch15_practice5.py index 961452a8..8e456b10 100644 --- a/3_advanced/chapter16/practice/ch16_practice5.py +++ b/3_advanced/chapter15/practice/ch15_practice5.py @@ -1,18 +1,18 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -At the end, put the total running time of code. -The input of the problem is ex_2d_list, and assume -it has n numbers. This problem assumes you have the -knowledge of 2D Lists. - - -ex_2d_list = [[?,?,?],[?,?]...]#Input,O(1) -list_sum = 0#O(1) -for ex_1d_list in ex_2d_list: - for element in ex_1d_list: - list_sum += element -print(list_sum) -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +At the end, put the total running time of code. +The input of the problem is ex_2d_list, and assume +it has n numbers. This problem assumes you have the +knowledge of 2D Lists. + + +ex_2d_list = [[?,?,?],[?,?]...]#Input,O(1) +list_sum = 0#O(1) +for ex_1d_list in ex_2d_list: + for element in ex_1d_list: + list_sum += element +print(list_sum) +""" diff --git a/3_advanced/chapter15/practice/filler b/3_advanced/chapter15/practice/filler deleted file mode 100644 index 644012f7..00000000 --- a/3_advanced/chapter15/practice/filler +++ /dev/null @@ -1 +0,0 @@ -#This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter16/solutions/ch16_practice1.py b/3_advanced/chapter15/solutions/ch15_practice1.py similarity index 100% rename from 3_advanced/chapter16/solutions/ch16_practice1.py rename to 3_advanced/chapter15/solutions/ch15_practice1.py diff --git a/3_advanced/chapter16/solutions/ch16_practice2.py b/3_advanced/chapter15/solutions/ch15_practice2.py similarity index 100% rename from 3_advanced/chapter16/solutions/ch16_practice2.py rename to 3_advanced/chapter15/solutions/ch15_practice2.py diff --git a/3_advanced/chapter16/solutions/ch16_practice3.py b/3_advanced/chapter15/solutions/ch15_practice3.py similarity index 96% rename from 3_advanced/chapter16/solutions/ch16_practice3.py rename to 3_advanced/chapter15/solutions/ch15_practice3.py index 02164783..94f6739d 100644 --- a/3_advanced/chapter16/solutions/ch16_practice3.py +++ b/3_advanced/chapter15/solutions/ch15_practice3.py @@ -1,21 +1,21 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -The input of the problem is ex_list, and assume it -has n elements. At the end, put the total running -time of code. - - -ex_list = [?,?,?,...]#Input,O(1) -for i in range(len(ex_list)):#O(n) - if i%2 == 0:#O(1) - print(1)#O(1) - else:#O(1) - print(2)#O(1) - for j in range(len(ex_list)):#O(n) - for k in range(len(ex_list)):#O(n) - print(j,k)#O(1) -#Total running time = O(n^3) -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +The input of the problem is ex_list, and assume it +has n elements. At the end, put the total running +time of code. + + +ex_list = [?,?,?,...]#Input,O(1) +for i in range(len(ex_list)):#O(n) + if i%2 == 0:#O(1) + print(1)#O(1) + else:#O(1) + print(2)#O(1) + for j in range(len(ex_list)):#O(n) + for k in range(len(ex_list)):#O(n) + print(j,k)#O(1) +#Total running time = O(n^3) +""" diff --git a/3_advanced/chapter16/solutions/ch16_practice4.py b/3_advanced/chapter15/solutions/ch15_practice4.py similarity index 97% rename from 3_advanced/chapter16/solutions/ch16_practice4.py rename to 3_advanced/chapter15/solutions/ch15_practice4.py index 93c726e5..29e88c25 100644 --- a/3_advanced/chapter16/solutions/ch16_practice4.py +++ b/3_advanced/chapter15/solutions/ch15_practice4.py @@ -1,28 +1,28 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -The input of the problem is ex_list, and assume it -has n elements. At the end, put the total running -time of code. Note: You will be surprised! - - -ex_list = [?,?,?,...]#Input,O(1) -for i in range(len(ex_list)):#O(n) - print(i)#O(1) - ex_list.append(i)#O(1) -for i in ex_list:#No Big-O. Runs forever. - print(i)#O(1) - ex_list.append(i)#O(1) -#Total running time = There is no upper bound, so -#no Big-O. -# -#Explanation: The second for loop will keep looping -#since ex_list will keep increasing in size each time -#you loop. You may ask why doesn't the first for loop do -#the same? That is because the number of times the first -#for loop loops is set at the very start of the loop, -#whereas for the second for loop will keep looping until -#every element is checked. -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +The input of the problem is ex_list, and assume it +has n elements. At the end, put the total running +time of code. Note: You will be surprised! + + +ex_list = [?,?,?,...]#Input,O(1) +for i in range(len(ex_list)):#O(n) + print(i)#O(1) + ex_list.append(i)#O(1) +for i in ex_list:#No Big-O. Runs forever. + print(i)#O(1) + ex_list.append(i)#O(1) +#Total running time = There is no upper bound, so +#no Big-O. +# +#Explanation: The second for loop will keep looping +#since ex_list will keep increasing in size each time +#you loop. You may ask why doesn't the first for loop do +#the same? That is because the number of times the first +#for loop loops is set at the very start of the loop, +#whereas for the second for loop will keep looping until +#every element is checked. +""" diff --git a/3_advanced/chapter16/solutions/ch16_practice5.py b/3_advanced/chapter15/solutions/ch15_practice5.py similarity index 97% rename from 3_advanced/chapter16/solutions/ch16_practice5.py rename to 3_advanced/chapter15/solutions/ch15_practice5.py index d65635fc..a56efd4d 100644 --- a/3_advanced/chapter16/solutions/ch16_practice5.py +++ b/3_advanced/chapter15/solutions/ch15_practice5.py @@ -1,23 +1,23 @@ -""" -The following code is not meant to be run because -there's no input. Instead, analyze it's running time -in terms of Big-O. The first two lines are already -analyzed for you. Do the same for all the other lines. -At the end, put the total running time of code. -The input of the problem is ex_2d_list, and assume -it has n numbers. This problem assumes you have the -knowledge of 2D Lists. - - -ex_2d_list = [[?,?,?],[?,?]...]#Input,O(1) -list_sum = 0#O(1) -for ex_1d_list in ex_2d_list:#This line and next line combined = O(n) - for element in ex_1d_list: - list_sum += element#O(1) -print(list_sum)#O(1) -#Total running time = O(n) -# -#Explanation: We are finding the running time in terms of the input. -#The whole 2d list has n elements so the double for loop will loop -#n times in total. -""" +""" +The following code is not meant to be run because +there's no input. Instead, analyze it's running time +in terms of Big-O. The first two lines are already +analyzed for you. Do the same for all the other lines. +At the end, put the total running time of code. +The input of the problem is ex_2d_list, and assume +it has n numbers. This problem assumes you have the +knowledge of 2D Lists. + + +ex_2d_list = [[?,?,?],[?,?]...]#Input,O(1) +list_sum = 0#O(1) +for ex_1d_list in ex_2d_list:#This line and next line combined = O(n) + for element in ex_1d_list: + list_sum += element#O(1) +print(list_sum)#O(1) +#Total running time = O(n) +# +#Explanation: We are finding the running time in terms of the input. +#The whole 2d list has n elements so the double for loop will loop +#n times in total. +""" diff --git a/3_advanced/chapter15/solutions/filler b/3_advanced/chapter15/solutions/filler deleted file mode 100644 index 644012f7..00000000 --- a/3_advanced/chapter15/solutions/filler +++ /dev/null @@ -1 +0,0 @@ -#This is filler content. You can only add 1 folder at a time? diff --git a/3_advanced/chapter15/examples/Selection Sort Code.py b/3_advanced/chapter16/examples/Selection Sort Code.py similarity index 97% rename from 3_advanced/chapter15/examples/Selection Sort Code.py rename to 3_advanced/chapter16/examples/Selection Sort Code.py index 4a2411cc..dc919a08 100644 --- a/3_advanced/chapter15/examples/Selection Sort Code.py +++ b/3_advanced/chapter16/examples/Selection Sort Code.py @@ -1,9 +1,9 @@ -arr = [1, 4, 2, 7, 7, 6] # change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx + 1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -print(arr) +arr = [1, 4, 2, 7, 7, 6] # change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx + 1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +print(arr) diff --git a/3_advanced/chapter16/examples/Selection_Sort_analyzed.py b/3_advanced/chapter16/examples/Selection_Sort_analyzed.py new file mode 100644 index 00000000..a37ce68a --- /dev/null +++ b/3_advanced/chapter16/examples/Selection_Sort_analyzed.py @@ -0,0 +1,18 @@ +arr = [int, int, int] # this is the input, so we're not analyzing it + +for first_idx in range(len(arr)): # O(n) + min_idx = first_idx # O(1) * O(n) = O(n) + + for second_idx in range(first_idx + 1, len(arr)): # O(n) * O(n) = O(n^2) + if arr[second_idx] < arr[min_idx]: # O(1) * O(n) * O(n) = O(n^2) + min_idx = second_idx # O(1) * O(n) * O(n) = O(n^2) + + arr[first_idx], arr[min_idx] = ( + arr[min_idx], + arr[first_idx], + ) # O(1) * O(n) = O(n) + + +# Sum = O(n) + O(n) + O(n^2) + O(n^2) + O(n^2) + O(n) +# Sum = 3*O(n) + 3*O(n^2) +# Final Running Time = O(n^2) diff --git a/3_advanced/chapter16/examples/filler b/3_advanced/chapter16/examples/filler deleted file mode 100644 index 8c040cb1..00000000 --- a/3_advanced/chapter16/examples/filler +++ /dev/null @@ -1 +0,0 @@ -#Filler Content. Will Remove later diff --git a/3_advanced/chapter15/practice/selection_sort_even.py b/3_advanced/chapter16/practice/selection_sort_even.py similarity index 96% rename from 3_advanced/chapter15/practice/selection_sort_even.py rename to 3_advanced/chapter16/practice/selection_sort_even.py index 27cd2859..2be5473c 100644 --- a/3_advanced/chapter15/practice/selection_sort_even.py +++ b/3_advanced/chapter16/practice/selection_sort_even.py @@ -1,18 +1,18 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify this code so that the code sorts only the elements at the even -indexes, ignoring elements at odd indexes. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify this code so that the code sorts only the elements at the even +indexes, ignoring elements at odd indexes. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below diff --git a/3_advanced/chapter15/practice/selection_sort_f3.py b/3_advanced/chapter16/practice/selection_sort_f3.py similarity index 96% rename from 3_advanced/chapter15/practice/selection_sort_f3.py rename to 3_advanced/chapter16/practice/selection_sort_f3.py index 1c3a6ac4..3fb8c4d5 100644 --- a/3_advanced/chapter15/practice/selection_sort_f3.py +++ b/3_advanced/chapter16/practice/selection_sort_f3.py @@ -1,18 +1,18 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify this code so that the code sorts only the first three elements -of an array. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify this code so that the code sorts only the first three elements +of an array. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below diff --git a/3_advanced/chapter15/practice/selection_sort_gtl.py b/3_advanced/chapter16/practice/selection_sort_gtl.py similarity index 96% rename from 3_advanced/chapter15/practice/selection_sort_gtl.py rename to 3_advanced/chapter16/practice/selection_sort_gtl.py index 876507c6..52459438 100644 --- a/3_advanced/chapter15/practice/selection_sort_gtl.py +++ b/3_advanced/chapter16/practice/selection_sort_gtl.py @@ -1,17 +1,17 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify the code so that the code sorts an array from greatest to least. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify the code so that the code sorts an array from greatest to least. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below diff --git a/3_advanced/chapter15/solutions/selection_sort_even.py b/3_advanced/chapter16/solutions/selection_sort_even.py similarity index 96% rename from 3_advanced/chapter15/solutions/selection_sort_even.py rename to 3_advanced/chapter16/solutions/selection_sort_even.py index 06c96dbb..5e23944e 100644 --- a/3_advanced/chapter15/solutions/selection_sort_even.py +++ b/3_advanced/chapter16/solutions/selection_sort_even.py @@ -1,28 +1,28 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify this code so that the code sorts only the elements at the even -indexes, ignoring elements at odd indexes. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below - -arr = [4, 1, 2, 5, 123, 98, 23] -for first_idx in range(0, len(arr), 2): # range(start, stop, step) - min_idx = first_idx - for second_idx in range(first_idx, len(arr), 2): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -print(arr) +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify this code so that the code sorts only the elements at the even +indexes, ignoring elements at odd indexes. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below + +arr = [4, 1, 2, 5, 123, 98, 23] +for first_idx in range(0, len(arr), 2): # range(start, stop, step) + min_idx = first_idx + for second_idx in range(first_idx, len(arr), 2): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +print(arr) diff --git a/3_advanced/chapter15/solutions/selection_sort_f3.py b/3_advanced/chapter16/solutions/selection_sort_f3.py similarity index 97% rename from 3_advanced/chapter15/solutions/selection_sort_f3.py rename to 3_advanced/chapter16/solutions/selection_sort_f3.py index ed3190c1..8fabd885 100644 --- a/3_advanced/chapter15/solutions/selection_sort_f3.py +++ b/3_advanced/chapter16/solutions/selection_sort_f3.py @@ -1,30 +1,30 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify this code so that the code sorts only the first three elements -of an array. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below - -arr = [4, 1, 2, 5, 123, 98, 23] -f3_arr = arr[:3] # this will contains the elements before the 3rd index. -remaining_arr = arr[3:] # this will be [] if original arr <= 3 -for first_idx in range(len(f3_arr)): - min_idx = first_idx - for second_idx in range(first_idx + 1, len(f3_arr)): - if f3_arr[second_idx] < f3_arr[min_idx]: - min_idx = second_idx - f3_arr[first_idx], f3_arr[min_idx] = f3_arr[min_idx], f3_arr[first_idx] - -print(f3_arr + remaining_arr) # adding lists will combine the lists +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify this code so that the code sorts only the first three elements +of an array. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below + +arr = [4, 1, 2, 5, 123, 98, 23] +f3_arr = arr[:3] # this will contains the elements before the 3rd index. +remaining_arr = arr[3:] # this will be [] if original arr <= 3 +for first_idx in range(len(f3_arr)): + min_idx = first_idx + for second_idx in range(first_idx + 1, len(f3_arr)): + if f3_arr[second_idx] < f3_arr[min_idx]: + min_idx = second_idx + f3_arr[first_idx], f3_arr[min_idx] = f3_arr[min_idx], f3_arr[first_idx] + +print(f3_arr + remaining_arr) # adding lists will combine the lists diff --git a/3_advanced/chapter15/solutions/selection_sort_gtl.py b/3_advanced/chapter16/solutions/selection_sort_gtl.py similarity index 96% rename from 3_advanced/chapter15/solutions/selection_sort_gtl.py rename to 3_advanced/chapter16/solutions/selection_sort_gtl.py index 26ac4bcc..425e064b 100644 --- a/3_advanced/chapter15/solutions/selection_sort_gtl.py +++ b/3_advanced/chapter16/solutions/selection_sort_gtl.py @@ -1,26 +1,26 @@ -""" -The Selection Sort code we saw sorts an array from least to greatest. -Modify the code so that the code sorts an array from greatest to least. - -Selection Sort Code: - -arr = [?,?,?]#change this array to the array you want to sort -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx+1, len(arr)): - if arr[second_idx] < arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] - -""" - -# write your code below - -arr = [1, 27, 412, 3, 12, 4] -for first_idx in range(len(arr)): - min_idx = first_idx - for second_idx in range(first_idx + 1, len(arr)): - if arr[second_idx] > arr[min_idx]: - min_idx = second_idx - arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] -print(arr) +""" +The Selection Sort code we saw sorts an array from least to greatest. +Modify the code so that the code sorts an array from greatest to least. + +Selection Sort Code: + +arr = [?,?,?]#change this array to the array you want to sort +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx+1, len(arr)): + if arr[second_idx] < arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] + +""" + +# write your code below + +arr = [1, 27, 412, 3, 12, 4] +for first_idx in range(len(arr)): + min_idx = first_idx + for second_idx in range(first_idx + 1, len(arr)): + if arr[second_idx] > arr[min_idx]: + min_idx = second_idx + arr[first_idx], arr[min_idx] = arr[min_idx], arr[first_idx] +print(arr) diff --git a/3_advanced/chapter17/examples/sets.py b/3_advanced/chapter17/examples/sets.py index 7f10ef71..7cb86a04 100644 --- a/3_advanced/chapter17/examples/sets.py +++ b/3_advanced/chapter17/examples/sets.py @@ -39,7 +39,7 @@ # will print 6,7 which is the difference items in set 2 (the 'difference') print(set2.difference(set1)) # will print 1,2,6,7 since those are the different items in both -print(set1.symettricdifference(set2)) +print(set1.symmetric_difference(set2)) # will print 1,2,3,4,5,6,7 since those are the unique items print(set1.union(set2)) diff --git a/3_advanced/chapter17/examples/tuples.py b/3_advanced/chapter17/examples/tuples.py index bfe29486..cc41a975 100644 --- a/3_advanced/chapter17/examples/tuples.py +++ b/3_advanced/chapter17/examples/tuples.py @@ -1,8 +1,20 @@ # initializing a tuple mytuple = () # is an empty tuple +mytuple = tuple() # also an empty tuple myothtuple = (1,) # tuples with just 1 item need a comma at the end moretuple = (4, 6, 3, {5, 6}, [7]) # valid; tuples accept all types + +# these all work and will run with no error +tup = tuple([2, 4, 6, 8]) # creates a tuple out of the list +tup = tuple("tuple") # creates a tuple out of the string +tup = tuple({"a": "A", "b": "B"}) # creates a tuple out of the dict +# note: it creates the tuple out of the dict's keys, not values +tup = tuple({2, 4, 6, 8}) # creates a tuple out of the set +tup = 2, 4, 6, 8 # you don't even need parentheses +# however, you need at least one element in the tuple to do this + + # modifying a tuple # you can't modify a tuple's main elements anothtuple = (4, 56, 7, [4, 6, 8]) @@ -12,6 +24,7 @@ anothtuple[3][0] = 6 # this works since you you're modifying the list's elements, not the tuple's + # tuple methods # includes .index and .count lasttupexample = (4, 6, 8, 10, 4, 2) diff --git a/3_advanced/chapter17/solutions/min_superset.py b/3_advanced/chapter17/solutions/min_superset.py index cce07d80..e7b55ff7 100644 --- a/3_advanced/chapter17/solutions/min_superset.py +++ b/3_advanced/chapter17/solutions/min_superset.py @@ -2,6 +2,7 @@ # of minimum size which is the superset of all the given sets. # Implement the following method: + # superset calcuated using Principle of Inclusion and Exclusion # sets: a vector containing 3 sets def findMinSupersetLength(sets): diff --git a/3_advanced/chapter18/examples/binary_search.py b/3_advanced/chapter18/examples/binary_search.py index 2b98e198..c14336b5 100644 --- a/3_advanced/chapter18/examples/binary_search.py +++ b/3_advanced/chapter18/examples/binary_search.py @@ -1,29 +1,29 @@ # Code for binary search -def binary_search(arr, low, high, x): +def binary_search(arr, low, high, key): """ Parameters: - 1)arr is the sorted array in which we will be finding the element - 2)low is the lower bound of the interval in which we will - be finding the element index - 3)high is the upper bound of the interval in which we will - be finding the element index - 4)x is the element we are trying to find the index of + 1) arr is the sorted array in which we will be finding the element + 2) low is the lower bound of the interval in which we will + be finding the element index + 3) high is the upper bound of the interval in which we will + be finding the element index + 4) key is the element we are trying to find the index of - Output: the index of the element x in the array arr. + Output: the index of the element key in the array arr. If the element x does not exist in array arr, -1 will be returned. """ - while high >= low: + if high >= low: mid = (high + low) // 2 - if arr[mid] == x: # Base Case 1 + if arr[mid] == key: # Base Case 1 return mid - elif arr[mid] < x: # Recursive Case 1 - return binary_search(arr, mid + 1, high, x) - else: # Recursive Case 2 - return binary_search(arr, low, mid - 1, x) + elif arr[mid] < key: # Recursive Case 1 + return binary_search(arr, mid + 1, high, key) + else: # Recursive Case 2 (arr[mid] > x) + return binary_search(arr, low, mid - 1, key) else: # Base Case 2: element not found return -1 diff --git a/3_advanced/chapter18/examples/fibonacci.py b/3_advanced/chapter18/examples/fibonacci.py index 0c0cadc4..d881582b 100644 --- a/3_advanced/chapter18/examples/fibonacci.py +++ b/3_advanced/chapter18/examples/fibonacci.py @@ -1,22 +1,79 @@ -# Code for finding the nth term in the Fibonacci sequence +# Fibonacci +# The Fibonacci sequence starts with 0 and 1. +# The next number in the sequence is the sum of the previous 2 numbers. +# Thus, the first 5 Fibonacci numbers are: 0, 1, 1, 2, 3. -def fibonacci(n): +def recursive_fib(n): + """ + Returns the nth number in the Fibonacci sequence recursively - # Parameter: n is the position of the number in the Fibonacci sequence. - # Output: The nth number of the Fibonacci sequence will be outputted. + Args: + n (int): the position of the number in the Fibonacci sequence you want - # fibonacci(5) means we are finding the 5th fibonacci number - # in the fiboacci sequence going from the left - - if n < 0: # Base Case 1: out of bounds - return "Does not exist" - elif n == 1: # Base Case 2: first number is 0 + Returns: + int: the nth number in the Fibonacci sequence + For example, recursive_fib(5) will return 3 + """ + if n <= 0: # Base Case 1: out of bounds + return None + elif n == 1: # Base Case 2 return 0 - elif n == 2: # Base Case 3: second number is 1 + elif n == 2: # Base Case 3 return 1 - else: # Recursive Case: - return fibonacci(n - 1) + fibonacci(n - 2) + else: # Recursive Case + return recursive_fib(n - 1) + recursive_fib(n - 2) + + +def iterative_fib(n): + """ + Returns the nth number in the Fibonacci sequence iteratively + + Args: + n (int): the position of the number in the Fibonacci sequence you want + + Returns: + int: the nth number in the Fibonacci sequence + For example, iterative_fib(5) will return 3 + """ + if n <= 0: + return None # base case; out of bounds + + current = 0 + next_term = 1 + + for i in range(n - 1): # this is equivalent to for i in range(1, n) + current, next_term = next_term, current + next_term + # this is just a slightly rewritten fib sequence; + # instead of looking at the past 2 cases, it looks at the + # current and next terms to determine the next next term + + return current # will be 0 if n is 1, 1 if n is 2, etc... + + +def fib_sequence(n): + """ + Returns the fibonacci sequence as a list up to the nth fibonacci number + + Args: + n (int): the position of the number in the Fibonacci + sequence you want to go up to + + Returns: + list: the nth number in the Fibonacci sequence + For example, fib_sequence(5) will return [0, 1, 1, 2, 3] + + Adapted from: + https://medium.com/@danfcorreia/fibonacci-iterative-28b042a3eec + """ + sequence = [0, 1] + + for i in range(2, n): + sequence.append(sequence[i - 2] + sequence[i - 1]) + + return sequence -print(fibonacci(5)) +print("Recursive fib:", recursive_fib(5)) +print("Iterative fib:", iterative_fib(5)) +print("Fib sequence:", fib_sequence(5)) diff --git a/3_advanced/chapter18/examples/infinite_recursion.py b/3_advanced/chapter18/examples/infinite_recursion.py new file mode 100644 index 00000000..067371ba --- /dev/null +++ b/3_advanced/chapter18/examples/infinite_recursion.py @@ -0,0 +1,24 @@ +# notice how there is no base +# case, meaning no way out + + +def recurse(i): + i = i + 1 + print(i) + recurse(i) + + +recurse(0) # this will result in the following message: +# RecursionError: maximum recursion depth exceeded while +# calling a Python object + +# RecursionError happens when you exceed your maximum +# recursion limit. By default, it is set to 1000 +# you can check the maximum recursion depth by doing +# import sys +# sys.getrecursionlimit() +# you can change the maximum recursion depth by doing +# import sys +# sys.setrecursionlimit() +# However, this can be dangerous, so only do it if you +# know what you're doing. diff --git a/3_advanced/chapter19/examples/error_handle.py b/3_advanced/chapter19/examples/error_handle.py new file mode 100644 index 00000000..a7e070d0 --- /dev/null +++ b/3_advanced/chapter19/examples/error_handle.py @@ -0,0 +1,25 @@ +# Error handling with try clauses or the assert keyword +# can help coders debug programs. They are also useful +# if you want to ignore a certain error. + + +""" try clause """ +try: # this will try the following code + x = 1 + y = "hi" + x + y +except TypeError: # this will only run if there's a TypeError + print("incorrect types, try again") +except NameError: # this will only run if there's a NameError + print("maybe you forgot to create that") +else: # this will run if no error occurs + print("everything good here!") +finally: + # will run no matter what + print("x is", x, "\ny is", y) + + +""" assert keyword """ +string = "goodbye" +assert string == "hello", "string is not hello" +print(string) # this will not be run because assert raises an exception diff --git a/3_advanced/chapter19/practice/type_checker.py b/3_advanced/chapter19/practice/type_checker.py index 68609a01..da688d35 100644 --- a/3_advanced/chapter19/practice/type_checker.py +++ b/3_advanced/chapter19/practice/type_checker.py @@ -1,4 +1,7 @@ -# Create a function that takes one argument and multiplies it by 4. +# Domestic bees make their honeycombs in rings where the total cells is +# (n + 1) * (3n) + 1 where n is the number of rows in the honeycomb +# Create a function that takes one argument and prints how many total +# cells there are in the honeycomb. # If the argument is not the correct type, print a message saying so. # It should be able to run through the list provided. @@ -7,6 +10,6 @@ def type_checker(x): pass # remove this -arg_list = [4, "hi", "obviously NAN", 5.6] +arg_list = [4, "hi", "obviously NAN", 5.6, None, {3: 4}, [3, 3]] for i in arg_list: type_checker(i) diff --git a/3_advanced/chapter19/solutions/list_practice.py b/3_advanced/chapter19/solutions/list_practice.py index d1ee1a46..a86e9dd6 100644 --- a/3_advanced/chapter19/solutions/list_practice.py +++ b/3_advanced/chapter19/solutions/list_practice.py @@ -19,14 +19,13 @@ def list_practice(): for i in range(times): globlist.append(input("What to append? ")) myinput = input( - "press q to quit; input a number to access " - + "that value in the list" + "press q to quit; input a number to access that value in the list" ) while myinput != "q": print(globlist[int(myinput)]) myinput = input( - "press q to quit, input a number to access " - + "that value of the list" + "press q to quit, input a number to access that value of" + + " the list" ) except ValueError: print("That's not a number") diff --git a/3_advanced/chapter19/solutions/type_checker.py b/3_advanced/chapter19/solutions/type_checker.py index 38e14e96..a5456e81 100644 --- a/3_advanced/chapter19/solutions/type_checker.py +++ b/3_advanced/chapter19/solutions/type_checker.py @@ -1,15 +1,21 @@ -# Create a function that takes one argument and prints arg * 4. +# Domestic bees make their honeycombs in rings where the total cells is +# (n + 1) * (3n) + 1 where n is the number of rows in the honeycomb +# Create a function that takes one argument and prints how many total +# cells there are in the honeycomb. # If the argument is not the correct type, print a message saying so. # It should be able to run through the list provided. def type_checker(x): try: - print(x * 4) + print((x + 1) * (3 * x) + 1) + # note to students: print(x * any number) would not result in + # an error if x is a string; it would just print x that many + # times except TypeError: print("That's not a valid number") -arg_list = [4, "hi", "obviously NAN", 5.6] +arg_list = [4, "hi", "obviously NAN", 5.6, None, {3: 4}, [3, 3]] for i in arg_list: type_checker(i) diff --git a/3_advanced/chapter20/examples/json.py b/3_advanced/chapter20/examples/json.py new file mode 100644 index 00000000..098dc9f9 --- /dev/null +++ b/3_advanced/chapter20/examples/json.py @@ -0,0 +1,52 @@ +import json + + +""" Writing """ +x = open("filename.json", "w") # opens JSON file with write mode +topdict = {} + +chinese = {"hello": "ni hao", "bye": "zai jian", "how are you": "ni hao ma"} +frenchlist = [34, 1, 2, 6] + +topdict["chinese"] = chinese +topdict["frenchlist"] = frenchlist + +json.dump(topdict, x, indent=4) # writes value of topdict into JSON file +x.close() # closes the JSON file and saves the changes + + +""" Reading """ +x = open("./testit.json", "r") # opens JSON file with write read +y = json.load(x) # "grabs" JSON data from testit.json + +for key in y: + print(key, ", ", y[key]) # prints the top values of the JSON file + +x.close() + + +""" Editing a pre-existing JSON file """ +x = open("filename.json", "r") +y = json.load(x) # y becomes the equivalent of a Python dictionary +x.close() + +# the value can be all the normal types that dictionaries can hold +y["some_key"] = "some value" +x = open("filename.json", "w") +json.dump(y, x, indent=4) +x.close() + + +""" json.dumps() method """ +oldDict = {"fname": "john", "lname": "doe", "age": 20} +print("oldDict:", type(oldDict)) # prints data type of oldDict +newStr = json.dumps(oldDict) # converts oldDict to string format +print("newStr:", type(newStr)) # prints data type of newStr + + +""" json.loads() method """ + +oldStr = '{"fname": "john", "lname": "doe", "age": 20}' +print("oldStr:", type(oldStr)) # prints data type of oldStr +newDict = json.loads(oldStr) # converts oldStr to string format +print("newDict:", type(newDict)) # prints data type of newDict diff --git a/3_advanced/chapter20/examples/shelve.py b/3_advanced/chapter20/examples/shelve.py new file mode 100644 index 00000000..01c75a50 --- /dev/null +++ b/3_advanced/chapter20/examples/shelve.py @@ -0,0 +1,17 @@ +# shelve is a Python module that aids with storing data. +# It functions similar to a dictionary, although it +# only allows keys to be strings. + + +import shelve + +# this will open or create a database +myshelf = shelve.open("mydatabase") + +# remember, while the key must be a string, the value can be any type +myshelf["key1"] = 4 + +print(myshelf["key1"]) # prints 4 + +myshelf.close() +# always remember to close the shelve after writing to save the data diff --git a/3_advanced/chapter20/examples/text_files.py b/3_advanced/chapter20/examples/text_files.py new file mode 100644 index 00000000..4f57ea51 --- /dev/null +++ b/3_advanced/chapter20/examples/text_files.py @@ -0,0 +1,71 @@ +# The functions below are the basics of +# creating, editting, and reading text files. + + +# "a" stands for "append" +myfile = open("mytext.txt", "a") + + +# "w" stands for "write" +myfile = open("mytext.txt", "w") + + +# writes into a mytext.txt on different lines +# "hello world\nhi again\nhelloooo" +# You need to use the "\n" character if you want to write to a new line; if you +# don't use it, the next .write() will write to the same line as the previous .write() +myfile.write("hello world\n") # writes on line 1 +myfile.write("hi again\n") # writes on line 2 +myfile.write("helloooo") # writes on line 3 + + +# saves file +myfile.close() + + +# "r" stands for "read" +myfile = open("mytext.txt", "r") + + +# takes data from mytext.txt and prints it +mydata = myfile.read() +print(mydata) + + +# determines if myfile is readable +print(myfile.readable()) + + +print(myfile.readline()) # prints the first line +print(myfile.readline()) # prints the second line +print(myfile.readline()) # prints the third line + + +# determines if you can set your position in myfile +print(myfile.seekable()) + + +# sets your position to the 0th index +myfile.seek(0) + + +# prints a list of all the lines in the file +print(myfile.readlines()) + + +# prints your position in a file +print(myfile.tell()) + + +# determines if you can write into myfile +print(myfile.writable()) + + +myfile = open("mytext.txt", "w") + + +# writes lines from provided list into myfile +myfile.writelines(["line one\n", "line 2"]) + + +myfile.close() diff --git a/3_advanced/chapter20/practice/favorite_foods.json b/3_advanced/chapter20/practice/favorite_foods.json new file mode 100644 index 00000000..ba2df668 --- /dev/null +++ b/3_advanced/chapter20/practice/favorite_foods.json @@ -0,0 +1,8 @@ +{ + "favorite foods": { + "Jerry": "ice cream", + "Ben": "ice cream", + "Steven": "eggroll", + "Spongebob": "Krabby Patty" + } +} \ No newline at end of file diff --git a/3_advanced/chapter20/practice/hidden_message.py b/3_advanced/chapter20/practice/hidden_message.py new file mode 100644 index 00000000..e878863d --- /dev/null +++ b/3_advanced/chapter20/practice/hidden_message.py @@ -0,0 +1,5 @@ +# Create a program that reads textfile.txt and writes (appends) 2 +# newlines and then every 7th word followed by a space + +# ex: given “hi”, “ho”, “ha”, “hy”, “he”, “hu”, “we”, “everyone” +# it would print 2 newlines and then ‘hi everyone’ diff --git a/3_advanced/chapter20/practice/json_practice_1.py b/3_advanced/chapter20/practice/json_practice_1.py new file mode 100644 index 00000000..4961d68f --- /dev/null +++ b/3_advanced/chapter20/practice/json_practice_1.py @@ -0,0 +1,6 @@ +# use the "favorite_foods.json" +# in that json file, there will be a dictionary called "favorite_foods" +# print all the unique favorite foods, which will be the values. +# Save all the names into a list. Add that list to the dictionary +# ('names' should be the key and the names list should be the value) +# and write the dictionary into the json file diff --git a/3_advanced/chapter20/practice/json_practice_2.py b/3_advanced/chapter20/practice/json_practice_2.py new file mode 100644 index 00000000..8b6426dd --- /dev/null +++ b/3_advanced/chapter20/practice/json_practice_2.py @@ -0,0 +1,4 @@ +# use the file "wildlife.json" +# load the data in the JSON file +# add at least one habitat and corresponding animal(s) to the dictionary +# finally, write the updated dictionary to the json file. diff --git a/3_advanced/chapter20/practice/modify_random_text.py b/3_advanced/chapter20/practice/modify_random_text.py new file mode 100644 index 00000000..7e459c31 --- /dev/null +++ b/3_advanced/chapter20/practice/modify_random_text.py @@ -0,0 +1,5 @@ +# Create a program that creates a blank text file and writes a +# random number (in the form of a string) between 1 and 1000 on it. +# Next, close the file. Next, open the file again (this time read it) +# and read the text. Assign a variable to that data. +# print (not write) the variable, then print the int(variable) * 4. diff --git a/3_advanced/chapter20/practice/mydatabase.db b/3_advanced/chapter20/practice/mydatabase.db new file mode 100644 index 00000000..9c84d85b Binary files /dev/null and b/3_advanced/chapter20/practice/mydatabase.db differ diff --git a/3_advanced/chapter20/practice/mydb.db b/3_advanced/chapter20/practice/mydb.db new file mode 100644 index 00000000..56a11caf Binary files /dev/null and b/3_advanced/chapter20/practice/mydb.db differ diff --git a/3_advanced/chapter20/practice/shelve_practice_1.py b/3_advanced/chapter20/practice/shelve_practice_1.py new file mode 100644 index 00000000..ac470e74 --- /dev/null +++ b/3_advanced/chapter20/practice/shelve_practice_1.py @@ -0,0 +1,13 @@ +# Use mydatabase.db; Using it, first get all the keys and put them into a list. +# For help on this, see the hint below. Next, sort the list. Finally, print the +# corresponding values. +# To do that, do print((shelfname)[key]) where (shelfname) is the name of your +# shelf and key is the key. + +# Hint: to get a dictionary or shelf’s keys, all you have to do is this: +""" +for key in myshelf.keys(): + keylist.append(key) +""" +# Keep in mind that the “myshelf” is just a name for a dictionary or shelf and +# that the “keylist” is just a list holding the keys. diff --git a/3_advanced/chapter20/practice/shelve_practice_2.py b/3_advanced/chapter20/practice/shelve_practice_2.py new file mode 100644 index 00000000..89ea7dca --- /dev/null +++ b/3_advanced/chapter20/practice/shelve_practice_2.py @@ -0,0 +1,4 @@ +# Use mydb.db . Using it, first create a total variable. Then, add +# all of the shelve’s values to the total. Remember to check if +# the value is an integer before adding it to the total. +# After all, shelves can store all types. diff --git a/3_advanced/chapter20/practice/shelve_practice_3.py b/3_advanced/chapter20/practice/shelve_practice_3.py new file mode 100644 index 00000000..a87e6986 --- /dev/null +++ b/3_advanced/chapter20/practice/shelve_practice_3.py @@ -0,0 +1,12 @@ +# Create a database to store orders. Next, ask the customer for their +# name and store that as a variable. Next, ask the customer whether +# they want to view their previous order or make a new order. + +# If they want to make a new order, use the shelf to store the order +# as the value and the customer’s name as the key. + +# If they want to view a previous order, check if their name is in +# the shelf’s keys. If it is, print their previous order. +# If not, tell them that they haven’t ordered. +# Remember to close the shelf. +# Hint: to store their order, you can do: shelf[name] = order diff --git a/3_advanced/chapter20/practice/textfile.txt b/3_advanced/chapter20/practice/textfile.txt new file mode 100644 index 00000000..6056bb2a --- /dev/null +++ b/3_advanced/chapter20/practice/textfile.txt @@ -0,0 +1,6 @@ +hi ho he hu hy ha we everyone. wow pow kow some tome Biome How +are bar tsar czar ceasar tar do moo cow baa sheep pig big you too +blue eggs and ham spam do? sew. machine grow large. barge in hopefully +successfully totally completely absolutely did it! you bought that +old fish ten days, did it taste good? huh? what? ok. it is never +close till it is right! diff --git a/3_advanced/chapter20/practice/txt_write_practice.py b/3_advanced/chapter20/practice/txt_write_practice.py new file mode 100644 index 00000000..0d26d17c --- /dev/null +++ b/3_advanced/chapter20/practice/txt_write_practice.py @@ -0,0 +1,136 @@ +import requests +import shelve + + +class game: + def __init__(self, pageinfo, bsl: int, gns, gds, gps, gops, end): + """ + Arguments: + bsl is the beginning search location. It should be an integer + pageinfo is the html of a website converted to a string + gns is the 'game's name start'; it is what to look for directly + before a game's name + gds is the 'game's discount start'; it is what to look for directly + before a game's discount + gps is the 'game's price start'; it is what to look for directly + before a game's discounted price + gops is the 'game original price start'; it is what to look for + directly before a game's original price + + """ + self.string = pageinfo + self.isvalid = True + self.begin = bsl + self.endloc = 0 + + self.discount = self.find(self.string, gds, end, cb=True) + self.price = self.find(self.string, gps, end) + self.ogprice = self.find(self.string, gops, end) + self.name = self.find(self.string, gns, end, cwe=True) + + def find(self, string, start: str, end: str, cb=False, cwe=False): + """ + Arguments + string is the string where the substring you are looking for + is located + start is the substring directly before the substring you are + looking for + end is the substring directly after the substring you are + looking for + cb is whether or not to change the beginning point for the + searches to the endloc + cwe is whether to compare the endloc with self.begin and + check whether the difference is withing the acceptable range + """ + try: + startloc = string.index(start, self.begin) + endloc = string.index(end, startloc) + except Exception: + self.endloc = self.begin + 1 + self.isvalid = False + return + + if cb: + self.begin = endloc + if cwe: + # check if the end location is too far away from the + # beginning to be a valid name + if endloc - self.begin > 300: + self.isvalid = False + self.endloc = endloc + + return string[startloc:endloc].lstrip(start).rstrip(end) + + +class scansteampage: + def __init__(self, database="gameshelf"): + """ + See game's explanation for the abbreviations + """ + link = "https://store.steampowered.com/" + gns = '
' + gds = '
-' + gops = 'class="discount_original_price">' + gps = 'class="discount_final_price">' + end = "
" + + self.games = [] + self.database = database + info = requests.get(link).text + + self.gather_games(info, gns, gds, gps, gops, end) + self.write_info() + + def gather_games(self, info, gns, gds, gps, gops, end): + """ + This method adds game objects to the scansteampage + object's list self.games + """ + position = 0 + consecutive_fails = 0 + + while consecutive_fails < 2: + a_game = game(info, position, gns, gds, gps, gops, end) + position = a_game.endloc + if a_game.isvalid: + self.games.append(a_game) + consecutive_fails = 0 + else: + consecutive_fails += 1 + + def write_info(self): + """ + This method makes writes to the database. + + The database's keys will be games' titles + The database's values will be strings of the following + format: + (name) is on sale for (price), which is a (discount percent) + discount from its original price of (original price) + """ + gameshelf = shelve.open(self.database) + if len(gameshelf.keys()) > 0: + gameshelf.clear() + for game in self.games: + gameshelf[game.name] = ( + "%s is on sale for %s, which is a %s" + % (game.name, game.price, game.discount) + + " discount from its original price of %s" % game.ogprice + ) + gameshelf.close() + + +# comment out the below line after running it once +ourgamesshelf = scansteampage() + +# The above code creates a shelf called gameshelf +# First, create a list to store the values +# Next, write "Current Sales\n" on a blank text file. +# Finally, write the values followed by 2 newlines to The +# text file. +# Your end result should look like below: +# Current Sales +# Something is on sale for $1000.00, which is a 50% discount from +# its original price of 2000.00 + +# write your code here. diff --git a/3_advanced/chapter20/practice/wildlife.json b/3_advanced/chapter20/practice/wildlife.json new file mode 100644 index 00000000..468e9e76 --- /dev/null +++ b/3_advanced/chapter20/practice/wildlife.json @@ -0,0 +1,6 @@ +{ + "China": "pandas", + "Africa": "cheetas", + "North America": "bison", + "South America": "boa constrictor" +} \ No newline at end of file diff --git a/3_advanced/chapter20/solutions/favorite_foods.json b/3_advanced/chapter20/solutions/favorite_foods.json new file mode 100644 index 00000000..376b2db4 --- /dev/null +++ b/3_advanced/chapter20/solutions/favorite_foods.json @@ -0,0 +1,14 @@ +{ + "favorite foods": { + "Jerry": "ice cream", + "Ben": "ice cream", + "Steven": "eggroll", + "Spongebob": "Krabby Patty" + }, + "names": [ + "Jerry", + "Ben", + "Steven", + "Spongebob" + ] +} \ No newline at end of file diff --git a/3_advanced/chapter20/solutions/hidden_message.py b/3_advanced/chapter20/solutions/hidden_message.py new file mode 100644 index 00000000..2bc77063 --- /dev/null +++ b/3_advanced/chapter20/solutions/hidden_message.py @@ -0,0 +1,16 @@ +# Create a program that reads textfile.txt and writes (appends) 2 +# newlines and then every 7th word followed by a space + +# ex: given “hi”, “ho”, “ha”, “hy”, “he”, “hu”, “we”, “everyone” +# it would print 2 newlines and then ‘hi everyone’ + +myfile = open("./textfile.txt", "r") +text = myfile.read().split() +myfile.close() + +myfile = open("./textfile.txt", "a") +myfile.write("\n\n") +for i in range(len(text)): + if i % 7 == 0: + myfile.write(text[i] + " ") +myfile.close() diff --git a/3_advanced/chapter20/solutions/json_practice_1.py b/3_advanced/chapter20/solutions/json_practice_1.py new file mode 100644 index 00000000..ed6de397 --- /dev/null +++ b/3_advanced/chapter20/solutions/json_practice_1.py @@ -0,0 +1,23 @@ +# use the "favorite_foods.json" +# in that json file, there will be a dictionary called "favorite_foods" +# print all the unique favorite foods, which will be the values. +# Save all the names into a list. Add that list to the dictionary +# ('names' should be the key and the names list should be the value) +# and write the dictionary into the json file + +import json + +a = open("favorite_foods.json", "r") +x = json.load(a) +names = [] +foods = set() +for name, food in x["favorite foods"].items(): + foods.add(food) + names.append(name) +for food in foods: + print(food) +x["names"] = names # create an item within the dictionary that has the names +a.close() +n = open("favorite_foods.json", "w") +json.dump(x, n, indent=4) +n.close() diff --git a/3_advanced/chapter20/solutions/json_practice_2.py b/3_advanced/chapter20/solutions/json_practice_2.py new file mode 100644 index 00000000..5944b7ba --- /dev/null +++ b/3_advanced/chapter20/solutions/json_practice_2.py @@ -0,0 +1,14 @@ +# use the file "wildlife.json" +# load the data in the JSON file +# add at least one habitat and corresponding animal(s) to the dictionary +# finally, write the updated dictionary to the json file. + +import json + +a = open("wildlife.json", "r") +x = json.load(a) +a.close() +x["Deepest Peru"] = "Paddington" +n = open("wildlife.json", "w") +json.dump(x, n, indent=4) +n.close() diff --git a/3_advanced/chapter20/solutions/modify_random_text.py b/3_advanced/chapter20/solutions/modify_random_text.py new file mode 100644 index 00000000..2bdc2e67 --- /dev/null +++ b/3_advanced/chapter20/solutions/modify_random_text.py @@ -0,0 +1,16 @@ +# Create a program that creates a blank text file and writes a +# random number (in the form of a string) between 1 and 1000 on it. +# Next, close the file. Next, open the file again (this time read it) +# and read the text. Assign a variable to that data. +# print (not write) the variable, then print the int(variable) * 4. + +import random + +myfile = open("blank.txt", "w") +myfile.write(str(random.randint(0, 1000))) +myfile.close() + +refile = open("blank.txt", "r") +thetext = refile.read() +print(thetext) +print(int(thetext) * 4) diff --git a/3_advanced/chapter20/solutions/shelve_practice_1.py b/3_advanced/chapter20/solutions/shelve_practice_1.py new file mode 100644 index 00000000..53a3c2d2 --- /dev/null +++ b/3_advanced/chapter20/solutions/shelve_practice_1.py @@ -0,0 +1,24 @@ +# Use mydatabase.db; Using it, first get all the keys and put them into a list. +# For help on this, see the hint below. Next, sort the list. Finally, print the +# corresponding values. +# To do that, do print((shelfname)[key]) where (shelfname) is the name of your +# shelf and key is the key. + +# Hint: to get a dictionary or shelf’s keys, all you have to do is this: +""" +for key in myshelf.keys(): + keylist.append(key) +""" +# Keep in mind that the “myshelf” is just a name for a dictionary or shelf and +# that the “keylist” is just a list holding the keys. + +import shelve + +keylist = [] +myshelf = shelve.open("mydatabase") +for key in myshelf.keys(): + keylist.append(key) +keylist.sort() +for key in keylist: + print(myshelf[key]) +myshelf.close() diff --git a/3_advanced/chapter20/solutions/shelve_practice_2.py b/3_advanced/chapter20/solutions/shelve_practice_2.py new file mode 100644 index 00000000..1c6614ad --- /dev/null +++ b/3_advanced/chapter20/solutions/shelve_practice_2.py @@ -0,0 +1,14 @@ +# Use mydb.db . Using it, first create a total variable. Then, add +# all of the shelve’s values to the total. Remember to check if +# the value is an integer before adding it to the total. +# After all, shelves can store all types. + +import shelve + +total = 0 +ashelf = shelve.open("mydb") +for val in ashelf.values(): + if isinstance(val, int): + total += val +print(total) +ashelf.close() diff --git a/3_advanced/chapter20/solutions/shelve_practice_3.py b/3_advanced/chapter20/solutions/shelve_practice_3.py new file mode 100644 index 00000000..26a06d75 --- /dev/null +++ b/3_advanced/chapter20/solutions/shelve_practice_3.py @@ -0,0 +1,30 @@ +# Create a database to store orders. Next, ask the customer for their +# name and store that as a variable. Next, ask the customer whether +# they want to view their previous order or make a new order. + +# If they want to make a new order, use the shelf to store the order +# as the value and the customer’s name as the key. + +# If they want to view a previous order, check if their name is in +# the shelf’s keys. If it is, print their previous order. +# If not, tell them that they haven’t ordered. +# Remember to close the shelf. +# Hint: to store their order, you can do: shelf[name] = order + +import shelve + +shelf = shelve.open("orders") +name = input("What is your name? ") +instruction = input( + "Would you like to view a previous order or make" + + " a new order?\nAnswer with 'order' or 'view': " +) +if instruction == "order": + order = input("Type any order: ") + shelf[name] = order +elif instruction == "view" and name in shelf.keys(): + print("Here is your previous order: ") + print(shelf[name]) +elif instruction == "view" and name not in shelf.keys(): + print("Sorry, you don't seem to have ordered before.") +shelf.close() diff --git a/3_advanced/chapter20/solutions/textfile.txt b/3_advanced/chapter20/solutions/textfile.txt new file mode 100644 index 00000000..10499a72 --- /dev/null +++ b/3_advanced/chapter20/solutions/textfile.txt @@ -0,0 +1,8 @@ +hi ho he hu hy ha we everyone. wow pow kow some tome Biome How +are bar tsar czar ceasar tar do moo cow baa sheep pig big you too +blue eggs and ham spam do? sew. machine grow large. barge in hopefully +successfully totally completely absolutely did it! you bought that +old fish ten days, did it taste good? huh? what? ok. it is never +close till it is right! + +hi everyone. How do you do? hopefully you did it right! diff --git a/3_advanced/chapter20/solutions/txt_write_practice.py b/3_advanced/chapter20/solutions/txt_write_practice.py new file mode 100644 index 00000000..499cefff --- /dev/null +++ b/3_advanced/chapter20/solutions/txt_write_practice.py @@ -0,0 +1,141 @@ +import requests +import shelve + + +class game: + def __init__(self, pageinfo, bsl: int, gns, gds, gps, gops, end): + """ + Arguments: + bsl is the beginning search location. It should be an integer + pageinfo is the html of a website converted to a string + gns is the 'game's name start'; it is what to look for directly + before a game's name + gds is the 'game's discount start'; it is what to look for directly + before a game's discount + gps is the 'game's price start'; it is what to look for directly + before a game's discounted price + gops is the 'game original price start'; it is what to look for + directly before a game's original price + + """ + self.string = pageinfo + self.isvalid = True + self.begin = bsl + self.endloc = 0 + + self.discount = self.find(self.string, gds, end, cb=True) + self.price = self.find(self.string, gps, end) + self.ogprice = self.find(self.string, gops, end) + self.name = self.find(self.string, gns, end, cwe=True) + + def find(self, string, start: str, end: str, cb=False, cwe=False): + """ + Arguments + string is the string where the substring you are looking for + is located + start is the substring directly before the substring you are + looking for + end is the substring directly after the substring you are + looking for + cb is whether or not to change the beginning point for the + searches to the endloc + cwe is whether to compare the endloc with self.begin and + check whether the difference is withing the acceptable range + """ + try: + startloc = string.index(start, self.begin) + endloc = string.index(end, startloc) + except Exception: + self.endloc = self.begin + 1 + self.isvalid = False + return + + if cb: + self.begin = endloc + if cwe: + # check if the end location is too far away from the + # beginning to be a valid name + if endloc - self.begin > 300: + self.isvalid = False + self.endloc = endloc + + return string[startloc:endloc].lstrip(start).rstrip(end) + + +class scansteampage: + def __init__(self, database="gameshelf"): + """ + See game's explanation for the abbreviations + """ + link = "https://store.steampowered.com/" + gns = '
' + gds = '
-' + gops = 'class="discount_original_price">' + gps = 'class="discount_final_price">' + end = "
" + + self.games = [] + self.database = database + info = requests.get(link).text + + self.gather_games(info, gns, gds, gps, gops, end) + self.write_info() + + def gather_games(self, info, gns, gds, gps, gops, end): + """ + This method adds game objects to the scansteampage + object's list self.games + """ + position = 0 + consecutive_fails = 0 + + while consecutive_fails < 2: + a_game = game(info, position, gns, gds, gps, gops, end) + position = a_game.endloc + if a_game.isvalid: + self.games.append(a_game) + consecutive_fails = 0 + else: + consecutive_fails += 1 + + def write_info(self): + """ + This method makes writes to the database. + + The database's keys will be games' titles + The database's values will be strings of the following + format: + (name) is on sale for (price), which is a (discount percent) + discount from its original price of (original price) + """ + gameshelf = shelve.open(self.database) + if len(gameshelf.keys()) > 0: + gameshelf.clear() + for game in self.games: + gameshelf[game.name] = ( + "%s is on sale for %s, which is a %s" + % (game.name, game.price, game.discount) + + " discount from its original price of %s" % game.ogprice + ) + gameshelf.close() + + +# comment out the below line after running it once +ourgamesshelf = scansteampage() + +# The above code creates a shelf called gameshelf +# First, create a list to store the values +# Next, write "Current Sales\n" on a blank text file. +# Finally, write the values followed by 2 newlines to The +# text file. +# Your end result should look like below: +# Current Sales +# Something is on sale for $1000.00, which is a 50% discount from +# its original price of 2000.00 + +mygameshelf = shelve.open("gameshelf") +mytextfile = open("Sales!", "w") +mytextfile.write("Current Sales\n") +for value in mygameshelf.values(): + mytextfile.write(value + "\n\n") +mytextfile.close() diff --git a/3_advanced/chapter20/solutions/wildlife.json b/3_advanced/chapter20/solutions/wildlife.json new file mode 100644 index 00000000..c60a8d22 --- /dev/null +++ b/3_advanced/chapter20/solutions/wildlife.json @@ -0,0 +1,7 @@ +{ + "China": "pandas", + "Africa": "cheetas", + "North America": "bison", + "South America": "boa constrictor", + "Deepest Peru": "Paddington" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..77b1cea0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Code 4 Tomorrow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 37c5fac4..e2dfa533 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,14 @@ Source code from Code For Tomorrow's Python course ![Python (Lint Action)](https://github.com/code-for-tomorrow/python/workflows/Python%20(Lint%20Action)/badge.svg) ## Difficulty Level -Source code is organized under 3 difficulty levels: +Source code is organized under 4 categories: 1. `1_beginner` 2. `2_intermediate` 3. `3_advanced` +4. `games` ## Chapters -Under each of the 3 packages, source code is further divided by chapter. +Under each of the 4 packages, source code is further divided by chapter. ### Beginner 1. `chapter1` Intro to Python 2. `chapter2` Data @@ -27,17 +28,24 @@ Under each of the 3 packages, source code is further divided by chapter. 10. `chapter10` 2D Lists 11. `chapter11` Functions 12. `chapter12` Classes +13. `chapter13` Special Methods ### Advanced -13. `chapter13` Special Methods -14. `chapter14` Selection Sort -15. `chapter15` Tuples & Sets -16. `chapter16` Pythonness -17. `chapter17` Exception Handling +14. `chapter14` Unique Python Features +15. `chapter15` Asymptotic Analysis +16. `chapter16` Selection Sort +17. `chapter17` Tuples & Sets 18. `chapter18` Recursion -19. `chapter19` Asymptotic Analysis +19. `chapter19` Exception Handling 20. `chapter20` File I/O +### Games +1. `chapter1` Console Games +2. `chapter2` Pygame Basics +3. `chapter3` Pygame Events +4. `chapter4` OOP + Pygame +5. `chapter5` Pygame Sounds + ## Category Under each chapter, source code is further divided by category: 1. `examples` - example code to demo certain programming concepts diff --git a/dsa/chapter1/examples/recursion.py b/dsa/chapter1/examples/recursion.py new file mode 100644 index 00000000..15e6c106 --- /dev/null +++ b/dsa/chapter1/examples/recursion.py @@ -0,0 +1,35 @@ +# Code to figure out how many of a factor a number has + + +def number_factor(number, factor, factor_counter=0): + """ + Parameters: + 1) number is the number in which we are finding the number of + factors of. EX: 24 + 2) factor is the factor in which we are finding the number of + in the parameter number. EX: 2 + Output: The number of times the parameter number can be divisible + by the parameter factor. This number is also the parameter + factor_counter right before it is returned. EX: 3 + """ + + if number % factor != 0: # Base Case + return factor_counter + else: # Recursive Case + return number_factor(number / factor, factor, factor_counter + 1) + + +print(number_factor(24, 2)) + + +def countdown(n, arr=[]): + if n < 0: # base case 1 + return "out of bounds" + if n == 0: # base case 2 + return arr + # recursive case + arr.append(n) + return countdown(n - 1, arr) + + +print(countdown(5)) diff --git a/dsa/chapter1/practice/time_complexity.py b/dsa/chapter1/practice/time_complexity.py new file mode 100644 index 00000000..e3acacf8 --- /dev/null +++ b/dsa/chapter1/practice/time_complexity.py @@ -0,0 +1,25 @@ +""" +For each of the following time complexities, create +a function that has that time complexity. +""" + +# time complexity: O(1) +# your code here + + +# time complexity: O(n) +# your code here + + +# time complexity: O(n^2) +# your code here + + +# time complexity: O(log(n)) +# your code here + +# time complexity: O(n * log(n)) +# your code here + +# time complexity: O(2**n) +# your code here diff --git a/dsa/chapter1/practice/time_complexity_questions.py b/dsa/chapter1/practice/time_complexity_questions.py new file mode 100644 index 00000000..f272e328 --- /dev/null +++ b/dsa/chapter1/practice/time_complexity_questions.py @@ -0,0 +1,74 @@ +""" +Classify the following code examples with their +runtimes. Write your responses as comments. +""" + + +def do_something(): + # runtime for do_something() is O(1) + pass + + +# what is the runtime for example 1? +def example_one(n): + for i in range(n): + do_something() + + +# what is the runtime for example 2? +def example_two(n): + do_something() + + +# what is the runtime for example 3? +def example_three(n): + for i in range(n): + for x in range(i): + do_something() + + +# what is the runtime for example 4? +def example_four(n): + for i in range(n // 2): + do_something() + + +# what is the runtime for example 5? +def example_five(n): + i = 0 + while i < n: + do_something() + i *= 2 + + +# what is the runtime for example 6? +def example_six(n): + for i in range(10): + do_something() + + +# what is the runtime for example 7? +def example_seven(n): + for i in range(2**n): + do_something() + + +# what is the runtime for example 8? +def example_eight(n): + for i in range(n): + for x in range(7): + do_something() + + +# what is the runtime for example 9? +def example_nine(n): + for i in range(n): + example_one(n) + + +# what is the runtime for example 10? +def example_ten(n): + i = 0 + while i < n: + do_something() + i += 2 diff --git a/dsa/chapter1/solutions/time_complexity.py b/dsa/chapter1/solutions/time_complexity.py new file mode 100644 index 00000000..d006e0f8 --- /dev/null +++ b/dsa/chapter1/solutions/time_complexity.py @@ -0,0 +1,99 @@ +""" +For each of the following time complexities, create +a function that has that time complexity. The following solutions +are examples and not the only ways to have done this problem. +""" + + +# time complexity: O(1) +def double_my_number(number): + x = number + x *= 2 + return x + + +# time complexity: O(n) +def sum_till_n(n): + total = 0 + for i in range(n): + total += i + + return total + + +# time complexity: O(n^2) +def print_triangle(n): + for row in range(n): + for column in range(row): + print("* ", end="") + print() + + +# time complexity: O(log(n)) +def sum_powers_of_two(max_number): + power_of_two = 1 + total = 0 + + while power_of_two < max_number: + total += power_of_two + power_of_two *= 2 + + return total + + +# time complexity: O(n * log(n)) +def sum_many_powers_of_two(number_of_times): + total = 0 + for i in range(number_of_times): + # since sum_powers_of_two is O(log(n))and this for loop is O(n), + # the resulting time complexity is O(n * log(n)) + total += sum_powers_of_two(i) + + return total + + +# time complexity: O(2**n) +def get_binary_combinations(number_of_digits): + """ + Gets the combinations of binary numbers with number_of_digits digits + For example, get_binary_combinations(2) should give + ["00", "01", "10", "11"]. + + """ + cur_options = ["0", "1"] + next_options = [] + + operations = 0 + + # In total, this is O(2**n). It may be a bit confusing, but + # this is O(2**n) because of the fact that the current options + # doubles each time we go through the for loop, so it has to + # spend twice as long each time. + for i in range(number_of_digits - 1): + for option in cur_options: + next_options.append(option + "0") + next_options.append(option + "1") + operations += 1 + cur_options = next_options + next_options = [] + + print(f"took {operations} operations") + return cur_options + + +# for comparison, here's a very easy to understand +# function with O(2**n) runtime. +def regular_o_2_to_the_n(n): + operations = 0 + for i in range(2**n): + operations += 1 + print(f"took {operations} operations") + + +# if you actually don't believe that get_binary_combinations is O(2**n), +# try running the below. +# as you can see, they have similar operational cost, +# meaning that get_binary_combinations really is O(2**n) +# times = 20 +# get_binary_combinations(times) +# regular_o_2_to_the_n(times) diff --git a/dsa/chapter1/solutions/time_complexity_questions.py b/dsa/chapter1/solutions/time_complexity_questions.py new file mode 100644 index 00000000..96945dc9 --- /dev/null +++ b/dsa/chapter1/solutions/time_complexity_questions.py @@ -0,0 +1,84 @@ +""" +Classify the following code examples with their +runtimes. Write your responses as comments. +""" + + +def do_something(): + # runtime for do_something() is O(1) + pass + + +# what is the runtime for example 1? +# runtime is O(n) +def example_one(n): + for i in range(n): + do_something() + + +# what is the runtime for example 2? +# runtime is O(1) +def example_two(n): + do_something() + + +# what is the runtime for example 3? +# runtime is O(n^2) +def example_three(n): + for i in range(n): + for x in range(i): + do_something() + + +# what is the runtime for example 4? +# runtime is O(n) +def example_four(n): + for i in range(n // 2): + do_something() + + +# what is the runtime for example 5? +# runtime is O(log(n)) +def example_five(n): + i = 0 + while i < n: + do_something() + i *= 2 + + +# what is the runtime for example 6? +# runtime is O(1) +def example_six(n): + for i in range(10): + do_something() + + +# what is the runtime for example 7? +# runtime is O(2**n) +def example_seven(n): + for i in range(2**n): + do_something() + + +# what is the runtime for example 8? +# runtime is O(n) +def example_eight(n): + for i in range(n): + for x in range(7): + do_something() + + +# what is the runtime for example 9? +# runtime is O(n^2) +def example_nine(n): + for i in range(n): + example_one(n) + + +# what is the runtime for example 10? +# runtime is O(n) +def example_ten(n): + i = 0 + while i < n: + do_something() + i += 2 diff --git a/dsa/chapter2/examples/bst.py b/dsa/chapter2/examples/bst.py new file mode 100644 index 00000000..f3d8c4d5 --- /dev/null +++ b/dsa/chapter2/examples/bst.py @@ -0,0 +1,215 @@ +class Node: + def __init__(self, key, value) -> None: + # set key/value + self.key = key + self.value = value + + # set children to None + self.left: Node = None + self.right: Node = None + + def __str__(self) -> str: + return str(self.key) + ": " + str(self.value) + + def __repr__(self) -> str: + return str(self) + + +class BinaryTree: + def __init__(self) -> None: + self.root = None + + def search(self, key): + """ + Searches the binary tree for a node with the given key. + Takes advantage of the fact that, in a binary tree, + keys with lesser values go on the left and keys with greater + values go on the right + """ + current = self.root + while current is not None: + if key == current.key: + return current.value + elif key < current.key: + current = current.left + else: + current = current.right + raise Exception("KEY NOT FOUND") + + def get_height(self): + """ + Gets the height of the binary tree. + """ + if self.root is None: + return 0 + + height = 0 + next = [self.root] + while len(next) != 0: + temp_next = [] + height += 1 + for node in next: + if node.left is not None: + temp_next.append(node.left) + if node.right is not None: + temp_next.append(node.right) + next = temp_next + return height + + def insert_node(self, node: Node) -> None: + """ + Tries to insert a node into the tree. + If a node with the same key is already found, + sets the value of that node to the value of + the provided node + """ + # case where root is None + if self.root is None: + self.root = node + return + + # go through the nodes + current = self.root + while current is not None: + if node.key == current.key: + current.value = node.value + return + elif node.key < current.key: + if current.left is None: + current.left = node + return + else: + current = current.left + else: + if current.right is None: + current.right = node + return + else: + current = current.right + + def remove_node(self, parent, right_or_left="right"): + """ + Helper method to remove a node. + Notice how we have to set `parent.xxx` to something. + This is because, in order to remove a node from a binary + tree, what you are really doing is getting rid of all + references to that node. So, we make sure to change + the value stored in `parent.xxx` to a different node + (or `None`) so that we remove the node we're trying to get rid of + """ + if right_or_left == "right": + node = parent.right + else: + node = parent.left + + if node.right is not None: + temp = node.left + if right_or_left == "right": + parent.right = node.right + else: + parent.left = node.right + + if temp is not None: + self.insert_node(temp) + elif node.left is not None: + temp = node.right + if right_or_left == "right": + parent.right = node.left + else: + parent.left = node.left + if temp is not None: + self.insert_node(temp) + else: + if right_or_left == "right": + parent.right = None + else: + parent.left = None + + def __getitem__(self, key): + return self.search(key) + + def __setitem__(self, key, value): + self.insert_node(Node(key, value)) + + def __delitem__(self, key): + """ + Deletes an entry from the binary tree + """ + # case where key to delete is the root + if self.root is not None and self.root.key == key: + if self.root.right is not None: + temp = self.root.left + self.root = self.root.right + if temp is not None: + self.insert_node(temp) + elif self.root.left is not None: + temp = self.root.right + self.root = self.root.left + if temp is not None: + self.insert_node(temp) + else: + self.root = None + + # regular cases + current = self.root + while current is not None: + if current.left is not None and current.left.key == key: + self.remove_node(current, "left") + break + if current.right is not None and current.right.key == key: + self.remove_node(current, "right") + break + + if key < current.key: + current = current.left + if key > current.key: + current = current.right + + def print_structure(self) -> None: + """ + Prints out what the binary tree looks like + """ + if self.root is None: + print("{}") + return + + height = self.get_height() + spacing = 6 + total_width = spacing * (2**height) + + # print top divider + print("-" * total_width) + + current_generation = [self.root] + next_generation = [] + for i in range(1, height + 1): + margin_between = int( + (total_width - spacing * (2 ** (i - 1))) / ((2 ** (i - 1)) + 1) + ) + for node in current_generation: + print(" " * margin_between, end="") + if node is None: + print(" " * spacing, end="") + next_generation.extend([None] * 2) + else: + print(node, end="") + next_generation.extend([node.left, node.right]) + # print a newline + print() + + current_generation = next_generation + next_generation = [] + + # print bottom divider + print("-" * total_width) + + +myBinaryTree = BinaryTree() +myBinaryTree[33] = 22 +myBinaryTree[22] = 11 +myBinaryTree[44] = 33 +myBinaryTree[55] = 22 +del myBinaryTree[33] + +# to see how it internally arranges data +myBinaryTree.print_structure() diff --git a/dsa/chapter2/examples/graph.py b/dsa/chapter2/examples/graph.py new file mode 100644 index 00000000..189040aa --- /dev/null +++ b/dsa/chapter2/examples/graph.py @@ -0,0 +1,68 @@ +# simple graph +my_graph = { + "A": {"B", "C", "E"}, + "B": {"A", "D"}, + "C": {"A"}, + "D": {"B", "E"}, + "E": {"D", "A"}, +} + + +# graph data structure +class Node: + def __init__(self, val: str, neighbors: list = None): + self.val = val + if neighbors: + self.neighbors = neighbors + else: + self.neighbors = set() + + def addNeighbor(self, n): + self.neighbors.add(n) + + +class Graph: + def __init__(self, connections: list = None): + self.nodes = {} + if connections: + self.parse(connections) + + def createNode(self, values: list): + for value in values: + self.nodes[value] = Node(value) + + def createEdge(self, n1: str, n2: str): + self.nodes[n1].addNeighbor(self.nodes[n2]) + self.nodes[n2].addNeighbor(self.nodes[n1]) + + def parse(self, connections: list): + for connection in connections: + if connection[0] not in self.nodes: + self.createNode([connection[0]]) + if connection[1] not in self.nodes: + self.createNode([connection[1]]) + + self.createEdge(connection[0], connection[1]) + + def __repr__(self): + s = "{\n" + for value in self.nodes: + node = self.nodes[value] + s += f"\t{value}: {[n.val for n in node.neighbors]}\n" + s += "}" + return s + + +# Takes in a list of tuples representing connections +my_graph = Graph( + [ + ("A", "B"), + ("B", "E"), + ("E", "D"), + ("D", "F"), + ("D", "A"), + ("A", "C"), + ("C", "B"), + ] +) +print(my_graph) diff --git a/dsa/chapter2/examples/linked_list.py b/dsa/chapter2/examples/linked_list.py new file mode 100644 index 00000000..371c6131 --- /dev/null +++ b/dsa/chapter2/examples/linked_list.py @@ -0,0 +1,216 @@ +from datetime import datetime as d + + +class Node: + def __init__(self, value, prev=None, next=None) -> None: + self.value = value + self.prev = prev + self.next = next + + +class DoublyLinkedList: + def __init__(self) -> None: + self.head = Node(None) + self.head.next = Node(None, self.head) + self.tail = self.head.next + self.size = 0 + + def add_current(self, value, current) -> bool: + """ + Helper method + + O(1) operation + + Arguments: + @param value - the value to insert + + @param current - the node to insert the value in front of + + @returns bool - True on success + """ + if current.next is None: + current.next = Node(value, current) + else: + current.next = Node(value, current, current.next) + if current.next.next: + current.next.next.prev = current.next + self.size += 1 + return True + + def add_front(self, value) -> bool: + """ + O(1) operation + """ + return self.add_current(value, self.head) + + def add_back(self, value) -> bool: + """ + O(1) operation + """ + return self.add_current(value, self.tail.prev) + + def add(self, value, idx) -> bool: + """ + O(N) operation since it has to iterate to idx + """ + if idx > self.size: + return False + current = self.head + for i in range(idx): + current = current.next + self.add_current(value, current) + + def set(self, value, idx) -> bool: + """ + O(N) operation since it has to iterate to idx + """ + if idx >= self.size: + return False + current = self.head.next + for i in range(idx): + current = current.next + current.value = value + return True + + def set_front(self, value) -> bool: + """ + O(1) operation + """ + if self.size == 0: + return False + self.head.next.value = value + return True + + def set_back(self, value) -> bool: + """ + O(1) operation + """ + if self.size == 0: + return False + self.tail.prev.value = value + return True + + def remove_current(self, current) -> bool: + """ + Helper method (O(1) operation) + + Arguments: + @param current - the node to be removed + + @returns bool - True on success + """ + current.prev.next = current.next + if current.prev.next: + current.prev.next.prev = current.prev + self.size -= 1 + return True + + def remove_value(self, value) -> bool: + """ + Attempts to remove the first occurrence of value from the list. + + O(N) operation since it has to iterate through the list to find the value + + @returns bool - True on success (value found and removed), + False on failure to find the value + """ + current = self.head.next + + # advance the cursor until either we've reached the end + # of the list or we've reached the value + while current != self.tail and current.value != value: + current = current.next + + # found the item, time to remove + if current != self.tail and current.value == value: + self.remove_current(current) + return True + + # didn't find the item + return False + + def remove_front(self) -> bool: + if self.size == 0: + return True + return self.remove_current(self.head.next) + + def remove_back(self) -> bool: + if self.size == 0: + return True + return self.remove_current(self.tail.prev) + + def remove_idx(self, idx) -> bool: + """ + O(N) operation since it has to iterate to idx + """ + if idx >= self.size: + return False + current = self.head.next + for i in range(idx): + current = current.next + return self.remove_current(current) + + def clear(self) -> bool: + """ + O(1) operation + """ + self.head.next = self.tail + self.tail.prev = self.head + self.size = 0 + + def __str__(self) -> str: + ret = "[" + current = self.head.next + while current.next is not None: + ret += str(current.value) + current = current.next + if current.next is not None: + ret += ", " + ret += "]" + return ret + + def print_from_front(self) -> None: + print(self) + + def print_from_back(self) -> None: + current = self.tail.prev + print("[", end="") + while current.prev is not None: + print(current.value, end="") + current = current.prev + if current.prev is not None: + print(", ", end="") + print("]") + + +my_double = DoublyLinkedList() +my_regular = [] + +TEST_SIZE = 100000 + +start = d.now() +for i in range(TEST_SIZE): + my_regular.insert(0, i) +end = d.now() +print(f"it took {(end-start).total_seconds()} seconds to do that regularly") + +start = d.now() +for i in range(TEST_SIZE): + my_double.add_front(i) +end = d.now() +print(f"it took {(end-start).total_seconds()} seconds to do that doubly") + +start = d.now() +for i in range(TEST_SIZE - 1): + my_regular.pop(0) +end = d.now() +print(f"it took {(end-start).total_seconds()} seconds to do that regularly") + +start = d.now() +for i in range(TEST_SIZE - 1): + my_double.remove_front() +end = d.now() +print(f"it took {(end-start).total_seconds()} seconds to do that doubly") + +print(my_regular) +print(my_double) diff --git a/dsa/chapter2/examples/queue.py b/dsa/chapter2/examples/queue.py new file mode 100644 index 00000000..386fa5bd --- /dev/null +++ b/dsa/chapter2/examples/queue.py @@ -0,0 +1,32 @@ +class Queue: + def __init__(self): + # Make List + self.queue_list = [] + + def appending(self, item): + # Checks to see if there are any duplicates in list + if item in self.queue_list: + # If so it returns error + return "Value Already Exists" + else: + self.queue_list.append(item) + + def pops(self): + # Checks to see if list is empty + if len(self.queue_list) != 0: + # if it isn’t empty it removes first value + return self.queue_list.pop(0) + else: + return "List is Empty" + + +Check_Queue = Queue() + +# Should add value to list +Check_Queue.appending(100) +Check_Queue.appending(200) +Check_Queue.appending(300) + +# Should print 300 and then 200 +print(Check_Queue.pops()) +print(Check_Queue.pops()) diff --git a/dsa/chapter2/examples/stack.py b/dsa/chapter2/examples/stack.py new file mode 100644 index 00000000..bc2e00a3 --- /dev/null +++ b/dsa/chapter2/examples/stack.py @@ -0,0 +1,32 @@ +class Stack: + def __init__(self): + # Make List + self.stack_list = [] + + def appending(self, item): + # Checks to see if there are any duplicates in list + if item in self.stack_list: + # If so it returns error + return "Value Already Exists" + else: + self.stack_list.append(item) + + def pops(self): + # Checks to see if list is empty + if len(self.stack_list) != 0: + # if it isn’t empty it removes last value + return self.stack_list.pop() + else: + return "List is Empty" + + +Check_Stack = Stack() + +# Should add value to list +Check_Stack.appending(100) +Check_Stack.appending(200) +Check_Stack.appending(300) + +# Should print 300 and then 200 +print(Check_Stack.pops()) +print(Check_Stack.pops()) diff --git a/dsa/chapter2/practice/basic_bst.py b/dsa/chapter2/practice/basic_bst.py new file mode 100644 index 00000000..5f6469fa --- /dev/null +++ b/dsa/chapter2/practice/basic_bst.py @@ -0,0 +1,112 @@ +""" +Let's create a very basic version of a BST that only +has insertion capabilities. Your task will be to fill +in the node class and the BST class. The structure is somewhat +different from the BST that was given as an example, but +the logic is the same. +""" + + +class BSTNode: + def __init__(self, key, value): + """ + set self.key to key + set self.value to the value + create a left neighbor/child and a right neighbor/child, each of which + start as None + """ + + # your code here + pass + + def add_recursively(self, other_node): + """ + Adds the other node to this node or this node's children. + If other_node's key is equal to this node's key, do nothing. + If other_node's key is less than this node's key, then: + - if this node's left child is None, set this node's left child + to that other node + - if this node's left child is not None, then add this node + recursively to the left child + If other_node's key is greater than this node's key, then: + - if this node's right child is None, set this node's right child + to that other node + - if this node's right child is not None, then add this node + recursively to the right child + """ + + # your code here + pass + + def get_value(self, key): + """ + Tries to return the value of the node whose key matches `key`. + If this node's key matches `key`, return this node's value. + If the key is less than this node's key: + - if left child is None, return 0 + - else, get the value recursively + If the key is greater than this node's key: + - if right child is None, return 0 + - else, get the value recursively + """ + + # your code here + pass + + def __str__(self): + """ + Creates and returns a string that looks like: + left_child, self value, right_child + However, if left child or right_child is None, don't add them + to the string. + """ + + # your code here + pass + + +class BST: + def __init__(self): + # set a root node to None + + # your code here + pass + + def __setitem__(self, item, value): + """ + create a new node whose key is item and whose value is value + then, if root is None, set root to that node. + else, add that node recursively. + """ + + # your code here + pass + + def __getitem__(self, item): + """ + Try to find the node with key that matches item. + If no match is found, return 0 + """ + + # your code here + pass + + def __repr__(self): + """ + Returns a string representation of the root + """ + # your code here + pass + + +my_bst = BST() +my_bst[50] = 30 +my_bst[40] = 31 +my_bst[60] = 32 +my_bst[30] = 33 +my_bst[70] = 34 +my_bst[20] = 35 +my_bst[80] = 36 +my_bst[10] = 37 +my_bst[90] = 38 +print(my_bst) # 37, 35, 33, 31, 10, 32, 34, 36, 38 diff --git a/dsa/chapter2/practice/basic_linked_list.py b/dsa/chapter2/practice/basic_linked_list.py new file mode 100644 index 00000000..08ffd30d --- /dev/null +++ b/dsa/chapter2/practice/basic_linked_list.py @@ -0,0 +1,136 @@ +""" +In this problem, you will create a basic Doubly Linked List. +The goal is to be able to have nodes connected all the way +to 100. All you have to do is fill in the add_front and +add_back methods. +""" + + +class DoublyLinkedListNode: + def __init__(self, value) -> None: + self.value = value + + self.next = None + self.prev = None + + def __repr__(self): + return f"{self.value}" + + +class DoublyLinkedList: + def __init__(self) -> None: + """ + Creates a head and tail node. For convenience, + set the head to a node with the value `None` + and the tail to a node with the value `None`. + Sets head's next node as tail, and tail's previous + node as head. + Sets the size of the list to 0 as well. + + This way, the list will be "empty" when + the head's next node is the tail (and the tail's + previous node is the head). The purpose of these + nodes is to make insertion and deletion much faster. + They will not store any value besides `None` and can + be thought of as placeholders for the beginning and + end of the list + """ + # create the nodes + self.head = DoublyLinkedListNode(None) + self.tail = DoublyLinkedListNode(None) + + # set head's next to tail, and tail's prev to head + self.head.next, self.tail.prev = self.tail, self.head + + # set the size of the list to 0 + self.size = 0 + + def add_front(self, value): + """ + Adds a node with the provided value to the front + of the Doubly Linked List. Increases size by 1 as well. + + By "front of the Doubly Linked List," we mean that it + should be the node right after the placeholder head node. + + Ex: if you had nodes A, B, C, D, and you inserted node E, + then you would have A, E, B, C, D. A's next node would be + E, E's next node would be B, B's prev node would be E, + and E's prev node would be A. + """ + # create a node with the provided value + # add it to the front of the Doubly Linked List + # make sure to correctly set the prev/next nodes + # your code here + + # increase size by 1 + self.size += 1 + + def add_back(self, value): + """ + Adds a node with the provided value to the back + of the Doubly Linked List. Increases size by 1 as well. + + By "back of the Doubly Linked List," we mean that it should + be the node right before the placeholder tail node. + + Ex: if you had nodes A, B, C, D, and you inserted node E, + then you would have A, B, C, E, D. C's next node would be + E, E's next node would be D, D's prev node would be E, + and E's prev node would be C. + """ + # create a node with the provided value + # add it to the back of the Doubly Linked List + # make sure to correctly set the prev/next nodes + + # increase size by 1 + self.size += 1 + + def print_forward(self): + """ + Iterates through and prints all the nodes. + This should start at the head and end at the tail. + """ + # ignore self.head since self.head is a "placeholder" + cur_node = self.head.next + + while cur_node.next is not None: + print(cur_node, end=", ") + cur_node = cur_node.next + print() + + def print_backward(self): + """ + Iterates through and prints all the nodes. + This should start at the tail and end at the tail + """ + # ignore self.tail since self.tail is a "placeholder" + cur_node = self.tail.prev + + while cur_node.prev is not None: + print(cur_node, end=", ") + cur_node = cur_node.prev + print() + + def __len__(self): + """ + Returns the length of the list + """ + return self.size + + +our_doubly_linked_list = DoublyLinkedList() + +# add the numbers 50-99 +for i in range(50, 100): + our_doubly_linked_list.add_back(i) + +# add numbers 49 - 0 +for i in range(49, -1, -1): + our_doubly_linked_list.add_front(i) + +# print our list forward (0 -> 99) +our_doubly_linked_list.print_forward() + +# print our list backward (99 -> 9) +our_doubly_linked_list.print_backward() diff --git a/dsa/chapter2/practice/nodes_to_10.py b/dsa/chapter2/practice/nodes_to_10.py new file mode 100644 index 00000000..e8af4d1f --- /dev/null +++ b/dsa/chapter2/practice/nodes_to_10.py @@ -0,0 +1,35 @@ +""" +Nodes to 10 + +Fill in the node class, then create nodes so that +printing start_node will print the numbers from 0 to 10. +""" + + +class Node: + def __init__(self, value): + self.value = value + + # create a neighbors list + # your code here + + def __repr__(self) -> str: + # start with just the node's value + ret = f"{self.value}, " + + # add all of the neighbors' values (recursively) to the string + for node in self.neighbors: + ret += f"{node}" + + return ret + + +start_node = Node(0) + +# add code that creates nodes and adds them as neighbors so that +# start_node is connected to 1, 1 is connected to 2, 2 is connected to 3, +# 3 is connected to 4, etc. If done correctly, printing start_node will +# print 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, +# your code here + +print(start_node) diff --git a/dsa/chapter2/practice/restaurant_queue.py b/dsa/chapter2/practice/restaurant_queue.py new file mode 100644 index 00000000..1aea151a --- /dev/null +++ b/dsa/chapter2/practice/restaurant_queue.py @@ -0,0 +1,56 @@ +""" +Cook cook cook, orders all day + +As a chef in a restaurant, you cook a bunch of dishes +You can only take one order at a time, and you're tired of having +people complain at you when you don't do their order first. So you decide to +set up a system where you accumulate a "list" of orders and cook one order +-- the first order that was put into the "list" -- at a time. + +Your job is to implement this "list" as an OrderQueue. +You should be able to add new orders into your OrderQueue +and remove finished orders from your OrderQueue. +Starter code is provided. +""" + + +class OrderQueue: + def __init__(self) -> None: + self.orders = [] + + def dequeue(self) -> str: + """ + Removes the first order in the OrderQueue + and returns it + """ + # your code here + pass + + def enqueue(self, order: str) -> None: + """ + Inserts the order into the OrderQueue + + Args: + order: str - the order to be inserted into the OrderQueue + """ + # your code here + pass + + def __len__(self): + return len(self.orders) + + +# test code +uncooked_orders = OrderQueue() + +# 3 customers ordered at the same time +uncooked_orders.enqueue("a medium rare steak") +uncooked_orders.enqueue("six gyoza") +uncooked_orders.enqueue("two enchiladas") + +# now you cook them one by one +for order in range(len(uncooked_orders)): + # this should print + # the medium rare steak first, then the gyoza, and finally the enchiladas + print(f"finished cooking {uncooked_orders.dequeue()}") +print(f"done! (exactly {len(uncooked_orders)} orders left)") diff --git a/dsa/chapter2/practice/word_reversal.py b/dsa/chapter2/practice/word_reversal.py new file mode 100644 index 00000000..dfd89765 --- /dev/null +++ b/dsa/chapter2/practice/word_reversal.py @@ -0,0 +1,66 @@ +""" +Ever wanted to reverse a word a harder way? +Well, look no further than this problem that puts your +knowledge of Stacks to the test in order to solve a problem +that is already solveable by python builtins! + +Your job is to reverse a string by using a stack, +adding every letter in the string (starting from the beginning of the string) +into the stack, and then popping every letter from the stack. +If done correctly, this will result in a reversed version of the string. + +Starter code is given. +""" + + +class Stack: + def __init__(self) -> None: + """ + Initializes the stack. The back of the list will be + the top of the stack (so self.items[-1] is the first item + in the stack) + """ + self.items = [] + + def push(self, letter: str) -> None: + """ + Adds the letter to the stack. The letter should end up + on the *top* of the stack (the back of the list) + """ + # your code here + pass + + def pop(self) -> str: + """ + Removes the top letter from the stack. Returns this letter. + """ + # your code here + pass + + def __len__(self) -> int: + return len(self.items) + + +def reverse_word(word: str) -> str: + letter_stack = Stack() + + # push every letter in the word (starting from the beginning of the word) + # into the stack + for letter in word: + # your code here + pass + + reversed_word = "" + # pop every letter from the stack and add it to our reversed word + for i in range(len(letter_stack)): + # your code here + pass + + return reversed_word + + +# test code +print(reverse_word("boj doog")) +print(reverse_word("racecar")) +print(reverse_word("a man a plan a canal panama")) +print(reverse_word("read kool")) diff --git a/dsa/chapter2/solutions/basic_bst.py b/dsa/chapter2/solutions/basic_bst.py new file mode 100644 index 00000000..5186e4e3 --- /dev/null +++ b/dsa/chapter2/solutions/basic_bst.py @@ -0,0 +1,151 @@ +""" +Let's create a very basic version of a BST that only +has insertion capabilities. Your task will be to fill +in the node class and the BST class. The structure is somewhat +different from the BST that was given as an example, but +the logic is the same. +""" + + +class BSTNode: + def __init__(self, key, value): + """ + set self.key to key + set self.value to the value + create a left neighbor/child and a right neighbor/child, each of which + start as None + """ + + # set key, value + self.key, self.value = key, value + + # set left child, right child + self.left_child: BSTNode = None + self.right_child: BSTNode = None + + def add_recursively(self, other_node): + """ + Adds the other node to this node or this node's children. + If other_node's key is equal to this node's key, do nothing. + If other_node's key is less than this node's key, then: + - if this node's left child is None, set this node's left child + to that other node + - if this node's left child is not None, then add this node + recursively to the left child + If other_node's key is greater than this node's key, then: + - if this node's right child is None, set this node's right child + to that other node + - if this node's right child is not None, then add this node + recursively to the right child + """ + if other_node.key == self.key: + return # do nothing + + if other_node.key < self.key: + if self.left_child is None: + self.left_child = other_node + else: + self.left_child.add_recursively(other_node) + + if other_node.key > self.key: + if self.right_child is None: + self.right_child = other_node + else: + self.right_child.add_recursively(other_node) + + def get_value(self, key): + """ + Tries to return the value of the node whose key matches `key`. + If this node's key matches `key`, return this node's value. + If the key is less than this node's key: + - if left child is None, return 0 + - else, get the value recursively + If the key is greater than this node's key: + - if right child is None, return 0 + - else, get the value recursively + """ + if self.key == key: + return self.value + + if key < self.key: + if self.left_child is None: + return 0 + else: + return self.left_child.get_value(key) + + if key > self.key: + if self.right_child is None: + return 0 + else: + return self.right_child.get_value(key) + + def __str__(self): + """ + Creates and returns a string that looks like: + left_child, self value, right_child + However, if left child or right_child is None, don't add them + to the string. + """ + ret = "" + + # add left child to the string if it isn't None + if self.left_child is not None: + ret += str(self.left_child) + ", " + + # add self.value to the string + ret += str(self.value) + + # add right child to the string if it isn't None + if self.right_child is not None: + ret += ", " + str(self.right_child) + + return ret + + +class BST: + def __init__(self): + # set a root node to None + self.root = None + + def __setitem__(self, item, value): + """ + create a new node whose key is item and whose value is value + then, if root is None, set root to that node. + else, add that node recursively. + """ + # create the new node + new_node = BSTNode(item, value) + + # either add it recursively or set it as the root + if self.root is None: + self.root = new_node + else: + self.root.add_recursively(new_node) + + def __getitem__(self, item): + """ + Try to find the node with key that matches item. + If root is None or no match is found return 0 + """ + if self.root is None: + return 0 + return self.root.get_value(item) + + def __repr__(self): + """ + Returns a string representation of the root + """ + return str(self.root) + + +my_bst = BST() +my_bst[50] = 30 +my_bst[40] = 31 +my_bst[60] = 32 +my_bst[30] = 33 +my_bst[70] = 34 +my_bst[20] = 35 +my_bst[80] = 36 +my_bst[10] = 37 +my_bst[90] = 38 +print(my_bst) diff --git a/dsa/chapter2/solutions/basic_linked_list.py b/dsa/chapter2/solutions/basic_linked_list.py new file mode 100644 index 00000000..0719dea7 --- /dev/null +++ b/dsa/chapter2/solutions/basic_linked_list.py @@ -0,0 +1,147 @@ +""" +In this problem, you will create a basic Doubly Linked List. +The goal is to be able to have nodes connected all the way +to 100. All you have to do is fill in the add_front and +add_back methods. +""" + + +class DoublyLinkedListNode: + def __init__(self, value) -> None: + self.value = value + + self.next = None + self.prev = None + + def __repr__(self): + return f"{self.value}" + + +class DoublyLinkedList: + def __init__(self) -> None: + """ + Creates a head and tail node. For convenience, + set the head to a node with the value `None` + and the tail to a node with the value `None`. + Sets head's next node as tail, and tail's previous + node as head. + Sets the size of the list to 0 as well. + + This way, the list will be "empty" when + the head's next node is the tail (and the tail's + previous node is the head). The purpose of these + nodes is to make insertion and deletion much faster. + They will not store any value besides `None` and can + be thought of as placeholders for the beginning and + end of the list + """ + # create the nodes + self.head = DoublyLinkedListNode(None) + self.tail = DoublyLinkedListNode(None) + + # set head's next to tail, and tail's prev to head + self.head.next, self.tail.prev = self.tail, self.head + + # set the size of the list to 0 + self.size = 0 + + def add_front(self, value): + """ + Adds a node with the provided value to the front + of the Doubly Linked List. Increases size by 1 as well. + + By "front of the Doubly Linked List," we mean that it + should be the node right after the placeholder head node. + + Ex: if you had nodes A, B, C, D, and you inserted node E, + then you would have A, E, B, C, D. A's next node would be + E, E's next node would be B, B's prev node would be E, + and E's prev node would be A. + """ + # create a node with the provided value + new_node = DoublyLinkedListNode(value) + + # get the old second-to-front node + old_second_to_front_node = self.head.next + + # change the orders + old_second_to_front_node.prev, new_node.prev = new_node, self.head + self.head.next, new_node.next = new_node, old_second_to_front_node + + # increase size by 1 + self.size += 1 + + def add_back(self, value): + """ + Adds a node with the provided value to the back + of the Doubly Linked List. Increases size by 1 as well. + + By "back of the Doubly Linked List," we mean that it should + be the node right before the placeholder tail node. + + Ex: if you had nodes A, B, C, D, and you inserted node E, + then you would have A, B, C, E, D. C's next node would be + E, E's next node would be D, D's prev node would be E, + and E's prev node would be C. + """ + # create a node with the provided value + new_node = DoublyLinkedListNode(value) + + # get the old second-to-last node + old_second_to_last_node = self.tail.prev + + # change the orders + old_second_to_last_node.next, new_node.next = new_node, self.tail + self.tail.prev, new_node.prev = new_node, old_second_to_last_node + + # increase size by 1 + self.size += 1 + + def print_forward(self): + """ + Iterates through and prints all the nodes. + This should start at the head and end at the tail. + """ + # ignore self.head since self.head is a "placeholder" + cur_node = self.head.next + + while cur_node.next is not None: + print(cur_node, end=", ") + cur_node = cur_node.next + print() + + def print_backward(self): + """ + Iterates through and prints all the nodes. + This should start at the tail and end at the tail + """ + # ignore self.tail since self.tail is a "placeholder" + cur_node = self.tail.prev + + while cur_node.prev is not None: + print(cur_node, end=", ") + cur_node = cur_node.prev + print() + + def __len__(self): + """ + Returns the length of the list + """ + return self.size + + +our_doubly_linked_list = DoublyLinkedList() + +# add the numbers 50-99 +for i in range(50, 100): + our_doubly_linked_list.add_back(i) + +# add numbers 49 - 0 +for i in range(49, -1, -1): + our_doubly_linked_list.add_front(i) + +# print our list forward (0 -> 99) +our_doubly_linked_list.print_forward() + +# print our list backward (99 -> 9) +our_doubly_linked_list.print_backward() diff --git a/dsa/chapter2/solutions/nodes_to_10.py b/dsa/chapter2/solutions/nodes_to_10.py new file mode 100644 index 00000000..aef5a221 --- /dev/null +++ b/dsa/chapter2/solutions/nodes_to_10.py @@ -0,0 +1,39 @@ +""" +Nodes to 10 + +Fill in the node class, then create nodes so that +printing start_node will print the numbers from 0 to 10. +""" + + +class Node: + def __init__(self, value): + self.value = value + + # create a neighbors list + self.neighbors = [] + + def __repr__(self) -> str: + # start with just the node's value + ret = f"{self.value}, " + + # add all of the neighbors' values (recursively) to the string + for node in self.neighbors: + ret += f"{node}" + + return ret + + +start_node = Node(0) + +# add code that creates nodes and adds them as neighbors so that +# start_node is connected to 1, 1 is connected to 2, 2 is connected to 3, +# 3 is connected to 4, etc. If done correctly, printing start_node will +# print 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, +previous_node = start_node +for value in range(1, 11): + cur_node = Node(value) + previous_node.neighbors.append(cur_node) + previous_node = cur_node + +print(start_node) diff --git a/dsa/chapter2/solutions/restaurant_queue.py b/dsa/chapter2/solutions/restaurant_queue.py new file mode 100644 index 00000000..2eb55e07 --- /dev/null +++ b/dsa/chapter2/solutions/restaurant_queue.py @@ -0,0 +1,54 @@ +""" +Cook cook cook, orders all day + +As a chef in a restaurant, you cook a bunch of dishes +You can only take one order at a time, and you're tired of having +people complain at you when you don't do their order first. So you decide to +set up a system where you accumulate a "list" of orders and cook one order +-- the first order that was put into the "list" -- at a time. + +Your job is to implement this "list" as an OrderQueue. +You should be able to add new orders into your OrderQueue +and remove finished orders from your OrderQueue. +Starter code is provided. +""" + + +class OrderQueue: + def __init__(self) -> None: + self.orders = [] + + def dequeue(self) -> str: + """ + Removes the first order in the OrderQueue + and returns it + """ + return self.orders.pop(0) + + def enqueue(self, order: str) -> None: + """ + Inserts the order into the OrderQueue + + Args: + order: str - the order to be inserted into the OrderQueue + """ + self.orders.append(order) + + def __len__(self): + return len(self.orders) + + +# test code +uncooked_orders = OrderQueue() + +# 3 customers ordered at the same time +uncooked_orders.enqueue("a medium rare steak") +uncooked_orders.enqueue("six gyoza") +uncooked_orders.enqueue("two enchiladas") + +# now you cook them one by one +for order in range(len(uncooked_orders)): + # this should print + # the medium rare steak first, then the gyoza, and finally the enchiladas + print(f"finished cooking {uncooked_orders.dequeue()}") +print(f"done! (exactly {len(uncooked_orders)} orders left)") diff --git a/dsa/chapter2/solutions/word_reversal.py b/dsa/chapter2/solutions/word_reversal.py new file mode 100644 index 00000000..e8372fcb --- /dev/null +++ b/dsa/chapter2/solutions/word_reversal.py @@ -0,0 +1,62 @@ +""" +Ever wanted to reverse a word a harder way? +Well, look no further than this problem that puts your +knowledge of Stacks to the test in order to solve a problem +that is already solveable by python builtins! + +Your job is to reverse a string by using a stack, +adding every letter in the string (starting from the beginning of the string) +into the stack, and then popping every letter from the stack. +If done correctly, this will result in a reversed version of the string. + +Starter code is given. +""" + + +class Stack: + def __init__(self) -> None: + """ + Initializes the stack. The back of the list will be + the top of the stack (so self.items[-1] is the first item + in the stack) + """ + self.items = [] + + def push(self, letter: str) -> None: + """ + Adds the letter to the stack. The letter should end up + on the *top* of the stack (the back of the list) + """ + self.items.append(letter) + + def pop(self) -> str: + """ + Removes the top letter from the stack. Returns this letter. + """ + return self.items.pop() + + def __len__(self) -> int: + return len(self.items) + + +def reverse_word(word: str) -> str: + letter_stack = Stack() + + # push every letter in the word (starting from the beginning of the word) + # into the stack + for letter in word: + letter_stack.push(letter) + + reversed_word = "" + # pop every letter from the stack and add it to our reversed word + for i in range(len(letter_stack)): + reversed_word += letter_stack.pop() + + return reversed_word + + +# test code +print(reverse_word("boj doog")) +print(reverse_word("racecar")) +print(reverse_word("a man a plan a canal panama")) +print(reverse_word("read kool")) diff --git a/dsa/chapter3/examples/a_star.py b/dsa/chapter3/examples/a_star.py new file mode 100644 index 00000000..254b2337 --- /dev/null +++ b/dsa/chapter3/examples/a_star.py @@ -0,0 +1,84 @@ +from queue import PriorityQueue + + +def heuristic(cell, end): + # assuming (100, 100) is end + x1, y1 = cell + x2, y2 = end + return abs(x2 - x1) + abs(y2 - y1) + + +def reconstruct(path, end): + final = [] + curr = end + while curr in path: + final.append(curr) + curr = path[curr] + + return reversed(final) + + +def get_neighbors(cell, start=(0, 0), end=(100, 100)): + def between(a, b, c): + return (b <= a and a <= c) or (b >= a and a >= c) + + x1, y1 = cell + return [ + (x + x1, y + y1) + for x in range(-1, 2) + for y in range(-1, 2) + if ( + between(x + x1, start[0], end[0]) + and between(y + y1, start[1], end[1]) + ) + ] + + +def a_star(end: tuple = (100, 100), start: tuple = (0, 0)): + count = 0 + open = PriorityQueue() + open.put((0, count, start)) + path = {} + g_score = { + (x, y): float("inf") + for x in range(end[0] + 1) + for y in range(end[1] + 1) + } + g_score[start] = 0 + + f_score = { + (x, y): float("inf") for x in range(end[0]) for y in range(end[1]) + } + f_score[0] = heuristic(start, end) + + while not open.empty(): + curr = open.get()[2] + + temp_g = g_score[curr] + 1 + for n in get_neighbors(curr, start, end): + if n == end: + path[n] = curr + return reconstruct(path, end) + if temp_g < g_score[n]: + path[n] = curr + g_score[n] = temp_g + f_score[n] = temp_g + heuristic(n, end) + if not any(n == item[2] for item in open.queue): + count += 1 + open.put((f_score[n], count, n)) + + # path not found + return None + + +path = [coord for coord in a_star((50, 10))] +path.insert(0, (0, 0)) + +for y in range(0, 11): + lst = [] + for x in range(0, 51): + if (x, y) not in path: + lst.append("x") + else: + lst.append("o") + print(lst) diff --git a/dsa/chapter3/examples/bfs.py b/dsa/chapter3/examples/bfs.py new file mode 100644 index 00000000..5fe04c0a --- /dev/null +++ b/dsa/chapter3/examples/bfs.py @@ -0,0 +1,19 @@ +from queue import Queue + + +def BFS(start_node): + visited = set(start_node) + current_depth_nodes = Queue() + current_depth_nodes.put(start_node) + + while len(current_depth_nodes) != 0: # this depth is not empty + # returns and deletes the first element + current_node = current_depth_nodes.get() + + # add each neighbor to this depth + for neighbor in current_node.neighbors: + if neighbor not in visited: + # adds element to end + visited.add(neighbor) + # adds element to end + current_depth_nodes.put(neighbor) diff --git a/dsa/chapter3/examples/binary_search.py b/dsa/chapter3/examples/binary_search.py new file mode 100644 index 00000000..8869c353 --- /dev/null +++ b/dsa/chapter3/examples/binary_search.py @@ -0,0 +1,27 @@ +def binary_search(lst, item): + """ + Arguments: + lst - a list sorted in ascending order + item - the item that we want to find + Returns: + the idx of the item or -1 if not found + """ + + low_bound = 0 + upper_bound = len(lst) - 1 + + # take the average, but make sure it's an integer + cur_idx = (low_bound + upper_bound) // 2 + + while low_bound <= upper_bound: + if lst[cur_idx] == item: + return cur_idx + if lst[cur_idx] < item: + # it was an undershot, so set this as the new lower bound + low_bound = cur_idx + 1 + else: # lst[cur_idx] > item) + # it was an overshot, so set this as the new upper bound + upper_bound = cur_idx - 1 + # update cur_idx + cur_idx = (low_bound + upper_bound) // 2 + return -1 diff --git a/dsa/chapter3/examples/dfs.py b/dsa/chapter3/examples/dfs.py new file mode 100644 index 00000000..7de583cb --- /dev/null +++ b/dsa/chapter3/examples/dfs.py @@ -0,0 +1,20 @@ +def recursive_dfs(visited, graph, node): + if node not in visited: + print(node) + visited.add(node) + for neighbour in graph[node]: + recursive_dfs(visited, graph, neighbour) + + +def iterative_dfs(graph, start): + stack, path = [start], [] + + while stack: + node = stack.pop() + if node in path: + continue + path.append(node) + for neighbor in graph[node]: + stack.append(neighbor) + + return path diff --git a/dsa/chapter3/examples/linear_search.py b/dsa/chapter3/examples/linear_search.py new file mode 100644 index 00000000..e8821336 --- /dev/null +++ b/dsa/chapter3/examples/linear_search.py @@ -0,0 +1,14 @@ +def linear_search(arr, val) -> int: + """ + Search the provided array for the provided value + and get the index, if found + Arguments: + arr - the array to search in + val - the value to search for + Returns: + int - the index of the value if it was found, else -1 + """ + for i in range(len(arr)): + if arr[i] == val: + return i + return -1 diff --git a/dsa/chapter3/examples/mergesort.py b/dsa/chapter3/examples/mergesort.py new file mode 100644 index 00000000..235d46c9 --- /dev/null +++ b/dsa/chapter3/examples/mergesort.py @@ -0,0 +1,42 @@ +def mergelists(lst1, lst2): + idx1 = 0 + idx2 = 0 + ret = [] + + # while either one has unused items + while idx1 < len(lst1) or idx2 < len(lst2): + # both lists still have items + if idx1 < len(lst1) and idx2 < len(lst2): + if lst1[idx1] < lst2[idx2]: + ret.append(lst1[idx1]) # add the item from lst1 + idx1 += 1 # increment our idx in lst1 + else: # lst2[idx2] <= lst1[idx1] + ret.append(lst2[idx2]) # add the item from lst2 + idx2 += 1 # increment our idx in lst2 + + # only one list still has items + elif idx1 < len(lst1): # if only lst1 still has items + ret.extend(lst1[idx1:]) # add the rest of this list + idx1 = len(lst1) + elif idx2 < len(lst2): # if only lst2 still has items + ret.extend(lst2[idx2:]) # add the rest of this list + idx2 = len(lst2) + + return ret + + +def mergesort(lst): + # "base case" where the list is just 0 or 1 item(s). + # In this case, we can say it is already sorted and just return it. + if len(lst) <= 1: + return lst + + # if it's not just 1 or 0 item(s), then follow mergesort logic + middle_idx = len(lst) // 2 # we want an integer, so use // + first_half = mergesort(lst[:middle_idx]) # sort the first half + second_half = mergesort(lst[middle_idx:]) # sort the second half + return mergelists(first_half, second_half) # merge the two sorted halves + + +print(mergesort([5, 4, 3, 2, 1])) +print(mergesort([i for i in range(99, -1, -1)])) diff --git a/dsa/chapter3/examples/quicksort.py b/dsa/chapter3/examples/quicksort.py new file mode 100644 index 00000000..13c41aac --- /dev/null +++ b/dsa/chapter3/examples/quicksort.py @@ -0,0 +1,71 @@ +def partitionv1(arr, pi): + """ + partitionv1 takes some pivot index (pi), and puts all of the items + smaller than pivot to the left, and all of the items larger than + pivot to the right, + + in doing so we put pivot in the same spot as if the entire list was + sorted + + note: this is only one way of doing it + """ + # moves pivot to the end of the list so it doesn't get in the way + arr[-1], arr[pi] = arr[pi], arr[-1] + + i = 0 # i is initialized to be the left side of our list + + for j in range(len(arr)): + # if j is smaller than the pivot, arr[j] is smaller than the pivot, + # so we want to move it to the left + if arr[j] < arr[-1]: + # swaps arr[j] and arr[i], so arr[j] is at the left side of the list + arr[i], arr[j] = arr[j], arr[i] + i += 1 + # move the pivot from the end to the correct location + arr[i], arr[-1] = arr[-1], arr[i] + + +def partitionv2(arr, low, high): + """ + partitionv2 takes an array, a low, and a high and partitions + the section of the array between low and high (inclusive). + + partitionv2 always partitions with the last element in the + section as the pivot + """ + + i = low # i is initialized to be the left side of our list + + for j in range(low, high): + # if j is smaller than the pivot, arr[j] is smaller than the pivot, + # so we want to move it to the left + if arr[j] < arr[high]: + # swaps arr[j] and arr[i], so arr[j] is at the left side of the list + arr[i], arr[j] = arr[j], arr[i] + i += 1 + # move the pivot from the end to the correct location + arr[i], arr[high] = arr[high], arr[i] + + +def quicksort(arr, low, high): + """ + quicksort that recursively partitions the left nd right side + of the pivot + + This implementation always partitions with the last element as the pivot + """ + + i = low # i is initialized to be the left side of our list + + for j in range(low, high): + # if j is smaller than the pivot, arr[j] is smaller than the pivot, + # so we want to move it to the left + if arr[j] < arr[high]: + # swaps arr[j] and arr[i], so arr[j] is at the left side of the list + arr[i], arr[j] = arr[j], arr[i] + i += 1 + # move the pivot from the end to the correct location + arr[i], arr[high] = arr[high], arr[i] + + quicksort(arr, low, i - 1) # left side of the pivot + quicksort(arr, i + 1, high) # right side of the pivot diff --git a/dsa/chapter3/examples/selection_sort.py b/dsa/chapter3/examples/selection_sort.py new file mode 100644 index 00000000..b8731de5 --- /dev/null +++ b/dsa/chapter3/examples/selection_sort.py @@ -0,0 +1,27 @@ +def selection_sort(lst): + for i in range(len(lst)): + min_value = lst[i] + min_val_idx = i + + # find the new minimum value and its idx + for x in range(i, len(lst)): + if lst[x] < min_value: + min_value = lst[x] + min_val_idx = x + + # swap the minimum value with the value at the current idx + lst[i], lst[min_val_idx] = min_value, lst[i] + return lst + + +test1 = [3, 12, 7, 2, 0, 3] +test1 = selection_sort(test1) +print(test1) # [0, 2, 3, 3, 7, 12] + +test2 = [-23, 0, 72, -33, 11, 6, 2, -5, -9, 10, -1] +test2 = selection_sort(test2) +print(test2) # [-33, -23, -9, -5, -1, 0, 2, 6, 10, 11, 72] + +test3 = [i for i in range(1000, -1, -1)] +test3 = selection_sort(test3) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...] +print(test3) diff --git a/dsa/chapter3/practice/a_star.py b/dsa/chapter3/practice/a_star.py new file mode 100644 index 00000000..eb2b598a --- /dev/null +++ b/dsa/chapter3/practice/a_star.py @@ -0,0 +1,186 @@ +""" +A Star Practice + +In this practice problem, you get to fill in some a_star code +as well as see the effects of using different heuristics on +a_star's execution time. + +Heuristics and helper functions are given. Your job is to fill +in sections of the A* algorithm where it says 'your code here' +""" + +from queue import PriorityQueue +import math +import random + + +class Point: + def __init__(self, x: int, y: int) -> None: + self.x: int = x + self.y: int = y + + def get_neighbors(self, start, end): + """ + This function returns a list of neighboring points + using the fact that neighboring points will be the + following (p = neighboring, c = current) + + ``` + p p p + p c p + p p p + ``` + """ + + def between(a, b, c): + return (b <= a and a <= c) or (b >= a and a >= c) + + return [ + Point(x + self.x, y + self.y) + for x in range(-1, 2) + for y in range(-1, 2) + if ( + between(x + self.x, start.x, end.x) + and between(y + self.y, start.y, end.y) + ) + ] + + def __eq__(self, __o: object) -> bool: + return self.x == __o.x and self.y == __o.y + + def __hash__(self) -> int: + return hash((self.x, self.y)) + + def __str__(self): + return f"({self.x}, {self.y})" + + def __repr__(self) -> str: + return str(self) + + +def adding_heuristic(cur: Point, end: Point): + """ + this heuristic returns a value + that looks like the following: + abs(x-x1) + abs(y-y1). + """ + return abs(cur.x - end.x) + abs(cur.y - end.y) + + +def triangle_heuristic(cur: Point, end: Point): + """ + this heuristic will return a value + based off the pythagorean theorem + that looks like the following + sqrt((x-x1)^2 + (y-y1)^2) + """ + return math.sqrt((cur.x - end.x) ** 2 + (cur.y - end.y) ** 2) + + +def bad_heuristic(cur: Point, end: Point): + """ + This heuristic will return a value + that is the opposite of the distance, + meaning that the closer cur is to end, + the worse (higher) score this will give it + """ + return -(abs(cur.x - end.x) + abs(cur.y - end.y)) + + +def random_heuristic(cur: Point, end: Point): + """ + Returns a totally random number. + """ + return random.randint(cur.x, end.x) + random.randint(cur.y, end.y) + + +def reconstruct_path(path: dict, start, end): + backwards_path = [] + curr = end # we know that we start at the end + while curr in path: + # add the current node to the backwards_path + backwards_path.append(curr) + + # since path is a dictionary of node : how to get there, + # we get the previous node in the path by doing path[curr] + curr = path[curr] + + # this will be the first item after we reverse the list + backwards_path.append(start) + + return reversed(backwards_path) + + +def a_star(start: Point, end: Point, heuristic): + # min_x, max_x = min(start.x, end.x), max(start.x, end.x) # uncomment this + # min_y, max_y = min(start.y, end.y), max(start.y, end.y) # uncomment this + """ + initialize f_scores (final scores) to infinity for every + point between the [min_x, min_y] and [max_x, max_y] + + initialize g_scores (distance to get there) to infinity for every + point between [min_x, min_y] and [max_x, max_y] + + Since it takes 0 steps to get to the start, initialize that g score to 0 + """ + # your code here + + # initialize our unexplored queue and add + # insert the start node. + # format for inserting nodes: (f_score, count, node) + count = 0 + unexplored = PriorityQueue() + unexplored.put((0, count, start)) + + # this is a dictionary that stores entries in the format + # node: how to get there + # this means that path[(1, 1)] might equal (0, 0) + # since maybe the path goes from (0, 0) to (1, 1) + # we use this variable to help us reconstruct the path that + # a star found + path = {} + + # allows us to see how many executions it really took + num_executions = 0 + + while not unexplored.empty(): + current: Point = unexplored.get()[2] # just get the Point + + # it takes 1 more step to get to any neighbor, so their g_scores will + # be one more than the current g score + for node in current.get_neighbors(start, end): + if node == end: + # the way to get to the end is from the current node + path[node] = current + print(f"finished after {num_executions} executions") + return reconstruct_path(path, start, end) + else: + """ + if either we haven't explored this node yet + (meaning g_scores[node] = infinity) or this + is a shorter path to get to this node + (g_score[current] + 1 < g_scores[node]), then: + + * update our path + * update our f and g scores: + * remember, f score = g score + heuristic + * if it wasn't already in unexplored: + * update our count + * add the unexplored node w/ its score and count to + unexplored + """ + # your code here + + num_executions += 1 + print(f"no solution found after {num_executions} executions") + return None # no path found + + +# you can try changing the heuristic and seeing how that affects the path taken +# as well as the number of executions it took +path = a_star(Point(0, 0), Point(10, 15), adding_heuristic) +path_len = 0 +for i in path: + print(i) + path_len += 1 +print(f"path length was {path_len}") diff --git a/dsa/chapter3/practice/bfs_dfs.py b/dsa/chapter3/practice/bfs_dfs.py new file mode 100644 index 00000000..7480d097 --- /dev/null +++ b/dsa/chapter3/practice/bfs_dfs.py @@ -0,0 +1,101 @@ +""" +In this practice problem, we practice implementing +breadth first search and depth first search and see them +in action +""" + + +class Node: + def __init__(self, value: int) -> None: + self.value: int = value + self.children = [] + + def __str__(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return str(self) + + +def BFS(start_node: Node): + """ + Implement a breadth-first search algorithm, + that prints the nodes that you visit as you go. + + Remember, a breadth-first search algorithm works by + visiting all the children in a certain depth before + advancing to the next depth + + Note that your function will be slightly different from + the one in the example since the nodes in this file + have children and not neighbors + """ + # first, initialize an empty list of all visited nodes + # next, initialize a list of all the nodes at the current depth + # and have it contain start_node + # lastly, create an empty list of all the nodes at the next depth. + # your code here + + # iterate until the the list of nodes at the current depth is empty + # in each iteration, go through all the nodes at the current depth + # and, if it isn't in the list of visited nodes: + # print it + # add it to visited nodes + # add its children to the list of nodes at the next depth + # once done iterating through all the nodes at the current depth, + # set the list of nodes at the current depth equal to the + # list of nodes at the next depth + # and set the next depth to an empty list + # your code here + + +def DFS(start_node: Node, visited: list = []): + """ + Implement a recursive depth-first search algorithm, + that prints the nodes that you visit as you go. + + Remember, a depth-first search algorithm goes all the way + down to the last child before working its way up and visiting + neighbors + + Note that your function will be slightly different from + the one in the example since the nodes in this file + have children and not neighbors + """ + # check if the start node is in visited + # if it isn't, then print the node + # add it to the list of visited, + # and then use DFS on each of its children + # (make sure to pass the list of visited as an argument) + + +if __name__ == "__main__": + # make a graph that looks like the following + # / 5 + # 2 - 6 - 10 + # / + # 1 - 3 - 7 - 11 - 12 + # \ + # 4 - 8 + # \ 9 + start_node = Node(1) + for i in range(3): + start_node.children.append(Node(i + 2)) + start_node.children[0].children.append(Node(5)) + start_node.children[0].children.append(Node(6)) + start_node.children[0].children[1].children.append(Node(10)) + + start_node.children[1].children.append(Node(7)) + start_node.children[1].children[0].children.append(Node(11)) + start_node.children[1].children[0].children[0].children.append(Node(12)) + + start_node.children[2].children.append(Node(8)) + start_node.children[2].children.append(Node(9)) + + print("with BFS") + BFS(start_node) # 1 2 3 4 5 6 7 8 9 10 11 12 + print() + + print("with DFS") + DFS(start_node) # 1 2 5 6 10 3 7 11 12 4 8 9 + print() diff --git a/dsa/chapter3/practice/mergesort.py b/dsa/chapter3/practice/mergesort.py new file mode 100644 index 00000000..11d85596 --- /dev/null +++ b/dsa/chapter3/practice/mergesort.py @@ -0,0 +1,69 @@ +def mergesort(lst: list) -> list: + """ + Let's implement mergesort, + First let's create a base case where if the list is 0 or 1 elements long, + return it + """ + # your code here + + """ + Now that we have handled the base case, if the list is any longer, we can + go into typical mergesort logic, + + We need to split the list into 2 halves, so lets first find the middle + index value. Use // instead of / because we want an integer + """ + # your code here + + """ + Now we can run mergesort on the first and second halves of lst. Create a + variable first_half which is the result of calling mergesort on the first + half of lst. Repeat for the second half of the list, creating the variable + second_half. Use list splicing for this. + """ + # your code here + + """ + Now we need to merge the two sorted halves. In order to do this, we will + implement a mergelists helper function. Return the result of mergelists + with first_half and second_half as the two parameters. + """ + return # your code here + + +def mergelists(lst1: list, lst2: list) -> list: + idx1 = 0 + idx2 = 0 + ret = [] + + """ + Let's create a while loop that runs for as long as idx1 or idx2 is less + than the len of lst1 or lst2. + """ + while idx1 < len(lst1) or idx2 < len(lst2): + # If both lists have items, we need to compare the first items of the + # lst1 and lst2, and append whichever item is smaller to ret. Then, we + # increment the idx1 or idx2 variable respectively + if idx1 < len(lst1) and idx2 < len(lst2): + # your code here + pass + + elif idx1 < len(lst1): + # if only lst1 has items left, append the remaining items to the + # end of ret, and set idx1 to len(lst1) + + # your code here + pass + elif idx2 < len(lst2): + # if only lst2 has items left, append the remaining items to the + # end of ret, and set idx2 to len(lst2) + + # your code here + pass + return ret + + +if __name__ == "__main__": + lst = [-3, 5, -10, 18, 74, 22, 1, -40] + mergesort(lst) + print(lst) diff --git a/dsa/chapter3/practice/quicksort.py b/dsa/chapter3/practice/quicksort.py new file mode 100644 index 00000000..a5b76662 --- /dev/null +++ b/dsa/chapter3/practice/quicksort.py @@ -0,0 +1,89 @@ +lst = [-3, 5, -10, 18, 74, 22, 1, -40] + + +def quicksort(arr: list): + """ + quicksort_recursive takes in the list you are sorting, the first index of + the sublist you want to sort, and the last index of the sublist you want + to sort, in that order + + For the first call to quicksort_recursive, the first index and last index + should be 0 and the index to the last item of the list respectively + + Make a call to quicksort_recursive with the appropriate arguments below + """ + # your code here + + +def quicksort_recursive(arr, low, high): + """ + Arguments: + arr: list, the entire list we are sorting, + low: int, the first index of the sublist we are sorting + high: int, the last index of the sublist we are sorting + """ + # base case + if low >= high: + return + + # partition the sublist and return the pivot_index + pivot_index = partition(arr, low, high) # NOQA + + # recursive calls + """ + After the list has been partitioned around the pivot_index, we need to + call quicksort_recursive on the two sublists: the one to the left of the + pivot_index, and the one to the right + + We do this on the right side by setting the high index to one less than + pivot_index, and on the left side by setting the low index to one higher + than pivot_index + + Make calls to quicksort_recursive with the appropriate arguments below + """ + # your code here + + +def partition(arr, low, high): + """ + Partition takes a pivot (in our case, arr[high]), and accomplishes the + following: + All of the elements between low and high that are SMALLER than the + pivot are placed to the LEFT of the pivot. + Conversely, all elements between low and high that are LARGER than + the pivot are placed to the RIGHT of the pivot + This has the side effect that the location of pivot after the partition has + taken place is the same as if the list was sorted. Of course, the areas to + the left and right of the pivot are not yet sorted. + """ + i = low # initialize i to the left side of what we are sorting + + """ + Create a for loop that creates an index j, and loops through indexes low + (inclusive) to high (exclusive) + """ + # your code here + """ + Inside our loop, we are trying to find items (arr[j]) that are less than + our pivot (arr[high]). If we find one, we want to swap our item + (arr[j]) with arr[i], an item thats to the left side of our + sublist. Then, we will increment i by one, so we don't continuously + swap with same arr[i] over and over again. + + Create an if statement to do this below + """ + # your code here + + """ + Our pivot (arr[high]) is still on the right side of our sublist. Let's swap + it with arr[i] so it moves to the right spot. + """ + # your code here + + # return the pivot_index + return i + + +if __name__ == "__main__": + quicksort(lst, 0, len(lst) - 1) + print(lst) diff --git a/dsa/chapter3/practice/searching.py b/dsa/chapter3/practice/searching.py new file mode 100644 index 00000000..fb3bb591 --- /dev/null +++ b/dsa/chapter3/practice/searching.py @@ -0,0 +1,124 @@ +""" +Let's see the difference between linear and binary searches! +Some of the algorithm is already done for you, but you +will have to fill in some areas. + +Then, run the code and you can see the results +""" + +import random +from datetime import datetime as d + + +def linear_search(arr, val) -> int: + """ + Linear Search - iterates through all the items in the array and checks + equality with the provided value. If the value matches, returns + the index of that value. Else, returns -1 + Arguments: + arr - the array to search + val - the value to search for + Returns: + int - index of the value on success, -1 on failure + """ + for i in range(len(arr)): + # your code here + pass + return -1 + + +def binary_search(arr, val) -> int: + """ + Binary Search - checks the list for a value using a binary search. + Only works on sorted lists since it assumes that all the values + in indexes greater than i are greater and all the values in + indexes less than i are less. + Arguments: + arr - the array to search + val - the value to search for + Returns: + int - index of the value on success, -1 on failure + """ + low = 0 + high = len(arr) - 1 + while low <= high: + current = (low + high) // 2 + if val == arr[current]: + # your code here + pass + elif val < arr[current]: + # your code here + pass + else: # val > arr[current] + # your code here + pass + return -1 + + +# example 1 - sorted list +# the below demonstrates the binary search is faster than +# linear search on sorted lists +size = 100000 +lst_1 = [i for i in range(size)] + +tests = 3 +for i in range(tests): + print(f"sorted test #{i+1}:") + print("searching linearly") + target = random.randint(0, size) + linear_start = d.now() + linear_result = linear_search(lst_1, target) + linear_end = d.now() + print( + "finished searching linearly in " + + f"{(linear_end - linear_start).total_seconds()} seconds " + + f"and got the {'right' if linear_result == target else 'wrong'} result" + + f" ({linear_result})" + ) + + print("searching binarily") + binary_start = d.now() + binary_result = binary_search(lst_1, target) + binary_end = d.now() + print( + "finished searching binarily in " + + f"{(binary_end - binary_start).total_seconds()} seconds " + + f"and got the {'right' if binary_result == target else 'wrong'} result" + + f" ({binary_result})" + ) + print() + +# example 2 - unsorted list +# the below demonstrates that binary search doesn't work on unsorted +# lists, but linear search does +size = 100000 +lst_2 = [i for i in range(size)] +random.shuffle(lst_2) + +tests = 3 +for i in range(tests): + print(f"unsorted test #{i+1}:") + print("searching linearly") + idx = random.randint(0, size) + target = lst_2[idx] + linear_start = d.now() + linear_result = linear_search(lst_2, target) + linear_end = d.now() + print( + "finished searching linearly in " + + f"{(linear_end - linear_start).total_seconds()} seconds " + + f"and got the {'right' if linear_result == idx else 'wrong'} result" + + f" ({linear_result})" + ) + + print("searching binarily") + binary_start = d.now() + binary_result = binary_search(lst_2, target) + binary_end = d.now() + print( + "finished searching binarily in " + + f"{(binary_end - binary_start).total_seconds()} seconds " + + f"and got the {'right' if binary_result == idx else 'wrong'} result" + + f" ({binary_result})" + ) + print() diff --git a/dsa/chapter3/practice/selectionsort.py b/dsa/chapter3/practice/selectionsort.py new file mode 100644 index 00000000..a1ac9f51 --- /dev/null +++ b/dsa/chapter3/practice/selectionsort.py @@ -0,0 +1,31 @@ +def selectionsort(arr: list): + """ + Let's implement selection sort! There are 4 easy steps to follow in order + to implement it. + 1. Create a loop through iterate through the list. + 2. Create an inner loop that iterates from the outer index + 1 to the + end of the list. + 3. Compare the element at the outer index to the element at the inner + index. + 4. If the element at the outer index is larger than at the inner index, + swap the 2 elements. + """ + + # Step 1, create an outer loop that iterates through the whole list. Let's + # name the outer index "i" + + # Step 2, create an inner loop that iterates from i+1 to the end of the + # list, let's name inner index "j" + + # Step 3, check if the element at index i is larger than the element at + # index j + + # Step 4, swap the element at the outer index with the element at the + # inner index + pass + + +if __name__ == "__main__": + lst = [-3, 5, -10, 18, 74, 22, 1, -40] + selectionsort(lst) + print(lst) diff --git a/dsa/chapter3/solutions/a_star.py b/dsa/chapter3/solutions/a_star.py new file mode 100644 index 00000000..759f28a3 --- /dev/null +++ b/dsa/chapter3/solutions/a_star.py @@ -0,0 +1,195 @@ +""" +A Star Practice + +In this practice problem, you get to fill in some a_star code +as well as see the effects of using different heuristics on +a_star's execution time. + +Heuristics and helper functions are given. Your job is to fill +in sections of the A* algorithm where it says 'your code here' +""" + +from queue import PriorityQueue +import math +import random + + +class Point: + def __init__(self, x: int, y: int) -> None: + self.x: int = x + self.y: int = y + + def get_neighbors(self, start, end): + """ + This function returns a list of neighboring points + using the fact that neighboring points will be the + following (p = neighboring, c = current) + + ``` + p p p + p c p + p p p + ``` + """ + + def between(a, b, c): + return (b <= a and a <= c) or (b >= a and a >= c) + + return [ + Point(x + self.x, y + self.y) + for x in range(-1, 2) + for y in range(-1, 2) + if ( + between(x + self.x, start.x, end.x) + and between(y + self.y, start.y, end.y) + ) + ] + + def __eq__(self, __o: object) -> bool: + return self.x == __o.x and self.y == __o.y + + def __hash__(self) -> int: + return hash((self.x, self.y)) + + def __str__(self): + return f"({self.x}, {self.y})" + + def __repr__(self) -> str: + return str(self) + + +def adding_heuristic(cur: Point, end: Point): + """ + this heuristic returns a value + that looks like the following: + abs(x-x1) + abs(y-y1). + """ + return abs(cur.x - end.x) + abs(cur.y - end.y) + + +def triangle_heuristic(cur: Point, end: Point): + """ + this heuristic will return a value + based off the pythagorean theorem + that looks like the following + sqrt((x-x1)^2 + (y-y1)^2) + """ + return math.sqrt((cur.x - end.x) ** 2 + (cur.y - end.y) ** 2) + + +def bad_heuristic(cur: Point, end: Point): + """ + This heuristic will return a value + that is the opposite of the distance, + meaning that the closer cur is to end, + the worse (higher) score this will give it + """ + return -(abs(cur.x - end.x) + abs(cur.y - end.y)) + + +def random_heuristic(cur: Point, end: Point): + """ + Returns a totally random number. + """ + return random.randint(cur.x, end.x) + random.randint(cur.y, end.y) + + +def reconstruct_path(path: dict, start, end): + backwards_path = [] + curr = end # we know that we start at the end + while curr in path: + # add the current node to the backwards_path + backwards_path.append(curr) + + # since path is a dictionary of node : how to get there, + # we get the previous node in the path by doing path[curr] + curr = path[curr] + + # this will be the first item after we reverse the list + backwards_path.append(start) + + return reversed(backwards_path) + + +def a_star(start: Point, end: Point, heuristic): + min_x, max_x = min(start.x, end.x), max(start.x, end.x) + min_y, max_y = min(start.y, end.y), max(start.y, end.y) + + # initialize f scores (final scores) to infinity for every + # point between the (min_x, min_y) and (max_x, max_y) + f_scores = { + Point(x, y): float("inf") + for x in range(min_x, max_x + 1) + for y in range(min_y, max_y + 1) + } + + # initialize g scores (distance to get there) to infinity for every + # point between (min_x, min_y) and (max_x, max_y) + g_scores = { + Point(x, y): float("inf") + for x in range(min_x, max_x + 1) + for y in range(min_y, max_y + 1) + } + # it takes 0 steps to get to the start, so initialize that g score to 0 + g_scores[start] = 0 + + # this will be how many nodes we have added + # because priorityqueue sorts things, this is added as a backup measure + # when putting items into the queue to say that, if their f scores are + # the same, then just explore the one that we found first + count = 0 + unexplored = PriorityQueue() + unexplored.put((0, count, start)) + + # this is a dictionary that stores node: how to get there + # this means that path[(1, 1)] might equal (0, 0) + # we use this variable to help us reconstruct the path that + # a star found + path = {} + + # allows us to see how many executions it really took + num_executions = 0 + + while not unexplored.empty(): + current: Point = unexplored.get()[2] # just get the Point + + # it takes 1 more step to get to any neighbor, so their g_scores will be + # one more than the current g score + temp_g_score = g_scores[current] + 1 + for node in current.get_neighbors(start, end): + if node == end: + # the way to get to the end is from the current node + path[node] = current + print(f"finished after {num_executions} executions") + return reconstruct_path(path, start, end) + else: + # if either we haven't explored this node yet + # (meaning g_scores[node] = infinity) or this is a shorter path to + # get to this node, then + if temp_g_score < g_scores[node]: + # update our path that way now the shortest way to get to this node + # is through the current node + path[node] = current + + # update our f and g scores + g_scores[node] = temp_g_score + f_scores[node] = temp_g_score + heuristic(node, end) + + # add the node to unexplored if it wasn't already in unexplored + if not any(node == item[2] for item in unexplored.queue): + # update our count and add the unexplored node w/ its score + count += 1 + unexplored.put((f_scores[node], count, node)) + num_executions += 1 + print(f"no solution found after {num_executions} executions") + return None # no path found + + +# you can try changing the heuristic and seeing how that affects the path taken, +# as well as the number of executions it took +path = a_star(Point(0, 0), Point(15, 33), adding_heuristic) +path_len = 0 +for i in path: + print(i) + path_len += 1 +print(f"path length was {path_len}") diff --git a/dsa/chapter3/solutions/bfs_dfs.py b/dsa/chapter3/solutions/bfs_dfs.py new file mode 100644 index 00000000..acd6fe8a --- /dev/null +++ b/dsa/chapter3/solutions/bfs_dfs.py @@ -0,0 +1,87 @@ +class Node: + def __init__(self, value: int) -> None: + self.value: int = value + self.children = [] + + def __str__(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return str(self) + + +def BFS(start_node: Node): + """ + Implement a breadth-first search algorithm, + but print the nodes that you visit as you go. + + Remember, a breadth-first search algorithm works by + visiting all the children in a certain depth before + advancing to the next depth + + Note that your function will be slightly different from + the one in the example since the nodes in this file + have children and not neighbors + """ + + # initialize our lists + visited_nodes = [] + current_depth_nodes = [start_node] + next_depth_nodes = [] + + # iterate until there are no nodes at the current depth + while len(current_depth_nodes) != 0: + for node in current_depth_nodes: + if node not in visited_nodes: + print(node, end=" ") + + # add the node to visited + # and add its children to the list of nodes at the next depth + visited_nodes.append(node) + next_depth_nodes.extend(node.children) + + # "go to the next depth level" by setting + # current_depth_nodes = next_depth_nodes + current_depth_nodes = next_depth_nodes + next_depth_nodes = [] + + +def DFS(start_node: Node, visited: list = []): + if start_node not in visited: + print(start_node, end=" ") + + visited.append(start_node) + for node in start_node.children: + DFS(node, visited) + + +if __name__ == "__main__": + # make a graph that looks like the following + # / 5 + # 2 - 6 - 10 + # / + # 1 - 3 - 7 - 11 - 12 + # \ + # 4 - 8 + # \ 9 + start_node = Node(1) + for i in range(3): + start_node.children.append(Node(i + 2)) + start_node.children[0].children.append(Node(5)) + start_node.children[0].children.append(Node(6)) + start_node.children[0].children[1].children.append(Node(10)) + + start_node.children[1].children.append(Node(7)) + start_node.children[1].children[0].children.append(Node(11)) + start_node.children[1].children[0].children[0].children.append(Node(12)) + + start_node.children[2].children.append(Node(8)) + start_node.children[2].children.append(Node(9)) + + print("with BFS") + BFS(start_node) # 1 2 3 4 5 6 7 8 9 10 11 12 + print() + + print("with DFS") + DFS(start_node) # 1 2 5 6 10 3 7 11 12 4 8 9 + print() diff --git a/dsa/chapter3/solutions/mergesort.py b/dsa/chapter3/solutions/mergesort.py new file mode 100644 index 00000000..93deb682 --- /dev/null +++ b/dsa/chapter3/solutions/mergesort.py @@ -0,0 +1,74 @@ +def mergesort(lst: list) -> list: + """ + Let's implement mergesort, + First let's create a base case where if the list is 0 or 1 elements long, + return it + """ + if len(lst) <= 1: + return lst + + """ + Now that we have handled the base case, if the list is any longer, we can + go into typical mergesort logic, + + We need to split the list into 2 halves, so lets first find the middle + index value. Use // instead of / because we want an integer + """ + middle_idx = len(lst) // 2 + + """ + Now we can run mergesort on the first and second halves of lst. Create a + variable first_half which is the result of calling mergesort on the first + half of lst. Repeat for the second half of the list, creating the variable + second_half. Use list splicing for this. + """ + first_half = mergesort(lst[:middle_idx]) # sort the first half + second_half = mergesort(lst[middle_idx:]) # sort the second half + + """ + Now we need to merge the two sorted halves. In order to do this, we will + implement a mergelists helper function. Return the result of mergelists + with first_half and second_half as the two parameters. + """ + return mergelists(first_half, second_half) # merge the two sorted halves + + +def mergelists(lst1: list, lst2: list) -> list: + idx1 = 0 + idx2 = 0 + ret = [] + + """ + Let's create a while loop that runs for as long as idx1 or idx2 is less + than the len of lst1 or lst2. + """ + while idx1 < len(lst1) or idx2 < len(lst2): + # If both lists have items, we need to compare the first item of the + # lst1 and lst2, and append whichever item is smaller to ret. Then, we + # increment the idx1 or idx2 variable respectively + if idx1 < len(lst1) and idx2 < len(lst2): + if lst1[idx1] < lst2[idx2]: + ret.append(lst1[idx1]) # add the item from lst1 + idx1 += 1 # increment our idx in lst1 + else: # lst2[idx2] <= lst1[idx1] + ret.append(lst2[idx2]) # add the item from lst2 + idx2 += 1 # increment our idx in lst2 + + elif idx1 < len(lst1): + # if only lst1 has items left, append the remaining items to the + # end of ret, and set idx1 to len(lst1) + ret.extend(lst1[idx1:]) + idx1 = len(lst1) + elif idx2 < len(lst2): + # if only lst2 has items left, append the remaining items to the + # end of ret, and set idx2 to len(lst2) + ret.extend(lst2[idx2:]) + idx2 = len(lst2) + + return ret + + +if __name__ == "__main__": + lst = [-3, 5, -10, 18, 74, 22, 1, -40] + mergesort(lst) + print(lst) diff --git a/dsa/chapter3/solutions/quicksort.py b/dsa/chapter3/solutions/quicksort.py new file mode 100644 index 00000000..45d0bd90 --- /dev/null +++ b/dsa/chapter3/solutions/quicksort.py @@ -0,0 +1,89 @@ +def quicksort(arr: list): + """ + quicksort_recursive takes in the list you are sorting, the first index of + the sublist you want to sort, and the last index of the sublist you want + to sort, in that order + + For the first call to quicksort_recursive, the first index and last index + should be 0 and the index to the last item of the list respectively + + Make a call to quicksort_recursive with the appropriate arguments below + """ + quicksort_recursive(arr, 0, len(arr) - 1) + + +def quicksort_recursive(arr, low, high): + """ + Arguments: + arr: list, the entire list we are sorting, + low: int, the first index of the sublist we are sorting + high: int, the last index of the sublist we are sorting + """ + # base case + if low >= high: + return + + pivot_index = partition(arr, low, high) + + # recursive calls + """ + After the list has been partitioned around the pivot_index, we need to + call quicksort_recursive on the two sublists: the one to the left of the + pivot_index, and the one to the right + + We do this on the right side by setting the high index to one less than + pivot_index, and on the left side by setting the low index to one higher + than pivot_index + + Make calls to quicksort_recursive with the appropriate arguments below + """ + quicksort_recursive(arr, low, pivot_index - 1) # right side + quicksort_recursive(arr, pivot_index + 1, high) # left side + + +def partition(arr, low, high): + """ + Partition takes a pivot (in our case, arr[high]), and accomplishes the + following: + All of the elements between low and high that are SMALLER than the + pivot are placed to the LEFT of the pivot. + Conversely, all elements between low and high that are LARGER than + the pivot are placed to the RIGHT of the pivot + This has the side effect that the location of pivot after the partition has + taken place is the same as if the list was sorted. Of course, the areas to + the left and right of the pivot are not yet sorted. + """ + i = low # initialize i to the left side of what we are sorting + + """ + Create a for loop that creates an index j, and loops through indexes low + (inclusive) to high (exclusive) + """ + for j in range(low, high): # iterate through the list with arr[j] + """ + In our loop, we are trying to find items (arr[j]) that are less than + our pivot (arr[high]). If we find one, we want to swap our item + (arr[j]) with arr[i], an item thats to the left side of our + sublist. Then, we will increment i by one, so we don't continuously + swap with same arr[i] over and over again. + + Create an if statement to do this below + """ + if arr[j] < arr[high]: + # swap arr[j] with arr[i] so arr[j] is at the left side + arr[i], arr[j] = arr[j], arr[i] + i += 1 + """ + Our pivot (arr[high]) is still on the right side of our sublist. Let's swap + it with arr[i] so it moves to the right spot. + """ + arr[i], arr[high] = arr[high], arr[i] + + # return the pivot_index + return i + + +if __name__ == "__main__": + lst = [-3, 5, -10, 18, 74, 22, 1, -40] + quicksort(lst) + print(lst) diff --git a/dsa/chapter3/solutions/searching.py b/dsa/chapter3/solutions/searching.py new file mode 100644 index 00000000..8b6d332f --- /dev/null +++ b/dsa/chapter3/solutions/searching.py @@ -0,0 +1,121 @@ +""" +Let's see the difference between linear and binary searches! +Some of the algorithm is already done for you, but you +will have to fill in some areas. + +Then, run the code and you can see the results +""" + +import random +from datetime import datetime as d + + +def linear_search(arr, val) -> int: + """ + Linear Search - iterates through all the items in the array and checks + equality with the provided value. If the value matches, returns + the index of that value. Else, returns -1 + Arguments: + arr - the array to search + val - the value to search for + Returns: + int - index of the value on success, -1 on failure + """ + for i in range(len(arr)): + if arr[i] == val: + return i + return -1 + + +def binary_search(arr, val) -> int: + """ + Binary Search - checks the list for a value using a binary search. + Only works on sorted lists since it assumes that all the values + in indexes greater than i are greater and all the values in + indexes less than i are less. + Arguments: + arr - the array to search + val - the value to search for + Returns: + int - index of the value on success, -1 on failure + """ + low = 0 + high = len(arr) - 1 + while low <= high: + current = (low + high) // 2 + if val == arr[current]: + return current + elif val < arr[current]: + high = current - 1 + else: # val > arr[current] + low = current + 1 + return -1 + + +# example 1 - sorted list +# the below demonstrates the binary search is faster than +# linear search on sorted lists +size = 100000 +lst_1 = [i for i in range(size)] + +tests = 3 +for i in range(tests): + print(f"sorted test #{i+1}:") + print("searching linearly") + target = random.randint(0, size) + linear_start = d.now() + linear_result = linear_search(lst_1, target) + linear_end = d.now() + print( + "finished searching linearly in " + + f"{(linear_end - linear_start).total_seconds()} seconds " + + f"and got the {'right' if linear_result == target else 'wrong'} result" + + f" ({linear_result})" + ) + + print("searching binarily") + binary_start = d.now() + binary_result = binary_search(lst_1, target) + binary_end = d.now() + print( + "finished searching binarily in " + + f"{(binary_end - binary_start).total_seconds()} seconds " + + f"and got the {'right' if binary_result == target else 'wrong'} result" + + f" ({binary_result})" + ) + print() + +# example 2 - unsorted list +# the below demonstrates that binary search doesn't work on unsorted +# lists, but linear search does +size = 100000 +lst_2 = [i for i in range(size)] +random.shuffle(lst_2) + +tests = 3 +for i in range(tests): + print(f"unsorted test #{i+1}:") + print("searching linearly") + idx = random.randint(0, size) + target = lst_2[idx] + linear_start = d.now() + linear_result = linear_search(lst_2, target) + linear_end = d.now() + print( + "finished searching linearly in " + + f"{(linear_end - linear_start).total_seconds()} seconds " + + f"and got the {'right' if linear_result == idx else 'wrong'} result" + + f" ({linear_result})" + ) + + print("searching binarily") + binary_start = d.now() + binary_result = binary_search(lst_2, target) + binary_end = d.now() + print( + "finished searching binarily in " + + f"{(binary_end - binary_start).total_seconds()} seconds " + + f"and got the {'right' if binary_result == idx else 'wrong'} result" + + f" ({binary_result})" + ) + print() diff --git a/dsa/chapter3/solutions/selectionsort.py b/dsa/chapter3/solutions/selectionsort.py new file mode 100644 index 00000000..22cb686c --- /dev/null +++ b/dsa/chapter3/solutions/selectionsort.py @@ -0,0 +1,31 @@ +def selectionsort(arr: list): + """ + Let's implement selection sort! There are 4 easy steps to follow in order + to implement it. + 1. Create a loop through iterate through the list. + 2. Create an inner loop that iterates from the outer index + 1 to the + end of the list. + 3. Compare the element at the outer index to the element at the inner + index. + 4. If the element at the outer index is larger than at the inner index, + swap the 2 elements. + """ + + # Step 1, create an outer loop that iterates through the whole list. Let's + # name the outer index "i" + for i in range(len(arr)): + # Step 2, create an inner loop that iterates from i+1 to the end of the + # list, let's name inner index "j" + for j in range(i + 1, len(arr)): + # Step 3, check if the element at index i is larger than the + # element at index j + if arr[i] > arr[j]: + # Step 4, swap the element at the outer index with the element + # at the inner index + arr[i], arr[j] = arr[j], arr[i] + + +if __name__ == "__main__": + lst = [-3, 5, -10, 18, 74, 22, 1, -40] + selectionsort(lst) + print(lst) diff --git a/games/chapter1/examples/guessthepassword.py b/games/chapter1/examples/guessthepassword.py new file mode 100644 index 00000000..7961131b --- /dev/null +++ b/games/chapter1/examples/guessthepassword.py @@ -0,0 +1,20 @@ +# Directions: The goal of this exercise is to create +# a game where the user has to guess a certain password that +# you set and see how many tries it takes for that user to guess correctly + +# start with assigning the password to some variable +pas = "password" + +# set an input so it will appear in the console and ask the user +guess = input("Enter the password:") + +# set a counter to count the number of guesses +counter = 1 + +# set a while loop to check if the user guess correctly and count the number of guesses +while guess != pas: + guess = input("Incorrect Password. Try Again:") + counter += 1 + +# print the results +print(f"Nice Job. Unlocked. It took you {str(counter)} tries") diff --git a/games/chapter1/examples/hangman.py b/games/chapter1/examples/hangman.py new file mode 100644 index 00000000..380707ef --- /dev/null +++ b/games/chapter1/examples/hangman.py @@ -0,0 +1,60 @@ +# Directions: Lets Play Hangman. In the code, create a function that +# takes as a paramater the word that the user has to guess. +# The user should have 15 'lives'. +# Similar to the original game of hangman, if the user guesses an incorrect +# letter, then their lives goes down. If they guess a correct letter, they +# don't lose a life. + + +score = 0 + + +def hangman(endword: str): + global score + wordSet = set(endword) + print( + "Welcome to Hangman! You have 15 lives to " + + "figure out the correct word. Good Luck!" + ) + + lives = 15 + correctguesses = [] + + # mainloop + for i in range(15): + # take user input + guess = input(f"Guess a letter! You have {lives} lives left: ") + + # win condition + if guess == endword: + print(f"Nice, the word is '{endword}'") + score += 1 + break + + if guess in endword: + correctguesses.append(guess) + + # 'draw screen' phase + for i in range(len(endword)): + if endword[i] in correctguesses: + print(endword[i], end="") + else: + print("_ ", end="") + print() + + if guess not in wordSet: + lives -= 1 + + # update game state + # game over condition + if lives == 0: + print(f"You ran out of lives. The correct word is '{endword}'") + + # win condition + if set(correctguesses) == wordSet: + print(f"Nice, the word is '{endword}'") + score += 1 + break + + +hangman("hangman") diff --git a/games/chapter1/practice/blackjack.py b/games/chapter1/practice/blackjack.py new file mode 100644 index 00000000..af9644b1 --- /dev/null +++ b/games/chapter1/practice/blackjack.py @@ -0,0 +1,11 @@ +# Directions: The goal of blackjack is to be the first player +# to get to 21. Each player will draw randomly and the +# sum of the cards will add to 21. If the cards of a player go +# over 21, that person automatically loses. + +# Add your imports here + +print("Welcome to the game of BlackJack. ") +print("") + +# Add your code here diff --git a/games/chapter1/practice/poker.py b/games/chapter1/practice/poker.py new file mode 100644 index 00000000..931a9a9f --- /dev/null +++ b/games/chapter1/practice/poker.py @@ -0,0 +1,370 @@ +from random import choice, randint + +# how poker actually works: +# Every player is dealt two cards (face down) +# The number of cards in the middle (face up) is initially 3 and +# is increased one per round. Players decide if they want to bet on the round +# or fold before the next card is revealed. If a player bets, then all other +# players must 'call' (put in the same # of chips) +# once there are 5 cards in the middle, then the players see +# who can make the best match with their 2 cards and +# the 5 cards in the middle the player that makes the best match wins + +# check the code in the area that says "--CODE AREA--" +# THERE ARE 4 INSTRUCTIONS; if you fill them out, then the program should work. +# Note that, in our version of poker, the game ends once any player has less +# than 7 chips. + +suites = ["Clubs", "Diamonds", "Hearts", "Spades"] +face_cards = {11: "Jack", 12: "Queen", 13: "King", 14: "Ace"} +rankings = { + 0: "Royal Flush", + 1: "Straight Flush", + 2: "Four of a kind", + 3: "Full House", + 4: "Flush", + 5: "Straight", + 6: "Three of a Kind", + 7: "Two pairs", + 8: "Pair", + 9: "High Card", +} +deck = [] + + +# --- SUPPORTING CODE --- +class card: + def __init__(self, value: int, suite: str, name: str = None): + self.name = name if name else str(value) + self.value = value + self.suite = suite + + def __str__(self) -> str: + return f"A(n) {self.name} of {self.suite}" + + def __eq__(self, o) -> bool: + return str(self) == str(o) + + def __sub__(self, o) -> bool: + return self.value - o.value + + +class hand_results: + """ + A class for easy comparing of results of a hand + Note that a hand_result is considered "less than" another + hand_result if the hand_result's priority has a lower value + than the other hand_result's priority (meaning that + the first hand_result has a higher priority). Vice versa for + gt + """ + + def __init__(self, results: list): + self.results = results + self.priority = ( + results.index(True) if True in results else len(results) + ) + + def __lt__(self, o) -> bool: + return self.priority > o.priority + + def __le__(self, o) -> bool: + return self.priority >= o.priority + + def __gt__(self, o) -> bool: + return self.priority < o.priority + + def __ge__(self, o) -> bool: + return self.priority <= o.priority + + def __eq__(self, o) -> bool: + return self.priority == o.priority + + +class hand: + """ + This class represents one person's hand (or the river) + """ + + def __init__(self): + self.cards = [] + + def add_card(self, card: card) -> None: + self.cards.append(card) + + def __str__(self) -> str: + msg = "" + for card in self.cards: + msg += str(card) + ", " + return msg + + def __len__(self) -> int: + return len(self.cards) + + def union(self, o) -> None: + for card in o.cards: + self.cards.append(card) + + def does_val_card_exist( + self, val: int, cards_not_equal_to: list = [] + ) -> tuple: + for card in self.cards: + if card.value == val and card not in cards_not_equal_to: + return (True, card) + return (False, None) + + def find_matches( + self, num_matches: int, cards_to_exclude: list = [] + ) -> tuple: + for card in self.cards: + temp = [] + for oth in cards_to_exclude: + temp.append(oth) + if card not in temp: + temp.append(card) + + for i in range(num_matches - 1): + bool_val, potential_card = self.does_val_card_exist( + card.value, temp + ) + if not bool_val: + break + temp.append(potential_card) + else: + card_matches = temp + for oth_card in cards_to_exclude: + card_matches.remove(oth_card) + return (True, card_matches) + return (False, []) + + def check_straight_flush(self, card_start: card): + potential_card = card_start + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val or potential_card.suite != card_start.suite: + break + else: # for loop finished fine + return True + return False + + def check_straight(self, card_start: card) -> bool: + potential_card = card_start + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val: + break + else: # for loop finished fine + return True + return False + + def get_best_hand(self) -> hand_results: + # try to get a 5 card flush: + flush_possible = False + for card in self.cards: + same_suite = 0 + for other_card in self.cards: + if not card == other_card and card.suite == other_card.suite: + same_suite += 1 + if same_suite >= 5: + flush_possible = True + + # try to get a 5 card straight + straight_possible = False + for card in self.cards: + potential_card = card + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val: + break + else: # for loop finished fine + straight_possible = True + + # try to get a straight flush + straight_flush_possible = False + if straight_possible and flush_possible: + for card in self.cards: + if not straight_flush_possible: + straight_flush_possible = self.check_straight_flush(card) + + # royal flush possible + royal_flush_possible = False + if self.does_val_card_exist(10)[0]: + royal_flush_possible = self.check_straight( + self.does_val_card_exist(10)[1] + ) + + # try to get a pair (2 cards of same val) + pair_possible = self.find_matches(2)[0] + + # try to get a 3 of a kind + three_possible = self.find_matches(3)[0] + + four_possible = self.find_matches(4)[0] + + # try to get a full house + full_house_possible = False + for card in self.cards: + bool_val, cards = self.find_matches(3) + if ( + bool_val and not full_house_possible + ): # was able to find 3 of a kind (2 other cards of same value) + # use exclude and try to find a pair + full_house_possible = self.find_matches(2, cards)[0] + + two_pair_possible = False + for card in self.cards: + bool_val, cards = self.find_matches(2) # find a pair + if bool_val and not two_pair_possible: + two_pair_possible = self.find_matches(2, cards)[0] + + return hand_results( + [ + royal_flush_possible, + straight_flush_possible, + four_possible, + full_house_possible, + flush_possible, + straight_possible, + three_possible, + two_pair_possible, + pair_possible, + ] + ) + + +def initialize_deck(): + global deck + + deck = [ + card(value, suite, face_cards[value]) + if value >= 11 + else card(value, suite) + for value in range(2, 15) + for suite in suites + ] + + +def take_card() -> card: + global deck + c = choice(deck) + deck.remove(c) + return c + + +# -- CODE AREA -- +# -- Your code will go here -- + +dealer_chips = 20 +player_chips = 20 + + +def play_poker(): + global dealer_chips, player_chips, deck + + round_num = 0 + player_inp = "" + + # INSTRUCTION + # while both players have more than 7 chips + while """YOUR CONDITION HERE""": + initialize_deck() + + # inicialize hands to randomized ones each round + player = hand() + dealer = hand() + river = hand() + for i in range(2): # two cards initially + dealer.add_card(take_card()) + player.add_card(take_card()) + # initialize the pool in the middle + for i in range(3): + river.add_card(take_card()) + + chips_at_stake = 0 + winner = "" + + round_num += 1 + print(f"round number {round_num}") + # do one individual round + while len(river) < 5: + print(f"your hand right now is {player}") + print(f"the river is currently {river}") + # dealer bet + dealerbet = min( + randint(1, 5), dealer_chips + ) # that way the dealer doesn't go into negative chips + dealer_chips -= dealerbet + chips_at_stake += dealerbet + + # player either calls or folds + print(f"dealer bet {dealerbet}") + player_inp = input( + "call (bet that much) or fold (abandon this round) or STOP? " + ) + + # INSTRUCTION + # handle input + # if the input is 'STOP', then quit the program + # if the input is 'call', then the player bets the same number + # of chips that the dealer bet (player chips will decrease + # and chips_at_stake will increase) + # lastly, if the input is 'fold', then set winner to True + # and break out of the round (use the break keyword) + if player_inp == "STOP": + pass + if player_inp == "call": + pass + if player_inp == "fold": + pass + + # update the river + river.add_card(take_card()) + + print(f"currently, dealer has {dealer_chips} chips") + print(f"currently, you have {player_chips} chips") + + print() + + print(f"The river ended up as {river}") + print() + # no winner yet (meaning the round ended normally) + if winner == "": + # compare hands + dealer.union(river) + player.union(river) + + dealer_result = dealer.get_best_hand() + player_result = player.get_best_hand() + print( + "It was your", + rankings[player_result.priority], + "vs the dealer's", + rankings[dealer_result.priority], + ) + + # INSTRUCTION + # if player_result is greater than or equal to + # dealer_result, then the player won that round + # if not, then the dealer won that round. + # make sure to update the variable winner + + # INSTRUCTION + # if the dealer won, then + # print "The dealer won that round" + # The dealer then gets the chips that were in chips_at_stake + # if you won, then + # print "You won that round" + # the player gets the chips that were in chips_at_stake + # chips_at_stake will be 0 again no matter what + # Also, make sure to + # print how many chips each player has + print() # used to make it look prettier since adds extra line + + +play_poker() diff --git a/games/chapter1/solution/blackjack.py b/games/chapter1/solution/blackjack.py new file mode 100644 index 00000000..8cf43ec2 --- /dev/null +++ b/games/chapter1/solution/blackjack.py @@ -0,0 +1,86 @@ +# Directions: The goal of blackjack is to be the first player +# to get to 21. Each player will draw randomly and the +# sum of the cards will add to 21. If the cards of a player go +# over 21, that person automatically loses. + +import random + + +print("Welcome to the game of BlackJack. ") +print("") + +# Create the lists of the two players +# the dealer is the console and the player is the user +dealerList = [] +userList = [] + +# append two random cards to start the game +for i in range(2): + dealerList.append(random.randint(2, 11)) + userList.append(random.randint(2, 11)) + +# print the first two cards +print("Here is the dealer's cards:" + str(dealerList)) +print("Here is the user's cards:" + str(userList)) +if sum(userList) == 21: + print("User Won") + exit() +if sum(dealerList) == 21: + print("Dealer won") + exit() + +# ask hit or stay... write a functionn for hit and stay... +# conditional for the typed in key hit means to take another +# card and stay means to play with already drawn cards + +# print the instructions +ask = input("Type in H to hit and S to stay:") + + +# write a function for hit to use in multiple scenarios +def hit(cards): + cards.append(random.randint(2, 11)) + + +# while loop will keep checking the two players' cards to see if they reached 21 or not +while ask == "H" or ask == "h": + hit(userList) + print("Here is your hand:" + str(userList)) + + if sum(userList) > 21: + print("User is Busted") + exit() + if sum(userList) == 21: + print("User Won") + exit() + ask = input("Type in H to hit and S to stay:") + +# if statement for "stay" +if ask == "S" or ask == "s": + print("Here is your hand:" + str(userList)) + + +# when hit- append a anotehr random number into the list of the dealer/userList +# when stay- just go to next play and print out the list +# while dealer less than 17 append new cards to the list + +while sum(dealerList) < 17: + hit(dealerList) + print("Here is dealer's hand:" + str(dealerList)) + +# compare cards for win +if sum(dealerList) > 21: + print("Dealer is Busted") + + exit() +if sum(dealerList) == 21 and sum(userList) == 21: + print("It is a tie") + exit() +if sum(dealerList) == 21: + print("Dealer Won") + exit() + +if 21 - sum(dealerList) > 21 - sum(userList): + print("User is closer") +if 21 - sum(dealerList) < 21 - sum(userList): + print("Dealer is closer") diff --git a/games/chapter1/solution/poker.py b/games/chapter1/solution/poker.py new file mode 100644 index 00000000..21ea1f02 --- /dev/null +++ b/games/chapter1/solution/poker.py @@ -0,0 +1,362 @@ +from random import choice, randint + +# how poker actually works: +# Every player is dealt two cards (face down) +# The number of cards in the middle (face up) is initially 3 and +# is increased one per round. Players decide if they want to bet on the round +# or fold before the next card is revealed. If a player bets, then all other +# players must 'call' (put in the same # of chips) +# once there are 5 cards in the middle, then the players see +# who can make the best match with their 2 cards and +# the 5 cards in the middle the player that makes the best match wins + +# check the code in the area that says "--CODE AREA--" +suites = ["Clubs", "Diamonds", "Hearts", "Spades"] +face_cards = {11: "Jack", 12: "Queen", 13: "King", 14: "Ace"} +rankings = { + 0: "Royal Flush", + 1: "Straight Flush", + 2: "Four of a kind", + 3: "Full House", + 4: "Flush", + 5: "Straight", + 6: "Three of a Kind", + 7: "Two pairs", + 8: "Pair", + 9: "High Card", +} +deck = [] + + +# --- SUPPORTING CODE --- +class card: + def __init__(self, value: int, suite: str, name: str = None): + self.name = name if name else str(value) + self.value = value + self.suite = suite + + def __str__(self) -> str: + return f"A(n) {self.name} of {self.suite}" + + def __eq__(self, o) -> bool: + return str(self) == str(o) + + def __sub__(self, o) -> bool: + return self.value - o.value + + +class hand_results: + """ + A class for easy comparing of results of a hand + Note that a hand_result is considered "less than" another + hand_result if the hand_result's priority has a lower value + than the other hand_result's priority (meaning that + the first hand_result has a higher priority). Vice versa for + gt + """ + + def __init__(self, results: list): + self.results = results + self.priority = ( + results.index(True) if True in results else len(results) + ) + + def __lt__(self, o) -> bool: + return self.priority > o.priority + + def __le__(self, o) -> bool: + return self.priority >= o.priority + + def __gt__(self, o) -> bool: + return self.priority < o.priority + + def __ge__(self, o) -> bool: + return self.priority <= o.priority + + def __eq__(self, o) -> bool: + return self.priority == o.priority + + +class hand: + """ + This class represents one person's hand (or the river) + """ + + def __init__(self): + self.cards = [] + + def add_card(self, card: card) -> None: + self.cards.append(card) + + def __str__(self) -> str: + msg = "" + for card in self.cards: + msg += str(card) + ", " + return msg + + def __len__(self) -> int: + return len(self.cards) + + def union(self, o) -> None: + for card in o.cards: + self.cards.append(card) + + def does_val_card_exist( + self, val: int, cards_not_equal_to: list = [] + ) -> tuple: + for card in self.cards: + if card.value == val and card not in cards_not_equal_to: + return (True, card) + return (False, None) + + def find_matches( + self, num_matches: int, cards_to_exclude: list = [] + ) -> tuple: + for card in self.cards: + temp = [] + for oth in cards_to_exclude: + temp.append(oth) + if card not in temp: + temp.append(card) + + for i in range(num_matches - 1): + bool_val, potential_card = self.does_val_card_exist( + card.value, temp + ) + if not bool_val: + break + temp.append(potential_card) + else: + card_matches = temp + for oth_card in cards_to_exclude: + card_matches.remove(oth_card) + return (True, card_matches) + return (False, []) + + def check_straight_flush(self, card_start: card): + potential_card = card_start + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val or potential_card.suite != card_start.suite: + break + else: # for loop finished fine + return True + return False + + def check_straight(self, card_start: card) -> bool: + potential_card = card_start + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val: + break + else: # for loop finished fine + return True + return False + + def get_best_hand(self) -> hand_results: + # try to get a 5 card flush: + flush_possible = False + for card in self.cards: + same_suite = 0 + for other_card in self.cards: + if not card == other_card and card.suite == other_card.suite: + same_suite += 1 + if same_suite >= 5: + flush_possible = True + + # try to get a 5 card straight + straight_possible = False + for card in self.cards: + potential_card = card + for i in range(4): # there need to be 4 cards higher than it + bool_val, potential_card = self.does_val_card_exist( + potential_card.value + 1 + ) + if not bool_val: + break + else: # for loop finished fine + straight_possible = True + + # try to get a straight flush + straight_flush_possible = False + if straight_possible and flush_possible: + for card in self.cards: + if not straight_flush_possible: + straight_flush_possible = self.check_straight_flush(card) + + # royal flush possible + royal_flush_possible = False + if self.does_val_card_exist(10)[0]: + royal_flush_possible = self.check_straight( + self.does_val_card_exist(10)[1] + ) + + # try to get a pair (2 cards of same val) + pair_possible = self.find_matches(2)[0] + + # try to get a 3 of a kind + three_possible = self.find_matches(3)[0] + + four_possible = self.find_matches(4)[0] + + # try to get a full house + full_house_possible = False + for card in self.cards: + bool_val, cards = self.find_matches(3) + if ( + bool_val and not full_house_possible + ): # was able to find 3 of a kind (2 other cards of same value) + # use exclude and try to find a pair + full_house_possible = self.find_matches(2, cards)[0] + + two_pair_possible = False + for card in self.cards: + bool_val, cards = self.find_matches(2) # find a pair + if bool_val and not two_pair_possible: + two_pair_possible = self.find_matches(2, cards)[0] + + return hand_results( + [ + royal_flush_possible, + straight_flush_possible, + four_possible, + full_house_possible, + flush_possible, + straight_possible, + three_possible, + two_pair_possible, + pair_possible, + ] + ) + + +def initialize_deck(): + global deck + + deck = [ + card(value, suite, face_cards[value]) + if value >= 11 + else card(value, suite) + for value in range(2, 15) + for suite in suites + ] + + +def take_card() -> card: + global deck + c = choice(deck) + deck.remove(c) + return c + + +# -- CODE AREA -- +# -- Your code will go here -- + +# initialize two variables +# one will be the dealer's chips, the other will be the player's chips +dealer_chips = 20 +player_chips = 20 + + +def play_poker(): + global dealer_chips, player_chips, deck + + round_num = 0 + player_inp = "" + while player_inp != "STOP" and (dealer_chips > 7 and player_chips > 7): + initialize_deck() + + # inicialize hands to randomized ones each round + player = hand() + dealer = hand() + river = hand() + for i in range(2): # two cards initially + dealer.add_card(take_card()) + player.add_card(take_card()) + # initialize the pool in the middle + for i in range(3): + river.add_card(take_card()) + + chips_at_stake = 0 + winner = "" + + round_num += 1 + print(f"round number {round_num}") + # do one individual round + while len(river) < 5: + print(f"your hand right now is {player}") + print(f"the river is currently {river}") + # dealer bet + dealerbet = min( + randint(1, 5), dealer_chips + ) # that way the dealer doesn't go into negative chips + dealer_chips -= dealerbet + chips_at_stake += dealerbet + + # player either calls or folds + print(f"dealer bet {dealerbet}") + player_inp = input( + "call (bet that much) or fold (abandon this round) or STOP? " + ) + + # handle input + if player_inp == "STOP": + return # just get out of the function + if player_inp == "call": + chips_at_stake += dealerbet + player_chips -= dealerbet + # if betting dealerbet chips would put them in debt + if player_chips < 0: + print("Sorry, you lose") + return + if player_inp == "fold": + winner = "dealer" + break + + # update the river + river.add_card(take_card()) + + print(f"currently, dealer has {dealer_chips} chips") + print(f"currently, you have {player_chips} chips") + + print() + + print(f"The river ended up as {river}") + print() + # no winner yet + if winner == "": + # compare hands + dealer.union(river) + player.union(river) + + dealer_result = dealer.get_best_hand() + player_result = player.get_best_hand() + print( + "It was your", + rankings[player_result.priority], + "vs the dealer's", + rankings[dealer_result.priority], + ) + if player_result >= dealer_result: + winner = "player" + else: + winner = "dealer" + if winner == "dealer": + print("dealer won that round") + dealer_chips += chips_at_stake + chips_at_stake = 0 + else: # winner == "player" + print("you won that round") + player_chips += chips_at_stake + chips_at_stake = 0 + + print(f"currently, dealer has {dealer_chips} chips") + print(f"currently, you have {player_chips} chips") + print() + + +play_poker() diff --git a/games/chapter2/examples/basic_window.py b/games/chapter2/examples/basic_window.py new file mode 100644 index 00000000..f2cb1116 --- /dev/null +++ b/games/chapter2/examples/basic_window.py @@ -0,0 +1,25 @@ +import pygame # imports the module + +# RESIZABLE is only needed if you want a resizable window +from pygame.locals import RESIZABLE + +# initializes imported pygame modules +pygame.init() + +# creates resizable pygame window that is 500 pixels wide and 400 high +# sets the caption of the window to "My first pygame app!" +flag = RESIZABLE +window = pygame.display.set_mode((500, 400), flag) +pygame.display.set_caption("My first pygame app!") + +# this is where the game loop begins +run = True +while run: + for event in pygame.event.get(): + # checks if the close button is pressed + # if so, exit the game loop + if event.type == pygame.QUIT: + run = False + +# deactivates pygame modules, opposite of pygame.init() +pygame.quit() diff --git a/games/chapter2/examples/comprehensive_example.py b/games/chapter2/examples/comprehensive_example.py new file mode 100644 index 00000000..ed1942df --- /dev/null +++ b/games/chapter2/examples/comprehensive_example.py @@ -0,0 +1,37 @@ +import pygame + +RED = (255, 0, 0) +BLACK = (0, 0, 0) +x = y = 0 +width = 100 +height = 50 + +# initializes imported pygame modules +pygame.init() + +# creates pygame window that is 500 pixels wide and 400 high +# sets the caption of the window to "My first pygame app!" +window = pygame.display.set_mode((500, 400)) +pygame.display.set_caption("My first pygame app!") + +# this is where the game loop begins +run = True +while run: + # change the coordinates + x, y = x + 1, y + 1 + + # draw a black screen over the previous frame + window.fill(BLACK) + + # draw a new rectangle and update the screen + pygame.draw.rect(window, RED, (x, y, width, height)) + pygame.display.update() + + for event in pygame.event.get(): + # checks if the close button is pressed + # if so, exit the game loop + if event.type == pygame.QUIT: + run = False + +# deactivate pygame modules, opposite of pygame.init() +pygame.quit() diff --git a/games/chapter2/examples/draw_objects.py b/games/chapter2/examples/draw_objects.py new file mode 100644 index 00000000..fdc45209 --- /dev/null +++ b/games/chapter2/examples/draw_objects.py @@ -0,0 +1,54 @@ +import pygame + +pygame.init() + +window = pygame.display.set_mode((800, 800)) +pygame.display.set_caption("Drawing and Moving Objects") + +BLACK = (0, 0, 0) # background color +RED = (255, 0, 0) +GREEN = (0, 255, 0) + +# make a rectangle without the pygame.Rect class +x = 100 # top-left x value +y = 400 # top-left y value +width = 100 # width of the rectangle +height = 50 # height of the rectangle + +# make a pygame.Rect rectangle +# the syntax is `myvar = pygame.Rect(top-left x, top-left y, width, height)` +# with 0 as top-left x value, 100 as top-left y value, +# width = 50, height = 100 +green_rectangle = pygame.Rect(0, 100, 50, 100) + +run = True +while run: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + # move a rectangle that isn't a pygame.Rect object + x += 1 # move to the right 1 px + y += 1 # move down 1 px + + # move a rectangle that is a pygame.Rect object + green_rectangle.move_ip(1, 2) # moves 1 to the right, 2 down + # this is equivalent to green_rectangle = green_rectangle.move(1, 2) + + # erase the previous frame + window.fill(BLACK) + + # draw a rectangle that isn't a pygame.Rect object + pygame.draw.rect(window, RED, (x, y, width, height)) + + # draw a rectangle that is a pygame.Rect object + pygame.draw.rect(window, GREEN, green_rectangle) + + # update the screen + pygame.display.update() + + # sometimes you need to limit frame rate or your objects + # will seem to move too fast + pygame.time.wait(30) # wait 30 milliseconds between frame + +pygame.quit() # close pygame after finishing diff --git a/games/chapter2/examples/text.py b/games/chapter2/examples/text.py new file mode 100644 index 00000000..9e19a3c7 --- /dev/null +++ b/games/chapter2/examples/text.py @@ -0,0 +1,40 @@ +import pygame +from pygame.locals import RESIZABLE + +pygame.init() +flag = RESIZABLE +window = pygame.display.set_mode((500, 400), flag) +pygame.display.set_caption("Text!") +WHITE = (255, 255, 255) + +run = True +while run: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + # step 1 to writing: load the font with pygame.font.SysFont(font, size) + # For font - You can either use the default font + # (pygame.font.get_default_font()) or use a font name + # (like Comic Sans or Arial). + # For size - a positive integer representing the font size. + font = pygame.font.SysFont("Arial", 32) + + # step 2 - render the font with + # (font variable name).render( + # text: string, antialias: bool, color: tuple, background=None + # ) + # In this case, we render the text "Hello World!", pass True as antiaalias + # and have the color of the text be WHITE + text = font.render("Hello World!", True, WHITE) + + # step 3 - blit to the screen + # You can either blit the text to a rectangle on the screen or a specified + # coordinate + # In this case, we blit (draw) the text with a top-left value of (0, 0) + window.blit(text, (100, 100)) + + # update the screen; just like with moving/displaying rectangles + pygame.display.update() + +pygame.quit() diff --git a/games/chapter2/practice/add_text.py b/games/chapter2/practice/add_text.py new file mode 100644 index 00000000..d6b1f142 --- /dev/null +++ b/games/chapter2/practice/add_text.py @@ -0,0 +1,54 @@ +# Add some text to your game + +# This problem builds off of bouncingrect.py + +# Add some text to the screen. You can either: +# - draw the text to a specified coordinate OR +# - blit the text onto the bouncing rectangle. + +import pygame +import time # not necessary, but used for frame cap + +pygame.init() # initialize pygame module + +SCREEN_SIZE = (600, 400) +RECT_SIZE = (100, 100) +RED = (255, 0, 0) +BLACK = (0, 0, 0) +momentum = [1, 1] # (down and right) + +window = pygame.display.set_mode(SCREEN_SIZE) +running = True + +# start the rectangle in the middle of the screen +x = SCREEN_SIZE[0] // 2 +y = SCREEN_SIZE[1] // 2 + +while running: + # if the rectangle collided with the left or right side + # of the screen + if x + RECT_SIZE[0] >= SCREEN_SIZE[0] or x <= 0: + momentum[0] = -momentum[0] + # if the rectangle collided with the top or bottom + # of the screen + if y + RECT_SIZE[1] >= SCREEN_SIZE[1] or y <= 0: + momentum[1] = -momentum[1] + + # add the speed to the current x and y to get the + # new x and y + x += momentum[0] + y += momentum[1] + + window.fill(BLACK) # 'erase' the previous frame + pygame.draw.rect(window, RED, (x, y, RECT_SIZE[0], RECT_SIZE[1])) + + # Your code here. + + pygame.display.update() # update the display + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + time.sleep(0.01) # frame cap to make the rectangle more visible + +pygame.quit() # deactivate the pygame module diff --git a/games/chapter2/practice/bouncing_rect.py b/games/chapter2/practice/bouncing_rect.py new file mode 100644 index 00000000..e14e4f4d --- /dev/null +++ b/games/chapter2/practice/bouncing_rect.py @@ -0,0 +1,25 @@ +# Make a “bouncing rectangle!” + +# For this, please use the given screen and rectangle +# width and height. + +# The rectangle should start in (or close to) the middle of the +# screen. It should be moving down and right. If it collides +# with the screen’s lower or upper boundary, it should reverse +# its vertical direction. If it collides with the screen’s left +# or right boundary, it should reverse its horizontal direction. + +# Note: you can import the time module as well and use +# time.sleep(0.01) +# in your mainloop to act as a frame cap to make your rectangle +# more visible + +# put imports here + +SCREEN_SIZE = (600, 400) +RECT_SIZE = (100, 100) +RED = (255, 0, 0) +BLACK = (0, 0, 0) +momentum = [1, 1] # (down and right) + +# add code here diff --git a/games/chapter2/practice/moving_text.py b/games/chapter2/practice/moving_text.py new file mode 100644 index 00000000..27a67359 --- /dev/null +++ b/games/chapter2/practice/moving_text.py @@ -0,0 +1,23 @@ +# move some text! + +# The text should start in the upper corner and be moving +# down and to the right. You can move the text using coordinates +# or blitz the text on to a moving rectangle. Feel free to be +# creative with colors, fonts, and font sizes. However, if +# applicable, make the rectangle proportional to the text, and +# everything smaller than the screen + + +# Note: you can import a time module in your loop +# to make it more clear + + +# imports! + +SCREEN_SIZE = (800, 800) +BLACK = (0, 0, 0) +BLUE = (0, 0, 255) +WHITE = (255, 255, 255) +momentum = (2, 2) # down and right + +# add code here diff --git a/games/chapter2/practice/reset_position.py b/games/chapter2/practice/reset_position.py new file mode 100644 index 00000000..8599a1af --- /dev/null +++ b/games/chapter2/practice/reset_position.py @@ -0,0 +1,27 @@ +# Reset the moving rectangle's position if it leaves the screen! +# The rectangle that will be moving is already provided +# it is `red_rectangle`. Your job is to move it across the screen +# at a speed of 5px down and 5px right per frame. Then, if the +# bottom of the rectangle is greater than the screen height or the +# right of the rectangle is greater than the screen width, reset +# the rectangle's x and y to 0 and 0. + +import pygame + +pygame.init() + +SCREEN_HEIGHT = 600 +SCREEN_WIDTH = 600 + +window = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) +pygame.display.set_caption("Reset Position") + +# color constants +RED = (255, 0, 0) +BLACK = (0, 0, 0) + +# makes a pygame.Rect rectangle +# the syntax is `myvar = pygame.Rect(top-left x, top-left y, width, height)` +red_rectangle = pygame.Rect(0, 0, 100, 100) + +# add code here diff --git a/games/chapter2/solutions/add_text.py b/games/chapter2/solutions/add_text.py new file mode 100644 index 00000000..38838645 --- /dev/null +++ b/games/chapter2/solutions/add_text.py @@ -0,0 +1,61 @@ +# Add some text to your game + +# This problem builds off of bouncingrect.py + +# Add some text to the screen. You can either: +# - draw the text to a specified coordinate OR +# - blit the text onto the bouncing rectangle. + +import pygame +import time # not necessary, but used for frame cap + +pygame.init() # initialize pygame module + +SCREEN_SIZE = (600, 400) +RECT_SIZE = (100, 100) +RED = (255, 0, 0) +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +momentum = [1, 1] # (down and right) + +window = pygame.display.set_mode(SCREEN_SIZE) +running = True + +# start the rectangle in the middle of the screen +x = SCREEN_SIZE[0] // 2 +y = SCREEN_SIZE[1] // 2 + +while running: + # if the rectangle collided with the left or right side + # of the screen + if x + RECT_SIZE[0] >= SCREEN_SIZE[0] or x <= 0: + momentum[0] = -momentum[0] + # if the rectangle collided with the top or bottom + # of the screen + if y + RECT_SIZE[1] >= SCREEN_SIZE[1] or y <= 0: + momentum[1] = -momentum[1] + + # add the speed to the current x and y to get the + # new x and y + x += momentum[0] + y += momentum[1] + + window.fill(BLACK) # 'erase' the previous frame + pygame.draw.rect(window, RED, (x, y, RECT_SIZE[0], RECT_SIZE[1])) + + font = pygame.font.SysFont("Calibri", 16) + + bouncetext = font.render("This Bounces!", True, WHITE) + stationarytext = font.render("This doesn't bounce", True, WHITE) + + window.blit(bouncetext, (x, y, RECT_SIZE[0], RECT_SIZE[1])) + window.blit(stationarytext, (100, 100)) + + pygame.display.update() # update the display + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + time.sleep(0.01) # frame cap to make the rectangle more visible + +pygame.quit() # deactivate the pygame module diff --git a/games/chapter2/solutions/bouncing_rect.py b/games/chapter2/solutions/bouncing_rect.py new file mode 100644 index 00000000..36a9d81b --- /dev/null +++ b/games/chapter2/solutions/bouncing_rect.py @@ -0,0 +1,59 @@ +# Make a “bouncing rectangle!” + +# For this, please use the given screen and rectangle +# width and height. + +# The rectangle should start in (or close to) the middle of the +# screen. It should be moving down and right. If it collides +# with the screen’s lower or upper boundary, it should reverse +# its vertical direction. If it collides with the screen’s left +# or right boundary, it should reverse its horizontal direction. + +# Note: you can import the time module as well and use +# time.sleep(0.01) +# in your mainloop to act as a frame cap to make your rectangle +# more visible + +import pygame +import time # not necessary, but used for frame cap + +pygame.init() # initialize pygame module + +SCREEN_SIZE = (600, 400) +RECT_SIZE = (100, 100) +RED = (255, 0, 0) +BLACK = (0, 0, 0) +momentum = [1, 1] # (down and right) + +window = pygame.display.set_mode(SCREEN_SIZE) +running = True + +# start the rectangle in the middle of the screen +x = SCREEN_SIZE[0] // 2 +y = SCREEN_SIZE[1] // 2 + +while running: + # if the rectangle collided with the left or right side + # of the screen + if x + RECT_SIZE[0] >= SCREEN_SIZE[0] or x <= 0: + momentum[0] = -momentum[0] + # if the rectangle collided with the top or bottom + # of the screen + if y + RECT_SIZE[1] >= SCREEN_SIZE[1] or y <= 0: + momentum[1] = -momentum[1] + + # add the speed to the current x and y to get the + # new x and y + x += momentum[0] + y += momentum[1] + + window.fill(BLACK) # 'erase' the previous frame + pygame.draw.rect(window, RED, (x, y, RECT_SIZE[0], RECT_SIZE[1])) + pygame.display.update() # update the display + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + time.sleep(0.01) # frame cap to make the rectangle more visible + +pygame.quit() # deactivate the pygame module diff --git a/games/chapter2/solutions/moving_text.py b/games/chapter2/solutions/moving_text.py new file mode 100644 index 00000000..4c92e455 --- /dev/null +++ b/games/chapter2/solutions/moving_text.py @@ -0,0 +1,58 @@ +import pygame + +pygame.init() # initializes pygame module + +SCREEN_SIZE = (800, 800) +BLACK = (0, 0, 0) # background color +BLUE = (0, 0, 255) # color of font +WHITE = (255, 255, 255) # color of rectangle +momentum = (2, 2) + +window = pygame.display.set_mode(SCREEN_SIZE) +pygame.display.set_caption("Moving-Text") + + +# make a pygame.Rect rectangle +white_rectangle = pygame.Rect(0, 100, 130, 40) + +# move the text with coordinates instead of the rectangle +x = 0 # x-coordinate of the top-left pixel of text +y = 400 # y-coordinate of the top-left pixel of text + +# sets a font and font size +font = pygame.font.SysFont("Times New Roman", 40) + +# create a piece of text +text = font.render("HELLO", False, BLUE) + +run = True +while run: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + # moves a pygame.Rect rectangle relative to its position + white_rectangle.move_ip(momentum) # moves 2 to the right, 2 down + + # move the text by coordinates + x += 1 # move right by one pixel + y += 1 # move down by one pixel + + # erase the previous frame + window.fill(BLACK) + + # draw a rectangle that is a pygame.Rect object + pygame.draw.rect(window, WHITE, white_rectangle) + + # draws text onto the rectangle + window.blit(text, white_rectangle) + # you can also use coordinates in the form of a tuple + # the coordinates would place the top left pixel + # Syntax: window.blit(text, (x,y)) + + # update the screen + pygame.display.update() + + pygame.time.wait(30) # adds a 30 millisecond delay + +pygame.quit() # close pygame after finishing diff --git a/games/chapter2/solutions/reset_position.py b/games/chapter2/solutions/reset_position.py new file mode 100644 index 00000000..fe5cbc5f --- /dev/null +++ b/games/chapter2/solutions/reset_position.py @@ -0,0 +1,55 @@ +# Reset the moving rectangle's position if it leaves the screen! +# The rectangle that will be moving is already provided +# it is `red_rectangle`. Your job is to move it across the screen +# at a speed of 5px down and 5px right per frame. Then, if the +# bottom of the rectangle is greater than the screen height or the +# right of the rectangle is greater than the screen width, reset +# the rectangle's x and y to 0 and 0. + +import pygame + +pygame.init() + +SCREEN_HEIGHT = 600 +SCREEN_WIDTH = 600 + +window = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) +pygame.display.set_caption("Reset Position") + +# color constants +RED = (255, 0, 0) +BLACK = (0, 0, 0) + +# makes a pygame.Rect rectangle +# the syntax is `myvar = pygame.Rect(top-left x, top-left y, width, height)` +red_rectangle = pygame.Rect(0, 0, 100, 100) + +run = True +while run: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + # move the rectangle 5 units right and 5 units down each frame + red_rectangle.move_ip(5, 5) + + # erase the previous frame + window.fill(BLACK) + + # reset the rectangle if its right is past the screen width or + # its bottom is below the screen height + if ( + red_rectangle.right > SCREEN_WIDTH + or red_rectangle.bottom > SCREEN_HEIGHT + ): + red_rectangle.x, red_rectangle.y = 0, 0 + + # draw the rectangle in red + pygame.draw.rect(window, RED, red_rectangle) + + # update the screen + pygame.display.update() + + pygame.time.wait(50) # wait 50 milliseconds between frame + +pygame.quit() diff --git a/games/chapter3/examples/comprehensive_example.py b/games/chapter3/examples/comprehensive_example.py new file mode 100644 index 00000000..d978eff4 --- /dev/null +++ b/games/chapter3/examples/comprehensive_example.py @@ -0,0 +1,23 @@ +import pygame + +pygame.init() + +flag = pygame.locals.RESIZABLE +window = pygame.display.set_mode((500, 400), flag) + +pygame.event.set_blocked(pygame.KEYDOWN) + +run = True +while run: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + # if event is QUIT + run = False + if event.type == pygame.KEYUP: + # if event is KEYUP + print("Up up up!") + if event.type == pygame.KEYDOWN: + # will never happen because KEYDOWN is blocked + print("Down down down!") + +pygame.quit() diff --git a/games/chapter3/practice/MoveRectProblem.py b/games/chapter3/practice/MoveRectProblem.py new file mode 100644 index 00000000..22d180ad --- /dev/null +++ b/games/chapter3/practice/MoveRectProblem.py @@ -0,0 +1,11 @@ +# Build off of your previous code (the code from QuitPygameProblem.py) +# Draw the provided rectangle onto the screen. +# Move the object up when either the W or up arrow key is pressed; +# right when either the D or right arrow is pressed; etc. + +# rect[0] = rect color +# rect[1] = x-coord +# rect[2] = y-coord +# rect[3] = width +# rect[4] = height +rectangle = [(255, 0, 0), 20, 20, 20, 20] diff --git a/games/chapter3/practice/QuitPygameProblem.py b/games/chapter3/practice/QuitPygameProblem.py new file mode 100644 index 00000000..f18c99c8 --- /dev/null +++ b/games/chapter3/practice/QuitPygameProblem.py @@ -0,0 +1,8 @@ +# Create a pygame window. +# Close the pygame window when either the quit event occurs +# or the escape key is pressed. +# Use the provided width and height. Fill the screen with white + +width = 500 +height = 500 +white = (255, 255, 255) diff --git a/games/chapter3/practice/SpaceCounter.py b/games/chapter3/practice/SpaceCounter.py new file mode 100644 index 00000000..da33176d --- /dev/null +++ b/games/chapter3/practice/SpaceCounter.py @@ -0,0 +1,7 @@ +# Create a program that increments a counter every time the space bar is +# pressed. This counter should be displayed as text on the pygame window. + +import pygame + +pygame.init() +screen = pygame.display.set_mode((400, 400)) diff --git a/games/chapter3/practice/ticking_counter.py b/games/chapter3/practice/ticking_counter.py new file mode 100644 index 00000000..4befdd84 --- /dev/null +++ b/games/chapter3/practice/ticking_counter.py @@ -0,0 +1,22 @@ +# make a time bomb! + +# Create a counter that starts at a number, such +# 10 and goes down everytime the user presses +# the keyboard. However, this is a time bomb! +# create some text to warn the user, and when the +# number gets low, switch the message. Then, when +# the number hits zero, switch the message again +# to show that they've blown up, and exit the program. +# Wait a little before exiting so that the last message +# is readable. +# Make sure to use some of the methods featured in 3.4! + +import pygame # add more imports if needed + +screen = pygame.display.set_mode((400, 400)) + +# feel free to change these values +fonts = pygame.font.SysFont("arial", 20) +font = pygame.font.SysFont("arial", 70) +text = "DON'T PRESS A KEY" +counter = 10 diff --git a/games/chapter3/solutions/MoveRectProblem.py b/games/chapter3/solutions/MoveRectProblem.py new file mode 100644 index 00000000..14056966 --- /dev/null +++ b/games/chapter3/solutions/MoveRectProblem.py @@ -0,0 +1,57 @@ +# Build off of your previous code (the code from QuitPygameProblem.py) +# Draw the provided rectangle onto the screen. +# Move the object up when either the W or up arrow key is pressed; +# right when either the D or right arrow is pressed; etc. + +import pygame + +pygame.init() + +run = True +width = 500 +height = 500 +white = (255, 255, 255) +screen = pygame.display.set_mode((width, height)) +screen.fill(white) + +# rect[0] = rect color +# rect[1] = x-coord +# rect[2] = y-coord +# rect[3] = width +# rect[4] = height +rectangle = [(255, 0, 0), 20, 20, 20, 20] + +# pygame main loop +while run: + pygame.time.delay(50) + # clear the screen by filling it white + screen.fill(white) + # draw rect + pygame.draw.rect( + screen, + rectangle[0], + pygame.Rect(rectangle[1], rectangle[2], rectangle[3], rectangle[4]), + ) + # Check events + for event in pygame.event.get(): + if event.type == pygame.QUIT: + # there are a couple of ways of stopping the pygame + # loop. One way is to set run = false. Or you can + # import sys and use sys.exit() to stop your program. + run = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + run = False + # get states of keys + keysPressed = pygame.key.get_pressed() + # recall that y coord decreases as you go up the window + # the origin is at the top left corner + if keysPressed[pygame.K_UP] or keysPressed[pygame.K_w]: + rectangle[2] -= 5 if rectangle[2] >= 5 else 0 + if keysPressed[pygame.K_s] or keysPressed[pygame.K_DOWN]: + rectangle[2] += 5 if rectangle[2] <= 475 else 0 + if keysPressed[pygame.K_d] or keysPressed[pygame.K_RIGHT]: + rectangle[1] += 5 if rectangle[1] <= 475 else 0 + if keysPressed[pygame.K_a] or keysPressed[pygame.K_LEFT]: + rectangle[1] -= 5 if rectangle[1] >= 5 else 0 + pygame.display.update() diff --git a/games/chapter3/solutions/QuitPygameProblem.py b/games/chapter3/solutions/QuitPygameProblem.py new file mode 100644 index 00000000..2ab243e3 --- /dev/null +++ b/games/chapter3/solutions/QuitPygameProblem.py @@ -0,0 +1,29 @@ +# Create a pygame window. +# Close the pygame window when either the quit event occurs +# or the escape key is pressed. +# Use the provided width and height. Fill the screen with white + +import pygame + +pygame.init() + +run = True +width = 500 +height = 500 +white = (255, 255, 255) +screen = pygame.display.set_mode((width, height)) +screen.fill(white) + +# pygame main loop +while run: + pygame.time.delay(50) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + # there are a couple of ways of stopping the pygame + # loop. One way is to set run = false. Or you can + # import sys and use sys.exit() to stop your program. + run = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + run = False + pygame.display.update() diff --git a/games/chapter3/solutions/SpaceCounter.py b/games/chapter3/solutions/SpaceCounter.py new file mode 100644 index 00000000..bb742f0f --- /dev/null +++ b/games/chapter3/solutions/SpaceCounter.py @@ -0,0 +1,39 @@ +# Create a program that increments a counter every time the space bar is +# pressed. This counter should be displayed as text on the pygame window. + +import pygame + +pygame.init() +screen = pygame.display.set_mode((400, 400)) + +font = pygame.font.SysFont("arial", 70) + +display_counter = 0 + +run = True + +while run: + # Render the "display_counter" to the screen + show_counter = font.render(str(display_counter), True, (255, 192, 203)) + + # Makes Screen Black + screen.fill((0, 0, 0)) + + # Prints Data on Screen + screen.blit(show_counter, (30, 30)) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + # Checks to see if key is pressed + if event.type == pygame.KEYDOWN: + # Checks to see if the space is pressed + if event.key == pygame.K_SPACE: + # Adds one to the counter + display_counter += 1 + + # Updates the data + pygame.display.update() + +pygame.quit() diff --git a/games/chapter3/solutions/ticking_counter.py b/games/chapter3/solutions/ticking_counter.py new file mode 100644 index 00000000..13e8478b --- /dev/null +++ b/games/chapter3/solutions/ticking_counter.py @@ -0,0 +1,38 @@ +import pygame +import time # use this to show text + +pygame.init() +screen = pygame.display.set_mode((400, 400)) # set frame + +fonts = pygame.font.SysFont("arial", 20) # size of message +font = pygame.font.SysFont("arial", 70) # size of counter +text = "DON'T PRESS A KEY" # text +counter = 10 # sets the counter + +run = True + +while run: + if pygame.event.peek(pygame.KEYDOWN): # checks queue for keydown + counter -= 1 # decreases counter by one + pygame.event.clear(pygame.KEYDOWN) # clears keydown from queue + if counter == 1: # changes text + text = "PLEASE YOU'll BLOW US UP!" + if counter == 0: # changes text and ends program + text = "YOU BLEW US UP D:" + run = False + # the if statements are before because when + # run is false, they will still run + # one last time, showing the last message + show_message = fonts.render( + text, True, (255, 102, 253) + ) # sets the message + show_counter = font.render( + str(counter), True, (255, 230, 102) + ) # sets the counter + screen.fill((0, 0, 0)) # refreshes every frame + screen.blit(show_counter, (200, 200)) # shows counter + screen.blit(show_message, (50, 100)) # shows message + pygame.display.update() # updates the frame + +time.sleep(1) # makes the last value of text readable +pygame.quit() diff --git a/games/chapter4/examples/OOP_game.py b/games/chapter4/examples/OOP_game.py new file mode 100644 index 00000000..ab3bf7c7 --- /dev/null +++ b/games/chapter4/examples/OOP_game.py @@ -0,0 +1,473 @@ +""" +This is a tank game made with pygame and original images. + +Brief description of classes within this file: + Game_obj - the abstract base class for all the objects that appear + on-screen, including the Tank class, the Bullet class, and + the Target class + Bullet - inherits from Game_obj. + Target - inherits from Game_obj. It is always stationary. + Tank - inherits from Game_obj. Takes keyboard input (W, A, S, and D) + to control the tank's movement. + App - the abstract base class for the actual Tank_game class. It's + purpose is to define a structure for the game. + Tank_game - the functional class that inherits from App. It creates + a bullet whenever the mouse is clicked. It handles the collisions + (if a bullet hits a target, both are deleted. If the tank runs into + the target, the target is deleted.) +""" + +import pygame + +from pygame.locals import ( + K_w, + K_s, + K_a, + K_d, + KEYDOWN, + KEYUP, + QUIT, + RESIZABLE, + MOUSEBUTTONDOWN, +) +import time +import math +import random + +BULLET_IMG_PATH = "./bullet.png" +TARGET_IMG_PATH = "./target.png" +TANK_IMG_PATH = "./completetank.png" + +BLACK = (255, 255, 255) +DIRTBROWN = (168, 95, 0) +SANDBROWN = (237, 201, 175) + +TANKSPEED = [2, 2] # speed x and speed y +BULLETSPEED = [8, 8] + + +class Game_obj: + def __init__(self, picture: str, **kwargs) -> None: + """ + A basic game object class. It handles collisions, + the basic drawing method, the move and moveto methods, + and the check_out_of_screen method. + + Arguments: + picture:str - the location of the picture that will be displayed on + the screen for this object + Valid keyword arguments: + "size":tuple(x,y) - a specific size that you want to have the object be. + The picture will be scaled to that size and the hitbox + will be updated accordingly. + "position":tuple(x,y) - the tuple at which the top left of the object + should be positioned at + "speed":tuple(x,y) - the tuple that represents the object's speed. + """ + self.name = "" + + # self.image will be a pygame.Surface class + self.image = pygame.image.load(picture) + self.image = ( + pygame.transform.scale( + self.image, (kwargs["size"][0], kwargs["size"][1]) + ) + if "size" in kwargs + else self.image + ) + + self.rect = ( + self.image.get_rect() + ) # self.rect will be of pygame.Rect class + self.size = self.rect.size # will be a tuple of (sizex, sizey) + + if "position" in kwargs: + self.moveto(kwargs["position"]) + + self.speed = ( + {"x": kwargs["speed"][0], "y": kwargs["speed"][1]} + if "speed" in kwargs + else {"x": 0, "y": 0} + ) + + def check_collision(self, other: object) -> bool: + if not isinstance(other, Game_obj): + raise TypeError( + "Invalid type; need a game_obj or a child class of game_obj" + ) + # the rect class's colliderect method returns 1 if there is + # a collision and 0 if there isn't a collision + return self.rect.colliderect(other.rect) == 1 + + def draw(self, screen: pygame.Surface, color: tuple) -> None: + pygame.draw.rect(screen, color, self.rect, 0) + screen.blit(self.image, self.rect) + + def move(self) -> None: + """ + Moves the object according to it's current speed. + """ + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + # self.draw(screen, color) + + def set_speed(self, new_speed: tuple) -> None: + """ + Sets the object's speed to the provided tuple + Arguments: + new_speed (tuple(x,y)) - a tuple containing the desired speed for + the object to have. + """ + self.speed["x"], self.speed["y"] = new_speed[0], new_speed[1] + + def moveto(self, position: tuple) -> None: + """ + A helper function that moves the rectangle to the desired position. + + Arguments: + position (tuple) - the x and y coordinates of where you want the rectangle's + top left to be moved to. + """ + self.rect = self.rect.move( + position[0] - self.rect.topleft[0], + position[1] - self.rect.topleft[1], + ) + + def check_out_of_screen(self, screen_size: tuple) -> bool: + """ + Checks whether or not the object is completely outside of the screen. + Returns True or False accordingly. + Arguments: + screen_size (tuple) - the size of the screen (x,y) + """ + if ( + self.rect.bottom > screen_size[1] + or self.rect.top < 0 + or self.rect.left < 0 + or self.rect.right > screen_size[0] + ): + return True + return False + + def __str__(self): + return ( + f"{self.name} object located at the position {self.rect.topleft}" + ) + + +class Bullet(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(BULLET_IMG_PATH, **kwargs) + self.name = "Bullet" + + +class Target(Game_obj): + def __init__(self, **kwargs) -> None: + kwargs["size"] = 40, 40 + super().__init__(TARGET_IMG_PATH, **kwargs) + self.name = "Target" + + +class Tank(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(TANK_IMG_PATH, **kwargs) + self.direction = [0, 0] + self.SPEED = kwargs["speed"] if "speed" in kwargs else [2, 2] + self.speed["x"], self.speed["y"] = 0, 0 + + def set_speed(self) -> None: + # use math stuff to calculate the speed given that the + # max speed is self.SPEED + self.speed["x"] = ( + self.direction[0] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[0] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[0] * self.SPEED[0] + ) + self.speed["y"] = ( + self.direction[1] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[1] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[1] * self.SPEED[1] + ) + + def set_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] -= 1 + if direction == "down": + self.direction[1] += 1 + if direction == "left": + self.direction[0] -= 1 + if direction == "right": + self.direction[0] += 1 + + def unset_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] += 1 + if direction == "down": + self.direction[1] -= 1 + if direction == "left": + self.direction[0] += 1 + if direction == "right": + self.direction[0] -= 1 + + +class App: + """ + The abstract base class for the actual Tank_game class. It's + main purpose is to define a structure for the game. + It's structure is as follows: + Upon initialization, it runs the create_objects method + It's mainloop is comprised of the following methods: + check_events + check_collisions + move_objects + update_display + """ + + def __init__( + self, flags=RESIZABLE, width=960, height=540, title="My Game" + ): + pygame.init() + self.size = [width, height] + self.screen = pygame.display.set_mode(self.size, flags) + pygame.display.set_caption(title, title) + + self.running = True + + self.create_objects() + + def create_objects(self): + """ + This should create the initial objects on the screen. + """ + pass + + def check_events(self, event): + """ + This should take user input and handle it appropriately. + """ + pass + + def update_display(self): + """ + This should utilize clear the screen and then draw + all current objects onto the screen. + """ + pass + + def move_objects(self): + """ + This should utilize the move method that the game objects have. + """ + pass + + def check_collisions(self): + """ + This should utilize the check_collision method that the game objects + have. + """ + pass + + def mainloop(self): + while self.running: + for event in pygame.event.get(): + if event.type == QUIT: + self.running = False + break + else: + self.check_events( + event + ) # this will handle checking for user input + # such as KEYUP and MOUSEBUTTONDOWN events needed to run the game + self.check_collisions() # checks collisions between bullet/tank and targets + self.move_objects() # moves each object on the screen + self.update_display() # redraws updated objects onto the screen + pygame.display.update() # pygame’s method to show the updated screen + time.sleep(0.01) # not necessary; it's a frame cap + pygame.quit() + + +class Tank_Game(App): + def __init__(self): + # this can be changed, it's the number of targets allowed at a time. + # we initialize this before super().__init__ because super().__init__ calls + # create_objects, which utilizes self.NUM_TARGETS + self.NUM_TARGETS = 3 + + super().__init__(title="Tanks") + + self.playerscore = 0 # the player's score + + # sets the display icon to the TankIcon.png provided + pygame.display.set_icon(pygame.image.load("./TankIcon.png")) + + def create_objects(self): + """ + This creates the initial objects seen when the game + first starts up. + """ + # tank + self.tank = Tank(speed=TANKSPEED) + self.tank.moveto( + ( + self.size[0] / 2 - self.tank.size[0], # move to middle x + self.size[1] - self.tank.size[1], # move to bottom y + ) + ) + + # targets + self.targets = [Target(speed=[0, 0]) for i in range(self.NUM_TARGETS)] + for target in self.targets: + target.moveto( + ( + random.randint( + 0, self.size[0] - target.size[0] + ), # random x + random.randint( + 0, self.size[1] - target.size[1] + ), # random y + ) + ) + + # bullets + self.bullets = [] + + # Score text + self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32) + + def check_events(self, event): + """ + We imported all from pygame.locals, so that means + that we can check KEYDOWN and KEYUP and individual + keys such as K_w (w key), K_a (a key), etc. + """ + # change the path of the tank if w, a, s, or d was pressed + if event.type == KEYDOWN: + if event.key == K_w: + self.tank.set_path("up") + if event.key == K_s: + self.tank.set_path("down") + if event.key == K_a: + self.tank.set_path("left") + if event.key == K_d: + self.tank.set_path("right") + if event.type == KEYUP: + if event.key == K_w: + self.tank.unset_path("up") + if event.key == K_s: + self.tank.unset_path("down") + if event.key == K_a: + self.tank.unset_path("left") + if event.key == K_d: + self.tank.unset_path("right") + self.tank.set_speed() + + # create bullets if mouse button was pressed + if event.type == MOUSEBUTTONDOWN: + bul = Bullet(speed=BULLETSPEED) + bul.moveto( + (self.tank.rect.centerx, (self.tank.rect.top - bul.size[1])) + ) # move the bullet to the front of the tank + + # math stuff to calculate trajectory + mouse_pos = pygame.mouse.get_pos() + h = mouse_pos[1] - bul.rect.center[1] + w = mouse_pos[0] - bul.rect.center[0] + hyp = math.sqrt(h**2 + w**2) + vertical_speed = ( + BULLETSPEED[1] * (h / hyp) if hyp != 0 else BULLETSPEED[1] * h + ) + horizontal_speed = ( + BULLETSPEED[0] * (w / hyp) if hyp != 0 else BULLETSPEED[0] * w + ) + + bul.set_speed((horizontal_speed, vertical_speed)) + self.bullets.append(bul) + + def move_objects(self): + """ + This method moves the objects within the game. + If a bullet is outside of the screen, it is + not moved and is unreferenced. + """ + self.tank.move() + + self.bullets = [ + bullet + for bullet in self.bullets + if bullet.check_out_of_screen(self.size) is False + ] + + for bullet in self.bullets: + bullet.move() + + def check_collisions(self): + """ + This checks whether any of the objects within the game have collided + with each other. Specifically, we are looking for collisions between + bullets and targets or the tank and targets + """ + deletions = 0 # number of targets deleted + num_bullets = len(self.bullets) + + # check bullet-target collisions + for i in range(num_bullets): + for target in self.targets: + # if the bullet collided with the target + if self.bullets[i - deletions].check_collision(target) is True: + # pop both the bullet and target so that they will be + # effectively deleted + self.bullets.pop(i - deletions) + self.targets.pop(self.targets.index(target)) + + # give points for hitting the target + self.playerscore += 20 + deletions += 1 + break # stop the current iteration since the target and + # bullet are popped, so referencing them would error. + + # check tank-target collisions + for target in self.targets: + if self.tank.check_collision(target) is True: + self.targets.pop(self.targets.index(target)) + deletions += 1 + self.playerscore += 10 # only 10 for running over targets lol + + # create a new target for every deleted target + for i in range(deletions): + a = Target(speed=[0, 0]) + a.moveto( + ( + random.randint(0, self.size[0] - a.size[0]), + random.randint(0, self.size[1] - a.size[1]), + ) + ) + self.targets.append(a) + + def update_display(self): + self.screen.fill(SANDBROWN) + + # tank + self.tank.draw(self.screen, SANDBROWN) + + # targets + for target in self.targets: + target.draw(self.screen, BLACK) + + # bullets + for bullet in self.bullets: + bullet.draw(self.screen, BLACK) + + # score text + font_img = self.font.render( + "Score: %s" % str(self.playerscore), True, BLACK + ) + font_rect = font_img.get_rect() + pygame.draw.rect(self.screen, SANDBROWN, font_rect, 1) + self.screen.blit(font_img, font_rect) + + +game = Tank_Game() +game.mainloop() diff --git a/games/chapter4/examples/TankIcon.png b/games/chapter4/examples/TankIcon.png new file mode 100644 index 00000000..d9e00bbe Binary files /dev/null and b/games/chapter4/examples/TankIcon.png differ diff --git a/games/chapter4/examples/Target.png b/games/chapter4/examples/Target.png new file mode 100644 index 00000000..d48bc78e Binary files /dev/null and b/games/chapter4/examples/Target.png differ diff --git a/games/chapter4/examples/bullet.png b/games/chapter4/examples/bullet.png new file mode 100644 index 00000000..fce8c161 Binary files /dev/null and b/games/chapter4/examples/bullet.png differ diff --git a/games/chapter4/examples/completetank.png b/games/chapter4/examples/completetank.png new file mode 100644 index 00000000..036cd84b Binary files /dev/null and b/games/chapter4/examples/completetank.png differ diff --git a/games/chapter4/examples/tank_game.zip b/games/chapter4/examples/tank_game.zip new file mode 100644 index 00000000..c6900645 Binary files /dev/null and b/games/chapter4/examples/tank_game.zip differ diff --git a/games/chapter4/practice/flappy_bird/OOPflappybird.py b/games/chapter4/practice/flappy_bird/OOPflappybird.py new file mode 100644 index 00000000..08ca2aa2 --- /dev/null +++ b/games/chapter4/practice/flappy_bird/OOPflappybird.py @@ -0,0 +1,494 @@ +# TODO +# Create the FlappyBird game!! + +# You are provided with some starting code. +# The starting code, however, doesn't run by itself. +# What you need to do: +# define GameObj's draw method +# define GameObj's check_collision method. + +# Complete all the methods within Tubes class + +# Complete the draw_score and draw_buttons methods in the FlappyBird class + +import pygame +import random + +pygame.init() + +# screen +width = 800 +height = 600 +SIZE = (width, height) +screen = pygame.display.set_mode(SIZE) + +# colors +LGREEN = (62, 245, 59) +DGREEN = (40, 143, 39) +YELLOW = (250, 250, 37) +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +RED = (255, 0, 0) +LILAC = (175, 95, 237) +LBLUE = (80, 221, 242) +DBLUE = (80, 99, 242) +PINK = (245, 144, 188) +CYAN = (0, 150, 150) + +# images +BACKGROUNDIMG = pygame.image.load("./background.png") +BACKGROUNDIMG = pygame.transform.scale(BACKGROUNDIMG, (width, height)) +SPRITESHEET = pygame.image.load("./flyingbird.png") +COINPIC = pygame.image.load("./coin.png") + +# ---------- States of the Game ---------- +MENUSTATE = 0 # Menu Screen +GAMESTATE = 1 # Play Game +LOSESTATE = 2 # u loose >:) +QUITSTATE = 3 +NUMSTATES = 4 + + +class GameObj: + """ + An abstract class used as the base class for all the + game's objects + """ + + def __init__(self): + """ + This __init__ method provides no functionality. + It just enables the methods defined in this class. + Thus, calling super().__init__ is unnecessary. + """ + self.rect = pygame.Rect + + def draw( + self, + screen: pygame.Surface, + color: tuple, + specific_rect: pygame.Rect = None, + ): + """ + Draws a rectangle onto the screen in the specified color. + If specific_rect is not None, draw specific_rect onto the screen. + If specific_rect is None, draw self.rect onto the screen. + """ + pass + + def move(self, speed: dict = None, specific_rect: pygame.Rect = None): + """ + Moves a rectangle. + @param speed - The speed to move the rectangle at. It should be + a dictionary of form {'x': int, 'y': int}; for example, + {'x':33, 'y':-22}. If no speed is provided, uses self.speed + @param specific_rect - if specific_rect is None, then this method + will move self.rect. If specific_rect is not None, then this method will + move specific_rect + """ + if not speed and hasattr(self, "speed"): + if specific_rect: + return specific_rect.move(self.speed["x"], self.speed["y"]) + else: + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + if speed: + if specific_rect: + return specific_rect.move(speed["x"], speed["y"]) + else: + self.rect = self.rect.move(speed["x"], speed["y"]) + + def check_collision(self, other, specific_rect: pygame.Rect = None): + """ + Checks if rectangles have collided. If specific_rect is not None, + checks if specific_rect collides with other.rect. If specific_rect is None, + checks if self.rect collides with other.rect. + """ + pass + + +class Tubes(GameObj): + """ + Class to represent the two tubes. + + Ex: + The tubes will look sort of like the below drawing + (one on the top, one on the bottom) + (let - be top or bottom of school) + ------------ + | | + |_| + + _ + | | + | | + | | + ------------ + """ + + TUBEGAP = 230 # smaller TUBEGAP -> smaller dist between tubes + TUBEWIDTH = 100 + + def __init__(self, bottom_tube_height: int): + """ + Initializes two pygame.Rect objects: one for the + top tube (call it top_tube) and one for the bottom tube + (call it bottom_tube). Uses the TUBEGAP + and TUBEWIDTH variables as dimensions. + """ + pass + + def draw(self, screen: pygame.Surface): + """ + Uses the draw() method from the inherited + GameObj class to draw the top and bottom tubes. + Hint: this will use the specific_rect argument + """ + pass + + def move(self, speed: dict): + """ + Uses the move() method from the inherited + GameObj class to move the specific top and bottom tubes. + Hint: this will use the specific_rect argument + """ + pass + + def check_collision(self, other) -> bool: + """ + Uses the check_collision() method from the inherited + GameObj class to check for any collisions + between the given object and the tubes. + Hint: this will use the specific_rect argument + + Returns: + boolean - if either tube is collided with, return True + """ + pass + + +class Coin(GameObj): + """ + The coin that the bird will get + in-between tubes. + Doesn't need to do anything, so pretty short class. + """ + + def __init__(self, center_y: int): + """ + Makes a coin object. + The coin's x coordinate will be the width of the screen + The coin's y coordinate will be centered around `center_y` + @param center_y:int - the y coordinate to center the coin around + """ + temprect = COINPIC.get_rect() + self.rect = pygame.Rect( + width, + center_y - temprect.height // 2, + temprect.width, + temprect.height, + ) + + def draw(self, screen: pygame.Surface): + super().draw(screen, BLACK) + + def blit(self, screen: pygame.Surface): + screen.blit(COINPIC, self.rect) + + +class Bird(GameObj): + """ + The bird itself. It processes the sprites + and handles jumping. + """ + + start_center_pos = (width // 8, height // 2) + + def __init__(self): + self.process_spritesheet(SPRITESHEET, 3, 3) + self.rect = pygame.Rect( + self.start_center_pos[0] - self.sprite_frame_width // 2, + self.start_center_pos[1] - self.sprite_frame_height // 2, + self.sprite_frame_width, + self.sprite_frame_height, + ) + self.momentum = 0 # the bird's current vertical speed + self.jump_height = 15 + self.min_speed = -10 # the maximum speed the bird flies down at + self.cur_sprite_idx = 0 + + def process_spritesheet( + self, + spritesheet: pygame.Surface, + num_pics_x: int, + num_pics_y: int, + offset_x: int = 0, + offset_y: int = 0, + ): + """ + Creates sprites from the spritesheet. + @param spritesheet: pygame.Surface - the spritesheet. + @param num_pics_x: int - the number of sprites in each row on + the spritesheet + @param num_pics_y: int - the number of sprites in each column + on the spritesheet + @param offset_x: int - the x offset before the sprite rows start + @param offset_y: int - the y offset before the sprite columns start + """ + self.sprites = [] + self.sprite_frame_width = ( + spritesheet.get_width() - offset_x + ) // num_pics_x + self.sprite_frame_height = ( + spritesheet.get_height() - offset_y + ) // num_pics_y + for row in range(num_pics_x): + for column in range(num_pics_y): + temp = spritesheet.subsurface( + ( + row * self.sprite_frame_width + offset_x, + column * self.sprite_frame_height + offset_y, + self.sprite_frame_width, + self.sprite_frame_height, + ) + ) + # get the bounding box for the actual colored pixels + # (so that we won't be blit-ing extra empty pixels) + # (makes collisions more accurate) + temprect = temp.get_bounding_rect() + # then, append the shortened image to the sprites list + self.sprites.append(temp.subsurface(temprect)) + + def draw(self, screen: pygame.Surface, framecount: int): + curr_sprite_idx = framecount // 5 % len(self.sprites) + if curr_sprite_idx != self.cur_sprite_idx: + # if it is now a different sprite, adjust self.rect + # so that it won't be bigger or smaller than the new sprite + self.cur_sprite_idx = curr_sprite_idx + temp = self.sprites[self.cur_sprite_idx] + self.rect = temp.get_rect().move( + self.rect.topleft[0], self.rect.topleft[1] + ) + super().draw(screen, BLACK) + + def blit(self, screen: pygame.Surface): + screen.blit(self.sprites[self.cur_sprite_idx], self.rect) + + def process_movement(self, event): + if event.type == pygame.KEYDOWN and event.key == pygame.K_UP: + self.momentum = self.jump_height + + def move(self): + super().move({"x": 0, "y": -self.momentum}) + self.momentum -= 1 + + # if the bird would fly down faster than self.min_speed, + # cap self.momentum at self.min_speed + if self.momentum < self.min_speed: + self.momentum = self.min_speed + + +class Button(GameObj): + """ + A button with text. Used for the + 'Quit Game' 'Start Game' and 'Retry' buttons + """ + + def __init__( + self, + center_x: int, + center_y: int, + bgcolor: tuple, + textcolor: tuple, + text: str = "", + textsize: int = 32, + ): + """ + Creates a Button object. + @param center_x: int - the x coordinate of the button's center + @param center_y: int - the y coordinate of the button's center + @param bgcolor: tuple - the color for the button's background + @param textcolor: tuple - the color for the button's text + @param text: str - the text to put inside the button + @param textsize: int - the size of the button's text + """ + self.font = pygame.font.SysFont("arial", textsize) + self.font_img = self.font.render(text, True, textcolor) + self.rect = self.font_img.get_rect() + self.rect.center = (center_x, center_y) + self.bgcolor = bgcolor + self.active = True + + def draw(self, screen: pygame.Surface): + super().draw(screen, self.bgcolor) + screen.blit(self.font_img, self.rect) + + def is_clicked(self, event: pygame.event.Event): + if event.type == pygame.MOUSEBUTTONDOWN: + return event.pos in self + + def __contains__(self, coordinate): + return self.rect.contains((coordinate[0], coordinate[1], 0, 0)) + + +class FlappyBird: + """ + This is the game class. + """ + + def __init__(self): + self.running = True + self.gamestate = MENUSTATE + self.create_buttons() + self.framecount = 0 + self.clock = pygame.time.Clock() + + def create_buttons(self, button1text="Start Game", button2bg=RED): + self.buttons = { + "start": Button( + width // 2, height // 4, LGREEN, LILAC, button1text + ), + "quit": Button( + width // 2, height // 4 * 3, button2bg, LILAC, "Quit Game" + ), + } + + def mainloop(self): + while self.running: + events = pygame.event.get() + for event in events: + self.set_state(event) + if event.type == pygame.QUIT: + self.running = False + if self.gamestate == MENUSTATE: + screen.fill(LBLUE) + self.draw_buttons(screen) + + elif self.gamestate == GAMESTATE: + self.draw_all() + self.check_collisions() + + # update bird's speed + for event in events: + self.bird.process_movement(event) + self.move_objects() + + self.create_tubes() + + elif self.gamestate == LOSESTATE: + screen.fill(RED) + self.draw_buttons(screen) + + elif self.gamestate == QUITSTATE: + self.running = False + + self.framecount += 1 + pygame.display.update() + self.clock.tick(60) + pygame.quit() + + def set_state(self, event): + if event.type == pygame.MOUSEBUTTONDOWN: + if all([but.active for but in self.buttons.values()]): + if self.buttons["start"].is_clicked(event): + self.buttons["start"].active = False + self.buttons["quit"].active = False + self.start_game() + elif self.buttons["quit"].is_clicked(event): + self.gamestate = QUITSTATE + self.buttons["start"].active = False + self.buttons["quit"].active = False + + def draw_background(self): + screen.blit(BACKGROUNDIMG, (self.background_x, 0)) + screen.blit(BACKGROUNDIMG, (self.background_x + width, 0)) + self.background_x -= 2 + if self.background_x < -1 * width: + self.background_x = 0 + + def draw_score(self): + """ + Writes the player's score onto the screen in the top + right corner. + Hint: this uses pygame fonts + """ + pass + + def draw_buttons(self, screen: pygame.Surface): + """ + Draws the "start" and "quit" buttons onto the screen. + Hint: this uses `self.buttons` (which is already made) + """ + pass + + def create_tubes(self): + """ + Creates tubes and puts a coin in the middle of each tube. + """ + if ( + len(self.tubes) == 0 + or self.tubes[-1].bottom_tube.right < width - 200 + ): + bottom_tube_height = random.randint(0, height - Tubes.TUBEGAP) + self.tubes.append(Tubes(bottom_tube_height)) + self.coins.append( + Coin(height - bottom_tube_height - (Tubes.TUBEGAP // 2)) + ) + + def draw_all(self): + # draw bird and coin rectangles before background so that they won't + # show + self.bird.draw(screen, self.framecount) + for coin in self.coins: + coin.draw(screen) + + self.draw_background() + + for tube in self.tubes: + tube.draw(screen) + + # blit images/sprites onto the screen + self.bird.blit(screen) + for coin in self.coins: + coin.blit(screen) + + self.draw_score() + + def check_collisions(self): + for tube in self.tubes: + if tube.check_collision(self.bird): + self.gamestate = LOSESTATE + self.create_buttons("Retry?", LBLUE) + if tube.bottom_tube.right < 0: + self.tubes.remove(tube) + + for coin in self.coins: + if self.bird.check_collision(coin): + self.score += 1 + self.coins.remove(coin) + if coin.rect.right < 0: + self.coins.remove(coin) + + if self.bird.rect.bottom > height: # fell out of screen + self.gamestate = LOSESTATE + self.create_buttons("Retry?", LBLUE) + + def move_objects(self): + SPEED = 3 # x speed that objects move towards the bird at + for tube in self.tubes: + tube.move({"x": -SPEED, "y": 0}) + for coin in self.coins: + coin.move({"x": -SPEED, "y": 0}) + self.bird.move() + + def start_game(self): + self.gamestate = GAMESTATE + self.background_x = 0 + self.score = 0 + self.bird = Bird() + self.tubes = [] + self.coins = [] + self.create_tubes() + + +a = FlappyBird() +a.mainloop() diff --git a/games/chapter4/practice/flappy_bird/background.png b/games/chapter4/practice/flappy_bird/background.png new file mode 100644 index 00000000..0be8c233 Binary files /dev/null and b/games/chapter4/practice/flappy_bird/background.png differ diff --git a/games/chapter4/practice/flappy_bird/bird.png b/games/chapter4/practice/flappy_bird/bird.png new file mode 100644 index 00000000..2b8a5b32 Binary files /dev/null and b/games/chapter4/practice/flappy_bird/bird.png differ diff --git a/games/chapter4/practice/flappy_bird/coin.png b/games/chapter4/practice/flappy_bird/coin.png new file mode 100644 index 00000000..dc457bff Binary files /dev/null and b/games/chapter4/practice/flappy_bird/coin.png differ diff --git a/games/chapter4/practice/flappy_bird/explosion_transparent.png b/games/chapter4/practice/flappy_bird/explosion_transparent.png new file mode 100644 index 00000000..a92d7099 Binary files /dev/null and b/games/chapter4/practice/flappy_bird/explosion_transparent.png differ diff --git a/games/chapter4/practice/flappy_bird/flappy_bird.zip b/games/chapter4/practice/flappy_bird/flappy_bird.zip new file mode 100644 index 00000000..4a21a88a Binary files /dev/null and b/games/chapter4/practice/flappy_bird/flappy_bird.zip differ diff --git a/games/chapter4/practice/flappy_bird/flyingbird.png b/games/chapter4/practice/flappy_bird/flyingbird.png new file mode 100644 index 00000000..25bd824a Binary files /dev/null and b/games/chapter4/practice/flappy_bird/flyingbird.png differ diff --git a/games/chapter4/practice/hockey.py b/games/chapter4/practice/hockey.py new file mode 100644 index 00000000..78db3c80 --- /dev/null +++ b/games/chapter4/practice/hockey.py @@ -0,0 +1,625 @@ +# Make a two-player hockey game! The application will consist +# of two rectangular paddles, starting on each side of the screen, +# and one circular ball that players must bounce around. Players can +# move the paddles in any direction to hit the ball into the goal. +# If the ball makes contact with safe parts of the screen, it will +# bounce off at a random angle but in the same general direction +# (left or right). It will do the same if it makes contact with one +# of the paddles, but will head towards the opposite general direction +# instead. If the ball touches the goals on either side of the screen, +# the application will say “Game Over. Player _ Wins”. You must put +# your code in classes and have separate keys for each player to +# move their paddles. + +# please use the provided constants. + +# TODO - +# Fill in the Game_obj class (init is done) +# Fill in Player class's init, draw, and setpath methods +# Fill in Ball class's collide_line and get_obj_path methods +# Fill in BoundingLine's init method +# Fill in Hockey class's update_display, move_objects, check_events, +# and check_collisions methods + + +import pygame +from pygame.locals import ( + K_w, + K_s, + K_a, + K_d, + K_UP, + K_DOWN, + K_LEFT, + K_RIGHT, + KEYDOWN, + KEYUP, + QUIT, + RESIZABLE, +) +from pygame.rect import Rect + +import math +import time +import random + +# define the necessary color constants using rgb values +BLACK = (0, 0, 0) +GREEN = (0, 120, 0) +RED = (120, 0, 0) +WHITE = (255, 255, 255) + +# define player controls +PLAYER1CONTROLS = {"up": K_w, "down": K_s, "left": K_a, "right": K_d} +PLAYER2CONTROLS = { + "up": K_UP, + "down": K_DOWN, + "left": K_LEFT, + "right": K_RIGHT, +} + +# initial screensize +SCREENSIZE = [900, 600] + +# how big the ball's radius will be +BALL_RADIUS = 3 + + +class Game_obj: + def __init__(self): + """ + This should just declare the variables + later used in the other methods + """ + self.speed = {"x": 0, "y": 0} + self.rect = Rect + self.prev_rect = Rect + + def move(self): + """ + This should first set self.prev_rect equal to self.rect. + Then, it should move self.rect according to self.speed + by using self.rect's move method as demonstrated in OOP_game.py. + """ + pass # your code here + + def move_to(self, coordinate): + """ + This should first set self.prev_rect equal to self.rect. + Then, it should move self.rect so that its top left lies at + the provided coordinate. + """ + pass # your code here + + def check_collision(self, other): + """ + This should return either True or False based on whether + self.rect's collide_rect method returns 1 or 0 (respectively) + similar to how it is done in OOP_game.py + """ + pass # your code here + + +class Player(Game_obj): + PLAYERSPEED = (3, 3) + PADDLESIZE = (10, 50) # x width, y width + + def __init__(self, control_keys): + """ + Creates a player rectangle. It should have an attribute + self.control_keys from provided control keys. It should + create a rectangle at (0, 0) that has self.PADDLESIZE dimensions. + It should also initialize self.path to [0,0] (x_path, y_path) + Arguments: + control_keys - (dict) should be a dictionary of the following format: + { + "up": (KEY) (ex: K_w), + "down": (KEY) (ex: K_s), + "left": (KEY) (ex: K_a), + "right": (KEY) (ex: K_d) + } + """ + pass + + def draw(self, surface: pygame.Surface): + """ + This should draw self.rect onto the provided surface in + the color GREEN + Note: the surface acts just like 'window' in previous + lessons + """ + pass # your code here + + def set_path(self, event): + """ + This is the method that calls self.key_checker with + the provided event and 'up', 'down', 'left', and 'right' + + note: a call to self.key_checker will look like: + self.key_checker(event, 'direction string here') + """ + pass # your code here + + def key_checker(self, event, direction): + """ + Helper function to deal with event keys. Sets self.path + according to PATH_VALUES + Arguments: + event(pygame.event.Event) - the event + direction(str) - the direction to check KEYDOWN and KEYUP for. + """ + PATH_VALUES = {"up": 1, "down": 1, "left": 0, "right": 0} + DIRECTION_VALUES = {"up": -1, "down": 1, "left": -1, "right": 1} + + # if the event doesn't have a key attribute, just return + if not hasattr(event, "key"): + return + + # if it does, then check if it the right key + if event.key == self.control_keys[direction]: + if event.type == KEYUP: + self.path[PATH_VALUES[direction]] += -DIRECTION_VALUES[ + direction + ] + if event.type == KEYDOWN: + self.path[PATH_VALUES[direction]] += DIRECTION_VALUES[ + direction + ] + + def set_speed(self): + """ + Sets the speed according to the Player object's path. + This should be called after self.path has been set. + """ + # this is provided since it's math-intensive. + self.speed["x"] = ( + self.path[0] + / math.sqrt(sum(abs(num) for num in self.path)) + * self.PLAYERSPEED[0] + if (sum(abs(num) for num in self.path)) != 0 + else self.path[0] * self.PLAYERSPEED[0] + ) + self.speed["y"] = ( + self.path[1] + / math.sqrt(sum(abs(num) for num in self.path)) + * self.PLAYERSPEED[1] + if (sum(abs(num) for num in self.path)) != 0 + else self.path[1] * self.PLAYERSPEED[1] + ) + + +class Ball(Game_obj): + BALLSPEED = (6, 6) + + def __init__(self, radius): + super().__init__() + self.rect = Rect(0, 0, radius * 2, radius * 2) + self.radius = radius + + # set up initial speed + initial_ang = random.randint(1, int(math.pi / 2 * 100)) / 100 + self.speed["x"] = ( + math.cos(initial_ang) + * self.BALLSPEED[0] + * (-1 if random.randint(0, 1) == 0 else 1) + ) + self.speed["y"] = ( + math.sin(initial_ang) + * self.BALLSPEED[1] + * (-1 if random.randint(0, 1) == 0 else 1) + ) + + def draw(self, screen: pygame.Surface): + pygame.draw.circle( + screen, WHITE, center=self.rect.center, radius=self.radius + ) + + def collide_line(self, other): + """ + Checks if the ball has hit a line. + If it did, update the speed accordingly + + IE: + if it collided with top or bottom, set + self.speed['y'] to negative self.speed['y'] + if it collided with right or left, + set self.speed['x'] to negative self.speed['x'] + + Arguments: + other (BoundingLine or Goal) - the line to check for a collision with + Returns: + True - if the collision happened + False - if the collision didn't happen + """ + pass # your code here + + def get_obj_path(self, object: Game_obj) -> tuple: + """ + if the object's speed is greater than 0, x_path = 1 + elif the object's speed is less than 0, x_path = -1 + else, the x_path = 0. + Do the same for y speed for a variable y_path. + """ + # your code here + + return # (x_path, y_path) (uncomment when you write your code here) + + def get_paddle_collision_dir(self, paddle: Player) -> tuple: + """ + Gets the direction in which the ball will be headed + after a collision with a paddle. + Does not actually check if the collision happened + Provided since it's somewhat complicated + + Arguments: + paddle (Player) - the player that the ball 'collided with' + Returns: + tuple(int, int) - a tuple of length 2 with just +-1's + ex: (1, 1) or (1, -1) or (-1, 1), or (-1, -1) + It corresponds to the direction in which the ball + will be headed. The first item will be the x direction + and the second item will be the y direction. + """ + paddle_x_dir, paddle_y_dir = self.get_obj_path(paddle) + + ball_x_dir, ball_y_dir = self.get_obj_path(self) + + resulting_x_dir = None + resulting_y_dir = None + + if paddle.speed["x"] == 0 or paddle_x_dir == ball_x_dir: + if abs(paddle.speed["x"]) > abs(self.speed["x"]): + resulting_x_dir = paddle_x_dir + elif abs(paddle.speed["x"]) < abs(self.speed["x"]): + resulting_x_dir = -ball_x_dir + else: + resulting_x_dir = -ball_x_dir + + if paddle.speed["y"] == 0 or paddle_y_dir == ball_y_dir: + if abs(paddle.speed["y"]) > abs(self.speed["y"]): + resulting_y_dir = paddle_y_dir + elif abs(paddle.speed["y"]) < abs(self.speed["y"]): + resulting_y_dir = -ball_y_dir + else: + resulting_y_dir = -ball_y_dir + + return (resulting_x_dir, resulting_y_dir) + + def collide_paddle(self, paddle: Player, executions: int) -> None: + """ + Handles collisions with paddles. + + Checks if the ball hit the provided player. If it did, + it will adjust the ball's direction. + + Arguments: + paddle(Player) - the paddle to check for a collision with + executions(int) - the amount of executions of the game's mainloop + It's not important, but it prevents unwanted collisions during + the 0th execution when we first set up the game by moving + the objects to the right place + """ + PROPORTION = 0.25 # used when "escaping" a collision + MINIMUM_ANGLE = ( + 15 # this is in degrees; it's just a fine-tuning aspect + ) + # that makes the game more realistic + + resulting_x_dir = None + resulting_y_dir = None + + a = self.trace_collisions(paddle) + if a[0] and executions != 0: + resulting_x_dir, resulting_y_dir = a[1] + + if self.check_collision(paddle): + resulting_x_dir, resulting_y_dir = self.get_paddle_collision_dir( + paddle + ) + + # if resulting_x_dir and resulting_y_dir aren't None, then update ball speed + if resulting_x_dir and resulting_y_dir: + print(MINIMUM_ANGLE * math.pi / 180 * 100) + print(math.pi / 2 * 100) + angle = ( + random.randint( + 0, + int( + math.pi / 2 * 100 + - (MINIMUM_ANGLE * math.pi / 180 * 100) + ), + ) + / 100 + ) + + print("angle", angle) + + self.speed["x"] = ( + math.cos(angle) * self.BALLSPEED[0] * resulting_x_dir + ) + self.speed["y"] = ( + math.sin(angle) * self.BALLSPEED[1] * resulting_y_dir + ) + + # escape the collision so as to prevent the "same" collision from being + # handled when collide_paddle is called next time. + while self.check_collision(paddle): + self.move_to( + ( + self.rect.topleft[0] + PROPORTION * self.speed["x"], + self.rect.topleft[1] + PROPORTION * self.speed["y"], + ) + ) + + def trace_collisions(self, paddle): + COLLISIONS_TO_CHECK = 30 # the higher this is, the slower. + + # find how much the ball moved during the past execution + delta_x = self.rect.topleft[0] - self.prev_rect.topleft[0] + delta_y = self.rect.topleft[1] - self.prev_rect.topleft[1] + + # find how much the paddle moved during the past execution + paddle_delta_x = paddle.rect.topleft[0] - paddle.prev_rect.topleft[0] + paddle_delta_y = paddle.rect.topleft[1] - paddle.prev_rect.topleft[1] + + # check COLLISIONS_TO_CHECK times for a collision that occurred during + # the "update game" phase (when we moved the objects) + for i in range(COLLISIONS_TO_CHECK): + # move both the ball and the paddle/player to where they would've been if we + # subdivided the move phase into COLLISIONS_TO_CHECK individual frames + ball_past = Ball(BALL_RADIUS) + ball_past.move_to( + ( + self.prev_rect.topleft[0] + + (delta_x * i / COLLISIONS_TO_CHECK), + self.prev_rect.topleft[1] + + (delta_y * i / COLLISIONS_TO_CHECK), + ) + ) + paddle_past = Player({}) + paddle_past.move_to( + ( + paddle.prev_rect.topleft[0] + + (paddle_delta_x * i / COLLISIONS_TO_CHECK), + paddle.prev_rect.topleft[1] + + (paddle_delta_y * i / COLLISIONS_TO_CHECK), + ) + ) + + # now that we have a ball and a paddle, check if they collided + if ball_past.check_collision(paddle_past): + return (True, ball_past.get_paddle_collision_dir(paddle_past)) + # if the loop failed (didn't return), then + # return False and an empty tuple + return (False, tuple()) + + +class BoundingLine: + DEFAULT_SIZE = 3 + + def __init__(self, parameters): + """ + Replace 'parameters' with real parameters. + This should take: + the starting coordinate of the line + the ending coordinate of the line + the line's name (which should either be + 'top', 'bottom', 'left', or 'right') + (optional) default_size - the size of the line. If not provided, + use DEFAULT_SIZE + This should create the rectangle that stretches from the start + coordinate to the end coordinate with a width or height + (depending on its orientation) of default_size + (or DEFAULT_SIZE if default_size isn't provided) + + Make sure to: + if it is the bottom line: move it DEFAULT_SIZE units up + if it is the right line: move it DEFAULT_SIZE units left + *This is a workaround so that these will show on screen and not + be off-screen + """ + + def draw(self, screen: pygame.Surface, color): + pygame.draw.rect(screen, color, self.rect) + + +class Goal(BoundingLine): + def draw(self, screen: pygame.Surface): + super().draw( + screen, WHITE + ) # the goal should be in white so you can see it + + +class App: + def __init__( + self, flags=RESIZABLE, width=900, height=600, title="My game" + ): + pygame.init() + self.size = [width, height] + self.screen = pygame.display.set_mode(self.size, flags) + pygame.display.set_caption(title, title) + self.running = True + + self.GAMESTATE = 0 + self.WONSTATE = 1 + self.QUITSTATE = 2 + + self.currstate = self.GAMESTATE + self.winning_player = 0 # will be 1 or 2 when a player won + + self.executions = 0 # useful for debugging + + def mainloop(self): + while self.running: + # main game loop (for the game itself) + # because this is a while loop, the game will keep going until someone won + # so we don't need to worry about the post-game text being displayed + if self.currstate == self.GAMESTATE: + for event in pygame.event.get(): + if event.type == QUIT: + # set the variables that are keeping the game running + # to values that won't keep the game running + self.running = False + self.currstate = self.QUITSTATE + else: + self.check_events(event) + self.check_collisions() + self.move_objects() + self.update_display() + pygame.display.update() + time.sleep(0.01) + self.executions += 1 + + if self.currstate == self.WONSTATE: + # 'post-game' game loop (just shows winning text) + for event in pygame.event.get(): + if event.type == QUIT: + self.running = False + self.currstate = self.QUITSTATE + self.display_winning_text() + + if self.currstate == self.QUITSTATE: + pygame.quit() + + def check_events(self, event) -> None: + pass + + def check_collisions(self) -> None: + pass + + def move_objects(self) -> None: + pass + + def update_display(self) -> None: + pass + + def display_winning_text(self) -> None: + pass + + +class Hockey(App): + """ + This is the functional class whose mainloop will be called + to play hockey. + This should inherit from App (where mainloop is defined) + Its methods should utilize the methods within the + game object classes. + """ + + def __init__(self): + super().__init__(title="Hockey!") + + # initialize players + self.player_1 = Player(PLAYER1CONTROLS) + self.player_2 = Player(PLAYER2CONTROLS) + + # move players to starting positions + self.player_1.move_to( + ( + self.size[0] / 8 - self.player_1.rect.width, + self.size[1] / 2 - self.player_1.rect.height, + ) + ) + self.player_2.move_to( + ( + self.size[0] / 8 * 7 - self.player_2.rect.width, + self.size[1] / 2 - self.player_2.rect.height, + ) + ) + + # initialize ball and move it to starting position (center) + self.ball = Ball(BALL_RADIUS) + self.ball.move_to( + ( + self.size[0] / 2 - self.ball.rect.width, + self.size[1] / 2 - self.ball.rect.height, + ) + ) + + # initialize bounding lines - the edges of the screen off which the + # ball should bounce + self.top_line = BoundingLine((0, 0), (self.size[0], 0), "top") + self.bottom_line = BoundingLine( + (0, self.size[1]), (self.size[0], self.size[1]), "bottom" + ) + self.left_line = BoundingLine((0, 0), (0, self.size[1]), "left") + self.right_line = BoundingLine( + (self.size[0], 0), (self.size[0], self.size[1]), "right" + ) + self.bounding_lines = [ + self.top_line, + self.bottom_line, + self.left_line, + self.right_line, + ] + + # initialize Goals + self.goal_1 = Goal( + (0, (self.size[1] / 2) - (5 * self.size[1] / 16)), + (0, (self.size[1] / 2) + (self.size[1] / 16)), + "left", + 3, + ) + self.goal_2 = Goal( + (self.size[0], (self.size[1] / 2) - (5 * self.size[1] / 16)), + (self.size[0], (self.size[1] / 2) + (self.size[1] / 16)), + "right", + 3, + ) + + def update_display(self): + """ + This should fill the screen with BLACK and then + use the draw method of each of the objects to draw them onto + the screen. + """ + pass # your code here + + def move_objects(self): + """ + This should use the move method for the players and the ball + """ + pass # your code here + + def check_events(self): + """ + This should set the path for each of the players before setting + their speed. + """ + pass # your code here + + def check_collisions(self): + """ + This should check whether the ball collided with the goal, the + bounding lines, or a player's paddle + + If the ball collided with a goal, then set self.curstate to + self.WINSTATE, and set self.winner to player 1 if the ball + hit goal 2 or player 2 if the ball hit goal 1 + + Note: use the ball's methods (ie collide_paddle or collide_line) + """ + pass # your code here + + def display_winning_text(self): + self.screen.fill(BLACK) + + self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32) + if self.winning_player != 0: + font_img = self.font.render( + "Game Over. Player %s won" % str(self.winning_player), + True, + WHITE, + ) + else: + # this won't actually be seen, but it prevents "Player 0 won" from showing + # up on the screen for a split-second if QUIT was pressed before someone won + font_img = self.font.render("Nobody won", True, WHITE) + font_rect = font_img.get_rect() + pygame.draw.rect(self.screen, BLACK, font_rect, 1) + self.screen.blit(font_img, font_rect) + pygame.display.update() # show the new text. + + +our_game = Hockey() +our_game.mainloop() diff --git a/games/chapter4/solutions/flappy_bird/.DS_Store b/games/chapter4/solutions/flappy_bird/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/.DS_Store differ diff --git a/games/chapter4/solutions/flappy_bird/OOPflappybird.py b/games/chapter4/solutions/flappy_bird/OOPflappybird.py new file mode 100644 index 00000000..3df29777 --- /dev/null +++ b/games/chapter4/solutions/flappy_bird/OOPflappybird.py @@ -0,0 +1,500 @@ +import pygame +import random + +pygame.init() + +# screen +width = 800 +height = 600 +SIZE = (width, height) +screen = pygame.display.set_mode(SIZE) + +# colors +LGREEN = (62, 245, 59) +DGREEN = (40, 143, 39) +YELLOW = (250, 250, 37) +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) +RED = (255, 0, 0) +LILAC = (175, 95, 237) +LBLUE = (80, 221, 242) +DBLUE = (80, 99, 242) +PINK = (245, 144, 188) +CYAN = (0, 150, 150) + +# images +BACKGROUNDIMG = pygame.image.load("./background.png") +BACKGROUNDIMG = pygame.transform.scale(BACKGROUNDIMG, (width, height)) +SPRITESHEET = pygame.image.load("./flyingbird.png") +COINPIC = pygame.image.load("./coin.png") + +# ---------- States of the Game ---------- +MENUSTATE = 0 # Menu Screen +GAMESTATE = 1 # Play Game +LOSESTATE = 2 # u loose +QUITSTATE = 3 +NUMSTATES = 4 + + +class GameObj: + """ + An abstract class used as the base class for all the + game's objects + """ + + def __init__(self): + """ + This __init__ method provides no functionality. + It merely enables the methods defined in this class. + Thus, calling super().__init__ is unnecessary. + """ + self.rect = pygame.Rect + + def draw( + self, + screen: pygame.Surface, + color: tuple, + specific_rect: pygame.Rect = None, + ): + """ + Draws a rectangle onto the screen in the specified color. + If specific_rect is not None, draw specific_rect onto the screen. + If specific_rect is None, draw self.rect onto the screen. + """ + if not specific_rect: + pygame.draw.rect(screen, color, self.rect) + else: + pygame.draw.rect(screen, color, specific_rect) + + def move(self, speed: dict = None, specific_rect: pygame.Rect = None): + """ + Moves a rectangle. + @param speed - The speed to move the rectangle at. It should be + a dictionary of form {'x': int, 'y': int}; for example, + {'x':33, 'y':-22}. If no speed is provided, uses self.speed + @param specific_rect - if specific_rect is None, then this method + will move self.rect. If specific_rect is not None, then this method will + move specific_rect + """ + if not speed and hasattr(self, "speed"): + if specific_rect: + return specific_rect.move(self.speed["x"], self.speed["y"]) + else: + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + if speed: + if specific_rect: + return specific_rect.move(speed["x"], speed["y"]) + else: + self.rect = self.rect.move(speed["x"], speed["y"]) + + def check_collision(self, other, specific_rect: pygame.Rect = None): + if not specific_rect: + return self.rect.colliderect(other.rect) == 1 + else: + return specific_rect.colliderect(other.rect) == 1 + + +class Tubes(GameObj): + """ + Class to represent the two tubes. + + Ex: + The tubes will look sort of like the below drawing + (one on the top, one on the bottom) + (let - be top or bottom of school) + ------------ + | | + |_| + + _ + | | + | | + | | + ------------ + """ + + TUBEGAP = 230 # smaller TUBEGAP -> smaller dist between tubes + TUBEWIDTH = 100 + + def __init__(self, bottom_tube_height: int): + """ + Initializes two pygame.Rect objects: one for the + top tube (call it top_tube) and one for the bottom tube + (call it bottom_tube). Uses the TUBEGAP + and TUBEWIDTH variables as dimensions. + """ + self.bottom_tube = pygame.Rect( + width, + height - bottom_tube_height, + self.TUBEWIDTH, + bottom_tube_height, + ) + self.top_tube = pygame.Rect( + width, + 0, + self.TUBEWIDTH, + height - bottom_tube_height - self.TUBEGAP, + ) + + def draw(self, screen: pygame.Surface): + """ + Uses the draw() method from the inherited + GameObj class to draw the top and bottom tubes. + Hint: this will use the specific_rect argument + """ + super().draw(screen, DGREEN, self.bottom_tube) + super().draw(screen, DGREEN, self.top_tube) + + def move(self, speed: dict): + """ + Uses the move() method from the inherited + GameObj class to move the specific top and bottom tubes. + Hint: this will use the specific_rect argument + """ + self.bottom_tube = super().move(speed, self.bottom_tube) + self.top_tube = super().move(speed, self.top_tube) + + def check_collision(self, other) -> bool: + """ + Uses the check_collision() method from the inherited + GameObj class to check for any collisions + between the given object and the tubes. + Hint: this will use the specific_rect argument + + Returns: + boolean - if either tube is collided with, return True + """ + bottom = super().check_collision(other, self.bottom_tube) + top = super().check_collision(other, self.top_tube) + return bottom or top + + +class Coin(GameObj): + """ + The coin that the bird will get + in-between tubes. + Doesn't need to do anything, so pretty short class. + """ + + def __init__(self, center_y): + """ + Makes a coin object. + The coin's x coordinate will be the width of the screen + The coin's y coordinate will be centered around `center_y` + @param center_y:int - the y coordinate to center the coin around + """ + temprect = COINPIC.get_rect() + self.rect = pygame.Rect( + width, + center_y - temprect.height // 2, + temprect.width, + temprect.height, + ) + + def draw(self, screen: pygame.Surface): + super().draw(screen, BLACK) + + def blit(self, screen: pygame.Surface): + screen.blit(COINPIC, self.rect) + + +class Bird(GameObj): + """ + The bird itself. It processes the sprites + and handles jumping. + """ + + start_center_pos = (width // 8, height // 2) + + def __init__(self): + self.process_spritesheet(SPRITESHEET, 3, 3) + self.rect = pygame.Rect( + self.start_center_pos[0] - self.sprite_frame_width // 2, + self.start_center_pos[1] - self.sprite_frame_height // 2, + self.sprite_frame_width, + self.sprite_frame_height, + ) + self.momentum = 0 # the bird's current vertical speed + self.jump_height = 15 + self.min_speed = -10 # the maximum speed the bird flies down at + self.cur_sprite_idx = 0 + + def process_spritesheet( + self, + spritesheet: pygame.Surface, + num_pics_x: int, + num_pics_y: int, + offset_x: int = 0, + offset_y: int = 0, + ): + """ + Creates sprites from the spritesheet. + @param spritesheet: pygame.Surface - the spritesheet. + @param num_pics_x: int - the number of sprites in each row on + the spritesheet + @param num_pics_y: int - the number of sprites in each column + on the spritesheet + @param offset_x: int - the x offset before the sprite rows start + @param offset_y: int - the y offset before the sprite columns start + """ + self.sprites = [] + self.sprite_frame_width = ( + spritesheet.get_width() - offset_x + ) // num_pics_x + self.sprite_frame_height = ( + spritesheet.get_height() - offset_y + ) // num_pics_y + for row in range(num_pics_x): + for column in range(num_pics_y): + temp = spritesheet.subsurface( + ( + row * self.sprite_frame_width + offset_x, + column * self.sprite_frame_height + offset_y, + self.sprite_frame_width, + self.sprite_frame_height, + ) + ) + # get the bounding box for the actual colored pixels + # (so that we won't be blit-ing extra empty pixels) + # (makes collisions more accurate) + temprect = temp.get_bounding_rect() + # then, append the shortened image to the sprites list + self.sprites.append(temp.subsurface(temprect)) + + def draw(self, screen: pygame.Surface, framecount: int): + curr_sprite_idx = framecount // 5 % len(self.sprites) + if curr_sprite_idx != self.cur_sprite_idx: + # if it is now a different sprite, adjust self.rect + # so that it won't be bigger or smaller than the new sprite + self.cur_sprite_idx = curr_sprite_idx + temp = self.sprites[self.cur_sprite_idx] + self.rect = temp.get_rect().move( + self.rect.topleft[0], self.rect.topleft[1] + ) + super().draw(screen, BLACK) + + def blit(self, screen: pygame.Surface): + screen.blit(self.sprites[self.cur_sprite_idx], self.rect) + + def process_movement(self, event): + if event.type == pygame.KEYDOWN and event.key == pygame.K_UP: + self.momentum = self.jump_height + + def move(self): + super().move({"x": 0, "y": -self.momentum}) + self.momentum -= 1 + + # if the bird would fly down faster than self.min_speed, + # cap self.momentum at self.min_speed + if self.momentum < self.min_speed: + self.momentum = self.min_speed + + +class Button(GameObj): + """ + A Button with text. Used for the + 'Quit Game' 'Start Game' and 'Retry' buttons + """ + + def __init__( + self, + center_x: int, + center_y: int, + bgcolor: tuple, + textcolor: tuple, + text: str = "", + textsize: int = 32, + ): + """ + Creates a Button object. + @param center_x: int - the x coordinate of the button's center + @param center_y: int - the y coordinate of the button's center + @param bgcolor: tuple - the color for the button's background + @param textcolor: tuple - the color for the button's text + @param text: str - the text to put inside the button + @param textsize: int - the size of the button's text + """ + self.font = pygame.font.SysFont("arial", textsize) + self.font_img = self.font.render(text, True, textcolor) + self.rect = self.font_img.get_rect() + self.rect.center = (center_x, center_y) + self.bgcolor = bgcolor + self.active = True + + def draw(self, screen: pygame.Surface): + super().draw(screen, self.bgcolor) + screen.blit(self.font_img, self.rect) + + def is_clicked(self, event: pygame.event.Event): + if event.type == pygame.MOUSEBUTTONDOWN: + return event.pos in self + + def __contains__(self, coordinate): + return self.rect.contains((coordinate[0], coordinate[1], 0, 0)) + + +class FlappyBird: + """ + This is the game class. + """ + + def __init__(self): + self.running = True + self.gamestate = MENUSTATE + self.create_buttons() + self.framecount = 0 + self.clock = pygame.time.Clock() + + def create_buttons(self, button1text="Start Game", button2bg=RED): + self.buttons = { + "start": Button( + width // 2, height // 4, LGREEN, LILAC, button1text + ), + "quit": Button( + width // 2, height // 4 * 3, button2bg, LILAC, "Quit Game" + ), + } + + def mainloop(self): + while self.running: + events = pygame.event.get() + for event in events: + self.set_state(event) + if event.type == pygame.QUIT: + self.running = False + if self.gamestate == MENUSTATE: + screen.fill(LBLUE) + self.draw_buttons(screen) + + elif self.gamestate == GAMESTATE: + self.draw_all() + self.check_collisions() + + # update bird's speed + for event in events: + self.bird.process_movement(event) + self.moveobjects() + + self.create_tubes() + + elif self.gamestate == LOSESTATE: + screen.fill(RED) + self.draw_buttons(screen) + + elif self.gamestate == QUITSTATE: + self.running = False + + self.framecount += 1 + pygame.display.update() + self.clock.tick(60) + pygame.quit() + + def set_state(self, event): + if event.type == pygame.MOUSEBUTTONDOWN: + if all([but.active for but in self.buttons.values()]): + if self.buttons["start"].is_clicked(event): + self.buttons["start"].active = False + self.buttons["quit"].active = False + self.startgame() + elif self.buttons["quit"].is_clicked(event): + self.gamestate = QUITSTATE + self.buttons["start"].active = False + self.buttons["quit"].active = False + + def draw_background(self): + screen.blit(BACKGROUNDIMG, (self.background_x, 0)) + screen.blit(BACKGROUNDIMG, (self.background_x + width, 0)) + self.background_x -= 2 + if self.background_x < -1 * width: + self.background_x = 0 + + def draw_score(self): + """ + Writes the player's score onto the screen in the top + right corner. + Hint: this uses pygame fonts + """ + font = pygame.font.SysFont("arial", 32) + font_img = font.render(f"Score : {self.score}", True, WHITE) + screen.blit(font_img, font_img.get_rect().move(width - 200, 0)) + + def draw_buttons(self, screen): + """ + Draws the "start" and "quit" buttons onto the screen. + Hint: this uses `self.buttons` (which is already made) + """ + self.buttons["start"].draw(screen) + self.buttons["quit"].draw(screen) + + def create_tubes(self): + """ + Creates tubes and puts a coin in the middle of each tube. + """ + if ( + len(self.tubes) == 0 + or self.tubes[-1].bottom_tube.right < width - 200 + ): + bottom_tube_height = random.randint(0, height - Tubes.TUBEGAP) + self.tubes.append(Tubes(bottom_tube_height)) + self.coins.append( + Coin(height - bottom_tube_height - (Tubes.TUBEGAP // 2)) + ) + + def draw_all(self): + # draw bird and coin rectangles before background so that they won't + # show + self.bird.draw(screen, self.framecount) + for coin in self.coins: + coin.draw(screen) + + self.draw_background() + + for tube in self.tubes: + tube.draw(screen) + + # blit images/sprites onto the screen + self.bird.blit(screen) + for coin in self.coins: + coin.blit(screen) + + self.draw_score() + + def check_collisions(self): + for tube in self.tubes: + if tube.check_collision(self.bird): + self.gamestate = LOSESTATE + self.create_buttons("Retry?", LBLUE) + if tube.bottom_tube.right < 0: + self.tubes.remove(tube) + + for coin in self.coins: + if self.bird.check_collision(coin): + self.score += 1 + self.coins.remove(coin) + if coin.rect.right < 0: + self.coins.remove(coin) + + if self.bird.rect.bottom > height: # fell out of screen + self.gamestate = LOSESTATE + self.create_buttons("Retry?", LBLUE) + + def moveobjects(self): + SPEED = 3 # x speed that objects move towards the bird at + for tube in self.tubes: + tube.move({"x": -SPEED, "y": 0}) + for coin in self.coins: + coin.move({"x": -SPEED, "y": 0}) + self.bird.move() + + def startgame(self): + self.gamestate = GAMESTATE + self.background_x = 0 + self.score = 0 + self.bird = Bird() + self.tubes = [] + self.coins = [] + self.create_tubes() + + +a = FlappyBird() +a.mainloop() diff --git a/games/chapter4/solutions/flappy_bird/background.png b/games/chapter4/solutions/flappy_bird/background.png new file mode 100644 index 00000000..0be8c233 Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/background.png differ diff --git a/games/chapter4/solutions/flappy_bird/bird.png b/games/chapter4/solutions/flappy_bird/bird.png new file mode 100644 index 00000000..2b8a5b32 Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/bird.png differ diff --git a/games/chapter4/solutions/flappy_bird/coin.png b/games/chapter4/solutions/flappy_bird/coin.png new file mode 100644 index 00000000..dc457bff Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/coin.png differ diff --git a/games/chapter4/solutions/flappy_bird/explosion_transparent.png b/games/chapter4/solutions/flappy_bird/explosion_transparent.png new file mode 100644 index 00000000..a92d7099 Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/explosion_transparent.png differ diff --git a/games/chapter4/solutions/flappy_bird/flyingbird.png b/games/chapter4/solutions/flappy_bird/flyingbird.png new file mode 100644 index 00000000..25bd824a Binary files /dev/null and b/games/chapter4/solutions/flappy_bird/flyingbird.png differ diff --git a/games/chapter4/solutions/hockey.py b/games/chapter4/solutions/hockey.py new file mode 100644 index 00000000..ab4dd66f --- /dev/null +++ b/games/chapter4/solutions/hockey.py @@ -0,0 +1,606 @@ +# Make a two-player hockey game! The application will consist +# of two rectangular paddles, starting on each side of the screen, +# and one circular ball that players must bounce around. Players can +# move the paddles in any direction to hit the ball into the goal. +# If the ball makes contact with safe parts of the screen, it will +# bounce off at a random angle but in the same general direction +# (left or right). It will do the same if it makes contact with one +# of the paddles, but will head towards the opposite general direction +# instead. If the ball touches the goals on either side of the screen, +# the application will say “Game Over. Player _ Wins”. You must put +# your code in classes and have separate keys for each player to +# move their paddles. + + +import pygame +from pygame.locals import ( + K_w, + K_s, + K_a, + K_d, + K_UP, + K_DOWN, + K_LEFT, + K_RIGHT, + KEYDOWN, + KEYUP, + QUIT, + RESIZABLE, +) +from pygame.rect import Rect + +import math +import time +import random + +# define the necessary color constants using rgb values +BLACK = (0, 0, 0) +GREEN = (0, 120, 0) +RED = (120, 0, 0) +WHITE = (255, 255, 255) + +# define player controls +PLAYER1CONTROLS = {"up": K_w, "down": K_s, "left": K_a, "right": K_d} +PLAYER2CONTROLS = { + "up": K_UP, + "down": K_DOWN, + "left": K_LEFT, + "right": K_RIGHT, +} + +# initial screensize +SCREENSIZE = [900, 600] + +# how big the ball's radius will be +BALL_RADIUS = 3 + + +class Game_obj: + def __init__(self): + # we don't want to pass actual values to the Rect class since + # we want this class to be abstract + self.rect = Rect + self.prev_rect = Rect + self.speed = {"x": 0, "y": 0} + + def move(self): + self.prev_rect = self.rect + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + + def move_to(self, position: tuple): + self.prev_rect = self.rect + self.rect = self.rect.move( + position[0] - self.rect.topleft[0], + position[1] - self.rect.topleft[1], + ) + + def check_collision(self, other) -> bool: + return self.rect.colliderect(other.rect) == 1 + + +class Player(Game_obj): + PLAYERSPEED = (3, 3) + PADDLESIZE = (10, 50) # x width, y width + + def __init__(self, control_keys: dict) -> None: + """ + Creates a player rectangle with the provided control keys. + Arguments: + control_keys - (dict) should be a dictionary of the following format: + { + "up": (KEY) (ex: K_w), + "down": (KEY) (ex: K_s), + "left": (KEY) (ex: K_a), + "right": (KEY) (ex: K_d) + } + """ + super().__init__() + self.control_keys = control_keys + self.rect = Rect(0, 0, self.PADDLESIZE[0], self.PADDLESIZE[1]) + self.path = [0, 0] + + def draw(self, screen: pygame.Surface): + pygame.draw.rect(screen, GREEN, self.rect) + + def set_path(self, event): + self.key_checker(event, "up") + self.key_checker(event, "down") + self.key_checker(event, "left") + self.key_checker(event, "right") + + def key_checker(self, event: pygame.event.Event, direction: str) -> None: + """ + Helper function to deal with event keys. Sets self.path + according to PATH_VALUES + Arguments: + event(pygame.event.Event) - the event + direction(str) - the direction to check KEYDOWN and KEYUP for. + """ + PATH_VALUES = {"up": 1, "down": 1, "left": 0, "right": 0} + DIRECTION_VALUES = {"up": -1, "down": 1, "left": -1, "right": 1} + + # if the event doesn't have a key attribute, just return + if not hasattr(event, "key"): + return + + # if it does, then check if it the right key + if event.key == self.control_keys[direction]: + if event.type == KEYUP: + self.path[PATH_VALUES[direction]] += -DIRECTION_VALUES[ + direction + ] + if event.type == KEYDOWN: + self.path[PATH_VALUES[direction]] += DIRECTION_VALUES[ + direction + ] + + def set_speed(self) -> None: + """ + Sets the speed according to the Player object's path. + This should be called after self.path has been set. + """ + self.speed["x"] = ( + self.path[0] + / math.sqrt(sum(abs(num) for num in self.path)) + * self.PLAYERSPEED[0] + if (sum(abs(num) for num in self.path)) != 0 + else self.path[0] * self.PLAYERSPEED[0] + ) + self.speed["y"] = ( + self.path[1] + / math.sqrt(sum(abs(num) for num in self.path)) + * self.PLAYERSPEED[1] + if (sum(abs(num) for num in self.path)) != 0 + else self.path[1] * self.PLAYERSPEED[1] + ) + + +class Ball(Game_obj): + BALLSPEED = (6, 6) + + def __init__(self, radius: int) -> None: + super().__init__() + self.rect = Rect(0, 0, radius * 2, radius * 2) + self.radius = radius + + # set up initial speed + initial_ang = random.randint(1, int(math.pi / 2 * 100)) / 100 + self.speed["x"] = ( + math.cos(initial_ang) + * self.BALLSPEED[0] + * (-1 if random.randint(0, 1) == 0 else 1) + ) + self.speed["y"] = ( + math.sin(initial_ang) + * self.BALLSPEED[1] + * (-1 if random.randint(0, 1) == 0 else 1) + ) + + def draw(self, screen: pygame.Surface): + pygame.draw.circle( + screen, WHITE, center=self.rect.center, radius=self.radius + ) + + def collide_line(self, other) -> bool: + """ + Checks if the ball has hit a line. + If it did, update the speed accordingly + + Arguments: + other (BoundingLine or Goal) - the line to check for a collision with + Returns: + True - if the collision happened + False - if the collision didn't happen + """ + if self.check_collision(other): + if other.name == "top" or other.name == "bottom": + self.speed["y"] = -self.speed["y"] + return True + elif other.name == "left" or other.name == "right": + self.speed["x"] = -self.speed["x"] + return True + return False + + def get_obj_path(self, object: Game_obj) -> tuple: + if object.speed["x"] > 0: + x_path = 1 + elif object.speed["x"] < 0: + x_path = -1 + else: + x_path = 0 + + if object.speed["y"] > 0: + y_path = 1 + elif object.speed["y"] < 0: + y_path = -1 + else: + y_path = 0 + + return (x_path, y_path) + + def get_paddle_collision_dir(self, paddle: Player) -> tuple: + """ + Gets the direction in which the ball will be headed + after a collision with a paddle. + Does not actually check if the collision happened + + Arguments: + paddle (Player) - the player that the ball 'collided with' + Returns: + tuple(int, int) - a tuple of length 2 with just +-1's + ex: (1, 1) or (1, -1) or (-1, 1), or (-1, -1) + It corresponds to the direction in which the ball + will be headed. The first item will be the x direction + and the second item will be the y direction. + """ + paddle_x_dir, paddle_y_dir = self.get_obj_path(paddle) + + ball_x_dir, ball_y_dir = self.get_obj_path(self) + + resulting_x_dir = None + resulting_y_dir = None + + if paddle.speed["x"] == 0 or paddle_x_dir == ball_x_dir: + if abs(paddle.speed["x"]) > abs(self.speed["x"]): + resulting_x_dir = paddle_x_dir + elif abs(paddle.speed["x"]) < abs(self.speed["x"]): + resulting_x_dir = -ball_x_dir + else: + resulting_x_dir = -ball_x_dir + + if paddle.speed["y"] == 0 or paddle_y_dir == ball_y_dir: + if abs(paddle.speed["y"]) > abs(self.speed["y"]): + resulting_y_dir = paddle_y_dir + elif abs(paddle.speed["y"]) < abs(self.speed["y"]): + resulting_y_dir = -ball_y_dir + else: + resulting_y_dir = -ball_y_dir + + return (resulting_x_dir, resulting_y_dir) + + def collide_paddle(self, paddle: Player, executions: int) -> None: + """ + Handles collisions with paddles. + Arguments: + paddle(Player) - the paddle to check for a collision with + executions(int) - the amount of executions of the game's mainloop + It's not important, but it prevents unwanted collisions during + the 0th execution when we first set up the game by moving + the objects to the right place + """ + PROPORTION = 0.25 # used when "escaping" a collision + MINIMUM_ANGLE = ( + 30 # this is in degrees; it's just a fine-tuning aspect + ) + # that makes the game more realistic + + resulting_x_dir = None + resulting_y_dir = None + + a = self.trace_collisions(paddle) + if a[0] and executions != 0: + resulting_x_dir, resulting_y_dir = a[1] + + if self.check_collision(paddle): + resulting_x_dir, resulting_y_dir = self.get_paddle_collision_dir( + paddle + ) + + # if resulting_x_dir and resulting_y_dir aren't None, then update ball speed + if resulting_x_dir and resulting_y_dir: + print(MINIMUM_ANGLE * math.pi / 180 * 100) + print(math.pi / 2 * 100) + angle = ( + random.randint( + 0, + int( + math.pi / 2 * 100 + - (MINIMUM_ANGLE * math.pi / 180 * 100) + ), + ) + / 100 + ) + + print("angle", angle) + + self.speed["x"] = ( + math.cos(angle) * self.BALLSPEED[0] * resulting_x_dir + ) + self.speed["y"] = ( + math.sin(angle) * self.BALLSPEED[1] * resulting_y_dir + ) + + # escape the collision so as to prevent the "same" collision from being + # handled when collide_paddle is called next time. + while self.check_collision(paddle): + self.move_to( + ( + self.rect.topleft[0] + PROPORTION * self.speed["x"], + self.rect.topleft[1] + PROPORTION * self.speed["y"], + ) + ) + + def trace_collisions(self, paddle): + COLLISIONS_TO_CHECK = 30 # the higher this is, the slower. + + # find how much the ball moved during the past execution + delta_x = self.rect.topleft[0] - self.prev_rect.topleft[0] + delta_y = self.rect.topleft[1] - self.prev_rect.topleft[1] + + # find how much the paddle moved during the past execution + paddle_delta_x = paddle.rect.topleft[0] - paddle.prev_rect.topleft[0] + paddle_delta_y = paddle.rect.topleft[1] - paddle.prev_rect.topleft[1] + + # check COLLISIONS_TO_CHECK times for a collision that occurred during + # the "update game" phase (when we moved the objects) + for i in range(COLLISIONS_TO_CHECK): + # move both the ball and the paddle/player to where they would've been if we + # subdivided the move phase into COLLISIONS_TO_CHECK individual frames + ball_past = Ball(BALL_RADIUS) + ball_past.move_to( + ( + self.prev_rect.topleft[0] + + (delta_x * i / COLLISIONS_TO_CHECK), + self.prev_rect.topleft[1] + + (delta_y * i / COLLISIONS_TO_CHECK), + ) + ) + paddle_past = Player({}) + paddle_past.move_to( + ( + paddle.prev_rect.topleft[0] + + (paddle_delta_x * i / COLLISIONS_TO_CHECK), + paddle.prev_rect.topleft[1] + + (paddle_delta_y * i / COLLISIONS_TO_CHECK), + ) + ) + + # now that we have a ball and a paddle, check if they collided + if ball_past.check_collision(paddle_past): + return (True, ball_past.get_paddle_collision_dir(paddle_past)) + # if the loop failed (didn't return), then + # return False and an empty tuple + return (False, tuple()) + + +class BoundingLine: + DEFAULT_SIZE = 3 + + def __init__( + self, + start_coord: tuple, + end_coord: tuple, + name: str, + default_size=None, + ): + if default_size: # if a default size is provided + self.DEFAULT_SIZE = default_size + + self.start_coord = start_coord # starting coordinate, normally topleft + self.end_coord = end_coord # ending coordinate, normally bottomright + + self.rect = Rect( + start_coord[0], + start_coord[1], + end_coord[0] + if end_coord[0] - start_coord[0] != 0 + else self.DEFAULT_SIZE, + end_coord[1] + if end_coord[1] - start_coord[1] != 0 + else self.DEFAULT_SIZE, + ) + + self.name = name # this comes in handy in Ball's collide_line method + + # small fix for bottom and right bounding lines getting drawn outside of + # the screen + if self.name == "bottom" and self.rect.bottom > SCREENSIZE[1]: + self.rect = self.rect.move(0, -3) + elif self.name == "right" and self.rect.right > SCREENSIZE[0]: + self.rect = self.rect.move(-3, 0) + + def draw(self, screen: pygame.Surface, color=RED): + pygame.draw.rect(screen, color, self.rect) + + +class Goal(BoundingLine): + def draw(self, screen: pygame.Surface): + super().draw( + screen, WHITE + ) # the goal should be in white so you can see it + + +class App: + def __init__( + self, flags=RESIZABLE, width=900, height=600, title="My game" + ): + pygame.init() + self.size = [width, height] + self.screen = pygame.display.set_mode(self.size, flags) + pygame.display.set_caption(title, title) + self.running = True + + self.GAMESTATE = 0 + self.WONSTATE = 1 + self.QUITSTATE = 2 + + self.currstate = self.GAMESTATE + self.winning_player = 0 # will be 1 or 2 when a player won + + self.executions = 0 # useful for debugging + + def mainloop(self): + while self.running: + # main game loop (for the game itself) + # because this is a while loop, the game will keep going until someone won + # so we don't need to worry about the post-game text being displayed + if self.currstate == self.GAMESTATE: + for event in pygame.event.get(): + if event.type == QUIT: + # set the variables that are keeping the game running + # to values that won't keep the game running + self.running = False + self.currstate = self.QUITSTATE + else: + self.check_events(event) + self.check_collisions() + self.move_objects() + self.update_display() + pygame.display.update() + time.sleep(0.01) + self.executions += 1 + + if self.currstate == self.WONSTATE: + # 'post-game' game loop (just shows winning text) + for event in pygame.event.get(): + if event.type == QUIT: + self.running = False + self.currstate = self.QUITSTATE + self.display_winning_text() + + if self.currstate == self.QUITSTATE: + pygame.quit() + + def check_events(self, event) -> None: + pass + + def check_collisions(self) -> None: + pass + + def move_objects(self) -> None: + pass + + def update_display(self) -> None: + pass + + def display_winning_text(self) -> None: + pass + + +class Hockey(App): + def __init__(self): + super().__init__(title="Hockey!") + + # initialize players + self.player_1 = Player(PLAYER1CONTROLS) + self.player_2 = Player(PLAYER2CONTROLS) + + # move players to starting positions + self.player_1.move_to( + ( + self.size[0] / 8 - self.player_1.rect.width, + self.size[1] / 2 - self.player_1.rect.height, + ) + ) + self.player_2.move_to( + ( + self.size[0] / 8 * 7 - self.player_2.rect.width, + self.size[1] / 2 - self.player_2.rect.height, + ) + ) + + # initialize ball and move it to starting position (center) + self.ball = Ball(BALL_RADIUS) + self.ball.move_to( + ( + self.size[0] / 2 - self.ball.rect.width, + self.size[1] / 2 - self.ball.rect.height, + ) + ) + + # initialize bounding lines - the edges of the screen off which the + # ball should bounce + self.top_line = BoundingLine((0, 0), (self.size[0], 0), "top") + self.bottom_line = BoundingLine( + (0, self.size[1]), (self.size[0], self.size[1]), "bottom" + ) + self.left_line = BoundingLine((0, 0), (0, self.size[1]), "left") + self.right_line = BoundingLine( + (self.size[0], 0), (self.size[0], self.size[1]), "right" + ) + self.bounding_lines = [ + self.top_line, + self.bottom_line, + self.left_line, + self.right_line, + ] + + # initialize Goals + self.goal_1 = Goal( + (0, (self.size[1] / 2) - (5 * self.size[1] / 16)), + (0, (self.size[1] / 2) + (self.size[1] / 16)), + "left", + 3, + ) + self.goal_2 = Goal( + (self.size[0], (self.size[1] / 2) - (5 * self.size[1] / 16)), + (self.size[0], (self.size[1] / 2) + (self.size[1] / 16)), + "right", + 3, + ) + + def update_display(self) -> None: + self.screen.fill(BLACK) # fill the screen with black to clear it + + # draw main objects + self.player_1.draw(self.screen) + self.player_2.draw(self.screen) + self.ball.draw(self.screen) + + # draw bounding lines + self.top_line.draw(self.screen) + self.bottom_line.draw(self.screen) + self.left_line.draw(self.screen) + self.right_line.draw(self.screen) + + # draw goals + self.goal_1.draw(self.screen) + self.goal_2.draw(self.screen) + + def move_objects(self) -> None: + self.player_1.move() + self.player_2.move() + self.ball.move() + + def check_events(self, event) -> None: + self.player_1.set_path(event) + self.player_2.set_path(event) + + self.player_1.set_speed() + self.player_2.set_speed() + + def check_collisions(self) -> None: + for line in self.bounding_lines: + self.ball.collide_line(line) + self.ball.collide_paddle(self.player_1, self.executions) + self.ball.collide_paddle(self.player_2, self.executions) + + if self.ball.collide_line(self.goal_1): + self.currstate = self.WONSTATE + self.winning_player = 2 # player 2 (right player) scored + elif self.ball.collide_line(self.goal_2): + self.currstate = self.WONSTATE + self.winning_player = 1 # player 1 (left player) scored + + def display_winning_text(self) -> None: + self.screen.fill(BLACK) + + self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32) + if self.winning_player != 0: + font_img = self.font.render( + "Game Over. Player %s won" % str(self.winning_player), + True, + WHITE, + ) + else: + # this won't actually be seen, but it prevents "Player 0 won" from showing + # up on the screen for a split-second if QUIT was pressed before someone won + font_img = self.font.render("Nobody won", True, WHITE) + font_rect = font_img.get_rect() + pygame.draw.rect(self.screen, BLACK, font_rect, 1) + self.screen.blit(font_img, font_rect) + pygame.display.update() # show the new text. + + +our_game = Hockey() +our_game.mainloop() diff --git a/games/chapter5/examples/bounce.wav b/games/chapter5/examples/bounce.wav new file mode 100644 index 00000000..44f62bce Binary files /dev/null and b/games/chapter5/examples/bounce.wav differ diff --git a/games/chapter5/examples/bouncing_rect.py b/games/chapter5/examples/bouncing_rect.py new file mode 100644 index 00000000..f838b0b5 --- /dev/null +++ b/games/chapter5/examples/bouncing_rect.py @@ -0,0 +1,47 @@ +import pygame +import time # not necessary, but used for frame cap + +pygame.init() # initialize pygame module + +bounce_sound = pygame.mixer.Sound("./bounce.wav") + +SCREEN_SIZE = (600, 400) +RECT_SIZE = (100, 100) +RED = (255, 0, 0) +BLACK = (0, 0, 0) +momentum = [1, 1] # (down and right) + +window = pygame.display.set_mode(SCREEN_SIZE) +running = True + +# start the rectangle in the middle of the screen +x = SCREEN_SIZE[0] // 2 +y = SCREEN_SIZE[1] // 2 + +while running: + # if the rectangle collided with the left or right side + # of the screen + if x + RECT_SIZE[0] >= SCREEN_SIZE[0] or x <= 0: + momentum[0] = -momentum[0] + bounce_sound.play() # add in the bounce sound for collisions + # if the rectangle collided with the top or bottom + # of the screen + if y + RECT_SIZE[1] >= SCREEN_SIZE[1] or y <= 0: + momentum[1] = -momentum[1] + bounce_sound.play() # add in the bounce sound for collisions + + # add the speed to the current x and y to get the + # new x and y + x += momentum[0] + y += momentum[1] + + window.fill(BLACK) # 'erase' the previous frame + pygame.draw.rect(window, RED, (x, y, RECT_SIZE[0], RECT_SIZE[1])) + pygame.display.update() # update the display + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + time.sleep(0.01) # frame cap to make the rectangle more visible + +pygame.quit() # deactivate the pygame module diff --git a/games/chapter5/practice/TankIcon.png b/games/chapter5/practice/TankIcon.png new file mode 100644 index 00000000..d9e00bbe Binary files /dev/null and b/games/chapter5/practice/TankIcon.png differ diff --git a/games/chapter5/practice/Target.png b/games/chapter5/practice/Target.png new file mode 100644 index 00000000..d48bc78e Binary files /dev/null and b/games/chapter5/practice/Target.png differ diff --git a/games/chapter5/practice/bullet.png b/games/chapter5/practice/bullet.png new file mode 100644 index 00000000..fce8c161 Binary files /dev/null and b/games/chapter5/practice/bullet.png differ diff --git a/games/chapter5/practice/completetank.png b/games/chapter5/practice/completetank.png new file mode 100644 index 00000000..036cd84b Binary files /dev/null and b/games/chapter5/practice/completetank.png differ diff --git a/games/chapter5/practice/explosion.wav b/games/chapter5/practice/explosion.wav new file mode 100644 index 00000000..c33f0412 Binary files /dev/null and b/games/chapter5/practice/explosion.wav differ diff --git a/games/chapter5/practice/fire.wav b/games/chapter5/practice/fire.wav new file mode 100644 index 00000000..17a60459 Binary files /dev/null and b/games/chapter5/practice/fire.wav differ diff --git a/games/chapter5/practice/simple_bg.wav b/games/chapter5/practice/simple_bg.wav new file mode 100644 index 00000000..b09f4e25 Binary files /dev/null and b/games/chapter5/practice/simple_bg.wav differ diff --git a/games/chapter5/practice/sound_tanks.py b/games/chapter5/practice/sound_tanks.py new file mode 100644 index 00000000..e33e7e6a --- /dev/null +++ b/games/chapter5/practice/sound_tanks.py @@ -0,0 +1,468 @@ +# Edit the code from the tank game so that it will: +# play an explosion sound when a target is hit or run over +# play a gunshot sound when a bullet is "fired" (created) +# play background music during the whole game. + +# you can either use the provided .wav files or find +# your own (responsibly, of course). + +# Note: +# because the pygame.init is inside App's init method, we don't put +# sounds here. instead, we make the sounds and the music inside Tankgame's +# init method. + + +import pygame + +from pygame.locals import ( + K_w, + K_s, + K_a, + K_d, + KEYDOWN, + KEYUP, + QUIT, + RESIZABLE, + MOUSEBUTTONDOWN, +) +import time +import math +import random + +BULLET_IMG_PATH = "./bullet.png" +TARGET_IMG_PATH = "./target.png" +TANK_IMG_PATH = "./completetank.png" + +BLACK = (255, 255, 255) +DIRTBROWN = (168, 95, 0) +SANDBROWN = (237, 201, 175) + +TANKSPEED = [2, 2] # speed x and speed y +BULLETSPEED = [8, 8] + + +class Game_obj: + def __init__(self, picture: str, **kwargs) -> None: + """ + A basic game object class. It handles collisions, + the basic drawing method, the move and moveto methods, + and the check_out_of_screen method. + + Arguments: + picture:str - the location of the picture that will be displayed on + the screen for this object + Valid keyword arguments: + "size":tuple(x,y) - a specific size that you want to have the object be. + The picture will be scaled to that size and the hitbox + will be updated accordingly. + "position":tuple(x,y) - the tuple at which the top left of the object + should be positioned at + "speed":tuple(x,y) - the tuple that represents the object's speed. + """ + self.name = "" + + # self.image will be a pygame.Surface class + self.image = pygame.image.load(picture) + self.image = ( + pygame.transform.scale( + self.image, (kwargs["size"][0], kwargs["size"][1]) + ) + if "size" in kwargs + else self.image + ) + + self.rect = ( + self.image.get_rect() + ) # self.rect will be of pygame.Rect class + self.size = self.rect.size # will be a tuple of (sizex, sizey) + + if "position" in kwargs: + self.moveto(kwargs["position"]) + + self.speed = ( + {"x": kwargs["speed"][0], "y": kwargs["speed"][1]} + if "speed" in kwargs + else {"x": 0, "y": 0} + ) + + def check_collision(self, other: object) -> bool: + if not isinstance(other, Game_obj): + raise TypeError( + "Invalid type; need a game_obj or a child class of game_obj" + ) + # the rect class's colliderect method returns 1 if there is + # a collision and 0 if there isn't a collision + return self.rect.colliderect(other.rect) == 1 + + def draw(self, screen: pygame.Surface, color: tuple) -> None: + pygame.draw.rect(screen, color, self.rect, 0) + screen.blit(self.image, self.rect) + + def move(self) -> None: + """ + Moves the object according to it's current speed. + """ + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + # self.draw(screen, color) + + def set_speed(self, new_speed: tuple) -> None: + """ + Sets the object's speed to the provided tuple + Arguments: + new_speed (tuple(x,y)) - a tuple containing the desired speed for + the object to have. + """ + self.speed["x"], self.speed["y"] = new_speed[0], new_speed[1] + + def moveto(self, position: tuple) -> None: + """ + A helper function that moves the rectangle to the desired position. + + Arguments: + position (tuple) - the x and y coordinates of where you want the rectangle's + top left to be moved to. + """ + self.rect = self.rect.move( + position[0] - self.rect.topleft[0], + position[1] - self.rect.topleft[1], + ) + + def check_out_of_screen(self, screen_size: tuple) -> bool: + """ + Checks whether or not the object is completely outside of the screen. + Returns True or False accordingly. + Arguments: + screen_size (tuple) - the size of the screen (x,y) + """ + if ( + self.rect.bottom > screen_size[1] + or self.rect.top < 0 + or self.rect.left < 0 + or self.rect.right > screen_size[0] + ): + return True + return False + + def __str__(self): + return ( + f"{self.name} object located at the position {self.rect.topleft}" + ) + + +class Bullet(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(BULLET_IMG_PATH, **kwargs) + self.name = "Bullet" + + +class Target(Game_obj): + def __init__(self, **kwargs) -> None: + kwargs["size"] = 40, 40 + super().__init__(TARGET_IMG_PATH, **kwargs) + self.name = "Target" + + +class Tank(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(TANK_IMG_PATH, **kwargs) + self.direction = [0, 0] + self.SPEED = kwargs["speed"] if "speed" in kwargs else [2, 2] + self.speed["x"], self.speed["y"] = 0, 0 + + def set_speed(self) -> None: + # use math stuff to calculate the speed given that the + # max speed is self.SPEED + self.speed["x"] = ( + self.direction[0] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[0] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[0] * self.SPEED[0] + ) + self.speed["y"] = ( + self.direction[1] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[1] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[1] * self.SPEED[1] + ) + + def set_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] -= 1 + if direction == "down": + self.direction[1] += 1 + if direction == "left": + self.direction[0] -= 1 + if direction == "right": + self.direction[0] += 1 + + def unset_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] += 1 + if direction == "down": + self.direction[1] -= 1 + if direction == "left": + self.direction[0] += 1 + if direction == "right": + self.direction[0] -= 1 + + +class App: + """ + The abstract base class for the actual Tank_game class. It's + main purpose is to define a structure for the game. + It's structure is as follows: + Upon initialization, it runs the create_objects method + It's mainloop is comprised of the following methods: + check_events + check_collisions + move_objects + update_display + """ + + def __init__( + self, flags=RESIZABLE, width=960, height=540, title="My Game" + ): + pygame.init() + self.size = [width, height] + self.screen = pygame.display.set_mode(self.size, flags) + pygame.display.set_caption(title, title) + + self.running = True + + self.create_objects() + + def create_objects(self): + """ + This should create the initial objects on the screen. + """ + pass + + def check_events(self, event): + """ + This should take user input and handle it appropriately. + """ + pass + + def update_display(self): + """ + This should utilize clear the screen and then draw + all current objects onto the screen. + """ + pass + + def move_objects(self): + """ + This should utilize the move method that the game objects have. + """ + pass + + def check_collisions(self): + """ + This should utilize the check_collision method that the game objects + have. + """ + pass + + def mainloop(self): + while self.running: + for event in pygame.event.get(): + if event.type == QUIT: + self.running = False + break + else: + self.check_events( + event + ) # this will handle checking for user input + # such as KEYUP and MOUSEBUTTONDOWN events needed to run the game + self.check_collisions() # checks collisions between bullet/tank and targets + self.move_objects() # moves each object on the screen + self.update_display() # redraws updated objects onto the screen + pygame.display.update() # pygame’s method to show the updated screen + time.sleep(0.01) # not necessary; it's a frame cap + pygame.quit() + + +class Tank_Game(App): + def __init__(self): + # this can be changed, it's the number of targets allowed at a time. + # we initialize this before super().__init__ because super().__init__ calls + # create_objects, which utilizes self.NUM_TARGETS + self.NUM_TARGETS = 3 + + super().__init__(title="Tanks") + + self.playerscore = 0 # the player's score + + # sets the display icon to the TankIcon.png provided + pygame.display.set_icon(pygame.image.load("./TankIcon.png")) + + def create_objects(self): + """ + This creates the initial objects seen when the game + first starts up. + """ + # tank + self.tank = Tank(speed=TANKSPEED) + self.tank.moveto( + ( + self.size[0] / 2 - self.tank.size[0], # move to middle x + self.size[1] - self.tank.size[1], # move to bottom y + ) + ) + + # targets + self.targets = [Target(speed=[0, 0]) for i in range(self.NUM_TARGETS)] + for target in self.targets: + target.moveto( + ( + random.randint( + 0, self.size[0] - target.size[0] + ), # random x + random.randint( + 0, self.size[1] - target.size[1] + ), # random y + ) + ) + + # bullets + self.bullets = [] + + # Score text + self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32) + + def check_events(self, event): + """ + We imported all from pygame.locals, so that means + that we can check KEYDOWN and KEYUP and individual + keys such as K_w (w key), K_a (a key), etc. + """ + # change the path of the tank if w, a, s, or d was pressed + if event.type == KEYDOWN: + if event.key == K_w: + self.tank.set_path("up") + if event.key == K_s: + self.tank.set_path("down") + if event.key == K_a: + self.tank.set_path("left") + if event.key == K_d: + self.tank.set_path("right") + if event.type == KEYUP: + if event.key == K_w: + self.tank.unset_path("up") + if event.key == K_s: + self.tank.unset_path("down") + if event.key == K_a: + self.tank.unset_path("left") + if event.key == K_d: + self.tank.unset_path("right") + self.tank.set_speed() + + # create bullets if mouse button was pressed + if event.type == MOUSEBUTTONDOWN: + bul = Bullet(speed=BULLETSPEED) + bul.moveto( + (self.tank.rect.centerx, (self.tank.rect.top - bul.size[1])) + ) # move the bullet to the front of the tank + + # math stuff to calculate trajectory + mouse_pos = pygame.mouse.get_pos() + h = mouse_pos[1] - bul.rect.center[1] + w = mouse_pos[0] - bul.rect.center[0] + hyp = math.sqrt(h**2 + w**2) + vertical_speed = ( + BULLETSPEED[1] * (h / hyp) if hyp != 0 else BULLETSPEED[1] * h + ) + horizontal_speed = ( + BULLETSPEED[0] * (w / hyp) if hyp != 0 else BULLETSPEED[0] * w + ) + + bul.set_speed((horizontal_speed, vertical_speed)) + self.bullets.append(bul) + + def move_objects(self): + """ + This method moves the objects within the game. + If a bullet is outside of the screen, it is + not moved and is unreferenced. + """ + self.tank.move() + + self.bullets = [ + bullet + for bullet in self.bullets + if bullet.check_out_of_screen(self.size) is False + ] + + for bullet in self.bullets: + bullet.move() + + def check_collisions(self): + """ + This checks whether any of the objects within the game have collided + with each other. Specifically, we are looking for collisions between + bullets and targets or the tank and targets + """ + deletions = 0 # number of targets deleted + num_bullets = len(self.bullets) + + # check bullet-target collisions + for i in range(num_bullets): + for target in self.targets: + # if the bullet collided with the target + if self.bullets[i - deletions].check_collision(target) is True: + # pop both the bullet and target so that they will be + # effectively deleted + self.bullets.pop(i - deletions) + self.targets.pop(self.targets.index(target)) + + # give points for hitting the target + self.playerscore += 20 + deletions += 1 + break # stop the current iteration since the target and + # bullet are popped, so referencing them would error. + + # check tank-target collisions + for target in self.targets: + if self.tank.check_collision(target) is True: + self.targets.pop(self.targets.index(target)) + deletions += 1 + self.playerscore += 10 # only 10 for running over targets lol + + # create a new target for every deleted target + for i in range(deletions): + a = Target(speed=[0, 0]) + a.moveto( + ( + random.randint(0, self.size[0] - a.size[0]), + random.randint(0, self.size[1] - a.size[1]), + ) + ) + self.targets.append(a) + + def update_display(self): + self.screen.fill(SANDBROWN) + + # tank + self.tank.draw(self.screen, SANDBROWN) + + # targets + for target in self.targets: + target.draw(self.screen, BLACK) + + # bullets + for bullet in self.bullets: + bullet.draw(self.screen, BLACK) + + # score text + font_img = self.font.render( + "Score: %s" % str(self.playerscore), True, BLACK + ) + font_rect = font_img.get_rect() + pygame.draw.rect(self.screen, SANDBROWN, font_rect, 1) + self.screen.blit(font_img, font_rect) + + +game = Tank_Game() +game.mainloop() diff --git a/games/chapter5/solutions/TankIcon.png b/games/chapter5/solutions/TankIcon.png new file mode 100644 index 00000000..d9e00bbe Binary files /dev/null and b/games/chapter5/solutions/TankIcon.png differ diff --git a/games/chapter5/solutions/Target.png b/games/chapter5/solutions/Target.png new file mode 100644 index 00000000..d48bc78e Binary files /dev/null and b/games/chapter5/solutions/Target.png differ diff --git a/games/chapter5/solutions/bullet.png b/games/chapter5/solutions/bullet.png new file mode 100644 index 00000000..fce8c161 Binary files /dev/null and b/games/chapter5/solutions/bullet.png differ diff --git a/games/chapter5/solutions/completetank.png b/games/chapter5/solutions/completetank.png new file mode 100644 index 00000000..036cd84b Binary files /dev/null and b/games/chapter5/solutions/completetank.png differ diff --git a/games/chapter5/solutions/explosion.wav b/games/chapter5/solutions/explosion.wav new file mode 100644 index 00000000..c33f0412 Binary files /dev/null and b/games/chapter5/solutions/explosion.wav differ diff --git a/games/chapter5/solutions/fire.wav b/games/chapter5/solutions/fire.wav new file mode 100644 index 00000000..17a60459 Binary files /dev/null and b/games/chapter5/solutions/fire.wav differ diff --git a/games/chapter5/solutions/simple_bg.wav b/games/chapter5/solutions/simple_bg.wav new file mode 100644 index 00000000..b09f4e25 Binary files /dev/null and b/games/chapter5/solutions/simple_bg.wav differ diff --git a/games/chapter5/solutions/sound_tanks.py b/games/chapter5/solutions/sound_tanks.py new file mode 100644 index 00000000..051e181c --- /dev/null +++ b/games/chapter5/solutions/sound_tanks.py @@ -0,0 +1,483 @@ +# Edit the code from the tank game so that it will: +# play an explosion sound when a target is hit or run over +# play a gunshot sound when a bullet is "fired" (created) +# play background music during the whole game. + +# you can either use the provided .wav files or find +# your own (responsibly, of course). + +# Note: +# because the pygame.init is inside App's init method, we don't put +# sounds here. instead, we make the sounds and the music inside Tankgame's +# init method. + + +import pygame + +from pygame.locals import ( + K_w, + K_s, + K_a, + K_d, + KEYDOWN, + KEYUP, + QUIT, + RESIZABLE, + MOUSEBUTTONDOWN, +) +import time +import math +import random + +BULLET_IMG_PATH = "./bullet.png" +TARGET_IMG_PATH = "./target.png" +TANK_IMG_PATH = "./completetank.png" + +BLACK = (255, 255, 255) +DIRTBROWN = (168, 95, 0) +SANDBROWN = (237, 201, 175) + +TANKSPEED = [2, 2] # speed x and speed y +BULLETSPEED = [8, 8] + + +class Game_obj: + def __init__(self, picture: str, **kwargs) -> None: + """ + A basic game object class. It handles collisions, + the basic drawing method, the move and moveto methods, + and the check_out_of_screen method. + + Arguments: + picture:str - the location of the picture that will be displayed on + the screen for this object + Valid keyword arguments: + "size":tuple(x,y) - a specific size that you want to have the object be. + The picture will be scaled to that size and the hitbox + will be updated accordingly. + "position":tuple(x,y) - the tuple at which the top left of the object + should be positioned at + "speed":tuple(x,y) - the tuple that represents the object's speed. + """ + self.name = "" + + # self.image will be a pygame.Surface class + self.image = pygame.image.load(picture) + self.image = ( + pygame.transform.scale( + self.image, (kwargs["size"][0], kwargs["size"][1]) + ) + if "size" in kwargs + else self.image + ) + + self.rect = ( + self.image.get_rect() + ) # self.rect will be of pygame.Rect class + self.size = self.rect.size # will be a tuple of (sizex, sizey) + + if "position" in kwargs: + self.moveto(kwargs["position"]) + + self.speed = ( + {"x": kwargs["speed"][0], "y": kwargs["speed"][1]} + if "speed" in kwargs + else {"x": 0, "y": 0} + ) + + def check_collision(self, other: object) -> bool: + if not isinstance(other, Game_obj): + raise TypeError( + "Invalid type; need a game_obj or a child class of game_obj" + ) + # the rect class's colliderect method returns 1 if there is + # a collision and 0 if there isn't a collision + return self.rect.colliderect(other.rect) == 1 + + def draw(self, screen: pygame.Surface, color: tuple) -> None: + pygame.draw.rect(screen, color, self.rect, 0) + screen.blit(self.image, self.rect) + + def move(self) -> None: + """ + Moves the object according to it's current speed. + """ + self.rect = self.rect.move(self.speed["x"], self.speed["y"]) + # self.draw(screen, color) + + def set_speed(self, new_speed: tuple) -> None: + """ + Sets the object's speed to the provided tuple + Arguments: + new_speed (tuple(x,y)) - a tuple containing the desired speed for + the object to have. + """ + self.speed["x"], self.speed["y"] = new_speed[0], new_speed[1] + + def moveto(self, position: tuple) -> None: + """ + A helper function that moves the rectangle to the desired position. + + Arguments: + position (tuple) - the x and y coordinates of where you want the rectangle's + top left to be moved to. + """ + self.rect = self.rect.move( + position[0] - self.rect.topleft[0], + position[1] - self.rect.topleft[1], + ) + + def check_out_of_screen(self, screen_size: tuple) -> bool: + """ + Checks whether or not the object is completely outside of the screen. + Returns True or False accordingly. + Arguments: + screen_size (tuple) - the size of the screen (x,y) + """ + if ( + self.rect.bottom > screen_size[1] + or self.rect.top < 0 + or self.rect.left < 0 + or self.rect.right > screen_size[0] + ): + return True + return False + + def __str__(self): + return ( + f"{self.name} object located at the position {self.rect.topleft}" + ) + + +class Bullet(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(BULLET_IMG_PATH, **kwargs) + self.name = "Bullet" + + +class Target(Game_obj): + def __init__(self, **kwargs) -> None: + kwargs["size"] = 40, 40 + super().__init__(TARGET_IMG_PATH, **kwargs) + self.name = "Target" + + +class Tank(Game_obj): + def __init__(self, **kwargs) -> None: + super().__init__(TANK_IMG_PATH, **kwargs) + self.direction = [0, 0] + self.SPEED = kwargs["speed"] if "speed" in kwargs else [2, 2] + self.speed["x"], self.speed["y"] = 0, 0 + + def set_speed(self) -> None: + # use math stuff to calculate the speed given that the + # max speed is self.SPEED + self.speed["x"] = ( + self.direction[0] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[0] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[0] * self.SPEED[0] + ) + self.speed["y"] = ( + self.direction[1] + / math.sqrt(sum(abs(num) for num in self.direction)) + * self.SPEED[1] + if (sum(abs(num) for num in self.direction)) != 0 + else self.direction[1] * self.SPEED[1] + ) + + def set_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] -= 1 + if direction == "down": + self.direction[1] += 1 + if direction == "left": + self.direction[0] -= 1 + if direction == "right": + self.direction[0] += 1 + + def unset_path(self, direction: str) -> None: + if direction == "up": + self.direction[1] += 1 + if direction == "down": + self.direction[1] -= 1 + if direction == "left": + self.direction[0] += 1 + if direction == "right": + self.direction[0] -= 1 + + +class App: + """ + The abstract base class for the actual Tank_game class. It's + main purpose is to define a structure for the game. + It's structure is as follows: + Upon initialization, it runs the create_objects method + It's mainloop is comprised of the following methods: + check_events + check_collisions + move_objects + update_display + """ + + def __init__( + self, flags=RESIZABLE, width=960, height=540, title="My Game" + ): + pygame.init() + self.size = [width, height] + self.screen = pygame.display.set_mode(self.size, flags) + pygame.display.set_caption(title, title) + + self.running = True + + self.create_objects() + + def create_objects(self): + """ + This should create the initial objects on the screen. + """ + pass + + def check_events(self, event): + """ + This should take user input and handle it appropriately. + """ + pass + + def update_display(self): + """ + This should utilize clear the screen and then draw + all current objects onto the screen. + """ + pass + + def move_objects(self): + """ + This should utilize the move method that the game objects have. + """ + pass + + def check_collisions(self): + """ + This should utilize the check_collision method that the game objects + have. + """ + pass + + def mainloop(self): + while self.running: + for event in pygame.event.get(): + if event.type == QUIT: + self.running = False + break + else: + self.check_events( + event + ) # this will handle checking for user input + # such as KEYUP and MOUSEBUTTONDOWN events needed to run the game + self.check_collisions() # checks collisions between bullet/tank and targets + self.move_objects() # moves each object on the screen + self.update_display() # redraws updated objects onto the screen + pygame.display.update() # pygame’s method to show the updated screen + time.sleep(0.01) # not necessary; it's a frame cap + pygame.quit() + + +class Tank_Game(App): + def __init__(self): + # this can be changed, it's the number of targets allowed at a time. + # we initialize this before super().__init__ because super().__init__ calls + # create_objects, which utilizes self.NUM_TARGETS + self.NUM_TARGETS = 3 + + super().__init__(title="Tanks") + + self.playerscore = 0 # the player's score + + # sets the display icon to the TankIcon.png provided + pygame.display.set_icon(pygame.image.load("./TankIcon.png")) + + # set up the sounds + self.fire_sound = pygame.mixer.Sound("./fire.wav") + self.explosion_sound = pygame.mixer.Sound("./explosion.wav") + + # load the music and play it + pygame.mixer.music.load("./simple_bg.wav") + pygame.mixer.music.play(-1) + + def create_objects(self): + """ + This creates the initial objects seen when the game + first starts up. + """ + # tank + self.tank = Tank(speed=TANKSPEED) + self.tank.moveto( + ( + self.size[0] / 2 - self.tank.size[0], # move to middle x + self.size[1] - self.tank.size[1], # move to bottom y + ) + ) + + # targets + self.targets = [Target(speed=[0, 0]) for i in range(self.NUM_TARGETS)] + for target in self.targets: + target.moveto( + ( + random.randint( + 0, self.size[0] - target.size[0] + ), # random x + random.randint( + 0, self.size[1] - target.size[1] + ), # random y + ) + ) + + # bullets + self.bullets = [] + + # Score text + self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32) + + def check_events(self, event): + """ + We imported all from pygame.locals, so that means + that we can check KEYDOWN and KEYUP and individual + keys such as K_w (w key), K_a (a key), etc. + """ + # change the path of the tank if w, a, s, or d was pressed + if event.type == KEYDOWN: + if event.key == K_w: + self.tank.set_path("up") + if event.key == K_s: + self.tank.set_path("down") + if event.key == K_a: + self.tank.set_path("left") + if event.key == K_d: + self.tank.set_path("right") + if event.type == KEYUP: + if event.key == K_w: + self.tank.unset_path("up") + if event.key == K_s: + self.tank.unset_path("down") + if event.key == K_a: + self.tank.unset_path("left") + if event.key == K_d: + self.tank.unset_path("right") + self.tank.set_speed() + + # create bullets if mouse button was pressed + if event.type == MOUSEBUTTONDOWN: + bul = Bullet(speed=BULLETSPEED) + bul.moveto( + (self.tank.rect.centerx, (self.tank.rect.top - bul.size[1])) + ) # move the bullet to the front of the tank + + # math stuff to calculate trajectory + mouse_pos = pygame.mouse.get_pos() + h = mouse_pos[1] - bul.rect.center[1] + w = mouse_pos[0] - bul.rect.center[0] + hyp = math.sqrt(h**2 + w**2) + vertical_speed = ( + BULLETSPEED[1] * (h / hyp) if hyp != 0 else BULLETSPEED[1] * h + ) + horizontal_speed = ( + BULLETSPEED[0] * (w / hyp) if hyp != 0 else BULLETSPEED[0] * w + ) + + bul.set_speed((horizontal_speed, vertical_speed)) + self.bullets.append(bul) + + # play the bullet fired sound since a new bullet was fired + self.fire_sound.play() + + def move_objects(self): + """ + This method moves the objects within the game. + If a bullet is outside of the screen, it is + not moved and is unreferenced. + """ + self.tank.move() + + self.bullets = [ + bullet + for bullet in self.bullets + if bullet.check_out_of_screen(self.size) is False + ] + + for bullet in self.bullets: + bullet.move() + + def check_collisions(self): + """ + This checks whether any of the objects within the game have collided + with each other. Specifically, we are looking for collisions between + bullets and targets or the tank and targets + """ + deletions = 0 # number of targets deleted + num_bullets = len(self.bullets) + + # check bullet-target collisions + for i in range(num_bullets): + for target in self.targets: + # if the bullet collided with the target + if self.bullets[i - deletions].check_collision(target) is True: + # pop both the bullet and target so that they will be + # effectively deleted + self.bullets.pop(i - deletions) + self.targets.pop(self.targets.index(target)) + + # give points for hitting the target + self.playerscore += 20 + deletions += 1 + + self.explosion_sound.play() # play the explosion sound + + break # stop the current iteration since the target and + # bullet are popped, so referencing them would error. + + # check tank-target collisions + for target in self.targets: + if self.tank.check_collision(target) is True: + self.targets.pop(self.targets.index(target)) + deletions += 1 + self.playerscore += 10 # only 10 for running over targets lol + self.explosion_sound.play() + + # create a new target for every deleted target + for i in range(deletions): + a = Target(speed=[0, 0]) + a.moveto( + ( + random.randint(0, self.size[0] - a.size[0]), + random.randint(0, self.size[1] - a.size[1]), + ) + ) + self.targets.append(a) + + def update_display(self): + self.screen.fill(SANDBROWN) + + # tank + self.tank.draw(self.screen, SANDBROWN) + + # targets + for target in self.targets: + target.draw(self.screen, BLACK) + + # bullets + for bullet in self.bullets: + bullet.draw(self.screen, BLACK) + + # score text + font_img = self.font.render( + "Score: %s" % str(self.playerscore), True, BLACK + ) + font_rect = font_img.get_rect() + pygame.draw.rect(self.screen, SANDBROWN, font_rect, 1) + self.screen.blit(font_img, font_rect) + + +game = Tank_Game() +game.mainloop()