投資、科技、生活


Stanford Code in Place - Python入門課程Week 6

Stanford Python程式課 - 第六週

從第六週開始Code in Place課程提供學員CS 106A後續課程資料、程式碼自學,學校不對外提供上課影片!

第六週學習目標

目前課程進度與後續課程規劃,Source: Stanford Code in Place & CS106A




Lesson 15 More Lists

學習重點

  • 學習 slices
  • 熟悉 two-dimensional lists

課程講義 


講義下載連結:More lists

點我到 More lists 課程程式範例



Lesson 16: Graphics

學習重點

  • 如何繪製圖形
  • Variables 被存在記憶體位址,記憶體位址像是網址 URL
  • Binding 是修改記憶體位址
  • Mutating 是修改對應的內容
  • 使用 tkinter 

課程講義


講義下載連結:Graphics

延伸閱讀 tkinter

使用 Tk

Tk 是Python上最常被用來繪圖的模組,使用前需要在程式開頭 import tkinter。

  import tkinter

Canvas是繪圖空間,最左上角座標為 (0,0),X是水平方向、Y是垂直方向的座標。


點我到 Graphics 程式範例





Lesson 17: Animation

學習重點

  • 寫出動畫程式

講義下載連結:Animation

點我到 Animation 程式範例

請到程式範例下載 graphics.py ,存放在程式範例相同目錄下,程式才能順利執行。


Assignment 3

Assignment 3 講義下載:Assignment3Handout.pdf

Assignment 3 Starter code下載:Assignment3.zip


延伸閱讀

第一週課程

第二週課程

第三週課程

第四週課程

第五週課程

未完待續 ...






More list 程式

gridswap.py

"""
File: gridswap.py
-----------------
This program shows an example of swapping items
in a list of lists (grid).
"""


def swap(grid, row1, col1, row2, col2):
    """
    This function swaps the elements at locations (row1, col1)
    and (row2, col2) in the grid passed in.
    """
    temp = grid[row1][col1]
    grid[row1][col1] = grid[row2][col2]
    grid[row2][col2] = temp


def main():
    my_grid = [[10, 20, 30], [40, 50, 60]]
    print("Original grid:")
    print(my_grid)

    swap(my_grid, 0, 1, 1, 2)
    print("Grid after swapping two elements:")
    print(my_grid)


if __name__ == '__main__':
    main()

listswap.py

"""
File: listswap.py
-----------------
This program shows examples of buggy and successful ways
of swapping two elements in a list.
"""


def swap_elements_buggy(elem1, elem2):
    """
    This function tries to swap the two elements passed it.
    This code is BUGGY!
    """
    temp = elem1
    elem1 = elem2
    elem2 = temp


def swap_elements_working(alist, index1, index2):
    """
    This function successfully swaps the two elements at
    positions index1 and index2 in the list (alist) passed in.
    """
    temp = alist[index1]
    alist[index1] = alist[index2]
    alist[index2] = temp


def main():
    my_list = [10, 20, 30]
    print("Original list:")
    print(my_list)

    swap_elements_buggy(my_list[0], my_list[1])
    print("List after buggy attempt at swap:")
    print(my_list)

    print("List after successful attempt at swap:")
    swap_elements_working(my_list, 0, 1)
    print(my_list)


if __name__ == '__main__':
    main()

spread.py

"""
File: spread.py
---------------
This program simulates the spread of an infection in a population.  A world
is represented as a list of lists (2-dimensional list), where each cell
contains either a person who is negative for a condition ('-'), a person who
is positive for a condition ('+'), no person but the cell is within reach of
infection ('.'), or is empty and not within reach of infection(None).
An initial infection point is specified and all people within RADIUS cells
around that point (i.e., a square) are infected.  Iteratively, all newly
infected people can infect others who are within RADIUS cells of them.  The
simulation continues until no new infections are detected.
"""

import random

SIZE = 10       # The world used will be SIZE x SIZE
RADIUS = 1      # Radius around which an infection spreads
DENSITY = 0.25  # Probability that a cell starts with a (negative) person in it

def initialize_grid(n):
    """
    Creates and returns a grid (list of lists), that has n rows and
    each row has n columns.  All the elements in the grid are set to
    random values, representing None (no one) or '-' (person who is
    negative for the initial condition) based on DENSITY of people.
    """
    grid = []                           # Create empty grid
    for row in range(n):                # Create rows one at a time
        row = []
        for col in range(n):            # Build up each row by appending to a list
            chance = random.random()    # Get random float between 0.0 and 1.0
            if chance < DENSITY:     # Add a person (who is negative)
                row.append('-')
            else:
                row.append(None)        # Add an "empty" cell
        grid.append(row)                # Append the row (list) onto grid

	return grid

