diff --git a/Chapter 1.zip b/Chapter 1.zip deleted file mode 100644 index bbe2f2f..0000000 Binary files a/Chapter 1.zip and /dev/null differ diff --git a/Chapter 10.zip b/Chapter 10.zip deleted file mode 100644 index 16f7e5d..0000000 Binary files a/Chapter 10.zip and /dev/null differ diff --git a/Chapter 11.zip b/Chapter 11.zip deleted file mode 100644 index 09d6a2f..0000000 Binary files a/Chapter 11.zip and /dev/null differ diff --git a/Chapter 2.zip b/Chapter 2.zip deleted file mode 100644 index 0705a4e..0000000 Binary files a/Chapter 2.zip and /dev/null differ diff --git a/Chapter 3.zip b/Chapter 3.zip deleted file mode 100644 index ae3f786..0000000 Binary files a/Chapter 3.zip and /dev/null differ diff --git a/Chapter 4.zip b/Chapter 4.zip deleted file mode 100644 index 135170f..0000000 Binary files a/Chapter 4.zip and /dev/null differ diff --git a/Chapter 5.zip b/Chapter 5.zip deleted file mode 100644 index 8f73bc8..0000000 Binary files a/Chapter 5.zip and /dev/null differ diff --git a/Chapter 6.zip b/Chapter 6.zip deleted file mode 100644 index 6742135..0000000 Binary files a/Chapter 6.zip and /dev/null differ diff --git a/Chapter 7.zip b/Chapter 7.zip deleted file mode 100644 index 166b8c8..0000000 Binary files a/Chapter 7.zip and /dev/null differ diff --git a/Chapter 8.zip b/Chapter 8.zip deleted file mode 100644 index 447d60c..0000000 Binary files a/Chapter 8.zip and /dev/null differ diff --git a/Chapter 9.zip b/Chapter 9.zip deleted file mode 100644 index 6ef9a7f..0000000 Binary files a/Chapter 9.zip and /dev/null differ diff --git a/Chapter_01/Listing 1_1.py b/Chapter_01/Listing 1_1.py new file mode 100644 index 0000000..3a24432 --- /dev/null +++ b/Chapter_01/Listing 1_1.py @@ -0,0 +1,19 @@ +"""Hello world! in Python and pygame.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +# Annotate variable +screen: pygame.Surface + +# Display "Hello world!" as the window title. +screen = pygame.display.set_mode((400, 200)) +pygame.display.set_caption("Hello world!") + +# Display "Hello world!" in the console. +print("Hello world!") + +# Quit when the user presses enter. +input("Press enter to quit...") +pygame.quit() diff --git a/Chapter_01/Listing 1_10.py b/Chapter_01/Listing 1_10.py new file mode 100644 index 0000000..43325c5 --- /dev/null +++ b/Chapter_01/Listing 1_10.py @@ -0,0 +1,18 @@ +"""Calculate the angle between stars in an aerial firework.""" + +import random + +# Annotate variables +DEGREES_CIRCLE: int = 360 +num_stars: int +angle: float + +# Randomly generate a number of stars. +num_stars = random.randint(2, 360) + +# Calculate the angle between the stars. +angle = DEGREES_CIRCLE / num_stars + +# Tell the user the angle between stars. +print("You will need an angle of " + str(angle) + + "° between " + str(num_stars) + " stars.") diff --git a/Chapter_01/Listing 1_11.py b/Chapter_01/Listing 1_11.py new file mode 100644 index 0000000..89ec33b --- /dev/null +++ b/Chapter_01/Listing 1_11.py @@ -0,0 +1,24 @@ +"""Play a number guessing game.""" + +import random + +# Annotate variables +number: int +guess: int + +# Randomly generate a number between 1 and 50. +number = random.randint(1, 50) + +# Set up the game. +print("I'm thinking of a number between 1 and 50.") + +# Obtain the user's guess until they guess correctly. +guess = 0 +while guess != number: + guess = int(input("What is your guess? ")) + if guess < number: + print("Higher...") + elif guess > number: + print("Lower...") + else: + print("You got it!") diff --git a/Chapter_01/Listing 1_12.py b/Chapter_01/Listing 1_12.py new file mode 100644 index 0000000..51d1986 --- /dev/null +++ b/Chapter_01/Listing 1_12.py @@ -0,0 +1,45 @@ +"""Draw firework stars with pygame.""" + +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define some size constants to make drawing easier. +PI_OVER_4: float = math.radians(45) +RADIUS: int = 120 +SIZE: int = 480 +CENTER: float = (SIZE - 40) / 2 + +# Annotate variables +screen: pygame.Surface +star: pygame.Surface +star_num: int +x: float +y: float + +# Create a pygame window. +screen = pygame.display.set_mode((SIZE, SIZE)) +pygame.display.set_caption("Stars!") + +# Load the star image. +star = pygame.image.load("star.png") + +# Draw eight stars at 45° angles. +star_num = 0 +while star_num < 8: + # Compute the x and y coordinates of a star. + star_num = star_num + 1 + x = CENTER + RADIUS * math.cos(star_num * PI_OVER_4) + y = CENTER + RADIUS * math.sin(star_num * PI_OVER_4) + + # Add the star to the drawing. + screen.blit(star, (x,y)) + +# Show the drawing. +pygame.display.flip() + +# Quit when the user presses enter. +input("Press enter to quit...") +pygame.quit() diff --git a/Chapter_01/Listing 1_13.py b/Chapter_01/Listing 1_13.py new file mode 100644 index 0000000..a9b5e28 --- /dev/null +++ b/Chapter_01/Listing 1_13.py @@ -0,0 +1,15 @@ +"""Validate fireworks input.""" + +# Annotate variables +num_stars: int + +# Obtain the number of stars from the user. +num_stars = int(input("How many stars? ")) + +# Check that the value is between 2 and 360. +while num_stars < 2 or num_stars > 360: + print("The number must be between 2 and 360.") + num_stars = int(input("How many stars? ")) + +# The input is now valid, print a message. +print("Your input is valid.") diff --git a/Chapter_01/Listing 1_14.py b/Chapter_01/Listing 1_14.py new file mode 100644 index 0000000..a1ce8ef --- /dev/null +++ b/Chapter_01/Listing 1_14.py @@ -0,0 +1,35 @@ +"""Manage a library summer reading program.""" + +# Annotate and initialize variables. +max_minutes: int = -1 +total_minutes: int = 0 +max_name: str = None +instructions: str +num_children: int +name: str +num_minutes: int + +# Display instructions to the user. +instructions = "Enter each child's name and the number of " +instructions += "minutes they read this week." +print(instructions) + +# Obtain information for five children. +num_children = 0 +while num_children < 5: + name = input("What is the child's name? ") + num_minutes = int(input("How many minutes did " + + name + " read this week? ")) + # Find the max so far and total. + if num_minutes > max_minutes: + max_minutes = num_minutes + max_name = name + total_minutes += num_minutes + # Update the counter. + num_children += 1 + +# Display a summary of reading information. +print(max_name + " is our star reader, with a total of " + + str(max_minutes) + " minutes.") +print("The total number of minutes read by all of the" + + " children was " + str(total_minutes) + ".") diff --git a/Chapter_01/Listing 1_15.py b/Chapter_01/Listing 1_15.py new file mode 100644 index 0000000..a401483 --- /dev/null +++ b/Chapter_01/Listing 1_15.py @@ -0,0 +1,30 @@ +"""Manage a library summer reading program.""" + +# Annotate and initialize variables. +max_minutes: int = -1 +total_minutes: int = 0 +max_name: str = None +name: str +num_minutes: int + +# Display instructions to the user. +print("Enter each child's name and the number of minutes ") +print("they read this week. Enter 'quit' when done.") + +# Obtain child information until done. +name = input("Enter a child's name or 'quit': ") +while name != "quit": + num_minutes = int(input("How many minutes did " + + name + " read this week? ")) + if num_minutes > max_minutes: + max_minutes = num_minutes + max_name = name + total_minutes += num_minutes + name = input("Enter a child's name or 'quit': ") + +# Display a summary of reading information. +if total_minutes != 0: + print("The child who read the most is " + max_name + + ", who read " + str(max_minutes) + " minutes.") + print("The total number of minutes read was " + + str(total_minutes)) diff --git a/Chapter_01/Listing 1_16.py b/Chapter_01/Listing 1_16.py new file mode 100644 index 0000000..07e46b0 --- /dev/null +++ b/Chapter_01/Listing 1_16.py @@ -0,0 +1,12 @@ +"""Manage a library summer reading program.""" + +# Annotate variables +num_children: int +name: str +num_minutes: int + +# Obtain information for five children. +for num_children in range(5): + name = input("What is the child's name? ") + num_minutes = int(input("How many minutes did " + + name + " read this week? ")) diff --git a/Chapter_01/Listing 1_17.py b/Chapter_01/Listing 1_17.py new file mode 100644 index 0000000..fee9971 --- /dev/null +++ b/Chapter_01/Listing 1_17.py @@ -0,0 +1,45 @@ +"""Draw firework stars with pygame.""" + +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define some size constants to make drawing easier. +PI_OVER_4: float = math.radians(45) +RADIUS: int = 120 +SIZE: int = 480 +CENTER: float = (SIZE - 40) / 2 + +# Annotate variables +screen: pygame.Surface +star: pygame.Surface +star_num: int +x: float +y: float + +# Create a pygame window. +screen = pygame.display.set_mode((SIZE, SIZE)) +pygame.display.set_caption("Stars!") + +# Load the star image. +star = pygame.image.load("star.png") + +# Draw eight stars at 45° angles. +for star_num in range(8): + # Compute the x and y coordinates of a star. + x = CENTER + RADIUS * math.cos(star_num * PI_OVER_4) + y = CENTER + RADIUS * math.sin(star_num * PI_OVER_4) + + # Add the star to the drawing. + screen.blit(star, (x,y)) + +# Show the drawing. +pygame.display.flip() + +# Quit when the user presses enter. +input("Press enter to quit...") +pygame.quit() + + diff --git a/Chapter_01/Listing 1_18.py b/Chapter_01/Listing 1_18.py new file mode 100644 index 0000000..5a9bab8 --- /dev/null +++ b/Chapter_01/Listing 1_18.py @@ -0,0 +1,51 @@ +"""Draw firework stars with pygame.""" + +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define some size constants to make drawing easier. +PI_OVER_4: float = math.radians(45) +RADIUS: int = 120 +SIZE: int = 480 +CENTER: float = (SIZE - 40) / 2 + +# Annotate variables +screen: pygame.Surface +star: pygame.Surface +star_num: int +x: float +y: float +user_quit: bool +event: pygame.event.Event + +# Create a pygame window. +screen = pygame.display.set_mode((SIZE, SIZE)) +pygame.display.set_caption("Stars!") + +# Load the star image. +star = pygame.image.load("star.png") + +# Draw eight stars at 45° angles. +for star_num in range(8): + # Compute the x and y coordinates of a star. + x = CENTER + RADIUS * math.cos(star_num * PI_OVER_4) + y = CENTER + RADIUS * math.sin(star_num * PI_OVER_4) + + # Add the star to the drawing. + screen.blit(star, (x,y)) + +# Show the drawing. +pygame.display.flip() + +# Quit when the user closes the window. +user_quit = False +while not user_quit: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + +pygame.quit() + diff --git a/Chapter_01/Listing 1_19.py b/Chapter_01/Listing 1_19.py new file mode 100644 index 0000000..15aacc7 --- /dev/null +++ b/Chapter_01/Listing 1_19.py @@ -0,0 +1,45 @@ +"""Draw a star at a random location for each user click.""" + +import random + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define constants and annotate variables +SIZE: int = 480 +screen: pygame.Surface +star: pygame.Surface +offset_w: float +offset_h: float +user_quit: bool +event: pygame.event.Event +x: int +y: int + +# Create a pygame window. +screen = pygame.display.set_mode((SIZE, SIZE)) +pygame.display.set_caption("Click to make a star!") + +# Load the star image. +star = pygame.image.load("star.png") +offset_w = star.get_width() / 2 +offset_h = star.get_height() / 2 + +# Draw a star for each click. +user_quit = False +while not user_quit: + for event in pygame.event.get(): + # Process a quit choice. + if event.type == pygame.QUIT: + user_quit = True + # Process a click by drawing a star. + elif event.type == pygame.MOUSEBUTTONUP: + x = random.randint(0,SIZE) - offset_w + y = random.randint(0,SIZE) - offset_h + screen.blit(star, (x,y)) + # Show the drawing. + pygame.display.flip() + +pygame.quit() + diff --git a/Chapter_01/Listing 1_2.py b/Chapter_01/Listing 1_2.py new file mode 100644 index 0000000..0beb1b4 --- /dev/null +++ b/Chapter_01/Listing 1_2.py @@ -0,0 +1,16 @@ +"""Calculate the angle between stars in an aerial firework.""" + +# Annotate variables +DEGREES_CIRCLE: int = 360 +num_stars: int +angle: float + +# Obtain the number of stars from the user. +num_stars = int(input("How many stars? ")) + +# Calculate the angle between the stars. +angle = DEGREES_CIRCLE / num_stars + +# Tell the user the angle between stars. +print("You will need an angle of " + str(angle) + + "\u00B0 between each star.") diff --git a/Chapter_01/Listing 1_3.py b/Chapter_01/Listing 1_3.py new file mode 100644 index 0000000..e141ae0 --- /dev/null +++ b/Chapter_01/Listing 1_3.py @@ -0,0 +1,36 @@ +"""Demonstrate common mathematical equations + using the math library.""" + +import math + +# Declare some constants using the math library. +PI_OVER_6: float = math.radians(30) +PI_OVER_3: float = math.pi / 3 +RADIUS: int = 100 + +# Annotate variables +x: float +y: float +distance: float +y_floor: int +y_ceil: int +y_round: int + +# Compute the x and y coordinates of a star. +x = RADIUS * math.cos(PI_OVER_6) +y = RADIUS * math.sin(PI_OVER_6) + +# Compute the distance from origin to the star. +distance = math.sqrt(pow(x,2) + pow(y,2)) + +# Find the integers above and below the y coordinate. +y_floor = math.floor(y) +y_ceil = math.ceil(y) +y_round = round(y) + +# Print the computed values. +print("x = " + str(x) + "\ny = " + str(y) + + "\ndistance = " + str(distance) + + "\ny floored = " + str(y_floor) + + "\ny ceilinged = " + str(y_ceil) + + "\ny rounded = " + str(y_round)) diff --git a/Chapter_01/Listing 1_4.py b/Chapter_01/Listing 1_4.py new file mode 100644 index 0000000..b9134dc --- /dev/null +++ b/Chapter_01/Listing 1_4.py @@ -0,0 +1,45 @@ +"""Draw firework stars with pygame.""" + +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define some size constants to make drawing easier. +PI_OVER_4: float = math.radians(45) +RADIUS: int = 120 +SIZE: int = 480 +CENTER: float = (SIZE - 40) / 2 + +# Annotate variables +screen: pygame.Surface +star: pygame.Surface +x: float +y: float + +# Create a pygame window. +screen = pygame.display.set_mode((SIZE, SIZE)) +pygame.display.set_caption("Stars!") + +# Compute the x and y coordinates of a star. +x = CENTER + RADIUS * math.cos(PI_OVER_4) +y = CENTER + RADIUS * math.sin(PI_OVER_4) + +# Add the star to the drawing. +star = pygame.image.load("star.png") +screen.blit(star, (x,y)) + +# Compute the x and y coordinates of the next star. +x = CENTER + RADIUS * math.cos(2 * PI_OVER_4) +y = CENTER + RADIUS * math.sin(2 * PI_OVER_4) + +# Add the star to the drawing. +screen.blit(star, (x,y)) + +# Show the drawing. +pygame.display.flip() + +# Quit when the user presses enter. +input("Press enter to quit...") +pygame.quit() diff --git a/Chapter_01/Listing 1_5.py b/Chapter_01/Listing 1_5.py new file mode 100644 index 0000000..174ce55 --- /dev/null +++ b/Chapter_01/Listing 1_5.py @@ -0,0 +1,18 @@ +"""Calculate the angle between stars in an aerial firework.""" + +# Annotate variables +DEGREES_CIRCLE: int = 360 +num_stars: int +angle: float + +# Obtain the number of stars from the user. +num_stars = int(input("How many stars? ")) + +# Check that input is valid before processing. +if num_stars > 0: + # Calculate the angle between the stars. + angle = DEGREES_CIRCLE / num_stars + + # Tell the user the angle between stars. + print("You will need an angle of " + str(angle) + + "\u00B0 between each star.") diff --git a/Chapter_01/Listing 1_6.py b/Chapter_01/Listing 1_6.py new file mode 100644 index 0000000..e963661 --- /dev/null +++ b/Chapter_01/Listing 1_6.py @@ -0,0 +1,21 @@ +"""Calculate the angle between stars in an aerial firework.""" + +# Annotate variables +DEGREES_CIRCLE: int = 360 +num_stars: int +angle: float + +# Obtain the number of stars from the user. +num_stars = int(input("How many stars? ")) + +# Check that input is valid before processing. +if num_stars > 0: + # Calculate the angle between the stars. + angle = DEGREES_CIRCLE / num_stars + + # Tell the user the angle between stars. + print("You will need an angle of " + str(angle) + + "\u00B0 between each star.") +else: + # Explain to the user the input was invalid. + print("The number of stars must be positive.") diff --git a/Chapter_01/Listing 1_7.py b/Chapter_01/Listing 1_7.py new file mode 100644 index 0000000..faf94e2 --- /dev/null +++ b/Chapter_01/Listing 1_7.py @@ -0,0 +1,27 @@ +"""Load an image file based on user input.""" + +# Import and initialize pygame +import pygame +pygame.init() + +# Annotate variables +color: str +star: pygame.Surface + +# Ask the user what color star they want. +print("Stars can be red, blue, green, or yellow.") +color = input("What color would you like? ") + +# Load the appropriate image. +if color == "red": + star = pygame.image.load("red_star.png") +elif color == "blue": + star = pygame.image.load("blue_star.png") +elif color == "green": + star = pygame.image.load("green_star.png") +else: + star = pygame.image.load("yellow_star.png") + +# Quit when the user presses enter. +input("Press enter to quit...") +pygame.quit() diff --git a/Chapter_01/Listing 1_8.py b/Chapter_01/Listing 1_8.py new file mode 100644 index 0000000..4aad2fc --- /dev/null +++ b/Chapter_01/Listing 1_8.py @@ -0,0 +1,32 @@ +"""Demonstrate the relational operators.""" + +# Annotate variables +value1: int +value2: int + +# Obtain two values to compare from the user. +value1 = int(input("Please enter an integer: ")) +value2 = int(input("Please enter another integer: ")) + +# Show the greater than or equal to comparison. +if value1 >= value2: + print("The first value is greater than or equal to " + + "the second value.") + +# Show the less than or equal to comparison. +if value1 <= value2: + print("The first value is less than or equal to " + + "the second value.") + +# Show the test for equality. +if value1 == value2: + print("The values are equal.") + +# Show the greater than test. +if value1 > value2: + print("The first value is larger.") + +# Show the less than test. +if value1 < value2: + print("The first value is smaller.") + diff --git a/Chapter_01/Listing 1_9.py b/Chapter_01/Listing 1_9.py new file mode 100644 index 0000000..bdd0ca5 --- /dev/null +++ b/Chapter_01/Listing 1_9.py @@ -0,0 +1,17 @@ +"""Validate fireworks input.""" + +# Annotate variables +num_stars: int +is_valid: bool + +# Obtain the number of stars from the user. +num_stars = int(input("How many stars? ")) + +# Set the flag. +is_valid = num_stars > 0 + +# Message the user about input validity. +if is_valid: + print("Your input was valid!") +else: + print("Your input is not valid.") diff --git a/Chapter_01/blue_star.png b/Chapter_01/blue_star.png new file mode 100644 index 0000000..8b702bb Binary files /dev/null and b/Chapter_01/blue_star.png differ diff --git a/Chapter_01/gray_star.png b/Chapter_01/gray_star.png new file mode 100644 index 0000000..70ef981 Binary files /dev/null and b/Chapter_01/gray_star.png differ diff --git a/Chapter_01/green_star.png b/Chapter_01/green_star.png new file mode 100644 index 0000000..b5b6c3c Binary files /dev/null and b/Chapter_01/green_star.png differ diff --git a/Chapter_01/red_star.png b/Chapter_01/red_star.png new file mode 100644 index 0000000..92ce416 Binary files /dev/null and b/Chapter_01/red_star.png differ diff --git a/Chapter_01/star.png b/Chapter_01/star.png new file mode 100644 index 0000000..edf09e2 Binary files /dev/null and b/Chapter_01/star.png differ diff --git a/Chapter_01/yellow_star.png b/Chapter_01/yellow_star.png new file mode 100644 index 0000000..c2eb657 Binary files /dev/null and b/Chapter_01/yellow_star.png differ diff --git a/Chapter_02/Listing 2_1.py b/Chapter_02/Listing 2_1.py new file mode 100644 index 0000000..79eb3ab --- /dev/null +++ b/Chapter_02/Listing 2_1.py @@ -0,0 +1,30 @@ +"""Calculate the angle between stars in an aerial firework.""" + +def calc_angle(num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + +def main() -> None: + """Obtain number of stars from the user, calculate + the angle between them, and display result.""" + # Annotate variables + num_stars: int + angle: float + + # Obtain the number of stars from the user. + num_stars = int(input("How many stars? ")) + + # Calculate the angle between the stars. + angle = calc_angle(num_stars) + + # Tell the user the angle between stars. + print("You will need an angle of " + str(angle) + + "\u00B0 between each star.") + +main() diff --git a/Chapter_02/Listing 2_10.py b/Chapter_02/Listing 2_10.py new file mode 100644 index 0000000..dcbc41c --- /dev/null +++ b/Chapter_02/Listing 2_10.py @@ -0,0 +1,29 @@ +"""Manage a library summer reading program.""" + +# Annotate and initialize variables. +max_minutes: int = -1 +total_minutes: int = 0 +max_name: str = None +names: list +minutes: list +i: int = 0 + +# Create a list of names and minutes. +names = ["Edward", "Kamala", "Cory", "Ruth"] +minutes = [100, 200, 150, 300] + +# Process the lists. +while i < len(minutes): + # Find the max so far and total. + if minutes[i] > max_minutes: + max_minutes = minutes[i] + max_name = names[i] + total_minutes += minutes[i] + # Update the loop control variable. + i += 1 + +# Display a summary of reading information. +print(max_name + " is our star reader, with a total of " + + str(max_minutes) + " minutes.") +print("The total number of minutes read by all of the" + + " children was " + str(total_minutes) + ".") diff --git a/Chapter_02/Listing 2_11.py b/Chapter_02/Listing 2_11.py new file mode 100644 index 0000000..be00654 --- /dev/null +++ b/Chapter_02/Listing 2_11.py @@ -0,0 +1,47 @@ +"""Manage a library summer reading program.""" + +# Define menu constants +ADD: int = 1 +REMOVE: int = 2 +VIEW_ALL: int = 3 +QUIT: int = 4 +MENU_TEXT: str = ("1. Add a reader\n2. Remove a reader\n" + + "3. View all readers\n4. Quit") + +def menu() -> int: + """Obtain and return the user's menu choice.""" + choice: int = 0 + while choice < ADD or choice > QUIT: + print(MENU_TEXT) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + +def main() -> None: + """Manage a list of readers.""" + # Annotate and initialize variables + names: list = [] + choice: int = 0 + + # Greet the user. + print("Welcome! How can I help you manage the " + + "reading program?") + + # Process a menu choice. + while choice != QUIT: + choice = menu() + if choice == ADD: + # Obtain and add reader's name. + name = input("What is the reader's name? ") + names.append(name) + elif choice == REMOVE: + # Obtain reader's name. + name = input("What is the reader's name? ") + # Check to see if it's in the list and remove. + if name in names: + names.remove(name) + elif choice == VIEW_ALL: + # Print the list. + print(names) + +main() diff --git a/Chapter_02/Listing 2_12.py b/Chapter_02/Listing 2_12.py new file mode 100644 index 0000000..7bb8fd6 --- /dev/null +++ b/Chapter_02/Listing 2_12.py @@ -0,0 +1,36 @@ +"""Demonstrate list operations in a card-dealing program.""" + +import random + +def make_deck() -> list: + """Create and return a standard 52-card deck.""" + # Annotate and initialize variables + deck: list = [] + suit: str + value: str + + # Iterate through suits and values to make cards. + for suit in ["Hearts", "Clubs", "Diamonds", "Spades"]: + for value in ["Ace", "Two", "Three", "Four", "Five", + "Six", "Seven", "Eight", "Nine", "Ten", + "Jack", "Queen", "King"]: + deck.append(value + " of " + suit) + return deck + +def main() -> None: + """Make a deck, shuffle it, and deal.""" + # Annotate variables + deck: list + first_hand: list + second_hand: list + # Create and shuffle the deck. + deck = make_deck() + random.shuffle(deck) + # Deal two five-card hands. + first_hand = deck[:5] + second_hand = deck[5:10] + # Print the hands. + print(first_hand) + print(second_hand) + +main() diff --git a/Chapter_02/Listing 2_13.py b/Chapter_02/Listing 2_13.py new file mode 100644 index 0000000..c370f59 --- /dev/null +++ b/Chapter_02/Listing 2_13.py @@ -0,0 +1,17 @@ +"""Pre-process floating-point data.""" + +def remove_na(data: list) -> None: + """Remove all 'NA' values from the parameter list.""" + while "NA" in data: + data.remove("NA") + +def main() -> None: + """Demonstrate a function that modifies a parameter.""" + # Define and display a list. + data: list = [1.23, "NA", 1.15, .98, "NA", 1.02, "NA"] + print(data) + # Pass the list to a function and display again. + remove_na(data) + print(data) + +main() diff --git a/Chapter_02/Listing 2_14.py b/Chapter_02/Listing 2_14.py new file mode 100644 index 0000000..cf3119b --- /dev/null +++ b/Chapter_02/Listing 2_14.py @@ -0,0 +1,19 @@ +"""Pre-process floating-point data.""" + +def remove_na(data: list) -> None: + """Remove all 'NA' values from the parameter list.""" + while "NA" in data: + data.remove("NA") + +def main() -> None: + """Copy a list before passing it to a function.""" + # Define, copy, and display a list. + data: list = [1.23, "NA", 1.15, .98, "NA", 1.02, "NA"] + processed_data: list = data.copy() + print(data) + # Pass the copy to a function and display again. + remove_na(processed_data) + print(data) + print(processed_data) + +main() diff --git a/Chapter_02/Listing 2_15.py b/Chapter_02/Listing 2_15.py new file mode 100644 index 0000000..65490fc --- /dev/null +++ b/Chapter_02/Listing 2_15.py @@ -0,0 +1,107 @@ +"""Draw a random firework burst each time the user clicks.""" +import random +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +# Define constants. +SIZE: int = 480 +X_COORD: int = 0 +Y_COORD: int = 1 +RADIUS: int = 2 +NUM_STARS: int = 3 +STAR: int = 4 + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def calc_angle(num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: int + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + +def draw_burst(burst: list, screen: pygame.Surface) -> None: + """Draw the firework on screen, as specified + by burst parameter.""" + # Annotate variables + angle_degrees: float + angle: float + x_loc: float + y_loc: float + i: int + + angle_degrees = calc_angle(burst[NUM_STARS]) + angle = math.radians(angle_degrees) + for i in range(burst[NUM_STARS]): + x_loc = burst[X_COORD] + burst[RADIUS] * math.cos(i * angle) + y_loc = burst[Y_COORD] + burst[RADIUS] * math.sin(i * angle) + screen.blit(burst[STAR], (x_loc, y_loc)) + +def create_bursts(stars: list) -> list: + """Create and return a 2d list of starburst information.""" + # Annotate and initialize variables + bursts: list = [] + i: int + x: int + y: int + radius: int + num_stars: int + star: pygame.Surface + + for i in range(10): + x = random.randint(0,SIZE) + y = random.randint(0,SIZE) + radius = random.randint(25, 75) + num_stars = random.randint(5, 20) + star = random.choice(stars) + new_burst = [x, y, radius, num_stars, star] + bursts.append(new_burst) + return bursts + +def draw_bursts(screen: pygame.Surface, bursts: list) -> None: + """Draw the starbursts defined in the list bursts.""" + burst: list + for burst in bursts: + draw_burst(burst, screen) + +def main() -> None: + """Store burst information in a two-dimensional list and draw.""" + # Annotate variables + screen: pygame.Surface + stars: list + bursts: list + user_quit: bool + event: pygame.event.Event + + # Set up assets. + screen = make_window(SIZE, "Fireworks!") + stars = [pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + # Create the list of starburst data and draw. + bursts = create_bursts(stars) + draw_bursts(screen, bursts) + pygame.display.flip() + + # Wait for the user to close the window. + user_quit = False + while not user_quit: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + pygame.quit() + +main() diff --git a/Chapter_02/Listing 2_16.py b/Chapter_02/Listing 2_16.py new file mode 100644 index 0000000..db80d4c --- /dev/null +++ b/Chapter_02/Listing 2_16.py @@ -0,0 +1,92 @@ +"""Draw a random firework burst each time the user clicks.""" +import random +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def calc_angle(num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + +def draw_burst(x: int, + y: int, + radius: int, + num_stars: int, + star: pygame.Surface, + screen: pygame.Surface) -> None: + """Draw the firework on screen, as specified + by parameters.""" + # Annotate variables + angle_degrees: float + angle: float + offset_w: int + offset_h: int + i: int + x_loc: float + y_loc: float + + # Calculate the location of the star and blit. + angle_degrees = calc_angle(num_stars) + angle = math.radians(angle_degrees) + offset_w = star.get_width() / 2 + offset_h = star.get_height() / 2 + for i in range(num_stars): + x_loc = x + radius * math.cos(i * angle) - offset_w + y_loc = y + radius * math.sin(i * angle) - offset_h + screen.blit(star, (x_loc, y_loc)) + +def main() -> None: + """Process clicks by drawing bursts at click location.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + stars: list + user_quit: bool = False + event: pygame.event.Event + x: int + y: int + radius: int + num_stars: int + star: pygame.Surface + + # Set up assets. + screen = make_window(SIZE, "Click for a fireworks burst!") + stars = [pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + # Process events until the user chooses to quit. + while not user_quit: + for event in pygame.event.get(): + # Process a quit choice. + if event.type == pygame.QUIT: + user_quit = True + # Process a click by drawing a starburst. + elif event.type == pygame.MOUSEBUTTONUP: + x = event.__dict__["pos"][0] + y = event.__dict__["pos"][1] + radius = random.randint(25, 75) + num_stars = random.randint(5, 20) + star = random.choice(stars) + draw_burst(x,y,radius,num_stars, star, screen) + # Show the drawing. + pygame.display.flip() + pygame.quit() + +main() diff --git a/Chapter_02/Listing 2_17.py b/Chapter_02/Listing 2_17.py new file mode 100644 index 0000000..8e4d256 --- /dev/null +++ b/Chapter_02/Listing 2_17.py @@ -0,0 +1,16 @@ +"""A small table demonstrating format specifiers.""" + +def print_table() -> None: + """Print a table of data.""" + # Print the header + print("{:<15s}{:<15s}{:<15s}".format("Video name", + "Retention", + "Likes")) + print("{:<15s}{:<15.1f}{:<15,d}".format("Cat likes box", + 65.2, + 1426983)) + print("{:<15s}{:<15.1%}{:<15,d}".format("Epic fail", + .976, + 20059841)) + +print_table() diff --git a/Chapter_02/Listing 2_18.py b/Chapter_02/Listing 2_18.py new file mode 100644 index 0000000..94fe4f6 --- /dev/null +++ b/Chapter_02/Listing 2_18.py @@ -0,0 +1,42 @@ +"""A program that searches for a word in text.""" + +def analyze_text(text: str, word: str) -> None: + """Find occurrences of word in text.""" + # Annotate variables + num_occurrences: int + index1: int + index2: int + output: str = "" + + if word in text: + # Word is in text, count occurrences. + num_occurrences = text.count(word) + output += "There are {} ".format(num_occurrences) + output += "occurrences of {}. ".format(word) + # Get the surrounding text and display. + index = text.find(word) + if num_occurrences > 1: + # Rewrite to check indices with string length! + index2 = text.find(word, index + len(word)) + output += "The first two are: \n" + output += text[index-10:index+10] + output += "\n" + output += text[index2-10:index2+10] + else: + output += "It is: \n" + output += text[index-10:index+10] + print(output) + else: + output = "There are zero occurrences of " + output += "{}.".format(word) + print(output) + +def main() -> None: + text = ("The powers not delegated to the United States, " + + "by the Constitution, nor prohibited by it to" + + "the States, are reserved to the States " + + "respectively, or to the people.") + word = "States" + analyze_text(text, word) + +main() diff --git a/Chapter_02/Listing 2_19.py b/Chapter_02/Listing 2_19.py new file mode 100644 index 0000000..60bed40 --- /dev/null +++ b/Chapter_02/Listing 2_19.py @@ -0,0 +1,26 @@ +"""Write reading program names to a file.""" +import io + +# Annotate and initialize variables. +name: str +num_minutes: int +readers_file: io.TextIOWrapper + +# Display instructions to the user. +print("Enter each child's name and the number of minutes ") +print("they read this week. Enter 'quit' when done.") + +# Open a file +with open("readers.txt", "w") as readers_file: + # Obtain child information until done. + name = input("Enter a child's name or 'quit': ") + while name != "quit": + num_minutes = int(input("How many minutes did " + + name + " read this week? ")) + # Write data to the file output stream. + readers_file.write(name + "\n") + readers_file.write(str(num_minutes) + "\n") + name = input("Enter a child's name or 'quit': ") + + + diff --git a/Chapter_02/Listing 2_2.py b/Chapter_02/Listing 2_2.py new file mode 100644 index 0000000..d0582b7 --- /dev/null +++ b/Chapter_02/Listing 2_2.py @@ -0,0 +1,27 @@ +"""Draw a random firework burst each time the user clicks.""" + +def calc_angle(num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + +def main() -> None: + """Produce a table of stars and angles.""" + # Annotate variables + num_stars: int + angle: float + + # Print the table header. + print("Stars\tAngle") + + # Loop over number of stars and produce table row. + for num_stars in range(5,31,5): + angle = calc_angle(num_stars) + print(str(num_stars) + "\t" + str(angle)) + +main() diff --git a/Chapter_02/Listing 2_20.py b/Chapter_02/Listing 2_20.py new file mode 100644 index 0000000..47ae4d3 --- /dev/null +++ b/Chapter_02/Listing 2_20.py @@ -0,0 +1,25 @@ +"""Read reading program names from a file.""" +import io + +# Annotate and initialize variables. +name: str +minutes: int +minutes_str: str +readers_file: io.TextIOWrapper + +# Open a file +with open("readers.txt", "r") as readers_file: + # Read child information from the file until the end. + name = readers_file.readline() + while name != "": + name = name.strip() + minutes_str = readers_file.readline() + minutes = int(minutes_str) + + # Display the values to the user + print("{} read {} minutes.".format(name, minutes)) + + # Read the next name or eof + name = readers_file.readline() + + diff --git a/Chapter_02/Listing 2_21.py b/Chapter_02/Listing 2_21.py new file mode 100644 index 0000000..5d1fd4a --- /dev/null +++ b/Chapter_02/Listing 2_21.py @@ -0,0 +1,17 @@ +"""Read reading program names from a file.""" +import io +import os.path + +# Annotate and initialize variables. +data: list +readers_file: io.TextIOWrapper + +# Check for the file. +if os.path.isfile("readers.txt"): + # If the file exists, read the data in. + with open("readers.txt", "r") as readers_file: + data = readers_file.readlines() +else: + # If the file does not exist, display error. + print("The file readers.txt could not be found.") + diff --git a/Chapter_02/Listing 2_22.py b/Chapter_02/Listing 2_22.py new file mode 100644 index 0000000..e9ae313 --- /dev/null +++ b/Chapter_02/Listing 2_22.py @@ -0,0 +1,17 @@ +"""Read reading program names from a file.""" +import io + +# Annotate and initialize variables. +data: list +readers_file: io.TextIOWrapper + +# Check for the file. +try: + # If the file exists, read the data in. + with open("readers.txt", "r") as readers_file: + data = readers_file.readlines() +except FileNotFoundError: + print("The file readers.txt could not be found.") +except: + print("Something went wrong.") + diff --git a/Chapter_02/Listing 2_3.py b/Chapter_02/Listing 2_3.py new file mode 100644 index 0000000..9d6b20b --- /dev/null +++ b/Chapter_02/Listing 2_3.py @@ -0,0 +1,117 @@ +"""Draw a random firework burst each time the user clicks.""" +import random +import math + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def get_random_star(red_star: pygame.Surface, + blue_star: pygame.Surface, + green_star: pygame.Surface, + yellow_star: pygame.Surface) -> pygame.Surface: + """Randomly select a color and return a Surface + with a star of that color.""" + # Annotate variable + star: pygame.Surface + + # Randomly choose a color star to return. + color: int = random.randint(1,4) + if color == 1: + star = red_star + elif color == 2: + star = blue_star + elif color == 3: + star = green_star + else: + star = yellow_star + return star + +def calc_angle(num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + +def draw_burst(x: int, + y: int, + radius: int, + num_stars: int, + star: pygame.Surface, + screen: pygame.Surface) -> None: + """Draw the firework on screen, as specified + by parameters.""" + # Annotate variables + angle_degrees: float + angle: float + offset_w: int + offset_h: int + i: int + x_loc: float + y_loc: float + + # Calculate the location of the star and blit. + angle_degrees = calc_angle(num_stars) + angle = math.radians(angle_degrees) + offset_w = star.get_width() / 2 + offset_h = star.get_height() / 2 + for i in range(num_stars): + x_loc = x + radius * math.cos(i * angle) - offset_w + y_loc = y + radius * math.sin(i * angle) - offset_h + screen.blit(star, (x_loc, y_loc)) + +def main() -> None: + """Process clicks by drawing random bursts.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + red_star: pygame.Surface + blue_star: pygame.Surface + green_star: pygame.Surface + yellow_star: pygame.Surface + user_quit: bool = False + event: pygame.event.Event + x: int + y: int + radius: int + num_stars: int + star: pygame.Surface + + # Set up assets. + screen = make_window(SIZE, "Click for a fireworks burst!") + red_star = pygame.image.load("small_red_star.png") + blue_star = pygame.image.load("small_blue_star.png") + green_star = pygame.image.load("small_green_star.png") + yellow_star = pygame.image.load("small_yellow_star.png") + + # Process events until the user chooses to quit. + while not user_quit: + for event in pygame.event.get(): + # Process a quit choice. + if event.type == pygame.QUIT: + user_quit = True + # Process a click by drawing a starburst. + elif event.type == pygame.MOUSEBUTTONUP: + x = random.randint(0,SIZE) + y = random.randint(0,SIZE) + radius = random.randint(25, 75) + num_stars = random.randint(5, 20) + star = get_random_star(red_star, blue_star, + green_star, yellow_star) + draw_burst(x,y,radius,num_stars, star, screen) + # Show the drawing. + pygame.display.flip() + pygame.quit() + +main() diff --git a/Chapter_02/Listing 2_4.py b/Chapter_02/Listing 2_4.py new file mode 100644 index 0000000..c5aff71 --- /dev/null +++ b/Chapter_02/Listing 2_4.py @@ -0,0 +1,26 @@ +"""Validate fireworks input.""" + +def in_range(value: int) -> bool: + """Return True if value is between 2 and 360 inclusive.""" + result: bool = False + if value >= 2 and value <= 360: + result = True + return result + +def main() -> None: + """Obtain and validate number of stars.""" + # Annotate variable + num_stars: int + + # Obtain the number of stars from the user. + num_stars = int(input("How many stars? ")) + + # Check that the value is between 2 and 360. + while not in_range(num_stars): + print("The number must be between 2 and 360.") + num_stars = int(input("How many stars? ")) + + # The input is now valid, print a message. + print("Your input is valid.") + +main() diff --git a/Chapter_02/Listing 2_5.py b/Chapter_02/Listing 2_5.py new file mode 100644 index 0000000..a698a49 --- /dev/null +++ b/Chapter_02/Listing 2_5.py @@ -0,0 +1,29 @@ +"""Validate fireworks input.""" + +def in_range(minimum: int, + maximum: int, + value: int) -> bool: + """Return True if value is between minimum and + maximum inclusive.""" + result: bool = False + if value >= minimum and value <= maximum: + result = True + return result + +def main() -> None: + """Obtain and validate number of stars.""" + # Annotate variable + num_stars: int + + # Obtain the number of stars from the user. + num_stars = int(input("How many stars? ")) + + # Check that the value is between 2 and 360. + while not in_range(2, 360, num_stars): + print("The number must be between 2 and 360.") + num_stars = int(input("How many stars? ")) + + # The input is now valid, print a message. + print("Your input is valid.") + +main() diff --git a/Chapter_02/Listing 2_6.py b/Chapter_02/Listing 2_6.py new file mode 100644 index 0000000..a05d0d6 --- /dev/null +++ b/Chapter_02/Listing 2_6.py @@ -0,0 +1,17 @@ +"""Demonstrate scope.""" + +def func(param: int) -> None: + """Define a function with a parameter and + two local variables.""" + var: int = 0 + var2: int = 0 + print("\nIn func: ") + print("\tvar: " + str(var)) + print("\tvar2: " + str(var2)) + print("\tparam: " + str(param)) + +# This code is at the module level. +var: int = 10 +print("In module: ") +print("\tvar: " + str(var)) +func(var) diff --git a/Chapter_02/Listing 2_7.py b/Chapter_02/Listing 2_7.py new file mode 100644 index 0000000..8b4a76a --- /dev/null +++ b/Chapter_02/Listing 2_7.py @@ -0,0 +1,23 @@ +"""Demonstrate scope.""" + +def func() -> None: + """Define a function containing a function.""" + + def inner_func() -> None: + """Define a function within a function.""" + var1: int = 3 + print(var1, var2) + + # Declare some variables and call inner_func. + var1: int = 1 + var2: int = 2 + inner_func() + +def main() -> None: + """Invoke a function.""" + func() + +main() + + + diff --git a/Chapter_02/Listing 2_8.py b/Chapter_02/Listing 2_8.py new file mode 100644 index 0000000..1ad0ee3 --- /dev/null +++ b/Chapter_02/Listing 2_8.py @@ -0,0 +1,13 @@ +"""Demonstrate scope.""" + +def func() -> None: + """Demonstrate scope error.""" + print(var) + var: int = 5 + +# Module-level scope. +var: int = 10 +func() + + + diff --git a/Chapter_02/Listing 2_9.py b/Chapter_02/Listing 2_9.py new file mode 100644 index 0000000..2b1d881 --- /dev/null +++ b/Chapter_02/Listing 2_9.py @@ -0,0 +1,16 @@ +"""Demonstrate list indexing with a list of names.""" + +# Create and display a list of names. +names: list = ["Rayn", "Amari", "Asa", "Mathias", "Yosef"] +print(names) + +# Display the first and last names. +print(names[0]) +print(names[4]) + +# Change the second name. +names[1] = "Micah" + +# Display the whole list. +print(names) + diff --git a/Chapter_02/readers.txt b/Chapter_02/readers.txt new file mode 100644 index 0000000..084e557 --- /dev/null +++ b/Chapter_02/readers.txt @@ -0,0 +1,2 @@ +fred +10 diff --git a/Chapter_02/small_blue_star.png b/Chapter_02/small_blue_star.png new file mode 100644 index 0000000..6119629 Binary files /dev/null and b/Chapter_02/small_blue_star.png differ diff --git a/Chapter_02/small_green_star.png b/Chapter_02/small_green_star.png new file mode 100644 index 0000000..e4cd070 Binary files /dev/null and b/Chapter_02/small_green_star.png differ diff --git a/Chapter_02/small_red_star.png b/Chapter_02/small_red_star.png new file mode 100644 index 0000000..ac9a2e2 Binary files /dev/null and b/Chapter_02/small_red_star.png differ diff --git a/Chapter_02/small_yellow_star.png b/Chapter_02/small_yellow_star.png new file mode 100644 index 0000000..3b5ac23 Binary files /dev/null and b/Chapter_02/small_yellow_star.png differ diff --git a/Chapter_03/Listing 3-5.py b/Chapter_03/Listing 3-5.py new file mode 100644 index 0000000..6c0b18d --- /dev/null +++ b/Chapter_03/Listing 3-5.py @@ -0,0 +1,9 @@ +"""Demonstrate using the Name class.""" +from Name import * + +# Create a Name object. +turing: Name = Name("Alan", "Mathison", "Turing") + +# Invoke Name methods. +print(turing.get_full_name()) +print(turing.get_initial_name()) diff --git a/Chapter_03/Listing 3-6.py b/Chapter_03/Listing 3-6.py new file mode 100644 index 0000000..7a7302a --- /dev/null +++ b/Chapter_03/Listing 3-6.py @@ -0,0 +1,27 @@ +class Name(): + """A class to represent a person's name. + + Public methods: __init__, get_full_name, + get_initial_name + """ + + # Annotate object-level fields + _full_name: str + _initial_name: str + + def __init__(self, + first: str, + middle: str, + last: str)-> None: + """Initialize full name and initial name fields.""" + self._full_name = first + " " + middle + " " + last + self._initial_name = first[0] + ". " + last + + def get_full_name(self) -> str: + """Return the full name.""" + return self._full_name + + def get_initial_name(self) -> str: + """Return the name as first initial last name.""" + return self._initial_name + diff --git a/Chapter_03/Listing 3-7.py b/Chapter_03/Listing 3-7.py new file mode 100644 index 0000000..bddd4c2 --- /dev/null +++ b/Chapter_03/Listing 3-7.py @@ -0,0 +1,8 @@ +"""Another client of the Name class.""" +from Name import * + +# Create a Name object. +hopper: Name = Name("Grace", "Brewster Murray", "Hopper") + +# Invoke Name method. +print(hopper.get_first_last_name()) diff --git a/Chapter_03/Listing 3_1.py b/Chapter_03/Listing 3_1.py new file mode 100644 index 0000000..5cc8704 --- /dev/null +++ b/Chapter_03/Listing 3_1.py @@ -0,0 +1,57 @@ +import random +import math +import pygame +pygame.init() + +class StarBurst(): + """A class representing a fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate object-level fields + _x: float + _y: float + _radius: float + _num_stars: int + _star: pygame.Surface + + + def __init__(self, + x: float, + y: float, + star: pygame.Surface) -> None: + """Initialize an instance of StarBurst at x,y.""" + self._x = x + self._y = y + self._radius = random.randint(25, 75) + self._num_stars = random.randint(5, 20) + self._star = star + + def _calc_angle(self, num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + + def draw_burst(self, screen: pygame.Surface) -> None: + """Draw the firework on screen.""" + # Annotate and initialize variables. + w: int = self._star.get_width() + h: int = self._star.get_height() + angle_degrees: float = self._calc_angle(self._num_stars) + angle: float = math.radians(angle_degrees) + i: int + x_loc: float + y_loc: float + + # Draw the stars. + for i in range(self._num_stars): + x_loc = self._x + self._radius*math.cos(i*angle) - w/2 + y_loc = self._y + self._radius*math.sin(i*angle) - h/2 + screen.blit(self._star, (x_loc, y_loc)) + diff --git a/Chapter_03/Listing 3_2.py b/Chapter_03/Listing 3_2.py new file mode 100644 index 0000000..3abda6f --- /dev/null +++ b/Chapter_03/Listing 3_2.py @@ -0,0 +1,49 @@ +"""A simple object demonstration.""" + +from StarBurst import * +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + + +def main() -> None: + """Create and draw two StarBursts.""" + # Annotate and initialize variables. + SIZE: int = 480 + screen: pygame.Surface + stars: list + burst1: StarBurst + burst2: StarBurst + user_quit: bool + event: pygame.Event + + # Set up assets. + screen = make_window(SIZE, "Fireworks!") + stars = [pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + + # Make two starbursts and draw them. + burst1 = StarBurst(136, 225, random.choice(stars)) + burst2 = StarBurst(312, 128, random.choice(stars)) + burst1.draw_burst(screen) + burst2.draw_burst(screen) + pygame.display.flip() + + # Wait for the user to close the window. + user_quit = False + while not user_quit: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + pygame.quit() + +main() diff --git a/Chapter_03/Listing 3_3.py b/Chapter_03/Listing 3_3.py new file mode 100644 index 0000000..c77a88e --- /dev/null +++ b/Chapter_03/Listing 3_3.py @@ -0,0 +1,37 @@ +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate object-level fields + _name: str + _minutes: list + + def __init__(self, name: str, minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> str: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str: str = "Name: " + self._name + minutes_str: str = ("Minutes: " + + str(self.get_total_minutes())) + return name_str + "\n" + minutes_str diff --git a/Chapter_03/Listing 3_4.py b/Chapter_03/Listing 3_4.py new file mode 100644 index 0000000..e267569 --- /dev/null +++ b/Chapter_03/Listing 3_4.py @@ -0,0 +1,56 @@ +"""Manage a library summer reading program.""" + +from Reader import * + +# Define menu constants +ADD: int = 1 +VIEW_ALL: int = 2 +QUIT: int = 3 +MENU_TEXT: str = ("1. Add a reader\n2. View all readers" + + "\n3. Quit") + +def menu() -> int: + """Obtain and return the user's menu choice.""" + choice: int = 0 + while choice < ADD or choice > QUIT: + print(MENU_TEXT) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + +def main() -> None: + """Manage a list of Reader objects.""" + # Annotate and initialize variables. + readers: list = [] + choice: int = 0 + name: str + has_minutes: str + minutes: int + reader: Reader + + # Greet the user. + print("Welcome! How can I help you manage the " + + "reading program?") + + # Process a menu choice. + while choice != QUIT: + choice = menu() + if choice == ADD: + # Create and add a reader to the list. + name = input("What is the child's name? ") + has_minutes = input("Has the child read? (Y or N) ") + if has_minutes == "Y": + minutes = int(input("How many minutes? ")) + reader = Reader(name, minutes) + else: + reader = Reader(name) + readers.append(reader) + elif choice == VIEW_ALL: + # Print the readers in the list. + if len(readers) > 0: + for reader in readers: + print(reader) + else: + print("There are no readers.") + +main() diff --git a/Chapter_03/Listing3-8.py b/Chapter_03/Listing3-8.py new file mode 100644 index 0000000..0930889 --- /dev/null +++ b/Chapter_03/Listing3-8.py @@ -0,0 +1,35 @@ +class Name(): + """A class to represent a person's name. + + Public methods: __init__, get_full_name, + get_initial_name, get_first_last_name + """ + + # Annotate object-level fields + _first: str + _middle: str + _last: str + + def __init__(self, + first: str, + middle: str, + last: str) -> None: + """Initialize full name and initial name fields.""" + self._first = first + self._middle = middle + self._last = last + + def get_full_name(self) -> str: + """Return the full name.""" + return (self._first + " " + self._middle + + " " + self._last) + + def get_initial_name(self) -> str: + """Return the name as first initial last name.""" + return self._first[0] + ". " + self._last + + def get_first_last_name(self) -> str: + """Return the name as first initial last name.""" + return self._first + " " + self._last + + diff --git a/Chapter_03/Name.py b/Chapter_03/Name.py new file mode 100644 index 0000000..65a6aef --- /dev/null +++ b/Chapter_03/Name.py @@ -0,0 +1,27 @@ +class Name(): + """A class to represent a person's name. + + Public methods: __init__, get_full_name, + get_initial_name, get_first_last_name + """ + + def __init__(self, first, middle, last): + """Initialize full name and initial name fields.""" + self._first = first + self._middle = middle + self._last = last + + def get_full_name(self): + """Return the full name.""" + return self._first + " " + self._middle + \ + " " + self._last + + def get_initial_name(self): + """Return the name as first initial last name.""" + return self._first[0] + ". " + self._last + + def get_first_last_name(self): + """Return the name as first initial last name.""" + return self._first + " " + self._last + + diff --git a/Chapter_03/Reader.py b/Chapter_03/Reader.py new file mode 100644 index 0000000..a1c9718 --- /dev/null +++ b/Chapter_03/Reader.py @@ -0,0 +1,39 @@ +# This is Listing 3.3 + +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate object-level fields + _name: str + _minutes: list + + def __init__(self, name: str, minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> str: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str: str = "Name: " + self._name + minutes_str: str = ("Minutes: " + + str(self.get_total_minutes())) + return name_str + "\n" + minutes_str diff --git a/Chapter_03/StarBurst.py b/Chapter_03/StarBurst.py new file mode 100644 index 0000000..224f7cb --- /dev/null +++ b/Chapter_03/StarBurst.py @@ -0,0 +1,58 @@ +# This file is Listing 3.1, but named so it can be imported. + +import random +import math +import pygame + +class StarBurst(): + """A class representing a fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate object-level fields + _x: float + _y: float + _radius: float + _num_stars: int + _star: pygame.Surface + + + def __init__(self, + x: float, + y: float, + star: pygame.Surface) -> None: + """Initialize an instance of StarBurst at x,y.""" + self._x = x + self._y = y + self._radius = random.randint(25, 75) + self._num_stars = random.randint(5, 20) + self._star = star + + def _calc_angle(self, num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + + def draw_burst(self, screen: pygame.Surface) -> None: + """Draw the firework on screen.""" + # Annotate and initialize variables. + w: int = self._star.get_width() + h: int = self._star.get_height() + angle_degrees: float = self._calc_angle(self._num_stars) + angle: float = math.radians(angle_degrees) + i: int + x_loc: float + y_loc: float + + # Draw the stars. + for i in range(self._num_stars): + x_loc = self._x + self._radius*math.cos(i*angle) - w/2 + y_loc = self._y + self._radius*math.sin(i*angle) - h/2 + screen.blit(self._star, (x_loc, y_loc)) + diff --git a/Chapter_03/find_reader.py b/Chapter_03/find_reader.py new file mode 100644 index 0000000..5855580 --- /dev/null +++ b/Chapter_03/find_reader.py @@ -0,0 +1,13 @@ +from Reader import * + +def find_reader(name: str, readers: list) -> Reader: + """Return the object with name passed.""" + reader: Reader = None + found: bool = False + i: int = 0 + while not found and i < len(readers): + if readers[i].get_name() == name: + found = True + reader = readers[i] + i += 1 + return reader diff --git a/Chapter_03/small_blue_star.png b/Chapter_03/small_blue_star.png new file mode 100644 index 0000000..6119629 Binary files /dev/null and b/Chapter_03/small_blue_star.png differ diff --git a/Chapter_03/small_green_star.png b/Chapter_03/small_green_star.png new file mode 100644 index 0000000..e4cd070 Binary files /dev/null and b/Chapter_03/small_green_star.png differ diff --git a/Chapter_03/small_red_star.png b/Chapter_03/small_red_star.png new file mode 100644 index 0000000..ac9a2e2 Binary files /dev/null and b/Chapter_03/small_red_star.png differ diff --git a/Chapter_03/small_yellow_star.png b/Chapter_03/small_yellow_star.png new file mode 100644 index 0000000..3b5ac23 Binary files /dev/null and b/Chapter_03/small_yellow_star.png differ diff --git a/Chapter_04/Listing 4-1.py b/Chapter_04/Listing 4-1.py new file mode 100644 index 0000000..2e3a2a7 --- /dev/null +++ b/Chapter_04/Listing 4-1.py @@ -0,0 +1,95 @@ +"""A book review program.""" + +class Book(): + """A class representing a book. + + Public methods: __init__, __str__ + """ + + # Annotate object-level fields + _title: str + _author: str + + def __init__(self, title: str, author: str) -> None: + """Create a Book with title and author.""" + self._title = title + self._author = author + + def __str__(self) -> str: + """Return a string description of the book.""" + return self._title + " by " + self._author + +class Reader(): + """A class representing a library reader. + + Public methods: __init__, __str__ + """ + + # Annotate object-level fields + _name: str + _card_number: int + + def __init__(self, name: str, card_number: int) -> None: + """Create a Reader with name and card number.""" + self._name = name + self._card_number = card_number + + def __str__(self) -> str: + """Return a string description of the reader.""" + return self._name + ", " + str(self._card_number) + +class Review(): + """A class representing a book review. + + Public methods: __init__, __str__ + """ + + # Annotate object-level fields + _reviewer: Reader + _book: Book + _rating: int + _review: str + + def __init__(self, + reader: Reader, + book: Book, + rating: int, + review: str) -> None: + """Create a Review with parameters passed.""" + self._reviewer = reader + self._book = book + self._rating = rating + self._review = review + + def __str__(self) -> str: + """Return a string describing the review.""" + return "{:15}{}\n{:15}{}\n{:15}{}\n{:15}{}".format( + "Review of:", str(self._book), "By:", + str(self._reviewer), "Rating:", str(self._rating), + "Review", self._review) + +def main() -> None: + """Demonstrate the Book, Review, and Reader classes.""" + # Annotate variables + book: Book + reader_tim: Reader + reader_john: Reader + review_tim: Review + review_john: Review + + # Create a book and two readers. + book = Book("Palindromemordnilap", "Hannah Nolon") + reader_tim = Reader("Tim Smit", 100226843) + reader_john = Reader("John Lincoln", 100235178) + + # Create two reviews of the same book. + review_tim = Review(reader_tim, book, 5, + "Loved it! I'll never read only forward again!") + review_john = Review(reader_john, book, 1, + "Book was twice as long as I expected.") + + # Print the reviews. + print(review_tim) + print(review_john) + +main() diff --git a/Chapter_04/Listing 4-2.py b/Chapter_04/Listing 4-2.py new file mode 100644 index 0000000..e1f93a9 --- /dev/null +++ b/Chapter_04/Listing 4-2.py @@ -0,0 +1,47 @@ +from typing import ClassVar + +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate and define class-level field + _counter: ClassVar[int] = 0 + + # Annotate object-level fields + _name: str + _minutes: list + _reader_id: int + + def __init__(self, + name: str, + minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + Reader._counter += 1 + self._reader_id = Reader._counter + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> int: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str = "Name: " + self._name + id_str = "Reader ID: " + str(self._reader_id) + minutes_str = "Minutes: " + str(self.get_total_minutes()) + return name_str + "\n" + id_str + "\n" + minutes_str diff --git a/Chapter_04/Listing 4-3.py b/Chapter_04/Listing 4-3.py new file mode 100644 index 0000000..7a5ace9 --- /dev/null +++ b/Chapter_04/Listing 4-3.py @@ -0,0 +1,62 @@ +import random +import math +from typing import ClassVar + +import pygame +pygame.init() + +class StarBurst(): + """A class representing a fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate and initialize class-level field + _stars: ClassVar[list] =[ + pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + # Annotate object-level fields + _x: float + _y: float + _radius: float + _num_stars: int + _star: pygame.Surface + + def __init__(self, x: float, y: float) -> None: + """Initialize an instance of StarBurst at x,y.""" + self._x = x + self._y = y + self._radius = random.randint(25, 75) + self._num_stars = random.randint(5, 20) + self._star = random.choice(StarBurst._stars) + + def _calc_angle(self, num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + + def draw_burst(self, surface: pygame.Surface) -> None: + """Draw the firework on screen.""" + # Annotate and initialize variables. + w: int = self._star.get_width() + h: int = self._star.get_height() + angle_degrees: float = self._calc_angle(self._num_stars) + angle: float = math.radians(angle_degrees) + i: int + x_loc: float + y_loc: float + + # Draw the stars. + for i in range(self._num_stars): + x_loc = self._x + self._radius*math.cos(i*angle) - w/2 + y_loc = self._y + self._radius*math.sin(i*angle) - h/2 + surface.blit(self._star, (x_loc, y_loc)) + diff --git a/Chapter_04/Listing 4-4.py b/Chapter_04/Listing 4-4.py new file mode 100644 index 0000000..0ee0387 --- /dev/null +++ b/Chapter_04/Listing 4-4.py @@ -0,0 +1,186 @@ +from typing import ClassVar + +class Stamp(): + """A passport stamp in the library reading program. + + Public methods: __init__, get_value, get_cost, + __str__ + """ + + # Annotating object-level fields + _value: str + _cost: int + + def __init__(self, value: str, cost: int) -> None: + """Initialize fields with parameters.""" + self._value = value + self._cost = cost + + def get_value(self) -> str: + """Return the value of the stamp.""" + return self._value + + def get_cost(self) -> int: + """Return the cost of the stamp.""" + return self._cost + + def __str__(self) -> str: + """Return a string version of the stamp.""" + return "{:<15s}{: int: + """Return the cost of a stamp.""" + return StampManager._stamp_cost + + def get_empty_stamp(self) -> Stamp: + """Return a reference to the empty stamp.""" + return StampManager._empty_stamp + + + +class Passport(): + """Manage a passport in the reading program. + + Public methods: __init__, request_stamp, __str__ + """ + + # Annotating object-level fields + _stamps: list + _stamp_manager: StampManager + + def __init__(self, stamp_manager: StampManager) -> None: + """Initialize passport from parameters.""" + self._stamps = [] + self._stamp_manager = stamp_manager + + def request_stamp(self, reader_account: "ReaderAccount") -> bool: + """Return true if stamp request is accepted, false otherwise.""" + # Annotate and initialize variables + success: bool = False + cost: int + free_minutes: int + stamp: Stamp + + # Fulfill stamp request and return success. + cost = self._stamp_manager.get_stamp_cost() + free_minutes = reader_account.get_free_minutes() + if free_minutes >= cost: + stamp = self._stamp_manager.get_empty_stamp() + self._stamps.insert(0, stamp) + reader_account.deduct_free_minutes(cost) + success = True + return success + + def __str__(self) -> str: + """Return a string version of the passport.""" + stamp_str: str = "" + if len(self._stamps) > 0: + for stamp in self._stamps: + stamp_str += str(stamp) + "\t" + else: + stamp_str = "No stamps in passport" + return stamp_str + + +class ReaderAccount(): + """Manage a Reader's reading program account. + Public methods: __init__, get_free_minutes, + log_minutes, deduct_free_minutes, + request_stamp, __str__ + """ + + # Annotating object-level fields + _reader_id: int + _name: str + _free_minutes: int + _passport: Passport + + def __init__(self, + reader_id: int, + name: str, + stamp_manager: StampManager) -> None: + """Initialize reader account from parameters.""" + self._reader_id = reader_id + self._name = name + self._free_minutes = 0 + self._passport = Passport(stamp_manager) + + def get_free_minutes(self) -> int: + """Return the number of free minutes.""" + return self._free_minutes + + def log_minutes(self, minutes: int) -> None: + """Add parameter minutes to log.""" + self._free_minutes += minutes + + def deduct_free_minutes(self, minutes: int) -> None: + """Deduct parameter minutes from log.""" + self._free_minutes -= minutes + + def request_stamp(self) -> bool: + """Request a stamp for accumulated minutes.""" + success: bool = self._passport.request_stamp(self) + return success + + def __str__(self) -> str: + """Return a string version of the reader account.""" + return ("{:<10s}{:<10d}{}\n".format(self._name, + self._reader_id, + self._free_minutes) + + str(self._passport)) + + + +def main() -> None: + """Test the request passport stamp use case.""" + # Annotate variables + manager: StampManager + reader1: ReaderAccount + reader2: ReaderAccount + success: bool + + # Make one StampManager + manager = StampManager() + + # Make two ReaderAccounts + reader1 = ReaderAccount(112233, "D.Knuth", manager) + reader2 = ReaderAccount(445566, "G.Hopper", manager) + + print("Reader 1 does not log enough minutes:") + reader1.log_minutes(60) + print(reader1) + success = reader1.request_stamp() + print("Successful stamp request: " + str(success)) + print(reader1) + + print("\nReader 2 logs enough minutes: ") + reader2.log_minutes(90) + print(reader2) + success = reader2.request_stamp() + print("Successful stamp request: " + str(success)) + print(reader2) + + print("\nReader 1 logs enough minutes: ") + reader1.log_minutes(60) + print(reader1) + success = reader1.request_stamp() + print("Successful stamp request: " + str(success)) + print(reader1) + + +main() diff --git a/Chapter_04/Reader.py b/Chapter_04/Reader.py new file mode 100644 index 0000000..996485c --- /dev/null +++ b/Chapter_04/Reader.py @@ -0,0 +1,49 @@ +# Copy of Listing 4.2 so it can be imported + +from typing import ClassVar + +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate and define class-level field + _counter: ClassVar[int] = 0 + + # Annotate object-level fields + _name: str + _minutes: list + _reader_id: int + + def __init__(self, + name: str, + minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + Reader._counter += 1 + self._reader_id = Reader._counter + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> int: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str = "Name: " + self._name + id_str = "Reader ID: " + str(self._reader_id) + minutes_str = "Minutes: " + str(self.get_total_minutes()) + return name_str + "\n" + id_str + "\n" + minutes_str diff --git a/Chapter_04/StarBurst.py b/Chapter_04/StarBurst.py new file mode 100644 index 0000000..61af9ca --- /dev/null +++ b/Chapter_04/StarBurst.py @@ -0,0 +1,63 @@ +# Listing 4.3, named so we can import it + +import random +import math +from typing import ClassVar + +import pygame + +class StarBurst(): + """A class representing a fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate and initialize class-level field + _stars: ClassVar[list] =[ + pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + # Annotate object-level fields + _x: float + _y: float + _radius: float + _num_stars: int + _star: pygame.Surface + + def __init__(self, x: float, y: float) -> None: + """Initialize an instance of StarBurst at x,y.""" + self._x = x + self._y = y + self._radius = random.randint(25, 75) + self._num_stars = random.randint(5, 20) + self._star = random.choice(StarBurst._stars) + + def _calc_angle(self, num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + + def draw_burst(self, surface: pygame.Surface) -> None: + """Draw the firework on screen.""" + # Annotate and initialize variables. + w: int = self._star.get_width() + h: int = self._star.get_height() + angle_degrees: float = self._calc_angle(self._num_stars) + angle: float = math.radians(angle_degrees) + i: int + x_loc: float + y_loc: float + + # Draw the stars. + for i in range(self._num_stars): + x_loc = self._x + self._radius*math.cos(i*angle) - w/2 + y_loc = self._y + self._radius*math.sin(i*angle) - h/2 + surface.blit(self._star, (x_loc, y_loc)) + diff --git a/Chapter_04/small_blue_star.png b/Chapter_04/small_blue_star.png new file mode 100644 index 0000000..6119629 Binary files /dev/null and b/Chapter_04/small_blue_star.png differ diff --git a/Chapter_04/small_green_star.png b/Chapter_04/small_green_star.png new file mode 100644 index 0000000..e4cd070 Binary files /dev/null and b/Chapter_04/small_green_star.png differ diff --git a/Chapter_04/small_red_star.png b/Chapter_04/small_red_star.png new file mode 100644 index 0000000..ac9a2e2 Binary files /dev/null and b/Chapter_04/small_red_star.png differ diff --git a/Chapter_04/small_yellow_star.png b/Chapter_04/small_yellow_star.png new file mode 100644 index 0000000..3b5ac23 Binary files /dev/null and b/Chapter_04/small_yellow_star.png differ diff --git a/Chapter_05/Listing 5-1.py b/Chapter_05/Listing 5-1.py new file mode 100644 index 0000000..79de769 --- /dev/null +++ b/Chapter_05/Listing 5-1.py @@ -0,0 +1,76 @@ +"""An inheritance example from the course software.""" +from datetime import date + + +class Assignment(): + """A course assignment. + + Public methods: __init__, assign_grade, + __str__ + """ + + # Annotate object-level fields + _feedback: str + _grade: int + _due_date: date + + def __init__(self, + year_due: int, + month_due: int, + day_due: int) -> None: + """Initialize an assignment from parameters.""" + self._feedback = None + self._grade = 0 + self._due_date = date(year_due, month_due, day_due) + + + def assign_grade(self, grade: int) -> None: + """Assign a grade to this assignment.""" + self._grade = grade + + def __str__(self) -> str: + """Convert assignment to a string.""" + return "{:12s}{}\n{:12s}{}\n{:12s}{}".format( + "Grade:", self._grade,"Feedback:", self._feedback, + "Due date:", self._due_date) + +class Essay(Assignment): + """An essay type of assignment. + + Public methods: __init__, __str__ + """ + + # Annotate object-level fields + _prompt: str + _response: str + + def __init__(self, + year_due: int, + month_due: int, + day_due: int, + prompt: str) -> None: + """Initialize the essay from parameters.""" + super().__init__(**kwargs) + self._prompt = prompt + self._response = None + + def __str__(self) -> str: + """Convert essay to a string.""" + return (super().__str__() + + "\n\n{:12s}{}\n{:12s}{}".format( + "Prompt:", self._prompt, "Response:", + self._response)) + +def main() -> None: + """Create an essay and print.""" + # Annotate variable + essay: Essay + + # Create an Essay Assignment, set grade, and print. + essay = Essay(year_due = 2019, month_due = 11, day_due = 30, + prompt = "Explain the \"isa\" relationship.") + essay.assign_grade(100) + print(essay) + +main() + diff --git a/Chapter_05/Listing 5-2.py b/Chapter_05/Listing 5-2.py new file mode 100644 index 0000000..c6263f5 --- /dev/null +++ b/Chapter_05/Listing 5-2.py @@ -0,0 +1,107 @@ +"""A polymorphism example from the course software.""" +from datetime import date + + +class Assignment(): + """A course assignment. + + Public methods: __init__, get_grade + """ + + # Annotate object-level fields + _feedback: str + _grade: int + _due_date: date + + def __init__(self, + year_due: int, + month_due: int, + day_due: int) -> None: + """Initialize an assignment from parameters.""" + self._feedback = None + self._grade = 0 + self._due_date = date(year_due, month_due, day_due) + + def get_grade(self) -> int: + """Return the grade for this assignment.""" + return self._grade + + +class Essay(Assignment): + """An essay type of assignment. + + Public method: __init__ + """ + + # Annotate object-level fields + _prompt: str + _response: str + + def __init__(self, + year_due: int, + month_due: int, + day_due: int, + prompt: str) -> None: + """Initialize the essay from parameters.""" + super().__init__(year_due, month_due, day_due) + self._prompt = prompt + self._response = None + + +class Quiz(Assignment): + """A quiz type of assignment. + + Public methods: __init__, get_grade + """ + + # Annotate object-level fields + _questions: list + _answers: list + _response: list + + def __init__(self, + year_due: int, + month_due: int, + day_due: int, + questions: list, + answers: list) -> None: + """Initialize the essay from parameters.""" + super().__init__(year_due, month_due, day_due) + self._questions = questions + self._answers = answers + # Hard-coded response for testing purposes. + self._response = [True, False] + + def get_grade(self) -> int: + """Return the grade for this assignment.""" + # Initialize and annotate variables + total: int = 0 + i: int + + # Iterate through answer key and answers and count + # correct answers. + for i in range(len(self._answers)): + if self._answers[i] == self._response[i]: + total += 1 + + return total / len(self._answers) * 100 + +def main() -> None: + """Create assignments and get grades.""" + # Initialize and annotate assignment list + assignments: list[Assignment] = [] + + # Create two assignments. + assignments.append(Essay(2019, 11, 30, + "Explain the \"isa\" relationship.")) + assignments.append(Quiz(2019, 11, 30, + ["Guido van Rossum authored Python", + "UML diagrams OO systems"], + [True, True])) + + # Send the get_grade message to the assignments. + for assignment in assignments: + print(assignment.get_grade()) + +main() + diff --git a/Chapter_05/Listing 5-3.py b/Chapter_05/Listing 5-3.py new file mode 100644 index 0000000..d5fe01c --- /dev/null +++ b/Chapter_05/Listing 5-3.py @@ -0,0 +1,138 @@ +import random +import math +from typing import ClassVar + +import pygame + +class StarBurst(): + """A class representing a fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate and initialize class-level field + _stars: ClassVar[list] =[ + pygame.image.load("small_red_star.png"), + pygame.image.load("small_blue_star.png"), + pygame.image.load("small_green_star.png"), + pygame.image.load("small_yellow_star.png")] + + # Annotate object-level fields + _x: float + _y: float + _radius: float + _num_stars: int + _star: pygame.Surface + + def __init__(self, x: float, y: float) -> None: + """Initialize an instance of StarBurst at x,y.""" + self._x = x + self._y = y + self._radius = random.randint(25, 75) + self._num_stars = random.randint(5, 20) + self._star = random.choice(StarBurst._stars) + + def _calc_angle(self, num_points: int) -> float: + """Calculate and return the angle between points + evenly distributed around a circle.""" + DEGREES_CIRCLE: int = 360 + angle: float + + # Calculate and return the angle. + angle = DEGREES_CIRCLE / num_points + return angle + + def draw_burst(self, surface: pygame.Surface) -> None: + """Draw the firework on screen.""" + # Annotate and initialize variables. + w: int = self._star.get_width() + h: int = self._star.get_height() + angle_degrees: float = self._calc_angle(self._num_stars) + angle: float = math.radians(angle_degrees) + i: int + x_loc: float + y_loc: float + + # Draw the stars. + for i in range(self._num_stars): + x_loc = self._x + self._radius*math.cos(i*angle) - w/2 + y_loc = self._y + self._radius*math.sin(i*angle) - h/2 + surface.blit(self._star, (x_loc, y_loc)) + + +class AnimatedBurst(StarBurst): + """A class representing an animated fireworks starburst. + + Public methods: __init__, draw_burst + """ + + # Annotate object-level fields + _state_index: int + _state: list + + def __init__(self, x: int, y: int) -> None: + """Initialize an instance of GradualBurst at x,y.""" + super().__init__(x, y) + self._state_index = 0 + self._state = [self._radius / 5, self._radius / 4, self._radius / 3, self._radius / 2, self._radius] + self._radius = self._state[0] + + def draw_burst(self, surface: pygame.Surface) -> None: + """Draw the firework on surface.""" + # Change to the next radius in the list + self._state_index = ((self._state_index + 1) + % len(self._state)) + self._radius = self._state[self._state_index] + # Draw the burst + super().draw_burst(surface) + + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Create and draw ten StarBursts (animated or static).""" + # Annotate and initialize variables + SIZE: int = 480 + user_quit: bool = False + clock: pygame.time.Clock = pygame.time.Clock() + screen: pygame.Surface + background: pygame.Surface + burst_list: list[StarBurst] + choice: int + x: int + y: int + + # Set up assets. + screen = make_window(SIZE, "Fireworks!") + background = pygame.Surface((480,480)) + + # Make ten starbursts. + burst_list = [] + for i in range(10): + choice = random.randint(0,1) + x = random.randint(0, 480) + y = random.randint(0, 480) + if choice == 0: + burst_list.append(AnimatedBurst(x, y)) + else: + burst_list.append(StarBurst(x, y)) + + # Run until the user closes the window. + while not user_quit: + # Loop five times per second + clock.tick(5) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + screen.blit(background,(0,0)) + for burst in burst_list: + burst.draw_burst(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_05/small_blue_star.png b/Chapter_05/small_blue_star.png new file mode 100644 index 0000000..6119629 Binary files /dev/null and b/Chapter_05/small_blue_star.png differ diff --git a/Chapter_05/small_green_star.png b/Chapter_05/small_green_star.png new file mode 100644 index 0000000..e4cd070 Binary files /dev/null and b/Chapter_05/small_green_star.png differ diff --git a/Chapter_05/small_red_star.png b/Chapter_05/small_red_star.png new file mode 100644 index 0000000..ac9a2e2 Binary files /dev/null and b/Chapter_05/small_red_star.png differ diff --git a/Chapter_05/small_yellow_star.png b/Chapter_05/small_yellow_star.png new file mode 100644 index 0000000..3b5ac23 Binary files /dev/null and b/Chapter_05/small_yellow_star.png differ diff --git a/Chapter_06/Listing 6-1.py b/Chapter_06/Listing 6-1.py new file mode 100644 index 0000000..1c3787d --- /dev/null +++ b/Chapter_06/Listing 6-1.py @@ -0,0 +1,26 @@ +"""Write and read a list of integers using the pickle library.""" + +import pickle +import io + +def main(): + """Demonstrate the pickle library.""" + # Annotate variables + file_out: io.BufferedWriter + file_in: io.BufferedReader + numbers_out: list = [1, 2, 3, 4, 5] + numbers_in: list + + # Write the list to a binary file. + print("List before pickling: " + str(numbers_out)) + with open("pickle_test", "wb") as file_out: + pickle.dump(numbers_out, file_out) + + # Read a list from a binary file. + with open("pickle_test", "rb") as file_in: + numbers_in = pickle.load(file_in) + print("List after unpickling: " + str(numbers_in)) + +main() + + diff --git a/Chapter_06/Listing 6-10.py b/Chapter_06/Listing 6-10.py new file mode 100644 index 0000000..04c2268 --- /dev/null +++ b/Chapter_06/Listing 6-10.py @@ -0,0 +1,88 @@ +"""A simple observer example.""" +import math + +class Subject(): + """A Subject object represents something with a + state change that will be reflected in other objects. + + Public methods: __init__, attach, detach, notify, + get_state, update_state + """ + # Annotate object-level variables + _state: int + _observers: list + + def __init__(self) -> None: + """Initialize state to zero and observers to empty list.""" + self._state = 0 + self._observers = [] + + def attach(self, observer: object) -> None: + """Attach an observer to this object.""" + self._observers.append(observer) + + def detach(self, observer: object) -> None: + """Detach an observer from this object.""" + if observer in self._observers: + self._observers.remove(observer) + + def notify(self) -> None: + """Notify observers that this object's state has changed.""" + for observer in self._observers: + observer.update() + + def get_state(self) -> None: + """Return state information about this object.""" + return self._state + + def update_state(self) -> None: + """Update the state of this object.""" + self._state += 1 + self.notify() + +class Observer(): + """An Observer object represents something that must + update in response to a change in the subject. + + Public methods: __init__, update, get_state + """ + # Annotate object-level variables + _func: "function" + _value: float + _subject: Subject + + def __init__(self, subject: Subject, func: "function") -> None: + """Initialize fields from parameters, + add self as observer to subject.""" + subject.attach(self) + self._func = func + self._value = self._func(subject.get_state()) + self._subject = subject + + def get_state(self) -> float: + """Return state of this object.""" + return self._value + + def update(self) -> None: + """Change state to reflect updated subject.""" + self._value = self._func(self._subject.get_state()) + +def main(): + # Create a subject and two different observers. + subject = Subject() + observer_1 = Observer(subject, math.factorial) + observer_2 = Observer(subject, math.sqrt) + + # Update the subject's state and then display state of observers. + for i in range(10): + subject.update_state() + print("Observer 1's state: " + + str(observer_1.get_state())) + print("Observer 2's state: " + str(observer_2.get_state()) + "\n") + +main() + + + + + diff --git a/Chapter_06/Listing 6-11.py b/Chapter_06/Listing 6-11.py new file mode 100644 index 0000000..0aec54a --- /dev/null +++ b/Chapter_06/Listing 6-11.py @@ -0,0 +1,385 @@ +"""A constellation labeling program.""" +from typing import ClassVar +import pygame + +class Star(): + """A class representing a star. + + Public methods: __init__, draw, select, get_name, set_color + """ + + # Annotate object-level fields + _x: float + _y: float + _name: str + _star: pygame.Surface + _star_selected: pygame.Surface + _color: tuple + + def __init__(self, x: float, y: float, name: str, color: tuple) -> None: + """Initialize an instance of star at x,y.""" + self._x = x + self._y = y + self._name = name + self._selected = False + self._color = color + # self._star is a small circle in the star's color + self._star = pygame.Surface((20,20)) + pygame.draw.circle(self._star, self._color, (10, 10), 5) + # self._star_selected is a larger white circle + self._star_selected = pygame.Surface((20,20)) + pygame.draw.circle(self._star_selected, (255, 255, 255), (10, 10), 10) + + def select(self, selected: bool) -> None: + """Select or deselected according to parameter.""" + self._selected = selected + + def set_color(self, color: tuple) -> None: + """Set the star's color to the parameter.""" + self._color = color + pygame.draw.circle(self._star, self._color, (10, 10), 5) + + def get_name(self) -> str: + """Return the star's name.""" + return self._name + + def draw(self, surface: pygame.Surface) -> None: + """Draw the star on the surface.""" + if self._selected: + surface.blit(self._star_selected, (self._x, self._y)) + else: + surface.blit(self._star, (self._x, self._y)) + +class ConstellationIter(): + """An iterator for iterating over a Constellation's stars. + + Public methods: __init__, __iter__, __next__, first + """ + + # Annotate object-level variables + _constellation: "Constellation" + _star_index: int + _num_stars: int + + def __init__(self, constellation: "Constellation", num_stars: int) -> None: + """Initialize from parameters and set index to zero.""" + self._constellation = constellation + self._num_stars = num_stars + self._star_index = 0 + + def __iter__(self) -> "ConstellationIter": + """Return self as required by Python.""" + return self + + def __next__(self) -> Star: + """Return the next Star in the Constellation or raise exception + and advance the index.""" + # Annotate variable + star: Star + if self._star_index == self._num_stars: + raise StopIteration + else: + star = self._constellation[self._star_index] + self._star_index += 1 + return star + + def first(self) -> None: + """Reset the iterator to the first element.""" + self._star_index = 0 + +class Constellation(): + """A class representing a constellation. + + Public methods: __init__, draw, __getitem__, __iter__ + get_name, update + """ + + # Annotate object-level fields + _stars: list + _name: str + _button_manager: "ButtonGroup" + + def __init__(self, stars: list, name: str, button_manager: "ButtonGroup") -> None: + """Initialize from parameters.""" + self._stars = stars + self._name = name + self._button_manager = button_manager + button_manager.attach(self) + + def update(self) -> None: + """Update stars to reflect selected button.""" + star_color = self._button_manager.get_selected_color() + for star in self._stars: + star.set_color(star_color) + + def draw(self, surface: pygame.Surface) -> None: + """Draw the constellation.""" + for star in self._stars: + star.draw(surface) + + def get_name(self) -> str: + """Return the constellation name.""" + return self._name + + def __getitem__(self, star_index: int) -> Star: + """Allow indexing into a Constellation, return star at + star_index.""" + return self._stars[star_index] + + def __iter__(self) -> ConstellationIter: + """Create and return a ConstellationIter.""" + return ConstellationIter(self, len(self._stars)) + +class Button(): + """A button that can be moused over and pressed. + + Public methods: __init__, set_state, get_color, + draw, point_in + """ + + # Annotate and define class-level fields + MOUSE_OUT: ClassVar[int] = 0 + MOUSE_OVER: ClassVar[int] = 1 + SELECTED: ClassVar[int] = 2 + + # Annotate object-level fields + _text: str + _state: int + _color: tuple + _rect: pygame.Rect + _text_surface: pygame.Surface + + def __init__(self, text: str, color: tuple, + rect: pygame.Rect, state: int) -> None: + """Initialize from parameters and make text Surface.""" + self._text = text + self._state = state + self._color = color + self._rect = rect + font = pygame.font.SysFont("Verdana", 18) + self._text_surface = font.render(self._text, 1, (25, 25, 25)) + + def set_state(self, state: int) -> None: + """Change the button's state to parameter.""" + self._state = state + + def get_color(self) -> tuple: + """Return the button's color.""" + return self._color + + def draw(self, surface: pygame.Surface) -> None: + """Draw to Surface.""" + # Create the surfaces and fill the background. + button = pygame.Surface((100, 50)) + button.fill((255, 255, 255)) + raised = pygame.Surface((100, 50)) + shadow = pygame.Surface((100, 50)) + + if self._state == Button.MOUSE_OUT: + # Draw a light gray button with shadow. + raised.fill((245, 245, 245)) + shadow.fill((225, 225, 225)) + button.blit(shadow, (2, 2)) + button.blit(raised, (-2, -2)) + elif self._state == Button.MOUSE_OVER: + # Draw a button in light color with shadow. + r = (self._color[0]+150 if self._color[0] < 155 + else self._color[0]) + g = self._color[1]+150 if self._color[1] < 155 else self._color[1] + b = self._color[2]+150 if self._color[2] < 155 else self._color[2] + raised.fill((r, g, b)) + shadow.fill((225, 225, 225)) + button.blit(shadow, (-2, -2)) + button.blit(raised, (2, 2)) + else: + # (SELECTED) Draw a button in color with shadow. + raised.fill(self._color) + shadow.fill((225, 225, 225)) + button.blit(shadow, (-2, -2)) + button.blit(raised, (2, 2)) + # Blit the text and then the button to surface. + button.blit(self._text_surface, (15, 15)) + surface.blit(button, (self._rect.left, self._rect.top)) + + def point_in(self, point: tuple) -> bool: + """Return True if point within rect.""" + return (self._rect.left <= point[0] and self._rect.right >= point[0] + and self._rect.top <= point[1] and self._rect.bottom >= point[1]) + +class ButtonGroup(): + """A class to manage a group of related buttons. + This is a subject in an observer pattern. + + Public methods: __init__, attach, detach, notify, + get_selected_color, point_in, + handle_click, handle_mouse_over, draw + """ + + # Annotate object-level variables + _buttons: list + _selected: Button + _rect: pygame.Rect + _observers: list + + def __init__(self, rect: pygame.Rect) -> None: + """Initialize from parameter and create buttons and observer list.""" + self._rect = rect + self._buttons = [] + self._buttons.append(Button("Red", (255, 0, 0), pygame.Rect(20, 495, 100, 50), Button.MOUSE_OUT)) + self._buttons.append(Button("Blue", (0, 0, 255), pygame.Rect(130, 495, 100, 50), Button.MOUSE_OUT)) + self._buttons.append(Button("White", (255, 255, 255), pygame.Rect(240, 495, 100, 50), Button.MOUSE_OUT)) + self._buttons.append(Button("Yellow", (255, 255, 0),pygame.Rect(350, 495, 100, 50), Button.SELECTED)) + self._selected = self._buttons[3] + self._observers = [] + + def attach(self, observer: object) -> None: + """Allow an observer to attach.""" + self._observers.append(observer) + + def detach(self, observer: object) -> None: + """Allow an observer to detach.""" + if observer in self._observers: + observer.remove() + + def notify(self) -> None: + """Notify observers of a state change.""" + for observer in self._observers: + observer.update() + + def get_selected_color(self) -> tuple: + """Return the color of the selected button.""" + return self._selected.get_color() + + def point_in(self, point: tuple) -> bool: + """Return True if point within rect.""" + return (self._rect.left <= point[0] and self._rect.right >= point[0] + and self._rect.top <= point[1] and self._rect.bottom >= point[1]) + + def handle_click(self, point: tuple) -> None: + """Handle the click by changing button selection if appropriate.""" + # Annotate variables + new_selection: Button = None + i = 0 + while i < len(self._buttons) and new_selection == None: + if not self._selected == self._buttons[i]: + if self._buttons[i].point_in(point): + new_selection = self._buttons[i] + new_selection.set_state(Button.SELECTED) + self._selected.set_state(Button.MOUSE_OUT) + self._selected = new_selection + self.notify() + i += 1 + + def handle_mouse_over(self, point: tuple) -> None: + """Handle the mouseover by highlighting button, if appropriate.""" + for button in self._buttons: + if not self._selected == button: + button.set_state(Button.MOUSE_OUT) + if button.point_in(point): + button.set_state(Button.MOUSE_OVER) + + def draw(self, surface: pygame.Surface) -> None: + """Draw a white background and all buttons.""" + background = pygame.Surface((480, 80)) + background.fill((255, 255, 255)) + surface.blit(background, (0, 480)) + for button in self._buttons: + button.draw(surface) + +def make_window(h_size: int, v_size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen = pygame.display.set_mode((h_size, v_size)) + pygame.display.set_caption(caption) + return screen + +def read_stars(file: str, color: tuple) -> list: + """Read and return star data from file.""" + # Annotate variables + stars: list = [] + star: str + star_data: list + star_list: list + # Open and read the file. + with open(file) as star_file: + star_data = star_file.readlines() + # Split lines and organize in a 2D list. + for star in star_data: + star_list = star.split(",") + stars.append(Star(int(star_list[0]), + int(star_list[1]), + star_list[2], + color)) + return stars + +def main() -> None: + """Draw a constellation and allow the user to click to + iterate through the stars and set star color.""" + # Annotate and initialize variables + H_SIZE: int = 480 + V_SIZE: int = 560 + user_quit: bool = False + clock: pygame.time.Clock = pygame.time.Clock() + screen: pygame.Surface + background: pygame.Surface + stars: list + orion: Constellation + orion_iter: ConstellationIter + selected_star: Star + event: pygame.event.Event + button_group: ButtonGroup + + # Initialize pygame + pygame.init() + + # Set up assets. + screen = make_window(H_SIZE, V_SIZE, "Orion") + background = pygame.Surface((H_SIZE,V_SIZE)) + button_group = ButtonGroup(pygame.Rect(0, H_SIZE, H_SIZE, V_SIZE-H_SIZE)) + + # Read in star data and create the constellation. + stars = read_stars("orion.txt", (255, 255, 0)) + orion = Constellation(stars, "Orion", button_group) + + # Set up an iterator over the constellation. + orion_iter = iter(orion) + selected_star = None + + # Run until the user closes the window. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + elif event.type == pygame.MOUSEMOTION: + button_group.handle_mouse_over(event.__dict__["pos"]) + elif event.type == pygame.MOUSEBUTTONUP: + # Check if it's a button click + if button_group.point_in(event.__dict__["pos"]): + button_group.handle_click(event.__dict__["pos"]) + # Otherwise it's within the constellation pane + else: + # Deselect current selection and select + # next star in the Constellation + try: + if selected_star != None: + selected_star.select(False) + selected_star = next(orion_iter) + if selected_star != None: + selected_star.select(True) + name = selected_star.get_name() + pygame.display.set_caption(name) + # Create a new iterator and do it over if + # we've reached the last star. + except StopIteration: + orion_iter.first() + pygame.display.set_caption("Orion") + + screen.blit(background,(0,0)) + orion.draw(screen) + button_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_06/Listing 6-2.py b/Chapter_06/Listing 6-2.py new file mode 100644 index 0000000..266dbdb --- /dev/null +++ b/Chapter_06/Listing 6-2.py @@ -0,0 +1,79 @@ +"""A more complicated example of saving program state + with the pickle library.""" + +import pickle +import io + +class Recipe: + """A Recipe object represents a single recipe.""" + # Annotate object-level variables + _ingredients: list + _instructions: list + + def __init__(self, ingredients: list, instructions: list) -> None: + """Initialize from parameters.""" + self._ingredients = ingredients + self._instructions = instructions + + def __str__(self) -> str: + """Return a string representing the recipe.""" + return str(self._ingredients) + "\n" + str(self._instructions) + +class CookingSite: + """Represent a single cooking website.""" + # Annotate object-level variables + _url: str + _name: str + _review: str + + def __init__(self, url: str, name: str, review: str) -> None: + """Initialize from parameters.""" + self._url = url + self._name = name + self._review = review + + def __str__(self) -> str: + """Return a string representation of the cooking site.""" + return (self._name + "\n" + self._url + "\n" + self._review) + +def main(): + """Demonstrate pickling more complicated data.""" + # Annotate variables + file_out: io.BufferedWriter + file_in: io.BufferedReader + recipes: list + websites: list + + # Create the program state. + recipes = [Recipe(["box of cheesy mac", "water", "butter"], + ["Cook mac", "Add cheese and butter"]), + Recipe(["frozen pizza", "vegetables"], + ["Cook pizza", "Make a salad out of veg"])] + websites = [CookingSite("www.fastrecipes.com", "Fast Recipes", + "Fast!"), + CookingSite("www.fancyrecipes.com", "Fancy Recipes", + "Fancy.")] + + # Display recipe and website data. + for recipe in recipes: + print(recipe) + for website in websites: + print(website) + # Write recipe and website data to a binary file. + with open("recipes", "wb") as file_out: + pickle.dump(recipes, file_out) + pickle.dump(websites, file_out) + + # Read recipe and website data from a binary file. + with open("recipes", "rb") as file_in: + recipes = pickle.load(file_in) + websites = pickle.load(file_in) + # Display recipe and website data. + for recipe in recipes: + print(recipe) + for website in websites: + print(website) + +main() + + diff --git a/Chapter_06/Listing 6-3.py b/Chapter_06/Listing 6-3.py new file mode 100644 index 0000000..4926306 --- /dev/null +++ b/Chapter_06/Listing 6-3.py @@ -0,0 +1,88 @@ +"""Manage a library summer reading program.""" +# Warning: quit command not implemented! + +class MenuItem(): + """An Invoker object in the Command design pattern. + + Public methods: __init__, get_text, action + """ + + # Annotate object-level fields + _menu_text: str + _command: object + + def __init__(self, menu_text: str, command: object) -> None: + """Initialize a MenuItem from parameters.""" + self._menu_text = menu_text + self._command = command + + def get_text(self) -> str: + """Return the interface text.""" + return self._menu_text + + def action(self) -> object: + """Execute the command.""" + return None + +class Menu(): + """The loop that drives the program through a text menu. + There should be only one Menu per program. + + Public methods: __init__, go, quit + """ + + # Annotate object-level fields + _menu_items: list + _quit: bool = False + + def __init__(self) -> None: + """Initialize Menu object and create menu.""" + self._menu_items = [] + + def _show_menu(self) -> int: + """Obtain and return the user's menu choice.""" + # Annotate and initialize variable + choice: int = 0 + while choice < 1 or choice > len(self._menu_items): + for i in range(len(self._menu_items)): + print(str(i + 1) + ". " + + self._menu_items[i].get_text()) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + + def add_menu_item(self, menu_item: MenuItem) -> None: + """Add the menu_item to the menu.""" + self._menu_items.append(menu_item) + + def remove_menu_item(self, menu_item: MenuItem) -> None: + """Remove the menu_item from the menu.""" + if menu_item in self._menu_items: + self._menu_items.remove(menu_item) + + def quit(self) -> None: + """Quit the program.""" + self._quit = True + + def go(self) -> None: + """Display menu and process choices until quit.""" + # Annotate and initialize variable + choice: int = 0 + while not self._quit: + choice = self._show_menu() + new_command = self._menu_items[choice - 1].action() + +def main() -> None: + """Create the menu and go.""" + # Annotate constants + ADD: str = "Add a reader" + VIEW: str = "View all readers" + QUIT: str = "Quit" + + menu: Menu = Menu() + menu.add_menu_item(MenuItem(ADD, None)) + menu.add_menu_item(MenuItem(VIEW, None)) + menu.add_menu_item(MenuItem(QUIT, None)) + menu.go() + +main() diff --git a/Chapter_06/Listing 6-4.py b/Chapter_06/Listing 6-4.py new file mode 100644 index 0000000..c6a8e2d --- /dev/null +++ b/Chapter_06/Listing 6-4.py @@ -0,0 +1,115 @@ +"""Manage a library summer reading program.""" + +class Command(): + """Abstract superclass for command structure. + + Public methods: __init__, execute, undo + """ + + # Annotate object-level fields + _executor: object + + def __init__(self, executor: object) -> None: + """Initialize from parameter.""" + self._executor = executor + + def execute(self): + """Execute the command.""" + pass + +class QuitCommand(Command): + """A command to quit the program. + + Public methods: __init__, execute, undo + """ + + #_executor: Menu + def execute(self) -> None: + self._executor.quit() + +class MenuItem(): + """An Invoker object in the Command design pattern. + + Public methods: __init__, get_text, action + """ + + # Annotate object-level fields + _menu_text: str + _command: object + + def __init__(self, menu_text: str, command: object) -> None: + """Initialize a MenuItem from parameters.""" + self._menu_text = menu_text + self._command = command + + def get_text(self) -> str: + """Return the interface text.""" + return self._menu_text + + def action(self) -> object: + """Execute the command.""" + self._command.execute() + return None + +class Menu(): + """The loop that drives the program through a text menu. + There should be only one Menu per program. + + Public methods: __init__, go, quit + """ + + # Annotate object-level fields + _menu_items: list + _quit: bool = False + + def __init__(self) -> None: + """Initialize Menu object and create menu.""" + self._menu_items = [] + + def _show_menu(self) -> int: + """Obtain and return the user's menu choice.""" + # Annotate and initialize variable + choice: int = 0 + while choice < 1 or choice > len(self._menu_items): + for i in range(len(self._menu_items)): + print(str(i + 1) + ". " + + self._menu_items[i].get_text()) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + + def add_menu_item(self, menu_item: MenuItem) -> None: + """Add the menu_item to the menu.""" + self._menu_items.append(menu_item) + + def remove_menu_item(self, menu_item: MenuItem) -> None: + """Remove the menu_item from the menu.""" + if menu_item in self._menu_items: + self._menu_items.remove(menu_item) + + def quit(self) -> None: + """Quit the program.""" + self._quit = True + + def go(self) -> None: + """Display menu and process choices until quit.""" + # Annotate and initialize variable + choice: int = 0 + while not self._quit: + choice = self._show_menu() + new_command = self._menu_items[choice - 1].action() + +def main() -> None: + """Create the menu and go.""" + # Annotate constants + ADD: str = "Add a reader" + VIEW: str = "View all readers" + QUIT: str = "Quit" + + menu: Menu = Menu() + menu.add_menu_item(MenuItem(ADD, Command(menu))) + menu.add_menu_item(MenuItem(VIEW, Command(menu))) + menu.add_menu_item(MenuItem(QUIT, QuitCommand(menu))) + menu.go() + +main() diff --git a/Chapter_06/Listing 6-5.py b/Chapter_06/Listing 6-5.py new file mode 100644 index 0000000..cee7340 --- /dev/null +++ b/Chapter_06/Listing 6-5.py @@ -0,0 +1,232 @@ +"""Manage a library summer reading program.""" + +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate object-level fields + _name: str + _minutes: list + + def __init__(self, name: str, minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> str: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str: str = "Name: " + self._name + minutes_str: str = ("Minutes: " + + str(self.get_total_minutes())) + return name_str + "\n" + minutes_str + +class ReaderManager(): + """A class that manages the list of readers. + + Public methods: __init__, add_reader, remove_reader, + display_readers + """ + + # Annotate object-level fields + _readers: list + + def __init__(self) -> None: + """Create an empty list for Reader objects.""" + self._readers = [] + + def add_reader(self, reader) -> None: + """Add a Reader object to the list.""" + self._readers.append(reader) + + def remove_reader(self, reader) -> None: + """Remove a Reader object from the list.""" + self._readers.remove(reader) + + def display_readers(self) -> None: + """Display all Reader objects in the list.""" + if len(self._readers) > 0: + for reader in self._readers: + print(reader) + else: + print("There are no readers.") + +class Command(): + """Abstract superclass for command structure. + + Public methods: __init__, execute + """ + + # Annotate object-level fields + _executor: object + + def __init__(self, executor: object) -> None: + """Initialize from parameter.""" + self._executor = executor + + def execute(self) -> None: + """Execute the command.""" + pass + +class QuitCommand(Command): + """A command to quit the program. + + Public methods: __init__, execute + """ + + #_executor: Menu + def execute(self) -> None: + self._executor.quit() + +class AddCommand(Command): + """A command to add a reader or undo. + + Public methods: __init__, execute + """ + + # Annotate object-level fields + _reader: Reader + #_executor: ReaderManager + + def __init__(self, executor: object) -> None: + """Initialize from parameter.""" + super().__init__(executor) + self._reader = None + + def execute(self) -> None: + """Create a Reader and add to the list.""" + # Annotate variables. + name: str + has_minutes: str + minutes: int + + # Create a reader. + name = input("What is the child's name? ") + has_minutes = input("Has the child read? (Y or N) ") + if has_minutes == "Y": + minutes = int(input("How many minutes? ")) + self._reader = Reader(name, minutes) + else: + self._reader = Reader(name) + + # Ask the executor to add the reader. + self._executor.add_reader(self._reader) + +class ViewAllCommand(Command): + """A command to view all readers. + + Public methods: __init__, execute + """ + #_executor: ReaderManager + + def execute(self) -> None: + """Show all readers.""" + self._executor.display_readers() + + +class MenuItem(): + """An Invoker object in the Command design pattern. + + Public methods: __init__, get_text, action + """ + + # Annotate object-level fields + _menu_text: str + _command: object + + def __init__(self, menu_text: str, command: object) -> None: + """Initialize a MenuItem from parameters.""" + self._menu_text = menu_text + self._command = command + + def get_text(self) -> str: + """Return the interface text.""" + return self._menu_text + + def action(self) -> object: + """Execute the command.""" + self._command.execute() + return None + +class Menu(): + """The loop that drives the program through a text menu. + There should be only one Menu per program. + + Public methods: __init__, go, quit + """ + + # Annotate object-level fields + _menu_items: list + _quit: bool = False + + def __init__(self) -> None: + """Initialize Menu object and create menu.""" + self._menu_items = [] + + def _show_menu(self) -> int: + """Obtain and return the user's menu choice.""" + # Annotate and initialize variable + choice: int = 0 + while choice < 1 or choice > len(self._menu_items): + for i in range(len(self._menu_items)): + print(str(i + 1) + ". " + + self._menu_items[i].get_text()) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + + def add_menu_item(self, menu_item: MenuItem) -> None: + """Add the menu_item to the menu.""" + self._menu_items.append(menu_item) + + def remove_menu_item(self, menu_item: MenuItem) -> None: + """Remove the menu_item from the menu.""" + if menu_item in self._menu_items: + self._menu_items.remove(menu_item) + + def quit(self) -> None: + """Quit the program.""" + self._quit = True + + def go(self) -> None: + """Display menu and process choices until quit.""" + # Annotate and initialize variable + choice: int = 0 + while not self._quit: + choice = self._show_menu() + new_command = self._menu_items[choice - 1].action() + +def main() -> None: + """Create the menu and go.""" + # Annotate constants + ADD: str = "Add a reader" + VIEW: str = "View all readers" + QUIT: str = "Quit" + + # Annotate and initialize variables + menu: Menu = Menu() + reader_manager: ReaderManager = ReaderManager() + + menu.add_menu_item(MenuItem(ADD, AddCommand(reader_manager))) + menu.add_menu_item(MenuItem(VIEW, ViewAllCommand(reader_manager))) + menu.add_menu_item(MenuItem(QUIT, QuitCommand(menu))) + menu.go() + +main() diff --git a/Chapter_06/Listing 6-6.py b/Chapter_06/Listing 6-6.py new file mode 100644 index 0000000..7e145ec --- /dev/null +++ b/Chapter_06/Listing 6-6.py @@ -0,0 +1,283 @@ +"""Manage a library summer reading program.""" +import copy + +class Reader(): + """A class representing a participant in the + library reading program. + + Public methods: __init__, get_name, get_total_minutes, + add_minutes, __str__ + """ + + # Annotate object-level fields + _name: str + _minutes: list + + def __init__(self, name: str, minutes_read: int = 0) -> None: + """Initialize a Reader with name and minutes.""" + self._name = name + self._minutes = [] + if minutes_read != 0: + self._minutes.append(minutes_read) + + def get_name(self) -> str: + """Return the reader's name.""" + return self._name + + def get_total_minutes(self) -> str: + """Return the total minutes read.""" + return sum(self._minutes) + + def add_minutes(self, minutes_read: int) -> None: + """Add minutes_read to minutes.""" + self._minutes.append(minutes_read) + + def __str__(self) -> str: + """Return a string version of the reader.""" + name_str: str = "Name: " + self._name + minutes_str: str = ("Minutes: " + + str(self.get_total_minutes())) + return name_str + "\n" + minutes_str + +class ReaderManager(): + """A class that manages the list of readers. + + Public methods: __init__, add_reader, remove_reader, + display_readers + """ + + # Annotate object-level fields + _readers: list + + def __init__(self) -> None: + """Create an empty list for Reader objects.""" + self._readers = [] + + def add_reader(self, reader) -> None: + """Add a Reader object to the list.""" + self._readers.append(reader) + + def remove_reader(self, reader) -> None: + """Remove a Reader object from the list.""" + self._readers.remove(reader) + + def display_readers(self) -> None: + """Display all Reader objects in the list.""" + if len(self._readers) > 0: + for reader in self._readers: + print(reader) + else: + print("There are no readers.") + +class Command(): + """Abstract superclass for command structure. + + Public methods: __init__, execute, undo + """ + + # Annotate object-level fields + _executor: object + + def __init__(self, executor: object) -> None: + """Initialize from parameter.""" + self._executor = executor + + def execute(self) -> None: + """Execute the command.""" + pass + + def undo(self) -> None: + """Undo the command.""" + pass + +class QuitCommand(Command): + """A command to quit the program. + + Public methods: __init__, execute + """ + + #_executor: Menu + def execute(self) -> None: + self._executor.quit() + +class AddCommand(Command): + """A command to add a reader or undo. + + Public methods: __init__, execute, undo + """ + + # Annotate object-level fields + _reader: Reader + #_executor: ReaderManager + + def __init__(self, executor: object) -> None: + """Initialize from parameter.""" + super().__init__(executor) + self._reader = None + + def execute(self) -> None: + """Create a Reader and add to the list.""" + # Annotate variables. + name: str + has_minutes: str + minutes: int + + # Create a reader. + if not self._reader: + name = input("What is the child's name? ") + has_minutes = input("Has the child read? (Y or N) ") + if has_minutes == "Y": + minutes = int(input("How many minutes? ")) + self._reader = Reader(name, minutes) + else: + self._reader = Reader(name) + + # Ask the executor to add the reader. + self._executor.add_reader(self._reader) + + def undo(self) -> None: + """Remove the reader from the list.""" + # Ask the executor to remove the reader. + self._executor.remove_reader(self._reader) + + +class ViewAllCommand(Command): + """A command to view all readers. + + Public methods: __init__, execute + """ + #_executor: ReaderManager + + def execute(self) -> None: + """Show all readers.""" + self._executor.display_readers() + +class UndoCommand(Command): + """A command to undo the last command. + + Public methods: __init__, execute + """ + + #_executor: Menu + def execute(self) -> None: + """Undo the last undoable command.""" + self._executor.undo_last_command() + + +class MenuItem(): + """An Invoker object in the Command design pattern. + + Public methods: __init__, get_text, action + """ + + # Annotate object-level fields + _menu_text: str + _command: object + + def __init__(self, menu_text: str, command: object) -> None: + """Initialize a MenuItem from parameters.""" + self._menu_text = menu_text + self._command = command + + def get_text(self) -> str: + """Return the interface text.""" + return self._menu_text + + def action(self) -> object: + """Execute the command.""" + self._command.execute() + return None + +class UndoableMenuItem(MenuItem): + """A MenuItem that can be undone. + + Public methods: action + """ + + def action(self): + command_copy = copy.copy(self._command) + command_copy.execute() + return command_copy + +class Menu(): + """The loop that drives the program through a text menu. + There should be only one Menu per program. + + Public methods: __init__, go, quit + """ + + # Annotate object-level fields + _menu_items: list + _undoable_commands: list + _quit: bool = False + + def __init__(self) -> None: + """Initialize Menu object and create menu.""" + self._menu_items = [] + self._undoable_commands = [] + + def _show_menu(self) -> int: + """Obtain and return the user's menu choice.""" + # Annotate and initialize variable + choice: int = 0 + while choice < 1 or choice > len(self._menu_items): + for i in range(len(self._menu_items)): + print(str(i + 1) + ". " + + self._menu_items[i].get_text()) + choice = int(input("Please enter the number of " + + "your choice: ")) + return choice + + def add_menu_item(self, menu_item: MenuItem) -> None: + """Add the menu_item to the menu.""" + self._menu_items.append(menu_item) + + def remove_menu_item(self, menu_item: MenuItem) -> None: + """Remove the menu_item from the menu.""" + if menu_item in self._menu_items: + self._menu_items.remove(menu_item) + + def undo_last_command(self) -> None: + """Undo the last command, if any.""" + num_commands = len(self._undoable_commands) + if num_commands > 0: + last_command = self._undoable_commands[-1] + last_command.undo() + self._undoable_commands.remove(last_command) + else: + print("There are no undoable commands.") + + def quit(self) -> None: + """Quit the program.""" + self._quit = True + + def go(self) -> None: + """Display menu and process choices until quit.""" + # Annotate and initialize variable + choice: int = 0 + new_command: Command + while not self._quit: + choice = self._show_menu() + new_command = self._menu_items[choice - 1].action() + if new_command: + self._undoable_commands.append(new_command) + +def main() -> None: + """Create the menu and go.""" + # Annotate constants + ADD: str = "Add a reader" + VIEW: str = "View all readers" + UNDO: str = "Undo last command" + QUIT: str = "Quit" + + # Annotate and initialize variables + menu: Menu = Menu() + reader_manager: ReaderManager = ReaderManager() + + menu.add_menu_item(UndoableMenuItem(ADD, AddCommand(reader_manager))) + menu.add_menu_item(MenuItem(VIEW, ViewAllCommand(reader_manager))) + menu.add_menu_item(MenuItem(UNDO, UndoCommand(menu))) + menu.add_menu_item(MenuItem(QUIT, QuitCommand(menu))) + menu.go() + +main() diff --git a/Chapter_06/Listing 6-7.py b/Chapter_06/Listing 6-7.py new file mode 100644 index 0000000..5ed3e12 --- /dev/null +++ b/Chapter_06/Listing 6-7.py @@ -0,0 +1,79 @@ +"""Illustrate the iterator pattern with a row-major table.""" + +class TableIterator(): + """Iterate over a Table object. + Public methods: __init__, __iter__, __next__ + """ + # Annotate object-level fields + _table: "Table" + _last_row: int + _last_col: int + _row: int + _col: int + + def __init__(self, table: "Table", num_rows: int, + num_cols: int) -> None: + """Initialize fields from parameters + and set indices to first element.""" + self._table = table + self._last_row = num_rows - 1 + self._last_col = num_cols - 1 + self._row = 0 + self._col = 0 + + def __iter__(self) -> "TableIterator": + """Return this object as the iterator.""" + return self + + def __next__(self) -> int: + """Return the current element and increment indices + or throw exception if out of elements.""" + # Annotate variable + item: int + # Check to see if we're done and raise exception. + if self._row == self._last_row + 1: + raise StopIteration + # If we're still here, get item and increment indices. + item = self._table[(self._row, self._col)] + if self._col == self._last_col: + self._row += 1 + self._col = 0 + else: + self._col += 1 + # Return the item. + return item + +class Table(): + """Manage a row-major table of integers. + Public methods: __init__, __iter__, __getitem__ + """ + + # Annotate object-level fields + _data: list + + def __init__(self, data: list) -> None: + """Initialize field from parameter. + data is a 2d list of at least one row.""" + self._data = data + + def __getitem__(self, indices: tuple) -> int: + """Return item at index indices[0], indices[1].""" + return self._data[indices[0]][indices[1]] + + def __iter__(self) -> TableIterator: + """Return an iterator.""" + return TableIterator(self, + len(self._data), + len(self._data[0])) + +def main(): + """Create a table and iterate over it.""" + # Annotate variables + table: Table + item: int + + table = Table([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + for item in table: + print(item) + +main() diff --git a/Chapter_06/Listing 6-8.py b/Chapter_06/Listing 6-8.py new file mode 100644 index 0000000..eccdbcf --- /dev/null +++ b/Chapter_06/Listing 6-8.py @@ -0,0 +1,84 @@ +"""Illustrate the iterator pattern with a row-major table.""" + +class TableIterator(): + """Iterate over a Table object. + Public methods: __init__, __iter__, __next__ + """ + # Annotate object-level fields + _table: "Table" + _last_row: int + _last_col: int + _row: int + _col: int + + def __init__(self, table: "Table", num_rows: int, + num_cols: int) -> None: + """Initialize fields from parameters + and set indices to first element.""" + self._table = table + self._last_row = num_rows - 1 + self._last_col = num_cols - 1 + self._row = 0 + self._col = 0 + + def __iter__(self) -> "TableIterator": + """Return this object as the iterator.""" + return self + + def __next__(self) -> int: + """Return the current element and increment indices + or throw exception if out of elements.""" + # Annotate variable + item: int + # Check to see if we're done and raise exception. + if self._row == self._last_row + 1: + raise StopIteration + # If we're still here, get item and increment indices. + item = self._table[(self._row, self._col)] + if self._col == self._last_col: + self._row += 1 + self._col = 0 + else: + self._col += 1 + # Return the item. + return item + +class Table(): + """Manage a row-major table of integers. + Public methods: __init__, __iter__, __getitem__ + """ + + # Annotate object-level fields + _data: list + + def __init__(self, data: list) -> None: + """Initialize field from parameter. + data is a 2d list of at least one row.""" + self._data = data + + def __getitem__(self, indices: tuple) -> int: + """Return item at index indices[0], indices[1].""" + return self._data[indices[0]][indices[1]] + + def __iter__(self) -> TableIterator: + """Return an iterator.""" + return TableIterator(self, + len(self._data), + len(self._data[0])) + +def main(): + """Create a table and iterate over it.""" + # Annotate and initialize variables + item: int + done: bool = False + table: Table = Table([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + table_iter: TableIterator = iter(table) + + while not done: + try: + item = next(table_iter) + print(item) + except StopIteration: + done = True + +main() diff --git a/Chapter_06/Listing 6-9.py b/Chapter_06/Listing 6-9.py new file mode 100644 index 0000000..90defbd --- /dev/null +++ b/Chapter_06/Listing 6-9.py @@ -0,0 +1,202 @@ +"""A constellation labeling program.""" +import pygame +pygame.init() + +class Star(): + """A class representing a star. + + Public methods: __init__, draw, select, get_name + """ + + # Annotate object-level fields + _x: float + _y: float + _name: str + _star: pygame.Surface + _star_selected: pygame.Surface + _color: tuple + + def __init__(self, x: float, y: float, name: str, color: tuple) -> None: + """Initialize an instance of star at x,y.""" + self._x = x + self._y = y + self._name = name + self._selected = False + self._color = color + # self._star is a small circle in the star's color + self._star = pygame.Surface((20,20)) + pygame.draw.circle(self._star, self._color, (10, 10), 5) + # self._star_selected is a larger white circle + self._star_selected = pygame.Surface((20,20)) + pygame.draw.circle(self._star_selected, (255, 255, 255), (10, 10), 10) + + def select(self, selected: bool) -> None: + """Select or deselect according to parameter.""" + self._selected = selected + + def get_name(self) -> str: + """Return the star's name.""" + return self._name + + def draw(self, surface: pygame.Surface) -> None: + """Draw the star on the surface.""" + if self._selected: + surface.blit(self._star_selected, (self._x, self._y)) + else: + surface.blit(self._star, (self._x, self._y)) + +class ConstellationIter(): + """An iterator for iterating over a Constellation's stars. + + Public methods: __init__, __iter__, __next__ + """ + + # Annotate object-level variables + _constellation: "Constellation" + _star_index: int + _num_stars: int + + def __init__(self, constellation: "Constellation", num_stars: int) -> None: + """Initialize from parameters and set index to zero.""" + self._constellation = constellation + self._num_stars = num_stars + self._star_index = 0 + + def __iter__(self) -> "ConstellationIter": + """Return self as required by Python.""" + return self + + def __next__(self) -> Star: + """Return the next Star in the Constellation or raise exception + and advance the index.""" + # Annotate variable + star: Star + if self._star_index == self._num_stars: + raise StopIteration + else: + star = self._constellation[self._star_index] + self._star_index += 1 + return star + + def first(self) -> None: + """Reset the iterator to the first element.""" + self._star_index = 0 + + +class Constellation(): + """A class representing a constellation. + + Public methods: __init__, draw, __getitem__, __iter__ + get_name + """ + + # Annotate object-level fields + _stars: list + _name: str + + def __init__(self, stars: list, name: str) -> None: + """Initialize from parameters.""" + self._stars = stars + self._name = name + + def draw(self, surface: pygame.Surface) -> None: + """Draw the constellation.""" + for star in self._stars: + star.draw(surface) + + def get_name(self) -> str: + """Return the constellation name.""" + return self._name + + def __getitem__(self, star_index: int) -> Star: + """Allow indexing into a Constellation, return star at + star_index.""" + return self._stars[star_index] + + def __iter__(self) -> ConstellationIter: + """Create and return a ConstellationIter.""" + return ConstellationIter(self, len(self._stars)) + +def make_window(h_size: int, v_size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen = pygame.display.set_mode((h_size, v_size)) + pygame.display.set_caption(caption) + return screen + +def read_stars(file: str, color: tuple) -> list: + """Read and return star data from file.""" + # Annotate variables + stars: list = [] + star: str + star_data: list + star_list: list + # Open and read the file. + with open(file) as star_file: + star_data = star_file.readlines() + # Split lines and organize in a 2D list. + for star in star_data: + star_list = star.split(",") + stars.append(Star(int(star_list[0]), + int(star_list[1]), + star_list[2], color)) + return stars + +def main() -> None: + """Draw a constellation and allow the user to click to + iterate through the stars.""" + # Annotate and initialize variables + H_SIZE: int = 480 + V_SIZE: int = 480 + user_quit: bool = False + clock: pygame.time.Clock = pygame.time.Clock() + screen: pygame.Surface + background: pygame.Surface + stars: list + orion: Constellation + orion_iter: ConstellationIter + selected_star: Star + event: pygame.event.Event + + # Read in star data and create the constellation. + stars = read_stars("orion.txt", (255, 255, 0)) + orion = Constellation(stars, "Orion") + + # Set up pygame assets. + screen = make_window(H_SIZE, V_SIZE, "Orion") + background = pygame.Surface((H_SIZE,V_SIZE)) + + # Set up an iterator over the constellation. + orion_iter = iter(orion) + selected_star = None + + # Run until the user closes the window. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + elif event.type == pygame.MOUSEBUTTONUP: + # Deselect current selection and select + # next star in the Constellation + try: + if selected_star != None: + selected_star.select(False) + selected_star = next(orion_iter) + if selected_star != None: + selected_star.select(True) + name = selected_star.get_name() + pygame.display.set_caption(name) + # Reset the iterator and do it over if + # we've reached the last star. + except StopIteration: + orion_iter.first() + pygame.display.set_caption("Orion") + + screen.blit(background,(0,0)) + orion.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_06/orion.txt b/Chapter_06/orion.txt new file mode 100644 index 0000000..50fcdc7 --- /dev/null +++ b/Chapter_06/orion.txt @@ -0,0 +1,8 @@ +160, 118, Betelgeuse +247, 77, Meissa +289, 138, Bellatrix +261, 250, Mintaka +245, 265, Alnilam +224, 278, Alnitak +195, 409, Saiph +334, 383, Rigel diff --git a/Chapter_06/small_blue_star.png b/Chapter_06/small_blue_star.png new file mode 100644 index 0000000..6119629 Binary files /dev/null and b/Chapter_06/small_blue_star.png differ diff --git a/Chapter_06/small_green_star.png b/Chapter_06/small_green_star.png new file mode 100644 index 0000000..e4cd070 Binary files /dev/null and b/Chapter_06/small_green_star.png differ diff --git a/Chapter_06/small_red_star.png b/Chapter_06/small_red_star.png new file mode 100644 index 0000000..ac9a2e2 Binary files /dev/null and b/Chapter_06/small_red_star.png differ diff --git a/Chapter_06/small_yellow_star.png b/Chapter_06/small_yellow_star.png new file mode 100644 index 0000000..3b5ac23 Binary files /dev/null and b/Chapter_06/small_yellow_star.png differ diff --git a/Chapter_07/Listing 7-1.py b/Chapter_07/Listing 7-1.py new file mode 100644 index 0000000..b827182 --- /dev/null +++ b/Chapter_07/Listing 7-1.py @@ -0,0 +1,109 @@ +"""A calendar, purchase and subscription program, + to illustrate multiple inheritance. +""" +from datetime import date + +class Calendar(): + pass + +class Cart(): + pass + +class CalendarItem(): + """Represent one item in a calendar. + + Public methods: __init__, __str__, activate + """ + + # Annotate object-level fields + _date: date + _name: str + _calendar: Calendar + + def __init__(self, item_date: date, + name: str, calendar: Calendar, **kwargs) -> None: + """Initialize fields from parameters.""" + super().__init__(**kwargs) + self._date = item_date + self._name = name + self._calendar = Calendar + + def activate(self) -> None: + """Take necessary action as item date is now.""" + pass + + def __str__(self) -> str: + """Return a string version of the calendar item.""" + return (str(self._date) + "\n" + + self._name + "\n") + +class Purchase(): + """Represent a purchase. + + Public methods: __init__, __str__, add_to_cart + """ + + # Annotate object-level fields + _item: str + _description: str + _cost: float + _cart: Cart + + def __init__(self, item: str, description: str, cost: float, cart: Cart, **kwargs) -> None: + """Initialize fields from parameters.""" + print(kwargs) + super().__init__(**kwargs) + self._item = item + self._description = description + self._cost = cost + self._cart = cart + + def add_to_cart(self) -> None: + """Item is being purchased; add to cart.""" + pass + + def __str__(self) -> str: + """Return a string version of the purchase.""" + return (super().__str__() + self._item + "\n" + self._description + "\n" + + "${:.2f}".format(self._cost)) + +class Subscription(Purchase, CalendarItem): + """Represent a subscription. + + Public methods: __init__, __str__, activate + """ + + # Annotate object-level fields + _frequency: int + + def __init__(self, frequency: int, **kwargs) -> None: + """Initialize fields from parameters.""" + super().__init__(**kwargs) + self._frequency = frequency + + def activate(self) -> None: + """Add self to cart and create a copy one _frequency interval + in the future and add to calendar.""" + # Add self to cart + # Copy self with date _frequency in the future + # Add self to calendar + + def __str__(self) -> str: + """Return a string version of the subscription.""" + return (super().__str__() + "\n" + + "Every " + str(self._frequency) + " month(s)") + +def main(): + """Create and print a Subscription object.""" + subscribe = Subscription(frequency = 1, + item = "coffee", + description = "2 lb French roast", + name = "place coffee order", + item_date = date(2019, 1, 1), + cost = 50.0, + cart = Cart(), calendar = Calendar()) + + print(subscribe) + +main() + diff --git a/Chapter_07/Listing 7-2.py b/Chapter_07/Listing 7-2.py new file mode 100644 index 0000000..159ed2c --- /dev/null +++ b/Chapter_07/Listing 7-2.py @@ -0,0 +1,71 @@ +"""A simplified calendar, purchase and subscription program, + to illustrate naming conflicts. +""" + +class CalendarItem(): + """Represent one item in a calendar. + + Public methods: __init__, duplicate, overridden + """ + + # Annotate object-level fields + name: str + + def __init__(self) -> None: + """Initialize fields from parameters.""" + self.name = "calendar_item" + + def duplicate(self) -> None: + print("In CalendarItem duplicate.") + + def overridden(self) -> None: + print("In CalendarItem overridden.") + +class Purchase(): + """Represent a purchase. + + Public methods: __init__, duplicate, overridden + """ + + # Annotate object-level fields + name: str + + def __init__(self) -> None: + """Initialize fields from parameters.""" + self.name = "purchase" + + def duplicate(self) -> None: + print("In Purchase duplicate.") + + def overridden(self) -> None: + print("In Purchase overridden.") + +class Subscription(Purchase, CalendarItem): + """Represent a subscription. + + Public methods: __init__, overridden + """ + + def __init__(self) -> None: + """Initialize fields from parameters.""" + super().__init__() + +## def overridden(self) -> None: +## super().overridden() +## print("In Subscription overridden.") + + def overridden(self) -> None: + CalendarItem.overridden(self) + Purchase.overridden(self) + print("In Subscription overridden.") + +def main(): + """Create and print a Subscription object.""" + subscribe = Subscription() + print(subscribe.name) + subscribe.duplicate() + subscribe.overridden() + + +main() + diff --git a/Chapter_07/Listing 7-3.py b/Chapter_07/Listing 7-3.py new file mode 100644 index 0000000..772b039 --- /dev/null +++ b/Chapter_07/Listing 7-3.py @@ -0,0 +1,259 @@ +"""A projectile motion program demonstrating an observer + pattern with multiple inheritance.""" +import math +import pygame +pygame.init() +class Observer(): + """An observer mixin for the observer pattern. + + Public methods: __init__, update + """ + # Annotate object-level fields + executor: object + + def __init__(self, executor: object, **kwargs) -> None: + """Initialize field from parameter.""" + super().__init__(**kwargs) + self.executor = executor + self.executor.attach(self) + + def update() -> None: + pass + +class Projectile(): + """A projectile motion calculator. + + Public methods: __init__, handle_key, notify, + attach, detach, get_updated_data + """ + # Annotate and initialize class-level constants + NONE: int = 0 + ANGLE: int = 1 + VELOCITY: int = 2 + RADIANS_MOD: float = math.pi / 180 + VELOCITY_MOD: float = 1.0 + + # Annotate object-level fields + _angle_radians: float + _velocity: float + _mode: int + _observers: list + + def __init__(self, angle: float, velocity: float) -> None: + """Initialize fields from parameters, init mode and observers.""" + self._angle_radians = angle + self._velocity = velocity + self._mode = Projectile.NONE + self._observers = [] + + def attach(self, observer: Observer) -> None: + """Attach the observer parameter.""" + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + """Detach the observer parameter.""" + if observer in self._observers: + self._observers.remove(observer) + + def notify(self) -> None: + """Notify all observers of a change in state.""" + for observer in self._observers: + observer.update() + + def get_angle(self) -> float: + """Return the angle.""" + return self._angle_radians + + def get_velocity(self) -> float: + """Return the velocity.""" + return self._velocity + + def get_flight_time(self) -> float: + """Return the time of flight.""" + return math.ceil(2 * math.sin(self._angle_radians) * self._velocity / 9.8) + + def get_max_height(self) -> float: + """Return the maximum height.""" + return self._velocity**2 * (math.sin(self._angle_radians))**2 / (2 * 9.8) + + def get_horizontal_range(self) -> float: + """Return the distance traveled.""" + return self._velocity**2 * math.sin(2 * self._angle_radians) / 9.8 + + def handle_key(self, key: int) -> None: + """Handle a keypress.""" + # Change the mode. + if key == pygame.K_a: + self._mode = Projectile.ANGLE + elif key == pygame.K_v: + self._mode = Projectile.VELOCITY + # Change a value. + elif key == pygame.K_UP: + if self._mode == Projectile.ANGLE: + self._angle_radians += Projectile.RADIANS_MOD + self.notify() + elif self._mode == Projectile.VELOCITY: + self._velocity += Projectile.VELOCITY_MOD + self.notify() + elif key == pygame.K_DOWN: + if self._mode == Projectile.ANGLE: + self._angle_radians -= Projectile.RADIANS_MOD + self.notify() + elif self._mode == Projectile.VELOCITY: + self._velocity -= Projectile.VELOCITY_MOD + self.notify() + +class TextView(): + """Displays text information on the screen. + + Public methods: __init__, draw + """ + # Annotate object-level fields + _x: float + _y: float + _text: str + + def __init__(self, x: float, y: float, **kwargs) -> None: + """Initialize fields from parameters.""" + super().__init__(**kwargs) + self._x = x + self._y = y + self._text = "" + + def set_text(self, text: str) -> None: + """Set the label text.""" + self._text = text + + def draw(self, surface: pygame.Surface) -> None: + """Draw to surface.""" + # Annotate variables + font: pygame.font.Font + label_surface: pygame.Surface + + font = pygame.font.SysFont("Helvetica", 30) + label_surface = font.render(self._text, + True, (255, 255, 255)) + surface.blit(label_surface, (self._x, self._y)) + +class TextViewObserver(TextView, Observer): + """A TextView that is synchronized with an object. + + Public methods: __init__, update + """ + + def __init__(self, **kwargs) -> None: + """Initialize superclass parts of the object and update.""" + super().__init__(**kwargs) + radians: float = self.executor.get_angle() + velocity: float = self.executor.get_velocity() + self.set_text("{:.1f} degrees, {:.1f} m/s".format(math.degrees(radians), velocity)) + + def update(self) -> None: + """Update the textview based on new data.""" + radians: float = self.executor.get_angle() + velocity: float = self.executor.get_velocity() + self.set_text("{:.1f} degrees, {:.1f} m/s".format(math.degrees(radians), velocity)) + +class ProjectilePlot(): + """A plot of a projectile motion equation. + + Public methods: __init__, draw + """ + # Annotate object-level fields + _x: float + _y: float + _plot: pygame.Surface + + def __init__(self, x: float, y: float, **kwargs) -> None: + """Initialize fields from parameters.""" + super().__init__(**kwargs) + self._x = x + self._y = y + self._plot = pygame.Surface((300, 300)) + + def update_plot(self, v_zero: float, theta: float) -> None: + """Update the plot to reflect new values.""" + # Clear the drawing. + self._plot.fill((255, 255, 255)) + # Draw a grid. + for x in range(10, 301, 10): + pygame.draw.line(self._plot, (200, 200, 200), (x, 0), (x, 290)) + for y in range(10, 301, 10): + pygame.draw.line(self._plot, (200, 200, 200), (10, y), (300, y)) + # Plot the projectile. + time: float = math.ceil(2 * math.sin(theta) * v_zero / 9.8) + for t in range(time + 1): + x = v_zero * math.cos(theta) * t + y = v_zero * math.sin(theta) * t - (.5 * 9.8 * t**2) + pygame.draw.circle(self._plot, (0, 0, 0), (int(10 + x), int(290-y)), 3) + + def draw(self, surface: pygame.Surface) -> None: + """Draw the plot.""" + surface.blit(self._plot, (self._x, self._y)) + +class PlotObserver(ProjectilePlot, Observer): + """A plot that is synchronized with an object. + + Public methods: __init__, update + """ + def __init__(self, **kwargs) -> None: + """Initialize superclass parts of the object and update.""" + super().__init__(**kwargs) + self.update_plot(self.executor.get_velocity(), self.executor.get_angle()) + + def update(self) -> None: + """Update the plot based on new data.""" + self.update_plot(self.executor.get_velocity(), self.executor.get_angle()) + +def main(): + """Run the main event loop.""" + # Annotate variables. + projectile: Projectile + event: pygame.event.Event + text_view: TextViewObserver + plot: PlotObserver + screen: pygame.Surface + background: pygame.Surface + key: int + user_quit: bool = False + clock: pygame.time.Clock = pygame.time.Clock() + + # Initialize pygame and enable events for pressed keys. + pygame.init() + pygame.key.set_repeat(1, 100) + + # Create assets. + projectile = Projectile(math.pi / 6, 100) + text_view = TextViewObserver(executor = projectile, + x = 15, y = 15) + plot = PlotObserver(executor = projectile, x = 90, y = 100) + + # Create a window. + screen = pygame.display.set_mode((480, 480)) + pygame.display.set_caption("Projectile motion") + background = pygame.Surface((480, 480)) + background.fill((90, 110, 130)) + + # Run until the user closes the window. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for event in pygame.event.get(): + if event.type == pygame.QUIT: + user_quit = True + elif event.type == pygame.KEYDOWN: + # Send key events to the Projectile + key = event.__dict__["key"] + projectile.handle_key(key) + + # Draw the background and picture + screen.blit(background,(0,0)) + text_view.draw(screen) + plot.draw(screen) + pygame.display.flip() + +main() +pygame.quit() + + + diff --git a/Chapter_08/Listing 8-1.py b/Chapter_08/Listing 8-1.py new file mode 100644 index 0000000..211754a --- /dev/null +++ b/Chapter_08/Listing 8-1.py @@ -0,0 +1,42 @@ +"""A simple event loop.""" +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Process events by displaying event information.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SIZE, "Try generating events!") + clock: pygame.time.Clock = pygame.time.Clock() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Display event information. + print("{:7s}{}".format("Name: ", pygame.event.event_name(e.type))) + print("{:7s}{}".format("Type: ", e.type)) + print("{:7s}{}".format("Dict: ", e.__dict__)) + print("*******") + # Show the display. + pygame.display.flip() + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-2.py b/Chapter_08/Listing 8-2.py new file mode 100644 index 0000000..72738f4 --- /dev/null +++ b/Chapter_08/Listing 8-2.py @@ -0,0 +1,66 @@ +"""Move with the mouse.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Process mouse events by reacting with a blob.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + blob: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + blob_x: int = 0 + blob_y: int = 0 + caption: str = "The blob!" + + # Set up assets. + screen = make_window(SIZE, caption) + background = pygame.Surface((SIZE, SIZE)) + background.fill((255, 255, 255)) + blob = pygame.image.load("blob.png") + blob = blob.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Change the caption. + elif e.type == pygame.ACTIVEEVENT: + if e.__dict__["gain"] == 0: + pygame.display.set_caption("Come back!") + else: + pygame.display.set_caption(caption) + # Move the blob. + elif e.type == pygame.MOUSEMOTION: + blob_x = e.__dict__["pos"][0] + blob_y = e.__dict__["pos"][1] + # Draw the blob to the screen permanently. + elif e.type == pygame.MOUSEBUTTONDOWN: + background.blit(blob, (e.__dict__["pos"][0], + e.__dict__["pos"][1])) + + # Draw the background and blob. + screen.blit(background, (0, 0)) + screen.blit(blob, (blob_x, blob_y)) + + # Show the display. + pygame.display.flip() + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-3.py b/Chapter_08/Listing 8-3.py new file mode 100644 index 0000000..58cc4e9 --- /dev/null +++ b/Chapter_08/Listing 8-3.py @@ -0,0 +1,71 @@ +"""Move with the keyboard.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Process keyboard events by reacting with a blob.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + blob: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + blob_x: int = 0 + blob_y: int = 0 + speed_mult: int = 1 + + # Set up assets. + screen = make_window(SIZE, "The blob!") + background = pygame.Surface((SIZE, SIZE)) + background.fill((255, 255, 255)) + blob = pygame.image.load("blob.png") + blob = blob.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Move the blob. + elif e.type == pygame.KEYDOWN: + # Control key for double speed. + if e.__dict__["mod"] & pygame.KMOD_CTRL: + speed_mult = 2 + else: + speed_mult = 1 + # Arrow keys for direction. + if e.__dict__["key"] == pygame.K_UP: + blob_y -= 3 * speed_mult + elif e.__dict__["key"] == pygame.K_DOWN: + blob_y += 3 * speed_mult + elif e.__dict__["key"] == pygame.K_LEFT: + blob_x -= 3 * speed_mult + elif e.__dict__["key"] == pygame.K_RIGHT: + blob_x += 3 * speed_mult + # Space bar to draw the blob permanently. + elif e.__dict__["key"] == pygame.K_SPACE: + background.blit(blob, (blob_x, blob_y)) + + # Draw the background and blob. + screen.blit(background, (0, 0)) + screen.blit(blob, (blob_x, blob_y)) + + # Show the display. + pygame.display.flip() + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-4.py b/Chapter_08/Listing 8-4.py new file mode 100644 index 0000000..f54a5c9 --- /dev/null +++ b/Chapter_08/Listing 8-4.py @@ -0,0 +1,64 @@ +"""Illustrate transparency options.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(size: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((size, size)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Draw images with different transparency settings.""" + # Annotate and initialize variables + SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + star: pygame.Surface + alpha_star: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SIZE, "Transparency Demo") + background = pygame.image.load("checkerboard.jpg") + star = pygame.image.load("star.gif") + star = star.convert() + alpha_star = pygame.image.load("alpha_star.png") + alpha_star = alpha_star.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + # Draw the background and stars. + screen.blit(background, (0, 0)) + # Star with no transparency. + screen.blit(star, (50, 100)) + ##print(star.get_alpha()) #None + # Star with transparent background. + star.set_colorkey((255, 255, 255)) + screen.blit(star, (200, 100)) + # Star with less opacity. + star.set_alpha(150) + star.set_colorkey(None) + screen.blit(star, (350, 100)) + ##print(star.get_alpha()) #150 + # Star with an alpha channel. + screen.blit(alpha_star, (200, 250)) + + # Show the display. + pygame.display.flip() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-5.py b/Chapter_08/Listing 8-5.py new file mode 100644 index 0000000..1408540 --- /dev/null +++ b/Chapter_08/Listing 8-5.py @@ -0,0 +1,58 @@ +"""Illustrate centering and boundary checking.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Draw an image centered and at the boundaries.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + SQUARE_SIZE: int = 50 + screen: pygame.Surface + background: pygame.Surface + square: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Boundary Demo") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((255, 255, 255)) + square = pygame.Surface((SQUARE_SIZE, SQUARE_SIZE)) + square.fill((0, 150, 150)) + clock: pygame.time.Clock = pygame.time.Clock() + + # Calculate coordinate to blit square to center of background. + center_xcoord: float = (background.get_width() / 2) - (square.get_width() / 2) + center_ycoord: float = (background.get_height() / 2) - (square.get_height() / 2) + + # Calculate coordinate to blit square to the bottom, right. + bottom_right_xcoord: float = background.get_width() - square.get_width() + bottom_right_ycoord: float = background.get_height() - square.get_height() + + # Draw to the screen and show. + background.blit(square, (center_xcoord, center_ycoord)) + background.blit(square, (bottom_right_xcoord, bottom_right_ycoord)) + screen.blit(background, (0, 0)) + pygame.display.flip() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-6.py b/Chapter_08/Listing 8-6.py new file mode 100644 index 0000000..899a7a7 --- /dev/null +++ b/Chapter_08/Listing 8-6.py @@ -0,0 +1,56 @@ +"""Illustrate clipping regions.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Draw images within clipping regions.""" + # Annotate and initialize variables + WIDTH: int = 480 + HEIGHT: int = 300 + screen: pygame.Surface + background: pygame.Surface + photo1: pygame.Surface + photo2: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(WIDTH, HEIGHT, "Clipping Demo") + background = pygame.image.load("frames_background.jpg") + photo1 = pygame.image.load("photo1.jpg") + photo1 = photo1.convert() + photo2 = pygame.image.load("photo2.jpg") + photo2 = photo2.convert() + clock: pygame.time.Clock = pygame.time.Clock() + + # Set the clipping region and draw the first photo. + background.set_clip(52, 59, 114, 183) + background.blit(photo1, (50, 50)) + # Set the clipping region and draw the second photo. + background.set_clip(315, 59, 114, 183) + background.blit(photo2, (315, 59)) + # Draw to the screen and show. + screen.blit(background, (0, 0)) + pygame.display.flip() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-7.py b/Chapter_08/Listing 8-7.py new file mode 100644 index 0000000..a593d27 --- /dev/null +++ b/Chapter_08/Listing 8-7.py @@ -0,0 +1,60 @@ +"""Illustrate the draw module.""" + +# Imports and initialize pygame. +import math +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Demonstrate the draw module functions.""" + # Annotate and initialize variables + WIDTH: int = 480 + HEIGHT: int = 480 + screen: pygame.Surface + bg: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(WIDTH, HEIGHT, "Draw Demo") + bg = pygame.Surface((WIDTH, HEIGHT)) + bg.fill((255, 255, 255)) + clock: pygame.time.Clock = pygame.time.Clock() + + # Demonstrate one of each draw command. + poly_points: list = [(60, 30), (20, 110), (60, 190), (100, 110)] + line_points: list = [(400, 30), (350, 60), (400, 90), (450, 60)] + aa_line_points: list = [(410, 40), (360, 70), (410, 100), (460, 70)] + pygame.draw.rect(bg, (255, 0, 0), (10, 10, 100, 200), 5) + pygame.draw.polygon(bg, (0, 255, 0), poly_points, 3) + pygame.draw.circle(bg, (0, 0, 255), (240, 240), 50, 10) + pygame.draw.ellipse(bg, (255, 255, 0), (10, 10, 100, 200), 10) + pygame.draw.arc(bg, (0, 255, 255), (300, 250, 100, 200), 0, math.pi / 2, 5) + pygame.draw.line(bg, (255, 0, 255), (0, 420), (200, 300), 1) + pygame.draw.aaline(bg, (255, 0, 255), (0, 430), (200, 310), True) + pygame.draw.lines(bg, (0, 0, 0), False, line_points, 1) + pygame.draw.aalines(bg, (0, 0, 0), False, aa_line_points, True) + + # Draw to the screen and show. + screen.blit(bg, (0, 0)) + pygame.display.flip() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + pygame.quit() + +main() diff --git a/Chapter_08/Listing 8-8.py b/Chapter_08/Listing 8-8.py new file mode 100644 index 0000000..0649a95 --- /dev/null +++ b/Chapter_08/Listing 8-8.py @@ -0,0 +1,73 @@ +"""Illustrate transformations.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + + +def main() -> None: + """Process keypresses to transform an image.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + rotation: int = 0 + screen: pygame.Surface + background: pygame.Surface + pineapple: pygame.Surface + blit_pineapple: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Transformation Demo") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((140, 211, 226)) + pineapple = pygame.image.load("cool_pineapple.png") + pineapple= pineapple.convert_alpha() + blit_pineapple = pineapple + clock: pygame.time.Clock = pygame.time.Clock() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + if e.type == pygame.KEYDOWN: + # Flip, scale, or rotate based on key press. + if e.__dict__["key"] == pygame.K_RIGHT: + blit_pineapple = pygame.transform.flip(pineapple, True, False) + elif e.__dict__["key"] == pygame.K_UP: + blit_pineapple = pygame.transform.flip(pineapple, False, True) + elif e.__dict__["key"] == pygame.K_SPACE: + rotation += 30 + blit_pineapple = pygame.transform.rotate(pineapple, rotation) + elif e.__dict__["key"] == pygame.K_m: + blit_pineapple = pygame.transform.scale(pineapple, + (int(pineapple.get_width() * 1.5), + int(pineapple.get_height() * 1.5))) + # These keys undo flipping and scaling: + elif (e.__dict__["key"] == pygame.K_s + or e.__dict__["key"] == pygame.K_LEFT + or e.__dict__["key"] == pygame.K_DOWN): + blit_pineapple = pineapple + + # Calculate pineapple coordinates for the middle of the screen. + center_xcoord: float = (background.get_width() / 2) - (blit_pineapple.get_width() / 2) + center_ycoord: float = (background.get_height() / 2) - (blit_pineapple.get_height() / 2) + # Draw to the screen and show. + screen.blit(background, (0, 0)) + screen.blit(blit_pineapple, (center_xcoord, center_ycoord)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_08/alpha_star.png b/Chapter_08/alpha_star.png new file mode 100644 index 0000000..d1463d2 Binary files /dev/null and b/Chapter_08/alpha_star.png differ diff --git a/Chapter_08/blob.png b/Chapter_08/blob.png new file mode 100644 index 0000000..adcb83f Binary files /dev/null and b/Chapter_08/blob.png differ diff --git a/Chapter_08/bug.gif b/Chapter_08/bug.gif new file mode 100644 index 0000000..d70b934 Binary files /dev/null and b/Chapter_08/bug.gif differ diff --git a/Chapter_08/checkerboard.jpg b/Chapter_08/checkerboard.jpg new file mode 100644 index 0000000..053f56d Binary files /dev/null and b/Chapter_08/checkerboard.jpg differ diff --git a/Chapter_08/cool_pineapple.png b/Chapter_08/cool_pineapple.png new file mode 100644 index 0000000..6019c09 Binary files /dev/null and b/Chapter_08/cool_pineapple.png differ diff --git a/Chapter_08/frames_background.jpg b/Chapter_08/frames_background.jpg new file mode 100644 index 0000000..b3ec13c Binary files /dev/null and b/Chapter_08/frames_background.jpg differ diff --git a/Chapter_08/photo1.jpg b/Chapter_08/photo1.jpg new file mode 100644 index 0000000..67608ef Binary files /dev/null and b/Chapter_08/photo1.jpg differ diff --git a/Chapter_08/photo2.jpg b/Chapter_08/photo2.jpg new file mode 100644 index 0000000..315f4b8 Binary files /dev/null and b/Chapter_08/photo2.jpg differ diff --git a/Chapter_08/pineapple.gif b/Chapter_08/pineapple.gif new file mode 100644 index 0000000..f8a24df Binary files /dev/null and b/Chapter_08/pineapple.gif differ diff --git a/Chapter_08/pineapple_pool.jpg b/Chapter_08/pineapple_pool.jpg new file mode 100644 index 0000000..e04b389 Binary files /dev/null and b/Chapter_08/pineapple_pool.jpg differ diff --git a/Chapter_08/star.gif b/Chapter_08/star.gif new file mode 100644 index 0000000..a47081a Binary files /dev/null and b/Chapter_08/star.gif differ diff --git a/Chapter_08/star.jpg b/Chapter_08/star.jpg new file mode 100644 index 0000000..450c881 Binary files /dev/null and b/Chapter_08/star.jpg differ diff --git a/Chapter_08/star.png b/Chapter_08/star.png new file mode 100644 index 0000000..edf09e2 Binary files /dev/null and b/Chapter_08/star.png differ diff --git a/Chapter_09/Listing 9-1.py b/Chapter_09/Listing 9-1.py new file mode 100644 index 0000000..595531e --- /dev/null +++ b/Chapter_09/Listing 9-1.py @@ -0,0 +1,56 @@ +"""Basic motion.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + image: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + x: int = 0 + y: int = 0 + dx: int = 5 + dy: int = 5 + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Basic Motion") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + image = pygame.image.load("ball.gif") + image = image.convert() + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Change x and y. + x += dx + y += dy + + # Draw to the screen and show. + screen.blit(background, (0, 0)) + screen.blit(image, (x, y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-2.py b/Chapter_09/Listing 9-2.py new file mode 100644 index 0000000..a5a0174 --- /dev/null +++ b/Chapter_09/Listing 9-2.py @@ -0,0 +1,70 @@ +"""Basic motion with boundary checking.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + image: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + x: int = 0 + y: int = 0 + dx: int = 5 + dy: int = 5 + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Basic Motion") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + image = pygame.image.load("ball.gif") + image = image.convert() + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Change x and y. + x += dx + y += dy + + # Check boundaries and adjust. + if x < 0: + x = 0 + dx *= -1 + elif x + image.get_width() > screen.get_width(): + x = screen.get_width() - image.get_width() + dx *= -1 + if y < 0: + y = 0 + dy *= -1 + elif y + image.get_height() > screen.get_height(): + y = screen.get_height() - image.get_height() + dy *= -1 + + # Draw to the screen and show. + screen.blit(background, (0, 0)) + screen.blit(image, (x, y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-3.py b/Chapter_09/Listing 9-3.py new file mode 100644 index 0000000..0aecb6d --- /dev/null +++ b/Chapter_09/Listing 9-3.py @@ -0,0 +1,69 @@ +"""Allow the user to change the direction of the arrow + without changing the speed.""" + +# Imports and initialize pygame. +import math +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + image: pygame.Surface + blit_image: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + x: float = 0 + y: float = 0 + speed: int = 3 + angle: float = 0 + dx: float + dy: float + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Changing vector direction") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + image = pygame.image.load("arrow.png") + image = image.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_RIGHT: + angle += 10 + elif e.__dict__["key"] == pygame.K_LEFT: + angle -= 10 + + # Calculate dx and dy according to angle and speed. + dx = speed * math.cos(math.radians(angle)) + dy = speed * math.sin(math.radians(angle)) + x += dx + y += dy + + # Draw to the screen and show. + blit_image = pygame.transform.rotate(image, -angle) + screen.blit(background, (0, 0)) + screen.blit(blit_image, (x, y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-4.py b/Chapter_09/Listing 9-4.py new file mode 100644 index 0000000..ee4a963 --- /dev/null +++ b/Chapter_09/Listing 9-4.py @@ -0,0 +1,89 @@ +"""Demonstrate unit vectors with variable speed.""" + +# Imports and initialize pygame. +import math +import random +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen and chase it.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + ball: pygame.Surface + target: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + ball_x: float = 0 + ball_y: float = 0 + target_x: int = 0 + target_y: int = 0 + base_speed: int = 2 + speed: float + unit_dx: float + unit_dy: float + count: int = 0 + distance_x: float + distance_y: float + distance: float + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Unit vector demonstration") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + ball = pygame.image.load("ball.png") + ball = ball.convert_alpha() + target = pygame.image.load("target.png") + target = target.convert_alpha() + target_x = random.randint(0, screen.get_width() - target.get_width()) + target_y = random.randint(0, screen.get_height() - target.get_height()) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Randomly generate a location for the target periodically. + if count == 45: + count = 0 + target_x = random.randint(0, screen.get_width() - target.get_width()) + target_y = random.randint(0, screen.get_height() - target.get_height()) + count += 1 + + # Determine the unit offsets for the ball. + distance_x = (target_x + target.get_width()/2) - (ball_x + ball.get_width()/2) + distance_y = (target_y + target.get_height()/2) - (ball_y + ball.get_height()/2) + distance = math.sqrt(distance_x ** 2 + distance_y ** 2) + unit_dx = distance_x / distance + unit_dy = distance_y / distance + + # Decrease the speed as the ball gets closer to target. + speed = base_speed * (distance / (screen.get_width() / 5)) + + # Multiply offsets by speed and move ball. + ball_x += speed * unit_dx + ball_y += speed * unit_dy + + # Draw to the screen and show. + screen.blit(background, (0, 0)) + screen.blit(target, (target_x, target_y)) + screen.blit(ball, (ball_x, ball_y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-5.py b/Chapter_09/Listing 9-5.py new file mode 100644 index 0000000..d5c5aa2 --- /dev/null +++ b/Chapter_09/Listing 9-5.py @@ -0,0 +1,80 @@ +"""Demonstrate a falling equation.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + pisa_x: int = 0 + pisa_y: int = 0 + pisa: pygame.Surface + small_ball_x: float = 138 + large_ball_x: float = 208 + y_start: float = 24 + small_ball_y: float = y_start + large_ball_y : float = y_start + large_ball: pygame.Surface + small_ball: pygame.Surface + time: float = 0 + drop: bool = False + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Pisa Gravity Experiment") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + large_ball = pygame.image.load("large_ball.png") + large_ball = large_ball.convert_alpha() + small_ball = pygame.image.load("small_ball.png") + small_ball = small_ball.convert_alpha() + pisa = pygame.image.load("pisa.png") + pisa = pisa.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process space bar as jump and arrow key as move. + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_SPACE: + drop = True + + # Calculate new y coordinates for balls + if drop: + time += 1 / 30 + small_ball_y = y_start + .5 * 78 * time ** 2 + large_ball_y = small_ball_y + # Check if we've hit the ground + if large_ball_y + large_ball.get_height() >= screen.get_height(): + small_ball_y = screen.get_height() - small_ball.get_height() + large_ball_y = screen.get_height() - large_ball.get_height() + drop = False + + # Draw to the screen and show. + screen.blit(background, (0, 0)) + screen.blit(pisa, (pisa_x, pisa_y)) + screen.blit(large_ball, (large_ball_x, large_ball_y)) + screen.blit(small_ball, (small_ball_x, small_ball_y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-6.py b/Chapter_09/Listing 9-6.py new file mode 100644 index 0000000..ef03350 --- /dev/null +++ b/Chapter_09/Listing 9-6.py @@ -0,0 +1,96 @@ +"""Projectile demonstration.""" + +# Imports and initialize pygame. +import math +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_HEIGHT: int = 480 + SCREEN_WIDTH: int = 640 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + projectile: pygame.Surface + flag: pygame.Surface + start_x: float = 0 + start_y: float = -100 + x: float = start_x + y: float = start_y + time: float = 0 + shoot: bool = False + angle: float = 0 + speed: float = 100 + + # Set up assets. + screen = make_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Angle: 0 Speed: 200") + background = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT)) + background.fill((222, 237, 244)) + projectile = pygame.image.load("large_ball.png") + projectile = projectile.convert_alpha() + flag = pygame.image.load("flag.png") + flag = flag.convert_alpha() + start_y = screen.get_height() - projectile.get_height() + y = start_y + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process keys to adjust angle and shoot. + elif e.type == pygame.KEYDOWN and not shoot: + if e.__dict__["key"] == pygame.K_UP and angle < 90: + angle += 10 + elif e.__dict__["key"] == pygame.K_DOWN and angle >= 10: + angle -= 10 + elif e.__dict__["key"] == pygame.K_RIGHT: + speed += 10 + elif e.__dict__["key"] == pygame.K_LEFT and speed >= 10: + speed -= 10 + elif e.__dict__["key"] == pygame.K_SPACE: + shoot = True + + # Move the projectile through the air + if shoot: + # Increment time + time += 1/15 + # Calculate location + x = (start_x + + math.cos(math.radians(angle)) * speed * time) + y = (start_y + - (math.sin(math.radians(angle)) * speed * time) + + .5 * 72 * time**2) + # Check if it's hit the "ground" + if y + projectile.get_height() >= screen.get_height(): + time = 0 + shoot = False + # Put a flag where it landed + background.blit(flag, (x, screen.get_height() - flag.get_height())) + # Reset projectile to start + x = start_x + y = start_y + + # Draw to the screen and show. + pygame.display.set_caption("Angle: " + str(angle) + " Speed: " + str(speed)) + screen.blit(background, (0, 0)) + screen.blit(projectile, (x, y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-7.py b/Chapter_09/Listing 9-7.py new file mode 100644 index 0000000..e20b7d4 --- /dev/null +++ b/Chapter_09/Listing 9-7.py @@ -0,0 +1,119 @@ +"""Motion that includes drag.""" + +# Constants for drawing +LEFT = 0 +TOP = 3 +RIGHT = 2 +BOTTOM = 1 +NO_JETS = -1 + +# Imports and initialize pygame. +import math +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def draw_jets(direction: int, surf: pygame.Surface, bubbles: pygame.Surface) -> None: + """Draw the jets along the edge of the Surface.""" + blit_bubbles: pygame.Surface = pygame.transform.rotate(bubbles, direction * 90) + if direction == LEFT: + for i in range(20, surf.get_height() - bubbles.get_height(), 50): + surf.blit(blit_bubbles, (0, i)) + elif direction == RIGHT: + for i in range(20, surf.get_height() - bubbles.get_height(), 50): + surf.blit(blit_bubbles, (surf.get_width() - blit_bubbles.get_width(), i)) + elif direction == TOP: + for i in range(20, surf.get_width() - bubbles.get_width(), 50): + surf.blit(blit_bubbles, (i, 0)) + elif direction == BOTTOM: + for i in range(20, surf.get_width() - bubbles.get_width(), 50): + surf.blit(blit_bubbles, (i, surf.get_height() - blit_bubbles.get_height())) + +def main() -> None: + """Draw an image centered and at the boundaries.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + CHANGE = 10 + screen: pygame.Surface + background: pygame.Surface + pineapple: pygame.Surface + bubbles: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + drag: float = .96 + x: float = SCREEN_SIZE / 2 + y: float = SCREEN_SIZE / 2 + dx: float = 0 + dy: float = 0 + jets: int = NO_JETS + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Press arrow keys for jets") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((140, 211, 226)) + pineapple = pygame.image.load("small_pineapple.png") + pineapple.convert_alpha() + bubbles = pygame.image.load("bubbles.png") + bubbles.convert_alpha() + clock: pygame.time.Clock = pygame.time.Clock() + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_UP: + dy += -CHANGE + jets = BOTTOM + elif e.__dict__["key"] == pygame.K_DOWN: + dy += CHANGE + jets = TOP + elif e.__dict__["key"] == pygame.K_LEFT: + dx += -CHANGE + jets = RIGHT + elif e.__dict__["key"] == pygame.K_RIGHT: + dx += CHANGE + jets = LEFT + elif e.type == pygame.KEYUP: + jets = -1 + + # Move the pineapple. + dx *= drag + dy *= drag + x += dx + y += dy + + # Check boundaries + if x < 0: + x = 0 + dx *= -1 + elif x + pineapple.get_width() > screen.get_width(): + x = screen.get_width() - pineapple.get_width() + dx *= -1 + if y < 0: + y = 0 + dy *= -1 + elif y + pineapple.get_height() > screen.get_height(): + y = screen.get_height() - pineapple.get_height() + dy *= -1 + + # Draw to the screen and show. + screen.blit(background, (0, 0)) + draw_jets(jets, screen, bubbles) + screen.blit(pineapple, (x, y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-8.py b/Chapter_09/Listing 9-8.py new file mode 100644 index 0000000..04310a1 --- /dev/null +++ b/Chapter_09/Listing 9-8.py @@ -0,0 +1,92 @@ +"""Scrolling background demo.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + LEFT: int = 0 + RIGHT: int = 1 + NOT_MOVING: int = -1 + screen: pygame.Surface + background: pygame.Surface + penguin_left: pygame.Surface + penguin_right: pygame.Surface + penguin: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + penguin_x: int = 0 + penguin_y: int = SCREEN_SIZE - 138 + background_x: int = 0 + background_y: int = 0 + move_amount: int = 5 + scroll_threshold: int = 100 + moving: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + background = pygame.image.load("icecastle.jpg") + background = background.convert() + penguin_right = pygame.image.load("penguin.png") + penguin_right = penguin_right.convert_alpha() + penguin_left = pygame.transform.flip(penguin_right, True, False) + penguin = penguin_right + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + moving = LEFT + penguin = penguin_left + elif e.__dict__["key"] == pygame.K_RIGHT: + moving = RIGHT + penguin = penguin_right + elif e.type == pygame.KEYUP: + moving = NOT_MOVING + + # Move the penguin or the background + if moving == LEFT: + if penguin_x < scroll_threshold and background_x < 0: + background_x += move_amount + if background_x > 0: + background_x = 0 + else: + penguin_x -= move_amount + if penguin_x < 0: + penguin_x = 0 + elif moving == RIGHT: + if (penguin_x + penguin.get_width() > screen.get_width() - scroll_threshold + and background_x + background.get_width() > screen.get_width()): + background_x -= move_amount + if background_x + background.get_width() < screen.get_width(): + background_x = -(background.get_width()-screen.get_width()) + else: + penguin_x += move_amount + if penguin_x > screen.get_width() - penguin.get_width(): + penguin_x = screen.get_width() - penguin.get_width() + + # Draw to the screen and show. + screen.blit(background, (background_x, background_y)) + screen.blit(penguin, (penguin_x, penguin_y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/Listing 9-9.py b/Chapter_09/Listing 9-9.py new file mode 100644 index 0000000..6c76db1 --- /dev/null +++ b/Chapter_09/Listing 9-9.py @@ -0,0 +1,131 @@ +"""Tiling background demo.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def make_background(image_files: list, terrain_file: str) -> pygame.Surface: + """Create and return a background Surface according to + the terrain map and composed of image_files images. + Assumes at least one image and at least one line of terrain.""" + # Annotate variables + name: str + line: str + tile_size: int + width: int + height: int + x: int + y: int + # Load the images to pygame Surfaces. + tiles: list = [] + for name in image_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + background = pygame.Surface((width, height)) + # Blit the images to the Surface and return. + x = 0 + y = 0 + for row in terrain_map: + for i in row: + background.blit(tiles[int(i)], (x, y)) + x += tile_size + y += tile_size + x = 0 + return background + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + LEFT: int = 0 + RIGHT: int = 1 + NOT_MOVING: int = -1 + screen: pygame.Surface + background: pygame.Surface + penguin_left: pygame.Surface + penguin_right: pygame.Surface + penguin: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + penguin_x: int = 0 + penguin_y: int = SCREEN_SIZE - 138 + background_x: int = 0 + background_y: int = 0 + move_amount: int = 5 + scroll_threshold: int = 100 + moving: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + image_files: list = ["ice_block.jpg","ice.jpg","ice_top.jpg","ice_wall.jpg","ice_bottom.jpg"] + background = make_background(image_files, "ice_castle.txt") + penguin_right = pygame.image.load("penguin.png") + penguin_right = penguin_right.convert_alpha() + penguin_left = pygame.transform.flip(penguin_right, True, False) + penguin = penguin_right + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + moving = LEFT + penguin = penguin_left + elif e.__dict__["key"] == pygame.K_RIGHT: + moving = RIGHT + penguin = penguin_right + elif e.type == pygame.KEYUP: + moving = NOT_MOVING + + # Move the penguin or the background + if moving == LEFT: + if penguin_x < scroll_threshold and background_x < 0: + background_x += move_amount + if background_x > 0: + background_x = 0 + else: + penguin_x -= move_amount + if penguin_x < 0: + penguin_x = 0 + elif moving == RIGHT: + if (penguin_x + penguin.get_width() > screen.get_width() - scroll_threshold + and background_x + background.get_width() > screen.get_width()): + background_x -= move_amount + if background_x + background.get_width() < screen.get_width(): + background_x = -(background.get_width()-screen.get_width()) + else: + penguin_x += move_amount + if penguin_x > screen.get_width() - penguin.get_width(): + penguin_x = screen.get_width() - penguin.get_width() + + # Draw to the screen and show. + screen.blit(background, (background_x, background_y)) + screen.blit(penguin, (penguin_x, penguin_y)) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_09/arrow.jpg b/Chapter_09/arrow.jpg new file mode 100644 index 0000000..756abf7 Binary files /dev/null and b/Chapter_09/arrow.jpg differ diff --git a/Chapter_09/arrow.png b/Chapter_09/arrow.png new file mode 100644 index 0000000..0d25b0e Binary files /dev/null and b/Chapter_09/arrow.png differ diff --git a/Chapter_09/ball.gif b/Chapter_09/ball.gif new file mode 100644 index 0000000..4206936 Binary files /dev/null and b/Chapter_09/ball.gif differ diff --git a/Chapter_09/ball.png b/Chapter_09/ball.png new file mode 100644 index 0000000..b65dfc5 Binary files /dev/null and b/Chapter_09/ball.png differ diff --git a/Chapter_09/bubbles.png b/Chapter_09/bubbles.png new file mode 100644 index 0000000..aabd576 Binary files /dev/null and b/Chapter_09/bubbles.png differ diff --git a/Chapter_09/cool_pineapple.png b/Chapter_09/cool_pineapple.png new file mode 100644 index 0000000..6019c09 Binary files /dev/null and b/Chapter_09/cool_pineapple.png differ diff --git a/Chapter_09/flag.png b/Chapter_09/flag.png new file mode 100644 index 0000000..6d3217a Binary files /dev/null and b/Chapter_09/flag.png differ diff --git a/Chapter_09/green_ball.png b/Chapter_09/green_ball.png new file mode 100644 index 0000000..c5e9505 Binary files /dev/null and b/Chapter_09/green_ball.png differ diff --git a/Chapter_09/ice.jpg b/Chapter_09/ice.jpg new file mode 100644 index 0000000..f74e959 Binary files /dev/null and b/Chapter_09/ice.jpg differ diff --git a/Chapter_09/ice_block.jpg b/Chapter_09/ice_block.jpg new file mode 100644 index 0000000..d970736 Binary files /dev/null and b/Chapter_09/ice_block.jpg differ diff --git a/Chapter_09/ice_bottom.jpg b/Chapter_09/ice_bottom.jpg new file mode 100644 index 0000000..dd1679e Binary files /dev/null and b/Chapter_09/ice_bottom.jpg differ diff --git a/Chapter_09/ice_castle.txt b/Chapter_09/ice_castle.txt new file mode 100644 index 0000000..261e063 --- /dev/null +++ b/Chapter_09/ice_castle.txt @@ -0,0 +1,12 @@ +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 +3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +3 3 0 0 0 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 +3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 +3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 +2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 diff --git a/Chapter_09/ice_top.jpg b/Chapter_09/ice_top.jpg new file mode 100644 index 0000000..f937102 Binary files /dev/null and b/Chapter_09/ice_top.jpg differ diff --git a/Chapter_09/ice_wall.jpg b/Chapter_09/ice_wall.jpg new file mode 100644 index 0000000..55be5c3 Binary files /dev/null and b/Chapter_09/ice_wall.jpg differ diff --git a/Chapter_09/icecastle.jpg b/Chapter_09/icecastle.jpg new file mode 100644 index 0000000..2942b9f Binary files /dev/null and b/Chapter_09/icecastle.jpg differ diff --git a/Chapter_09/large_ball.png b/Chapter_09/large_ball.png new file mode 100644 index 0000000..53e016c Binary files /dev/null and b/Chapter_09/large_ball.png differ diff --git a/Chapter_09/penguin.png b/Chapter_09/penguin.png new file mode 100644 index 0000000..b1b0304 Binary files /dev/null and b/Chapter_09/penguin.png differ diff --git a/Chapter_09/pisa.png b/Chapter_09/pisa.png new file mode 100644 index 0000000..a944876 Binary files /dev/null and b/Chapter_09/pisa.png differ diff --git a/Chapter_09/red_ball.png b/Chapter_09/red_ball.png new file mode 100644 index 0000000..ca11d31 Binary files /dev/null and b/Chapter_09/red_ball.png differ diff --git a/Chapter_09/small_ball.png b/Chapter_09/small_ball.png new file mode 100644 index 0000000..626e1f1 Binary files /dev/null and b/Chapter_09/small_ball.png differ diff --git a/Chapter_09/small_pineapple.png b/Chapter_09/small_pineapple.png new file mode 100644 index 0000000..8819660 Binary files /dev/null and b/Chapter_09/small_pineapple.png differ diff --git a/Chapter_09/target.png b/Chapter_09/target.png new file mode 100644 index 0000000..aa53516 Binary files /dev/null and b/Chapter_09/target.png differ diff --git a/Chapter_10/Listing 10-1.py b/Chapter_10/Listing 10-1.py new file mode 100644 index 0000000..c21846c --- /dev/null +++ b/Chapter_10/Listing 10-1.py @@ -0,0 +1,85 @@ +"""Basic motion with boundary checking with Sprite.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +class Ball(pygame.sprite.Sprite): + """A bouncing ball sprite.""" + + # Annotate object-level fields + _dx: int + _dy: int + + def __init__(self, image: pygame.Surface, x: int, y: int, + dx: int, dy: int) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = image + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + self._dx = dx + self._dy = dy + + def update(self, screen: pygame.Surface) -> None: + """Move the ball and check boundaries.""" + self.rect.centerx += self._dx + self.rect.centery += self._dy + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + self._dx *= -1 + elif self.rect.left < 0: + self.rect.left = 0 + self._dx *= -1 + if self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + self._dy *= -1 + elif self.rect.top < 0: + self.rect.top = 0 + self._dy *= -1 + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + ball: Ball + sprites: pygame.sprite.Group + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Basic Motion") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + screen.blit(background, (0, 0)) + ball = Ball(pygame.image.load("ball.gif").convert(), 0, 0, 5, 5) + sprites = pygame.sprite.Group(ball) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Draw to the screen and show. + sprites.clear(screen, background) + sprites.update(screen) + sprites.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-10.py b/Chapter_10/Listing 10-10.py new file mode 100644 index 0000000..3065a6e --- /dev/null +++ b/Chapter_10/Listing 10-10.py @@ -0,0 +1,253 @@ +"""Scrolling, tiled background sprite + with wall collision detection.""" + +# Imports and initialize pygame. +import pygame +import io +import math +pygame.init() + +# Constants +LEFT: int = 0 +RIGHT: int = 1 +UP: int = 2 +DOWN: int = 3 +NOT_MOVING: int = -1 + +class Brick(pygame.sprite.Sprite): + """Just another brick...""" + + def __init__(self, x: int, y: int, length: int, width: int) -> None: + """Create an invisible sprite.""" + super().__init__() + self.image = pygame.Surface((width, length)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + + def move(self, dx: int, dy: int) -> None: + """Move by dx and dy.""" + self.rect.left += dx + self.rect.right += dy + +class ScrollingBackground(pygame.sprite.Sprite): + """A tile-based scrolling background.""" + + # Annotate object-level fields + _speed: int + _bricks: list + + def __init__(self, tile_files: list, terrain_file: str, bricks: list) -> None: + """Load map and build Surface.""" + # Annotate and initialize local variables + name: str + line: str + tile_size: int + width: int + height: int + x: int = 0 + y: int = 0 + terrain: io.TextIOWrapper + # Superclass init. + super().__init__() + # Load the images to pygame Surfaces. + tiles: list = [] + for name in tile_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + self.image = pygame.Surface((width, height)) + # Blit the images to the Surface. + # Create a Block if necessary. + self._bricks = [] + for row in terrain_map: + for i in row: + self.image.blit(tiles[int(i)], (x, y)) + if int(i) in bricks: + self._bricks.append(Brick(x, y, tile_size, tile_size)) + x += tile_size + y += tile_size + x = 0 + # Complete initialization + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + self._speed = 5 + + def scroll(self, direction: int, screen: pygame.Surface) -> None: + """Move left, right, or not at all.""" + # Annotate and initialize local variable + left: int = self.rect.left + # Scroll image and bricks. + if direction == LEFT: + self.rect.left += self._speed + if self.rect.left > 0: + self.rect.left = 0 + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + elif direction == RIGHT: + self.rect.left -= self._speed + if self.rect.right < screen.get_width(): + self.rect.right = screen.get_width() + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + + def can_scroll(self, screen: pygame.Surface, direction: int) -> bool: + """Return True if can scroll in direction.""" + scroll: bool = False + if direction == LEFT and self.rect.left < 0: + scroll = True + elif direction == RIGHT and self.rect.right > screen.get_width(): + scroll = True + return scroll + + def get_bricks(self) -> list: + """Return the bricks.""" + return self._bricks + +class Penguin(pygame.sprite.Sprite): + """A player-controlled character.""" + + # Annotate object-level fields + _raw_image: pygame.Surface + _speed: int + _angle: int + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("top_penguin.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self.rect.topleft = (0, screen.get_height() / 2 - self.rect.height / 2) + self._speed = 5 + self._angle = 0 + + def move(self, screen: pygame.Surface, threshold: int, + direction: int, background: ScrollingBackground) -> bool: + """Attempt to move, return True if moved.""" + moved: bool = False + if direction == LEFT: + self._angle = 180 + self.image = pygame.transform.rotate(self._raw_image, 180) + if not(self.rect.left < threshold and background.can_scroll(screen, direction)): + self.rect.left -= self._speed + if self.rect.left < 0: + self.rect.left = 0 + moved = True + elif direction == RIGHT: + self._angle = 0 + self.image = self._raw_image + if not (self.rect.right > screen.get_width() - threshold + and background.can_scroll(screen, direction)): + self.rect.left += self._speed + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + moved = True + elif direction == UP: + self._angle = 270 + self.image = pygame.transform.rotate(self._raw_image, 90) + self.rect.top -= self._speed + if self.rect.top < 0: + self.rect.top = 0 + moved = True + elif direction == DOWN: + self._angle = 90 + self.image = pygame.transform.rotate(self._raw_image, -90) + self.rect.top += self._speed + if self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + moved = True + return moved + + def backup(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dx: float = -math.cos(math.radians(self._angle)) + dy: float = -math.sin(math.radians(self._angle)) + # Add to rectangle + self.rect.left += dx + self.rect.top += dy + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: ScrollingBackground + bkgd_group: pygame.sprite.Group + bricks: pygame.sprite.Group + penguin: Penguin + penguin_group: pygame.sprite.Group + user_quit: bool = False + e: pygame.event.Event + scroll_threshold: int = 100 + direction: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + image_files: list = ["ice_block.jpg", "ice_wall.jpg"] + background = ScrollingBackground(image_files, "ice_castle.txt", [0]) + bkgd_group = pygame.sprite.Group(background) + bricks = pygame.sprite.Group(background.get_bricks()) + penguin = Penguin(screen) + penguin_group = pygame.sprite.Group(penguin) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + # Process movement arrow keys. + if e.__dict__["key"] == pygame.K_LEFT: + direction = LEFT + elif e.__dict__["key"] == pygame.K_RIGHT: + direction = RIGHT + elif e.__dict__["key"] == pygame.K_UP: + direction = UP + elif e.__dict__["key"] == pygame.K_DOWN: + direction = DOWN + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP] + and not pygame.key.get_pressed()[pygame.K_DOWN]): + direction = NOT_MOVING + + # Move the penguin or the background. + penguin_group.clear(screen, background.image) + if not penguin.move(screen, scroll_threshold, direction, background): + background.scroll(direction, screen) + # Check for collisions. + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup() + # Redraw and show. + bkgd_group.draw(screen) + penguin_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-11.py b/Chapter_10/Listing 10-11.py new file mode 100644 index 0000000..3baa023 --- /dev/null +++ b/Chapter_10/Listing 10-11.py @@ -0,0 +1,310 @@ +"""Platform scroller.""" + +# Imports and initialize pygame. +import pygame +import io +import math +pygame.init() + +# Constants +LEFT: int = 0 +RIGHT: int = 1 +UP: int = 2 +NOT_MOVING: int = -1 + +class Brick(pygame.sprite.Sprite): + """Just another brick...""" + + def __init__(self, x: int, y: int, length: int, width: int) -> None: + """Create an invisible sprite.""" + super().__init__() + self.image = pygame.Surface((width, length)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + + def move(self, dx: int, dy: int) -> None: + """Move by dx and dy.""" + self.rect.left += dx + self.rect.right += dy + +class ScrollingBackground(pygame.sprite.Sprite): + """A tile-based scrolling background.""" + + # Annotate object-level fields + _speed: int + _bricks: list + + def __init__(self, tile_files: list, terrain_file: str, bricks: list) -> None: + """Load map and build Surface.""" + # Annotate and initialize local variables + name: str + line: str + tile_size: int + width: int + height: int + x: int = 0 + y: int = 0 + terrain: io.TextIOWrapper + # Superclass init. + super().__init__() + # Load the images to pygame Surfaces. + tiles: list = [] + for name in tile_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + self.image = pygame.Surface((width, height)) + # Blit the images to the Surface. + # Create a Block if necessary. + self._bricks = [] + for row in terrain_map: + for i in row: + self.image.blit(tiles[int(i)], (x, y)) + if int(i) in bricks: + self._bricks.append(Brick(x, y, tile_size, tile_size)) + x += tile_size + y += tile_size + x = 0 + # Complete initialization + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + self._speed = 5 + + def scroll(self, direction: int, screen: pygame.Surface) -> None: + """Move left, right, or not at all.""" + # Annotate and initialize local variable + left: int = self.rect.left + # Scroll image and bricks. + if direction == LEFT: + self.rect.left += self._speed + if self.rect.left > 0: + self.rect.left = 0 + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + elif direction == RIGHT: + self.rect.left -= self._speed + if self.rect.right < screen.get_width(): + self.rect.right = screen.get_width() + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + + def can_scroll(self, screen: pygame.Surface, direction: int) -> bool: + """Return True if can scroll in direction.""" + scroll: bool = False + if direction == LEFT and self.rect.left < 0: + scroll = True + elif direction == RIGHT and self.rect.right > screen.get_width(): + scroll = True + return scroll + + def get_bricks(self) -> list: + """Return the bricks.""" + return self._bricks + +class Penguin(pygame.sprite.Sprite): + """A player-controlled character.""" + + # Annotate object-level fields + _raw_image: pygame.Surface + _dx: float + _dy: float + _facing: int + _x: float + _y: float + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("tiny_penguin.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self._x = 0 + self._y = screen.get_height() - 40 - self.rect.height + self.rect.topleft = (self._x, self._y) + self._dx = 0 + self._dy = 0 + self._facing = RIGHT + + def on_ground(self, bricks) -> bool: + """Return True if the sprite is on a solid brick.""" + on: bool = False + self.rect.top += 1 + if pygame.sprite.spritecollide(self, bricks, False): + on = True + self.rect.top -= 1 + return on + + def process_move_command(self, move_command: int, + bricks: pygame.sprite.Group) -> None: + """Process a command from the user.""" + # If it's on the ground, it can move or jump. + if self.on_ground(bricks): + self._dy = 0 + if move_command == LEFT: + self._dx = -5 + self._facing = LEFT + self.image = pygame.transform.flip(self._raw_image, True, False) + elif move_command == RIGHT: + self._dx = 5 + self._facing = RIGHT + self.image = self._raw_image + elif move_command == UP: + self._dy = -50 + if self._facing == LEFT: + self._dx = -5 + else: + self._dx = 5 + # Stop moving in the x direction. + if move_command == NOT_MOVING: + self._dx = 0 + + + def move_x(self, screen: pygame.Surface, threshold: int, + background: ScrollingBackground) -> int: + """Attempt to move, return True if moved.""" + moved: bool = False + scroll_dir: int = NOT_MOVING + + # Adjust x according to dx. + if self._dx < 0: + can_scroll = background.can_scroll(screen, LEFT) + if not(can_scroll and self.rect.left < threshold): + self._x += self._dx + if self._x < 0: + self._x = 0 + self.rect.left = self._x + else: + scroll_dir = LEFT + elif self._dx > 0: + can_scroll = background.can_scroll(screen, RIGHT) + if not(can_scroll and self.rect.right > screen.get_width() - threshold): + self._x += self._dx + if self._x > screen.get_width() - self.rect.width: + self._x = screen.get_width() - self.rect.width + self.rect.left = self._x + else: + scroll_dir = RIGHT + + return scroll_dir + + def backup_x(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dx: float = -(self._dx / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._x += dx + self.rect.topleft = (self._x, self._y) + + def move_y(self, screen: pygame.Surface, bricks: pygame.sprite.Group): + """Move in the y coordinate.""" + # If it's not on the ground, it's falling. + if not self.on_ground(bricks): + # Start freefall + if round(self._dy) == 0: + self._dy = 1 + # Going up + elif self._dy < 0: + self._dy *= .4 + # Going down + else: + self._dy += 2 + + # Adjust y according to dy. + self._y += self._dy + self.rect.top = self._y + + def backup_y(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dy: float = -(self._dy / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._y += dy + self.rect.topleft = (self._x, self._y) + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: ScrollingBackground + bkgd_group: pygame.sprite.Group + bricks: pygame.sprite.Group + penguin: Penguin + penguin_group: pygame.sprite.Group + user_quit: bool = False + e: pygame.event.Event + scroll_threshold: int = 100 + direction: int = NOT_MOVING + scroll_dir: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + image_files: list = ["ice_block.jpg", "ice_wall.jpg"] + background = ScrollingBackground(image_files, "ice_castle.txt", [0]) + bkgd_group = pygame.sprite.Group(background) + bricks = pygame.sprite.Group(background.get_bricks()) + penguin = Penguin(screen) + penguin_group = pygame.sprite.Group(penguin) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + # Process movement arrow keys. + if e.__dict__["key"] == pygame.K_LEFT: + penguin.process_move_command(LEFT, bricks) + elif e.__dict__["key"] == pygame.K_RIGHT: + penguin.process_move_command(RIGHT, bricks) + elif e.__dict__["key"] == pygame.K_UP: + penguin.process_move_command(UP, bricks) + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP]): + penguin.process_move_command(NOT_MOVING, bricks) + + # Move the penguin or the background. + penguin_group.clear(screen, background.image) + # Move and check for collisions. + scroll_dir = penguin.move_x(screen, scroll_threshold, background) + background.scroll(scroll_dir, screen) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_x() + penguin.move_y(screen, bricks) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_y() + + # Redraw and show. + bkgd_group.draw(screen) + penguin_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-12.py b/Chapter_10/Listing 10-12.py new file mode 100644 index 0000000..ebdf7d8 --- /dev/null +++ b/Chapter_10/Listing 10-12.py @@ -0,0 +1,333 @@ +"""Platform scroller with animated sprite.""" + +# Imports and initialize pygame. +import pygame +import io +import math +pygame.init() + +# Constants +LEFT: int = 0 +RIGHT: int = 1 +UP: int = 2 +NOT_MOVING: int = -1 + +class Brick(pygame.sprite.Sprite): + """Just another brick...""" + + def __init__(self, x: int, y: int, length: int, width: int) -> None: + """Create an invisible sprite.""" + super().__init__() + self.image = pygame.Surface((width, length)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + + def move(self, dx: int, dy: int) -> None: + """Move by dx and dy.""" + self.rect.left += dx + self.rect.right += dy + +class ScrollingBackground(pygame.sprite.Sprite): + """A tile-based scrolling background.""" + + # Annotate object-level fields + _speed: int + _bricks: list + + def __init__(self, tile_files: list, terrain_file: str, bricks: list) -> None: + """Load map and build Surface.""" + # Annotate and initialize local variables + name: str + line: str + tile_size: int + width: int + height: int + x: int = 0 + y: int = 0 + terrain: io.TextIOWrapper + # Superclass init. + super().__init__() + # Load the images to pygame Surfaces. + tiles: list = [] + for name in tile_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + self.image = pygame.Surface((width, height)) + # Blit the images to the Surface. + # Create a Block if necessary. + self._bricks = [] + for row in terrain_map: + for i in row: + self.image.blit(tiles[int(i)], (x, y)) + if int(i) in bricks: + self._bricks.append(Brick(x, y, tile_size, tile_size)) + x += tile_size + y += tile_size + x = 0 + # Complete initialization + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + self._speed = 5 + + def scroll(self, direction: int, screen: pygame.Surface) -> None: + """Move left, right, or not at all.""" + # Annotate and initialize local variable + left: int = self.rect.left + # Scroll image and bricks. + if direction == LEFT: + self.rect.left += self._speed + if self.rect.left > 0: + self.rect.left = 0 + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + elif direction == RIGHT: + self.rect.left -= self._speed + if self.rect.right < screen.get_width(): + self.rect.right = screen.get_width() + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + + def can_scroll(self, screen: pygame.Surface, direction: int) -> bool: + """Return True if can scroll in direction.""" + scroll: bool = False + if direction == LEFT and self.rect.left < 0: + scroll = True + elif direction == RIGHT and self.rect.right > screen.get_width(): + scroll = True + return scroll + + def get_bricks(self) -> list: + """Return the bricks.""" + return self._bricks + +class Penguin(pygame.sprite.Sprite): + """A player-controlled character.""" + + # Annotate object-level fields + _raw_image: pygame.Surface + _flap_images: list + _flap_index: int + _dx: float + _dy: float + _facing: int + _x: float + _y: float + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("tiny_penguin.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self._flap_images = [] + for i in range(9): + self._flap_images.append(pygame.image.load("penguin_flap_" + str(i) +".png").convert_alpha()) + self._x = 0 + self._y = screen.get_height() - 40 - self.rect.height + self.rect.topleft = (self._x, self._y) + self._dx = 0 + self._dy = 0 + self._facing = RIGHT + self._flap_index = 0 + self._hop_sound = pygame.mixer.Sound("hop.ogg") + + + def on_ground(self, bricks) -> bool: + """Return True if the sprite is on a solid brick.""" + on: bool = False + self.rect.top += 1 + if pygame.sprite.spritecollide(self, bricks, False): + on = True + self.rect.top -= 1 + return on + + def process_move_command(self, move_command: int, + bricks: pygame.sprite.Group) -> None: + """Process a command from the user.""" + # If it's on the ground, it can move or jump. + if self.on_ground(bricks): + self._dy = 0 + if move_command == LEFT: + self._dx = -5 + self._facing = LEFT + elif move_command == RIGHT: + self._dx = 5 + self._facing = RIGHT + elif move_command == UP: + self._dy = -50 + if self._facing == LEFT: + self._dx = -5 + else: + self._dx = 5 + + # Stop moving in the x direction. + if move_command == NOT_MOVING: + self._dx = 0 + + def move_x(self, screen: pygame.Surface, threshold: int, + background: ScrollingBackground) -> int: + """Attempt to move, return True if moved.""" + moved: bool = False + scroll_dir: int = NOT_MOVING + + # Adjust x according to dx. + if self._dx < 0: + can_scroll = background.can_scroll(screen, LEFT) + if not(can_scroll and self.rect.left < threshold): + self._x += self._dx + if self._x < 0: + self._x = 0 + self.rect.left = self._x + else: + scroll_dir = LEFT + elif self._dx > 0: + can_scroll = background.can_scroll(screen, RIGHT) + if not(can_scroll and self.rect.right > screen.get_width() - threshold): + self._x += self._dx + if self._x > screen.get_width() - self.rect.width: + self._x = screen.get_width() - self.rect.width + self.rect.left = self._x + else: + scroll_dir = RIGHT + + return scroll_dir + + def backup_x(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dx: float = -(self._dx / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._x += dx + self.rect.topleft = (self._x, self._y) + + def move_y(self, screen: pygame.Surface, bricks: pygame.sprite.Group): + """Move in the y coordinate.""" + + # If it's not on the ground, it's falling. + if not self.on_ground(bricks): + # Start freefall + if round(self._dy) == 0: + self._dy = 1 + # Going up + elif self._dy < 0: + self._dy *= .4 + # Going down + else: + self._dy += 2 + + # Adjust y according to dy. + self._y += self._dy + self.rect.top = self._y + + + def backup_y(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dy: float = -(self._dy / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._y += dy + self.rect.topleft = (self._x, self._y) + + def update(self, bricks: pygame.sprite.Group) -> None: + """Flap if in the air.""" + if not self.on_ground(bricks): + self.image = self._flap_images[self._flap_index] + if self._facing == LEFT: + self.image = pygame.transform.flip(self._raw_image, True, False) + self._flap_index += 1 + self._flap_index %= len(self._flap_images) + else: + if self._facing == LEFT: + self.image = pygame.transform.flip(self._raw_image, True, False) + else: + self.image = self._raw_image + self._flap_index = 0 + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: ScrollingBackground + bkgd_group: pygame.sprite.Group + bricks: pygame.sprite.Group + penguin: Penguin + penguin_group: pygame.sprite.Group + user_quit: bool = False + e: pygame.event.Event + scroll_threshold: int = 100 + direction: int = NOT_MOVING + scroll_dir: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + image_files: list = ["ice_block.jpg", "ice_wall.jpg"] + background = ScrollingBackground(image_files, "ice_castle.txt", [0]) + bkgd_group = pygame.sprite.Group(background) + bricks = pygame.sprite.Group(background.get_bricks()) + penguin = Penguin(screen) + penguin_group = pygame.sprite.Group(penguin) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + # Process movement arrow keys. + if e.__dict__["key"] == pygame.K_LEFT: + penguin.process_move_command(LEFT, bricks) + elif e.__dict__["key"] == pygame.K_RIGHT: + penguin.process_move_command(RIGHT, bricks) + elif e.__dict__["key"] == pygame.K_UP: + penguin.process_move_command(UP, bricks) + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP]): + penguin.process_move_command(NOT_MOVING, bricks) + + # Move the penguin or the background. + penguin_group.clear(screen, background.image) + # Move and check for collisions. + scroll_dir = penguin.move_x(screen, scroll_threshold, background) + background.scroll(scroll_dir, screen) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_x() + penguin.move_y(screen, bricks) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_y() + + # Redraw and show. + bkgd_group.draw(screen) + penguin_group.update(bricks) + penguin_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-13.py b/Chapter_10/Listing 10-13.py new file mode 100644 index 0000000..9efb97a --- /dev/null +++ b/Chapter_10/Listing 10-13.py @@ -0,0 +1,345 @@ +"""Platform scroller.""" + +# Imports and initialize pygame. +import pygame +import io +import math +pygame.init() + +# Constants +LEFT: int = 0 +RIGHT: int = 1 +UP: int = 2 +NOT_MOVING: int = -1 + +class Brick(pygame.sprite.Sprite): + """Just another brick...""" + + def __init__(self, x: int, y: int, length: int, width: int) -> None: + """Create an invisible sprite.""" + super().__init__() + self.image = pygame.Surface((width, length)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + + def move(self, dx: int, dy: int) -> None: + """Move by dx and dy.""" + self.rect.left += dx + self.rect.right += dy + +class ScrollingBackground(pygame.sprite.Sprite): + """A tile-based scrolling background.""" + + # Annotate object-level fields + _speed: int + _bricks: list + + def __init__(self, tile_files: list, terrain_file: str, bricks: list) -> None: + """Load map and build Surface.""" + # Annotate and initialize local variables + name: str + line: str + tile_size: int + width: int + height: int + x: int = 0 + y: int = 0 + terrain: io.TextIOWrapper + # Superclass init. + super().__init__() + # Load the images to pygame Surfaces. + tiles: list = [] + for name in tile_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + self.image = pygame.Surface((width, height)) + # Blit the images to the Surface. + # Create a Block if necessary. + self._bricks = [] + for row in terrain_map: + for i in row: + self.image.blit(tiles[int(i)], (x, y)) + if int(i) in bricks: + self._bricks.append(Brick(x, y, tile_size, tile_size)) + x += tile_size + y += tile_size + x = 0 + # Complete initialization + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + self._speed = 5 + + def scroll(self, direction: int, screen: pygame.Surface) -> None: + """Move left, right, or not at all.""" + # Annotate and initialize local variable + left: int = self.rect.left + # Scroll image and bricks. + if direction == LEFT: + self.rect.left += self._speed + if self.rect.left > 0: + self.rect.left = 0 + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + elif direction == RIGHT: + self.rect.left -= self._speed + if self.rect.right < screen.get_width(): + self.rect.right = screen.get_width() + for brick in self._bricks: + brick.move(self.rect.left - left, 0) + + def can_scroll(self, screen: pygame.Surface, direction: int) -> bool: + """Return True if can scroll in direction.""" + scroll: bool = False + if direction == LEFT and self.rect.left < 0: + scroll = True + elif direction == RIGHT and self.rect.right > screen.get_width(): + scroll = True + return scroll + + def get_bricks(self) -> list: + """Return the bricks.""" + return self._bricks + +class AnimatedGroup(pygame.sprite.Group): + """A group for any Sprite that is blitting only part of its image.""" + def draw(self, surface): + """Draw all sprites onto the surface. + Code modified from AbstractGroup.draw + """ + sprites = self.sprites() + for sprite in sprites: + self.spritedict[sprite] = surface.blit(sprite.image, sprite.rect, sprite.source_rect) + self.lostsprites = [] + +class Penguin(pygame.sprite.Sprite): + """A player-controlled character.""" + + # Annotate object-level fields + source_rect: pygame.Rect + _index: int + _dx: float + _dy: float + _facing: int + _x: float + _y: float + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.image.load("penguin_sprite_sheet2.png") + self.image = self.image.convert_alpha() + # source_rect is the rectangle from image to use + # rect is the location to blit + self.source_rect = pygame.Rect(0, 0, 36, 36) + self.rect = self.source_rect + + self._x = 0 + self._y = screen.get_height() - 40 - self.rect.height + self.rect.topleft = (self._x, self._y) + self._dx = 0 + self._dy = 0 + self._facing = RIGHT + self._index = 0 + + def on_ground(self, bricks) -> bool: + """Return True if the sprite is on a solid brick.""" + on: bool = False + self.rect.top += 1 + if pygame.sprite.spritecollide(self, bricks, False): + on = True + self.rect.top -= 1 + return on + + def process_move_command(self, move_command: int, + bricks: pygame.sprite.Group) -> None: + """Process a command from the user.""" + # If it's on the ground, it can move or jump. + if self.on_ground(bricks): + self._dy = 0 + if move_command == LEFT: + self._dx = -5 + self._facing = LEFT + elif move_command == RIGHT: + self._dx = 5 + self._facing = RIGHT + elif move_command == UP: + self._dy = -50 + if self._facing == LEFT: + self._dx = -5 + else: + self._dx = 5 + + # Stop moving in the x direction. + if move_command == NOT_MOVING: + self._dx = 0 + + def move_x(self, screen: pygame.Surface, threshold: int, + background: ScrollingBackground) -> int: + """Attempt to move, return True if moved.""" + moved: bool = False + scroll_dir: int = NOT_MOVING + + # Adjust x according to dx. + if self._dx < 0: + can_scroll = background.can_scroll(screen, LEFT) + if not(can_scroll and self.rect.left < threshold): + self._x += self._dx + if self._x < 0: + self._x = 0 + self.rect.left = self._x + else: + scroll_dir = LEFT + elif self._dx > 0: + can_scroll = background.can_scroll(screen, RIGHT) + if not(can_scroll and self.rect.right > screen.get_width() - threshold): + self._x += self._dx + if self._x > screen.get_width() - self.rect.width: + self._x = screen.get_width() - self.rect.width + self.rect.left = self._x + else: + scroll_dir = RIGHT + + return scroll_dir + + def backup_x(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dx: float = -(self._dx / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._x += dx + self.rect.topleft = (self._x, self._y) + + def move_y(self, screen: pygame.Surface, bricks: pygame.sprite.Group): + """Move in the y coordinate.""" + + # If it's not on the ground, it's falling. + if not self.on_ground(bricks): + # Start freefall + if round(self._dy) == 0: + self._dy = 1 + # Going up + elif self._dy < 0: + self._dy *= .4 + # Going down + else: + self._dy += 2 + + # Adjust y according to dy. + self._y += self._dy + self.rect.top = self._y + + + def backup_y(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dy: float = -(self._dy / (math.sqrt(self._dx**2 + self._dy**2))) + # Add to coordinates + self._y += dy + self.rect.topleft = (self._x, self._y) + + def update(self, bricks: pygame.sprite.Group) -> None: + """Flap if in the air.""" + index: int + if not self.on_ground(bricks): + if self._facing == LEFT: + index = self._index % 9 + 11 + self.source_rect = pygame.Rect(index * 36, 0, 36, 36) + self._index += 1 + else: + index = self._index % 9 + 2 + self.source_rect = pygame.Rect(index * 36, 0, 36, 36) + self._index += 1 + else: + if self._facing == LEFT: + self.source_rect = pygame.Rect(36, 0, 36, 36) + else: + self.source_rect = pygame.Rect(0, 0, 36, 36) + self._index = 0 + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: ScrollingBackground + bkgd_group: pygame.sprite.Group + bricks: pygame.sprite.Group + penguin: Penguin + penguin_group: AnimatedGroup + user_quit: bool = False + e: pygame.event.Event + scroll_threshold: int = 100 + direction: int = NOT_MOVING + scroll_dir: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scrolling Background") + image_files: list = ["ice_block.jpg", "ice_wall.jpg"] + background = ScrollingBackground(image_files, "ice_castle.txt", [0]) + bkgd_group = pygame.sprite.Group(background) + bricks = pygame.sprite.Group(background.get_bricks()) + penguin = Penguin(screen) + penguin_group = AnimatedGroup(penguin) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + # Process movement arrow keys. + if e.__dict__["key"] == pygame.K_LEFT: + penguin.process_move_command(LEFT, bricks) + elif e.__dict__["key"] == pygame.K_RIGHT: + penguin.process_move_command(RIGHT, bricks) + elif e.__dict__["key"] == pygame.K_UP: + penguin.process_move_command(UP, bricks) + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP]): + penguin.process_move_command(NOT_MOVING, bricks) + + # Move the penguin or the background. + penguin_group.clear(screen, background.image) + # Move and check for collisions. + scroll_dir = penguin.move_x(screen, scroll_threshold, background) + background.scroll(scroll_dir, screen) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_x() + penguin.move_y(screen, bricks) + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup_y() + + # Redraw and show. + bkgd_group.draw(screen) + penguin_group.update(bricks) + penguin_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-14.py b/Chapter_10/Listing 10-14.py new file mode 100644 index 0000000..8dd7b6c --- /dev/null +++ b/Chapter_10/Listing 10-14.py @@ -0,0 +1,49 @@ +"""Simple sound program.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Create a window and process events related to sound.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Press the space bar") + screen.fill((140, 211, 226)) + pygame.display.flip() + clock: pygame.time.Clock = pygame.time.Clock() + hop: pygame.mixer.Sound + + # Load sound + if pygame.mixer: + hop = pygame.mixer.Sound(file = "hop.ogg") + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process space bar press to play sound. + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_SPACE: + if pygame.mixer: + hop.play() + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-15.py b/Chapter_10/Listing 10-15.py new file mode 100644 index 0000000..cb7c2f7 --- /dev/null +++ b/Chapter_10/Listing 10-15.py @@ -0,0 +1,64 @@ +"""Simple sound program.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Create a window and process events related to sound.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Press the space bar") + screen.fill((140, 211, 226)) + pygame.display.flip() + clock: pygame.time.Clock = pygame.time.Clock() + hop: pygame.mixer.Sound + + # Load sound + if pygame.mixer: + hop = pygame.mixer.Sound(file = "hop.ogg") + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process space bar press to play sound. + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_SPACE: + if pygame.mixer: + hop.play() + print(hop.get_num_channels()) + elif e.__dict__["key"] == pygame.K_UP: + if pygame.mixer: + hop.set_volume(hop.get_volume() + .2) + elif e.__dict__["key"] == pygame.K_DOWN: + if pygame.mixer: + hop.set_volume(hop.get_volume() - .2) + elif e.__dict__["key"] == pygame.K_RIGHT: + if pygame.mixer: + hop.fadeout(500) + elif e.__dict__["key"] == pygame.K_LEFT: + if pygame.mixer: + hop.stop() + + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-16.py b/Chapter_10/Listing 10-16.py new file mode 100644 index 0000000..6154be9 --- /dev/null +++ b/Chapter_10/Listing 10-16.py @@ -0,0 +1,68 @@ +"""Simple sound program.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Create a window and process events related to sound.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Arrow keys change the background") + screen.fill((140, 211, 226)) + pygame.display.flip() + clock: pygame.time.Clock = pygame.time.Clock() + + # Load sound + if pygame.mixer: + pygame.mixer.music.load("splashing.ogg") + pygame.mixer.music.play(-1) + + # Process events until the user chooses to quit. + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process space bar press to play sound. + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_SPACE: + if pygame.mixer: + pygame.mixer.music.stop() + elif e.__dict__["key"] == pygame.K_UP: + if pygame.mixer: + pygame.mixer.music.set_volume( + pygame.mixer.music.get_volume() + .2) + elif e.__dict__["key"] == pygame.K_DOWN: + if pygame.mixer: + pygame.mixer.music.set_volume( + pygame.mixer.music.get_volume() - .2) + elif e.__dict__["key"] == pygame.K_RIGHT: + if pygame.mixer: + pygame.mixer.music.pause() + elif e.__dict__["key"] == pygame.K_LEFT: + if pygame.mixer: + pygame.mixer.music.unpause() + elif e.__dict__["key"] == pygame.K_f: + if pygame.mixer: + pygame.mixer.music.fadeout(4000) + + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-2.py b/Chapter_10/Listing 10-2.py new file mode 100644 index 0000000..3731f80 --- /dev/null +++ b/Chapter_10/Listing 10-2.py @@ -0,0 +1,88 @@ +"""Allowing the user to add sprites.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +class Ball(pygame.sprite.Sprite): + """A bouncing ball sprite.""" + + # Annotate object-level fields + _dx: float + _dy: float + + def __init__(self, image: pygame.Surface, x: float, y: float, + dx: float, dy: float) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = image + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + self._dx = dx + self._dy = dy + + def update(self, screen: pygame.Surface) -> None: + """Move the ball and check boundaries.""" + self.rect.centerx += self._dx + self.rect.centery += self._dy + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + self._dx *= -1 + elif self.rect.left < 0: + self.rect.left = 0 + self._dx *= -1 + if self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + self._dy *= -1 + elif self.rect.top < 0: + self.rect.top = 0 + self._dy *= -1 + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + ball: Ball + sprites: pygame.sprite.Group + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Click to add a ball") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + screen.blit(background, (0, 0)) + ball = Ball(pygame.image.load("ball.gif").convert(), 0, 0, 5, 5) + sprites = pygame.sprite.Group(ball) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + if e.type == pygame.MOUSEBUTTONDOWN: + ball = Ball(pygame.image.load("ball.gif").convert(), 0, 0, 5, 5) + sprites.add(ball) + + # Draw to the screen and show. + sprites.clear(screen, background) + sprites.update(screen) + sprites.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-3.py b/Chapter_10/Listing 10-3.py new file mode 100644 index 0000000..2678133 --- /dev/null +++ b/Chapter_10/Listing 10-3.py @@ -0,0 +1,91 @@ +"""Generating multiple sprites.""" + +# Imports and initialize pygame. +import random +import pygame +pygame.init() + +class Ball(pygame.sprite.Sprite): + """A bouncing ball sprite.""" + + # Annotate object-level fields + _dx: float + _dy: float + + def __init__(self, image: pygame.Surface, x: float, y: float, + dx: float, dy: float) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = image + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + self._dx = dx + self._dy = dy + + def update(self, screen: pygame.Surface) -> None: + """Move the ball and check boundaries.""" + self.rect.centerx += self._dx + self.rect.centery += self._dy + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + self._dx *= -1 + elif self.rect.left < 0: + self.rect.left = 0 + self._dx *= -1 + if self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + self._dy *= -1 + elif self.rect.top < 0: + self.rect.top = 0 + self._dy *= -1 + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + ball: Ball + sprites: pygame.sprite.Group + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Basic Motion") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + screen.blit(background, (0, 0)) + sprites = pygame.sprite.Group() + for i in range(10): + ball = Ball(pygame.image.load("ball.gif").convert(), + random.randint(0, 200),random.randint(0, 200), + random.randint(1, 7),random.randint(1, 7)) + sprites.add(ball) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Draw to the screen and show. + sprites.clear(screen, background) + sprites.update(screen) + sprites.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-4.py b/Chapter_10/Listing 10-4.py new file mode 100644 index 0000000..8fb847b --- /dev/null +++ b/Chapter_10/Listing 10-4.py @@ -0,0 +1,102 @@ +"""Layered groups.""" + +# Imports and initialize pygame. +import random +import pygame +pygame.init() + +class Firefly(pygame.sprite.Sprite): + """A firefly sprite.""" + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.Surface((4,4)) + pygame.draw.circle(self.image, (255, 255, 255),(2, 2), 2) + self.image.set_colorkey((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.top = random.randint(0, screen.get_height() - self.rect.height) + self.rect.left = random.randint(0, screen.get_width() - self.rect.width) + +class Camper(pygame.sprite.Sprite): + """A moving camper silhouette""" + + # Annotate object-level fields + _dx: int + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.image.load("camper.png").convert_alpha() + self.rect = self.image.get_rect() + self.rect.top = screen.get_height() - self.rect.height + self.rect.left = random.randint(0, screen.get_width() - self.rect.width) + self._dx = random.randint(1, 3) + + def update(self, screen: pygame.Surface) -> None: + """Move left to right and back.""" + self.rect.centerx += self._dx + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + self._dx *= -1 + elif self.rect.left < 0: + self.rect.left = 0 + self._dx *= -1 + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + firefly: Firefly + fireflies: pygame.sprite.Group + camper: Camper + campers: pygame.sprite.Group + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Camping") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((0, 0, 0)) + screen.blit(background, (0, 0)) + clock: pygame.time.Clock = pygame.time.Clock() + # Make fireflies and group + fireflies = pygame.sprite.Group() + for i in range(20): + firefly = Firefly(screen) + fireflies.add(firefly) + # Make camper and group + camper = Camper(screen) + campers = pygame.sprite.Group(camper) + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Draw to the screen and show. + fireflies.clear(screen, background) + campers.clear(screen, background) + fireflies.update() + campers.update(screen) + fireflies.draw(screen) + campers.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-5.py b/Chapter_10/Listing 10-5.py new file mode 100644 index 0000000..fab616a --- /dev/null +++ b/Chapter_10/Listing 10-5.py @@ -0,0 +1,128 @@ +"""Sprite variations.""" + +# Imports and initialize pygame. +import random +import pygame +pygame.init() + +class Firefly(pygame.sprite.Sprite): + """A firefly sprite.""" + + # Annotate object-level fields + _max_time: int + _timer: int + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.Surface((4,4)) + self.image.set_colorkey((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.top = random.randint(0, screen.get_height() - self.rect.height) + self.rect.left = random.randint(0, screen.get_width() - self.rect.width) + self._max_time = random.randint(500, 1000) + self._timer = random.randint(1, self._max_time) + + def update(self) -> None: + """Increment the timer and flash when it goes off.""" + self._timer += 1 + if self._timer == self._max_time: + pygame.draw.circle(self.image, (255, 255, 255),(2, 2), 2) + self._timer = 0 + elif self._timer == 10: + self.image.fill((0, 0, 0)) + +class Camper(pygame.sprite.Sprite): + """A moving camper sillhouette""" + + # Annotate object-level fields + _speed: int + _dx: int + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.image.load("camper.png").convert_alpha() + self.rect = self.image.get_rect() + self.rect.top = screen.get_height() - self.rect.bottom + self.rect.left = random.randint(0, screen.get_width() - self.rect.right) + self._speed = random.randint(1, 3) + self._dx = 0 + + def update(self, screen: pygame.Surface) -> None: + """Move the object by _dx and boundary check.""" + self.rect.centerx += self._dx + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + elif self.rect.left < 0: + self.rect.left = 0 + + def moving(self, multiplier: int) -> None: + """Set moving state; multiplier is 0 (stop), -1 (left), 1 (right).""" + self._dx = self._speed * multiplier + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + firefly: Firefly + fireflies: pygame.sprite.Group + camper: Camper + camper_group: pygame.sprite.Group + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Camping") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((0, 0, 0)) + screen.blit(background, (0, 0)) + clock: pygame.time.Clock = pygame.time.Clock() + # Make fireflies and group + fireflies = pygame.sprite.Group() + for i in range(20): + firefly = Firefly(screen) + fireflies.add(firefly) + # Make camper and group + camper = Camper(screen) + camper_group = pygame.sprite.Group(camper) + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + camper.moving(-1) + elif e.__dict__["key"] == pygame.K_RIGHT: + camper.moving(1) + elif e.type == pygame.KEYUP: + camper.moving(0) + + # Draw to the screen and show. + fireflies.clear(screen, background) + camper_group.clear(screen, background) + fireflies.update() + camper_group.update(screen) + fireflies.draw(screen) + camper_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-6.py b/Chapter_10/Listing 10-6.py new file mode 100644 index 0000000..5ba786e --- /dev/null +++ b/Chapter_10/Listing 10-6.py @@ -0,0 +1,118 @@ +"""Bad motion.""" + +# Imports and initialize pygame. +import random +import math +import pygame +pygame.init() + + +class Car(pygame.sprite.Sprite): + """A player-controlled car.""" + + # Annotate object-level fields + _speed: int + _angle: int + _moving: bool + _raw_image: pygame.Surface + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("tiny_car.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self.rect.top = random.randint(0, screen.get_height() + - self.rect.height) + self.rect.left = random.randint(0, screen.get_width() + - self.rect.width) + self._speed = 3 + self._angle = 0 + self._moving = False + + def update(self, screen: pygame.Surface) -> None: + """Move the car (no boundary checking).""" + if self._moving: + # Calculate velocity + dx: float = math.cos(math.radians(self._angle)) * self._speed + dy: float = math.sin(math.radians(self._angle)) * self._speed + # Rotate image and adjust rectangle + self.image = pygame.transform.rotate(self._raw_image, -self._angle) + x: int = self.rect.centerx + y: int = self.rect.centery + self.rect = self.image.get_rect() + # Add location and velocity to new rectangle + self.rect.centerx = x + dx + self.rect.centery = y + dy + + def change_angle(self, increase: bool) -> None: + """Increase or decrease the angle of motion.""" + if increase: + self._angle += 10 + if self._angle == 360: + self._angle = 0 + else: + self._angle -= 10 + if self._angle == -10: + self._angle = 350 + + def stop_or_go(self) -> None: + """Start or stop.""" + self._moving = not self._moving + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move a car around a window, badly.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + car: Car + car_group: pygame.sprite.Group + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Left and right arrows change direction") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((100, 100, 100)) + screen.blit(background, (0, 0)) + clock: pygame.time.Clock = pygame.time.Clock() + # Make car and group + car = Car(screen) + car_group = pygame.sprite.Group(car) + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + car.change_angle(False) + elif e.__dict__["key"] == pygame.K_RIGHT: + car.change_angle(True) + elif e.__dict__["key"] == pygame.K_SPACE: + car.stop_or_go() + + + # Draw to the screen and show. + car_group.clear(screen, background) + car_group.update(screen) + car_group.draw(screen) + + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-7.py b/Chapter_10/Listing 10-7.py new file mode 100644 index 0000000..a0147da --- /dev/null +++ b/Chapter_10/Listing 10-7.py @@ -0,0 +1,120 @@ +"""Better motion.""" + +# Imports and initialize pygame. +import random +import math +import pygame +pygame.init() + + +class Car(pygame.sprite.Sprite): + """A player-controlled car.""" + + # Annotate object-level fields + _speed: int + _angle: int + _moving: bool + _x: float + _y: float + _raw_image: pygame.Surface + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("tiny_car.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self._x = random.randint(0, screen.get_height() + - self.rect.height) + self._y = random.randint(0, screen.get_width() + - self.rect.width) + self.rect.topleft = (self._x, self._y) + self._speed = 3 + self._angle = 0 + self._moving = False + + def update(self, screen: pygame.Surface) -> None: + """Move the car (no boundary checking).""" + if self._moving: + # Calculate velocity + dx: float = math.cos(math.radians(self._angle)) * self._speed + dy: float = math.sin(math.radians(self._angle)) * self._speed + # Rotate image and adjust rectangle + self.image = pygame.transform.rotate(self._raw_image, -self._angle) + self.rect = self.image.get_rect() + # Add velocity to (x, y) and set rectangle + self._x += dx + self._y += dy + self.rect.topleft = (self._x, self._y) + + def change_angle(self, increase: bool) -> None: + """Increase or decrease the angle of motion.""" + if increase: + self._angle += 10 + if self._angle == 360: + self._angle = 0 + else: + self._angle -= 10 + if self._angle == -10: + self._angle = 350 + + def stop_or_go(self) -> None: + """Start or stop.""" + self._moving = not self._moving + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move the car around a window.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + car: Car + car_group: pygame.sprite.Group + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Left and right arrows change direction") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((100, 100, 100)) + screen.blit(background, (0, 0)) + clock: pygame.time.Clock = pygame.time.Clock() + # Make car and group + car = Car(screen) + car_group = pygame.sprite.Group(car) + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + car.change_angle(False) + elif e.__dict__["key"] == pygame.K_RIGHT: + car.change_angle(True) + elif e.__dict__["key"] == pygame.K_SPACE: + car.stop_or_go() + + + # Draw to the screen and show. + car_group.clear(screen, background) + car_group.update(screen) + car_group.draw(screen) + + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-8.py b/Chapter_10/Listing 10-8.py new file mode 100644 index 0000000..153444d --- /dev/null +++ b/Chapter_10/Listing 10-8.py @@ -0,0 +1,153 @@ +"""Collision detection.""" + +# Imports and initialize pygame. +import random +import math +import pygame +pygame.init() + + +class Car(pygame.sprite.Sprite): + """A player-controlled car.""" + + # Annotate object-level fields + _speed: int + _angle: int + _moving: bool + _x: float + _y: float + _raw_image: pygame.Surface + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("tiny_car.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self._x = random.randint(0, screen.get_height() + - self.rect.height) + self._y = random.randint(0, screen.get_width() + - self.rect.width) + self.rect.topleft = (self._x, self._y) + self._speed = 3 + self._angle = 0 + self._moving = False + + def update(self, screen: pygame.Surface) -> None: + """Move the car (no boundary checking).""" + if self._moving: + # Calculate velocity + dx: float = math.cos(math.radians(self._angle)) * self._speed + dy: float = math.sin(math.radians(self._angle)) * self._speed + # Rotate image and adjust rectangle + self.image = pygame.transform.rotate(self._raw_image, -self._angle) + self.rect = self.image.get_rect() + # Add velocity to (x, y) and set rectangle + self._x += dx + self._y += dy + self.rect.topleft = (self._x, self._y) + + def change_angle(self, increase: bool) -> None: + """Increase or decrease the angle of motion.""" + if increase: + self._angle += 10 + if self._angle == 360: + self._angle = 0 + else: + self._angle -= 10 + if self._angle == -10: + self._angle = 350 + + def stop_or_go(self) -> None: + """Start or stop.""" + self._moving = not self._moving + + def slow(self) -> None: + """Slow the car.""" + if self._speed > 0: + self._speed -= 1 + +class Hazard(pygame.sprite.Sprite): + """A road hazard.""" + + def __init__(self, screen: pygame.Surface, image: str) -> None: + """Initialize from parameters.""" + super().__init__() + self.image = pygame.image.load(image).convert_alpha() + self.rect = self.image.get_rect() + self.rect.left = random.randint(0, screen.get_width() + - self.rect.width) + self.rect.top = random.randint(0, screen.get_height() + - self.rect.height) + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Detect collisions between car and hazards.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + car: Car + car_group: pygame.sprite.Group + hazard: Hazard + hazards: pygame.sprite.Group + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Left and right arrows change direction") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((100, 100, 100)) + screen.blit(background, (0, 0)) + clock: pygame.time.Clock = pygame.time.Clock() + # Make hazards and group + hazards = pygame.sprite.Group() + for i in range(10): + hazard = Hazard(screen, "tiny_cone.png") + hazards.add(hazard) + # Make car and group + car = Car(screen) + car_group = pygame.sprite.Group(car) + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + if e.__dict__["key"] == pygame.K_LEFT: + car.change_angle(False) + elif e.__dict__["key"] == pygame.K_RIGHT: + car.change_angle(True) + elif e.__dict__["key"] == pygame.K_SPACE: + car.stop_or_go() + + # Check for collisions + crashed_hazards = pygame.sprite.spritecollide(car, + hazards, True) + for hazard in crashed_hazards: + car.slow() + + # Draw to the screen and show. + hazards.clear(screen, background) + car_group.clear(screen, background) + car_group.update(screen) + hazards.draw(screen) + car_group.draw(screen) + + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/Listing 10-9.py b/Chapter_10/Listing 10-9.py new file mode 100644 index 0000000..3ae3c90 --- /dev/null +++ b/Chapter_10/Listing 10-9.py @@ -0,0 +1,207 @@ +"""Tiled background sprite with wall collision detection.""" + +# Imports and initialize pygame. +import pygame +import io +import math +pygame.init() + +# Constants +LEFT: int = 0 +RIGHT: int = 1 +UP: int = 2 +DOWN: int = 3 +NOT_MOVING: int = -1 + +class Brick(pygame.sprite.Sprite): + """Just another brick...""" + + def __init__(self, x: int, y: int, length: int, width: int) -> None: + """Create an invisible sprite.""" + super().__init__() + self.image = pygame.Surface((width, length)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.rect.topleft = (x, y) + +class Background(pygame.sprite.Sprite): + """A tile-based background.""" + + # Annotate object-level fields + _bricks: list + + def __init__(self, tile_files: list, terrain_file: str, bricks: list) -> None: + """Load map and build Surface.""" + # Annotate and initialize local variables + name: str + line: str + tile_size: int + width: int + height: int + x: int = 0 + y: int = 0 + terrain: io.TextIOWrapper + # Superclass init. + super().__init__() + # Load the images to pygame Surfaces. + tiles: list = [] + for name in tile_files: + tiles.append(pygame.image.load(name).convert()) + # Load the terrain map into a 2D list. + terrain_map: list = [] + with open(terrain_file) as terrain: + line = terrain.readline() + while line != "": + terrain_map.append(line.split()) + line = terrain.readline() + # Calculate the size of the Surface and create. + tile_size = tiles[0].get_width() + width = len(terrain_map[0]) * tile_size + height = len(terrain_map) * tile_size + self.image = pygame.Surface((width, height)) + # Blit the images to the Surface. + # Create a Block if necessary. + self._bricks = [] + for row in terrain_map: + for i in row: + self.image.blit(tiles[int(i)], (x, y)) + if int(i) in bricks: + self._bricks.append(Brick(x, y, tile_size, tile_size)) + x += tile_size + y += tile_size + x = 0 + # Complete initialization + self.rect = self.image.get_rect() + self.rect.topleft = (0, 0) + + def get_bricks(self) -> list: + """Return the bricks.""" + return self._bricks + +class Penguin(pygame.sprite.Sprite): + """A player-controlled character.""" + + # Annotate object-level fields + _raw_image: pygame.Surface + _speed: int + _angle: int + + def __init__(self, screen: pygame.Surface) -> None: + """Initialize from parameters.""" + super().__init__() + self._raw_image = pygame.image.load("top_penguin.png") + self._raw_image = self._raw_image.convert_alpha() + self.image = self._raw_image + self.rect = self.image.get_rect() + self.rect.topleft = (0, screen.get_height() / 2 - self.rect.height / 2) + self._speed = 5 + self._angle = 0 + + def move(self, screen: pygame.Surface, + direction: int) -> None: + """Move in direction.""" + if direction == LEFT: + self._angle = 180 + self.image = pygame.transform.rotate(self._raw_image, 180) + self.rect.left -= self._speed + if self.rect.left < 0: + self.rect.left = 0 + elif direction == RIGHT: + self._angle = 0 + self.image = self._raw_image + self.rect.left += self._speed + if self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + elif direction == UP: + self._angle = 270 + self.image = pygame.transform.rotate(self._raw_image, 90) + self.rect.top -= self._speed + if self.rect.top < 0: + self.rect.top = 0 + elif direction == DOWN: + self._angle = 90 + self.image = pygame.transform.rotate(self._raw_image, -90) + self.rect.top += self._speed + if self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + + def backup(self) -> None: + """Back up one unit displacement vector.""" + # Calculate a unit vector + dx: float = -math.cos(math.radians(self._angle)) + dy: float = -math.sin(math.radians(self._angle)) + # Add to rectangle + self.rect.left += dx + self.rect.top += dy + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """The arrow keys move the penguin or scroll.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: ScrollingBackground + bkgd_group: pygame.sprite.Group + bricks: pygame.sprite.Group + penguin: Penguin + penguin_group: pygame.sprite.Group + user_quit: bool = False + e: pygame.event.Event + direction: int = NOT_MOVING + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Terrain collisions") + image_files: list = ["ice_block.jpg", "ice_wall.jpg"] + background = Background(image_files, "small_ice_castle.txt", [0]) + bkgd_group = pygame.sprite.Group(background) + bricks = pygame.sprite.Group(background.get_bricks()) + penguin = Penguin(screen) + penguin_group = pygame.sprite.Group(penguin) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + elif e.type == pygame.KEYDOWN: + # Process movement arrow keys. + if e.__dict__["key"] == pygame.K_LEFT: + direction = LEFT + elif e.__dict__["key"] == pygame.K_RIGHT: + direction = RIGHT + elif e.__dict__["key"] == pygame.K_UP: + direction = UP + elif e.__dict__["key"] == pygame.K_DOWN: + direction = DOWN + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP] + and not pygame.key.get_pressed()[pygame.K_DOWN]): + direction = NOT_MOVING + + # Move the penguin. + penguin_group.clear(screen, background.image) + penguin.move(screen, direction) + # Check for collisions. + while pygame.sprite.spritecollide(penguin, bricks, False): + penguin.backup() + # Redraw and show. + bkgd_group.draw(screen) + penguin_group.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_10/arrow.png b/Chapter_10/arrow.png new file mode 100644 index 0000000..0d25b0e Binary files /dev/null and b/Chapter_10/arrow.png differ diff --git a/Chapter_10/ball.gif b/Chapter_10/ball.gif new file mode 100644 index 0000000..4206936 Binary files /dev/null and b/Chapter_10/ball.gif differ diff --git a/Chapter_10/camper.png b/Chapter_10/camper.png new file mode 100644 index 0000000..771336a Binary files /dev/null and b/Chapter_10/camper.png differ diff --git a/Chapter_10/hop.ogg b/Chapter_10/hop.ogg new file mode 100644 index 0000000..8bb5a31 Binary files /dev/null and b/Chapter_10/hop.ogg differ diff --git a/Chapter_10/ice_block.jpg b/Chapter_10/ice_block.jpg new file mode 100644 index 0000000..d970736 Binary files /dev/null and b/Chapter_10/ice_block.jpg differ diff --git a/Chapter_10/ice_castle.txt b/Chapter_10/ice_castle.txt new file mode 100644 index 0000000..ccb1cb0 --- /dev/null +++ b/Chapter_10/ice_castle.txt @@ -0,0 +1,12 @@ +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 +1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 0 0 0 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/Chapter_10/ice_top.jpg b/Chapter_10/ice_top.jpg new file mode 100644 index 0000000..f937102 Binary files /dev/null and b/Chapter_10/ice_top.jpg differ diff --git a/Chapter_10/ice_wall.jpg b/Chapter_10/ice_wall.jpg new file mode 100644 index 0000000..55be5c3 Binary files /dev/null and b/Chapter_10/ice_wall.jpg differ diff --git a/Chapter_10/penguin_sprite_sheet2.png b/Chapter_10/penguin_sprite_sheet2.png new file mode 100644 index 0000000..1a43414 Binary files /dev/null and b/Chapter_10/penguin_sprite_sheet2.png differ diff --git a/Chapter_10/small_ice_castle.txt b/Chapter_10/small_ice_castle.txt new file mode 100644 index 0000000..067f6b6 --- /dev/null +++ b/Chapter_10/small_ice_castle.txt @@ -0,0 +1,12 @@ +0 0 0 0 0 0 0 0 0 0 0 0 +1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 0 0 0 0 +1 1 1 1 1 0 0 0 0 1 1 1 +1 1 1 1 1 0 1 1 1 1 0 0 +1 1 1 0 0 0 1 1 1 1 0 1 +1 1 1 1 1 1 1 1 1 1 0 1 +1 1 1 0 0 0 1 1 1 1 0 1 +1 1 1 1 1 0 1 1 1 1 0 1 +1 1 1 1 1 0 0 0 0 0 0 1 +1 1 1 1 1 1 1 1 1 1 1 1 +0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/Chapter_10/splashing.ogg b/Chapter_10/splashing.ogg new file mode 100644 index 0000000..4159232 Binary files /dev/null and b/Chapter_10/splashing.ogg differ diff --git a/Chapter_10/tiny_car.png b/Chapter_10/tiny_car.png new file mode 100644 index 0000000..2fba635 Binary files /dev/null and b/Chapter_10/tiny_car.png differ diff --git a/Chapter_10/tiny_cone.png b/Chapter_10/tiny_cone.png new file mode 100644 index 0000000..c92428c Binary files /dev/null and b/Chapter_10/tiny_cone.png differ diff --git a/Chapter_10/tiny_penguin.png b/Chapter_10/tiny_penguin.png new file mode 100644 index 0000000..41f0429 Binary files /dev/null and b/Chapter_10/tiny_penguin.png differ diff --git a/Chapter_10/top_penguin.png b/Chapter_10/top_penguin.png new file mode 100644 index 0000000..195f9bb Binary files /dev/null and b/Chapter_10/top_penguin.png differ diff --git a/Chapter_11/Listing 11-1.py b/Chapter_11/Listing 11-1.py new file mode 100644 index 0000000..d4eab8a --- /dev/null +++ b/Chapter_11/Listing 11-1.py @@ -0,0 +1,59 @@ +"""Font demonstration.""" + +# Import and initialize pygame. +import pygame +pygame.init() + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Create and render fonts.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 250 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + sys_font: pygame.font.Font + file_font: pygame.font.Font + font_surf: pygame.Surface + + # Set up the window. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Fonts") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((255, 255, 255)) + screen.blit(background, (0, 0)) + + # Make, render, and display the fonts. + sys_font = pygame.font.SysFont("cambria", 36) + file_font = pygame.font.Font("game-asset.ttf", 36) + font_surf = sys_font.render("Hello, world!", True, (0, 0, 0)) + screen.blit(font_surf, (10, 10)) + font_surf = file_font.render("HELLO WORLD", True, (0, 255, 0)) + screen.blit(font_surf, (10, 60)) + + # Make some changes, render, and display more. + sys_font.set_bold(True) + sys_font.set_italic(True) + sys_font.set_underline(True) + font_surf = sys_font.render("Hello, again!", True, (255, 0, 0)) + screen.blit(font_surf, (10, 100)) + pygame.display.flip() + + print(sys_font.size("Hello, world!")) + + while not user_quit: + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + pygame.quit() + +main() diff --git a/Chapter_11/Listing 11-2.py b/Chapter_11/Listing 11-2.py new file mode 100644 index 0000000..62df3dd --- /dev/null +++ b/Chapter_11/Listing 11-2.py @@ -0,0 +1,104 @@ +"""Scorekeeper sprite and stubs.""" + +# Import and initialize pygame. +import pygame +pygame.init() + +class Player(pygame.sprite.Sprite): + """Stub for player.""" + def get_health(self) -> int: + return 5 + def get_num_spheres(self) -> int: + return 2 + +class Level(): + """Stub for level.""" + def get_level(self) -> int: + return 1 + def get_total_spheres(self) -> int: + return 3 + +class Scorekeeper(pygame.sprite.Sprite): + """Responsible for displaying an updated score.""" + + # Annotate object-level fields + _player: Player + _level: Level + _font: pygame.font.Font + + def __init__(self, player: Player, level: Level) -> None: + """Initialize from parameters.""" + super().__init__() + self._player = player + self._level = level + if pygame.font: + self._font = pygame.font.SysFont("Bauhaus 93", 24) + self.image = pygame.Surface((0, 0)) + self.rect = self.image.get_rect() + + def update(self, screen: pygame.Surface) -> None: + """Get information from player, level and render.""" + health: int = self._player.get_health() + spheres: int = self._player.get_num_spheres() + level: int = self._level.get_level() + total_spheres: int = self._level.get_total_spheres() + if pygame.font: + level_surf: pygame.Surface = self._font.render("Level " + str(level), True, (0, 0, 0)) + health_surf: pygame.Surface = self._font.render("Health " + str(health), True, (0, 0, 0)) + spheres_surf: pygame.Surface = self._font.render("Spheres " + str(spheres) + + "/" + str(total_spheres), True, (0, 0, 0)) + self.image = pygame.Surface((screen.get_width(), self._font.get_height())) + self.image.fill((222, 237, 244)) + self.image.blit(level_surf, (0, 0)) + self.image.blit(spheres_surf, (screen.get_width() - spheres_surf.get_width(), 0)) + self.image.blit(health_surf, ((screen.get_width() - health_surf.get_width()) / 2, 0)) + self.rect = self.image.get_rect() + else: + pygame.display.set_caption("Level " + str(level) + " Health " + str(health) + + " Spheres " + str(spheres) + "/" + str(total_spheres)) + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Move an image randomly on the screen.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + e: pygame.event.Event + scorekeeper: Scorekeeper + sprites: pygame.sprite.Group + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Scorekeeper") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((222, 237, 244)) + screen.blit(background, (0, 0)) + scorekeeper = Scorekeeper(Player(), Level()) + sprites = pygame.sprite.Group(scorekeeper) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + + # Draw to the screen and show. + sprites.clear(screen, background) + sprites.update(screen) + sprites.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_11/Listing 11-3.py b/Chapter_11/Listing 11-3.py new file mode 100644 index 0000000..86966f8 --- /dev/null +++ b/Chapter_11/Listing 11-3.py @@ -0,0 +1,293 @@ +"""Scorekeeper sprite and stubs.""" + +from typing import ClassVar +import random + +# Import and initialize pygame. +import pygame +pygame.init() + +class Enemy(pygame.sprite.Sprite): + """A randomly moving enemy.""" + + # Annotate and initialize class-level field + MAX_TIME: ClassVar[int] = 120 + + # Annotate object-level fields + _dx: int + _dy: int + _timer: int + + def __init__(self, screen: pygame.Surface, size: int) -> None: + """Set sprite to initial state.""" + super().__init__() + self._dx = random.randint(1, 7) + self._dy = random.randint(0, 5) + self._timer = random.randint(0, Enemy.MAX_TIME - 1) + self.image = pygame.Surface((size, size)) + self.image.fill((255, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = random.randint(0, screen.get_width() - size) + self.rect.top = random.randint(0, screen.get_height() - size) + + def update(self, screen) -> None: + """Move and possibly reset dx and dy.""" + # Randomize speed and direction each MAX_TIME. + self._timer += 1 + if self._timer == Enemy.MAX_TIME: + self._dx = random.randint(1, 7) + self._dy = random.randint(0, 5) + self._timer = 0 + # Move and wrap at boundaries. + self.rect.left += self._dx + self.rect.top += self._dy + if self.rect.right < 0: + self.rect.left = screen.get_width() + elif self.rect.left > screen.get_width(): + self.rect.right = 0 + if self.rect.bottom < 0: + self.rect.top = screen.get_height() + elif self.rect.top > screen.get_height(): + self.rect.bottom = 0 + + def change_location(self, screen: pygame.Surface) -> None: + """Change to a new random location.""" + self.rect.left = random.randint(0, screen.get_width() - self.rect.width) + self.rect.top = random.randint(0, screen.get_height() - self.rect.height) + +class Sphere(pygame.sprite.Sprite): + """A non-moving goal.""" + + def __init__(self, screen: pygame.Surface, size: int) -> None: + """Set sprite to initial state.""" + super().__init__() + self.image = pygame.Surface((size, size)) + self.image.fill((0, 0, 0)) + pygame.draw.circle(self.image, (213, 244, 255), (size // 2, size // 2), size // 2) + self.image.set_colorkey((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = random.randint(0, screen.get_width() - size) + self.rect.top = random.randint(0, screen.get_height() - size) + +class Player(pygame.sprite.Sprite): + """Player motion is controlled by the arrow keys.""" + + # Annotate and initialize class-level fields + STOP: ClassVar[int] = -1 + LEFT: ClassVar[int] = 0 + RIGHT: ClassVar[int] = 1 + UP: ClassVar[int] = 2 + DOWN: ClassVar[int] = 3 + + # Annotate object-level fields + _health: int + _spheres: int + _dx: int + _dy: int + + def __init__(self, screen: pygame.Surface) -> None: + """Set sprite to initial state.""" + super().__init__() + self._health = 5 + self._spheres = 0 + self._dx = 0 + self._dy = 0 + self.image = pygame.Surface((20, 20)) + self.image.fill((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = (screen.get_width() - self.rect.width) / 2 + self.rect.top = (screen.get_height() - self.rect.height) / 2 + + def update(self, screen: pygame.Surface) -> None: + """Update sprite location.""" + self.rect.left += self._dx + self.rect.top += self._dy + if self.rect.left < 0: + self.rect.left = 0 + elif self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + if self.rect.top < 0: + self.rect.top = 0 + elif self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + + def move(self, direction: int) -> None: + """Set dx or dy.""" + self._dx = 0 + self._dy = 0 + if direction == Player.LEFT: + self._dx = -5 + elif direction == Player.RIGHT: + self._dx = 5 + elif direction == Player.UP: + self._dy = -5 + elif direction == Player.DOWN: + self._dy = 5 + + def reset_location(self, screen: pygame.Surface) -> None: + """Rest to screen center.""" + self.rect.left = (screen.get_width() - self.rect.width) / 2 + self.rect.top = (screen.get_height() - self.rect.height) / 2 + + def lose_health(self) -> bool: + """Decrement health, return True if still alive.""" + self._health -= 1 + return self._health > 0 + + def add_sphere(self) -> None: + """Add to spheres score.""" + self._spheres += 1 + + def get_health(self) -> int: + """Return current health value.""" + return self._health + + def get_num_spheres(self) -> int: + """Return current sphere value.""" + return self._spheres + +class Level(): + """Stub for level.""" + def get_level(self) -> int: + return 1 + def get_total_spheres(self) -> int: + return 3 + +class Scorekeeper(pygame.sprite.Sprite): + """Responsible for displaying an updated score.""" + + # Annotate object-level fields + _player: Player + _level: Level + _font: pygame.font.Font + + def __init__(self, player: Player, level: Level) -> None: + """Initialize from parameters.""" + pygame.sprite.Sprite.__init__(self) + self._player = player + self._level = level + if pygame.font: + self._font = pygame.font.SysFont("Bauhaus 93", 24) + self.image = pygame.Surface((0, 0)) + self.rect = self.image.get_rect() + + def update(self, screen: pygame.Surface) -> None: + """Get information from player, level and render.""" + health: int = self._player.get_health() + spheres: int = self._player.get_num_spheres() + level: int = self._level.get_level() + total_spheres: int = self._level.get_total_spheres() + if pygame.font: + level_surf: pygame.Surface = self._font.render("Level " + str(level), True, (0, 0, 0)) + health_surf: pygame.Surface = self._font.render("Health " + str(health), True, (0, 0, 0)) + spheres_surf: pygame.Surface = self._font.render("Spheres " + str(spheres) + + "/" + str(total_spheres), True, (0, 0, 0)) + self.image = pygame.Surface((screen.get_width(), self._font.get_height())) + self.image.fill((200, 200, 200)) + self.image.blit(level_surf, (0, 0)) + self.image.blit(spheres_surf, (screen.get_width() - spheres_surf.get_width(), 0)) + self.image.blit(health_surf, ((screen.get_width() - health_surf.get_width()) / 2, 0)) + self.rect = self.image.get_rect() + else: + pygame.display.set_caption("Level " + str(level) + " Health " + str(health) + + " Spheres " + str(spheres) + "/" + str(total_spheres)) + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Sphere capture game with enemies.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + game_over: bool = False + e: pygame.event.Event + scorekeeper: Scorekeeper + player: Player + sphere: Sphere + enemies: pygame.sprite.Group + sprites: pygame.sprite.Group + spheres: pygame.sprite.Group + collected_spheres: list + collided_enemies: list + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Sphere Collector") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((200, 200, 200)) + screen.blit(background, (0, 0)) + player = Player(screen) + scorekeeper = Scorekeeper(player, Level()) + sprites = pygame.sprite.Group(scorekeeper, player) + enemies = pygame.sprite.Group() + spheres = pygame.sprite.Group() + for i in range(3): + enemies.add(Enemy(screen, 20)) + for i in range(3): + spheres.add(Sphere(screen, 40)) + + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process arrow keys by changing direction. + elif e.type == pygame.KEYDOWN and not game_over: + if e.__dict__["key"] == pygame.K_LEFT: + player.move(Player.LEFT) + elif e.__dict__["key"] == pygame.K_RIGHT: + player.move(Player.RIGHT) + elif e.__dict__["key"] == pygame.K_UP: + player.move(Player.UP) + elif e.__dict__["key"] == pygame.K_DOWN: + player.move(Player.DOWN) + # Process keys up as stopping. + elif e.type == pygame.KEYUP and not game_over: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP] + and not pygame.key.get_pressed()[pygame.K_DOWN]): + player.move(Player.STOP) + + if not game_over: + # Check for collision with enemies. + collided_enemies = pygame.sprite.spritecollide(player, enemies, False) + # Reset locations and set game_over if player at zero lives. + for enemy in collided_enemies: + enemy.change_location(screen) + if player.lose_health(): + player.reset_location(screen) + else: + game_over = True + + # Check for collision with spheres. + collected_spheres = pygame.sprite.spritecollide(player, spheres, True) + for sphere in collected_spheres: + player.add_sphere() + + # Draw to the screen and show. + sprites.clear(screen, background) + spheres.clear(screen, background) + enemies.clear(screen, background) + sprites.update(screen) + enemies.update(screen) + sprites.draw(screen) + spheres.draw(screen) + enemies.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_11/Listing 11-4.py b/Chapter_11/Listing 11-4.py new file mode 100644 index 0000000..e4e0145 --- /dev/null +++ b/Chapter_11/Listing 11-4.py @@ -0,0 +1,414 @@ +"""Sphere collector game.""" + +from typing import ClassVar +import random + +# Import and initialize pygame. +import pygame +pygame.init() + +class Enemy(pygame.sprite.Sprite): + """A randomly moving enemy.""" + + # Annotate and initialize class-level field + MAX_TIME: ClassVar[int] = 120 + + # Annotate object-level fields + _dx: int + _dy: int + _timer: int + + def __init__(self, screen: pygame.Surface, size: int) -> None: + """Set sprite to initial state.""" + pygame.sprite.Sprite.__init__(self) + self._dx = random.randint(1, 7) + self._dy = random.randint(0, 5) + self._timer = random.randint(0, Enemy.MAX_TIME - 1) + self.image = pygame.Surface((size, size)) + self.image.fill((255, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = random.randint(0, screen.get_width() - size) + self.rect.top = random.randint(0, screen.get_height() - size) + + def update(self, screen) -> None: + """Move and possibly reset dx and dy.""" + # Randomize speed and direction each MAX_TIME. + self._timer += 1 + if self._timer == Enemy.MAX_TIME: + self._dx = random.randint(1, 7) + self._dy = random.randint(0, 5) + self._timer = 0 + # Move and wrap at boundaries. + self.rect.left += self._dx + self.rect.top += self._dy + if self.rect.right < 0: + self.rect.left = screen.get_width() + elif self.rect.left > screen.get_width(): + self.rect.right = 0 + if self.rect.bottom < 0: + self.rect.top = screen.get_height() + elif self.rect.top > screen.get_height(): + self.rect.bottom = 0 + + def change_location(self, screen: pygame.Surface) -> None: + """Change to a new random location.""" + self.rect.left = random.randint(0, screen.get_width() - self.rect.width) + self.rect.top = random.randint(0, screen.get_height() - self.rect.height) + +class Sphere(pygame.sprite.Sprite): + """A non-moving goal.""" + + def __init__(self, screen: pygame.Surface, size: int) -> None: + """Set sprite to initial state.""" + pygame.sprite.Sprite.__init__(self) + self.image = pygame.Surface((size, size)) + self.image.fill((0, 0, 0)) + pygame.draw.circle(self.image, (213, 244, 255), (size // 2, size // 2), size // 2) + self.image.set_colorkey((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = random.randint(0, screen.get_width() - size) + self.rect.top = random.randint(0, screen.get_height() - size) + +class Player(pygame.sprite.Sprite): + """Player motion is controlled by the arrow keys.""" + + # Annotate and initialize class-level fields + STOP: ClassVar[int] = -1 + LEFT: ClassVar[int] = 0 + RIGHT: ClassVar[int] = 1 + UP: ClassVar[int] = 2 + DOWN: ClassVar[int] = 3 + + # Annotate object-level fields + _health: int + _spheres: int + _dx: int + _dy: int + + def __init__(self, screen: pygame.Surface) -> None: + """Set sprite to initial state.""" + pygame.sprite.Sprite.__init__(self) + self._health = 5 + self._spheres = 0 + self._dx = 0 + self._dy = 0 + self.image = pygame.Surface((20, 20)) + self.image.fill((0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = (screen.get_width() - self.rect.width) / 2 + self.rect.top = (screen.get_height() - self.rect.height) / 2 + + def update(self, screen: pygame.Surface) -> None: + """Update sprite location.""" + self.rect.left += self._dx + self.rect.top += self._dy + if self.rect.left < 0: + self.rect.left = 0 + elif self.rect.right > screen.get_width(): + self.rect.right = screen.get_width() + if self.rect.top < 0: + self.rect.top = 0 + elif self.rect.bottom > screen.get_height(): + self.rect.bottom = screen.get_height() + + def move(self, direction: int) -> None: + """Set dx or dy.""" + self._dx = 0 + self._dy = 0 + if direction == Player.LEFT: + self._dx = -5 + elif direction == Player.RIGHT: + self._dx = 5 + elif direction == Player.UP: + self._dy = -5 + elif direction == Player.DOWN: + self._dy = 5 + + def reset_location(self, screen: pygame.Surface) -> None: + """Rest to screen center.""" + self.rect.left = (screen.get_width() - self.rect.width) / 2 + self.rect.top = (screen.get_height() - self.rect.height) / 2 + + def lose_health(self) -> bool: + """Decrement health, return True if still alive.""" + self._health -= 1 + return self._health > 0 + + def level_up(self, screen: pygame.Surface) -> None: + """Increment health by one, reset loc and spheres.""" + self._health += 1 + self._spheres = 0 + self.reset_location(screen) + + def reset(self, screen: pygame.Surface) -> None: + """Reset to starting state.""" + self._health = 5 + self._spheres = 0 + self.reset_location(screen) + self._dx = 0 + self._dy = 0 + + def add_sphere(self) -> None: + """Add to spheres score.""" + self._spheres += 1 + + def get_health(self) -> int: + """Return current health value.""" + return self._health + + def get_num_spheres(self) -> int: + """Return current sphere value.""" + return self._spheres + +class Level(): + """A level in the game.""" + + # Annotate object-level fields. + _level_number: int + _num_enemies: int + _size_enemies: int + _num_spheres: int + _size_spheres: int + + def __init__(self, num: int, enemy_num: int, enemy_size: int, sphere_num: int, + sphere_size: int) -> None: + """Initialize from parameters.""" + self._level_number = num + self._num_enemies = enemy_num + self._size_enemies = enemy_size + self._num_spheres = sphere_num + self._size_spheres = sphere_size + + def get_enemies(self, screen: pygame.Surface) -> pygame.sprite.Group: + """Return a group of enemies.""" + enemies: pygame.sprite.Group = pygame.sprite.Group() + i: int + for i in range(self._num_enemies): + enemies.add(Enemy(screen, self._size_enemies)) + return enemies + + def get_spheres(self, screen: pygame.Surface) -> pygame.sprite.Group: + """Return a group of spheres.""" + spheres: pygame.sprite.Group = pygame.sprite.Group() + i: int + for i in range(self._num_spheres): + spheres.add(Sphere(screen, self._size_spheres)) + return spheres + + def get_level(self) -> int: + """Return the number of this level.""" + return self._level_number + + def get_total_spheres(self) -> int: + """Return the total number of spheres.""" + return self._num_spheres + +class Scorekeeper(pygame.sprite.Sprite): + """Responsible for displaying an updated score.""" + + # Annotate object-level fields + _player: Player + _level: Level + _font: pygame.font.Font + _demo: bool + + def __init__(self, player: Player, level: Level) -> None: + """Initialize from parameters.""" + pygame.sprite.Sprite.__init__(self) + self._player = player + self._level = level + self._demo = False + if pygame.font: + self._font = pygame.font.SysFont("Bauhaus 93", 24) + self.image = pygame.Surface((0, 0)) + self.rect = self.image.get_rect() + + def level_up(self, level: Level) -> None: + """Set to new level.""" + self._level = level + + def demo(self, over: bool) -> None: + """Set game over status.""" + self._demo = over + + def update(self, screen: pygame.Surface) -> None: + """Get information from player, level and render.""" + health: int = self._player.get_health() + spheres: int = self._player.get_num_spheres() + level: int = self._level.get_level() + total_spheres: int = self._level.get_total_spheres() + if pygame.font: + level_surf: pygame.Surface = self._font.render("Level " + str(level), True, (0, 0, 0)) + health_surf: pygame.Surface = self._font.render("Health " + str(health), True, (0, 0, 0)) + spheres_surf: pygame.Surface = self._font.render("Spheres " + str(spheres) + + "/" + str(total_spheres), True, (0, 0, 0)) + self.image = pygame.Surface((screen.get_width(), self._font.get_height())) + self.image.fill((200, 200, 200)) + if self._demo: + self.image.set_alpha(100) + self.image.blit(level_surf, (0, 0)) + self.image.blit(spheres_surf, (screen.get_width() - spheres_surf.get_width(), 0)) + self.image.blit(health_surf, ((screen.get_width() - health_surf.get_width()) / 2, 0)) + self.rect = self.image.get_rect() + else: + pygame.display.set_caption("Level " + str(level) + " Health " + str(health) + + " Spheres " + str(spheres) + "/" + str(total_spheres)) + +class Message(pygame.sprite.Sprite): + """A message centered in the screen.""" + + def __init__(self) -> None: + """Initialize from parameters.""" + pygame.sprite.Sprite.__init__(self) + self.image = pygame.Surface((0, 0)) + self.rect = self.image.get_rect() + + def set_message(self, message: str, screen: pygame.Surface) -> bool: + """Change to new message.""" + # Annotate and initialize variables + too_long: bool = True + font: pygame.Font + size: int = 38 + + if pygame.font: + while too_long and size >= 10: + size -= 2 + font = pygame.font.SysFont("Bauhaus 93", size) + too_long = font.size(message)[0] > screen.get_width() + if size >= 10: + self.image = font.render(message, True, (0, 0, 0)) + self.rect = self.image.get_rect() + self.rect.left = (screen.get_width() - self.rect.width) / 2 + self.rect.top = (screen.get_height() - self.rect.height) / 2 + return self.rect.width > 0 + + +def make_window(width: int, height: int, caption: str) -> pygame.Surface: + """Create and return a pygame window.""" + screen: pygame.Surface + screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption(caption) + return screen + +def main() -> None: + """Sphere capture game with enemies.""" + # Annotate and initialize variables. + SCREEN_SIZE: int = 480 + screen: pygame.Surface + background: pygame.Surface + user_quit: bool = False + demo: bool = False + e: pygame.event.Event + scorekeeper: Scorekeeper + player: Player + sphere: Sphere + message: Message + enemies: pygame.sprite.Group + sprites: pygame.sprite.Group + spheres: pygame.sprite.Group + collected_spheres: list + collided_enemies: list + levels: list + current_level: int = 0 + i: int + + # Set up assets. + screen = make_window(SCREEN_SIZE, SCREEN_SIZE, "Sphere Collector") + background = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE)) + background.fill((200, 200, 200)) + screen.blit(background, (0, 0)) + player = Player(screen) + levels = [Level(1, 3, 20, 3, 40), Level(2, 4, 30, 4, 30), + Level(3, 5, 40, 5, 20)] + scorekeeper = Scorekeeper(player, levels[current_level]) + message = Message() + sprites = pygame.sprite.Group(scorekeeper, player, message) + enemies = levels[current_level].get_enemies(screen) + spheres = levels[current_level].get_spheres(screen) + clock: pygame.time.Clock = pygame.time.Clock() + + while not user_quit: + # Loop 30 times per second + clock.tick(30) + + for e in pygame.event.get(): + # Process a quit choice. + if e.type == pygame.QUIT: + user_quit = True + # Process arrow keys by changing direction. + elif e.type == pygame.KEYDOWN and not demo: + if e.__dict__["key"] == pygame.K_LEFT: + player.move(Player.LEFT) + elif e.__dict__["key"] == pygame.K_RIGHT: + player.move(Player.RIGHT) + elif e.__dict__["key"] == pygame.K_UP: + player.move(Player.UP) + elif e.__dict__["key"] == pygame.K_DOWN: + player.move(Player.DOWN) + elif e.type == pygame.KEYDOWN and demo: + if e.__dict__["key"] == pygame.K_SPACE: + # Start the game over + demo = False + scorekeeper.demo(False) + message.set_message("", screen) + current_level = 0 + enemies.clear(screen, background) + spheres.clear(screen, background) + enemies = levels[current_level].get_enemies(screen) + spheres = levels[current_level].get_spheres(screen) + player.reset(screen) + scorekeeper.level_up(levels[current_level]) + + # Process keys up as stopping. + elif e.type == pygame.KEYUP: + if (not pygame.key.get_pressed()[pygame.K_LEFT] + and not pygame.key.get_pressed()[pygame.K_RIGHT] + and not pygame.key.get_pressed()[pygame.K_UP] + and not pygame.key.get_pressed()[pygame.K_DOWN]): + player.move(Player.STOP) + + if not demo: + # Check for collision with enemies. + collided_enemies = pygame.sprite.spritecollide(player, enemies, False) + # Reset locations and set demo if player at zero lives. + for enemy in collided_enemies: + enemy.change_location(screen) + if not demo and player.lose_health(): + player.reset_location(screen) + else: + demo = True + scorekeeper.demo(True) + message.set_message("Space bar to play", screen) + + # Check for collision with spheres. + collected_spheres = pygame.sprite.spritecollide(player, spheres, True) + for sphere in collected_spheres: + player.add_sphere() + if player.get_num_spheres() == levels[current_level].get_total_spheres(): + current_level += 1 + if current_level < len(levels): + scorekeeper.level_up(levels[current_level]) + enemies.clear(screen, background) + spheres.clear(screen, background) + enemies = levels[current_level].get_enemies(screen) + spheres = levels[current_level].get_spheres(screen) + player.level_up(screen) + else: + demo = True + scorekeeper.demo(True) + message.set_message("Space bar to play", screen) + + # Draw to the screen and show. + sprites.clear(screen, background) + spheres.clear(screen, background) + enemies.clear(screen, background) + sprites.update(screen) + enemies.update(screen) + sprites.draw(screen) + spheres.draw(screen) + enemies.draw(screen) + pygame.display.flip() + + pygame.quit() + +main() diff --git a/Chapter_11/game-asset.ttf b/Chapter_11/game-asset.ttf new file mode 100644 index 0000000..afa0d20 Binary files /dev/null and b/Chapter_11/game-asset.ttf differ diff --git a/README.md b/README.md index 2523495..b28605b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # PythonObjectsGames -The code examples from Objects and Games with Python + +The code examples from _Objects and Games with Python_ + August, 2019