Dynamic Grids

Objective: Dynamic Grids

Let's begin by reviewing our objective.

Objective: Write a Python script using Turtle graphics. Use nested for loops (e.g., outer loop for rows, inner loop for columns, both iterating 4 times for a 4x4 grid) to draw squares (side length 20, with centers spaced 30 pixels apart). For each square: 1. If the current row index (from the outer loop, 0-indexed) is less than 2, set the square's line color to red; otherwise, set it to blue. 2. If the current column index (from the inner loop, 0-indexed) is an odd number, fill the square with a light gray color; otherwise, the square should be an outline only (not filled).

Check Spec: See Failing Tests

Next, we'll run pytest to see the failing tests. This confirms the engineering specification we need to meet.

Run the tests now.

Test Results:

  • test_grid_drawing_and_conditions

Implement: main.py

Now, let's build the solution by following the TODO comments in the skeleton code.

Step by step checklist:

  1. Set the pen color based on the 'row' variable. If the row is less than 2, the color should be "red". Otherwise, it should be "blue".
  2. Determine if the square should be filled based on the 'col' variable. If the column is an odd number, set the fill color to "light gray".
  3. Draw the square. Put the pen down before drawing. If the square should be filled (from step 2), call begin_fill(). Draw the 4 sides of the square using the SQUARE_SIDE constant. If filling, call end_fill().

The following documentation sections are going to be helpful:

  • Conditional Execution
  • The if Statement
  • The if/else Statement
  • Comparisons and Logic
  • Comparing Values: Relational Operators
  • The Modulo Operator (%)
  • Repeating Actions: Loops
  • The for Loop and range()
  • Patterns and Grids: Nested Loops
  • Turtle Graphics Commands Used
  • t.pencolor(color_name)
  • t.fillcolor(color_name)
  • t.begin_fill()
  • t.end_fill()
  • t.pendown()
  • t.forward(distance)
  • t.right(angle)

Validate: Run Tests Again

With the code in place, let's run the tests again to validate our work.

Run the tests now.

Test Results:

  • test_grid_drawing_and_conditions All tests passed!

Debrief: Prepare for Code Review

Great job! You've successfully implemented the dynamic grid drawing logic using nested loops and conditional statements.

Now, prepare for your code review with your mentor. Be ready to explain:

  • How you used the row index to determine the pen color.
  • How you used the col index and the modulo operator to determine if a square should be filled.
  • How you integrated the begin_fill() and end_fill() calls conditionally.
  • How the nested loops structure creates the grid pattern.

Documentation

Python Fundamentals: Conditions and Nested Structures

This document provides a quick reference for key Python concepts related to conditional execution, comparisons, arithmetic, loops, and functions, as covered in this learning module.

Conditional Execution

Code can be executed selectively based on whether a condition is true or false.

The if Statement

Executes a block of code only if a specified condition is true.

name = "Admin"
if name == "Admin":
    print("Welcome, Administrator!")
# If the condition (name == "Admin") is false, the print statement is skipped.

Flowchart illustrating a single 'if' condition leading to a block or skipping it.

The if/else Statement

Provides two paths of execution: one if the condition is true, and another if it is false.

password_length = 7
if password_length >= 8:
    print("Strong password.")
else:
    print("Weak password. Consider making it longer.")

Flowchart illustrating an 'if/else' condition leading to one of two blocks.

The if/elif/else Statement

Handles multiple distinct conditions in sequence. elif stands for "else if". Python evaluates conditions from top to bottom. The code block associated with the first true condition is executed. If no conditions are true, the else block (if present) runs.

day_number = 6 # Example input

if 1 <= day_number <= 5: # Checks if day_number is between 1 and 5 (inclusive)
    print("It is a weekday.")
elif day_number == 6 or day_number == 7:
    print("It is a weekend day!")
else:
    print("Invalid day number.")

Flowchart illustrating an 'if/elif/else' structure with multiple possible paths.

Comparisons and Logic

Conditions are created using operators that compare values or check relationships.

Comparing Values: Relational Operators

Relational operators are used to compare two values. They evaluate to a Boolean value: True or False.

  • == : Equal to
  • != : Not equal to
  • > : Greater than
  • < : Less than
  • >= : Greater than or equal to
  • <= : Less than or equal to

Example using relational operators in conditions:

temperature = 25 # Example input

if temperature > 30:
    print("It is hot.")
elif temperature > 15: # This condition is checked only if temperature > 30 was false.
                       # It effectively checks if 15 < temperature <= 30.
    print("It is warm.")
elif temperature > 0:  # Effectively checks if 0 < temperature <= 15.
    print("It is cool.")
else:                  # Effectively checks if temperature <= 0.
    print("It is cold.")

The Modulo Operator (%)

The modulo operator (%) returns the remainder of a division.

Example:

remainder_result = 10 % 3    # Result: 1 (10 divided by 3 is 3 with a remainder of 1)
another_remainder = 17 % 5 # Result: 2 (17 divided by 5 is 3 with a remainder of 2)

Checking for even/odd numbers using modulo:

number = 4
if number % 2 == 0:
    print(f"{number} is even.")
else:
    print(f"{number} is odd.")

Working with Numbers

Python supports standard mathematical operations.

Arithmetic Operations

  • + : Addition (e.g., 10 + 3 results in 13)
  • - : Subtraction (e.g., 10 - 3 results in 7)
  • * : Multiplication (e.g., 10 * 3 results in 30)
  • / : True Division (always results in a float, e.g., 10 / 3 results in 3.333...)
  • //: Integer Division (discards the fractional part, e.g., 10 // 3 results in 3; 9 // 3 results in 3)
  • % : Modulo (gives the remainder of a division, e.g., 10 % 3 results in 1)