def set_infection(grid, row, col):
    """
    Given a grid and an infection point (col, row), this function sets any person
    who is negative within RADIUS squares of the infection point to be positive
    (denoted by '+') and any other cells to '.' to denoted they were exposed.
    The function returns True if any previously negative people were infected
    (i.e., become positive), False otherwise.
    """
    start_row = row - RADIUS
    end_row = row + RADIUS
    start_col = col - RADIUS
    end_col = col + RADIUS
    infected = False

    for row in range(start_row, end_row + 1):
        for col in range(start_col, end_col + 1):
            if row >= 0 and row < SIZE and col >= 0 and col < SIZE:
                if grid[row][col] == '-':   # negative people become positive
                    grid[row][col] = '+'
                    infected = True
                elif not grid[row][col]:    # "not grid[row][col]" checks for None
                    grid[row][col] = '.'    # empty cells marked as infected

    return infected

def spread_infection(grid):
    """
    Loops through all cells in the grid and for every positive person,
    it makes them an infection point to potentially spread infection.
    The function returns True if any previously negative people were infected
    (i.e., become positive), False otherwise.
    """
    infected = False
    rows = len(grid)                    # Could have used SIZE, but wanted
    cols = len(grid[0])                 # to show a different way to do this.

    for row in range(rows):
        for col in range(cols):
            if grid[row][col] == '+':   # Positive person is an infection point
                new_infection = set_infection(grid, row, col)
                infected = infected or new_infection

    return infected

def get_value_in_range(prompt, min, max):
    """
    Asks the user for a value using the prompt string.  Checks if
    user entered value is between min and max (inclusive) and prompts
    the user if the value is out of range.  Returns a valid value entered
    by the user.
    """
    while True:
        value = int(input(prompt))
        if value >= min and value <= max:
            return value
        print("Invalid entry.  Please enter a value between", min, "and", max)

def print_borderline(length):
    """
    Prints a border line of (length + 1) #'s, with leading spaces
    """
    print('  ', end='')
    for i in range(length + 1):
        print('# ', end='')
    print('#')

def print_column_indexes(length):
    """
    Prints the column index numbers (length of them), appropriately spaced
    """
    print('    ', end='')
    for i in range(length):
        print_formatted(i)
    print('')       # Print a "return" to end the line

def print_formatted(num):
    """
    Prints a one or two digit number in two spaces
    """
    if num < 10:
        print(str(num) + ' ', end='')
    else:
        print(str(num), end='')

def print_grid(grid):
    """
    Prints out grid, including row/column index numbers and borders
    """
    print_column_indexes(SIZE)
    print_borderline(SIZE)

    rows = len(grid)                    # Could have used SIZE, but wanted
    cols = len(grid[0])                 # to show a different way to do this.
    for row in range(rows):
        print_formatted(row)
        print('# ', end='')             # Print left-hand border
        for col in range(cols):
            symbol = grid[row][col]
            if not symbol:              # Print a space if symbol is None
                print(' ', end='')
            else:
                print(symbol, end='')
            print(' ', end='')          # Prints space for paddingS
        print('#')                      # Print right-hand border (and return)
    print_borderline(SIZE)

def main():
    """
    Create the initial (random) grid, ask the user for an initial infection
    point and then keep spreading the infection until no new people in the
    simulation are infected.  Print out the initial and final grids
    """
    random.seed(2)

    # Create the initial grid
    grid = initialize_grid(SIZE)
    print_grid(grid)

    # Get initial infection point
    row = get_value_in_range("Initial infection row: ", 0, SIZE - 1)
    col = get_value_in_range("Initial infection col: ", 0, SIZE - 1)
    new_infections = set_infection(grid, row, col)

    # Loop the repeatedly spreads the infection from positive people
    while new_infections:
        new_infections = spread_infection(grid)

    print_grid(grid)

if __name__ == '__main__':
    main()



Graphics 程式

circle.py

import sys
import tkinter
import random

WIDTH = 800
HEIGHT = 600
RADIUS_MIN = 30
RADIUS_MAX = 100
N_CIRCLES_MAX = 10

def make_canvas():
    """
    (provided)
    Creates and returns a drawing canvas
    of the given int size with a blue border,
    ready for drawing.
    """
    top = tkinter.Tk()
    top.minsize(width=WIDTH + 10, height=HEIGHT + 10)

    canvas = tkinter.Canvas(top, width=WIDTH, height=HEIGHT)
    canvas.pack()
    canvas.xview_scroll(6, 'units')  # hack so (0, 0) works correctly
    canvas.yview_scroll(6, 'units')

    return canvas

