Popcorn Hack 1 — Book class

Context: A Book is a small object that stores identifying information. This hack helps you practice defining a class, adding an initializer (__init__), and a simple method that returns a description string.

What to do: Read the code cell below, run it, then try the following:

  1. Create another Book instance with a different title and author and print its description.
  2. Add a year attribute to the class, update __init__, and include the year in the description() output.
# Popcorn Hack 1: Create a simple class for a Book
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def description(self):
        return f"'{self.title}' by {self.author}"

# Practice: Create a Book object and print its description
my_book = Book("1984", "George Orwell")
print(my_book.description())
'1984' by George Orwell

Popcorn Hack 2 — Rectangle class

Context: Practice creating a class that represents a simple geometric shape. This hack focuses on initializing numeric attributes and writing a method that computes and returns a derived value (area).

What to do: Run the code cell below, then try:

  1. Add a perimeter() method that returns the perimeter.
  2. Add validation in __init__ to ensure width and height are positive numbers (raise ValueError otherwise).
# Popcorn Hack 2: Create a class for a Rectangle with area calculation
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# Practice: Create a Rectangle object and print its area
my_rectangle = Rectangle(5, 3)
print("Area:", my_rectangle.area())

Homework: Tic-Tac-Toe (3 parts)

This homework uses small, function-style pieces of the Tic-Tac-Toe code (board as a list of 9 strings). Scoring: each part is worth up to 0.3 points, plus up to 0.03 points exceptional credit for elegant/pythonic solutions (e.g., use of all()/any() or clear docstrings).

Instructions: For each part below, replace the TODO implementation with working code and run the cell’s tests. When you’re ready, run the final grader cell which will print your score breakdown.

Part A — Win detection (0.3 pts)

Implement check_winner(board, symbol) which returns True when symbol (e.g. ‘X’) appears in any winning 3-in-a-row pattern on board. board is a list of 9 strings where an empty square is ' '.

Exceptional credit (0.01 pts): your implementation uses all() or any() and includes a short docstring explaining the approach.

Part B — Find winning/blocking move (0.3 pts)

Implement find_best_move(board, player) which returns the index (0-8) of an empty square that completes player to three-in-a-row, or -1 if none exists. This is the helper used by simple Tic-Tac-Toe AIs.

Exceptional credit (0.01 pts): your implementation is concise (uses comprehensions) and includes a short docstring.

# Part A: implement check_winner(board, symbol)
win_patterns = [
    [0, 1, 2], [3, 4, 5], [6, 7, 8],  # Rows
    [0, 3, 6], [1, 4, 7], [2, 5, 8],  # Columns
    [0, 4, 8], [2, 4, 6]              # Diagonals
]

def check_winner(board, symbol):
    """
    Check if the given symbol has a winning pattern on the board.
    
    Args:
        board (list): A list of 9 strings representing the Tic-Tac-Toe board.
        symbol (str): The player's symbol ('X' or 'O') to check for a win.
    
    Returns:
        bool: True if the symbol has a winning pattern, False otherwise.
    """
    # Ensure the board is valid
    if not isinstance(board, list) or len(board) != 9:
        raise ValueError("board must be a list of 9 elements")
    
    # Check all win patterns
    for pattern in win_patterns:
        if all(board[i] == symbol for i in pattern):
            return True
    return False

# Tests for Part A
_tests_A = [
    (['X', 'X', 'X', ' ', ' ', ' ', ' ', ' ', ' '], 'X', True),
    (['O', ' ', ' ', 'O', ' ', ' ', 'O', ' ', ' '], 'O', True),
    (['X', 'O', 'X', 'O', 'X', 'O', 'O', 'X', 'O'], 'X', False),
]

def grade_part_A():
    passed = 0
    for b, s, expected in _tests_A:
        try:
            result = check_winner(b, s)
        except Exception as e:
            print('Part A: error during execution:', e)
            return 0.0, 0.0  # No points if it crashes
        if result == expected:
            passed += 1
    score = 0.3 * (passed / len(_tests_A))
    # Small heuristic for exceptional credit: check source for 'all(' or 'any(' and presence of docstring
    import inspect
    extra = 0.0
    try:
        src = inspect.getsource(check_winner)
        if ('all(' in src or 'any(' in src) and check_winner.__doc__:
            extra = 0.03
    except Exception:
        pass
    return score, extra

# Run quick self-check (you'll see failures until you implement the function)
print('Part A quick check: call grade_part_A() after implementing check_winner')

Part C — Safe move placement (0.3 pts)

Implement make_move(board, position, player) which attempts to place player at position (0-8). It should return True and update the board when the move is valid; return False (and leave board unchanged) when the position is invalid or occupied.

Exceptional credit (0.01 pts): your implementation includes input validation and a docstring describing edge cases.

# Part C: implement make_move(board, position, player)
def make_move(board, position, player):
    """
    Attempt to place `player` at `position` (0-8) on `board`.
    
    Args:
        board (list): A list of 9 strings representing the Tic-Tac-Toe board.
        position (int): The index (0-8) where the player wants to place their symbol.
        player (str): The player's symbol ('X' or 'O').
    
    Returns:
        bool: True if the move is valid and the board is updated, False otherwise.
    """
    # Validate the board
    if not isinstance(board, list) or len(board) != 9:
        raise ValueError("board must be a list of 9 elements")
    
    # Validate the position
    if not isinstance(position, int) or position < 0 or position > 8:
        return False
    
    # Check if the position is already occupied
    if board[position] != ' ':
        return False
    
    # Make the move
    board[position] = player
    return True

# Tests for Part C
_tests_C = [
    ([' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], 4, 'X', True),
    (['X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], 0, 'O', False),
    (['X', 'O', 'X', 'O', 'X', 'O', 'O', 'X', 'O'], 2, 'X', False),
]

def grade_part_C():
    passed = 0
    for b_init, pos, player, expected in _tests_C:
        b = b_init.copy()
        try:
            result = make_move(b, pos, player)
        except Exception as e:
            print('Part C: error during execution:', e)
            return 0.0, 0.0
        # Check boolean result and that board updated only when expected
        if result == expected:
            if expected:
                if b[pos] == player:
                    passed += 1
            else:
                # Board should be unchanged for failed move
                if b == b_init:
                    passed += 1
    score = 0.3 * (passed / len(_tests_C))
    import inspect
    extra = 0.0
    try:
        src = inspect.getsource(make_move)
        # Exceptional credit: docstring present and no unnecessary raising
        if make_move.__doc__ and 'return' in src:
            extra = 0.03
    except Exception:
        pass
    return score, extra

print('Part C quick check: call grade_part_C() after implementing make_move')

Submission

Submit the hack here: https://forms.gle/hH9g7Ja9JQvXmsp89