Example:

num1 = 10
num2 = 3

sum_result = num1 + num2          # Result: 13
difference_result = num1 - num2   # Result: 7
product_result = num1 * num2      # Result: 30
true_div_result = num1 / num2     # Result: 3.3333333333333335
int_div_result = num1 // num2     # Result: 3
remainder_result = num1 % num2    # Result: 1

Repeating Actions: Loops

Loops allow a block of code to be executed multiple times.

The for Loop and range()

A for loop is often used to iterate over a sequence (like a list or string) or to repeat an action a specific number of times using the range() function.

range(n) generates a sequence of numbers from 0 up to (but not including) n. range(start, stop) generates numbers from start up to (but not including) stop.

Example: Repeating an action 3 times

for i in range(3):
    print("Repeat")
# This will print "Repeat" three times, with i being 0, then 1, then 2.

Patterns and Grids: Nested Loops

A loop can be placed inside another loop. This is known as nesting. Nested loops are often used for tasks like processing 2D data (e.g., grids or tables). The inner loop completes all its iterations for each single iteration of the outer loop.

Example: Printing a 3x4 grid of asterisks.

rows = 3
cols = 4

for r_idx in range(rows):      # Outer loop iterates 3 times (for rows 0, 1, 2)
    for c_idx in range(cols):  # Inner loop iterates 4 times (for columns 0, 1, 2, 3) for each row
        print("*", end="")     # Print an asterisk. 'end=""' prevents moving to a new line.
    print()                    # After the inner loop completes for a row, print a newline character.

Output:

****
****
****

Flowchart illustrating the execution path of nested loops.

Functions

Functions are blocks of reusable code that perform a specific task.

Defining and Calling Functions

Functions are defined using the def keyword. They can take parameters (inputs) and can return a value (output).

def greet(name): # 'name' is a parameter
    print(f"Hello, {name}!")

# Calling the function
greet("Alice") # "Alice" is the argument passed to the 'name' parameter

Return Values

The return statement sends a value back from a function. If a function doesn't have a return statement, it implicitly returns None.

def add(a, b):
    sum_result = a + b
    return sum_result # Return the calculated sum

result = add(5, 3) # The value 8 is returned and stored in 'result'
print(result) # Prints 8

A function can return multiple values by packaging them into a tuple.

def get_stats(x, y):
    sum_val = x + y
    diff_val = x - y
    return sum_val, diff_val # Returns a tuple (sum_val, diff_val)

s, d = get_stats(10, 5) # Unpacks the tuple into two variables
print(f"Sum: {s}, Difference: {d}") # Prints "Sum: 15, Difference: 5"

Enhancing Readability: Type Hints (A Brief Look)

Type hints are annotations that suggest the expected data types for function parameters and return values. They do not affect how the code runs (Python remains dynamically typed), but they improve code readability and can be used by static analysis tools.

Syntax:

  • For parameters: parameter_name: type
  • For return values: -> return_type

Example:

def add_numbers(a: int, b: int) -> int:
    return a + b

result: int = add_numbers(5, 3) # result is 8
# The hints suggest 'a' and 'b' are integers, and the function returns an integer.

Diagram showing import random and random.randint(1, 10) with an arrow indicating a random number output.

Using External Code: Modules

Python's functionality can be extended using modules. Modules are files containing Python definitions and statements. Python comes with a standard library of modules, and many more are available from third parties.

To use functions or variables from a module, you first need to import it.

import random # Imports the 'random' module

Once imported, you can access its contents using dot notation (module_name.item_name).

# Generate a random integer between 1 and 10 (inclusive)
random_number = random.randint(1, 10)
print(random_number) # Prints a random number, e.g., 7

Output

Printing to the Console

The print() function displays output to the console.

print("Hello, world!") # Prints "Hello, world!" followed by a newline
print("Sum:", 10 + 5) # Prints "Sum: 15"

# Using f-strings for formatted output
num1 = 5
num2 = 10
result = 15
print(f"The sum of {num1} and {num2} is {result}") # Prints "The sum of 5 and 10 is 15"

The print() function has an optional end parameter which specifies what to print at the end. By default, end is \n (a newline). Setting end="" prevents the newline.

print("This is on one line", end="")
print(" and this continues it.")
# Output: This is on one line and this continues it.

Turtle Graphics Commands Used

This module utilizes several commands from the turtle module for drawing:

  • turtle.Turtle(): Creates a new turtle object.
  • t.speed(speed): Sets the turtle's speed (0 is fastest).
  • t.hideturtle(): Makes the turtle cursor invisible.
  • t.penup(): Lifts the pen so the turtle moves without drawing.
  • t.pendown(): Puts the pen down so the turtle draws when it moves.
  • t.goto(x, y): Moves the turtle to the specified coordinates.
  • t.forward(distance): Moves the turtle forward by the specified distance.
  • t.right(angle): Turns the turtle right by the specified angle (in degrees).
  • t.left(angle): Turns the turtle left by the specified angle (in degrees).
  • t.color(color_name): Sets both the pen color and fill color.
  • t.pencolor(color_name): Sets only the pen color.
  • t.fillcolor(color_name): Sets only the fill color.
  • t.begin_fill(): Starts the process of filling a shape.
  • t.end_fill(): Finishes the fill process, coloring the shape drawn since begin_fill.
  • t.circle(radius): Draws a circle with the given radius.
  • t.dot(size, color): Draws a dot with the given diameter and color.
  • turtle.Screen(): Creates a drawing window.
  • screen.tracer(n): Turns turtle animation on/off and set delay for updates (0 turns off animation).
  • screen.update(): Performs a turtle screen update when animation is off.
  • turtle.done() or screen.exitonclick(): Keeps the turtle window open until manually closed or clicked.