def make_all_circles():
    canvas = make_canvas()
    # TODO your code here

def main():
    """
    random-circles.py
    Write a program that draws a random number of circles of random sizes at random positions on the canvas. Be careful to make sure that none of the drawn circles are cut off by the edge of your canvas. You are provided with the constants WIDTH/HEIGHT (the canvas width/height), RADIUS_MAX/RADIUS_MIN (the maximum/minimum radius that each random circle may have), and N_CIRCLES_MAX (the maximum number of circles that may be generated in one run of our program. Note that each run should generate between 1 and N_CIRCLES_MAX circles inclusive on both ends). 
    """
    make_all_circles()
    tkinter.mainloop()
    
if __name__ == "__main__":
    main()

circle.py

import tkinter
import time

CANVAS_HEIGHT = 100
CANVAS_WIDTH = 500

DX = 4
DY = 0
DELAY = 0.01

def main():
    canvas = make_canvas(CANVAS_WIDTH, CANVAS_HEIGHT, "News Ticker")
    pass

    canvas.mainloop()

"""
You don't need to modify code below here
"""

def get_right_x(canvas, object):
    bbox = canvas.bbox(object)
    return bbox[2]

def make_canvas(width, height, title):
    """
    Creates and returns a drawing canvas
    of the given int size with a blue border,
    ready for drawing.
    """
    top = tkinter.Tk()
    top.minsize(width=width, height=height)
    top.title(title)
    canvas = tkinter.Canvas(top, width=width + 1, height=height + 1)
    canvas.pack()
    return canvas

if __name__ == "__main__":
    main()



Animation 程式

課程提供的 graphics.py

附註:請將graphics.py 跟 Animation python程式放在同一磁碟目錄內,範例程式才能順利執行!

import random
import tkinter
import tkinter.font

"""
File: graphics.py
Authors: Chris Piech, Lisa Yan and Nick Troccoli
Version Date: August 11, 2020

This file creates a standard Tkinter Canvas (in other
words it does the work of making the window pop up for you).
You can use any function that you would normally use on
a tk canvas (https://effbot.org/tkinterbook/canvas.htm)

In addition we added a few convenience functions:
- canvas.get_mouse_x()
- canvas.get_new_mouse_clicks()
- canvas.get_new_key_presses()
- canvas.wait_for_click()
- and a few more...

You can reuse this file in future projects freely and with joy.
"""

