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()
史丹佛
動畫
程式語言
資訊工程
繪圖
animation
Code in Place
Computer Science
CS 106A
graphics
list
Python
Stanford
Tech



1 comments
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)執行,也是有其它錯誤訊息,可能在學校的平台上執行沒有這種情況,不知道是不是也有其它同學在自己的電腦上執行動畫程式時也有類似的情況呢?