class Canvas(tkinter.Canvas):
    """
    Canvas is a simplified interface on top of the tkinter Canvas to allow for easier manipulation of graphical objects.
    Canvas has a variety of functionality to create, modify and delete graphical objects, and also get information
    about the canvas contents.  Canvas is a subclass of `tkinter.Canvas`, so all tkinter functionality is also available
    if needed.
    """

    DEFAULT_WIDTH = 754
    """The default width of the canvas is 754."""

    DEFAULT_HEIGHT = 492
    """The default height of the canvas is 492."""

    DEFAULT_TITLE = "Canvas"
    """The default text shown in the canvas window titlebar is 'Canvas'."""

    def __init__(self, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT, title=DEFAULT_TITLE):
        """
        When creating a canvas, you can optionally specify a width and height.  If no width and height are specified,
        the canvas is initialized with its default size.

        Args:
            width: the width of the Canvas to create (or if not specified, uses `Canvas.DEFAULT_WIDTH`)
            height: the height of the Canvas to create (or if not specified, uses `Canvas.DEFAULT_HEIGHT`)
        """

        # Create the main program window
        self.main_window = tkinter.Tk()
        self.main_window.geometry("{}x{}".format(width, height))
        self.main_window.title(title)

        # call the tkinter Canvas constructor
        super().__init__(self.main_window, width=width, height=height, bd=0, highlightthickness=0)

        # Optional callbacks the client can specify to be called on each event
        self.on_mouse_pressed = None
        self.on_mouse_released = None
        self.on_key_pressed = None

        # Tracks whether the mouse is currently on top of the canvas
        self.mouse_on_canvas = False

        # List of presses not handled by a callback
        self.mouse_presses = []

        # List of key presses not handled by a callback
        self.key_presses = []

        # These are state variables so wait_for_click knows when to stop waiting and to
        # not call handlers when we are waiting for click
        self.wait_for_click_click_happened = False
        self.currently_waiting_for_click = False

        # bind events
        self.focus_set()
        self.bind("", lambda event: self.__mouse_pressed(event))
        self.bind("", lambda event: self.__mouse_released(event))
        self.bind("", lambda event: self.__key_pressed(event))
        self.bind("", lambda event: self.__mouse_entered())
        self.bind("", lambda event: self.__mouse_exited())

        self._image_gb_protection = {}
        self.pack()
        self.update()

    def set_canvas_background_color(self, color):
        """
        Sets the background color of the canvas to the specified color string.

        Args:
            color: the color (string) to make the background of the canvas.
        """
        self.config(background=color)

    def get_canvas_width(self):
        """
        Get the width of the canvas.

        Returns:
            the current width of the canvas.
        """

        return self.winfo_width()

    def get_canvas_height(self):
        """
        Get the height of the canvas.

        Returns:
            the current height of the canvas.
        """
        return self.winfo_height()

    def set_canvas_title(self, title):
        """
        Sets the title text displayed in the Canvas's window title bar to be the specified text.

        Args:
            title: the text to display in the title bar
        """
        self.main_window.title(title)

    def set_canvas_size(self, width, height):
        """
        Sets the size of the canvas and its containing window to the specified width and height.

        Args:
            width: the width to set for the canvas and containing window
            height: the height to set for the canvas and containing window
        """
        self.main_window.geometry("{}x{}".format(width, height))
        self.config(width=width, height=height)

    """ EVENT HANDLING """

    def set_on_mouse_pressed(self, callback):
        """
        Set the specified function to be called whenever the mouse is pressed.  If this function is called
        multiple times, only the last specified function is called when the mouse is pressed.

        Args:
            callback: a function to call whenever the mouse is pressed.  Must take in two parameters, which
                are the x and y coordinates (in that order) of the mouse press that just occurred.  E.g. func(x, y).  If
                this parameter is None, no function will be called when the mouse is pressed.
        """
        self.on_mouse_pressed = callback

    def set_on_mouse_released(self, callback):
        """
        Set the specified function to be called whenever the mouse is released.  If this function is called
        multiple times, only the last specified function is called when the mouse is released.

        Args:
            callback: a function to call whenever the mouse is released.  Must take in two parameters, which
                are the x and y coordinates (in that order) of the mouse release that just occurred.  E.g. func(x, y).
                If this parameter is None, no function will be called when the mouse is released.
        """
        self.on_mouse_released = callback

    def set_on_key_pressed(self, callback):
        """
        Set the specified function to be called whenever a keyboard key is pressed.  If this function is called
        multiple times, only the last specified function is called when a key is pressed.

        Args:
            callback: a function to call whenever a key is pressed.  Must take in one parameter, which
                is the text name of the key that was just pressed (e.g. 'a' for the a key, 'b' for the b key, etc).
                E.g. func(key_char).  If this parameter is None, no function will be called when a key is pressed.
        """
        self.on_key_pressed = callback

    def get_new_mouse_clicks(self):
        """
        Returns a list of all mouse clicks that have occurred since the last call to this method or any registered
        mouse handler.

        Returns:
            a list of all mouse clicks that have occurred since the last call to this method or any registered
                mouse handler.  Each mouse click contains x and y properties for the click location, e.g.
                clicks = canvas.get_new_mouse_clicks(); print(clicks[0].x).
        """
        presses = self.mouse_presses
        self.mouse_presses = []
        return presses

    def get_new_key_presses(self):
        """
        Returns a list of all key presses that have occurred since the last call to this method or any registered
        key handler.

        Returns:
            a list of all key presses that have occurred since the last call to this method or any registered
                key handler.  Each key press contains a keysym property for the key pressed, e.g.
                presses = canvas.get_new_key_presses(); print(presses[0].keysym).
        """
        presses = self.key_presses
        self.key_presses = []
        return presses

    def __mouse_pressed(self, event):
        """
        Called every time the mouse is pressed.  If we are currently waiting for a mouse click via
        wait_for_click, do nothing.  Otherwise, if we have a registered mouse press handler, call that.  Otherwise,
        append the press to the list of mouse presses to be handled later.

        Args:
            event: an object representing the mouse press that just occurred.  Assumed to have x and y properties
                containing the x and y coordinates for this mouse press.
        """
        if not self.currently_waiting_for_click and self.on_mouse_pressed:
            self.on_mouse_pressed(event.x, event.y)
        elif not self.currently_waiting_for_click:
            self.mouse_presses.append(event)

    def __mouse_released(self, event):
        """
        Called every time the mouse is released.  If we are currently waiting for a mouse click via
        wait_for_click, update our state to reflect that a click happened.  Otherwise, if we have a registered mouse
        release handler, call that.

        Args:
            event: an object representing the mouse release that just occurred.  Assumed to have x and y properties
                containing the x and y coordinates for this mouse release.
        """

        # Do this all in one go to avoid setting click happened to True,
        # then having wait for click set currently waiting to false, then we go
        if self.currently_waiting_for_click:
            self.wait_for_click_click_happened = True
            return

        self.wait_for_click_click_happened = True
        if self.on_mouse_released:
            self.on_mouse_released(event.x, event.y)

    def __key_pressed(self, event):
        """
        Called every time a keyboard key is pressed.  If we have a registered key press handler, call that.  Otherwise,
        append the key press to the list of key presses to be handled later.

        Args:
            event: an object representing the key press that just occurred.  Assumed to have a keysym property
                containing the name of this key press.
        """
        if self.on_key_pressed:
            self.on_key_pressed(event.keysym)
        else:
            self.key_presses.append(event)

    def __mouse_entered(self):
        """
        Called every time the mouse enters the canvas.  Updates the internal state to record that
        the mouse is currently on the canvas.
        """
        self.mouse_on_canvas = True

    def __mouse_exited(self):
        """
        Called every time the mouse exits the canvas.  Updates the internal state to record that
        the mouse is currently not on the canvas.
        """
        self.mouse_on_canvas = False

    def mouse_is_on_canvas(self):
        """
        Returns whether or not the mouse is currently on the canvas.

        Returns:
            True if the mouse is currently on the canvas, or False otherwise.
        """
        return self.mouse_on_canvas

    def wait_for_click(self):
        """
        Waits until a mouse click occurs, and then returns.
        """
        self.currently_waiting_for_click = True
        self.wait_for_click_click_happened = False
        while not self.wait_for_click_click_happened:
            self.update()
        self.currently_waiting_for_click = False
        self.wait_for_click_click_happened = False

    def get_mouse_x(self):
        """
        Returns the mouse's current X location on the canvas.

        Returns:
            the mouses's current X location on the canvas.
        """
        """
        Note: winfo_pointerx is absolute mouse position (to screen, not window),
              winfo_rootx is absolute window position (to screen)
        Since move takes into account relative position to window,
        we adjust this mouse_x to be relative position to window.
        """
        return self.winfo_pointerx() - self.winfo_rootx()

    def get_mouse_y(self):
        """
        Returns the mouse's current Y location on the canvas.

        Returns:
            the mouse's current Y location on the canvas.
        """
        """
        Note: winfo_pointery is absolute mouse position (to screen, not window),
              winfo_rooty is absolute window position (to screen)
        Since move takes into account relative position to window,
        we adjust this mouse_y to be relative position to window.
        """
        return self.winfo_pointery() - self.winfo_rooty()

    """ GRAPHICAL OBJECT MANIPULATION """

    def get_left_x(self, obj):
        """
        Returns the leftmost x coordinate of the specified graphical object.

        Args:
            obj: the object for which to calculate the leftmost x coordinate

        Returns:
            the leftmost x coordinate of the specified graphical object.
        """
        if self.type(obj) != "text":
            return self.coords(obj)[0]
        else:
            return self.coords(obj)[0] - self.get_width(obj) / 2

    def get_top_y(self, obj):
        """
        Returns the topmost y coordinate of the specified graphical object.

        Args:
            obj: the object for which to calculate the topmost y coordinate

        Returns:
            the topmost y coordinate of the specified graphical object.
        """
        if self.type(obj) != "text":
            return self.coords(obj)[1]
        else:
            return self.coords(obj)[1] - self.get_height(obj) / 2

    def move_to(self, obj, new_x, new_y):
        """
        Same as `Canvas.moveto`.
        """
        # Note: Implements manually due to inconsistencies on some machines of bbox vs. coord.
        old_x = self.get_left_x(obj)
        old_y = self.get_top_y(obj)
        self.move(obj, new_x - old_x, new_y - old_y)

    def moveto(self, obj, x='', y=''):
        """
        Moves the specified graphical object to the specified location, which is its bounding box's
        new upper-left corner.

        Args:
            obj: the object to move
            x: the new x coordinate of the upper-left corner for the object
            y: the new y coordinate of the upper-left corner for the object
        """
        self.move_to(obj, float(x), float(y))


    def set_hidden(self, obj, hidden):
        """
        Sets the given graphical object to be either hidden or visible on the canvas.

        Args:
            obj: the graphical object to make hidden or visible on the canvas.
            hidden: True if the object should be hidden, False if the object should be visible.
        """
        self.itemconfig(obj, state='hidden' if hidden else 'normal')


    def set_fill_color(self, obj, fill_color):
        """
        Sets the fill color of the specified graphical object.  Cannot be used to change the fill color
        of non-fillable objects such as images - throws a tkinter.TclError.

        Args:
            obj: the object for which to set the fill color
            fill_color: the color to set the fill color to be, as a string.  If this is the empty string,
                the object will be set to be not filled.
        """
        try:
            self.itemconfig(obj, fill=fill_color)
        except tkinter.TclError:
            raise tkinter.TclError("You can't set the fill color on this object")

    def set_outline_color(self, obj, outline_color):
        """
        Sets the outline color of the specified graphical object.  Cannot be used to change the outline color
        of non-outlined objects such as images or text  - throws a tkinter.TclError.

        Args:
            obj: the object for which to set the outline color
            outline_color: the color to set the outline color to be, as a string.  If this is the empty string,
                the object will be set to not have an outline.
        """
        try:
            self.itemconfig(obj, outline=outline_color)
        except tkinter.TclError as e:
            raise tkinter.TclError("You can't set the outline color on this object")

    def set_outline_width(self, obj, width):
        """
        Sets the thickness of the outline of the specified graphical object.  Cannot be used on objects
        that are not outline-able, such as images or text.

        Args:
            obj: the object for which to set the outline width
            width: the width to set the outline to be.
        """
        self.itemconfig(obj, width=width)

    def set_text(self, obj, text):
        """
        Sets the text displayed by the given text object.  Cannot be used on any non-text graphical object.

        Args:
            obj: the text object for which to set the displayed text
            text: the new text for this graphical object to display
        """
        self.itemconfig(obj, text=text)

    def get_text(self, obj):
        """
        Returns the text displayed by the given text object.  Cannot be used on any non-text graphical object.

        Args:
            obj: the text object for which to get the displayed text

        Returns:
            the text currently displayed by this graphical object.
        """
        return self.itemcget(obj, 'text')

    def set_font(self, obj, font, size):
        """
        Sets the font and size for the text displayed by the given text object.  Cannot be used on any non-text
        graphical object.

        Args:
            obj: the text object for which to set the font and size
            font: the name of the font, as a string
            size: the size of the font
        """
        self.itemconfig(obj, font=(font, size))

    def raise_to_front(self, obj):
        """
        Sends the given object to the very front of all the other objects on the canvas.

        Args:
            obj: the object to bring to the front of the objects on the canvas
        """
        self.raise_in_front_of(obj, 'all')

    def raise_in_front_of(self, obj, above):
        """
        Sets the first object to be directly in front of the second object in Z-ordering on the canvas.  In other words,
        the first object will now appear in front of the second object and all objects behind the second object,
        but behind all objects that the second object is also behind.

        Args:
            obj: the object to put in front of the second object
            above: the object to put the first object directly in front of
        """
        self.tag_raise(obj, above)

    def lower_to_back(self, obj):
        """
        Sends the given object to be behind all the other objects on the canvas

        Args:
            obj: the object to put behind all other objects on the canvas
        """
        self.lower_behind(obj, 'all')

    def lower_behind(self, obj, behind):
        """
        Sets the first object to be directly behind the second object in Z-ordering on the canvas.  In other words,
        the first object will now appear directly behind the second object and all objects in front of the
        second object, but in front of all objects that the second object is also in front of.

        Args:
            obj: the object to put in front of the second object
            behind: the object to put the first object directly behind
        """
        self.tag_lower(obj, behind)

    def create_image_with_size(self, x, y, width, height, file_path, **kwargs):
        """
        Creates an image with the specified filename at the specified position on the canvas, and resized
        to the specified width and height.

        Args:
            x: the x coordinate of the top-left corner of the image on the canvas
            y: the y coordinate of the top-left corner of the image on the canvas
            width: the width to set for the image
            height: the height to set for the image
            file_path: the path to the image file to load and display on the canvas
            kwargs: other tkinter keyword args

        Returns:
            the graphical image object that is displaying the specified image at the specified location with the
                specified size.
        """
        return self.__create_image_with_optional_size(x, y, file_path, width=width, height=height, **kwargs)

    def __create_image_with_optional_size(self, x, y, file_path, width=None, height=None, **kwargs):
        """
        Creates an image with the specified filename at the specified position on the canvas.
        Optionally specify the width and height to resize the image.

        Args:
            x: the x coordinate of the top-left corner of the image on the canvas
            y: the y coordinate of the top-left corner of the image on the canvas
            file_path: the path to the image file to load and display on the canvas
            width: optional width to include for the image.  If none, uses the width of the image file.
            height: optional height to include for the image  If none, uses the height of the image file.
            kwargs: other tkinter keyword args

        Returns:
            the graphical image object that is displaying the specified image at the specified location.
        """
        from PIL import ImageTk
        from PIL import Image
        image = Image.open(file_path)

        # Resize the image if another width and height is specified
        if width is not None and height is not None:
            image = image.resize((width, height))

        image = ImageTk.PhotoImage(image)
        img_obj = super().create_image(x, y, anchor="nw", image=image, **kwargs)
        # note: if you don't do this, the image gets garbage collected!!!
        # this introduces a memory leak which can be fixed by overloading delete
        self._image_gb_protection[img_obj] = image
        return img_obj

move_rect.py

"""
File: move_rect.py
----------------
YOUR DESCRIPTION HERE
"""

import tkinter
from graphics import Canvas

CANVAS_WIDTH = 600      # Width of drawing canvas in pixels
CANVAS_HEIGHT = 600     # Height of drawing canvas in pixels
SQUARE_SIZE = 70

def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Move Square')
    # how beautiful it is, the humble task of creating a rectangle...
    start_y = CANVAS_HEIGHT / 2 - SQUARE_SIZE / 2
    end_y = start_y + SQUARE_SIZE
    rect = canvas.create_rectangle(0, start_y, SQUARE_SIZE, end_y, fill='black')

    # TODO: make the magic happen

    canvas.mainloop()

if __name__ == '__main__':
    main()

move_rect_soln.py

"""
File: move_rect_soln.py
----------------
YOUR DESCRIPTION HERE
"""

import tkinter
import time
from graphics import Canvas

CANVAS_WIDTH = 600      # Width of drawing canvas in pixels
CANVAS_HEIGHT = 600     # Height of drawing canvas in pixels
SQUARE_SIZE = 70

def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Move Square')
    # make the rectangle...
    start_y = CANVAS_HEIGHT / 2 - SQUARE_SIZE / 2
    end_y = start_y + SQUARE_SIZE
    rect = canvas.create_rectangle(0, start_y, SQUARE_SIZE, end_y, fill='black')

    # animation loop
    while not is_past_middle(canvas, rect):
        # update the world
        canvas.move(rect, 1, 0)
        canvas.update()

        # pause
        time.sleep(1/50.) #parameter is seconds to pause.
    canvas.mainloop()

def is_past_middle(canvas, rect):
    max_x = CANVAS_WIDTH / 2 - SQUARE_SIZE / 2
    curr_x = canvas.get_left_x(rect)
    return curr_x > max_x


if __name__ == '__main__':
    main()

ball_soln.py

"""
File: ball_soln.py
----------------
YOUR DESCRIPTION HERE
"""

import tkinter
import time
from graphics import Canvas

CANVAS_WIDTH = 600      # Width of drawing canvas in pixels
CANVAS_HEIGHT = 600     # Height of drawing canvas in pixels
CHANGE_X_START = 10
CHANGE_Y_START = 7
BALL_SIZE = 70

def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Bouncing Ball')

    ball = canvas.create_oval(0, 0, BALL_SIZE, BALL_SIZE, fill='blue', outline='blue')

    dx = CHANGE_X_START
    dy = CHANGE_Y_START
    while True:
        # update world
        canvas.move(ball, dx, dy)
        if hit_left_wall(canvas, ball) or hit_right_wall(canvas, ball):
            dx *= -1
        if hit_top_wall(canvas, ball) or hit_bottom_wall(canvas, ball):
            dy *= -1
        # redraw canvas
        canvas.update()
        # pause
        time.sleep(1/50.)

def hit_left_wall(canvas, object):
    return canvas.get_left_x(object) <= 0

def hit_top_wall(canvas, object):
    return canvas.get_top_y(object) <= 0

def hit_right_wall(canvas, object):
    return canvas.get_left_x(object) + BALL_SIZE >= CANVAS_WIDTH

def hit_bottom_wall(canvas, object):
    return canvas.get_top_y(object) + BALL_SIZE >= CANVAS_HEIGHT

if __name__ == '__main__':
    main()

ball_colors.py

"""
File: ball_colors.py
----------------
YOUR DESCRIPTION HERE
"""

import tkinter
import time
import random
from graphics import Canvas

CANVAS_WIDTH = 600      # Width of drawing canvas in pixels
CANVAS_HEIGHT = 600     # Height of drawing canvas in pixels

BALL_SIZE = 70

def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Bouncing Ball')

    ball = canvas.create_oval(0, 0, BALL_SIZE, BALL_SIZE, fill='blue', outline='blue')

    dx = 10
    dy = 7
    while True:
        # update world
        canvas.move(ball, dx, dy)
        if hit_left_wall(canvas, ball) or hit_right_wall(canvas, ball):
            dx *= -1
            change_color(canvas, ball)
        if hit_top_wall(canvas, ball) or hit_bottom_wall(canvas, ball):
            dy *= -1
            change_color(canvas, ball)
        # redraw canvas
        canvas.update()
        # pause
        time.sleep(1/50.)

def change_color(canvas, shape):
    '''
    When you call this method, the provided shape will change color to a
    randomly chosen color.
    :param canvas: the canvas where the shape exists
    :param shape: the shape you want to have its color changed
    '''
    # 1. get a random color
    color = random.choice(['blue', 'salmon', 'red', 'green', 'orange', 'plum'])
    # 2. use the tkinter method to change the shape's color
    # canvas.itemconfig(object, fill=color, outline=color)  # change color
    canvas.set_fill_color(shape, color)
    canvas.set_outline_color(shape, color)

def hit_left_wall(canvas, object):
    return canvas.get_left_x(object) <= 0

def hit_top_wall(canvas, object):
    return canvas.get_top_y(object) <= 0

def hit_right_wall(canvas, object):
    return canvas.get_left_x(object) + BALL_SIZE >= CANVAS_WIDTH


def hit_bottom_wall(canvas, object):
    return canvas.get_top_y(object) + BALL_SIZE >= CANVAS_HEIGHT

if __name__ == '__main__':
    main()

pong.py

import tkinter
import time
import math
from graphics import Canvas

CANVAS_WIDTH = 600      # Width of drawing canvas in pixels
CANVAS_HEIGHT = 600     # Height of drawing canvas in pixels

PADDLE_Y = CANVAS_HEIGHT - 60
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 20
BALL_SIZE = 70

def main():
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Pong 1')

    ball = canvas.create_oval(0, 0, BALL_SIZE, BALL_SIZE, fill='red', outline='red')
    # TODO: 1. we now make a paddle
    paddle = canvas.create_rectangle(0, PADDLE_Y, PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, fill="blue")

    dx = 10
    dy = 7
    while True:
        # TODO: 2. get the mouse location and react to it
        mouse_x = canvas.get_mouse_x()
        canvas.moveto(paddle, mouse_x, PADDLE_Y)

        canvas.move(ball, dx, dy)
        if hit_left_wall(canvas, ball) or hit_right_wall(canvas, ball):
            dx *= -1
        if hit_top_wall(canvas, ball):
            dy *= -1

        # TODO: 3. check if the ball hits the paddle
        if hit_paddle(canvas, ball, paddle):
            dy = -abs(dy)
        # redraw canvas
        canvas.update()
        # pause
        time.sleep(1/50.)

def hit_paddle(canvas, ball, paddle):
    # TODO: paddle_coords is of type list. Come to lecture Monday!
    paddle_coords = canvas.coords(paddle)
    x1 = paddle_coords[0]
    y1 = paddle_coords[1]
    x2 = paddle_coords[2]
    y2 = paddle_coords[3]
    results = canvas.find_overlapping(x1, y1, x2, y2)
    return len(results) > 1

def hit_left_wall(canvas, object):
    return canvas.get_left_x(object) <= 0

def hit_top_wall(canvas, object):
    return canvas.get_top_y(object) <= 0

def hit_right_wall(canvas, object):
    return canvas.get_left_x(object) + BALL_SIZE >= CANVAS_WIDTH

def hit_bottom_wall(canvas, object):
    return canvas.get_top_y(object) + BALL_SIZE >= CANVAS_HEIGHT

if __name__ == '__main__':
    main()

1 comments

  1. Hello, 你真是太佛心來的,從你提供的這些整理,真的對Python的學習又更進一步了。前面week1 到week5的影片講解真的很清楚,實在太感謝了。
    可惜Week6後學校不開放,只能告自己摸索。不過多虧你熱心提供,還真的要謝謝你。

    不好意思,關於動畫部份,能請教你一個問題嗎?
    我在試著學動畫程式的部份時,將graphics.py放在同一個目錄下,但執行時便出現以下錯誤訊息:

    Traceback (most recent call last):
    File "D:\AI_lab\Standford\week6\MoveRect.py", line 26, in
    main()
    File "D:\AI_lab\Standford\week6\MoveRect.py", line 15, in main
    canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT, 'Move Square')
    File "D:\AI_lab\Standford\week6\graphics.py", line 82, in __init__
    self.bind("", lambda event: self.__mouse_pressed(event))
    File "C:\Users\shang\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1384, in bind
    return self._bind(('bind', self._w), sequence, func, add)
    File "C:\Users\shang\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1338, in _bind
    self.tk.call(what + (sequence, cmd))
    _tkinter.TclError: no events specified in binding

    我猜測是graphics.py在呼叫__init__.py 時找不到對應的函數。我試過用python 3.9 跟3.6 都是同樣狀況,也試著用google corelab (上面是python 3.7)執行,也是有其它錯誤訊息,可能在學校的平台上執行沒有這種情況,不知道是不是也有其它同學在自己的電腦上執行動畫程式時也有類似的情況呢?

    回覆刪除